Parcourir la source

更新侧边栏生成逻辑,更新路由表,更新事件管理页面结构,更新事件管理总览页面,更新事件编辑页面

fxs il y a 9 mois
Parent
commit
c97be8aa04

+ 7 - 0
components.d.ts

@@ -7,6 +7,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    Dialog: typeof import('./src/components/common/Dialog.vue')['default']
     DropDownSelection: typeof import('./src/components/dataAnalysis/DropDownSelection.vue')['default']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
@@ -14,6 +15,9 @@ declare module 'vue' {
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDivider: typeof import('element-plus/es')['ElDivider']
+    ElDropdown: typeof import('element-plus/es')['ElDropdown']
+    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
+    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElIcon: typeof import('element-plus/es')['ElIcon']
@@ -26,6 +30,7 @@ declare module 'vue' {
     ElPopover: typeof import('element-plus/es')['ElPopover']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
@@ -34,12 +39,14 @@ declare module 'vue' {
     ElText: typeof import('element-plus/es')['ElText']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     FilterPopover: typeof import('./src/components/toolsBtn/FilterPopover.vue')['default']
+    Form: typeof import('./src/components/form/Form.vue')['default']
     HeaderCard: typeof import('./src/components/dataAnalysis/HeaderCard.vue')['default']
     IconIcBaselineVisibility: typeof import('~icons/ic/baseline-visibility')['default']
     IconIcBaselineVisibilityOff: typeof import('~icons/ic/baseline-visibility-off')['default']
     IconIconParkGameThree: typeof import('~icons/icon-park/game-three')['default']
     IconMaterialSymbolsLightLogout: typeof import('~icons/material-symbols-light/logout')['default']
     IconMdiPassword: typeof import('~icons/mdi/password')['default']
+    IconTablerPointFilled: typeof import('~icons/tabler/point-filled')['default']
     MyButton: typeof import('./src/components/form/MyButton.vue')['default']
     MyInput: typeof import('./src/components/form/MyInput.vue')['default']
     RegreshBtn: typeof import('./src/components/toolsBtn/RegreshBtn.vue')['default']

+ 11 - 0
package-lock.json

@@ -28,6 +28,7 @@
         "@iconify-json/logos": "^1.1.44",
         "@iconify-json/material-symbols-light": "^1.1.28",
         "@iconify-json/mdi": "^1.1.68",
+        "@iconify-json/tabler": "^1.2.0",
         "@rushstack/eslint-patch": "^1.8.0",
         "@tsconfig/node20": "^20.1.4",
         "@types/node": "^20.14.5",
@@ -751,6 +752,16 @@
         "@iconify/types": "*"
       }
     },
+    "node_modules/@iconify-json/tabler": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/@iconify-json/tabler/-/tabler-1.2.0.tgz",
+      "integrity": "sha512-ke7ESt/wurkWr/VrMrAguN8jOmf8tD9LOOqhn3OwAx8kg0lYwMkOahV6jTGXh2zU90z1drsD34cKV7RoNe0PuQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@iconify/types": "*"
+      }
+    },
     "node_modules/@iconify/types": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz",

+ 1 - 0
package.json

@@ -33,6 +33,7 @@
     "@iconify-json/logos": "^1.1.44",
     "@iconify-json/material-symbols-light": "^1.1.28",
     "@iconify-json/mdi": "^1.1.68",
+    "@iconify-json/tabler": "^1.2.0",
     "@rushstack/eslint-patch": "^1.8.0",
     "@tsconfig/node20": "^20.1.4",
     "@types/node": "^20.14.5",

+ 24 - 45
src/App.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:06:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-03 18:22:47
+ * @LastEditTime: 2024-09-04 10:19:43
  * @FilePath: \Game-Backstage-Management-System\src\App.vue
  * @Description: 
  * 
@@ -10,7 +10,8 @@
 <script setup lang="ts">
 import { zhCn } from 'element-plus/es/locales.mjs'
 import { RouterView } from 'vue-router'
-import { onMounted, reactive, ref, nextTick } from 'vue'
+import { onMounted, reactive, ref, computed } from 'vue'
+import { useRoute } from 'vue-router'
 import { ElMessage } from 'element-plus'
 import { getAllGameInfo } from '@/utils/table/table'
 import router from '@/router'
@@ -19,13 +20,22 @@ import DropDownSelection from '@/components/dataAnalysis/DropDownSelection.vue'
 import { useCommonStore } from '@/stores/useCommon'
 import { initLoadResouce } from '@/utils/resource'
 
+const route = useRoute()
 const { selectInfo } = useCommonStore()
 const isCollapse = ref(false)
 const navBarSelect = ref<string>('Home')
 const siderBarOpened = ref<Array<string>>(['数据总览'])
 const siderBar = ref()
+
+// 路由信息,同时也是侧边栏生成的依据信息
 const menuList = reactive<Array<any>>([])
