Administrator 8 kuukautta sitten
vanhempi
commit
3e9a93dacf

+ 54 - 0
src/api/activityRule.ts

@@ -0,0 +1,54 @@
+import request from './request'
+
+
+export default {
+
+    add(data) {
+        return request({
+            url: '/activity/rule/add',
+            method: 'post',
+            data
+        });
+    },
+
+    query(data) {
+        return request({
+            url: '/activity/rule/query',
+            method: 'post',
+            data
+        });
+    },
+
+    update(data) {
+        return request({
+            url: '/activity/rule/update',
+            method: 'put',
+            data
+        });
+    },
+
+    delete(data) {
+        return request({
+            url: '/activity/rule/delete',
+            method: 'delete',
+            data
+        });
+    },
+
+    deleteBatch(data) {
+        return request({
+            url: '/activity/rule/delete_batch',
+            method: 'delete',  
+            data
+        });
+    },
+
+    importBatch(data) {
+        return request({
+            url: '/activity/rule/import_batch',
+            method: 'post',  
+            data
+        });
+    }
+
+}

+ 54 - 0
src/api/activityTemplate.ts

@@ -0,0 +1,54 @@
+import request from './request'
+
+
+export default {
+
+    add(data) {
+        return request({
+            url: '/activity/template/add',
+            method: 'post',
+            data
+        });
+    },
+
+    query(data) {
+        return request({
+            url: '/activity/template/query',
+            method: 'post',
+            data
+        });
+    },
+
+    update(data) {
+        return request({
+            url: '/activity/template/update',
+            method: 'put',
+            data
+        });
+    },
+
+    delete(data) {
+        return request({
+            url: '/activity/template/delete',
+            method: 'delete',
+            data
+        });
+    },
+
+    deleteBatch(data) {
+        return request({
+            url: '/activity/template/delete_batch',
+            method: 'delete',  
+            data
+        });
+    },
+
+    importBatch(data) {
+        return request({
+            url: '/activity/template/import_batch',
+            method: 'post',  
+            data
+        });
+    }
+
+}

+ 2 - 0
src/routers/index.ts

@@ -21,6 +21,7 @@ import playerInfo from './modules/player'
 
 import serverRouter from './modules/server'
 import operationRouter from './modules/operation'
