Browse Source

更新路由,更新页面结构,更新事件列表页面,更新事件修改页面

fxs 9 months ago
parent
commit
9bb4d17862

+ 3 - 429
src/App.vue

@@ -2,445 +2,19 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:06:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-04 10:19:43
+ * @LastEditTime: 2024-09-05 17:30:14
  * @FilePath: \Game-Backstage-Management-System\src\App.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
 import { zhCn } from 'element-plus/es/locales.mjs'
-import { RouterView } from 'vue-router'
-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'
-import type { DropDownInfo } from '@/types/dataAnalysis'
-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 = computed(() => {
-  return route.meta.activeMenu
-})
-
-// 顶部导航栏信息
-const navBarMenuList = [
-  {
-    name: 'Home',
-    title: '应用分析'
-  },
-
-  {
-    name: 'AppManage',
-    title: '应用管理'
-  }
-]
-
-/**
- * @description: 侧边栏折叠改变
- * @return {*}
- */
-const changeCollapse = () => {
-  isCollapse.value = !isCollapse.value
-}
-
-// 登出
-const logOut = () => {
-  ElMessage({
-    type: 'success',
-    message: '退出成功',
-    duration: 1000
-  })
-  localStorage.removeItem('token')
-  localStorage.removeItem('refreshToken')
-  router.push('/login')
-}
-
-// 游戏下拉选择框需要的数据
-const gameSelectInfo = reactive<DropDownInfo>({
-  defaultSelect: '1001',
-  title: '请选择游戏',
-  optionsList: []
-})
-
-// 游戏信息是否加载成功
-const gameinfoLoad = ref(false)
-
-/**
- * @description: 更新整个页面的游戏选择
- * @param {*} gid 游戏id
- * @return {*}
- */
-const changeGame = (gid: any) => {
-  selectInfo.gid = gid
-}
-
-/**
- * @description: 头部导航栏改变
- * @param {*} val 对应的name
- * @return {*}
- */
-const changeNavBar = (val: string) => {
-  navBarSelect.value = val
-
-  router.push(`/${val}`)
-  createdMenuList()
-  console.log(menuList)
-  let title = navBarMenuList.find((item) => item.name === val)?.title
-  if (title) {
-    siderBarOpened.value.splice(0, 1, title)
-  }
-}
-
-/**
- * @description: 获取所有游戏列表
- * @return {*}
- */
-getAllGameInfo().then((data) => {
-  if (data) {
-    data.map((item) => {
-      gameSelectInfo.optionsList.push({
-        value: item.gid,
-        label: item.gameName
-      })
-    })
-  }
-  gameinfoLoad.value = true
-})
-
-// 资源的加载路径
-const resourceInfo: Record<string, string> = {
-  logo: `/img/logo.svg`,
-  defaultHead: `/img/default/defaultHead.png`
-}
-
-// 使用blob的资源路径信息
-const blobUrlInfo = reactive<Record<string, string>>({})
-
-// 侧边栏跳转路由的基本路由
-const basePath = ref<string | undefined>()
-
-/**
- * @description: 创建侧边栏menu
- * @return {*}
- */
-const createdMenuList = () => {
-  let routes = router.options.routes // 获取路由信息
-  let activeMenu = routes.find((item) => {
-    return item.name === navBarSelect.value // 根据顶部导航栏的选中情况来选择选中哪个具体的路由信息,可以打印自己看一下
-  })
-  basePath.value = activeMenu?.path // 找到需要激活的菜单的路由,后续用来拼接需要跳转的路由
-  menuList.splice(0, menuList.length, ...(activeMenu?.children as Array<any>)) // 清空原来的路由信息,并且加入新选中的
-}
-
-onMounted(() => {
-  // 去加载所有需要的资源
-  initLoadResouce(resourceInfo).then((data) => {
-    Object.assign(blobUrlInfo, data)
-  })
-
-  createdMenuList()
-})
 </script>
 
 <template>
   <el-config-provider :locale="zhCn">
-    <div class="body">
-      <div class="navBarBox">
-        <div class="logoBox">
-          <el-image :fit="'fill'" class="logoImg" :src="blobUrlInfo.logo"></el-image>
-          <span>淳皓科技</span>
-        </div>
-        <div class="gameSelect">
-          <el-icon class="gameIcon" :size="20">
-            <icon-icon-park-game-three></icon-icon-park-game-three>
-          </el-icon>
-          <DropDownSelection
-            :default-select="gameSelectInfo.defaultSelect"
-            :title="gameSelectInfo.title"
-            :options-list="gameSelectInfo.optionsList"
-            :size="'default'"
-            @change-select="changeGame"
-          ></DropDownSelection>
-        </div>
-        <!-- 顶部导航栏 -->
-        <div class="navBarMenu">
-          <el-menu
-            :default-active="navBarSelect"
-            class="el-menu-demo"
-            mode="horizontal"
-            @select="changeNavBar"
-          >
-            <el-menu-item
-              v-for="item in navBarMenuList"
-              class="navBarMenuItem"
-              :index="item.name"
-              >{{ item.title }}</el-menu-item
-            >
-          </el-menu>
-        </div>
-        <div class="headPortraitBox">
-          <el-popover popper-class="headPopper" placement="bottom-end" trigger="click">
-            <template #reference>
-              <el-image class="headPortrait" :src="blobUrlInfo.defaultHead"></el-image>
-            </template>
-            <div class="userTools">
-              <span class="userToolsItem" @click="logOut">
-                <icon-material-symbols-light-logout></icon-material-symbols-light-logout>
-                <span> 退出登录</span>
-              </span>
-            </div>
-          </el-popover>
-        </div>
-      </div>
-      <!-- 侧边栏 -->
-      <div class="sideBarBox">
-        <el-menu
-          :default-active="defaultActive"
-          class="sideBar"
-          :collapse="isCollapse"
-          ref="siderBar"
-        >
-          <template v-for="(item, index) in menuList">
-            <el-sub-menu :index="`${index}`" v-if="item.children && item.showChild">
-              <template #title>
-                <el-icon><component :is="item.icon"></component></el-icon>
-                <span>{{ item.cnName }}</span>
-              </template>
-              <router-link
-                style="text-decoration: none"
-                v-for="val in item.children"
-                :to="{ path: basePath + '/' + item.path + '/' + val.path }"
-                :key="index"
-              >
-                <el-menu-item :index="val.path">{{ val.cnName }}</el-menu-item>
-              </router-link>
-            </el-sub-menu>
-
-            <router-link
-              style="text-decoration: none"
-              v-else
-              :to="{ path: basePath + '/' + item.path }"
-              :key="index"
-            >
-              <el-menu-item :index="item.path">
-                <template #title>
-                  <el-icon><component :is="item.icon" /></el-icon>
-                  <span class="menuTitle">{{ item.cnName }}</span>
-                </template>
-              </el-menu-item>
-            </router-link>
-          </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>
-            <component
-              :is="Component"
-              :key="route.path"
-              v-if="route.meta.needKeepAlive == true"
-            ></component>
-          </keep-alive>
-          <component
-            :is="Component"
-            :key="route.path"
-            v-if="route.meta.needKeepAlive == false"
-          ></component>
-        </router-view>
-      </div>
-    </div>
+    <router-view></router-view>
   </el-config-provider>
 </template>
 