-const defaultActive = ref<string>('0')
+
+// 默认选中
+const defaultActive = computed(() => {
+  return route.meta.activeMenu
+})
+
+// 顶部导航栏信息
 const navBarMenuList = [
   {
     name: 'Home',
@@ -87,7 +97,7 @@ const changeNavBar = (val: string) => {
 
   router.push(`/${val}`)
   createdMenuList()
-
+  console.log(menuList)
   let title = navBarMenuList.find((item) => item.name === val)?.title
   if (title) {
     siderBarOpened.value.splice(0, 1, title)
@@ -119,21 +129,20 @@ const resourceInfo: Record<string, string> = {
 // 使用blob的资源路径信息
 const blobUrlInfo = reactive<Record<string, string>>({})
 
+// 侧边栏跳转路由的基本路由
 const basePath = ref<string | undefined>()
 
 /**
  * @description: 创建侧边栏menu
- * @param {*} let
  * @return {*}
  */
 const createdMenuList = () => {
-  let routes = router.options.routes
+  let routes = router.options.routes // 获取路由信息
   let activeMenu = routes.find((item) => {
-    return item.name === navBarSelect.value
+    return item.name === navBarSelect.value // 根据顶部导航栏的选中情况来选择选中哪个具体的路由信息,可以打印自己看一下
   })
-  basePath.value = activeMenu?.path
-  menuList.splice(0, menuList.length, ...(activeMenu?.children as Array<any>))
-  defaultActive.value = '0' // 仍有问题
+  basePath.value = activeMenu?.path // 找到需要激活的菜单的路由,后续用来拼接需要跳转的路由
+  menuList.splice(0, menuList.length, ...(activeMenu?.children as Array<any>)) // 清空原来的路由信息,并且加入新选中的
 }
 
 onMounted(() => {
@@ -166,6 +175,7 @@ onMounted(() => {
             @change-select="changeGame"
           ></DropDownSelection>
         </div>
+        <!-- 顶部导航栏 -->
         <div class="navBarMenu">
           <el-menu
             :default-active="navBarSelect"
@@ -195,7 +205,7 @@ onMounted(() => {
           </el-popover>
         </div>
       </div>
-
+      <!-- 侧边栏 -->
       <div class="sideBarBox">
         <el-menu
           :default-active="defaultActive"
@@ -209,14 +219,13 @@ onMounted(() => {
                 <el-icon><component :is="item.icon"></component></el-icon>
                 <span>{{ item.cnName }}</span>
               </template>
-              <!-- :to="{ name: val.name }" -->
               <router-link
                 style="text-decoration: none"
-                v-for="(val, subIndex) in item.children"
+                v-for="val in item.children"
                 :to="{ path: basePath + '/' + item.path + '/' + val.path }"
                 :key="index"
               >
-                <el-menu-item :index="index + '-' + subIndex">{{ val.cnName }}</el-menu-item>
+                <el-menu-item :index="val.path">{{ val.cnName }}</el-menu-item>
               </router-link>
             </el-sub-menu>
 
@@ -226,7 +235,7 @@ onMounted(() => {
               :to="{ path: basePath + '/' + item.path }"
               :key="index"
             >
-              <el-menu-item :index="`${index}`">
+              <el-menu-item :index="item.path">
                 <template #title>
                   <el-icon><component :is="item.icon" /></el-icon>
                   <span class="menuTitle">{{ item.cnName }}</span>
@@ -240,36 +249,6 @@ onMounted(() => {
         </el-menu>
       </div>
 
-      <!-- <div class="sideBarBox">
-        <el-menu
-          :router="true"
-          :default-active="$route.name"
-          class="sideBar"
-          :collapse="isCollapse"
-          ref="siderBar"
-        >
-          <template v-for="item in menuInfo[navBarSelect]">
-            <el-sub-menu v-if="item.children" :index="item.title">
-              <template #title>
-                <el-icon><component :is="item.icon" /></el-icon>
-                <span class="menuTitle">{{ item.title }}</span>
-              </template>
-              <el-menu-item v-for="v in item.children" :index="v.pathName">{{
-                v.title
-              }}</el-menu-item>
-            </el-sub-menu>
-            <el-menu-item v-else :index="item.pathName">
-              <template #title>
-                <el-icon><component :is="item.icon" /></el-icon>
-                <span class="menuTitle">{{ item.title }}</span>
-              </template>
-            </el-menu-item>
-          </template>
-          <div class="sideBarFold" @click="changeCollapse">
-            <el-icon :size="25"><Fold /></el-icon>
-          </div>
-        </el-menu>
-      </div> -->
       <div class="content">
         <router-view v-slot="{ Component, route }">
           <keep-alive>

+ 44 - 2
src/components/Table.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 18:16:18
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-03 14:11:01
+ * @LastEditTime: 2024-09-04 18:00:51
  * @FilePath: \Game-Backstage-Management-System\src\components\Table.vue
  * @Description: 
  * 
@@ -432,7 +432,9 @@ onMounted(() => {
           <el-table-column
             :prop="item.name"
             :label="item.cnName"
-            width="auto"
+            :min-width="
+              item.specialEffect?.type === FieldSpecialEffectType.DROPDOWN ? '170px' : '100px'
+            "
             align="center"
             show-overflow-tooltip
             v-if="item.isShow"
@@ -480,9 +482,49 @@ onMounted(() => {
 
               <!-- 翻译类 -->
               <el-text v-else-if="item.specialEffect?.type === FieldSpecialEffectType.TRANSLATE">
+                <el-icon
+                  v-if="item.specialEffect.othnerInfo.icon"
+                  style="padding-right: 8px"
+                  :color="scope.row[item.name] ? '#409EFF' : '#F56C6C'"
+                  ><icon-tabler-point-filled></icon-tabler-point-filled
+                ></el-icon>
                 {{ item.specialEffect.othnerInfo.translateText[scope.row[item.name]] }}
               </el-text>
 
+              <!-- 开关类 -->
+              <el-switch
+                v-else-if="item.specialEffect?.type === FieldSpecialEffectType.SWITCH"
+                v-model="scope.row[item.name]"
+                size="default"
+              ></el-switch>
+
+              <!-- 下拉菜单类 -->
+              <el-dropdown
+                trigger="click"
+                v-else-if="item.specialEffect?.type === FieldSpecialEffectType.DROPDOWN"
+              >
+                <span
+                  class="el-dropdown-link"
+                  style="display: flex; align-items: center; cursor: pointer"
+                >
+                  <el-icon
+                    style="padding-right: 8px"
+                    :color="scope.row[item.name] ? '#409EFF' : '#F56C6C'"
+                    ><icon-tabler-point-filled></icon-tabler-point-filled></el-icon
+                  >{{
+                    scope.row[item.name]
+                      ? item.specialEffect.othnerInfo.text[0]
+                      : item.specialEffect.othnerInfo.text[1]
+                  }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
+                </span>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item :command="{ value: true }">使用中</el-dropdown-item>
+                    <el-dropdown-item :command="{ value: false }">已弃用</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+
               <el-text v-else>
                 <!-- 其他列按默认方式显示 -->
 

+ 93 - 0
src/components/common/Dialog.vue

@@ -0,0 +1,93 @@
+<!--
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-09-04 11:21:05
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-09-04 18:21:52
+ * @FilePath: \Game-Backstage-Management-System\src\components\common\Dialog.vue
+ * @Description: 
+ * 
+-->
+
+<script setup lang="ts">
+import Form from '../form/Form.vue'
+import { useDialog } from '@/hooks/useDialog'
+
+import { onMounted, reactive, ref } from 'vue'
+
+import type { DialogConfig } from '@/types/dialog'
+
+interface DialogProps {
+  config: DialogConfig
+}
+
+// 临时用的close
+const { dialogClose2 } = useDialog()
+
+// 对话框ref
+const dialogFormRef = ref<InstanceType<typeof Form>>()
+
+// props
+const props = withDefaults(defineProps<DialogProps>(), {})
+
+// props中的关于配置的信息
+const dialogConfigInfo = props.config
+
+// 游戏配置对话框设置
+const dialogConfig = reactive({
+  dialogVisible: false,
+  title: '',
+  formLabelWidth: '150px',
+  type: 0 // 0 是新增 1是修改
+})
+
+// 游戏配置提交
+const submiteGameChange = () => {
+  dialogFormRef.value?.submitFormData()
+}
+
+// 改变显示状态
+const changeVisable = () => {
+  dialogConfig.dialogVisible = !dialogConfig.dialogVisible
+}
+
+// 表单关闭
+const closeDialog = () => {
+  dialogClose2(dialogFormRef.value, dialogConfig)
+}
+
+onMounted(() => {
+  dialogConfig.title = dialogConfigInfo.title
+})
+
+defineExpose({
+  changeVisable
+})
+</script>
+
+<template>
+  <div class="dialog">
+    <el-dialog
+      @close="closeDialog"
+      v-model="dialogConfig.dialogVisible"
+      :title="dialogConfig.title"
+      style="width: 40vw"
+    >
+      <Form
+        ref="dialogFormRef"
+        :config="{
+          fieldsInfo: dialogConfigInfo.fieldsInfo,
+          reqConfig: dialogConfigInfo.reqConfig,
+          rules: dialogConfigInfo.rules
+        }"
+      ></Form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submiteGameChange()"> 确认 </el-button>
+          <el-button @click="closeDialog">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style scoped></style>

+ 113 - 0
src/components/form/Form.vue

@@ -0,0 +1,113 @@
+<script setup lang="ts">
+import type { FormInstance } from 'element-plus'
+import type { FormConfig } from '@/types/form'
+
+import { FormFieldType } from '@/types/form'
+import { reactive, onMounted, ref, nextTick } from 'vue'
+import { clearReactiveData } from '@/utils/common'
+import { useForm } from '@/hooks/useForm'
+
+const { submitForm } = useForm()
+interface FormProp {
+  inline?: boolean
+  config: FormConfig
+}
+
+// 表单ref
+const formRef = ref<FormInstance>()
+
+// props
+const props = withDefaults(defineProps<FormProp>(), {
+  inline: false
+})
+
+// 表单的数据
+const formData = reactive<Record<string, any>>({})
+
+// 拿到表单的配置
+const formConfig = props.config
+
+/**
+ * @description: 表单数据提交
+ * @return {*}
+ */
+const submitFormData = () => {
+  submitForm(formRef.value, formConfig.reqConfig.url, formData)
+}
+
+/**
+ * @description: 重置表单
+ * @return {*}
+ */
+const resetForm = () => {
+  formRef.value?.resetFields()
+}
+
+onMounted(() => {
+  nextTick(() => {
+    clearReactiveData(formData)
+  })
+})
+
+defineExpose({
+  submitFormData,
+  resetForm
+})
+</script>
+
+<template>
+  <div class="formBox">
+    <el-form
+      class="form"
+      :inline="props.inline"
+      :label-position="props.inline ? 'top' : 'left'"
+      :rules="formConfig.rules"
+      :model="formData"
+      ref="formRef"
+      :label-width="'120px'"
+    >
+      <template v-for="item in formConfig.fieldsInfo">
+        <el-form-item
+          :prop="item.name"
+          :label="item.cnName"
+          style="margin-right: 150px; margin-bottom: 20px"
+        >
+          <el-input
+            style="width: 300px"
+            v-if="item.type === FormFieldType.INPUT"
+            v-model="formData[item.name]"
+            autocomplete="off"
+          />
+          <el-select
+            style="width: 300px"
+            v-if="item.type === FormFieldType.SELECT && item.otherOptions?.options"
+            v-model="formData[item.name]"
+            :placeholder="item.otherOptions.placeholder"
+            size="default"
+          >
+            <el-option
+              v-for="val in item.otherOptions.options"
+              :key="val.name"
+              :label="val.label"
+              :value="val.value"
+            />
+          </el-select>
+          <el-input
+            v-if="item.type === FormFieldType.RICHTEXT"
+            v-model="formData[item.name]"
+            style="width: 624px; margin-bottom: 20px"
+            :autosize="{ minRows: 4 }"
+            type="textarea"
+            :placeholder="item.otherOptions?.placeholder"
+          />
+        </el-form-item>
+      </template>
+    </el-form>
+  </div>
+</template>
+
+<style scoped>
+.form {
+  width: 100%;
+}
+</style>

+ 13 - 1
src/hooks/useDialog.ts

@@ -2,11 +2,12 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-21 17:23:32
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-02 16:22:53
+ * @LastEditTime: 2024-09-04 15:46:03
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useDialog.ts
  * @Description:
  *
  */
+import Form from '@/components/form/Form.vue'
 import type { DialogSetting } from '@/types/table'
 import type { FormInstance } from 'element-plus'
 import { useRequest } from './useRequest'
@@ -27,6 +28,16 @@ export function useDialog() {
     formEl.resetFields()
   }
 
+  const dialogClose2 = (
+    formEl: InstanceType<typeof Form> | undefined,
+    dialogConfig: DialogSetting
+  ) => {
+    if (!formEl) return
+
+    dialogConfig.dialogVisible = false
+    formEl.resetForm()
+  }
+
   // 对话框提交
   const submitDialog = (
     formEl: FormInstance | undefined,
@@ -107,6 +118,7 @@ export function useDialog() {
 
   return {
     dialogClose,
+    dialogClose2,
     submitDialog,
     handleEdit,
     addNeweItem

+ 72 - 0
src/hooks/useForm.ts

@@ -0,0 +1,72 @@
+import type { FormInstance } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { nextTick } from 'vue'
+import axiosInstance from '@/utils/axios/axiosInstance'
+import { useRequest } from './useRequest'
+
+const { analysisResCode } = useRequest()
+export function useForm() {
+  // 对话框提交
+  const submitForm = (formEl: FormInstance | undefined, url: string, formData: any) => {
+    return new Promise((reslove, reject) => {
+      ElMessageBox.confirm('确认提交吗?', '警告', {
+        confirmButtonText: '确认',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(() => {
+          try {
+            if (!formEl) {
+              reject(new Error('no formEl'))
+              return
+            }
+            formEl.validate(async (valid, field) => {
+              if (valid) {
+                let result = await axiosInstance.post(url, formData)
+                let info = JSON.parse(JSON.stringify(result))
+
+                analysisResCode(info)
+                  .then(() => {
+                    reslove(true)
+                  })
+                  .catch((err) => {
+                    reject(err)
+                    console.log(err)
+                  })
+              } else {
+                console.log(field)
+                console.log('表单校验不通过')
+              }
+            })
+          } catch (err) {
+            console.log(err)
+            ElMessage({
+              type: 'error',
+              message: '未知错误'
+            })
+            throw new Error('other err')
+          }
+        })
+        .catch(() => {
+          reject(false)
+        })
+    })
+  }
+
+  // 修改按钮
+  const editForm = (row: any, formData: any) => {
+    // 这里放到nextTick,因为resetFields原理是将表单重置到dom刚渲染时的数据
+    // 而这个表单,如果第一次使用就点击编辑的话,会将初始值直接设置为这行的数据,导致无法重置
+    nextTick(() => {
+      for (const key in row) {
+        if (key in formData) {
+          formData[key] = row[key]
+        } else {
+          console.log(key)
+        }
+      }
+    })
+  }
+
+  return { submitForm, editForm }
+}

+ 6 - 3
src/router/appManage.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:24:58
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-03 18:13:36
+ * @LastEditTime: 2024-09-04 17:53:20
  * @FilePath: \Game-Backstage-Management-System\src\router\appManage.ts
  * @Description:
  *
@@ -21,6 +21,7 @@ export default [
         cnName: '基本信息',
         component: () => import('@/views/AppManage/BaseInfoView.vue'),
         meta: {
+          activeMenu: 'gameBaseInfo',
           needKeepAlive: true
         }
       },
@@ -33,17 +34,19 @@ export default [
         component: () => import('@/views/AppManage/EventManageView.vue'),
         redirect: '/appManage/eventManage/eventTable',
         meta: {
-          needKeepAlive: false
+          needKeepAlive: false,
+          activeMenu: 'eventManage'
         },
         children: [
           {
-            path: 'eventDetail/:eventID?', // 注意:这里没有使用动态参数,而是使用查询参数
+            path: 'eventDetail', // 注意:这里没有使用动态参数,而是使用查询参数
             name: 'EventDetail',
             component: () => import('@/views/AppManage/EventDetailsView.vue')
           },
           {
             path: 'eventTable',
             name: 'EventTable',
+
             component: () => import('@/views/AppManage/EventMangeTable.vue')
           }
         ]

+ 6 - 1
src/router/home.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:24:58
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-03 18:11:37
+ * @LastEditTime: 2024-09-04 09:17:00
  * @FilePath: \Game-Backstage-Management-System\src\router\home.ts
  * @Description:
  *
@@ -21,6 +21,7 @@ export default [
         cnName: '数据总览',
         component: () => import('@/views/Home/Overview/OverView.vue'),
         meta: {
+          activeMenu: 'overView',
           needKeepAlive: true
         }
       },
@@ -36,6 +37,7 @@ export default [
             cnName: '游戏管理',
             component: () => import('@/views/Home/InfoManage/GameManageView.vue'),
             meta: {
+              activeMenu: 'gameManageView',
               needKeepAlive: true
             }
           },
@@ -45,6 +47,7 @@ export default [
             cnName: '玩家管理',
             component: () => import('@/views/Home/InfoManage/PlayerManageView.vue'),
             meta: {
+              activeMenu: 'playerManageView',
               needKeepAlive: true
             }
           }
@@ -62,6 +65,7 @@ export default [
             cnName: '留存分析',
             component: () => import('@/views/Home/Analysis/KeepView.vue'),
             meta: {
+              activeMenu: 'keepView',
               needKeepAlive: true
             }
           },
@@ -71,6 +75,7 @@ export default [
             cnName: '用户趋势',
             component: () => import('@/views/Home/Analysis/UserTrendView.vue'),
             meta: {
+              activeMenu: 'userTrendView',
               needKeepAlive: true
             }
           }

+ 19 - 0
src/types/dialog.ts

@@ -0,0 +1,19 @@
+/*
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-09-04 14:02:44
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-09-04 14:12:51
+ * @FilePath: \Game-Backstage-Management-System\src\types\dialog.ts
+ * @Description:
+ *
+ */
+import type { FormRules } from 'element-plus'
+import type { ReqConfig } from './dataAnalysis'
+import type { FormField } from './form'
+
+export interface DialogConfig {
+  title: string
+  rules: FormRules
+  reqConfig: ReqConfig
+  fieldsInfo: Array<FormField>
+}

+ 23 - 0
src/types/form.ts

@@ -0,0 +1,23 @@
+import type { ReqConfig } from '@/types/dataAnalysis'
+import type { FormRules } from 'element-plus'
+export enum FormFieldType {
+  SELECT = 'select',
+  INPUT = 'input',
+  RICHTEXT = 'richText'
+}
+
+export interface FormField {
+  name: string
+  cnName: string
+  type: FormFieldType
+  otherOptions?: {
+    placeholder?: string
+    options?: Array<{ name: string; label: string; value: any }>
+  }
+}
+
+export interface FormConfig {
+  reqConfig: ReqConfig
+  fieldsInfo: Array<FormField>
+  rules: FormRules
+}

+ 4 - 2
src/types/table.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:56:13
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-03 14:11:05
+ * @LastEditTime: 2024-09-04 10:45:49
  * @FilePath: \Game-Backstage-Management-System\src\types\table.ts
  * @Description:
  *
@@ -56,7 +56,9 @@ export enum FieldSpecialEffectType {
   IMG = 'img',
   TAG = 'tag',
   TEXT = 'text',
-  TRANSLATE = 'translate'
+  TRANSLATE = 'translate',
+  SWITCH = 'switch',
+  DROPDOWN = 'dropdown'
 }
 
 // 表格字段信息格式

+ 12 - 1
src/utils/common/index.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-26 15:46:42
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-03 12:31:19
+ * @LastEditTime: 2024-09-04 11:56:55
  * @FilePath: \Game-Backstage-Management-System\src\utils\common\index.ts
  * @Description:
  *
@@ -111,3 +111,14 @@ export const copyText = (text: string) => {
       })
   })
 }
+
+/**
+ * @description: 用于清除reactive对象的所有属性并保持响应式
+ * @param {Record} data 传入的对象
+ * @return {*}
+ */
+export const clearReactiveData = (data: Record<string, any>) => {
+  Object.keys(data).forEach((key) => {
+    delete data[key]
+  })
+}

+ 318 - 8
src/views/AppManage/EventDetailsView.vue

@@ -1,26 +1,336 @@
 <script setup lang="ts">
-import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+import Form from '@/components/form/Form.vue'
+import Table from '@/components/Table.vue'
+import Dialog from '@/components/common/Dialog.vue'
+import type { ReqConfig } from '@/types/dataAnalysis'
+import { type FormField, FormFieldType } from '@/types/form'
+import type { FormRules } from 'element-plus'
+import type { FormConfig } from '@/types/form'
+import {
+  type TablePaginationSetting,
+  type TableFieldInfo,
+  FieldSpecialEffectType
+} from '@/types/table'
+import type { DialogConfig } from '@/types/dialog'
 import { reactive, onMounted, ref } from 'vue'
-import { initLoadResouce } from '@/utils/resource'
-import { copyText } from '@/utils/common'
 import { useRoute } from 'vue-router'
+import { useRequest } from '@/hooks/useRequest'
+
+const { AllApi } = useRequest()
+
+const eventEditState = ref(false)
+
+// 属性对话框
+const attrDialog = ref()
+
+// 表单规则字段
+const ruleForm = reactive({
+  eventID: '',
+  eventDisplayName: '',
+  useageStatus: '',
+  triggerTimingDesc: ''
+})
+
+// 表单规则
+const rules = reactive<FormRules<typeof ruleForm>>({
+  eventID: [
+    { required: true, message: '事件ID是必填项', trigger: 'blur' },
+    {
+      min: 1,
+      max: 20,
+      message: '事件ID长度必须在1到20个字符之间',
+      trigger: 'blur'
+    }
+  ],
+  eventDisplayName: [
+    { required: true, message: '事件显示名是必填项', trigger: 'blur' },
+    {
+      min: 5,
+      max: 10,
+      message: '事件显示名长度必须在5到10个字符之间',
+      trigger: 'blur'
+    }
+  ],
+  useageStatus: [{ required: true, message: '使用状态是必填项', trigger: 'change' }],
+  triggerTimingDesc: [
+    {
+      min: 1,
+      max: 255,
+      message: '描述长度必须在1到255个字符之间',
+      trigger: 'blur'
+    }
+  ]
+})
+
+// 表单字段信息
+const FormFields: Array<FormField> = [
+  {
+    name: 'eventID',
+    cnName: '事件ID',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'eventDisplayName',
+    cnName: '事件显示',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'useageStatus',
+    cnName: '使用状态',
+    type: FormFieldType.SELECT,
+    otherOptions: {
+      placeholder: '请选择状态',
+      options: [
+        {
+          name: 'disabled',
+          label: '已禁用',
+          value: false
+        },
+        {
+          name: 'abled',
+          label: '已启用',
+          value: true
+        }
+      ]
+    }
+  },
+  {
+    name: 'triggerTimingDesc',
+    cnName: '触发时机描述',
+    type: FormFieldType.RICHTEXT,
+    otherOptions: {
+      placeholder: '请输入描述'
+    }
+  }
+]
+
+// 表单请求参数
+const formReq = reactive<ReqConfig>({
+  url: AllApi.mockEvent,
+  otherOptions: {}
+})
+
+// 表单设置
+const formConfig = reactive<FormConfig>({
+  reqConfig: formReq,
+  fieldsInfo: FormFields,
+  rules
+})
+
+// 表格分页设置
+const pageConfig = reactive<TablePaginationSetting>({
+  limit: 20,
+  currentPage: 1,
+  total: 0,
+  pagesizeList: [20, 30]
+})
+
+// 表格字段信息
+const tableFieldConfig = reactive<Array<TableFieldInfo>>([
+  {
+    name: 'attrID',
+    cnName: '参数ID',
+    isShow: true
+  },
+  {
+    name: 'useStatus',
+    cnName: '使用状态',
+    isShow: true,
+    specialEffect: {
+      othnerInfo: {
+        text: ['已使用', '已弃用']
+      },
+      type: FieldSpecialEffectType.DROPDOWN
+    }
+  }
+])
+
+// 表格请求配置
+const tableReqConfig = reactive<ReqConfig>({
+  url: AllApi.mockEvent,
+  otherOptions: {
+    eventID: ''
+  }
+})
+
+// 对话框请求参数
+const dialogReqConfig = reactive<ReqConfig>({
+  url: AllApi.mockEvent,
+  otherOptions: {}
+})
+
+// 对话框表单规则字段
+const dilogRuleForm = reactive({
+  attrID: '',
+  useStatus: false
+})
+
+// 表单规则
+const dialogRules = reactive<FormRules<typeof dilogRuleForm>>({
+  attrID: [
+    { required: true, message: '属性ID是必填项', trigger: 'blur' },
+    {
+      min: 1,
+      max: 20,
+      message: '属性ID长度必须在1到20个字符之间',
+      trigger: 'blur'
+    }
+  ],
+  useStatus: [{ required: true, message: '使用状态是必填项', trigger: 'change' }]
+})
+
+// 表单字段信息
+const dialogFormFields: Array<FormField> = [
+  {
+    name: 'attrID',
+    cnName: '属性ID',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'useStatus',
+    cnName: '使用状态',
+    type: FormFieldType.SELECT,
+    otherOptions: {
+      placeholder: '请选择状态',
+      options: [
+        {
+          name: 'disabled',
+          label: '已禁用',
+          value: false
+        },
+        {
+          name: 'abled',
+          label: '已启用',
+          value: true
+        }
+      ]
+    }
+  }
+]
+
+// 对话框设置
+const dialogConfig = reactive<DialogConfig>({
+  title: '自定义属性',
+  reqConfig: dialogReqConfig,
+  rules: dialogRules,
+  fieldsInfo: dialogFormFields
+})
+
+// 新增属性
+const addNewAttr = () => {
+  attrDialog.value.changeVisable()
+}
+
+// 改变编辑状态
+const changeEditState = (state: boolean) => {
+  eventEditState.value = state
+}
+
+/**
+ * @description: 取消编辑
+ * @return {*}
+ */
+const cancelEdit = () => {
+  changeEditState(false)
+}
+
+/**
+ * @description: 保存编辑
+ * @return {*}
+ */
+const saveEdit = () => {
+  changeEditState(false)
+}
+
+/**
+ * @description: 开始编辑
+ * @return {*}
+ */
+const startEdit = () => {
+  changeEditState(true)
+}
 
 onMounted(() => {
-  const route = useRoute()
-  let query = route.query
+  const routes = useRoute()
+  let query = routes.query
+
   console.log(query)
 })
 </script>
 
 <template>
-  <div class="eventDetail">okok</div>
+  <div class="eventDetail">
+    <div class="header">
+      <span>基本信息</span>
+      <div class="headerBtn">
+        <span v-if="eventEditState">
+          <el-button class="handleBtn" @click="cancelEdit" color="#626aef" plain>取消</el-button>
+          <el-button class="handleBtn" @click="saveEdit" color="#626aef">保存</el-button>
+        </span>
+        <span v-else>
+          <el-button class="handleBtn" @click="startEdit" color="#626aef">编辑</el-button>
+        </span>
+      </div>
+    </div>
+  </div>
+  <div class="eventForm">
+    <div class="formBox">
+      <Form :inline="true" :config="formConfig"></Form>
+    </div>
+  </div>
+  <div class="attrList">
+    <div class="header">
+      <span>属性列表</span>
+    </div>
+    <div class="list">
+      <Table
+        :need-left-tools="true"
+        :pagination-config="pageConfig"
+        :table-fields-info="tableFieldConfig"
+        :request-config="tableReqConfig"
+        @add-new-item="addNewAttr"
+      ></Table>
+    </div>
+  </div>
+  <div class="addAttrDialo">
+    <Dialog ref="attrDialog" :config="dialogConfig"></Dialog>
+  </div>
 </template>
 
 <style scoped>
-.eventDetail {
+.eventDetail,
+.eventForm,
+.attrList {
   width: 98%;
-  margin: 1% auto;
+  margin: 0 auto;
+  background-color: white;
+  box-sizing: border-box;
+}
 
+.header {
+  width: 100%;
   box-sizing: border-box;
+  background-color: white;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  height: 56px;
+  box-shadow: inset 0 -1px 0 0 rgba(23, 35, 61, 0.1);
+  font-weight: 600;
+  font-size: 14px;
+  color: #17233d;
+  padding: 12px 24px;
+}
+
+.headerBtn {
+  box-sizing: border-box;
+}
+
+.handleBtn {
+  margin-right: 15px;
+}
+
+.eventForm {
+  padding: 24px;
 }
 </style>

+ 5 - 12
src/views/AppManage/EventManageView.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-02 17:57:15
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-03 15:53:46
+ * @LastEditTime: 2024-09-04 18:24:01
  * @FilePath: \Game-Backstage-Management-System\src\views\AppManage\EventManageView.vue
  * @Description: 
  * 
@@ -86,15 +86,6 @@ const goBack = () => {
   router.push('/appManage/EventManage')
 }
 
-const viewDetails = (row: any) => {
-  router.push({
-    name: 'EventDetail',
-    query: {
-      eventID: row.eventID
-    }
-  })
-}
-
 onMounted(() => {})
 </script>
 
@@ -121,17 +112,19 @@ onMounted(() => {})
 .enventManage {
   width: 98%;
   margin: 1% auto;
-
+  background-color: white;
   box-sizing: border-box;
 }
 
 .breadcrumbBox {
+  background-color: white;
+  box-sizing: border-box;
   height: 64px;
   font-size: 16px;
   color: #17233d;
   font-weight: 600;
-  line-height: 64px;
   padding: 0 24px;
+  line-height: 64px;
 }
 
 .separator {

+ 234 - 8
src/views/AppManage/EventMangeTable.vue

@@ -1,22 +1,30 @@
 <script setup lang="ts">
 import Table from '@/components/Table.vue'
+import Dialog from '@/components/common/Dialog.vue'
 import { useRequest } from '@/hooks/useRequest'
 
-import type { TablePaginationSetting, TableFieldInfo } from '@/types/table'
+import type { TablePaginationSetting, TableFieldInfo, QueryInfo, SelectInfo } from '@/types/table'
+import { FieldSpecialEffectType, FilterType } from '@/types/table'
 import type { ReqConfig } from '@/types/dataAnalysis'
+import type { FormRules } from 'element-plus'
+import { type DialogConfig } from '@/types/dialog'
+import { FormFieldType } from '@/types/form'
+import type { FormField } from '@/types/form'
 
-import { reactive, onMounted } from 'vue'
+import { reactive, onMounted, ref } from 'vue'
 
 import router from '@/router'
 
 const { AllApi } = useRequest()
 
+const eventDialog = ref()
+
 // 表格分页设置
 const pagingConfig = reactive<TablePaginationSetting>({
-  limit: 15,
+  limit: 20,
   currentPage: 1,
   total: 0,
-  pagesizeList: [15, 30]
+  pagesizeList: [20, 30]
 })
 
 // 表格字段信息
@@ -34,22 +42,52 @@ const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
   {
     name: 'displayStatus',
     cnName: '显示状态',
-    isShow: true
+    isShow: true,
+    specialEffect: {
+      type: FieldSpecialEffectType.SWITCH,
+      othnerInfo: {}
+    }
   },
   {
     name: 'useStatus',
     cnName: '使用状态',
-    isShow: true
+    isShow: true,
+    specialEffect: {
+      type: FieldSpecialEffectType.DROPDOWN,
+      othnerInfo: {
+        text: ['已使用', '已弃用']
+      }
+    }
   },
   {
     name: 'usageStatus',
     cnName: '用量状态',
-    isShow: true
+    isShow: true,
+    specialEffect: {
+      type: FieldSpecialEffectType.TRANSLATE,
+      othnerInfo: {
+        icon: true,
+        translateText: {
+          true: '正常',
+          false: '弃用'
+        }
+      }
+    }
   },
   {
     name: 'platform',
     cnName: '平台',
-    isShow: true
+    isShow: true,
+    specialEffect: {
+      type: FieldSpecialEffectType.TRANSLATE,
+      othnerInfo: {
+        translateText: {
+          wx: '微信',
+          tt: '抖音',
+          web: 'Web'
+        }
+      }
+    }
   },
   {
     name: 'creationTime',
@@ -64,6 +102,179 @@ const requestConfig = reactive<ReqConfig>({
   otherOptions: {}
 })
 
+// 事件类型查询信息
+const eventTypeQueryInfo: Array<SelectInfo> = [
+  {
+    name: 'all',
+    cnName: '全部',
+    value: 'all'
+  },
+  {
+    name: 'custom',
+    cnName: '自定义事件',
+    value: 'custom'
+  },
+  {
+    name: 'standard',
+    cnName: '标准事件',
+    value: 'standard'
+  }
+]
+
+// 显示状态查询信息
+const showStateQueryInfo: Array<SelectInfo> = [
+  {
+    name: 'all',
+    cnName: '全部',
+    value: 'all'
+  },
+  {
+    name: 'show',
+    cnName: '显示',
+    value: 'show'
+  },
+  {
+    name: 'hide',
+    cnName: '不显示',
+    value: 'hide'
+  }
+]
+
+// 使用状态查询信息
+const useageStateQueryInfo: Array<SelectInfo> = [
+  {
+    name: 'all',
+    cnName: '全部',
+    value: 'all'
+  },
+  {
+    name: 'inUse',
+    cnName: '使用中',
+    value: 'inUse'
+  },
+  {
+    name: 'noUse',
+    cnName: '已弃用',
+    value: 'noUse'
+  }
+]
+
+// 查询字段设置
+const queryInfo: Array<QueryInfo> = [
+  {
+    name: 'evnetName',
+    label: '',
+    type: FilterType.INPUT,
+    placeholder: '请输入事件名搜索',
+    default: ''
+  },
+  {
+    name: 'evnetType',
+    label: '事件类型',
+    type: FilterType.SELECT,
+    placeholder: '请选择事件类型',
+    otherOption: eventTypeQueryInfo,
+    default: 'all'
+  },
+  {
+    name: 'showState',
+    label: '显示状态',
+    type: FilterType.SELECT,
+    placeholder: '请选择显示状态',
+    otherOption: showStateQueryInfo,
+    default: 'all'
+  },
+  {
+    name: 'useageState',
+    label: '使用状态',
+    type: FilterType.SELECT,
+    placeholder: '请选择使用状态',
+    otherOption: useageStateQueryInfo,
+    default: 'all'
+  }
+]
+
+// 对话框表单规则字段
+const dilogRuleForm = reactive({
+  eventDisplayName: '',
+  platform: ''
+})
+
+// 对话表单规则
+const dialogRules = reactive<FormRules<typeof dilogRuleForm>>({
+  eventDisplayName: [
+    { required: true, message: 'Event Display Name is required', trigger: 'blur' },
+    {
+      min: 5,
+      max: 10,
+      message: 'Event Display Name must be between 5 and 10 characters',
+      trigger: 'blur'
+    }
+  ],
+  platform: [
+    { required: true, message: 'Platform is required', trigger: 'change' },
+    {
+      type: 'enum',
+      enum: ['web', 'wx', 'tt'],
+      message: 'Platform must be either "web", "wx", or "tt"',
+      trigger: 'change'
+    }
+  ]
+})
+
+// 对话框请求参数
+const dialogReq = reactive<ReqConfig>({
+  url: AllApi.mockEvent,
+  otherOptions: {}
+})
+
+// 对话框表单字段信息
+const FormFields: Array<FormField> = [
+  {
+    name: 'eventDisplayName',
+    cnName: '事件名',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'platform',
+    cnName: '平台',
+    type: FormFieldType.SELECT,
+    otherOptions: {
+      placeholder: '请选择平台',
+      options: [
+        {
+          name: 'wx',
+          label: '微信',
+          value: 'wx'
+        },
+        {
+          name: 'tt',
+          label: '抖音',
+          value: 'tt'
+        },
+        {
+          name: 'web',
+          label: 'Web',
+          value: 'web'
+        }
+      ]
+    }
+  }
+]
+
+// 对话框需要的props
+const dialogInfo = reactive<DialogConfig>({
+  title: '新增事件',
+  rules: dialogRules,
+  reqConfig: dialogReq,
+  fieldsInfo: FormFields
+})
+
+/**
+ * @description: 查看详情
+ * @param {*} row 行信息
+ * @return {*}
+ */
 const viewDetails = (row: any) => {
   router.push({
     name: 'EventDetail',
@@ -73,6 +284,14 @@ const viewDetails = (row: any) => {
   })
 }
 
+/**
+ * @description: 触发新增
+ * @return {*}
+ */
+const addNewEvent = () => {
+  eventDialog.value.changeVisable()
+}
+
 onMounted(() => {})
 </script>
 
@@ -83,6 +302,10 @@ onMounted(() => {})
       :request-config="requestConfig"
       :pagination-config="pagingConfig"
       :table-fields-info="tableFieldsInfo"
+      :query-info="queryInfo"
+      :open-filter-query="true"
+      :need-left-tools="true"
+      @add-new-item="addNewEvent"
     >
       <template #tableOperation>
         <el-table-column label="操作" align="center">
@@ -99,6 +322,9 @@ onMounted(() => {})
         </el-table-column>
       </template>
     </Table>
+    <div class="eventDialog">
+      <Dialog ref="eventDialog" :config="dialogInfo"></Dialog>
+    </div>
   </div>
 </template>
 

+ 0 - 8
src/views/Home/InfoManage/GameManageView.vue

@@ -222,14 +222,6 @@ const submiteGameChange = () => {
             >
               修改
             </el-button>
-            <!-- <el-button
-              class="operationBtn"
-              size="small"
-              type="success"
-              @click="enterUserPage(scope.row)"
-            >
-              进入
-            </el-button> -->
           </template>
         </el-table-column>
       </template>