+import activityRouter from './modules/activity'
 
 // 异步组件
 export const asyncRoutes = [
@@ -41,6 +42,7 @@ export const asyncRoutes = [
   ...systemRouter,
   ...serverRouter,
   ...operationRouter,
+  ...activityRouter
 ]
 
 

+ 31 - 0
src/routers/modules/activity.ts

@@ -0,0 +1,31 @@
+
+/** When your routing operation is too long, you can split it into small modules**/
+
+import Layout from "@/layout/index.vue";
+
+const activityRouter = [{
+    path: '/activity',
+    component: Layout,
+    redirect: '/activity/rule',
+    name: 'activity',
+    meta: {
+        title: '运营活动',
+        icon: 'Box'
+    },
+    children: [
+        {
+            path: '/activity/rule',
+            component: () => import('@/views/activity/rule/index.vue'),
+            name: 'rule',
+            meta: { title: '活动规则', icon: 'Timer'}
+        },
+        {
+            path: '/activity/template',
+            component: () => import('@/views/activity/template/index.vue'),
+            name: 'template',
+            meta: { title: '活动模版', icon: 'Apple'}
+        }
+    ]
+}]
+
+export default activityRouter

+ 669 - 0
src/views/activity/rule/index.vue

@@ -0,0 +1,669 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <PropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="primary" @click="importByExcel"><el-icon>
+            <Upload />
+          </el-icon>导入</el-button>
+          <el-button type="primary" @click="add"><el-icon>
+              <plus />
+            </el-icon>添加</el-button>
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:type="scope">{{ getTypeLabel(scope.row.type) }}</template>
+      <template v-slot:validTime="scope">{{ dateUtils.getDate(scope.row.validTime) }}</template>
+      <template v-slot:invalidTime="scope">{{ dateUtils.getDate(scope.row.invalidTime) }}</template>
+      <template v-slot:state="scope">{{ getStateLabel(scope.row) }}</template>
+      <template v-slot:createTime="scope">{{ dateUtils.getDate(scope.row.createTime) }}</template>
+      <template v-slot:items="scope">{{ getItemsLabel(scope.row.items) }}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="More" @click="more(scope.row)">
+          详情
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </PropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="ID" prop="id" v-show="isIdShow">
+          <el-input readonly v-model="ruleForm.id" />
+        </el-form-item>
+        <el-form-item label="收件人服务器ID" prop="toServerIds">
+          <el-input v-model="ruleForm.toServerIds" placeholder="默认不填为全服,支持数组,格式:1,2,3..."/>
+        </el-form-item>
+        <el-form-item label="收件人角色ID" prop="toPlayerIds">
+          <el-input v-model="ruleForm.toPlayerIds" placeholder="指定具体角色发送,支持数组,格式:10000001,10000002..." />
+        </el-form-item>
+        <el-form-item label="收件人等级条件" prop="levelCondition">
+          <el-input v-model="ruleForm.levelCondition" placeholder="指定角色等级区间[min,max],格式:1,100" />
+        </el-form-item>
+        <el-form-item label="收件人道具条件" prop="itemCondition">
+          <el-input v-model="ruleForm.itemCondition" placeholder="指定角色拥有道具,支持数组,格式:1001-100,1002-200..." />
+        </el-form-item>
+        <el-form-item label="收件人英雄条件" prop="heroCondition">
+          <el-input v-model="ruleForm.heroCondition" placeholder="指定角色拥有英雄,支持数组,格式:101,102..."/>
+        </el-form-item>
+
+        <el-form-item label="收件人角色创建">
+          <el-col :span="11">
+            <el-form-item prop="roleCreateTimeStart">
+              <el-date-picker v-model="ruleForm.roleCreateTimeStart" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
+                placeholder="选择起始时间" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col class="text-center" :span="2" style="text-align: center">
+            <span class="text-gray-500">-</span>
+          </el-col>
+          <el-col :span="11">
+            <el-form-item prop="roleCreateTimeEnd">
+              <el-date-picker v-model="ruleForm.roleCreateTimeEnd" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
+                placeholder="选择截止时间" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-form-item>
+        <el-form-item label="邮件标题" prop="title">
+          <el-input v-model="ruleForm.title" placeholder="限制7字"/>
+        </el-form-item>
+        <el-form-item label="邮件类型" prop="type">
+          <el-radio-group v-model="ruleForm.type">
+            <el-radio :label="0">一次性(进入游戏或打开邮箱判断一次条件,满足则收到邮件)</el-radio>
+            <el-radio :label="1">常驻(邮件生效期间满足条件,都可以收到邮件)</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="发送者名称" prop="sendName">
+          <el-input v-model="ruleForm.sendName" placeholder="限制10字"/>
+        </el-form-item>
+        <el-form-item label="邮件道具" prop="items">
+          <el-input v-model="ruleForm.items" placeholder="支持数组,格式:1001-100,1002-200..."/>
+        </el-form-item>
+        <el-form-item label="邮件生效时间" required>
+          <el-col :span="7">
+            <el-form-item prop="validTime">
+              <el-date-picker v-model="ruleForm.validTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
+                placeholder="选择起始时间" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col class="text-center" :span="2" style="text-align: center">
+            <span class="text-gray-500">-</span>
+          </el-col>
+          <el-col :span="7">
+            <el-form-item prop="invalidTime">
+              <el-date-picker v-model="ruleForm.invalidTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
+                placeholder="选择截止时间" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col class="text-enpty" :span="1">
+          </el-col>
+          <el-button type="primary" @click="valid7Day">7天</el-button>
+          <el-button type="primary" @click="valid15Day">15天</el-button>
+          <el-button type="primary" @click="valid30Day">30天</el-button>
+        </el-form-item>
+        <el-form-item label="邮件内容" prop="content">
+          <el-input v-model="ruleForm.content" :rows="8" type="textarea" placeholder="限制3000字" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="importVisible" :title='title' width="70%" @close="importClose">
+      <PageWrapLayout class="m-upload-excel">
+        <el-upload style="width: 100%" ref="upload" class="upload-demo" drag action="/" :before-upload="beforeUploadAction" type="file"
+          accept=".xlsx, .xls" :limit="1" :on-remove="handleRemove">
+          <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+          <div class="el-upload__text"> 拖拽上传 <em>或者点击上传 Excel</em> </div>
+        </el-upload>
+        <div>
+          <el-table :data="tableData" border highlight-current-row style="width: 100%; margin-top: 20px">
+            <el-table-column v-for="item of tableHeader" :prop="item.value" :label="item.label" :key="item">
+            </el-table-column>
+          </el-table>
+        </div>
+      </PageWrapLayout>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="importClose">取消</el-button>
+          <el-button type="primary" @click="handleConfirm">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick, onBeforeMount } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import activityRuleApi from '@/api/activityRule'
+import dateUtils from '@/common/dateUtils'
+
+const loading = ref(true)
+const appContainer = ref(null)
+import PropTable from '@/components/Table/PropTable/index.vue'
+
+// import { ref } from 'vue'
+
+import ExcelJS from 'exceljs'
+import { UploadFilled } from '@element-plus/icons-vue'
+import type { UploadProps, UploadInstance } from 'element-plus'
+// import { ElMessage } from 'element-plus'
+const tableData = ref([])
+const tableHeader = ref([])
+const upload = ref<UploadInstance>()
+
+const tableColumn = ["title","sendName","items","validTime","invalidTime",
+  "content","toServerIds","toPlayerIds","levelCondition","roleCreateTimeStart","roleCreateTimeEnd"]
+const beforeUploadAction = (file, fileLi) => {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader()
+    reader.onload = async (e) => {
+      const data = e.target.result
+      const workbook = new ExcelJS.Workbook()
+      try {
+        let res = await workbook.xlsx.load(data)
+        const sheets =
+          res._worksheets && res._worksheets.filter((item) => typeof item !== 'undefined')
+        const table = []
+        let headers = []
+        sheets.forEach((sheet) => {
+          sheet._rows.forEach((row, index) => {
+            if (index === 0) {
+              row.values.forEach((it, i) => {
+                // console.log("string" + it + " " + i + "" + tableColumn[i])
+                headers.push({label: it, value: tableColumn[i - 1]})
+              })
+            } else {
+              let obj = {}
+              let arr = []
+              row.values.forEach((it) => {
+                arr.push(it)
+              })
+              tableColumn.forEach((ite, i) => {
+                if (arr[i] == undefined)  {
+                  obj[ite] = null
+                } else {
+                  obj[ite] = String(arr[i])
+                }
+              })
+              table.push(obj)
+            }
+            // const tableRow = {
+            //   position: "",
+            //   val: "",
+            // };
+            // row._cells.forEach((cell) => {
+            //   tableRow.position = cell._address;
+            //   tableRow.val = cell._value.model.value || "";
+            // });
+          })
+        })
+        tableData.value = table
+        tableHeader.value = headers
+        console.log("table", table)
+        console.log("headers", headers)
+      } catch (e) {
+        ElMessage.error('解析失败')
+      }
+    }
+    reader.readAsArrayBuffer(file)
+  })
+}
+
+const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
+  tableData.value = null
+  tableHeader.value = null
+}
+
+const data = ref([])
+let currPageNum = 1;
+const pageSize = 1000;
+onBeforeMount(() => {
+  loadActivityRuleInfo()
+})
+
+const loadActivityRuleInfo = () => {
+  let queryParams = reactive({
+    pageNum: currPageNum,
+    pageSize: pageSize
+  })
+  activityRuleApi.query(queryParams).then(res => {
+    data.value = res.data.result;
+    console.log(data)
+  })
+}
+
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'id', label: 'ID', width: 80 },
+  { name: 'type', label: '活动类型', inSearch: true, valueType: 'input' },
+  { name: 'actTem', label: '活动模版', inSearch: true, valueType: 'input' },
+  { name: 'serverIds', label: '生效服务器', slot: true, inSearch: true, valueType: 'input' },
+  { name: 'noServerIds', label: '排除服务器'},
+  { name: 'isOpen', label: '是否开启', slot: true},
+  { name: 'tipsTime', label: '预告时间', slot: true, valueType: 'datetime' },
+  { name: 'startTime', label: '开始时间', slot: true, valueType: 'datetime' },
+  { name: 'endTime', label: '结束时间', slot: true, valueType: 'datetime' },
+  { name: 'stayTime', label: '停留时间', slot: true, valueType: 'datetime' },
+  { name: 'state', label: '活动状态', slot: true },
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+
+const getTypeLabel = (val) => {
+  if (val == 0) {
+    return '一次性'
+  } else if (val == 1) {
+    return '常驻'
+  }
+}
+
+const getStateLabel = (val) => {
+  // 取得当前时间,单位:秒
+  const timestamp: number = Date.parse(new Date().toString());
+  if (timestamp < val.validTime) {
+    return '未发送'
+  } else if (timestamp >= val.validTime && timestamp <= val.invalidTime) {
+    return '发送中'
+  } else {
+    return '已送达'
+  }
+}
+
+const getItemsLabel = (val) => {
+  console.log("val:", val)
+  if (val != null) {
+    let label = ""
+    val.forEach((v, i) => {
+      label += v["itId"] + '-' + v["itCnt"]
+      if (i < val.length - 1) {
+        label += ","
+      }
+    });
+    return label;
+  }
+}
+
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+
+const ruleForm = reactive({
+  id: null,
+  title: null,
+  type: 0,
+  validTime: null,
+  invalidTime: null,
+  items: null,
+  sendName: '深渊联盟',
+  toServerIds: null,
+  toPlayerIds: null,
+  levelCondition: null,
+  itemCondition: null,
+  heroCondition: null,
+  roleCreateTimeStart: null,
+  roleCreateTimeEnd: null,
+  content: null,
+  createTime: null
+})
+
+const rules = reactive({
+  type: [
+    {
+      required: true,
+    }
+  ],
+  title: [
+    {
+      required: true,
+      message: '请输入邮件标题',
+      trigger: 'change',
+      
+    }, {max: 7, message: "邮件标题长度不能超过7字"}
+  ],
+  sendName: [
+    {
+      required: true,
+      message: '请输入发送者名称',
+      trigger: 'change',
+    }, {max: 10, message: "发送者名称长度不能超过10字"}
+  ],
+  // items: [
+  //   {
+  //     required: true,
+  //     message: '请输入邮件道具',
+  //     trigger: 'change',
+  //   },
+  // ],
+  validTime: [
+    {
+      required: true,
+      type: 'date',
+      message: '请选择邮件生效时间',
+      trigger: 'change',
+    },
+  ],
+  invalidTime: [
+    {
+      required: true,
+      type: 'date',
+      message: '请选择邮件失效时间',
+      trigger: 'change',
+    },
+  ],
+  content: [
+    {
+      required: true,
+      message: '请输入邮件内容',
+      trigger: 'change',
+    }, {max: 3000, message: "邮件内容长度不能超过3000字"}
+  ],
+})
+
+
+const dialogVisible = ref(false)
+const importVisible = ref(false)
+
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('详情')
+const isIdShow = ref(true)
+
+const importClose = () => {
+  importVisible.value = false
+  upload.value!.clearFiles()
+  tableData.value = null
+  tableHeader.value = null
+}
+
+const importByExcel = () => {
+  title.value = '导入'
+  importVisible.value = true
+  tableData.value = null
+  tableHeader.value = null
+}
+
+const add = () => {
+  title.value = '新增'
+  isIdShow.value = false
+  dialogVisible.value = true
+  ruleForm.id = null
+  ruleForm.type = 0
+  ruleForm.toServerIds = null
+  ruleForm.toPlayerIds = null
+  ruleForm.levelCondition = null
+  ruleForm.itemCondition = null
+  ruleForm.heroCondition = null
+  ruleForm.roleCreateTimeStart = null
+  ruleForm.roleCreateTimeEnd = null
+  ruleForm.title = null
+  ruleForm.sendName = '深渊联盟'
+  ruleForm.items = null
+  ruleForm.validTime = null
+  ruleForm.invalidTime = null
+  ruleForm.content = null
+}
+
+const valid7Day = () => {
+  let now = new Date();
+  ruleForm.validTime = dateUtils.getDate(now);
+  let invalidDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7, 
+    now.getHours(), now.getMinutes(), now.getSeconds())
+  ruleForm.invalidTime = dateUtils.getDate(invalidDate)
+}
+
+const valid15Day = () => {
+  let now = new Date();
+  ruleForm.validTime = dateUtils.getDate(now);
+  let invalidDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 15, 
+    now.getHours(), now.getMinutes(), now.getSeconds())
+  ruleForm.invalidTime = dateUtils.getDate(invalidDate)
+}
+
+const valid30Day = () => {
+  let now = new Date();
+  ruleForm.validTime = dateUtils.getDate(now);
+  let invalidDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 30, 
+    now.getHours(), now.getMinutes(), now.getSeconds())
+  ruleForm.invalidTime = dateUtils.getDate(invalidDate)
+}
+
+const handleConfirm = () => {
+  // 验证table数据
+  let isValid = true
+  tableData.value.forEach((ite) => {
+    if (ite["title"].length > 7) {
+      isValid = false
+      return ElMessage.error("邮件标题长度不能超过7字")
+    }
+    if (ite["sendName"].length > 10) {
+      isValid = false
+      return ElMessage.error("发送者名称长度不能超过10字")
+    }
+    if (ite["content"].length > 3000) {
+      isValid = false
+      return ElMessage.error("邮件内容长度不能超过3000字")
+    }      
+  })
+  if (isValid) {
+    let importParams = reactive({
+      importValue: tableData.value
+    })
+    activityRuleApi.importBatch(importParams).then(res => {
+      console.log('result', res)
+      if (res.data.code = 200) {
+        loadActivityRuleInfo()
+        importVisible.value = false
+        return ElMessage.success(res.data.msg)
+      } else {
+        return ElMessage.error(res.data.msg)
+      }
+    })
+    console.log('import submit!')
+  }
+}
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      if (title.value == '新增') {
+        activityRuleApi.add(ruleForm).then(res => {
+          console.log('result', res)
+          if (res.data.code = 200) {
+            loadActivityRuleInfo()
+            dialogVisible.value = false
+            return ElMessage.success(res.data.msg)
+          } else {
+            return ElMessage.error(res.data.msg)
+          }
+        })
+        console.log('add submit!')
+      } else {
+        // let updateParams = reactive({
+        //   id: rowObj.value.id,
+        //   updateValue: ruleForm
+        // })
+        // activityRuleApi.update(updateParams).then(res => {
+        //   if (res.data.code = 200) {
+        //     // item.state = ruleForm.state
+        //     loadactivityRuleInfo()
+        //     dialogVisible.value = false
+        //     return ElMessage.success('修改成功')
+        //   } else {
+        //     return ElMessage.error('修改失败')
+        //   }
+        // })
+        // console.log('update submit!')
+      }
+      console.log('submit!', ruleForm)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      let selectGmMaliIds = reactive([])
+      for (let value of selectObj.value) {
+        selectGmMaliIds.push(value.id)
+      }
+      let deleteParams = reactive({
+        gmMaliIds: selectGmMaliIds
+      })
+      activityRuleApi.deleteBatch(deleteParams).then(res => {
+        loadActivityRuleInfo()
+        ElMessage.success('批量删除成功')
+      })
+
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+
+
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const more = (row) => {
+  title.value = '详情'
+  isIdShow.value = true
+  rowObj.value = row
+  console.log(row)
+  dialogVisible.value = true
+  ruleForm.id = row.id
+  ruleForm.type = row.type
+  ruleForm.toServerIds = row.toServerIds
+  ruleForm.toPlayerIds = row.toPlayerIds
+  ruleForm.levelCondition = row.levelCondition
+  ruleForm.itemCondition = getItemsLabel(row.itemCondition)
+  ruleForm.heroCondition = row.heroCondition
+  ruleForm.roleCreateTimeStart = dateUtils.getDate(row.roleCreateTimeStart)
+  ruleForm.roleCreateTimeEnd = dateUtils.getDate(row.roleCreateTimeEnd)
+  ruleForm.title = row.title
+  ruleForm.sendName = row.sendName
+  ruleForm.items = getItemsLabel(row.items)
+  ruleForm.validTime = dateUtils.getDate(row.validTime)
+  ruleForm.invalidTime = dateUtils.getDate(row.invalidTime)
+  ruleForm.content = row.content
+  console.log(ruleForm)
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      let deleteParams = reactive({
+        id: row.id
+      })
+      activityRuleApi.delete(deleteParams).then(res => {
+        loadActivityRuleInfo()
+        ElMessage.success('删除成功')
+      })
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loadActivityRuleInfo()
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('重置成功')
+}
+
+const onSubmit = (val) => {
+  if (val.title == null || val.title == "") {
+    ElMessage.warning('请输入查询条件')
+    return
+  }
+  let queryParams = reactive({
+    pageNum: 1,
+    pageSize: pageSize,
+    condition: val
+  })
+  activityRuleApi.query(queryParams).then(res => {
+    data.value = res.data.result;
+    console.log(data.value)
+  })
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+
+.m-upload-excel {
+  .el-upload {
+    width: 100%;
+  }
+
+  .el-upload-dragger {
+    width: 100%;
+  }
+}
+</style>

+ 481 - 0
src/views/activity/template/index.vue

@@ -0,0 +1,481 @@
+<template>
+  <div class="app-container" ref="appContainer">
+    <PropTable :loading="loading" @selection-change="selectionChange" :columns="column" :data="list" @reset="reset"
+      @onSubmit="onSubmit">
+      <template v-slot:btn>
+        <div style="display: flex; justify-content: flex-end">
+          <el-button type="primary" @click="importByExcel"><el-icon>
+            <Upload />
+          </el-icon>导入</el-button>
+          <el-button type="primary" @click="add"><el-icon>
+              <plus />
+            </el-icon>添加</el-button>
+          <el-button type="danger" @click="batchDelete"><el-icon>
+              <delete />
+            </el-icon>删除</el-button>
+        </div>
+      </template>
+      <template v-slot:type="scope">{{ getIsOpenSvrLabel(scope.row.isOpenSvr) }}</template>
+      <template v-slot:operation="scope">
+        <el-button type="primary" size="small" icon="Edit" @click="edit(scope.row)">
+          编辑
+        </el-button>
+        <el-button @click="del(scope.row)" type="danger" size="small" icon="Delete">
+          删除
+        </el-button>
+      </template>
+    </PropTable>
+
+    <el-dialog v-model="dialogVisible" :title='title' width="50%">
+      <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm"
+        :size="formSize">
+        <el-form-item label="ID" prop="id">
+          <el-input v-model="ruleForm.id" type="number"/>
+        </el-form-item>
+        <el-form-item label="模版名称" prop="name">
+          <el-input v-model="ruleForm.name" />
+        </el-form-item>
+        <el-form-item label="模版描述" prop="des">
+          <el-input v-model="ruleForm.des" />
+        </el-form-item>
+        <el-form-item label="模版奖励" prop="reward">
+          <el-input v-model="ruleForm.reward" />
+        </el-form-item>
+        <el-form-item label="模版备注" prop="remark">
+          <el-input v-model="ruleForm.remark" />
+        </el-form-item>
+        <el-form-item label="持续天数" prop="keepDay">
+          <el-input v-model="ruleForm.keepDay" type="number"/>
+        </el-form-item>
+        <el-form-item label="停留天数" prop="stayDay">
+          <el-input v-model="ruleForm.stayDay" type="number"/>
+        </el-form-item>       
+        <el-form-item label="开服活动" prop="isOpenSvr">
+          <el-radio-group v-model="ruleForm.isOpenSvr">
+            <el-radio :label="0">否</el-radio>
+            <el-radio :label="1">是</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleClose(ruleFormRef)">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="importVisible" :title='title' width="70%" @close="importClose">
+      <PageWrapLayout class="m-upload-excel">
+        <el-upload style="width: 100%" ref="upload" class="upload-demo" drag action="/" :before-upload="beforeUploadAction" type="file"
+          accept=".xlsx, .xls" :limit="1" :on-remove="handleRemove">
+          <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+          <div class="el-upload__text"> 拖拽上传 <em>或者点击上传 Excel</em> </div>
+        </el-upload>
+        <div>
+          <el-table :data="tableData" border highlight-current-row style="width: 100%; margin-top: 20px">
+            <el-table-column v-for="item of tableHeader" :prop="item.value" :label="item.label" :key="item">
+            </el-table-column>
+          </el-table>
+        </div>
+      </PageWrapLayout>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="importClose">取消</el-button>
+          <el-button type="primary" @click="handleConfirm">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+  </div>
+</template>
+<script lang="ts" setup name="comprehensive">
+import { ref, reactive, onMounted, nextTick, onBeforeMount } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { FormInstance } from 'element-plus'
+import activityTemplateApi from '@/api/activityTemplate'
+import dateUtils from '@/common/dateUtils'
+
+const loading = ref(true)
+const appContainer = ref(null)
+import PropTable from '@/components/Table/PropTable/index.vue'
+
+// import { ref } from 'vue'
+
+import ExcelJS from 'exceljs'
+import { UploadFilled } from '@element-plus/icons-vue'
+import type { UploadProps, UploadInstance } from 'element-plus'
+// import { ElMessage } from 'element-plus'
+const tableData = ref([])
+const tableHeader = ref([])
+const upload = ref<UploadInstance>()
+
+const tableColumn = ["id","name","des","reward","remark","keepDay","stayDay","isOpenSvr"]
+const beforeUploadAction = (file, fileLi) => {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader()
+    reader.onload = async (e) => {
+      const data = e.target.result
+      const workbook = new ExcelJS.Workbook()
+      try {
+        let res = await workbook.xlsx.load(data)
+        const sheets =
+          res._worksheets && res._worksheets.filter((item) => typeof item !== 'undefined')
+        const table = []
+        let headers = []
+        sheets.forEach((sheet) => {
+          sheet._rows.forEach((row, index) => {
+            if (index === 0) {
+              row.values.forEach((it, i) => {
+                // console.log("string" + it + " " + i + "" + tableColumn[i])
+                headers.push({label: it, value: tableColumn[i - 1]})
+              })
+            } else {
+              let obj = {}
+              let arr = []
+              row.values.forEach((it) => {
+                arr.push(it)
+              })
+              tableColumn.forEach((ite, i) => {
+                if (arr[i] == undefined)  {
+                  obj[ite] = null
+                } else {
+                  obj[ite] = arr[i]
+                }
+              })
+              table.push(obj)
+            }
+            // const tableRow = {
+            //   position: "",
+            //   val: "",
+            // };
+            // row._cells.forEach((cell) => {
+            //   tableRow.position = cell._address;
+            //   tableRow.val = cell._value.model.value || "";
+            // });
+          })
+        })
+        tableData.value = table
+        tableHeader.value = headers
+        console.log("table", table)
+        console.log("headers", headers)
+      } catch (e) {
+        ElMessage.error('解析失败')
+      }
+    }
+    reader.readAsArrayBuffer(file)
+  })
+}
+
+const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
+  tableData.value = null
+  tableHeader.value = null
+}
+
+const data = ref([])
+let currPageNum = 1;
+const pageSize = 1000;
+onBeforeMount(() => {
+  loadActivityTemplateInfo()
+})
+
+const loadActivityTemplateInfo = () => {
+  let queryParams = reactive({
+    pageNum: currPageNum,
+    pageSize: pageSize
+  })
+  activityTemplateApi.query(queryParams).then(res => {
+    data.value = res.data.result;
+    console.log(data)
+  })
+}
+
+const column = [
+  { type: 'selection', width: 60, fixed: 'left' },
+  { name: 'id', label: 'ID', width: 80 },
+  { name: 'name', label: '模版名称', inSearch: true, valueType: 'input' },
+  { name: 'des', label: '模版描述'},
+  { name: 'reward', label: '模版奖励'},
+  { name: 'remark', label: '模版备注'},
+  { name: 'keepDay', label: '持续天数'},
+  { name: 'stayDay', label: '停留天数'},
+  { name: 'isOpenSvr', label: '开服活动', slot: true},
+  { name: 'operation', slot: true, fixed: 'right', width: 200, label: '操作' },
+]
+
+const getIsOpenSvrLabel = (val) => {
+  if (val == 0) {
+    return '否'
+  } else if (val == 1) {
+    return '是'
+  }
+}
+
+const list = ref(data)
+
+const formSize = ref('default')
+const ruleFormRef = ref<FormInstance>()
+
+const ruleForm = reactive({
+    id: null,
+    name: null,
+    des: null,
+    reward: null,
+    remark: null,
+    keepDay: null,
+    stayDay: null,
+    isOpenSvr:0
+})
+
+const rules = reactive({
+  name: [
+    {
+      required: true,
+      message: '请输入名称',
+      trigger: 'change',
+    },
+  ],
+  keepDay: [
+    {
+      required: true,
+      message: '请输入持续天数',
+      trigger: 'change',
+    },
+  ]
+})
+
+
+const dialogVisible = ref(false)
+const importVisible = ref(false)
+
+const rowObj = ref({})
+const selectObj = ref([])
+const title = ref('详情')
+
+const importClose = () => {
+  importVisible.value = false
+  upload.value!.clearFiles()
+  tableData.value = null
+  tableHeader.value = null
+}
+
+const importByExcel = () => {
+  title.value = '导入'
+  importVisible.value = true
+  tableData.value = null
+  tableHeader.value = null
+}
+
+const add = () => {
+  title.value = '新增'
+  dialogVisible.value = true
+  ruleForm.id = null
+  ruleForm.name = null
+  ruleForm.des = null
+  ruleForm.reward = null
+  ruleForm.remark = null
+  ruleForm.keepDay = null
+  ruleForm.stayDay = null
+  ruleForm.isOpenSvr = 0
+}
+
+const handleConfirm = () => {
+  // 验证table数据
+  // ...
+  let importParams = reactive({
+    importValue: tableData.value
+  })
+  activityTemplateApi.importBatch(importParams).then(res => {
+    console.log('result', res)
+    if (res.data.code = 200) {
+      loadActivityTemplateInfo()
+      importVisible.value = false
+      return ElMessage.success(res.data.msg)
+    } else {
+      return ElMessage.error(res.data.msg)
+    }
+  })
+  console.log('import submit!')
+}
+
+const handleClose = async (done: () => void) => {
+  await ruleFormRef.value.validate((valid, fields) => {
+    if (valid) {
+      if (title.value == '新增') {
+        activityTemplateApi.add(ruleForm).then(res => {
+          console.log('result', res)
+          if (res.data.code = 200) {
+            loadActivityTemplateInfo()
+            dialogVisible.value = false
+            return ElMessage.success(res.data.msg)
+          } else {
+            return ElMessage.error(res.data.msg)
+          }
+        })
+        console.log('add submit!')
+      } else {
+        // let updateParams = reactive({
+        //   id: rowObj.value.id,
+        //   updateValue: ruleForm
+        // })
+        activityTemplateApi.update(ruleForm).then(res => {
+          if (res.data.code = 200) {
+            loadActivityTemplateInfo()
+            dialogVisible.value = false
+            return ElMessage.success('修改成功')
+          } else {
+            return ElMessage.error('修改失败')
+          }
+        })
+        console.log('update submit!')
+      }
+      console.log('submit!', ruleForm)
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+const batchDelete = () => {
+  if (!selectObj.value.length) {
+    return ElMessage.error('未选中任何行')
+  }
+  ElMessageBox.confirm('你确定要删除选中项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      let selectactivityTemplateIds = reactive([])
+      for (let value of selectObj.value) {
+        selectactivityTemplateIds.push(value.id)
+      }
+      let deleteParams = reactive({
+        activityTemplateIds: selectactivityTemplateIds
+      })
+      activityTemplateApi.deleteBatch(deleteParams).then(res => {
+        loadActivityTemplateInfo()
+        ElMessage.success('批量删除成功')
+      })
+
+      list.value = list.value.concat([])
+    })
+    .catch(() => { })
+}
+
+
+const selectionChange = (val) => {
+  selectObj.value = val
+}
+
+const edit = (row) => {
+  title.value = '编辑'
+  rowObj.value = row
+  dialogVisible.value = true
+  ruleForm.id = row.id
+  ruleForm.name = row.name
+  ruleForm.des = row.des
+  ruleForm.reward = row.reward
+  ruleForm.remark = row.remark
+  ruleForm.keepDay = row.keepDay
+  ruleForm.stayDay = row.stayDay
+  ruleForm.isOpenSvr = row.isOpenSvr
+}
+
+const del = (row) => {
+  console.log('row==', row)
+  ElMessageBox.confirm('你确定要删除当前项吗?', '温馨提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+    draggable: true,
+  })
+    .then(() => {
+      let deleteParams = reactive({
+        id: row.id
+      })
+      activityTemplateApi.delete(deleteParams).then(res => {
+        loadActivityTemplateInfo()
+        ElMessage.success('删除成功')
+      })
+      loading.value = true
+      setTimeout(() => {
+        loading.value = false
+      }, 500)
+    })
+    .catch(() => { })
+}
+
+const reset = () => {
+  loadActivityTemplateInfo()
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+  ElMessage.success('重置成功')
+}
+
+const onSubmit = (val) => {
+  if ((val.title == null || val.title == "") && val.type == null) {
+    ElMessage.warning('请输入查询条件')
+    return
+  }
+  let queryParams = reactive({
+    pageNum: 1,
+    pageSize: pageSize,
+    condition: val
+  })
+  activityTemplateApi.query(queryParams).then(res => {
+    data.value = res.data.result;
+    console.log(data.value)
+  })
+  loading.value = true
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+}
+
+const getHeight = () => {
+
+}
+
+onMounted(() => {
+  nextTick(() => {
+    // let data = appContainer.value.
+  })
+  setTimeout(() => {
+    loading.value = false
+  }, 500)
+})
+</script>
+
+<style scoped>
+.edit-input {
+  padding-right: 100px;
+}
+
+.app-container {
+  flex: 1;
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+}
+
+.cancel-btn {
+  position: absolute;
+  right: 15px;
+  top: 10px;
+}
+
+.m-upload-excel {
+  .el-upload {
+    width: 100%;
+  }
+
+  .el-upload-dragger {
+    width: 100%;
+  }
+}
+</style>

+ 22 - 6
src/views/operation/announcement/index.vue

@@ -40,8 +40,9 @@
         </el-form-item>
         <el-form-item label="公共类型" prop="type">
           <el-radio-group v-model="ruleForm.type">
-            <el-radio :label="0">运营公告</el-radio>
-            <el-radio :label="1">维护更新</el-radio>
+            <el-radio :label="0">维护更新</el-radio>
+            <el-radio :label="1">游戏公告</el-radio>
+            <el-radio :label="2">运营公告</el-radio>
           </el-radio-group>
         </el-form-item>
         <el-form-item label="公告生效时间" required>
@@ -70,6 +71,12 @@
           <el-button type="primary" @click="valid15Day">15天</el-button>
           <el-button type="primary" @click="valid30Day">30天</el-button>
         </el-form-item>
+        <el-form-item label="能否跳过" prop="isCanSkip">
+          <el-radio-group v-model="ruleForm.isCanSkip">
+            <el-radio :label="0">否</el-radio>
+            <el-radio :label="1">能</el-radio>
+          </el-radio-group>
+        </el-form-item>
         <el-form-item label="公告内容" prop="content">
           <el-input v-model="ruleForm.content" :rows="10" type="textarea" />
         </el-form-item>
@@ -215,11 +222,15 @@ const column = [
     options: [
       {
         value: 0,
-        label: '运营公告',
+        label: '维护更新',
       },
       {
         value: 1,
-        label: '维护更新',
+        label: '游戏公告',
+      },
+      {
+        value: 2,
+        label: '运营公告',
       }
     ],
     valueType: 'select',},
@@ -232,9 +243,11 @@ const column = [
 
 const getTypeLabel = (val) => {
   if (val == 0) {
-    return '运营公告'
-  } else if (val == 1) {
     return '维护更新'
+  } else if (val == 1) {
+    return '游戏公告'
+  } else if (val == 2) {
+    return '运营公告'
   }
 }
 
@@ -250,6 +263,7 @@ const ruleForm = reactive({
     validTime: null,
     invalidTime: null,
     content: null,
+    isCanSkip:null
 })
 
 const rules = reactive({
@@ -325,6 +339,7 @@ const add = () => {
   ruleForm.validTime = null
   ruleForm.invalidTime = null
   ruleForm.content = null
+  ruleForm.isCanSkip = 0
 }
 
 const valid7Day = () => {
@@ -452,6 +467,7 @@ const edit = (row) => {
   ruleForm.validTime = dateUtils.getDate(row.validTime)
   ruleForm.invalidTime = dateUtils.getDate(row.invalidTime)
   ruleForm.content = row.content
+  ruleForm.isCanSkip = row.isCanSkip
 }
 
 const del = (row) => {