-<style scoped>
-.body {
-  width: 100%;
-  display: flex;
-  height: 100vh;
-}
-
-/* 设置宽度后,content无法适应宽度,只能去间接的调整内部元素的宽度 */
-.sideBarBox {
-  position: relative;
-  /* width: 12%; */
-  z-index: 1;
-  height: 93vh;
-  margin-top: 7vh;
-  top: 0;
-}
-
-.sideBar {
-  /* width: 12vw; */
-  height: 93vh;
-  position: relative;
-  overflow: scroll;
-}
-
-/* 设置弹出层的样式 */
-.el-popper > .logoText {
-  width: 100px;
-  font-size: 16px;
-  /* color: red; */
-}
-
-.logoImg {
-  display: flex;
-  align-items: center;
-  width: 33px;
-  /* margin-right: 20px; */
-  /* height: 50px; */
-}
-
-.logoText {
-  width: 80%;
-  height: 100%;
-  margin-left: 15%;
-  display: flex;
-  font-size: 18px;
-  align-items: center;
-  /* background-color: lightcoral; */
-}
-
-/* 主要用来调整整个menu的宽度 */
-.menuTitle {
-  margin-right: 40px;
-}
-
-.sideBarFold {
-  width: 5%;
-  height: 3%;
-  position: absolute;
-  right: 40px;
-  bottom: 20px;
-}
-
-.navBarBox {
-  position: fixed;
-  display: flex;
-  align-items: center;
-  width: 100vw;
-  z-index: 2;
-  height: 7vh;
-  top: 0;
-  background-color: white;
-  right: 0;
-  border-bottom: 1px solid gainsboro;
-}
-
-/* 调整LOGO */
-.logoBox {
-  box-sizing: border-box;
-  left: 30px;
-  position: relative;
-
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.gameSelect {
-  position: relative;
-
-  height: 80%;
-  display: flex;
-  align-items: center;
-  left: 5%;
-  display: flex;
-  align-items: center;
-}
-
-.gameIcon {
-  /* box-sizing: border-box; */
-  /* padding-right: 12px; */
-  margin-right: 12px;
-}
-
-.navBarMenu {
-  width: 60%;
-  position: relative;
-  left: 6%;
-}
-
-.headPortraitBox {
-  position: absolute;
-  right: 3%;
-  top: 50%;
-  transform: translateY(-50%);
-}
-
-.userTools {
-  width: 100%;
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-  justify-content: space-around;
-  align-items: center;
-}
-
-.userToolsItem {
-  cursor: pointer;
-  width: 100%;
-  height: 4vh;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  /* padding: 10px; */
-  margin: 2%;
-}
-
-.userToolsItem > span {
-  margin-left: 10%;
-}
-
-.userToolsItem:hover {
-  background-color: #f2f3f5;
-}
-
-.headPortrait {
-  cursor: pointer;
-  width: 50px;
-}
-
-.content {
-  /* flex-grow: 1; */
-  /* position: absolute; */
-
-  width: 100%;
-  /* height: 93%; */
-  margin-top: 7vh;
-  overflow: scroll;
-  background-color: #f2f3f5;
-  right: 0vw;
-  top: 0vh;
-}
-</style>
-
-<!-- 为了让popper-class生效,需要的单独写一份 -->
-<style>
-.headPopper {
-  padding: 0px !important;
-  border: 1px solid #e5e6eb;
-
-  background-color: white;
-}
-.el-menu--horizontal.el-menu {
-  border-bottom: none;
-}
-</style>
+<style scoped></style>

+ 92 - 14
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-04 18:00:51
+ * @LastEditTime: 2024-09-05 17:55:14
  * @FilePath: \Game-Backstage-Management-System\src\components\Table.vue
  * @Description: 
  * 
@@ -17,8 +17,13 @@ import { useTable } from '@/hooks/useTable'
 
 import FilterPopover from './toolsBtn/FilterPopover.vue'
 import RegreshBtn from './toolsBtn/RegreshBtn.vue'
+import { useRequest } from '@/hooks/useRequest'
 
 import type { FormInstance } from 'element-plus'
+import axiosInstance from '@/utils/axios/axiosInstance'
+import { clearReactiveData } from '@/utils/common'
+
+const { analysisResCode } = useRequest()
 
 // 表格工具图标大小
 const toolsIconSize = ref(25)
@@ -83,9 +88,6 @@ const inputFieldsList = computed(() => {
 // 所有类型为select的表单控件信息
 const selectFieldsList = computed(() => {
   return props.queryInfo?.filter((item) => {
-    if (item.default) {
-      queryFormData[item.name] = item.default
-    }
     return item.type === FilterType.SELECT
   })
 })
@@ -199,7 +201,9 @@ const queryTableData = () => {
  */
 const resetQueryForm = (formEl: FormInstance | undefined) => {
   if (!formEl) return
-  formEl?.resetFields()
+  console.log(formEl)
+  clearReactiveData(queryFormData)
+  // formEl?.resetFields()
 }
 
 /**
@@ -307,18 +311,62 @@ if (!props.openPageQuery) {
   changePageLimit()
 }
 
+/**
+ * @description: 拷贝一份配置文件
+ * @return {*}
+ */
 const initpageConfig = () => {
   Object.assign(paginationConfig2, props.paginationConfig)
 }
 
+/**
+ * @description: 初始化请求配置,用于把拷贝一份新的数据
+ * @return {*}
+ */
 const initReqConfig = () => {
   Object.assign(reqconfig, props.requestConfig)
 }
 
+/**
+ * @description: 表格排序
+ * @param {*} data 获取到的数据
+ * @return {*}
+ */
+const tableSortChange = (data: { column: any; prop: string; order: any }) => {
+  let { prop, order } = { ...data }
+  console.log(prop, order)
+  if (order === 'ascending') order = 'asc'
+  else if (order === 'descending') order = 'desc'
+  else order = ''
+  reqconfig.otherOptions.order = order
+  getData()
+}
+
+/**
+ * @description: 删除行
+ * @param {*} url 请求地址
+ * @param {*} row 行数据
+ * @return {*}
+ */
+const deleteRow = (url: string, filedsInfo: any) => {
+  console.log(url, filedsInfo)
+  axiosInstance
+    .post(url, { ...filedsInfo })
+    .then((data) => {
+      analysisResCode(data).then(() => {
+        getData()
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
+
 // 定义暴露出去的方法
 defineExpose({
   getData,
-  resetTableData
+  resetTableData,
+  deleteRow
 })
 
 onMounted(() => {
@@ -330,6 +378,8 @@ onMounted(() => {
   if (!props.openPageQuery) {
     getData()
   }
+
+  console.log(queryFormData)
 })
 </script>
 
@@ -420,6 +470,7 @@ onMounted(() => {
         :cell-style="tableCellStyle"
         v-loading="loading"
         :row-key="createRowKey()"
+        @sort-change="tableSortChange"
       >
         <el-table-column
           v-if="props.needRowindex"
@@ -432,12 +483,11 @@ onMounted(() => {
           <el-table-column
             :prop="item.name"
             :label="item.cnName"
-            :min-width="
-              item.specialEffect?.type === FieldSpecialEffectType.DROPDOWN ? '170px' : '100px'
-            "
+            :min-width="item.specialEffect?.type === FieldSpecialEffectType.DROPDOWN ? '170px' : ''"
             align="center"
             show-overflow-tooltip
             v-if="item.isShow"
+            :sortable="item.needSort ? 'custorm' : false"
           >
             <template v-slot="scope">
               <!-- tag类 -->
@@ -488,15 +538,40 @@ onMounted(() => {
                   :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]] }}
+                {{
+                  item.specialEffect.othnerInfo.translateText[scope.row[item.name]]
+                    ? item.specialEffect.othnerInfo.translateText[scope.row[item.name]]
+                    : '未知'
+                }}
+              </el-text>
+
+              <!-- 状态类 -->
+              <el-text v-else-if="item.specialEffect?.type === FieldSpecialEffectType.STATE">
+                <span>
+                  <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]
+                  }}</span
+                >
               </el-text>
 
               <!-- 开关类 -->
+
               <el-switch
+                :active-value="1"
+                :inactive-value="0"
                 v-else-if="item.specialEffect?.type === FieldSpecialEffectType.SWITCH"
                 v-model="scope.row[item.name]"
+                :data="scope.row[item.name]"
                 size="default"
-              ></el-switch>
+              >
+              </el-switch>
 
               <!-- 下拉菜单类 -->
               <el-dropdown
@@ -510,12 +585,15 @@ onMounted(() => {
                   <el-icon
                     style="padding-right: 8px"
                     :color="scope.row[item.name] ? '#409EFF' : '#F56C6C'"
-                    ><icon-tabler-point-filled></icon-tabler-point-filled></el-icon
-                  >{{
+                    ><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>
+                  }}
+
+                  <el-icon class="el-icon--right"><arrow-down /></el-icon>
                 </span>
                 <template #dropdown>
                   <el-dropdown-menu>

+ 57 - 13
src/components/common/Dialog.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-04 11:21:05
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-04 18:21:52
+ * @LastEditTime: 2024-09-05 16:23:15
  * @FilePath: \Game-Backstage-Management-System\src\components\common\Dialog.vue
  * @Description: 
  * 
@@ -12,7 +12,7 @@
 import Form from '../form/Form.vue'
 import { useDialog } from '@/hooks/useDialog'
 
-import { onMounted, reactive, ref } from 'vue'
+import { nextTick, onMounted, reactive, ref } from 'vue'
 
 import type { DialogConfig } from '@/types/dialog'
 
@@ -23,6 +23,12 @@ interface DialogProps {
 // 临时用的close
 const { dialogClose2 } = useDialog()
 
+const emits = defineEmits(['formSubmit'])
+
+// 新增的URL与更新URL,因为有时候更新和新增在一起,有时候是分开的,所以在这里去区分
+const addUrl = ref<string>('')
+const updateUrl = ref<string>('')
+
 // 对话框ref
 const dialogFormRef = ref<InstanceType<typeof Form>>()
 
@@ -42,12 +48,10 @@ const dialogConfig = reactive({
 
 // 游戏配置提交
 const submiteGameChange = () => {
-  dialogFormRef.value?.submitFormData()
-}
-
-// 改变显示状态
-const changeVisable = () => {
-  dialogConfig.dialogVisible = !dialogConfig.dialogVisible
+  dialogFormRef.value?.submitFormData().finally(() => {
+    dialogConfig.dialogVisible = false
+    emits('formSubmit')
+  })
 }
 
 // 表单关闭
@@ -55,12 +59,41 @@ const closeDialog = () => {
   dialogClose2(dialogFormRef.value, dialogConfig)
 }
 
+// 表单添加
+const addForm = () => {
+  dialogConfigInfo.reqConfig.url = addUrl.value
+  dialogConfig.dialogVisible = true
+}
+
+// 表单修改
+const editForm = (row: any, updateURL?: string) => {
+  dialogConfig.dialogVisible = true
+  if (updateURL) {
+    updateUrl.value = updateURL
+    dialogConfigInfo.reqConfig.url = updateUrl.value
+  }
+
+  nextTick(() => {
+    dialogFormRef.value?.fillForm(row)
+  })
+}
+
 onMounted(() => {
   dialogConfig.title = dialogConfigInfo.title
+  addUrl.value = props.config.reqConfig.url // 保存一下新增的URL
 })
 
+const encrypt = (fields: string, useFormField: boolean, encryptMsg: Array<string>) => {
+  dialogFormRef.value?.encryptData(fields, useFormField, encryptMsg).finally(() => {
+    dialogConfig.dialogVisible = false
+    emits('formSubmit')
+  })
+}
+
 defineExpose({
-  changeVisable
+  addForm,
+  editForm,
+  encrypt
 })
 </script>
 
@@ -70,9 +103,10 @@ defineExpose({
       @close="closeDialog"
       v-model="dialogConfig.dialogVisible"
       :title="dialogConfig.title"
-      style="width: 40vw"
+      style="width: 40%"
     >
       <Form
+        :disabled="true"
         ref="dialogFormRef"
         :config="{
           fieldsInfo: dialogConfigInfo.fieldsInfo,
@@ -82,12 +116,22 @@ defineExpose({
       ></Form>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="submiteGameChange()"> 确认 </el-button>
-          <el-button @click="closeDialog">取消</el-button>
+          <slot name="otherBtn"></slot>
+          <slot name="btnGroup">
+            <el-button class="dialogBtn" type="primary" @click="submiteGameChange()">
+              确认
+            </el-button>
+            <el-button class="dialogBtn" @click="closeDialog">取消</el-button>
+          </slot>
         </div>
       </template>
     </el-dialog>
   </div>
 </template>
 
-<style scoped></style>
+<style scoped>
+.dialogBtn {
+  box-sizing: border-box;
+  margin-right: 10px;
+}
+</style>

+ 97 - 22
src/components/form/Form.vue

@@ -3,13 +3,14 @@ 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 { reactive, onMounted, ref } from 'vue'
 import { useForm } from '@/hooks/useForm'
+import CryptoJS from 'crypto-js'
 
 const { submitForm } = useForm()
 interface FormProp {
   inline?: boolean
+  disabled?: boolean
   config: FormConfig
 }
 
@@ -18,60 +19,120 @@ const formRef = ref<FormInstance>()
 
 // props
 const props = withDefaults(defineProps<FormProp>(), {
-  inline: false
+  inline: false,
+  disabled: false
 })
 
 // 表单的数据
 const formData = reactive<Record<string, any>>({})
 
-// 拿到表单的配置
-const formConfig = props.config
+// 备份的表单数据
+const backupData = reactive<Record<string, any>>({})
 
 /**
  * @description: 表单数据提交
  * @return {*}
  */
-const submitFormData = () => {
-  submitForm(formRef.value, formConfig.reqConfig.url, formData)
+const submitFormData = (otherOption?: any) => {
+  return new Promise((reslove, reject) => {
+    Object.assign(formData, props.config.reqConfig.otherOptions.formData)
+    submitForm(formRef.value, props.config.reqConfig.url, { ...formData, ...otherOption })
+      .then(() => {
+        reslove(true)
+      })
+      .catch((err) => {
+        reject(err)
+      })
+  })
 }
 
 /**
- * @description: 重置表单
+ * @description: 重置表单为空
  * @return {*}
  */
 const resetForm = () => {
   formRef.value?.resetFields()
 }
 
+/**
+ * @description: 填充表单
+ * @param {*} row 传过来的数据
+ * @return {*}
+ */
+const fillForm = (row: any) => {
+  Object.assign(formData, row)
+}
+
+/**
+ * @description: 开启编辑的时候备份一下data
+ * @return {*}
+ */
+const backupFormData = () => {
+  Object.assign(backupData, formData)
+}
+
+/**
+ * @description: 恢复表单数据
+ * @return {*}
+ */
+const resumeFormData = () => {
+  Object.assign(formData, backupData)
+}
+
 onMounted(() => {
-  nextTick(() => {
-    clearReactiveData(formData)
-  })
+  // nextTick(() => {
+
+  // })
+  console.log(formData)
 })
 
+/**
+ * @description: 加密数据
+ * @param {*} fields 字段名称
+ * @return {*}
+ */
+const encryptData = (fields: string, useFormField: boolean, encryptMsg: Array<string>) => {
+  return new Promise((reslove, reject) => {
+    let message = ``
+    let fieldData = ``
+    encryptMsg.map((item) => {
+      message += useFormField ? formData[item] : item
+    })
+    fieldData = CryptoJS.HmacMD5(message, formData[fields]).toString()
+    submitFormData({ [fields]: fieldData })
+      .then(() => {
+        reslove(true)
+      })
+      .catch((err) => {
+        reject(err)
+      })
+  })
+}
+
 defineExpose({
   submitFormData,
-  resetForm
+  resetForm,
+  fillForm,
+  encryptData,
+  backupFormData,
+  resumeFormData
 })
 </script>
 
 <template>
   <div class="formBox">
     <el-form
-      class="form"
+      :class="props.inline ? '' : 'form'"
       :inline="props.inline"
       :label-position="props.inline ? 'top' : 'left'"
-      :rules="formConfig.rules"
+      :rules="props.config.rules"
       :model="formData"
       ref="formRef"
-      :label-width="'120px'"
+      :label-width="'auto'"
+      :disabled="!props.disabled"
     >
-      <template v-for="item in formConfig.fieldsInfo">
-        <el-form-item
-          :prop="item.name"
-          :label="item.cnName"
-          style="margin-right: 150px; margin-bottom: 20px"
-        >
+      <template v-for="item in props.config.fieldsInfo">
+        <el-form-item :prop="item.name" :label="item.cnName" class="formItem">
           <el-input
             style="width: 300px"
             v-if="item.type === FormFieldType.INPUT"
@@ -107,7 +168,21 @@ defineExpose({
 </template>
 
 <style scoped>
-.form {
+.formBox {
   width: 100%;
+  box-sizing: border-box;
+}
+
+.form {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.formItem {
+  /* width: 50%; */
+  /* display: flex; */
+  justify-content: center;
 }
 </style>

+ 13 - 4
src/hooks/useRequest.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:24:06
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-03 14:09:03
+ * @LastEditTime: 2024-09-05 17:12:43
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useRequest.ts
  * @Description:
  *
@@ -16,8 +16,8 @@ import type { AxiosResponse } from 'axios'
 import type { ResponseInfo } from '@/types/res'
 
 export function useRequest() {
-  const baseIp = 'http://server.ichunhao.cn' // 线上
-  // const baseIp = 'http://192.168.1.139:8000' // 本地
+  // const baseIp = 'http://server.ichunhao.cn' // 线上
+  const baseIp = 'http://192.168.1.139:8000' // 本地
 
   const AllApi = {
     // mock: `http://127.0.0.1:8003/mock`,
@@ -42,7 +42,16 @@ export function useRequest() {
     userTrendsOverview: `${baseIp}/user/userTrendsOverview`, //用户趋势 -总览
     userDataTrades: `${baseIp}/user/dataTrades`, //用户趋势 -数据趋势
     userDataTradesDetail: `${baseIp}/user/dataTradesDetail`, //用户趋势 -数据趋势详情
-    userRemainDataBydDay: `${baseIp}/user/remainDataBydDay` //用户留存数据
+    userRemainDataBydDay: `${baseIp}/user/remainDataBydDay`, //用户留存数据
+
+    // 事件相关
+    gameActionList: `${baseIp}/user/gameActionList`, // 游戏事件列表
+    gameActionDetail: `${baseIp}/user/gameActionDetail`, // 事件详情
+    updateGameAction: `${baseIp}/user/updateGameAction`, // 更新游戏事件
+    gameActionOptionList: `${baseIp}/user/gameActionOptionList`, // 获取事件参数列表
+    addGameActionOption: `${baseIp}/user/addGameActionOption`, // 新增事件参数
+    updateGameActionOption: `${baseIp}/user/updateGameActionOption`, // 更新事件参数
+    deleteGameActionOption: `${baseIp}/user/deleteGameActionOption` // 删除事件参数
   }
 
   const analysisResCode = (data: AxiosResponse, kind?: string): Promise<ResponseInfo> => {

+ 10 - 3
src/router/index.ts

@@ -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 16:29:30
+ * @LastEditTime: 2024-09-05 17:23:03
  * @FilePath: \Game-Backstage-Management-System\src\router\index.ts
  * @Description:
  *
@@ -16,9 +16,16 @@ import LoginRoutes from './login'
 import AppManage from './appManage'
 
 const routes = [
-  ...HomeRoutes,
   ...LoginRoutes,
-  ...AppManage,
+  // ...HomeRoutes,
+  // ...AppManage,
+  {
+    path: '/index',
+    name: 'Index',
+    redirect: '/index/home/overView',
+    component: () => import('@/views/Index.vue'),
+    children: [...HomeRoutes, ...AppManage]
+  },
   {
     path: '/',
     redirect: '/home/overView'

+ 5 - 3
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-04 10:45:49
+ * @LastEditTime: 2024-09-05 15:51:33
  * @FilePath: \Game-Backstage-Management-System\src\types\table.ts
  * @Description:
  *
@@ -58,7 +58,8 @@ export enum FieldSpecialEffectType {
   TEXT = 'text',
   TRANSLATE = 'translate',
   SWITCH = 'switch',
-  DROPDOWN = 'dropdown'
+  DROPDOWN = 'dropdown',
+  STATE = 'state'
 }
 
 // 表格字段信息格式
@@ -66,9 +67,10 @@ export interface TableFieldInfo {
   name: string
   cnName: string
   isShow: boolean
+  needSort: boolean
   specialEffect?: {
     type: FieldSpecialEffectType
-    othnerInfo: any
+    othnerInfo?: any
   }
 }
 

+ 271 - 64
src/views/AppManage/EventDetailsView.vue

@@ -4,7 +4,7 @@ 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 { ElMessageBox, type FormRules } from 'element-plus'
 import type { FormConfig } from '@/types/form'
 import {
   type TablePaginationSetting,
@@ -15,9 +15,24 @@ import type { DialogConfig } from '@/types/dialog'
 import { reactive, onMounted, ref } from 'vue'
 import { useRoute } from 'vue-router'
 import { useRequest } from '@/hooks/useRequest'
+import axiosInstance from '@/utils/axios/axiosInstance'
 
-const { AllApi } = useRequest()
+const { AllApi, analysisResCode } = useRequest()
 
+// 事件表单对象
+const eventFormRef = ref()
+
+// 参数表格ref对象
+const optionTableRef = ref()
+
+// 是否是新增触发
+const isAdd = ref(true)
+
+// 事件ID,这里的actionID,在获取事件详情的时候传入的字段是id字段,但是在获取
+// 参数列表的时候,传入的是actionid字段。。。。。。
+const actionId = ref()
+
+// 编辑状态
 const eventEditState = ref(false)
 
 // 属性对话框
@@ -25,15 +40,15 @@ const attrDialog = ref()
 
 // 表单规则字段
 const ruleForm = reactive({
-  eventID: '',
-  eventDisplayName: '',
-  useageStatus: '',
-  triggerTimingDesc: ''
+  actionId: '',
+  actionName: '',
+  status: 1,
+  remark: ''
 })
 
 // 表单规则
 const rules = reactive<FormRules<typeof ruleForm>>({
-  eventID: [
+  actionId: [
     { required: true, message: '事件ID是必填项', trigger: 'blur' },
     {
       min: 1,
@@ -42,8 +57,8 @@ const rules = reactive<FormRules<typeof ruleForm>>({
       trigger: 'blur'
     }
   ],
-  eventDisplayName: [
-    { required: true, message: '事件显示名是必填项', trigger: 'blur' },
+  actionName: [
+    { required: true, message: '事件名是必填项', trigger: 'blur' },
     {
       min: 5,
       max: 10,
@@ -51,12 +66,12 @@ const rules = reactive<FormRules<typeof ruleForm>>({
       trigger: 'blur'
     }
   ],
-  useageStatus: [{ required: true, message: '使用状态是必填项', trigger: 'change' }],
-  triggerTimingDesc: [
+  status: [{ required: true, message: '使用状态是必填项', trigger: 'change' }],
+  remark: [
     {
       min: 1,
       max: 255,
-      message: '描述长度必须在1到255个字符之间',
+      message: '备注长度必须在1到255个字符之间',
       trigger: 'blur'
     }
   ]
@@ -65,17 +80,17 @@ const rules = reactive<FormRules<typeof ruleForm>>({
 // 表单字段信息
 const FormFields: Array<FormField> = [
   {
-    name: 'eventID',
+    name: 'actionId',
     cnName: '事件ID',
     type: FormFieldType.INPUT
   },
   {
-    name: 'eventDisplayName',
-    cnName: '事件显示',
+    name: 'actionName',
+    cnName: '事件',
     type: FormFieldType.INPUT
   },
   {
-    name: 'useageStatus',
+    name: 'status',
     cnName: '使用状态',
     type: FormFieldType.SELECT,
     otherOptions: {
@@ -83,30 +98,30 @@ const FormFields: Array<FormField> = [
       options: [
         {
           name: 'disabled',
-          label: '禁用',
-          value: false
+          label: '禁用',
+          value: 0
         },
         {
           name: 'abled',
-          label: '启用',
-          value: true
+          label: '启用',
+          value: 1
         }
       ]
     }
   },
   {
-    name: 'triggerTimingDesc',
-    cnName: '触发时机描述',
+    name: 'remark',
+    cnName: '备注',
     type: FormFieldType.RICHTEXT,
     otherOptions: {
-      placeholder: '请输入描述'
+      placeholder: '请输入备注'
     }
   }
 ]
 
 // 表单请求参数
 const formReq = reactive<ReqConfig>({
-  url: AllApi.mockEvent,
+  url: AllApi.updateGameAction,
   otherOptions: {}
 })
 
@@ -128,67 +143,122 @@ const pageConfig = reactive<TablePaginationSetting>({
 // 表格字段信息
 const tableFieldConfig = reactive<Array<TableFieldInfo>>([
   {
-    name: 'attrID',
-    cnName: '参数ID',
-    isShow: true
+    name: 'actionId',
+    cnName: '事件ID',
+    isShow: true,
+    needSort: true
   },
   {
-    name: 'useStatus',
-    cnName: '使用状态',
+    name: 'createdAt',
+    cnName: '创建时间',
+    isShow: true,
+    needSort: true
+  },
+  {
+    name: 'id',
+    cnName: 'ID',
     isShow: true,
+    needSort: true
+  },
+  {
+    name: 'optionId',
+    cnName: '选项ID',
+    isShow: true,
+    needSort: true
+  },
+  {
+    name: 'optionName',
+    cnName: '选项名称',
+    isShow: true,
+    needSort: true
+  },
+  {
+    name: 'optionType',
+    cnName: '选项类型',
+    isShow: true,
+    needSort: true,
     specialEffect: {
+      type: FieldSpecialEffectType.TRANSLATE,
       othnerInfo: {
-        text: ['已使用', '已弃用']
+        translateText: {
+          string: '字符串',
+          int: '数字',
+          array: '数组'
+        }
+      }
+    }
+  },
+  {
+    name: 'status',
+    cnName: '使用状态状态',
+    isShow: true,
+    needSort: true,
+    specialEffect: {
+      othnerInfo: {
+        text: ['启用', '禁用']
       },
-      type: FieldSpecialEffectType.DROPDOWN
+      type: FieldSpecialEffectType.STATE
     }
+  },
+  {
+    name: 'updatedAt',
+    cnName: '更新时间',
+    isShow: true,
+    needSort: true
   }
 ])
 
 // 表格请求配置
 const tableReqConfig = reactive<ReqConfig>({
-  url: AllApi.mockEvent,
-  otherOptions: {
-    eventID: ''
-  }
+  url: AllApi.gameActionOptionList,
+  otherOptions: {}
 })
 
 // 对话框请求参数
 const dialogReqConfig = reactive<ReqConfig>({
-  url: AllApi.mockEvent,
-  otherOptions: {}
+  url: AllApi.addGameActionOption,
+  otherOptions: {
+    formData: {}
+  }
 })
 
 // 对话框表单规则字段
 const dilogRuleForm = reactive({
-  attrID: '',
-  useStatus: false
+  optionName: '',
+  optionId: '',
+  status: 1,
+  optionType: ''
 })
 
 // 表单规则
 const dialogRules = reactive<FormRules<typeof dilogRuleForm>>({
-  attrID: [
-    { required: true, message: '属性ID是必填项', trigger: 'blur' },
+  optionName: [{ required: true, message: '选项名称是必填项', trigger: 'change', type: 'string' }],
+  optionId: [{ required: true, message: '选项ID是必填项', trigger: 'change', type: 'string' }],
+  status: [{ required: true, message: '选项状态是必填项', trigger: 'change', type: 'number' }],
+  optionType: [
     {
-      min: 1,
-      max: 20,
-      message: '属性ID长度必须在1到20个字符之间',
-      trigger: 'blur'
+      required: true,
+      message: '选项类型是必填项',
+      trigger: 'change'
     }
-  ],
-  useStatus: [{ required: true, message: '使用状态是必填项', trigger: 'change' }]
+  ]
 })
 
 // 表单字段信息
 const dialogFormFields: Array<FormField> = [
   {
-    name: 'attrID',
-    cnName: '属性ID',
+    name: 'optionName',
+    cnName: '选项名称',
     type: FormFieldType.INPUT
   },
   {
-    name: 'useStatus',
-    cnName: '使用状态',
+    name: 'optionId',
+    cnName: '选项ID',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'status',
+    cnName: '选项状态',
     type: FormFieldType.SELECT,
     otherOptions: {
       placeholder: '请选择状态',
@@ -196,12 +266,37 @@ const dialogFormFields: Array<FormField> = [
         {
           name: 'disabled',
           label: '已禁用',
-          value: false
+          value: 0 // 对应int型状态
         },
         {
           name: 'abled',
           label: '已启用',
-          value: true
+          value: 1 // 对应int型状态
+        }
+      ]
+    }
+  },
+  {
+    name: 'optionType',
+    cnName: '选项类型',
+    type: FormFieldType.SELECT,
+    otherOptions: {
+      placeholder: '请选择类型',
+      options: [
+        {
+          name: 'string',
+          label: '字符串',
+          value: 'string'
+        },
+        {
+          name: 'int',
+          label: '整数',
+          value: 'int'
+        },
+        {
+          name: 'array',
+          label: '数组',
+          value: 'array'
         }
       ]
     }
@@ -216,9 +311,35 @@ const dialogConfig = reactive<DialogConfig>({
   fieldsInfo: dialogFormFields
 })
 
+/**
+ * @description: 获取事件详情
+ * @param {*} id 这个id还是路由参数中query传过来的id
+ * @return {*}
+ */
+const getEventInfo = (id: number) => {
+  axiosInstance
+    .post(AllApi.gameActionDetail, { id })
+    .then((data) => {
+      analysisResCode(data).then((info) => {
+        let result = info.data
+        let updateEventField = {
+          gid: '',
+          actionId: '',
+          actionName: '',
+          remark: '',
+          status: 1
+        }
+        eventFormRef.value.fillForm(Object.assign(updateEventField, { ...result }))
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
+
 // 新增属性
 const addNewAttr = () => {
-  attrDialog.value.changeVisable()
+  attrDialog.value.addForm()
 }
 
 // 改变编辑状态
@@ -227,35 +348,91 @@ const changeEditState = (state: boolean) => {
 }
 
 /**
- * @description: 取消编辑
+ * @description: 取消编辑,同时把数据恢复
  * @return {*}
  */
 const cancelEdit = () => {
   changeEditState(false)
+  eventFormRef.value.resumeFormData()
 }
 
 /**
- * @description: 保存编辑
+ * @description: 保存编辑,提交表单
  * @return {*}
  */
 const saveEdit = () => {
   changeEditState(false)
+  eventFormRef.value.submitFormData()
 }
 
 /**
- * @description: 开始编辑
+ * @description: 开始编辑,备份一份现有的表单数据
  * @return {*}
  */
 const startEdit = () => {
   changeEditState(true)
+  eventFormRef.value.backupFormData()
 }
 
-onMounted(() => {
+/**
+ * @description: 接受到表单提交事件后,需要去重新请求一下表格的数据
+ * @return {*}
+ */
+const formSub = () => {
+  optionTableRef.value.getData()
+}
+
+/**
+ * @description: 修改选项
+ * @return {*}
+ */
+const editOption = (row: any) => {
+  console.log(row)
+  attrDialog.value.editForm(row, AllApi.updateGameActionOption)
+}
+
+/**
+ * @description: 删除选项
+ * @return {*}
+ */
+const delOption = (row: any) => {
+  ElMessageBox.confirm('确认删除该配置吗', '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(() => {
+      optionTableRef.value.deleteRow(AllApi.deleteGameActionOption, { id: row.id })
+    })
+    .catch(() => {})
+}
+
+/**
+ * @description: 初始化一些参数
+ *  加载时如果有actionid,则代表是修改页面,需要去请求数据,没有则是新增页面
+ * 这里的actionid,是返回参数中id、actionID中的前者,在这个页面会被用于请求事件详情和相关的参数列表
+ * 在请求的时候,他们的名字不同,但传入的都是这个id
+ * @return {*}
+ */
+const initParams = () => {
   const routes = useRoute()
-  let query = routes.query
+  let query_actionId = routes.query.id as string
+  if (query_actionId) {
+    isAdd.value = false
+    actionId.value = parseInt(query_actionId)
+    getEventInfo(parseInt(actionId.value))
+    tableReqConfig.otherOptions.actionId = actionId.value
+    dialogReqConfig.otherOptions.formData.actionId = actionId.value
+  }
+}
 
-  console.log(query)
-})
+initParams()
+
+/**
+ * @description:
+ * @return {*}
+ */
+onMounted(() => {})
 </script>
 
 <template>
@@ -275,7 +452,12 @@ onMounted(() => {
   </div>
   <div class="eventForm">
     <div class="formBox">
-      <Form :inline="true" :config="formConfig"></Form>
+      <Form
+        :disabled="eventEditState"
+        ref="eventFormRef"
+        :inline="true"
+        :config="formConfig"
+      ></Form>
     </div>
   </div>
   <div class="attrList">
@@ -285,15 +467,40 @@ onMounted(() => {
     <div class="list">
       <Table
         :need-left-tools="true"
+        :open-page-query="true"
         :pagination-config="pageConfig"
         :table-fields-info="tableFieldConfig"
         :request-config="tableReqConfig"
         @add-new-item="addNewAttr"
-      ></Table>
+        ref="optionTableRef"
+      >
+        <template #tableOperation>
+          <el-table-column label="操作" align="center">
+            <template #default="scope">
+              <el-button
+                size="small"
+                type="primary"
+                @click="editOption(scope.row)"
+                class="operationBtn"
+              >
+                修改
+              </el-button>
+              <el-button
+                size="small"
+                type="danger"
+                @click="delOption(scope.row)"
+                class="operationBtn"
+              >
+                删除
+              </el-button>
+            </template>
+          </el-table-column>
+        </template>
+      </Table>
     </div>
   </div>
   <div class="addAttrDialo">
-    <Dialog ref="attrDialog" :config="dialogConfig"></Dialog>
+    <Dialog ref="attrDialog" :config="dialogConfig" @form-submit="formSub"></Dialog>
   </div>
 </template>
 

+ 2 - 67
src/views/AppManage/EventManageView.vue

@@ -2,80 +2,15 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-02 17:57:15
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-04 18:24:01
+ * @LastEditTime: 2024-09-05 12:28:01
  * @FilePath: \Game-Backstage-Management-System\src\views\AppManage\EventManageView.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
-import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
-import Table from '@/components/Table.vue'
-import { useRequest } from '@/hooks/useRequest'
+import { reactive, onMounted, computed } from 'vue'
 
-import type { TablePaginationSetting, TableFieldInfo } from '@/types/table'
-import type { ReqConfig } from '@/types/dataAnalysis'
-
-import { reactive, onMounted, ref, computed } from 'vue'
-import { initLoadResouce } from '@/utils/resource'
-import { copyText } from '@/utils/common'
 import router from '@/router'
-import { useRoute } from 'vue-router'
-
-const { AllApi } = useRequest()
-
-// 表格分页设置
-const pagingConfig = reactive<TablePaginationSetting>({
-  limit: 15,
-  currentPage: 1,
-  total: 0,
-  pagesizeList: [15, 30]
-})
-
-// 表格字段信息
-const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
-  {
-    name: 'eventID',
-    cnName: '事件ID',
-    isShow: true
-  },
-  {
-    name: 'eventDisplayName',
-    cnName: '事件显示名',
-    isShow: true
-  },
-  {
-    name: 'displayStatus',
-    cnName: '显示状态',
-    isShow: true
-  },
-  {
-    name: 'useStatus',
-    cnName: '使用状态',
-    isShow: true
-  },
-  {
-    name: 'usageStatus',
-    cnName: '用量状态',
-    isShow: true
-  },
-  {
-    name: 'platform',
-    cnName: '平台',
-    isShow: true
-  },
-  {
-    name: 'creationTime',
-    cnName: '创建时间',
-    isShow: true
-  }
-])
-
-// 表格请求配置
-const requestConfig = reactive<ReqConfig>({
-  url: AllApi.mockEvent,
-  otherOptions: {}
-})
-
 const breadcrumbList = reactive<Array<string>>([])
 
 // 是否可点击

+ 49 - 117
src/views/AppManage/EventMangeTable.vue

@@ -30,167 +30,96 @@ const pagingConfig = reactive<TablePaginationSetting>({
 // 表格字段信息
 const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
   {
-    name: 'eventID',
-    cnName: '事件ID',
-    isShow: true
+    name: 'gid',
+    cnName: '游戏ID',
+    isShow: true,
+    needSort: false
   },
   {
-    name: 'eventDisplayName',
-    cnName: '事件显示名',
-    isShow: true
+    name: 'actionId',
+    cnName: '事件ID',
+    isShow: true,
+    needSort: false
   },
   {
-    name: 'displayStatus',
-    cnName: '显示状态',
+    name: 'actionName',
+    cnName: '事件名称',
     isShow: true,
-    specialEffect: {
-      type: FieldSpecialEffectType.SWITCH,
-      othnerInfo: {}
-    }
+    needSort: false
   },
   {
-    name: 'useStatus',
-    cnName: '使用状态',
+    name: 'status',
+    cnName: '事件状态',
     isShow: true,
+    needSort: false,
     specialEffect: {
-      type: FieldSpecialEffectType.DROPDOWN,
+      type: FieldSpecialEffectType.STATE,
       othnerInfo: {
-        text: ['已使用', '已用']
+        text: ['已使用', '已用']
       }
     }
   },
   {
-    name: 'usageStatus',
-    cnName: '用量状态',
+    name: 'remark',
+    cnName: '备注',
     isShow: true,
-    specialEffect: {
-      type: FieldSpecialEffectType.TRANSLATE,
-      othnerInfo: {
-        icon: true,
-        translateText: {
-          true: '正常',
-          false: '弃用'
-        }
-      }
-    }
+    needSort: false
   },
   {
-    name: 'platform',
-    cnName: '平台',
+    name: 'createdAt',
+    cnName: '创建时间',
     isShow: true,
-    specialEffect: {
-      type: FieldSpecialEffectType.TRANSLATE,
-      othnerInfo: {
-        translateText: {
-          wx: '微信',
-          tt: '抖音',
-          web: 'Web'
-        }
-      }
-    }
+    needSort: true
   },
   {
-    name: 'creationTime',
-    cnName: '创建时间',
-    isShow: true
+    name: 'updatedAt',
+    cnName: '更新时间',
+    isShow: true,
+    needSort: false
   }
 ])
 
 // 表格请求配置
 const requestConfig = reactive<ReqConfig>({
-  url: AllApi.mockEvent,
+  url: AllApi.gameActionList,
   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> = [
+const eventStatus: Array<SelectInfo> = [
   {
     name: 'all',
     cnName: '全部',
-    value: 'all'
+    value: ''
   },
   {
-    name: 'inUse',
-    cnName: '使用',
-    value: 'inUse'
+    name: 'use',
+    cnName: '已使用',
+    value: '1'
   },
   {
-    name: 'noUse',
+    name: 'nouse',
     cnName: '已弃用',
-    value: 'noUse'
+    value: '0'
   }
 ]
 
 // 查询字段设置
 const queryInfo: Array<QueryInfo> = [
   {
-    name: 'evnetName',
+    name: 'search',
     label: '',
     type: FilterType.INPUT,
     placeholder: '请输入事件名搜索',
     default: ''
   },
   {
-    name: 'evnetType',
-    label: '事件类型',
+    name: 'status',
+    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'
+    otherOption: eventStatus,
+    default: ''
   }
 ]
 
@@ -276,12 +205,14 @@ const dialogInfo = reactive<DialogConfig>({
  * @return {*}
  */
 const viewDetails = (row: any) => {
-  router.push({
-    name: 'EventDetail',
-    query: {
-      eventID: row.eventID
-    }
-  })
+  if (row.id) {
+    router.push({
+      name: 'EventDetail',
+      query: { id: row.id }
+    })
+  } else {
+    throw new Error('no id')
+  }
 }
 
 /**
@@ -289,7 +220,7 @@ const viewDetails = (row: any) => {
  * @return {*}
  */
 const addNewEvent = () => {
-  eventDialog.value.changeVisable()
+  eventDialog.value.addForm()
 }
 
 onMounted(() => {})
@@ -300,6 +231,7 @@ onMounted(() => {})
     <Table
       :need-rowindex="false"
       :request-config="requestConfig"
+      :open-page-query="true"
       :pagination-config="pagingConfig"
       :table-fields-info="tableFieldsInfo"
       :query-info="queryInfo"

+ 26 - 12
src/views/Home/Analysis/KeepView.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-27 17:11:23
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-08-31 10:46:51
+ * @LastEditTime: 2024-09-05 17:06:19
  * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\KeepView.vue
  * @Description: 
  * 
@@ -58,57 +58,68 @@ const keepDataTableInfo = reactive<{
     {
       name: 'date',
       cnName: '日期',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: 'count',
       cnName: '用户数',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+1day',
       cnName: '+1日',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+2day',
       cnName: '+2日',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+3day',
       cnName: '+3日',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+4day',
       cnName: '+4日',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+5day',
       cnName: '+5日',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+6day',
       cnName: '+6日',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+7day',
       cnName: '+7日',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+14day',
       cnName: '+14日',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: '+30day',
       cnName: '+30日',
-      isShow: true
+      isShow: true,
+      needSort: false
     }
   ])
 })
@@ -171,6 +182,9 @@ const getTableData = () => {
           loading.value = false
         })
     })
+    .catch((err) => {
+      throw new Error(err)
+    })
 }
 
 /**

+ 14 - 7
src/views/Home/Analysis/UserTrendView.vue

@@ -156,37 +156,44 @@ const detailDataTableInfo = reactive<{
     {
       name: 'date',
       cnName: '日期',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: 'newUser',
       cnName: '新增用户',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: 'activeUser',
       cnName: '日活跃用户',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: 'activeUserWeek',
       cnName: '周活跃用户',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: 'activeUserMouth',
       cnName: '月活跃用户',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: 'activeStart',
       cnName: '启动次数',
-      isShow: true
+      isShow: true,
+      needSort: false
     },
     {
       name: 'avgTime',
       cnName: '平均使用时长',
-      isShow: true
+      isShow: true,
+      needSort: false
     }
   ])
 })

+ 83 - 76
src/views/Home/InfoManage/GameManageView.vue

@@ -1,31 +1,20 @@
 <script setup lang="ts">
-import { type TablePaginationSetting, type TableFieldInfo } from '@/types/table'
-import type { FormRules, FormInstance } from 'element-plus'
-
+import Dialog from '@/components/common/Dialog.vue'
 import Table from '@/components/Table.vue'
 
-import { reactive, ref } from 'vue'
+import { type TablePaginationSetting, type TableFieldInfo } from '@/types/table'
+import type { FormRules } from 'element-plus'
+import type { DialogConfig } from '@/types/dialog'
+import type { FormField } from '@/types/form'
+import { FormFieldType } from '@/types/form'
 
+import { reactive, ref } from 'vue'
 import { useRequest } from '@/hooks/useRequest'
-import { useDialog } from '@/hooks/useDialog'
 
 const { AllApi } = useRequest()
-const { dialogClose, submitDialog, handleEdit, addNeweItem } = useDialog()
-
-interface GameDialogFormData {
-  gid: string
-  wxAppid: string
-  wxSecret: string
-  ttAppid: string
-  ttSecret: string
-  gameName: string
-  appSecret: string
-}
 
 const gameTableRef = ref()
-
-// 游戏配置对话框对象
-const gameDialogFormRef = ref<FormInstance>()
+const gameDialogRef = ref()
 
 // 配置请求参数
 const requestConfig = reactive({
@@ -48,43 +37,41 @@ const filedsInfo = reactive<Array<TableFieldInfo>>([
   {
     name: 'gameName',
     cnName: '游戏名',
-    isShow: true
+    isShow: true,
+    needSort: false
   },
   {
     name: 'gid',
     cnName: '游戏ID',
-    isShow: true
+    isShow: true,
+    needSort: false
   },
   {
     name: 'ttAppid',
     cnName: '抖音App ID',
-    isShow: true
+    isShow: true,
+    needSort: false
   },
   {
     name: 'ttSecret',
     cnName: '抖音App Secret',
-    isShow: true
+    isShow: true,
+    needSort: false
   },
   {
     name: 'wxAppid',
     cnName: '微信App ID',
-    isShow: true
+    isShow: true,
+    needSort: false
   },
   {
     name: 'wxSecret',
     cnName: '微信App Secret',
-    isShow: true
+    isShow: true,
+    needSort: false
   }
 ])
 
-// 游戏配置对话框设置
-const dialogConfig = reactive({
-  dialogVisible: false,
-  title: '游戏配置',
-  formLabelWidth: '150px',
-  type: 0 // 0 是新增 1是修改
-})
-
 // 表单校验规则
 const gameFormRule = reactive({
   gameName: '',
@@ -177,23 +164,68 @@ const gameRules = reactive<FormRules<typeof gameFormRule>>({
   ]
 })
 
-// 对话框表单数据
-const gameFormData = reactive<GameDialogFormData>({
-  gid: '',
-  wxAppid: '',
-  wxSecret: '',
-  ttAppid: '',
-  ttSecret: '',
-  gameName: '',
-  appSecret: ''
+// 表单字段信息
+const dialogFormFields: Array<FormField> = [
+  {
+    name: 'gameName',
+    cnName: '游戏名',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'gid',
+    cnName: '游戏ID',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'ttAppid',
+    cnName: '抖音App ID',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'ttSecret',
+    cnName: '抖音App Secret',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'wxAppid',
+    cnName: '微信App ID',
+    type: FormFieldType.INPUT
+  },
+  {
+    name: 'wxSecret',
+    cnName: '微信App Secret',
+    type: FormFieldType.INPUT
+  }
+]
+
+/**
+ * @description: 游戏对话框提交
+ * @return {*}
+ */
+const gameDialogConfig = reactive<DialogConfig>({
+  title: '游戏配置',
+  rules: gameRules,
+  reqConfig: {
+    url: AllApi.addGame,
+    otherOptions: {
+      formData: {
+        appSecret: '6YJSuc50uJ18zj45'
+      }
+    }
+  },
+  fieldsInfo: dialogFormFields
 })
 
-// 游戏配置提交
-const submiteGameChange = () => {
-  gameFormData.appSecret = '6YJSuc50uJ18zj45'
-  submitDialog(gameDialogFormRef.value, dialogConfig, AllApi.addGame, gameFormData).then(() => {
-    gameTableRef.value.getData()
-  })
+const addNewItem = () => {
+  gameDialogRef.value.addForm()
+}
+
+const handleEdit = (row: any) => {
+  gameDialogRef.value.editForm(row)
+}
+
+const formSub = () => {
+  gameTableRef.value.getData()
 }
 </script>
 <template>
@@ -209,7 +241,7 @@ const submiteGameChange = () => {
       :table-fields-info="filedsInfo"
       :request-config="requestConfig"
       :pagination-config="paginationConfig"
-      @addNewItem="addNeweItem(dialogConfig)"
+      @addNewItem="addNewItem"
     >
       <template #tableOperation>
         <el-table-column label="操作" align="center">
@@ -217,7 +249,7 @@ const submiteGameChange = () => {
             <el-button
               size="small"
               type="primary"
-              @click="handleEdit(scope.row, gameFormData, dialogConfig)"
+              @click="handleEdit(scope.row)"
               class="operationBtn"
             >
               修改
@@ -227,32 +259,7 @@ const submiteGameChange = () => {
       </template>
     </Table>
     <div class="optionDialog">
-      <el-dialog
-        @close="dialogClose(gameDialogFormRef, dialogConfig)"
-        v-model="dialogConfig.dialogVisible"
-        :title="dialogConfig.title"
-      >
-        <el-form :rules="gameRules" :model="gameFormData" ref="gameDialogFormRef">
-          <template v-for="item in filedsInfo">
-            <el-form-item
-              :prop="item.name"
-              :label="item.cnName"
-              :label-width="dialogConfig.formLabelWidth"
-            >
-              <el-input
-                v-model="gameFormData[item.name as keyof GameDialogFormData]"
-                autocomplete="off"
-              />
-            </el-form-item>
-          </template>
-        </el-form>
-        <template #footer>
-          <div class="dialog-footer">
-            <el-button type="primary" @click="submiteGameChange()"> 确认 </el-button>
-            <el-button @click="dialogClose(gameDialogFormRef, dialogConfig)">取消</el-button>
-          </div>
-        </template>
-      </el-dialog>
+      <Dialog @form-submit="formSub" ref="gameDialogRef" :config="gameDialogConfig"></Dialog>
     </div>
   </div>
 </template>

+ 121 - 92
src/views/Home/InfoManage/PlayerManageView.vue

@@ -9,39 +9,33 @@ import {
   ColorType
 } from '@/types/table'
 
+import Dialog from '@/components/common/Dialog.vue'
 import Table from '@/components/Table.vue'
-import CryptoJS from 'crypto-js'
+
 import axiosInstance from '@/utils/axios/axiosInstance'
 
 import { onMounted, reactive, ref, watch } from 'vue'
 import { ElMessageBox } from 'element-plus'
-import type { FormRules, FormInstance } from 'element-plus'
+
+import type { FormRules } from 'element-plus'
+import type { FormField } from '@/types/form'
+import type { DialogConfig } from '@/types/dialog'
+import { FormFieldType } from '@/types/form'
 
 import { useTableStore } from '@/stores/useTable'
 import { useRequest } from '@/hooks/useRequest'
-import { useDialog } from '@/hooks/useDialog'
 import { useCommonStore } from '@/stores/useCommon'
 
 const { AllApi, analysisResCode } = useRequest()
 
 const tableStore = useTableStore()
 const commonStore = useCommonStore()
-const { dialogClose, submitDialog, handleEdit } = useDialog()
-
-// 对话框表单数据格式
-interface PlayerDialogFormData {
-  gid: string
-  openId: string
-  pf: string
-  option: string
-  userId: string
-}
 
 // 表格对象
 const playerTableRef = ref()
 
 // 游戏配置对话框对象
-const playerDialogFormRef = ref<FormInstance>()
+const playerDialogFormRef = ref()
 
 // 配置分页数据
 const paginationConfig: TablePaginationSetting = {
@@ -101,12 +95,14 @@ const filedsInfo = reactive<Array<TableFieldInfo>>([
   {
     name: 'gid',
     cnName: '游戏ID',
-    isShow: true
+    isShow: true,
+    needSort: false
   },
   {
     name: 'head',
     cnName: '头像',
     isShow: true,
+    needSort: false,
     specialEffect: {
       type: FieldSpecialEffectType.IMG,
       othnerInfo: {}
@@ -116,6 +112,7 @@ const filedsInfo = reactive<Array<TableFieldInfo>>([
     name: 'inBlack',
     cnName: '是否在黑名单',
     isShow: true,
+    needSort: false,
     specialEffect: {
       type: FieldSpecialEffectType.TAG,
       othnerInfo: {
@@ -127,17 +124,20 @@ const filedsInfo = reactive<Array<TableFieldInfo>>([
   {
     name: 'nickName',
     cnName: '昵称',
-    isShow: true
+    isShow: true,
+    needSort: false
   },
   {
     name: 'openId',
     cnName: 'Open ID',
-    isShow: true
+    isShow: true,
+    needSort: false
   },
   {
     name: 'option',
     cnName: '权限',
     isShow: true,
+    needSort: false,
     specialEffect: {
       type: FieldSpecialEffectType.TEXT,
       othnerInfo: {
@@ -150,6 +150,7 @@ const filedsInfo = reactive<Array<TableFieldInfo>>([
     name: 'pf',
     cnName: '平台',
     isShow: true,
+    needSort: false,
     specialEffect: {
       type: FieldSpecialEffectType.TRANSLATE,
       othnerInfo: {
@@ -164,17 +165,55 @@ const filedsInfo = reactive<Array<TableFieldInfo>>([
   {
     name: 'userId',
     cnName: '玩家ID',
-    isShow: true
+    isShow: true,
+    needSort: false
   }
 ])
 
-// 游戏配置对话框设置
-const dialogConfig = reactive({
-  dialogVisible: false,
-  title: '用户权限配置',
-  formLabelWidth: '150px',
-  type: 0 // 0 是新增 1是修改
-})
+// // 游戏配置对话框设置
+// const dialogConfig = reactive({
+//   dialogVisible: false,
+//   title: '用户权限配置',
+//   formLabelWidth: '150px',
+//   type: 0 // 0 是新增 1是修改
+// })
+
+// // 表单校验规则
+// const optionFormRule = reactive({
+//   option: ''
+// })
+
+// // 表单规则
+// const gameRules = reactive<FormRules<typeof optionFormRule>>({
+//   option: [
+//     { required: true, message: '请输入权限', trigger: 'blur' },
+//     { min: 1, max: 255, message: '最短1位,最长255位', trigger: 'blur' }
+//   ]
+// })
+
+// // 对话框表单数据
+// const optionFormData = reactive<PlayerDialogFormData>({
+//   gid: '',
+//   openId: '',
+//   pf: '',
+//   option: '',
+//   userId: ''
+// })
+
+// // 游戏配置提交
+// const submiteOptionChange = (isEncrypt: boolean = false) => {
+//   let option = optionFormData.option
+//   if (isEncrypt) {
+//     let message = `${optionFormData.gid}${optionFormData.userId}${optionFormData.pf}`
+//     option = CryptoJS.HmacMD5(message, optionFormData.option).toString()
+//   }
+//   submitDialog(playerDialogFormRef.value, dialogConfig, AllApi.addOption, {
+//     ...optionFormData,
+//     option
+//   }).then(() => {
+//     playerTableRef.value.getData()
+//   })
+// }
 
 // 表单校验规则
 const optionFormRule = reactive({
@@ -182,22 +221,44 @@ const optionFormRule = reactive({
 })
 
 // 表单规则
-const gameRules = reactive<FormRules<typeof optionFormRule>>({
+const optionRules = reactive<FormRules<typeof optionFormRule>>({
   option: [
     { required: true, message: '请输入权限', trigger: 'blur' },
     { min: 1, max: 255, message: '最短1位,最长255位', trigger: 'blur' }
   ]
 })
 
-// 对话框表单数据
-const optionFormData = reactive<PlayerDialogFormData>({
-  gid: '',
-  openId: '',
-  pf: '',
-  option: '',
-  userId: ''
+// 表单字段信息
+const dialogFormFields: Array<FormField> = [
+  {
+    name: 'option',
+    cnName: '权限',
+    type: FormFieldType.INPUT
+  }
+]
+
+/**
+ * @description: 游戏对话框提交
+ * @return {*}
+ */
+const optionDialogConfig = reactive<DialogConfig>({
+  title: '权限配置',
+  rules: optionRules,
+  reqConfig: {
+    url: AllApi.addOption,
+    otherOptions: {}
+  },
+  fieldsInfo: dialogFormFields
 })
 
+const handleEdit = (row: any) => {
+  playerDialogFormRef.value.editForm(row)
+}
+
+const formSub = () => {
+  playerTableRef.value.getData()
+}
+
 // 封禁/解封用户
 const blockedPlayer = (row: any) => {
   let url = row.inBlack ? AllApi.deleteUserToBlack : AllApi.addUserToBlack
@@ -212,33 +273,30 @@ const blockedPlayer = (row: any) => {
     confirmButtonText: '确认',
     cancelButtonText: '取消',
     type: 'warning'
-  }).then(() => {
-    axiosInstance.post(url, playerInfo).then((data) => {
-      analysisResCode(data)
-        .then((info) => {
-          console.log(info)
-          playerTableRef.value.getData()
-        })
-        .catch((err) => {
-          console.log(err)
-        })
-    })
   })
+    .then(() => {
+      axiosInstance.post(url, playerInfo).then((data) => {
+        analysisResCode(data)
+          .then((info) => {
+            console.log(info)
+            playerTableRef.value.getData()
+          })
+          .catch((err) => {
+            console.log(err)
+          })
+      })
+    })
+    .catch(() => {
+      // 在点击取消时他会抛出错误,这里需要去捕获一下,不然会在控制台出现
+    })
 }
 
-// 游戏配置提交
-const submiteOptionChange = (isEncrypt: boolean = false) => {
-  let option = optionFormData.option
-  if (isEncrypt) {
-    let message = `${optionFormData.gid}${optionFormData.userId}${optionFormData.pf}`
-    option = CryptoJS.HmacMD5(message, optionFormData.option).toString()
-  }
-  submitDialog(playerDialogFormRef.value, dialogConfig, AllApi.addOption, {
-    ...optionFormData,
-    option
-  }).then(() => {
-    playerTableRef.value.getData()
-  })
+/**
+ * @description: 加密字段
+ * @return {*}
+ */
+const encrypt = () => {
+  playerDialogFormRef.value.encrypt('option', true, ['gid', 'userId', 'pf'])
 }
 
 watch(
@@ -279,7 +337,7 @@ onMounted(() => {
             <el-button
               size="small"
               type="warning"
-              @click="handleEdit(scope.row, optionFormData, dialogConfig)"
+              @click="handleEdit(scope.row)"
               class="operationBtn"
             >
               修改权限
@@ -297,40 +355,11 @@ onMounted(() => {
       </template>
     </Table>
     <div class="optionDialog">
-      <el-dialog
-        @close="dialogClose(playerDialogFormRef, dialogConfig)"
-        v-model="dialogConfig.dialogVisible"
-        :title="dialogConfig.title"
-      >
-        <el-form :rules="gameRules" :model="optionFormData" ref="playerDialogFormRef">
-          <template v-for="item in filedsInfo">
-            <el-form-item
-              :prop="item.name"
-              :label="item.cnName"
-              :label-width="dialogConfig.formLabelWidth"
-              v-if="item.name === 'option'"
-            >
-              <el-input
-                v-model="optionFormData[item.name as keyof PlayerDialogFormData]"
-                autocomplete="off"
-              />
-            </el-form-item>
-          </template>
-        </el-form>
-        <template #footer>
-          <div class="dialog-footer">
-            <el-button class="subBtnItem" type="warning" @click="submiteOptionChange(true)"
-              >加密上传</el-button
-            >
-            <el-button class="subBtnItem" type="primary" @click="submiteOptionChange()">
-              普通上传
-            </el-button>
-            <el-button class="subBtnItem" @click="dialogClose(playerDialogFormRef, dialogConfig)"
-              >取消</el-button
-            >
-          </div>
+      <Dialog @form-submit="formSub" ref="playerDialogFormRef" :config="optionDialogConfig">
+        <template #otherBtn>
+          <el-button class="operationBtn" type="warning" @click="encrypt"> 加密上传 </el-button>
         </template>
-      </el-dialog>
+      </Dialog>
     </div>
   </div>
 </template>
@@ -348,6 +377,6 @@ onMounted(() => {
 }
 
 .operationBtn {
-  margin-right: 5%;
+  margin-right: 10px;
 }
 </style>

+ 468 - 0
src/views/Index.vue

@@ -0,0 +1,468 @@
+<!--
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-08-20 14:06:49
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-09-05 17:28:40
+ * @FilePath: \Game-Backstage-Management-System\src\views\Index.vue
+ * @Description: 
+ * 
+-->
+<script setup lang="ts">
+import { zhCn } from 'element-plus/es/locales.mjs'
+import { RouterView } from 'vue-router'
+import { onMounted, reactive, ref, computed, watch } from 'vue'
+import { useRoute } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import { getAllGameInfo } from '@/utils/table/table'
+import router from '@/router'
+import type { DropDownInfo } from '@/types/dataAnalysis'
+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 = computed(() => {
+  return route.meta.activeMenu
+})
+
+// 顶部导航栏信息
+const navBarMenuList = [
+  {
+    name: 'Home',
+    title: '应用分析'
+  },
+
+  {
+    name: 'AppManage',
+    title: '应用管理'
+  }
+]
+
+/**
+ * @description: 侧边栏折叠改变
+ * @return {*}
+ */
+const changeCollapse = () => {
+  isCollapse.value = !isCollapse.value
+}
+
+// 登出
+const logOut = () => {
+  ElMessage({
+    type: 'success',
+    message: '退出成功',
+    duration: 1000
+  })
+  localStorage.removeItem('token')
+  localStorage.removeItem('refreshToken')
+  router.push('/login')
+}
+
+// 游戏下拉选择框需要的数据
+const gameSelectInfo = reactive<DropDownInfo>({
+  defaultSelect: '1001',
+  title: '请选择游戏',
+  optionsList: []
+})
+
+// 游戏信息是否加载成功
+const gameinfoLoad = ref(false)
+
+/**
+ * @description: 更新整个页面的游戏选择
+ * @param {*} gid 游戏id
+ * @return {*}
+ */
+const changeGame = (gid: any) => {
+  selectInfo.gid = gid
+}
+
+/**
+ * @description: 头部导航栏改变
+ * @param {*} val 对应的name
+ * @return {*}
+ */
+const changeNavBar = (val: string) => {
+  navBarSelect.value = val
+
+  router.push(`/${val}`)
+  createdMenuList()
+  console.log(menuList)
+  let title = navBarMenuList.find((item) => item.name === val)?.title
+  if (title) {
+    siderBarOpened.value.splice(0, 1, title)
+  }
+}
+
+/**
+ * @description: 获取所有游戏列表
+ * @return {*}
+ */
+getAllGameInfo().then((data) => {
+  if (data) {
+    data.map((item) => {
+      gameSelectInfo.optionsList.push({
+        value: item.gid,
+        label: item.gameName
+      })
+    })
+  }
+  gameinfoLoad.value = true
+})
+
+// 资源的加载路径
+const resourceInfo: Record<string, string> = {
+  logo: `/img/logo.svg`,
+  defaultHead: `/img/default/defaultHead.png`
+}
+
+// 使用blob的资源路径信息
+const blobUrlInfo = reactive<Record<string, string>>({})
+
+// 侧边栏跳转路由的基本路由
+const basePath = ref<string | undefined>()
+
+/**
+ * @description: 创建侧边栏menu
+ * @return {*}
+ */
+const createdMenuList = () => {
+  let routes = router.options.routes // 获取路由信息
+  let indexRoutesChild = routes.find((item) => item.name === 'Index')?.children
+  let activeMenu = indexRoutesChild?.find((item) => {
+    return item.name === navBarSelect.value // 根据顶部导航栏的选中情况来选择选中哪个具体的路由信息,可以打印自己看一下
+  })
+  basePath.value = activeMenu?.path // 找到需要激活的菜单的路由,后续用来拼接需要跳转的路由
+  menuList.splice(0, menuList.length, ...(activeMenu?.children as Array<any>)) // 清空原来的路由信息,并且加入新选中的
+}
+
+/**
+ * @description: 当路由地址改变的时候,去获取最新的导航栏位置,并且重新生成侧边栏,不然刷新后,侧边栏会无法选中
+ * @param {*} router
+ * @return {*}
+ */
+watch(
+  () => [router.currentRoute.value.fullPath],
+  ([newFullPath]) => {
+    let routes = router.options.routes // 获取路由信息
+    let indexRoutesChild = routes.find((item) => item.name === 'Index')?.children
+    let activeMenu = indexRoutesChild?.find((item) => {
+      return newFullPath.includes(item.path) // 根据顶部导航栏的选中情况来选择选中哪个具体的路由信息,可以打印自己看一下
+    })
+    navBarSelect.value = activeMenu?.name as string
+    createdMenuList()
+  },
+  {
+    immediate: true
+  }
+)
+
+onMounted(() => {
+  // 去加载所有需要的资源
+  initLoadResouce(resourceInfo).then((data) => {
+    Object.assign(blobUrlInfo, data)
+  })
+})
+</script>
+
+<template>
+  <el-config-provider :locale="zhCn">
+    <div class="body">
+      <div class="navBarBox">
+        <div class="logoBox">
+          <el-image :fit="'fill'" class="logoImg" :src="blobUrlInfo.logo"></el-image>
+          <span>淳皓科技</span>
+        </div>
+
+        <div class="gameSelect">
+          <el-icon class="gameIcon" :size="20">
+            <icon-icon-park-game-three></icon-icon-park-game-three>
+          </el-icon>
+          <DropDownSelection
+            :default-select="gameSelectInfo.defaultSelect"
+            :title="gameSelectInfo.title"
+            :options-list="gameSelectInfo.optionsList"
+            :size="'default'"
+            @change-select="changeGame"
+          ></DropDownSelection>
+        </div>
+        <!-- 顶部导航栏 -->
+
+        <div class="navBarMenu">
+          <el-menu
+            :default-active="navBarSelect"
+            class="el-menu-demo"
+            mode="horizontal"
+            @select="changeNavBar"
+          >
+            <el-menu-item
+              v-for="item in navBarMenuList"
+              class="navBarMenuItem"
+              :index="item.name"
+              >{{ item.title }}</el-menu-item
+            >
+          </el-menu>
+        </div>
+        <div class="headPortraitBox">
+          <el-popover popper-class="headPopper" placement="bottom-end" trigger="click">
+            <template #reference>
+              <el-image class="headPortrait" :src="blobUrlInfo.defaultHead"></el-image>
+            </template>
+            <div class="userTools">
+              <span class="userToolsItem" @click="logOut">
+                <icon-material-symbols-light-logout></icon-material-symbols-light-logout>
+                <span> 退出登录</span>
+              </span>
+            </div>
+          </el-popover>
+        </div>
+      </div>
+      <!-- 侧边栏 -->
+      <div class="sideBarBox">
+        <el-menu
+          :default-active="defaultActive"
+          class="sideBar"
+          :collapse="isCollapse"
+          ref="siderBar"
+        >
+          <template v-for="(item, index) in menuList">
+            <el-sub-menu :index="`${index}`" v-if="item.children && item.showChild">
+              <template #title>
+                <el-icon><component :is="item.icon"></component></el-icon>
+                <span>{{ item.cnName }}</span>
+              </template>
+              <router-link
+                style="text-decoration: none"
+                v-for="val in item.children"
+                :to="{ path: basePath + '/' + item.path + '/' + val.path }"
+                :key="index"
+              >
+                <el-menu-item :index="val.path">{{ val.cnName }}</el-menu-item>
+              </router-link>
+            </el-sub-menu>
+
+            <router-link
+              style="text-decoration: none"
+              v-else
+              :to="{ path: basePath + '/' + item.path }"
+              :key="index"
+            >
+              <el-menu-item :index="item.path">
+                <el-icon><component :is="item.icon" /></el-icon>
+                <template #title>
+                  <span class="menuTitle">{{ item.cnName }}</span>
+                </template>
+              </el-menu-item>
+            </router-link>
+          </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>
+            <component
+              :is="Component"
+              :key="route.path"
+              v-if="route.meta.needKeepAlive == true"
+            ></component>
+          </keep-alive>
+          <component
+            :is="Component"
+            :key="route.path"
+            v-if="route.meta.needKeepAlive == false"
+          ></component>
+        </router-view>
+      </div>
+    </div>
+  </el-config-provider>
+</template>
+
+<style scoped>
+.body {
+  width: 100%;
+  display: flex;
+  height: 100vh;
+}
+
+/* 设置宽度后,content无法适应宽度,只能去间接的调整内部元素的宽度 */
+.sideBarBox {
+  position: relative;
+  /* width: 12%; */
+  z-index: 1;
+  height: 93vh;
+  margin-top: 7vh;
+  top: 0;
+}
+
+.sideBar {
+  /* width: 12vw; */
+  height: 93vh;
+  position: relative;
+  overflow: scroll;
+}
+
+/* 设置弹出层的样式 */
+.el-popper > .logoText {
+  width: 100px;
+  font-size: 16px;
+  /* color: red; */
+}
+
+.logoImg {
+  display: flex;
+  align-items: center;
+  width: 33px;
+  /* margin-right: 20px; */
+  /* height: 50px; */
+}
+
+.logoText {
+  width: 80%;
+  height: 100%;
+  margin-left: 15%;
+  display: flex;
+  font-size: 18px;
+  align-items: center;
+  /* background-color: lightcoral; */
+}
+
+/* 主要用来调整整个menu的宽度 */
+.menuTitle {
+  margin-right: 40px;
+}
+
+.sideBarFold {
+  width: 5%;
+  height: 3%;
+  position: absolute;
+  right: 40px;
+  bottom: 20px;
+}
+
+.navBarBox {
+  position: fixed;
+  display: flex;
+  align-items: center;
+  width: 100vw;
+  z-index: 2;
+  height: 7vh;
+  top: 0;
+  background-color: white;
+  right: 0;
+  border-bottom: 1px solid gainsboro;
+}
+
+/* 调整LOGO */
+.logoBox {
+  box-sizing: border-box;
+  left: 30px;
+  position: relative;
+
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.gameSelect {
+  position: relative;
+
+  height: 80%;
+  display: flex;
+  align-items: center;
+  left: 5%;
+  display: flex;
+  align-items: center;
+}
+
+.gameIcon {
+  /* box-sizing: border-box; */
+  /* padding-right: 12px; */
+  margin-right: 12px;
+}
+
+.navBarMenu {
+  width: 60%;
+  position: relative;
+  left: 6%;
+}
+
+.headPortraitBox {
+  position: absolute;
+  right: 3%;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.userTools {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-around;
+  align-items: center;
+}
+
+.userToolsItem {
+  cursor: pointer;
+  width: 100%;
+  height: 4vh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  /* padding: 10px; */
+  margin: 2%;
+}
+
+.userToolsItem > span {
+  margin-left: 10%;
+}
+
+.userToolsItem:hover {
+  background-color: #f2f3f5;
+}
+
+.headPortrait {
+  cursor: pointer;
+  width: 50px;
+}
+
+.content {
+  /* flex-grow: 1; */
+  /* position: absolute; */
+
+  width: 100%;
+  /* height: 93%; */
+  margin-top: 7vh;
+  overflow: scroll;
+  background-color: #f2f3f5;
+  right: 0vw;
+  top: 0vh;
+}
+</style>
+
+<!-- 为了让popper-class生效,需要的单独写一份 -->
+<style>
+.headPopper {
+  padding: 0px !important;
+  border: 1px solid #e5e6eb;
+
+  background-color: white;
+}
+.el-menu--horizontal.el-menu {
+  border-bottom: none;
+}
+</style>