fxs 7 mēneši atpakaļ
vecāks
revīzija
c0838f3919
54 mainītis faili ar 1951 papildinājumiem un 1329 dzēšanām
  1. 4 0
      .env
  2. 1 1
      .eslintrc.cjs
  3. 13 0
      config/api.ts
  4. 9 0
      env.d.ts
  5. 2 0
      package.json
  6. 164 129
      src/components/Table.vue
  7. 22 13
      src/components/common/Dialog.vue
  8. 148 42
      src/components/common/WithIconSelect.vue
  9. 11 5
      src/components/dataAnalysis/DropDownSelection.vue
  10. 62 89
      src/components/dataAnalysis/HeaderCard.vue
  11. 42 46
      src/components/dataAnalysis/StatisticText.vue
  12. 12 11
      src/components/dataAnalysis/TemporalTrend.vue
  13. 4 2
      src/components/echarts/TimeLineChart.vue
  14. 21 9
      src/components/form/FileUpload.vue
  15. 8 2
      src/components/form/Form.vue
  16. 43 22
      src/components/form/MyInput.vue
  17. 2 2
      src/hooks/useAnalysis.ts
  18. 21 82
      src/hooks/useDialog.ts
  19. 24 27
      src/hooks/useForm.ts
  20. 1 1
      src/hooks/usePage.ts
  21. 67 49
      src/hooks/useRequest.ts
  22. 49 77
      src/hooks/useTable.ts
  23. 20 2
      src/main.ts
  24. 1 1
      src/router/home.ts
  25. 15 16
      src/router/index.ts
  26. 16 2
      src/router/login.ts
  27. 113 4
      src/stores/useCommon.ts
  28. 2 5
      src/stores/useTable.ts
  29. 5 2
      src/types/dataAnalysis.ts
  30. 1 1
      src/types/dialog.ts
  31. 7 2
      src/types/res.ts
  32. 4 1
      src/types/table.ts
  33. 12 15
      src/utils/axios/auth.ts
  34. 86 41
      src/utils/axios/axiosInstance.ts
  35. 54 2
      src/utils/common/index.ts
  36. 80 0
      src/utils/localStorage/localStorage.ts
  37. 14 22
      src/utils/resource/index.ts
  38. 37 21
      src/utils/table/table.ts
  39. 95 0
      src/utils/token/token.ts
  40. 3 3
      src/views/AppManage/BaseInfoView.vue
  41. 24 19
      src/views/AppManage/EventDetailsView.vue
  42. 270 129
      src/views/AppManage/EventManageView.vue
  43. 12 12
      src/views/AppManage/EventMangeTable.vue
  44. 52 36
      src/views/Home/Analysis/EventAnalysisDetail.vue
  45. 22 15
      src/views/Home/Analysis/EventAnalysisTable.vue
  46. 12 7
      src/views/Home/Analysis/EventAnalysisView.vue
  47. 17 62
      src/views/Home/Analysis/KeepView.vue
  48. 15 101
      src/views/Home/Analysis/UserTrendView.vue
  49. 46 10
      src/views/Home/InfoManage/GameManageView.vue
  50. 34 109
      src/views/Home/InfoManage/PlayerManageView.vue
  51. 16 28
      src/views/Home/Overview/OverView.vue
  52. 83 22
      src/views/Index.vue
  53. 45 29
      src/views/Login/LoginView.vue
  54. 8 1
      tsconfig.app.json

+ 4 - 0
.env

@@ -0,0 +1,4 @@
+# 测试服和本地开发
+VITE_API_URL_TEST='http://server.ichunhao.cn'
+# 线上
+VITE_API_URL_PRODUCT='http://service.ichunhao.cn'

+ 1 - 1
.eslintrc.cjs

@@ -3,7 +3,7 @@ require('@rushstack/eslint-patch/modern-module-resolution')
 
 module.exports = {
   root: true,
-  'extends': [
+  extends: [
     'plugin:vue/vue3-essential',
     'eslint:recommended',
     '@vue/eslint-config-typescript',

+ 13 - 0
config/api.ts

@@ -0,0 +1,13 @@
+let BASE_URL: string = ''
+
+switch (import.meta.env.MODE) {
+  case 'production':
+    BASE_URL = import.meta.env.VITE_API_URL_PRODUCT
+    break
+  case 'test':
+    BASE_URL = import.meta.env.VITE_API_URL_TEST
+    break
+  default:
+    BASE_URL = import.meta.env.VITE_API_URL_TEST // 默认为开发环境
+}
+export default BASE_URL

+ 9 - 0
env.d.ts

@@ -1 +1,10 @@
 /// <reference types="vite/client" />
+interface ImportMetaEnv {
+  readonly VITE_API_URL_TEST: string
+  readonly VITE_API_URL_PRODUCT: string
+  // 更多环境变量...
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv
+}

+ 2 - 0
package.json

@@ -6,6 +6,8 @@
   "scripts": {
     "dev": "vite --host 0.0.0.0",
     "build": "run-p type-check \"build-only {@}\" --",
+    "build:test": "vite build --mode test",
+    "build:prod": "vite build --mode production",
     "preview": "vite preview",
     "build-only": "vite build",
     "type-check": "vue-tsc --build --force",

+ 164 - 129
src/components/Table.vue

@@ -2,30 +2,34 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 18:16:18
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-18 14:45:33
- * @FilePath: \Game-Backstage-Management-System\src\components\Table.vue
+ * @LastEditTime: 2024-10-16 11:15:24
+ * @FilePath: \Quantity-Creation-Management-Systemc:\Users\NINGMEI\Desktop\Game-Backstage-Management-System\src\components\Table.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
 import type { PropsParams, TablePaginationSetting } from '@/types/table'
 import type { ReqConfig } from '@/types/dataAnalysis'
+import type { TableFieldInfo } from '@/types/table'
+import type { FormInstance } from 'element-plus'
+
 import { FilterType, FieldSpecialEffectType } from '@/types/table'
 import { initLoadResouce } from '@/utils/resource'
 import { fuzzySearch } from '@/utils/common'
-
+import { throttleFunc } from '@/utils/common'
 import { computed, onMounted, reactive, ref, toRaw, watch } from 'vue'
 import { useTable } from '@/hooks/useTable'
+import { useRequest } from '@/hooks/useRequest'
 
 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'
 
 const { analysisResCode } = useRequest()
 
+// 节流的延迟时间
+const throttleTime = 500
+
 // 表格工具图标大小
 const toolsIconSize = ref(25)
 
@@ -41,7 +45,8 @@ const props = withDefaults(defineProps<PropsParams>(), {
   openFilterQuery: false,
   openPageQuery: false,
   needUpload: false,
-  needDownLoad: false
+  needDownLoad: false,
+  openRemoteinquiry: false
 })
 
 // 父组件触发的方法
@@ -62,9 +67,12 @@ const queryFormData = reactive<{ [key: string]: any }>({})
 
 const backupQueryFormData = reactive<{ [key: string]: any }>({})
 
+// 备份其他的请求信息
+let backupReqOtherOptions = {}
+
 // 分页数据
-const paginationConfig2 = reactive<TablePaginationSetting>({
-  currentPage: 0,
+const paginationConfig = reactive<TablePaginationSetting>({
+  currentPage: 1,
   limit: 0,
   total: 0,
   pagesizeList: []
@@ -85,12 +93,12 @@ const resourceInfo: Record<string, string> = {
 const blobUrlInfo = reactive<Record<string, string>>({})
 
 // 一些公用方法
-const { getTableData } = useTable(tableData, paginationConfig2)
+const { getTableData } = useTable(tableData, paginationConfig)
 
 // 没有开启分页查询的时候使用的数据
 const tableDataNoPaging = computed(() => {
-  let curPage = paginationConfig2.currentPage
-  let limit = paginationConfig2.limit
+  let curPage = paginationConfig.currentPage
+  let limit = paginationConfig.limit
   let begin = curPage * limit - limit
   //这里不减一是因为,slice方法裁切是左闭右开数组
   let end = curPage * limit
@@ -116,17 +124,59 @@ const dateFieldsList = computed(() => {
 
 // 计算行号
 const computedRowIndex = (index: number) => {
-  return (paginationConfig2.currentPage - 1) * paginationConfig2.limit + index + 1
+  return (paginationConfig.currentPage - 1) * paginationConfig.limit + index + 1
 }
 
 // 改变页码
 const handleCurrentChange = (val: number) => {
-  paginationConfig2.currentPage = val
+  paginationConfig.currentPage = val
 }
 
 // 改变每页大小
 const handleSizeChange = (val: number) => {
-  paginationConfig2.limit = val
+  paginationConfig.limit = val
+}
+
+/**
+ * @description: 加载表格数据
+ * @return {*}
+ */
+const loadTableData = async () => {
+  return new Promise(async (resolve, reject) => {
+    loading.value = true
+    if (props.dataList) {
+      tableData.splice(0, tableData.length, ...props.dataList)
+
+      paginationConfig.total = props.paginationConfig.total
+      loading.value = false
+
+      resolve(true)
+    } else {
+      if (props.requestConfig) {
+        try {
+          // 如果开启了分页查询,那么要计算出需要展示的页码位置所对应的偏移量
+          // 同时要将查询的条数改为对应的用户选择的展示条数
+          if (props.openPageQuery) {
+            reqconfig.otherOptions.offset =
+              (paginationConfig.currentPage - 1) * paginationConfig.limit
+            reqconfig.otherOptions.limit = paginationConfig.limit
+          }
+          await getTableData(reqconfig.url, reqconfig.otherOptions, props.openPageQuery)
+          backupTableData.splice(0, backupTableData.length, ...tableData)
+          resolve(true)
+        } catch (err) {
+          console.log(err)
+          reject(err)
+        } finally {
+          loading.value = false
+        }
+      } else {
+        loading.value = false
+
+        throw new Error('no match requestConfig')
+      }
+    }
+  })
 }
 
 /**
@@ -136,82 +186,31 @@ const handleSizeChange = (val: number) => {
 const getData = () => {
   return new Promise(async (resolve, reject) => {
     try {
-      const loadTableData = async () => {
-        return new Promise((resolve, reject) => {
-          if (props.dataList) {
-            tableData.splice(0, tableData.length, ...props.dataList)
-
-            paginationConfig2.total = props.paginationConfig.total
-            loading.value = false
-            // emits('loadSuccess', tableData)
-
-            resolve(true)
-          } else {
-            loading.value = true
-            if (props.requestConfig) {
-              if (props.openPageQuery) {
-                // 如果开启了分页查询,那么要计算出需要展示的页码位置所对应的偏移量
-                // 同时要将查询的条数改为对应的用户选择的展示条数
-                reqconfig.otherOptions.offset =
-                  (paginationConfig2.currentPage - 1) * paginationConfig2.limit
-                reqconfig.otherOptions.limit = paginationConfig2.limit
-              }
-
-              // 查询时要根据是否开启分页查询传入对应参数
-              getTableData(reqconfig.url, reqconfig.otherOptions, props.openPageQuery)
-                .then(() => {
-                  // emits('loadSuccess', tableData)
-                  backupTableData.splice(0, backupTableData.length, ...tableData)
-
-                  resolve(true)
-                })
-                .catch((err) => {
-                  console.log(err)
-
-                  reject(err)
-                })
-                .finally(() => {
-                  loading.value = false
-                })
-            } else {
-              loading.value = false
-
-              throw new Error('no match requestConfig')
-            }
-          }
-        })
-      }
       // 等待数据加载完成
       await loadTableData()
-        .then(async () => {
-          if (props.needAverage) {
-            let rowData: any = {}
-            let oldList: Array<any> = JSON.parse(JSON.stringify(tableData))
-            Object.values(props.tableFieldsInfo).map((item, index) => {
-              let sum = oldList
-                .map((item) => item.count)
-                .reduce((accumulator, currentValue) => accumulator + currentValue, 0)
-              let averageList = oldList
-                .map((val) => val[item.name])
-                .filter((item) => item !== undefined)
-              if (index === 0) rowData[item.name] = '均值'
-              else if (item.name === 'count') rowData[item.name] = sum
-              else {
-                let num =
-                  averageList.reduce((accumulator, currentValue) => accumulator + currentValue, 0) /
-                  averageList.length
-
-                rowData[item.name] = isNaN(num) ? 0 : num.toFixed(2)
-              }
-            })
-            insertRow(0, rowData)
+      if (props.needAverage) {
+        let rowData: any = {}
+        let oldList: Array<any> = JSON.parse(JSON.stringify(tableData))
+        Object.values(props.tableFieldsInfo).map((item: TableFieldInfo, index: number) => {
+          let sum = oldList
+            .map((item) => item.count)
+            .reduce((accumulator, currentValue) => accumulator + currentValue, 0)
+          let averageList = oldList
+            .map((val) => val[item.name])
+            .filter((item) => item !== undefined)
+          if (index === 0) rowData[item.name] = '均值'
+          else if (item.name === 'count') rowData[item.name] = sum
+          else {
+            let num =
+              averageList.reduce((accumulator, currentValue) => accumulator + currentValue, 0) /
+              averageList.length
+
+            rowData[item.name] = isNaN(num) ? 0 : num.toFixed(2)
           }
-          resolve(true)
-        })
-        .catch((err) => {
-          console.log(err)
-          reject(err)
         })
+        insertRow(0, rowData)
+      }
+      resolve(true)
     } catch (err) {
       console.log(err)
       reject(err)
@@ -219,6 +218,9 @@ const getData = () => {
   })
 }
 
+// 包装一下获取数据
+const throttleGetData = throttleFunc(getData, 1000)
+
 /**
  * @description: 清空表格数据
  * @return {*}
@@ -232,11 +234,10 @@ const resetTableData = () => {
  * @return {*}
  */
 const queryTableData = () => {
-  if (props.requestConfig) {
+  if (props.openRemoteinquiry && props.requestConfig) {
     reqconfig.otherOptions = { ...props.requestConfig.otherOptions, ...queryFormData }
-  }
-  if (props.openPageQuery) getData()
-  else {
+    getData()
+  } else {
     let filteredTable = []
     // 过滤出来所有符合formData数据的条件
     filteredTable = backupTableData.filter((item) => {
@@ -255,14 +256,15 @@ const queryTableData = () => {
   }
 }
 
+// 把查询方法包装一下,节流
+const throttleQueryTableData = throttleFunc(queryTableData, throttleTime)
+
 /**
  * @description: 重置整个查询表单,重置后,再请求一次全部表格
  * @param {*} formEl  表单对象
  * @return {*}
  */
-const resetQueryForm = (formEl: FormInstance | undefined) => {
-  if (!formEl) return
-
+const resetQueryForm = (neewQuery: boolean = true) => {
   // 使用函数返回保存的备份信息,这样可以正确的给queryformdata赋值
   // JSON.stringify()第二个参数可以用来处理undefined的情况,第一个参数设置为_可以避免ts检查
   function resetFormData() {
@@ -272,10 +274,14 @@ const resetQueryForm = (formEl: FormInstance | undefined) => {
     return data
   }
   Object.assign(queryFormData, resetFormData())
+  reqconfig.otherOptions = backupReqOtherOptions // 要把请求的参数也重置一次,不然切换平台等操作,会带着原来的查询参数请求
 
-  queryTableData()
+  if (neewQuery) queryTableData()
 }
 
+// 把重置方法包装一下,节流
+const throttleResetQueryForm = throttleFunc(resetQueryForm, throttleTime)
+
 /**
  * @description: 在获取完数据后,插入均值行
  * @param {*} start 插入的位置
@@ -324,26 +330,24 @@ const tableCellStyle = (info: any) => {
 /**
  * @description: 监听litmit,currentpage的变化,改变后去重新请求数据
  * 如果是limit的变化,则需要把当前页置为1
- *
- *  对于Gid需要去监听props的,而不是本地的,因为是外部的改变
  * @return {*}
  */
-const changePageLimit = watch(
-  () => [paginationConfig2.limit, paginationConfig2.currentPage],
-  ([newLimit, newCurPage], [oldLimit, oldCruPage]) => {
+watch(
+  () => [paginationConfig.limit, paginationConfig.currentPage],
+  ([newLimit], [oldLimit]) => {
+    resetQueryForm(false)
+
     if (newLimit != oldLimit) {
       // 需要给分页按钮加上:current-page.sync="current_page" 配置,不然不生效
       // 当改变每页大小时把之前的缓存全部清除,重新开始
-      paginationConfig2.currentPage = 1
-      // resetTableData()
+      paginationConfig.currentPage = 1
     }
 
-    if (newCurPage != oldCruPage) paginationConfig2.currentPage = newCurPage
-
-    // if (newGid != oldGid) reqconfig.otherOptions.gid = newGid
-    // || newGid != oldGid
-
-    if (newLimit != oldLimit || !tableData[paginationConfig2.currentPage]) {
+    // 开启分页查询的情况下,改变limit或者没有这页的缓存的情况下,重新请求
+    if (
+      props.openPageQuery &&
+      (newLimit !== oldLimit || !tableData[paginationConfig.currentPage])
+    ) {
       getData()
     }
   },
@@ -358,16 +362,33 @@ watch(
   () => props.requestConfig?.otherOptions.gid,
   (newGid, oldGid) => {
     if (newGid != oldGid) {
+      resetQueryForm(false)
       reqconfig.otherOptions.gid = newGid
+
       getData()
     }
-  }
+  },
+  { deep: true }
+)
+
+// 监听pf
+const watchPf = watch(
+  () => props.requestConfig?.otherOptions.pf,
+  (newPf, oldPf) => {
+    if (newPf != oldPf) {
+      resetQueryForm(false)
+      reqconfig.otherOptions.pf = newPf
+      getData()
+    }
+  },
+  { deep: true }
 )
 
 // 监听传入的datalist的变化,然后去更新数据
 const changeDataList = watch(
   () => [props.dataList],
   () => {
+    resetQueryForm(false)
     getData()
   },
   {
@@ -379,6 +400,7 @@ const changeDataList = watch(
 const watchDateChange = watch(
   () => [props.requestConfig?.otherOptions.startTime, props.requestConfig?.otherOptions.endTime],
   () => {
+    resetQueryForm(false)
     getData()
   },
   { deep: true }
@@ -392,6 +414,9 @@ const createRowKey = () => {
   return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
 }
 
+// 没有pf取消掉
+if (!props.requestConfig?.otherOptions.pf) watchPf()
+
 //如果没有日期就取消掉
 if (!props.requestConfig?.otherOptions.startTime && !props.requestConfig?.otherOptions.endTime) {
   watchDateChange()
@@ -402,17 +427,12 @@ if (!props.dataList) {
   changeDataList()
 }
 
-// 没有开启分页查询就关闭掉这个监听
-if (!props.openPageQuery) {
-  changePageLimit()
-}
-
 /**
  * @description: 拷贝一份配置文件
  * @return {*}
  */
 const initpageConfig = () => {
-  Object.assign(paginationConfig2, props.paginationConfig)
+  Object.assign(paginationConfig, props.paginationConfig)
 }
 
 /**
@@ -432,6 +452,8 @@ const initFormData = () => {
     queryFormData[item.name] = item.default
     backupQueryFormData[item.name] = item.default
   })
+  // backupQueryFormData = JSON.parse(JSON.stringify(queryFormData))
+  Object.assign(backupQueryFormData, JSON.parse(JSON.stringify(queryFormData)))
 }
 
 /**
@@ -440,6 +462,7 @@ const initFormData = () => {
  * @return {*}
  */
 const tableSortChange = (data: { column: any; prop: string; order: any }) => {
+  resetQueryForm(false)
   let { order } = { ...data }
   if (order === 'ascending') order = 'asc'
   else if (order === 'descending') order = 'desc'
@@ -459,6 +482,7 @@ const deleteRow = (url: string, filedsInfo: any) => {
     .post(url, { ...filedsInfo })
     .then((data) => {
       analysisResCode(data).then(() => {
+        resetQueryForm(false)
         getData()
       })
     })
@@ -489,19 +513,22 @@ defineExpose({
   resetTableData,
   deleteRow,
   downLoadTable,
-  outGetTableData
+  outGetTableData,
+  resetQueryForm
 })
 
 onMounted(() => {
   initpageConfig()
   initReqConfig()
   initFormData()
+  backupReqOtherOptions = reqconfig.otherOptions // 备份一份请求参数
+  // registerWatchProps()
   if (props.loadingState !== undefined) {
     loading.value = props.loadingState
   }
-  // if (!props.openPageQuery) {
-  //   getData()
-  // }
+  // 这里会造成有的table页第一次进来刷新两次,因为有的页面gid或者平台等数据会变化,导致请求第二次
+  // 但是这里又必须请求一次,因为有的页面数据是没有变化的
+  getData()
   // 去加载所有需要的资源
   initLoadResouce(resourceInfo).then((data) => {
     Object.assign(blobUrlInfo, data)
@@ -525,7 +552,12 @@ onMounted(() => {
           :label-position="'left'"
         >
           <!-- 所有的input查询框 -->
-          <el-form-item :label="item.label" v-for="item in inputFieldsList" class="filterItem">
+          <el-form-item
+            @keyup.enter.native="throttleQueryTableData"
+            :label="item.label"
+            v-for="item in inputFieldsList"
+            class="filterItem"
+          >
             <el-input
               v-model="queryFormData[item.name]"
               :placeholder="item.placeholder"
@@ -562,10 +594,10 @@ onMounted(() => {
         <div class="queryBox">
           <el-divider class="queryPartition" content-position="center" direction="vertical" />
           <div class="queryBtnBox">
-            <el-button class="queryBtn" color="#165dff" @click="queryTableData">
+            <el-button class="queryBtn" color="#165dff" @click="throttleQueryTableData">
               <el-icon><Search /></el-icon>查询
             </el-button>
-            <el-button class="refreshBtn" color="#f2f3f5" @click="resetQueryForm(queryFormRef)">
+            <el-button class="refreshBtn" color="#f2f3f5" @click="throttleResetQueryForm()">
               <el-icon><RefreshRight /></el-icon>重置
             </el-button>
           </div>
@@ -600,8 +632,8 @@ onMounted(() => {
         >
           <el-icon><Download /></el-icon>下载
         </el-button>
-
-        <RegreshBtn @refresh-table="getData" :icon-size="toolsIconSize"></RegreshBtn>
+        <!-- throttleFunc(queryTableData, 200) -->
+        <RegreshBtn @refresh-table="throttleGetData" :icon-size="toolsIconSize"></RegreshBtn>
 
         <FilterPopover
           :table-fields-info="tableFieldsInfo"
@@ -613,7 +645,7 @@ onMounted(() => {
     <div class="tableBox">
       <!-- 没有分页的时候需要重新计算一下data -->
       <el-table
-        :data="openPageQuery ? tableData[paginationConfig2.currentPage] : tableDataNoPaging"
+        :data="openPageQuery ? tableData[paginationConfig.currentPage] : tableDataNoPaging"
         style="width: 100%"
         class="tableBody"
         :cell-style="tableCellStyle"
@@ -775,12 +807,12 @@ onMounted(() => {
         <el-pagination
           class="userTablePagination"
           background
-          :page-size="paginationConfig2.limit"
-          :page-sizes="paginationConfig2.pagesizeList"
+          :page-size="paginationConfig.limit"
+          :page-sizes="paginationConfig.pagesizeList"
           table-layout="fixed"
           layout="prev, pager, next ,jumper ,sizes,total,"
-          :total="paginationConfig2.total"
-          :current-page.sync="paginationConfig2.currentPage"
+          :total="paginationConfig.total"
+          :current-page.sync="paginationConfig.currentPage"
           @current-change="handleCurrentChange"
           @size-change="handleSizeChange"
         />
@@ -814,6 +846,8 @@ onMounted(() => {
 .filterHeader,
 .filterBody {
   width: 98%;
+  box-sizing: border-box;
+
   margin: 0 auto;
 }
 
@@ -829,6 +863,7 @@ onMounted(() => {
 
 .filterBody {
   display: flex;
+  padding: 0 24px;
 }
 
 .queryBox {

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

@@ -2,26 +2,27 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-04 11:21:05
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 18:02:59
+ * @LastEditTime: 2024-10-14 16:57:09
  * @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 type { DialogConfig } from '@/types/dialog'
 
+import { useDialog } from '@/hooks/useDialog'
 import { nextTick, onMounted, reactive, ref } from 'vue'
 
-import type { DialogConfig } from '@/types/dialog'
+import Form from '../form/Form.vue'
 
 interface DialogProps {
   config: DialogConfig
+  configBtnText?: string
 }
 
 // 临时用的close
-const { dialogClose2 } = useDialog()
+const { dialogClose } = useDialog()
 
 const emits = defineEmits(['formSubmit'])
 
@@ -33,7 +34,9 @@ const updateUrl = ref<string>('')
 const dialogFormRef = ref<InstanceType<typeof Form>>()
 
 // props
-const props = withDefaults(defineProps<DialogProps>(), {})
+const props = withDefaults(defineProps<DialogProps>(), {
+  configBtnText: '确定'
+})
 
 // props中的关于配置的信息
 const dialogConfigInfo = props.config
@@ -48,25 +51,31 @@ const dialogConfig = reactive({
 
 // 游戏配置提交
 const submiteGameChange = () => {
-  dialogFormRef.value?.submitFormData().then(() => {
-    dialogConfig.dialogVisible = false
-  })
+  dialogFormRef.value
+    ?.submitFormData()
+    .then(() => {
+      // console.log(dialogConfigInfo.reqConfig.otherOptions.formData.gid )
+      dialogConfig.dialogVisible = false
+    })
+    .catch(() => {})
 }
 
 // 表单关闭
 const closeDialog = () => {
-  dialogClose2(dialogFormRef.value, dialogConfig)
+  dialogClose(dialogFormRef.value, dialogConfig)
 }
 
 // 表单添加
 const addForm = () => {
   dialogConfigInfo.reqConfig.url = addUrl.value
+  dialogConfig.type = 0
   dialogConfig.dialogVisible = true
 }
 
 // 表单修改
 const editForm = (row: any, updateURL?: string) => {
   dialogConfig.dialogVisible = true
+  dialogConfig.type = 1
   if (updateURL) {
     updateUrl.value = updateURL
     dialogConfigInfo.reqConfig.url = updateUrl.value
@@ -95,8 +104,8 @@ const encrypt = (fields: string, useFormField: boolean, encryptMsg: Array<string
   })
 }
 
-const subForm = () => {
-  emits('formSubmit')
+const subForm = (FormData: any) => {
+  emits('formSubmit', FormData, dialogConfig.type)
 }
 
 defineExpose({
@@ -131,7 +140,7 @@ defineExpose({
           <slot name="otherBtn"></slot>
           <slot name="btnGroup">
             <el-button class="dialogBtn" type="primary" @click="submiteGameChange()">
-              确认
+              {{ configBtnText }}
             </el-button>
             <el-button class="dialogBtn" @click="closeDialog">取消</el-button>
           </slot>

+ 148 - 42
src/components/common/WithIconSelect.vue

@@ -1,25 +1,39 @@
 <script setup lang="ts">
-import { onMounted, ref, reactive, computed } from 'vue'
-import { initLoadResouce } from '@/utils/resource'
 import type { DropdownInstance } from 'element-plus'
 import type { IconDropdownItem } from '@/types/dataAnalysis'
 
+import { onMounted, ref, reactive, watch } from 'vue'
+import { initLoadResouce } from '@/utils/resource'
+import { useCommonStore } from '@/stores/useCommon'
+
+const { selectInfo, tempMultipleChioce, saveSelectInfo, saveTempMultipleChioce } = useCommonStore()
+
 interface DropdownInfo {
-  isRadio?: boolean
+  isRadio?: boolean // 是否是单选
   slectInfo: Array<IconDropdownItem>
 }
 
-/**
- * @description: 最大选择数
- * @return {*}
- */
-const maxSelect = 1
-
 // props
 const props = withDefaults(defineProps<DropdownInfo>(), {
   isRadio: true
 })
 
+// 下拉框
+const dromDownMenu = ref()
+
+/**
+ * @description:  初始化选择信息
+ * @return {*}
+ */
+const selectBaseInfo = reactive<Array<IconDropdownItem>>(
+  JSON.parse(JSON.stringify(props.slectInfo))
+)
+
+// 判断是不是通过确认框关闭的。主要服务于多选的情况
+// 原理是因为点击确认关闭的话,确认事件会先触发,然后再触发关闭事件
+// 由此来去判断是否需要把多选框的信息恢复
+const isConfimClose = ref<Boolean>(false)
+
 // emits
 const emits = defineEmits(['changePf'])
 
@@ -27,7 +41,7 @@ const emits = defineEmits(['changePf'])
 const dropDownRef = ref<DropdownInstance>()
 
 // 资源的加载路径
-const resourceInfo: Record<string, string> = props.slectInfo.reduce(
+const resourceInfo: Record<string, string> = selectBaseInfo.reduce(
   (acc, item) => {
     acc[item.value] = item.icon
     return acc
@@ -43,15 +57,25 @@ const backupInfo = reactive<Array<IconDropdownItem>>([])
 
 /**
  * @description: 确认选择
+ * 这里主要用于多选的情况使用,对于单选,即使点击确认按钮也不会有二次请求,因为seleinfo没有改变
  * @return {*}
  */
 const confirmSelect = () => {
-  Object.assign(backupInfo, JSON.parse(JSON.stringify(props.slectInfo)))
+  isConfimClose.value = true // 通过确认事件关闭
+  // 如果没有选中任何平台,则直接返回,不进行任何操作
+  if (selectBaseInfo.filter((item) => item.isSelected).length === 0) {
+    dropDownRef.value?.handleClose()
+    return
+  }
+  Object.assign(backupInfo, JSON.parse(JSON.stringify(selectBaseInfo)))
   dropDownRef.value?.handleClose()
-  emits(
-    'changePf',
-    props.slectInfo.filter((item) => item.isSelected).map((item) => item.value)
-  )
+
+  if (props.isRadio) return
+
+  // 去把state的数据更新一下
+  tempMultipleChioce.pf = selectBaseInfo.filter((item) => item.isSelected).map((item) => item.value)
+
+  saveTempMultipleChioce() // 保存到本地
 }
 
 /**
@@ -60,7 +84,7 @@ const confirmSelect = () => {
  */
 const cancleSelect = () => {
   dropDownRef.value?.handleClose()
-  Object.assign(props.slectInfo, backupInfo)
+  Object.assign(selectBaseInfo, backupInfo)
 }
 
 /**
@@ -70,34 +94,102 @@ const cancleSelect = () => {
  */
 const dropdownVis = (state: boolean) => {
   if (state) {
-    Object.assign(backupInfo, JSON.parse(JSON.stringify(props.slectInfo)))
+    // 打开的时候,需要备份一下当前选择
+    Object.assign(backupInfo, JSON.parse(JSON.stringify(selectBaseInfo)))
   } else {
-    Object.assign(props.slectInfo, backupInfo)
+    // 关闭的时候,如果一个都没有选中,那么就需要把备份的信息恢复回去
+    // 这里只针对于多选情况,因为单选的逻辑中不存在一个都没有选中的情况,因为无法取消已经选中
+    // 而多选的情况下,因为即使可以取消,也不会立即改变selectInfo中的信息,只有点击了确认按钮才会改变,所以这里只需要恢复props的selectinfo就行了
+
+    // 不是点击确认按钮关闭的多选框,且是多选按钮,也需要恢复
+    if (!selectBaseInfo.find((item) => item.isSelected) || (!isConfimClose.value && !props.isRadio))
+      Object.assign(selectBaseInfo, backupInfo)
+
+    if (isConfimClose.value) isConfimClose.value = false // 把状态重置
   }
 }
 
 /**
- * @description: 用于限制最多的选择个数
- * @return {*}
- */
-const canSelect = computed(() => {
-  return props.slectInfo.filter((item) => item.isSelected).length < maxSelect
-})
-
-/**
  * @description: 选择事件,当目前可以选择或者是这个选项已经被选中的时候,就允许改变他的状态
  * @param {*} item  当前的选项信息
  * @return {*}
  */
 const selectPf = (item: any) => {
-  if (canSelect.value || item.isSelected) {
+  if (props.isRadio) {
+    selectInfo.pf = []
+    selectInfo.pf.push(item.value)
+
+    saveSelectInfo() // 选择之后保存到本地一次
+  } else {
+    // 多选就取反就行
     item.isSelected = !item.isSelected
   }
 }
 
+/**
+ * @description: 同步selectinfo的信息到页面上
+ * @return {*}
+ */
+const syncSelectInfo = () => {
+  selectBaseInfo.forEach((item) => {
+    item.isSelected = selectInfo.pf.includes(item.value)
+  })
+}
+
+/**
+ * @description: 同步tempMultipleChioce的信息到页面上
+ * @return {*}
+ */
+const syncTempMultipleChioce = () => {
+  selectBaseInfo.forEach((item) => {
+    item.isSelected = tempMultipleChioce.pf.includes(item.value)
+  })
+}
+
+/**
+ * @description: 监听selectinfo的变化,然后同步到页面上
+ * @return {*}
+ */
+const watchSelectPf = watch(
+  () => selectInfo.pf,
+  () => {
+    syncSelectInfo()
+  },
+  { deep: true }
+)
+
+/**
+ * @description:  监听tempMultipleChioce的变化,然后同步到页面上
+ * @return {*}
+ */
+const watchTempMultipleChioce = watch(
+  () => tempMultipleChioce.pf,
+  () => {
+    syncTempMultipleChioce()
+  },
+  { deep: true }
+)
+
+/**
+ * @description: 根据本地保存的方案信息,去初始化selectinfo的信息
+ * @return {*}
+ */
+const initSelectInfo = () => {
+  if (props.isRadio) {
+    watchTempMultipleChioce() // 如果是单选进来的,在这儿把多选的事件取消掉
+    syncSelectInfo() // 同步一下数据
+  } else {
+    watchSelectPf() // 与上面同理
+    syncTempMultipleChioce() // 同理
+  }
+}
+
+// 去加载一下本地的信息
+initSelectInfo()
+
 onMounted(() => {
+  // isTourShowed.value = getPfSelectTourShowState() ?? false
   // 去加载所有需要的资源
-
   initLoadResouce(resourceInfo).then((data) => {
     Object.assign(blobUrlInfo, data)
   })
@@ -109,14 +201,14 @@ onMounted(() => {
     <el-dropdown
       trigger="click"
       @visible-change="dropdownVis"
-      :hide-on-click="false"
+      :hide-on-click="props.isRadio"
       ref="dropDownRef"
     >
       <span class="displayBox">
         <div class="displayIcon">
-          <span class="iconItem" v-for="item in props.slectInfo">
+          <span class="iconItem" v-for="item in selectBaseInfo">
             <el-image
-              v-if="item.isSelected"
+              v-show="item.isSelected"
               style="width: 20px; height: 20px; margin-right: 5px"
               :src="blobUrlInfo[item.value]"
               :fit="'cover'"
@@ -129,29 +221,24 @@ onMounted(() => {
         </el-icon>
       </span>
       <template #dropdown>
-        <el-dropdown-menu>
+        <el-dropdown-menu ref="dromDownMenu" :class="{ radioMenu: isRadio }">
           <el-dropdown-item
-            v-for="item in props.slectInfo"
+            v-for="item in selectBaseInfo"
             :key="item.value"
             :value="item.value"
             @click="selectPf(item)"
-            :disabled="!canSelect && !item.isSelected"
+            style="box-sizing: border-box; padding-right: 15px"
           >
             <el-image
-              style="width: 20px; height: 20px; margin-right: 5px"
+              style="width: 20px; height: 20px; margin-right: 10px"
               :src="blobUrlInfo[item.value]"
               :fit="'cover'"
             />
             <!-- 禁用掉原生的点击事件,自己实现点击 -->
-            <el-checkbox
-              :disabled="!canSelect && !item.isSelected"
-              v-model="item.isSelected"
-              size="small"
-              @click.native.prevent="return"
-            />
+            <el-checkbox v-model="item.isSelected" size="small" @click.native.prevent="return" />
           </el-dropdown-item>
         </el-dropdown-menu>
-        <span class="btnGroup">
+        <span class="btnGroup" v-if="!isRadio">
           <el-button class="btnItem" size="small" type="primary" @click="confirmSelect"
             >确认</el-button
           >
@@ -159,6 +246,21 @@ onMounted(() => {
         </span>
       </template>
     </el-dropdown>
+    <!-- 引导 -->
+    <!-- <el-tour
+      @close="tourClose"
+      v-if="!isRadio && !isTourShowed"
+      v-model="tourOpen"
+      type="primary"
+      :mask="false"
+    >
+      <el-tour-step
+        :target="dromDownMenu?.$el"
+        title="多选框"
+        placement="right-start"
+        description="多选框需要点击确定后才会发送请求."
+      />
+    </el-tour> -->
   </div>
 </template>
 
@@ -182,6 +284,10 @@ onMounted(() => {
   margin-left: 4px;
 }
 
+.radioMenu > .selectItem {
+  padding-right: 15px !important;
+}
+
 .disabledSelect {
   cursor: not-allowed !important;
 }

+ 11 - 5
src/components/dataAnalysis/DropDownSelection.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-23 14:42:47
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 09:58:18
+ * @LastEditTime: 2024-10-11 15:34:29
  * @FilePath: \Game-Backstage-Management-System\src\components\dataAnalysis\DropDownSelection.vue
  * @Description: 下拉选择框,可用于分类字段或者切换平台等
  * 
@@ -16,13 +16,18 @@ const emits = defineEmits(['changeSelect'])
 
 const selectVal = ref()
 
-onMounted(() => {
-  selectVal.value = props.defaultSelect
-})
-
+/**
+ * @description: 改变选择,通知父组件
+ * @param {*} val
+ * @return {*}
+ */
 const changeSelect = (val: any) => {
   emits('changeSelect', val)
 }
+
+onMounted(() => {
+  selectVal.value = props.defaultSelect
+})
 </script>
 
 <template>
@@ -32,6 +37,7 @@ const changeSelect = (val: any) => {
       @change="changeSelect"
       v-model="selectVal"
       :placeholder="title"
+      filterable
       :size="props.size ? props.size : 'small'"
     >
       <el-option

+ 62 - 89
src/components/dataAnalysis/HeaderCard.vue

@@ -7,18 +7,22 @@
 -->
 
 <script setup lang="ts">
-import router from '@/router'
 import type { HeaderCardProps, IconDropdownItem } from '@/types/dataAnalysis'
+
 import { computed, onMounted, reactive, ref, watch } from 'vue'
-import WithIconSelect from '@/components/common/WithIconSelect.vue'
+import { createDateRange } from '@/utils/common'
 import { useCommonStore } from '@/stores/useCommon'
 
-const { selectInfo } = useCommonStore()
+import router from '@/router'
+import WithIconSelect from '@/components/common/WithIconSelect.vue'
+
+const { selectInfo, changeDateRange } = useCommonStore()
 
 const props = withDefaults(defineProps<HeaderCardProps>(), {
   openDateSelect: false,
   needPfSelect: true,
-  needBreadcrumb: false
+  needBreadcrumb: false,
+  isRadio: true
 })
 
 const emits = defineEmits(['changePf', 'changeDate'])
@@ -27,51 +31,22 @@ const emits = defineEmits(['changePf', 'changeDate'])
 const shortcuts = [
   {
     text: '上一周',
-    value: () => {
-      const end = new Date()
-      const start = new Date()
-      start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
-      return [start, end]
-    }
+    value: () => createDateRange(7)
   },
   {
     text: '上个月',
-    value: () => {
-      const end = new Date()
-      const start = new Date()
-      start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
-      return [start, end]
-    }
+    value: () => createDateRange(30)
   },
   {
     text: '近三个月',
-    value: () => {
-      const end = new Date()
-      const start = new Date()
-      start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
-      return [start, end]
-    }
+    value: () => createDateRange(90)
   }
 ]
 
 // 选择的日期
-const selectDate = ref<Array<Date>>(shortcuts[0].value())
-
-// 日期变化
-const dateChange = (val: any) => {
-  emits('changeDate', val)
-}
-
-// 控制日期范围
-/**
- * @description: 禁止选取今天之后的日期
- * @param {*} date
- * @return {*}
- */
-const disableDate = (time: Date) => {
-  return time.getTime() > Date.now()
-}
+const selectDate = ref<Array<Date>>([selectInfo.startTime, selectInfo.endTime])
 
+// 面包屑列表
 const breadcrumbList = reactive<
   Array<{
     title: string
@@ -79,10 +54,36 @@ const breadcrumbList = reactive<
   }>
 >([])
 
+// 下拉框的平台信息
+const pfSelectInfo = reactive<Array<IconDropdownItem>>([
+  {
+    value: 'web',
+    icon: '/img/platformIcon/web.svg',
+    label: '网页',
+    isSelected: false
+  },
+  {
+    value: 'wx',
+    icon: '/img/platformIcon/wx.svg',
+    label: '微信',
+    isSelected: true
+  },
+  {
+    value: 'tt',
+    icon: '/img/platformIcon/tt.svg',
+    label: '抖音',
+    isSelected: false
+  }
+])
+
 // 是否可点击
 const breadcrumbCanClick = computed(() => breadcrumbList.length > 1)
 
-// 返回总览
+/**
+ * @description: 返回总览,同时清除当前下标及之后的面包屑
+ * @param {*} index 当前面包屑的下标
+ * @return {*}
+ */
 const goBack = (index: number) => {
   if (breadcrumbCanClick) {
     router.push(breadcrumbList[index].pathName).then(() => {
@@ -92,6 +93,25 @@ const goBack = (index: number) => {
 }
 
 /**
+ * @description: 日期改变通知父组件
+ * @param {*} val 新日期
+ * @return {*}
+ */
+const dateChange = (val: Array<Date>) => {
+  changeDateRange(val)
+  emits('changeDate', val)
+}
+
+/**
+ * @description: 禁止选取今天之后的日期
+ * @param {*} date
+ * @return {*}
+ */
+const disableDate = (time: Date) => {
+  return time.getTime() > Date.now()
+}
+
+/**
  * @description: 添加导航栏的信息
  * @param {*} title 标题
  * @param {*} pathName 对应的路由name
@@ -117,32 +137,6 @@ const clearBreadcrumb = () => {
   }
 }
 
-// 下拉框的平台信息
-const pfSelectInfo = reactive<Array<IconDropdownItem>>([
-  {
-    value: 'web',
-    icon: '/img/platformIcon/web.svg',
-    label: '网页',
-    isSelected: false
-  },
-  {
-    value: 'wx',
-    icon: '/img/platformIcon/wx.svg',
-    label: '微信',
-    isSelected: true
-  },
-  {
-    value: 'tt',
-    icon: '/img/platformIcon/tt.svg',
-    label: '抖音',
-    isSelected: false
-  }
-])
-
-const changePlatForm = (val: Array<any>) => {
-  selectInfo.pf = [val[0]]
-}
-
 /**
  * @description: 监控当前路由,每次路由改变都要去执行clearBreadcrumb,来判断是否需要清除当前面包屑
  * @param {*} watch
@@ -156,28 +150,6 @@ const watchRoute = watch(
   { deep: true }
 )
 
-/**
- * @description: 这里是为了去监控store中的pf,pf变化的时候,emits最新的pf
- *   整体的流程是:当下拉框改变,他会emtis一个事件出来,这个时候,headercard组件接收新的值,去改变store中的pf
- *   headercard组件中还有一个watch,用来监听store中的pf,一旦改变那么就去重新给下拉选择框赋值,并且etmiss一次
- *   这个watch会在开始的时候执行一次,用来在页面加载的时候,给下拉框赋值,并且请求
- * @return {*}
- */
-watch(
-  () => selectInfo.pf,
-  (newPf) => {
-    emits('changePf', [newPf[0]])
-    pfSelectInfo.forEach((item) => {
-      if (item.value === newPf[0]) {
-        item.isSelected = true
-      } else {
-        item.isSelected = false
-      }
-    })
-  },
-  { immediate: true, deep: true }
-)
-
 if (!props.needBreadcrumb) watchRoute()
 
 defineExpose({
@@ -185,12 +157,13 @@ defineExpose({
   clearBreadcrumb
 })
 
+dateChange(selectDate.value) // 初始的时候触发一次,通知其他组件更新日期
+
 onMounted(() => {
   breadcrumbList.push({
     title: props.title,
     pathName: router.currentRoute.value.name as string
   })
-  dateChange(selectDate.value)
 })
 </script>
 
@@ -218,7 +191,7 @@ onMounted(() => {
     <div class="selectBox" v-if="props.needPfSelect">
       <el-divider direction="vertical" />
       <div class="selectItem">
-        <WithIconSelect @change-pf="changePlatForm" :slect-info="pfSelectInfo"></WithIconSelect>
+        <WithIconSelect :is-radio="props.isRadio" :slect-info="pfSelectInfo"></WithIconSelect>
       </div>
     </div>
     <div v-if="props.openDateSelect" class="datePicker">

+ 42 - 46
src/components/dataAnalysis/StatisticText.vue

@@ -1,66 +1,57 @@
-<!--
- * @Author: fxs bjnsfxs@163.com
- * @Date: 2024-08-26 13:57:37
- * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-11 15:00:44
- * @FilePath: \Game-Backstage-Management-System\src\components\dataAnalysis\StatisticText.vue
- * @Description: 用于展示统计数据,如总览页面上方的总览数据
- * 
--->
-
 <script setup lang="ts">
 import type { StaticDataInfo, StaticField } from '@/types/dataAnalysis'
+
 import { decimalToPercentage } from '@/utils/common'
 import { onMounted, reactive, ref, watch } from 'vue'
+import { useAnalysis } from '@/hooks/useAnalysis'
+
 import axiosInstance from '@/utils/axios/axiosInstance'
+
+const { updateReqConfig } = useAnalysis()
+
 const props = defineProps<StaticDataInfo>()
-const dataList = reactive<Array<StaticField>>([])
+let dataList = reactive<Array<StaticField>>([])
 const dataState = ref(false) // 数据是否加载成功
+
+/**
+ * @description: 创建dataList数组
+ * @param {*} fieldsInfo 字段信息
+ * @param {*} getValue 获取值的函数
+ * @return {*}
+ */
+const createDataList = (fieldsInfo: Array<StaticField>, getValue: (item: StaticField) => any) => {
+  return fieldsInfo.map((item) => ({
+    name: item.name,
+    cnName: item.cnName,
+    value: getValue(item)
+  }))
+}
+
 /**
  * @description: 用于获取数据
- * @tip  这里暂时只请求当日的数据,没有比较
  * @return {*}
  */
-const getData = () => {
+const getData = async () => {
   try {
+    let newDataList = []
     if (props.requestConfig) {
-      axiosInstance
-        .post(props.requestConfig.url, props.requestConfig.otherOptions)
-        .then((info) => {
-          dataList.splice(0, dataList.length) // 清空一下
-          let data = info.data
-          props.fieldsInfo.map((item) => {
-            dataList.push({
-              name: item.name,
-              cnName: item.cnName,
-              value: data[item.name]
-            })
-          })
-
-          dataState.value = true
-        })
-        .catch(() => {
-          dataState.value = false
-        })
+      let info = await axiosInstance.post(props.requestConfig.url, props.requestConfig.otherOptions)
+
+      let data = info.data
+      newDataList = createDataList(props.fieldsInfo, (item) => data[item.name])
     } else {
       let hasNull = props.fieldsInfo.every((item) => item.value !== '')
 
-      if (hasNull) {
-        dataList.splice(0, dataList.length) // 清空一下
-        props.fieldsInfo.map((item) => {
-          dataList.push({
-            name: item.name,
-            cnName: item.cnName,
-            value: item.value
-          })
-        })
-        dataState.value = true
-      } else {
-        dataState.value = false
-      }
+      if (!hasNull) throw new Error('全为空')
+
+      newDataList = createDataList(props.fieldsInfo, (item) => item.value)
     }
+    dataList.splice(0, dataList.length, ...newDataList)
+
+    dataState.value = true // 标记成功
   } catch (err) {
     console.log(err)
+    dataState.value = false
     throw new Error('数据获取失败')
   }
 }
@@ -76,15 +67,20 @@ watch(
     props.fieldsInfo
   ],
   () => {
+    // 如果有请求配置,要去更新一下
+    if (props.requestConfig)
+      updateReqConfig(props.requestConfig, { gid: props.requestConfig?.otherOptions.gid })
+
     getData()
+  },
+  {
+    deep: true
   }
 )
 
 onMounted(() => {
   getData()
 })
-
-defineExpose({})
 </script>
 
 <template>

+ 12 - 11
src/components/dataAnalysis/TemporalTrend.vue

@@ -7,13 +7,14 @@
 -->
 
 <script setup lang="ts">
-import { nextTick, onMounted, reactive, ref, watch } from 'vue'
 import type { TemporalTrendProps, OptionsProps, StaticField, ReqConfig } from '@/types/dataAnalysis'
 import type { TablePaginationSetting, TableFieldInfo } from '@/types/table'
+
+import { nextTick, onMounted, reactive, ref, watch } from 'vue'
+
 import Table from '../Table.vue'
 import TimeLineChart from '../echarts/TimeLineChart.vue'
 import StatisticText from './StatisticText.vue'
-
 import axiosInstance from '@/utils/axios/axiosInstance'
 
 interface CacheData {
@@ -90,13 +91,13 @@ let chartInfo = reactive<OptionsProps>({
 
 /**
  * @description: 改变图表形式,当是表格的时候,去执行表格的获取数据方法,拿到最新的数据
- * @param {*} name 图表的展现形式,可以使table或者trend
+ * @param {*} type 图表的展现形式,可以使table或者trend,1为图,2为表
  * @return {*}
  */
-const changeSelectShape = (name: number) => {
-  if (selectShape.value === name) return
-  selectShape.value = name
-  if (name === 2) {
+const changeSelectShape = (type: number) => {
+  if (selectShape.value === type) return
+  selectShape.value = type
+  if (type === 2) {
     nextTick(() => {
       chartTable.value?.getData()
     })
@@ -289,7 +290,6 @@ const getData = async (type: number) => {
       if (activeTab.value) setCacheData() // 如果有tab的话再去缓存
     })
     .catch((err) => {
-      loadDataState.state = false
       console.log(err)
     })
     .finally(() => {
@@ -302,13 +302,13 @@ const getData = async (type: number) => {
  * @param {*} tabName 对应的tabName,去tabinfo中找到对应的url
  * @return {*}
  */
-const tabChange = (tabName: string) => {
+const tabChange = async (tabName: string) => {
   if (props.tabInfo) {
     if (cacheData[tabName]) {
       setCacheData(true)
     } else {
       let type = props.tabInfo.find((item) => item.name === tabName)?.type
-      if (type) getData(type)
+      if (type) await getData(type)
       else throw new Error('No match type')
     }
   }
@@ -330,7 +330,8 @@ watch(
     getData(1)
   },
   {
-    deep: true
+    deep: true,
+    immediate: true
   }
 )
 

+ 4 - 2
src/components/echarts/TimeLineChart.vue

@@ -1,9 +1,11 @@
 <script setup lang="ts">
+import type { OptionsProps } from '@/types/dataAnalysis'
+
 import { onMounted, ref, shallowRef, watch, useAttrs } from 'vue'
-import echarts from '.'
 import { nextTick } from 'vue'
 import { debounceFunc } from '@/utils/common'
-import type { OptionsProps } from '@/types/dataAnalysis'
+
+import echarts from '.'
 
 // 图表需要的props
 const props = defineProps<OptionsProps>()

+ 21 - 9
src/components/form/FileUpload.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
+import type { UploadInstance, UploadRawFile } from 'element-plus'
+
 import { reactive, ref } from 'vue'
 import { ElMessage, genFileId } from 'element-plus'
-import type { UploadInstance, UploadRawFile } from 'element-plus'
 
 interface UploadProps {
   title: string
@@ -43,7 +44,7 @@ const beforeUpload = (file: UploadRawFile) => {
   reader.onload = function () {
     try {
       const finnalResult = reader.result as string
-      //   console.log(JSON.parse(finnalResult))
+
       emits('uploadSuccess', JSON.parse(finnalResult))
       state = true
     } catch {
@@ -130,6 +131,7 @@ defineExpose({
       v-model="uploadInfo.uploadVisible"
       title="文件上传"
       width="500"
+      :close-on-click-modal="false"
       @close="closeUpload"
     >
       <div class="uploadBox" v-loading="loading" element-loading-text="正在上传...">
@@ -172,7 +174,6 @@ defineExpose({
           <br />
           <p>
             <b>allEventTable</b>用于存放所有需要更新或者需要新增的事件,<b>以数组形式传入.</b>
-
             如果是需要更新的事件,需要提供<b>id</b>字段,否则不需要。如果不传入id字段则<b
               >默认为新增</b
             >
@@ -183,13 +184,20 @@ defineExpose({
             >用于存放所有的事件选项,以<b>对象</b>形式传入,<b>对象的键为该选项所对应的事件的actionId</b>,
             同样,新增的选项不需要传入id字段,如果不传入id字段则<b>默认为新增</b>
           </p>
+          <br />
+          <p>
+            <b>请确保上传的选项对应的均是同一游戏的选项,否则可能会上传失败</b>
+            <br />
+            <br />
+            <b>提供其他额外的字段可能会被直接忽略</b>
+          </p>
           <pre>
                     <code>
 {
     "allEventTable": [
       // 更新
         {
-            "id": 1,
+            "id": 1,  // 更新需要传入id
             "gid": "1200",
             "actionId": "123",
             "actionName": "第二关过关",
@@ -199,18 +207,20 @@ defineExpose({
       // 新增
         {
             "gid": "1200",
-            "actionId": "123",
+            "actionId": "456",
             "actionName": "第二关过关",
             "status": 0,
             "remark": "t"
         }
     ],
+
     // 选项的键需要对应对应事件的actionId
-    "allOptionsInfo": {
+    "allOptionsInfo": { 
       // 更新  
-        "button": [
+        "123": [
             {
-                "id": 1,
+                "id": 1,  // 更新需要传入id
+                "actionId": 123,
                 "optionId": "test",
                 "optionName": "test",
                 "optionType": "string",
@@ -218,8 +228,10 @@ defineExpose({
             }
         ],
         // 新增
-        "123":[
+        // 如果不存在这个actionid则视为新增
+        "888":[
             {
+              "actionId": 456,
                 "optionId": "ba",
                 "optionName": "bgbb",
                 "optionType": "int",

+ 8 - 2
src/components/form/Form.vue

@@ -5,6 +5,7 @@ import type { FormConfig } from '@/types/form'
 import { FormFieldType } from '@/types/form'
 import { reactive, ref } from 'vue'
 import { useForm } from '@/hooks/useForm'
+
 import CryptoJS from 'crypto-js'
 
 const { submitForm } = useForm()
@@ -36,11 +37,16 @@ const backupData = reactive<Record<string, any>>({})
 const submitFormData = (otherOption?: any) => {
   return new Promise((reslove, reject) => {
     Object.assign(formData, props.config.reqConfig.otherOptions.formData)
+    for (let item in formData) {
+      if (typeof formData[item] === 'string') {
+        formData[item] = formData[item].trim()
+      }
+    }
     formRef.value?.validate(async (vaild: boolean) => {
       if (vaild) {
         submitForm(formRef.value, props.config.reqConfig.url, { ...formData, ...otherOption })
           .then(() => {
-            emits('subForm')
+            emits('subForm', JSON.parse(JSON.stringify(formData)))
             reslove(true)
           })
           .catch((err) => {
@@ -148,7 +154,7 @@ defineExpose({
     <!-- :inline="props.inline" -->
     <el-form
       :class="props.inline ? 'formInline' : 'form'"
-      :label-position="props.inline ? 'top' : 'left'"
+      :label-position="props.inline ? 'top' : 'right'"
       :rules="props.config.rules"
       :model="formData"
       ref="formRef"

+ 43 - 22
src/components/form/MyInput.vue

@@ -1,7 +1,17 @@
+<!--
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-08-22 17:31:50
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-10-14 17:04:09
+ * @FilePath: \Game-Backstage-Management-System\src\components\form\MyInput.vue
+ * @Description: 
+ * 
+-->
 <script setup lang="ts">
-import { onMounted, ref } from 'vue'
 import type { RuleInfo } from '@/types/input'
 
+import { onMounted, ref } from 'vue'
+
 interface InputInfo {
   modelValue: string
   pInputType: string
@@ -26,25 +36,10 @@ let activeClass = ref(false) // 是否被选中
 let showTip = ref(false) // 是否展示提示框
 let errorMsg = ref('') // 提示的内容
 
-onMounted(() => {
-  // 点击输入框则聚焦,并给边框
-  document.addEventListener('click', (e) => {
-    // 如果点击的地方不在 div 内部,则移除 active-class
-    if (inputDivRef.value && !inputDivRef.value.contains(e.target)) {
-      activeClass.value = false
-    } else {
-      activeClass.value = true
-      // inputRef.value.focus()
-      // 这里有时会出现inputRef.value为空的情况
-      if (inputRef.value) {
-        inputRef.value.focus()
-      }
-    }
-  })
-  if (inputType.value === 'password') isPassword.value = true
-})
-
-// 改变密码框的可见性及可见按钮的切换
+/**
+ * @description: 改变密码框可见性
+ * @return {*}
+ */
 const changeVisible = () => {
   passwordVisiable.value = !passwordVisiable.value
   if (passwordVisiable.value) {
@@ -54,7 +49,11 @@ const changeVisible = () => {
   }
 }
 
-// 验证input框是否符合规则
+/**
+ * @description: 验证input框是否符合规则
+ * @param {*} let
+ * @return {*}
+ */
 const verifyIpt = () => {
   let rulesInfo = props.pinputRules
   if (rulesInfo) {
@@ -69,10 +68,32 @@ const verifyIpt = () => {
   }
 }
 
-// 当input框触发改变的时候,同步的更新v-model绑定的数据
+/**
+ * @description: 同步input框数据
+ * @param {*} val
+ * @return {*}
+ */
 const updateValue = (val: any) => {
   emits('update:modelValue', val.target.value)
 }
+
+onMounted(() => {
+  // 点击输入框则聚焦,并给边框
+  document.addEventListener('click', (e) => {
+    // 如果点击的地方不在 div 内部,则移除 active-class
+    if (inputDivRef.value && !inputDivRef.value.contains(e.target)) {
+      activeClass.value = false
+    } else {
+      activeClass.value = true
+      // inputRef.value.focus()
+      // 这里有时会出现inputRef.value为空的情况
+      if (inputRef.value) {
+        inputRef.value.focus()
+      }
+    }
+  })
+  if (inputType.value === 'password') isPassword.value = true
+})
 </script>
 
 <template>

+ 2 - 2
src/hooks/useAnalysis.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:15:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-11 15:27:44
+ * @LastEditTime: 2024-10-14 14:39:48
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useAnalysis.ts
  * @Description:
  *
@@ -11,7 +11,7 @@ import type { ReqConfig } from '@/types/dataAnalysis'
 
 export function useAnalysis() {
   /**
-   * @description: 更新统计组件的请求参数
+   * @description: 更新请求参数
    * @return {*}
    */
   const updateReqConfig = (config: ReqConfig, newVal: any) => {

+ 21 - 82
src/hooks/useDialog.ts

@@ -2,33 +2,28 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-21 17:23:32
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-04 15:46:03
+ * @LastEditTime: 2024-10-14 17:00:57
  * @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'
-import axiosInstance from '@/utils/axios/axiosInstance'
-import { ElMessage, ElMessageBox } from 'element-plus'
+
 import { nextTick } from 'vue'
+
+import Form from '@/components/form/Form.vue'
+
 import 'element-plus/theme-chalk/el-message.css'
 import 'element-plus/theme-chalk/el-message-box.css'
 
-const { analysisResCode } = useRequest()
-
 export function useDialog() {
-  // 对话框关闭
-  const dialogClose = (formEl: FormInstance | undefined, dialogConfig: DialogSetting) => {
-    if (!formEl) return
-
-    dialogConfig.dialogVisible = false
-    formEl.resetFields()
-  }
-
-  const dialogClose2 = (
+  /**
+   * @description: 对话框关闭
+   * @param {InstanceType} formEl 表单对象
+   * @param {DialogSetting} dialogConfig 对话框配置
+   * @return {*}
+   */
+  const dialogClose = (
     formEl: InstanceType<typeof Form> | undefined,
     dialogConfig: DialogSetting
   ) => {
@@ -38,60 +33,13 @@ export function useDialog() {
     formEl.resetForm()
   }
 
-  // 对话框提交
-  const submitDialog = (
-    formEl: FormInstance | undefined,
-    dialogConfig: DialogSetting,
-    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(() => {
-                    dialogConfig.dialogVisible = false
-                    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)
-        })
-    })
-  }
-
-  // 修改按钮
+  /**
+   * @description: 点击修改按钮,填充数据
+   * @param {any} row 行数据
+   * @param {any} formData 表单数据
+   * @param {DialogSetting} dialogConfig 对话框配置
+   * @return {*}
+   */
   const handleEdit = (row: any, formData: any, dialogConfig: DialogSetting) => {
     dialogConfig.type = 1
     dialogConfig.dialogVisible = true
@@ -107,20 +55,11 @@ export function useDialog() {
         }
       }
     })
-    console.log(dialogConfig)
-    // throw new Error('test')
-  }
-
-  const addNeweItem = (dialogConfig: DialogSetting) => {
-    dialogConfig.type = 0
-    dialogConfig.dialogVisible = true
   }
 
   return {
     dialogClose,
-    dialogClose2,
-    submitDialog,
-    handleEdit,
-    addNeweItem
+
+    handleEdit
   }
 }

+ 24 - 27
src/hooks/useForm.ts

@@ -2,19 +2,29 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-04 15:07:56
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 12:10:43
+ * @LastEditTime: 2024-10-14 16:50:35
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useForm.ts
  * @Description:
  *
  */
 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'
 
+import axiosInstance from '@/utils/axios/axiosInstance'
+
 const { analysisResCode } = useRequest()
 export function useForm() {
+  const vaildForm = async (formEl: FormInstance | undefined): Promise<boolean> => {
+    if (!formEl) {
+      console.log('找不到表单实例')
+      return false
+    }
+    return await formEl.validate()
+  }
+
   // 对话框提交
   const submitForm = (formEl: FormInstance | undefined, url: string, formData: any) => {
     return new Promise((reslove, reject) => {
@@ -24,40 +34,27 @@ export function useForm() {
         type: 'warning',
         appendTo: 'body'
       })
-        .then(() => {
+        .then(async () => {
           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))
+            let vaild = await vaildForm(formEl)
+            if (!vaild) throw new Error('表单校验不通过')
 
-                analysisResCode(info)
-                  .then(() => {
-                    reslove(true)
-                  })
-                  .catch((err) => {
-                    reject(err)
-                    console.log(err)
-                  })
-              } else {
-                console.log(field)
-                console.log('表单校验不通过')
-              }
-            })
+            let result = await axiosInstance.post(url, formData)
+            let info = JSON.parse(JSON.stringify(result))
+
+            await analysisResCode(info)
+            reslove(true)
           } catch (err) {
             console.log(err)
             ElMessage({
               type: 'error',
-              message: '未知错误'
+              message: '表单验证不通过'
             })
-            throw new Error('other err')
+            reject(false)
           }
         })
         .catch(() => {
+          console.log('取消选择')
           reject(false)
         })
     })
@@ -78,5 +75,5 @@ export function useForm() {
     })
   }
 
-  return { submitForm, editForm }
+  return { submitForm, editForm, vaildForm }
 }

+ 1 - 1
src/hooks/usePage.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-10 10:31:42
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-11 16:28:26
+ * @LastEditTime: 2024-10-10 15:37:44
  * @FilePath: \Game-Backstage-Management-System\src\hooks\usePage.ts
  * @Description:
  *

+ 67 - 49
src/hooks/useRequest.ts

@@ -2,76 +2,91 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:24:06
  * @LastEditors: fxs bjnsfxs@163.com
+<<<<<<< HEAD
  * @LastEditTime: 2024-09-18 12:26:12
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useRequest.ts
+=======
+ * @LastEditTime: 2024-10-17 09:26:16
+ * @FilePath: \Quantity-Creation-Management-Systemc:\Users\NINGMEI\Desktop\Game-Backstage-Management-System\src\hooks\useRequest.ts
+>>>>>>> develop
  * @Description:
  *
  */
-import { ElMessage } from 'element-plus'
-import 'element-plus/theme-chalk/el-message.css'
-import 'element-plus/theme-chalk/el-message-box.css'
-import { MessageType } from '@/types/res'
 
 import type { AxiosResponse } from 'axios'
 import type { ResponseInfo } from '@/types/res'
 
+import { ElMessage } from 'element-plus'
+import { MessageType } from '@/types/res'
+import BASE_URL from '../../config/api'
+
+import 'element-plus/theme-chalk/el-message.css'
+import 'element-plus/theme-chalk/el-message-box.css'
+
 export function useRequest() {
-  let baseIp = ''
-  // 根据环境不同,切换不同的IP
-  if (import.meta.env.MODE === 'development') {
-    // baseIp = 'http://service.ichunhao.cn' // 正式库
-    baseIp = 'http://server.ichunhao.cn' // 测试服
-  } else {
-    baseIp = 'http://service.ichunhao.cn' // 正式库
-  }
-  // const baseIp = 'http://192.168.1.139:8000' // 本地
+  let baseURL = BASE_URL
+  // if (import.meta.env.MODE === 'test') {
+  //   baseURL = import.meta.env.VITE_API_URL_TEST
+  // } else {
+  //   baseURL = import.meta.env.VITE_API_URL_PRODUCT
+  // }
 
   const AllApi = {
     // mock: `http://127.0.0.1:8003/mock`,
     mockEvent: `http://127.0.0.1:8003/mockEvent`,
-    mockDate: `http://127.0.0.1:8003/mockDate`,
+    mockTest: `http://127.0.0.1:8003/test`,
 
-    getGameTable: `${baseIp}/user/getGidConfig`, // 获取游戏列表
-    getUserTable: `${baseIp}/user/userList`, // 获取用户列表
-    addGame: `${baseIp}/user/addGidConfig`, // 添加/修改 游戏配置
-    userLogin: `${baseIp}/user/login`, // 登录
-    addOption: `${baseIp}/user/addUserOption`, // 添加/修改 权限
-    addUserToBlack: `${baseIp}/user/addUserToBlackList`, // 封禁用户
-    deleteUserToBlack: `${baseIp}/user/deleteUserToBlackList`, // 解封用户
-    getInterfaceInfo: `${baseIp}/user/getInterfaceInfo`, // 拿到所有接口的信息
-    getInterfaceDataByDay: `${baseIp}/user/getInterfaceDataByDay`, //获取接口的请求频次 (按天)
-    gerRefreshToken: `${baseIp}/user/refreshToken`, // 刷新token
-    getOverViewData: `${baseIp}/user/overview`, // 总览数据
+    getGameTable: `/user/getGidConfig`, // 获取游戏列表
+    getUserTable: `/user/userList`, // 获取用户列表
+    addGame: `/user/addGidConfig`, // 添加/修改 游戏配置
+    userLogin: `/user/login`, // 登录
+    addOption: `/user/addUserOption`, // 添加/修改 权限
+    addUserToBlack: `/user/addUserToBlackList`, // 封禁用户
+    deleteUserToBlack: `/user/deleteUserToBlackList`, // 解封用户
+    getInterfaceInfo: `/user/getInterfaceInfo`, // 拿到所有接口的信息
+    getInterfaceDataByDay: `/user/getInterfaceDataByDay`, //获取接口的请求频次 (按天)
+    getRefreshToken: `/user/refreshToken`, // 刷新token
+    getOverViewData: `/user/overview`, // 总览数据
 
     // 数据分析相关URL
-    timeDistributionData: `${baseIp}/user/timeDistributionData`, //用户概览 -时段分布
-    userSummary: `${baseIp}/user/summary`, //用户概览 -总览
-    userMouthDistributionData: `${baseIp}/user/mouthDistributionData`, //用户概览 -30日趋势
-    userTrendsOverview: `${baseIp}/user/userTrendsOverview`, //用户趋势 -总览
-    userDataTrades: `${baseIp}/user/dataTrades`, //用户趋势 -数据趋势
-    userDataTradesDetail: `${baseIp}/user/dataTradesDetail`, //用户趋势 -数据趋势详情
-    userRemainDataBydDay: `${baseIp}/user/remainDataBydDay`, //用户留存数据
+    timeDistributionData: `/user/timeDistributionData`, //用户概览 -时段分布
+    userSummary: `/user/summary`, //用户概览 -总览
+    userMouthDistributionData: `/user/mouthDistributionData`, //用户概览 -30日趋势
+    userTrendsOverview: `/user/userTrendsOverview`, //用户趋势 -总览
+    userDataTrades: `/user/dataTrades`, //用户趋势 -数据趋势
+    userDataTradesDetail: `/user/dataTradesDetail`, //用户趋势 -数据趋势详情
+    userRemainDataBydDay: `/user/remainDataBydDay`, //用户留存数据
 
     // 事件相关
     // 事件
-    gameActionList: `${baseIp}/user/gameActionList`, // 游戏事件列表
-    gameActionDetail: `${baseIp}/user/gameActionDetail`, // 事件详情
-    updateGameAction: `${baseIp}/user/updateGameAction`, // 更新游戏事件
-    setGameAction: `${baseIp}/user/setGameAction`, // 新增事件
+    gameActionList: `/user/gameActionList`, // 游戏事件列表
+    gameActionDetail: `/user/gameActionDetail`, // 事件详情
+    updateGameAction: `/user/updateGameAction`, // 更新游戏事件
+    setGameAction: `/user/setGameAction`, // 新增事件
 
     // 事件参数
-    gameActionOptionList: `${baseIp}/user/gameActionOptionList`, // 获取事件参数列表
-    addGameActionOption: `${baseIp}/user/addGameActionOption`, // 新增事件参数
-    updateGameActionOption: `${baseIp}/user/updateGameActionOption`, // 更新事件参数
-    deleteGameActionOption: `${baseIp}/user/deleteGameActionOption`, // 删除事件参数
+    gameActionOptionList: `/user/gameActionOptionList`, // 获取事件参数列表
+    addGameActionOption: `/user/addGameActionOption`, // 新增事件参数
+    updateGameActionOption: `/user/updateGameActionOption`, // 更新事件参数
+    deleteGameActionOption: `/user/deleteGameActionOption`, // 删除事件参数
 
     // 事件分析
-    userActionDetailDistribution: `${baseIp}/user/userActionDetailDistribution`, // 事件统计趋势图
-    userActionDetail: `${baseIp}/user/userActionDetail`, // 事件统计详情
-    userActionList: `${baseIp}/user/userActionList` // 游戏事件统计列表
+    userActionDetailDistribution: `/user/userActionDetailDistribution`, // 事件统计趋势图
+    userActionDetail: `/user/userActionDetail`, // 事件统计详情
+    userActionList: `/user/userActionList` // 游戏事件统计列表
   }
 
-  const analysisResCode = (data: AxiosResponse, kind?: string): Promise<ResponseInfo> => {
+  /**
+   * @description: 根据返回码给出提示
+   * @param {AxiosResponse} data 返回的数据
+   * @param {string} kind 请求类型
+   * @return {*}
+   */
+  const analysisResCode = (
+    data: AxiosResponse,
+    kind = '请求',
+    showMsg = true
+  ): Promise<ResponseInfo> => {
     return new Promise((resolve, reject) => {
       let info = JSON.parse(JSON.stringify(data)) as ResponseInfo
 
@@ -91,16 +106,19 @@ export function useRequest() {
           reject(info.msg)
         }
       }
-      ElMessage({
-        type,
-        message,
-        duration: 1000
-      })
+      if (showMsg) {
+        ElMessage({
+          type,
+          message,
+          duration: 1000
+        })
+      }
     })
   }
 
   return {
     AllApi,
+    baseURL,
     analysisResCode
   }
 }

+ 49 - 77
src/hooks/useTable.ts

@@ -2,22 +2,54 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:15:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-18 10:09:12
- * @FilePath: \Game-Backstage-Management-System\src\hooks\useTable.ts
+ * @LastEditTime: 2024-10-16 10:54:55
+ * @FilePath: \Quantity-Creation-Management-Systemc:\Users\NINGMEI\Desktop\Game-Backstage-Management-System\src\hooks\useTable.ts
  * @Description:
  *
  */
 import type { ResponseInfo } from '@/types/res'
+import type { TablePaginationSetting } from '@/types/table'
+
+import { initLoadResouce } from '@/utils/resource'
+import { reactive } from 'vue'
+
 import axiosInstance from '../utils/axios/axiosInstance'
 
-import { loadResource } from '@/utils/resource'
+// 资源的加载路径
+const resourceInfo: Record<string, string> = {
+  defaultHead: `/img/default/defaultHead.png`
+}
+
+// 使用blob的资源路径信息
+const blobUrlInfo = reactive<Record<string, string>>({})
 
-import type { TablePaginationSetting, DialogSetting } from '@/types/table'
-import { type FormInstance } from 'element-plus'
+// 初始化资源
+initLoadResouce(resourceInfo).then((data) => {
+  Object.assign(blobUrlInfo, data)
+})
 
 export function useTable(tableData: Array<any>, paginationSetting: TablePaginationSetting) {
-  // const { analysisResCode } = useRequest()
+  /**
+   * @description: 设置默认头像
+   * @param {Array} data 请求回来的数据表格
+   * @return {Array<any>} 返回处理后的数据
+   */
+  const setDefaultHead = (data: Array<any>): Array<any> => {
+    if (data) {
+      data.map(async (item: any) => {
+        if (!item.head) item.head = blobUrlInfo.defaultHead
+      })
+    }
+    return data
+  }
 
+  /**
+   * @description: 获取表格数据
+   * @param {string} url 请求地址
+   * @param {any} option 请求参数
+   * @param {boolean} isPagination 是否开启分页
+   * @return {*}
+   */
   const getTableData = (url: string, option: any, isPagination: boolean = false) => {
     return new Promise(async (reslove, reject) => {
       try {
@@ -25,71 +57,20 @@ export function useTable(tableData: Array<any>, paginationSetting: TablePaginati
           let info = JSON.parse(JSON.stringify(result)) as ResponseInfo
           let data = info.data
 
-          // 加载图片资源
-          // 如果有头像字段,就去加载这个头像字段,缓存起来
-          const loadImg = async () => {
-            return new Promise((resolve) => {
-              let resList: Array<Promise<any>> = []
-              if (data && data.length > 0) {
-                data.slice(0, data.length).map(async (item: any) => {
-                  if (item.head) {
-                    const loadHead = loadResource(item.head).then((res) => {
-                      item.head = res
-                    })
-                    resList.push(loadHead)
-                  }
-                })
-              }
-              Promise.allSettled(resList).then(() => {
-                resolve(true)
-              })
-            })
-          }
-          await loadImg()
-
-          // 如果开启了分页,那么默认这个tabledata是一个二维数组,每个位置对应当页的一个数据数组
-          // 没开启则是一个一维数组,直接赋值
-          if (isPagination) {
-            tableData[paginationSetting.currentPage] = data
+          // 没有数据则直接置为空
+          if (!data) {
+            tableData.length = 0
+            paginationSetting.total = 0
           } else {
-            if (data && data.length > 0) tableData.splice(0, tableData.length, ...data)
+            paginationSetting.total = info.count ?? data.length
+            data = setDefaultHead(data)
+            // 如果开启了分页,那么默认这个tabledata是一个二维数组,每个位置对应当页的一个数据数组
+            // 没开启则是一个一维数组,直接赋值
+            if (isPagination) tableData[paginationSetting.currentPage] = data
+            else tableData.splice(0, tableData.length, ...data)
           }
 
-          // 如果有的接口没有返回count属性,就需要自己写
-          // 这个length,如果数组长为0,则需要自己赋值,不然会报错
-          if (info.count) paginationSetting.total = info.count
-          else if (info.data) {
-            paginationSetting.total = info.data.length
-          } else {
-            paginationSetting.total = 0
-          }
           reslove(true)
-          // analysisResCode(data)
-          //   .then((info) => {
-          //     let data = info.data
-
-          //     // 如果开启了分页,那么默认这个tabledata是一个二维数组,每个位置对应当页的一个数据数组
-          //     // 没开启则是一个一维数组,直接赋值
-          //     if (isPagination) {
-          //       tableData[paginationSetting.currentPage] = data
-          //     } else {
-          //       tableData.splice(0, tableData.length, ...data)
-          //     }
-
-          //     // 如果有的接口没有返回count属性,就需要自己写
-          //     // 这个length,如果数组长为0,则需要自己赋值,不然会报错
-          //     if (info.count) paginationSetting.total = info.count
-          //     else if (info.data) {
-          //       paginationSetting.total = info.data.length
-          //     } else {
-          //       paginationSetting.total = 0
-          //     }
-          //     reslove(true)
-          //   })
-          //   .catch((err) => {
-          //     console.log(err)
-          //     throw new Error('请求失败')
-          //   })
         })
       } catch (err) {
         console.log(err)
@@ -99,16 +80,7 @@ export function useTable(tableData: Array<any>, paginationSetting: TablePaginati
     })
   }
 
-  // 对话框关闭
-  const dialogClose = (formEl: FormInstance | undefined, dialogParam: DialogSetting) => {
-    if (!formEl) return
-
-    dialogParam.dialogVisible = false
-    formEl.resetFields()
-  }
-
   return {
-    getTableData,
-    dialogClose
+    getTableData
   }
 }

+ 20 - 2
src/main.ts

@@ -1,10 +1,12 @@
 import { createApp } from 'vue'
 import { createPinia } from 'pinia'
-import * as ElementPlusIconsVue from '@element-plus/icons-vue'
-import './assets/base.css'
+
 import App from './App.vue'
 import router from './router'
 
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import './assets/base.css'
+
 const app = createApp(App)
 
 for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
@@ -14,4 +16,20 @@ app.use(createPinia())
 
 app.use(router)
 
+/**
+ * @description:  全局异常捕获,此处只用于调试
+ * @param {*} err 错误对象
+ * @param {*} instance 触发该错误的组件实例
+ * @param {*} info 错误来源类型信息
+ * @return {*}
+ */
+app.config.errorHandler = (err, instance, info) => {
+  console.log('-----------------')
+  console.log('未被捕获的异常')
+  console.log(err)
+  console.log(instance)
+  console.log(info)
+  console.log('-----------------')
+}
+
 app.mount('#app')

+ 1 - 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-12 17:47:02
+ * @LastEditTime: 2024-10-10 15:45:26
  * @FilePath: \Game-Backstage-Management-System\src\router\home.ts
  * @Description:
  *

+ 15 - 16
src/router/index.ts

@@ -2,14 +2,14 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:06:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-06 16:59:23
- * @FilePath: \Game-Backstage-Management-System\src\router\index.ts
+ * @LastEditTime: 2024-10-11 16:42:42
+ * @FilePath: \无标题 (工作区)c:\Users\NINGMEI\Desktop\Game-Backstage-Management-System\src\router\index.ts
  * @Description:
  *
  */
 import { createRouter, createWebHashHistory } from 'vue-router'
 
-import { authToken } from '@/utils/axios/auth'
+import { authLogin } from '@/utils/axios/auth'
 
 import HomeRoutes from './home'
 import LoginRoutes from './login'
@@ -20,13 +20,18 @@ const routes = [
   {
     path: '/index',
     name: 'Index',
-    redirect: '/index/home/overView',
+    redirect: '/home/overView',
     component: () => import('@/views/Index.vue'),
     children: [...HomeRoutes, ...AppManage]
   },
   {
     path: '/',
     redirect: '/home/overView'
+  },
+  {
+    //访问不存在的路由的时候,跳转到首页
+    path: '/:pathMatch(.*)',
+    redirect: '/home/overView'
   }
 ]
 
@@ -35,21 +40,15 @@ const router = createRouter({
   routes
 })
 
-router.beforeEach((to, from, next) => {
-  if (from) {
-    // 毫无意义,但是不加from,会导致from没有使用从而导致打包错误
-  }
-
+router.beforeEach((to, _from, next) => {
   if (to.name === 'Login') {
     next()
   } else {
-    authToken()
-      .then(() => {
-        next()
-      })
-      .catch(() => {
-        router.push('login')
-      })
+    if (authLogin()) {
+      next()
+    } else {
+      router.push('login')
+    }
   }
 })
 export default router

+ 16 - 2
src/router/login.ts

@@ -2,15 +2,29 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:32:43
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-08-31 09:59:13
+ * @LastEditTime: 2024-10-10 17:25:25
  * @FilePath: \Game-Backstage-Management-System\src\router\login.ts
  * @Description:
  *
  */
+import type { RouteLocationNormalized, NavigationGuardNext } from 'vue-router'
+
+import { getLoginState } from '@/utils/localStorage/localStorage'
 export default [
   {
     path: '/login',
     name: 'Login',
-    component: () => import('@/views/Login/LoginView.vue')
+    component: () => import('@/views/Login/LoginView.vue'),
+    beforeEnter: (
+      _to: RouteLocationNormalized,
+      _from: RouteLocationNormalized,
+      next: NavigationGuardNext
+    ) => {
+      if (getLoginState()) {
+        next('/')
+      } else {
+        next()
+      }
+    }
   }
 ]

+ 113 - 4
src/stores/useCommon.ts

@@ -2,23 +2,132 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-28 11:46:10
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-13 15:45:29
+ * @LastEditTime: 2024-10-15 11:01:18
  * @FilePath: \Game-Backstage-Management-System\src\stores\useCommon.ts
  * @Description:通用的store,在多个页面均会使用
  *
  */
 import { reactive } from 'vue'
 import { defineStore } from 'pinia'
+import { getLocalInfo, saveLocalInfo } from '@/utils/localStorage/localStorage'
+import { createDateRange, invaildDateRange } from '@/utils/common'
 
 interface SelectInfo {
   gid: string
   pf: Array<string>
+  startTime: Date
+  endTime: Date
+}
+
+interface MultipleChioce {
+  gid: string
+  pf: Array<string>
+}
+
+interface GameInfo {
+  gid: string
+  gameName: string
+}
+
+const defaultPf = ['web']
+const defaultGid = '1001'
+
+/**
+ * @description: 获取selectInfo
+ * @return {SelectInfo} 返回本地gid、pf和时间
+ */
+const getSelectInfo = (): SelectInfo => {
+  let gid = getLocalInfo('selectInfo', 'gid') as string
+  let pf = getLocalInfo('selectInfo', 'pf') as string[]
+  let localStartTime = getLocalInfo('selectInfo', 'startTime') as string
+  let localEndTime = getLocalInfo('selectInfo', 'endTime') as string
+  let newDateRange = createDateRange(7)
+  let startTime = newDateRange[0]
+  let endTime = newDateRange[1]
+
+  gid = gid ? gid : defaultGid
+  pf = pf ? [pf[0]] : defaultPf
+  if (invaildDateRange(localStartTime, localEndTime)) {
+    startTime = new Date(localStartTime)
+    endTime = new Date(localEndTime)
+  }
+  return { pf, gid, startTime, endTime }
+}
+
+/**
+ * @description: 获取多选的pf
+ * @return {{ tempPf: string[] }} 多选pf
+ */
+const getMultipleChioce = (): { tempPf: string[] } => {
+  let tempPf = getLocalInfo('tempMultipleChioce', 'pf') as string[]
+  tempPf = tempPf ? tempPf : defaultPf
+  return { tempPf }
+}
+
+/**
+ * @description: 初始化两种选择器
+ * @param {SelectInfo} selectInfo 单选的选择器
+ * @param {SelectInfo} tempMultipleChioce 多选的选择器
+ * @return {*}
+ */
+const initSelect = (selectInfo: SelectInfo, tempMultipleChioce: MultipleChioce) => {
+  const { gid, pf, startTime, endTime } = getSelectInfo()
+  const { tempPf } = getMultipleChioce()
+  Object.assign(selectInfo, { gid, pf, startTime, endTime })
+  Object.assign(tempMultipleChioce, { gid, pf: tempPf })
+  saveLocalInfo('selectInfo', selectInfo)
+  saveLocalInfo('tempMultipleChioce', tempMultipleChioce)
 }
 
 export const useCommonStore = defineStore('commonStore', () => {
+  // 用于保存当前的gid和pf的选择
   const selectInfo = reactive<SelectInfo>({
-    gid: '1001',
-    pf: ['wx']
+    gid: defaultGid,
+    pf: defaultPf,
+    startTime: new Date(),
+    endTime: new Date()
+  })
+
+  // 临时用来保存多选平台,因为目前业务只有部分页面需要多选,后续可能会改为全部多选
+  const tempMultipleChioce = reactive<MultipleChioce>({
+    gid: defaultGid,
+    pf: defaultPf
   })
-  return { selectInfo }
+
+  initSelect(selectInfo, tempMultipleChioce)
+
+  /**
+   * @description: 保存现有的selectInfo
+   * @return {*}
+   */
+  const saveSelectInfo = () => {
+    localStorage.setItem('selectInfo', JSON.stringify(selectInfo))
+  }
+
+  /**
+   * @description: 保存现有的tempMultipleChioce
+   * @return {*}
+   */
+  const saveTempMultipleChioce = () => {
+    localStorage.setItem('tempMultipleChioce', JSON.stringify(tempMultipleChioce))
+  }
+
+  const changeDateRange = (date: Array<Date>) => {
+    const startTime = date[0]
+    const endTime = date[1]
+
+    selectInfo.startTime = startTime
+    selectInfo.endTime = endTime
+    saveSelectInfo()
+  }
+
+  const allGameInfo = reactive<Array<GameInfo>>([])
+  return {
+    selectInfo,
+    allGameInfo,
+    tempMultipleChioce,
+    saveSelectInfo,
+    saveTempMultipleChioce,
+    changeDateRange
+  }
 })

+ 2 - 5
src/stores/useTable.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-22 10:05:10
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-08-28 11:44:55
+ * @LastEditTime: 2024-10-15 11:24:59
  * @FilePath: \Game-Backstage-Management-System\src\stores\useTable.ts
  * @Description:
  *
@@ -11,9 +11,6 @@
 import { reactive } from 'vue'
 import { defineStore } from 'pinia'
 
-// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
-// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
-// 第一个参数是你的应用中 Store 的唯一 ID。
 interface QueryGameInfo {
   gid: string
   gameName: string
@@ -23,7 +20,7 @@ export const useTableStore = defineStore('tableStore', () => {
   // 用户表查询的时候的参数
   const playerQueryInfo = reactive({
     gid: '1001',
-    pf: 'web'
+    pf: 'wx'
   })
 
   // 更新用户表查询参数

+ 5 - 2
src/types/dataAnalysis.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-23 14:58:29
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-13 16:29:32
+ * @LastEditTime: 2024-09-28 11:28:23
  * @FilePath: \Game-Backstage-Management-System\src\types\dataAnalysis.ts
  * @Description:用于dataAnalysis相关组件的type
  *
@@ -29,7 +29,9 @@ export interface DropDownInfo {
  */
 export interface ReqConfig {
   url: string
-  otherOptions: any
+  otherOptions: {
+    [key: string]: any
+  }
 }
 
 // 数据展示的字段信息
@@ -111,6 +113,7 @@ export interface HeaderCardProps {
   openDateSelect?: boolean // 是否开启时间选择
   needPfSelect?: boolean // 是否需要平台选择
   needBreadcrumb?: boolean // 是否需要面包屑导航
+  isRadio?: boolean // 是否为单选
 }
 
 // 趋势图组件需要的信息

+ 1 - 1
src/types/dialog.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-04 14:02:44
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-04 14:12:51
+ * @LastEditTime: 2024-10-12 18:03:07
  * @FilePath: \Game-Backstage-Management-System\src\types\dialog.ts
  * @Description:
  *

+ 7 - 2
src/types/res.ts

@@ -1,8 +1,13 @@
 export interface ResponseInfo {
   code: number
   msg: string
-  data: any
-  count: number
+  data?: any
+  count?: number
+}
+
+export interface ErrorResInfo {
+  code: number
+  msg: string
 }
 
 export enum MessageType {

+ 4 - 1
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-11 17:02:07
+ * @LastEditTime: 2024-10-12 18:25:23
  * @FilePath: \Game-Backstage-Management-System\src\types\table.ts
  * @Description:
  *
@@ -85,12 +85,15 @@ export interface PropsParams {
   needRightTools?: boolean // 是否需要右侧工具栏
   openFilterQuery?: boolean // 是否开启上方查询功能
   openPageQuery?: boolean // 是否开启分页查询
+  openRemoteinquiry?: boolean // 是否开启远程查询
 
   dataList?: Array<any> // 表格数据,可以直接传入,也可以给请求地址来请求
   queryInfo?: Array<QueryInfo> // 上方查询功能所需要的信息
   paginationConfig: TablePaginationSetting // 表格分页的信息
   tableFieldsInfo: Array<TableFieldInfo> // 表格字段信息
   requestConfig?: ReqConfig
+
+  watchReqProps?: Array<string> // 需要被监听的属性
 }
 
 // 选择下拉框的信息格式

+ 12 - 15
src/utils/axios/auth.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-21 11:12:21
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-08-31 10:59:37
+ * @LastEditTime: 2024-10-10 18:31:10
  * @FilePath: \Game-Backstage-Management-System\src\utils\axios\auth.ts
  * @Description:
  *
@@ -10,19 +10,16 @@
 
 import { ElMessage } from 'element-plus'
 import { MessageType } from '@/types/res'
+import { getLoginState } from '../localStorage/localStorage'
 
-export const authToken = () => {
-  return new Promise((reslove, reject) => {
-    let refreshToken = localStorage.getItem('refreshToken')
-    if (refreshToken) {
-      reslove(true)
-    } else {
-      ElMessage({
-        type: MessageType.Warning,
-        message: '请先登录',
-        duration: 1500
-      })
-      reject(false)
-    }
-  })
+export const authLogin = (): boolean => {
+  const state = getLoginState()
+  if (!state) {
+    ElMessage({
+      type: MessageType.Warning,
+      message: '请先登录',
+      duration: 1500
+    })
+  }
+  return state
 }

+ 86 - 41
src/utils/axios/axiosInstance.ts

@@ -2,71 +2,116 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:18:52
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 10:58:26
+ * @LastEditTime: 2024-10-15 11:53:42
  * @FilePath: \Game-Backstage-Management-System\src\utils\axios\axiosInstance.ts
  * @Description:
  *
  */
 // 引入axios
-import axios from 'axios'
-import router from '@/router'
+
 import { ElMessage } from 'element-plus'
 import { useRequest } from '@/hooks/useRequest'
 import { MessageType } from '@/types/res'
+import { getToken, refreshToken, setToken, removeAllToeken } from '../token/token'
+import { setLoginState } from '../localStorage/localStorage'
 
-const { AllApi } = useRequest()
-// 创建axios实例
-const axiosInstance = axios.create()
-// 请求拦截器
-axiosInstance.interceptors.request.use(
-  function (config) {
-    // console.log(config);
-    if (config.url === AllApi.gerRefreshToken) {
-      let refreshToken = localStorage.getItem('refreshToken')
-      if (refreshToken) {
-        config.headers.Authorization = refreshToken
-      }
-      console.log(config.headers.Authorization)
-    } else if (config.url !== AllApi.userLogin) {
-      let token = localStorage.getItem('token')
+import axios from 'axios'
+import router from '@/router'
 
-      if (token) {
-        config.headers.Authorization = token
-      }
-    }
+const { baseURL } = useRequest()
 
-    // 在发送请求之前做些什么
-    return config
-  },
-  function (error) {
-    // 对请求错误做些什么
-    return Promise.reject(error)
+const errorCodeMsg: {
+  [key: number]: string
+} = {
+  422: '请求缺少参数,请检查后重试'
+}
+
+// 创建axios实例
+const axiosInstance = axios.create({
+  baseURL,
+  headers: {
+    Authorization: `${getToken()}`
   }
-)
+})
+
+let isRefreshing = false // 是否正在刷新token
+let requestQueue: any[] = [] // 存储请求队列
+
+/**
+ * @description: 未登录的情况下展示的信息
+ * @param {*} ElMessage
+ * @return {*}
+ */
+const showUnloginInfo = (msg: string = '请先登录') => {
+  ElMessage({
+    type: MessageType.Warning,
+    message: msg,
+    duration: 1500
+  })
+  removeAllToeken()
+  setLoginState(false)
+  router.push('/login')
+}
+
 // 添加响应拦截器
 axiosInstance.interceptors.response.use(
-  function (response) {
-    // 对响应数据做点什么
-    if (response.data.code === -1) {
-      localStorage.removeItem('token')
-      localStorage.removeItem('refreshToken')
+  async function (response) {
+    const { code } = response.data
 
-      ElMessage({
-        type: MessageType.Warning,
-        message: '登录已过期,请重新登陆',
-        duration: 1500
-      })
-      router.push('/login')
+    // -2是token为空的情况
+    if (code === -2) {
+      showUnloginInfo()
+    }
+    // -1是token过期的情况
+    if (code === -1) {
+      const config = response.config // 保存一下这一次的请求,等token刷新成功之后重新请求
+
+      if (!isRefreshing) {
+        isRefreshing = true
+        return await refreshToken()
+          .then((res) => {
+            if (res.data.code === 0) {
+              const token = JSON.parse(JSON.stringify(res.data.data.token))
+              setToken(token)
+              config.headers.Authorization = token // 将本次请求的token替换成新的token
+              requestQueue.forEach((cb) => cb(token)) // 将队列中的请求重新发起
+              requestQueue = []
+              return axiosInstance(config)
+            } else {
+              showUnloginInfo('登录已过期,请重新登陆')
+            }
+          })
+          .catch((err) => {
+            console.log(err)
+            showUnloginInfo('登录已过期,请重新登陆')
+          })
+          .finally(() => {
+            isRefreshing = false
+          })
+      } else {
+        // 如果正在刷新token,将本次请求加入队列,等token刷新成功之后重新发起请求
+        return new Promise((resolve) => {
+          requestQueue.push((token: string) => {
+            config.headers.Authorization = token
+            resolve(axiosInstance(config))
+          })
+        })
+      }
     }
     return response.data
   },
   function (error) {
+    let code = error.response.data.code
+    let msg = errorCodeMsg[code] ?? '服务器错误,请稍后再试'
+
     // 对响应错误做点什么
     ElMessage({
       type: MessageType.Error,
-      message: '服务器错误,请稍后再试',
+      message: msg,
       duration: 1500
     })
+    // console.log(error)
+    // setLoginState(false)
     // router.push('/login')
     return Promise.reject(error)
   }

+ 54 - 2
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-13 17:35:33
+ * @LastEditTime: 2024-10-15 10:47:42
  * @FilePath: \Game-Backstage-Management-System\src\utils\common\index.ts
  * @Description:
  *
@@ -10,7 +10,12 @@
 
 import { ElMessage } from 'element-plus'
 
-// 防抖
+/**
+ * @description:  包装防抖函数
+ * @param {array} func 函数
+ * @param {number} delay 时间
+ * @return {*}
+ */
 export function debounceFunc<T extends (...args: any[]) => any>(
   func: T,
   delay: number
@@ -24,6 +29,28 @@ export function debounceFunc<T extends (...args: any[]) => any>(
   }
 }
 
+/**
+ * @description: 包装节流函数
+ * @param {T} func 函数
+ * @param {number} delay 时间
+ * @return {*}
+ */
+export function throttleFunc<T extends (...args: any[]) => any>(
+  func: T,
+  delay: number
+): (...args: Parameters<T>) => void {
+  let lastCall = 0
+
+  return function (...args: Parameters<T>) {
+    const now = Date.now()
+
+    if (now - lastCall >= delay) {
+      lastCall = now
+      func(...args)
+    }
+  }
+}
+
 // 小数转百分比
 export function decimalToPercentage(decimal: number, decimalPlaces: number = 0): string {
   // Convert the decimal to a percentage by multiplying by 100
@@ -77,6 +104,31 @@ export function resetTimeToMidnight(dateTime: Date): string {
 }
 
 /**
+ * @description: 创建一个日期范围
+ * @param {*} day 天数
+ * @return {*}
+ */
+export const createDateRange = (day: number): Array<Date> => {
+  const end = new Date()
+  const start = new Date()
+  start.setTime(start.getTime() - 3600 * 1000 * 24 * day)
+  return [start, end]
+}
+
+/**
+ * @description:  判断日期范围是否有效
+ * @param {string} startTime 开始时间
+ * @param {string} endTime 结束时间
+ * @return {boolean} 返回是否有效
+ */
+export const invaildDateRange = (startTime: string, endTime: string): boolean => {
+  const start = new Date(startTime).getTime()
+  const end = new Date(endTime).getTime()
+  // 判断日期是否有效,`isNaN` 判断生成的时间是否为 "Invalid Date"
+  return !isNaN(start) && !isNaN(end) && start < end
+}
+
+/**
  * @description: 生成一个打包后可以使用的url
  * @param {string} url  传入一个assets文件夹下的文件名
  * @return {*}

+ 80 - 0
src/utils/localStorage/localStorage.ts

@@ -0,0 +1,80 @@
+/**
+ * @description: 从本地拿数据
+ * @param {string} name locastorage的key
+ * @param {string} prop 属性名,不传就返回整个对象
+ * @return {string | string[]} 本地信息
+ */
+const getLocalInfo = (name: string, prop?: string): string | string[] => {
+  let info = localStorage.getItem(name)
+  let result = ''
+  if (info) {
+    let jsonInfo = JSON.parse(info)
+
+    if (prop) result = jsonInfo[prop]
+    else result = jsonInfo
+  }
+
+  return result
+}
+
+/**
+ * @description: 保存到本地
+ * @param {string} name locastorage的key
+ * @param {any} value 值
+ * @param {string} prop 属性名
+ * @return {*}
+ */
+const saveLocalInfo = (name: string, value: any, prop?: string) => {
+  let info = localStorage.getItem(name)
+  if (info) {
+    let jsonInfo = JSON.parse(info)
+    if (prop) jsonInfo[prop] = value
+    localStorage.setItem(name, JSON.stringify(jsonInfo))
+  } else {
+    localStorage.setItem(name, JSON.stringify(value))
+  }
+}
+
+/**
+ * @description: 设置登陆状态
+ * @param {boolean} state 登录状态
+ * @return {*}
+ */
+const setLoginState = (state: boolean) => {
+  localStorage.setItem('loginState', JSON.stringify(state))
+}
+
+/**
+ * @description: 获取当前登录状态
+ * @return {boolean}
+ */
+const getLoginState = (): boolean => {
+  return JSON.parse(localStorage.getItem('loginState') as string) as boolean
+}
+
+/**
+ * @description: 设置平台选择下拉框的引导层的显示状态
+ * @param {boolean} state 状态
+ * @return {*}
+ */
+const setPfSelectTourShowState = (state: boolean) => {
+  localStorage.setItem('pfSelectTourShowState', JSON.stringify(state))
+}
+
+/**
+ * @description:
+ * @param {*} boolean
+ * @return {*}
+ */
+const getPfSelectTourShowState = (): boolean => {
+  return JSON.parse(localStorage.getItem('pfSelectTourShowState') as string)
+}
+
+export {
+  getLocalInfo,
+  saveLocalInfo,
+  setLoginState,
+  getLoginState,
+  setPfSelectTourShowState,
+  getPfSelectTourShowState
+}

+ 14 - 22
src/utils/resource/index.ts

@@ -2,11 +2,12 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-31 14:51:20
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-18 09:24:17
+ * @LastEditTime: 2024-10-14 16:11:04
  * @FilePath: \Game-Backstage-Management-System\src\utils\resource\index.ts
  * @Description:
  *
  */
+
 // 缓存对象
 const resourceCache: Record<string, string> = {}
 
@@ -17,33 +18,24 @@ const resourceCache: Record<string, string> = {}
  * @param url - 资源的加载路径
  * @returns 返回blob对象的url
  */
-export const loadResource = (url: string): Promise<string> => {
-  // console.log(resourceCache, url)
-
+export const loadResource = async (url: string): Promise<string> => {
   // 检查是否已经存储了这个资源,已经缓存了就直接返回
   if (resourceCache[url]) {
-    // alert(JSON.stringify(resourceCache[url]))
     return Promise.resolve(resourceCache[url])
   }
 
   // 如果没有缓存则去请求这个资源,并将他转为bolb对象,并且缓存blob对象的ulr
-  return fetch(url)
-    .then((response) => {
-      if (!response.ok) {
-        throw new Error(`资源加载失败: ${url}`)
-      }
-
-      return response.blob()
-    })
-    .then((blob) => {
-      const objectURL = URL.createObjectURL(blob)
-      resourceCache[url] = objectURL // Cache the resource
-      return objectURL
-    })
-    .catch((error) => {
-      console.error(`资源加载出错 ${url}:`, error)
-      throw error
-    })
+  try {
+    const resourceBlob = await fetch(url)
+    if (!resourceBlob.ok) throw new Error(`资源加载失败: ${url}`)
+    const blob = await resourceBlob.blob()
+    const objectURL = URL.createObjectURL(blob)
+    resourceCache[url] = objectURL
+    return objectURL
+  } catch (err) {
+    console.log(err)
+    throw new Error('资源加载失败')
+  }
 }
 
 /**

+ 37 - 21
src/utils/table/table.ts

@@ -1,34 +1,50 @@
 import { useTableStore } from '@/stores/useTable'
 import { useRequest } from '@/hooks/useRequest'
+
 import axiosInstance from '../axios/axiosInstance'
+
 const { AllApi } = useRequest()
 
-// 拿到所有游戏的信息
+/**
+ * @description: 格式化游戏信息
+ * @param {Array} data 获取到的表格数据
+ * @param {number} code 状态码
+ * @return {*}
+ */
+const formatGameInfo = (data: Array<any>, code: number): Array<any> => {
+  const returnData: Array<any> = []
+
+  if (code === 0 && Array.isArray(data)) {
+    let gameInfoList = data.map((item) => {
+      return { gid: item.gid, gameName: item.gameName }
+    })
+    returnData.splice(0, returnData.length, ...gameInfoList)
+  } else {
+    console.log('获取游戏列表失败')
+  }
+  return returnData
+}
+
+/**
+ * @description: 获取所有游戏信息
+ * @return {*}
+ */
 export const getAllGameInfo = async () => {
   try {
-    const tableStore = useTableStore() // 这一句不要直接卸载函数外面,会导致在pinia挂载之前被调用
+    const tableStore = useTableStore() // 这一句不要直接写在函数外面,会导致在pinia挂载之前被调用
 
+    // 如果已经有了,那么直接用缓存
     if (tableStore.allGameInfo.length) return tableStore.allGameInfo
-    else {
-      const response = await axiosInstance.post(AllApi.getGameTable, {
-        appSecret: '6YJSuc50uJ18zj45'
-      })
-      const result = JSON.parse(JSON.stringify(response))
-      const data = result.data // 拿到返回的数据
 
-      if (result.code === 0) {
-        if (Array.isArray(data)) {
-          let gameInfoList = data.map((item) => {
-            return { gid: item.gid, gameName: item.gameName }
-          })
-          tableStore.setGameInfo(gameInfoList)
-          return gameInfoList
-        }
-      } else {
-        console.log('获取游戏列表失败')
-        return []
-      }
-    }
+    const response = await axiosInstance.post(AllApi.getGameTable, {
+      appSecret: '6YJSuc50uJ18zj45'
+    })
+    const result = JSON.parse(JSON.stringify(response))
+    const data = result.data
+    const returnData: Array<any> = formatGameInfo(data, result.code)
+    tableStore.setGameInfo(returnData) // 格式化后的数据放到tablestore中
+
+    return returnData
   } catch (err) {
     console.log(err)
     throw new Error('获取游戏列表失败')

+ 95 - 0
src/utils/token/token.ts

@@ -0,0 +1,95 @@
+import { useRequest } from '@/hooks/useRequest'
+
+import axios from 'axios'
+
+const { AllApi, baseURL } = useRequest()
+
+const TokenKey = 'token' // token的key
+const RefreshTokenKey = 'refreshToken' // 刷新token的key
+
+/**
+ * @description: 获取token
+ * @return {*}
+ */
+const getToken = () => {
+  return localStorage.getItem(TokenKey)
+}
+
+/**
+ * @description: 设置token
+ * @param {string} token token
+ * @return {*}
+ */
+const setToken = (token: string) => {
+  localStorage.setItem(TokenKey, token)
+}
+
+/**
+ * @description: 获取刷新Token
+ * @return {*}
+ */
+const getrefreshToken = () => {
+  return localStorage.getItem(RefreshTokenKey)
+}
+
+/**
+ * @description: 设置刷新Token
+ * @param {string} refreshToken 刷新token
+ * @return {*}
+ */
+const setRefreshToken = (refreshToken: string) => {
+  localStorage.setItem(RefreshTokenKey, refreshToken)
+}
+
+/**
+ * @description: 刷新Token
+ * @return {*}
+ */
+const refreshToken = async () => {
+  // 这里不要用实例去请求,如果refreshtoken也返回-1的话,会导致程序卡死
+  return await axios.post(
+    `${baseURL}${AllApi.getRefreshToken}`,
+    {},
+    {
+      headers: {
+        Authorization: `${getrefreshToken()}`
+      }
+    }
+  )
+}
+
+/**
+ * @description: 移除token
+ * @return {*}
+ */
+const removeToken = () => {
+  localStorage.removeItem(TokenKey)
+}
+
+/**
+ * @description: 移除refreshtoken
+ * @return {*}
+ */
+const removeRefreshToken = () => {
+  localStorage.removeItem(RefreshTokenKey)
+}
+
+/**
+ * @description: 移除所有token
+ * @return {*}
+ */
+const removeAllToeken = () => {
+  removeToken()
+  removeRefreshToken()
+}
+
+export {
+  getToken,
+  setToken,
+  getrefreshToken,
+  setRefreshToken,
+  refreshToken,
+  removeToken,
+  removeRefreshToken,
+  removeAllToeken
+}

+ 3 - 3
src/views/AppManage/BaseInfoView.vue

@@ -1,13 +1,13 @@
 <script setup lang="ts">
-import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
-
 import { reactive, onMounted, ref, watch } from 'vue'
 import { initLoadResouce } from '@/utils/resource'
 import { copyText } from '@/utils/common'
-import axiosInstance from '@/utils/axios/axiosInstance'
 import { useRequest } from '@/hooks/useRequest'
 import { useCommonStore } from '@/stores/useCommon'
 
+import axiosInstance from '@/utils/axios/axiosInstance'
+import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+
 const { AllApi } = useRequest()
 const { selectInfo } = useCommonStore()
 

+ 24 - 19
src/views/AppManage/EventDetailsView.vue

@@ -1,23 +1,23 @@
 <script setup lang="ts">
-import Form from '@/components/form/Form.vue'
-import Table from '@/components/Table.vue'
-import Dialog from '@/components/common/Dialog.vue'
-import FileUpload from '@/components/form/FileUpload.vue'
-
 import type { ReqConfig } from '@/types/dataAnalysis'
-import { type FormField, FormFieldType } from '@/types/form'
-import { ElMessageBox, type FormRules } from 'element-plus'
-import type { FormConfig } from '@/types/form'
-import {
-  type TablePaginationSetting,
-  type TableFieldInfo,
-  FieldSpecialEffectType
-} from '@/types/table'
+import type { FormField, FormConfig } from '@/types/form'
+import type { FormRules } from 'element-plus'
 import type { DialogConfig } from '@/types/dialog'
+import type { TablePaginationSetting, TableFieldInfo } from '@/types/table'
+
+import { FormFieldType } from '@/types/form'
+import { ElMessageBox } from 'element-plus'
+import { FieldSpecialEffectType } from '@/types/table'
 import { watch, onUnmounted, reactive, ref } from 'vue'
 import { useRoute } from 'vue-router'
 import { useRequest } from '@/hooks/useRequest'
 import { useCommonStore } from '@/stores/useCommon'
+
+import Form from '@/components/form/Form.vue'
+import Table from '@/components/Table.vue'
+import Dialog from '@/components/common/Dialog.vue'
+import FileUpload from '@/components/form/FileUpload.vue'
+
 import axiosInstance from '@/utils/axios/axiosInstance'
 import router from '@/router'
 
@@ -324,8 +324,8 @@ const dialogConfig = reactive<DialogConfig>({
 const getEventInfo = (id: number) => {
   axiosInstance
     .post(AllApi.gameActionDetail, { id })
-    .then((data) => {
-      analysisResCode(data).then((info) => {
+    .then((data: any) => {
+      analysisResCode(data, '请求', true).then((info) => {
         let result = info.data
         let updateEventField = {
           gid: '',
@@ -368,9 +368,14 @@ const cancelEdit = () => {
  * @return {*}
  */
 const saveEdit = () => {
-  eventFormRef.value.submitFormData().then(() => {
-    changeEditState(false)
-  })
+  eventFormRef.value
+    .submitFormData()
+    .then(() => {
+      changeEditState(false)
+    })
+    .catch((err: any) => {
+      console.log(err)
+    })
 }
 
 /**
@@ -485,7 +490,7 @@ onUnmounted(() => {
         :pagination-config="pageConfig"
         :table-fields-info="tableFieldConfig"
         :request-config="tableReqConfig"
-        :need-download="true"
+        :need-download="false"
         @add-new-item="addNewAttr"
         ref="optionTableRef"
       >

+ 270 - 129
src/views/AppManage/EventManageView.vue

@@ -2,26 +2,77 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-02 17:57:15
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-18 12:00:30
+ * @LastEditTime: 2024-10-15 14:12:21
  * @FilePath: \Game-Backstage-Management-System\src\views\AppManage\EventManageView.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
-import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
-import { shouldListenToEvent } from '@/utils/table/table'
-import { ref, reactive, computed } from 'vue'
-
-// import { ElMessage, ElNotification } from 'element-plus'
+import type { ResponseInfo } from '@/types/res'
 
-import FileUpload from '@/components/form/FileUpload.vue'
-import axiosInstance from '@/utils/axios/axiosInstance'
+import { ref, reactive, computed } from 'vue'
 import { useRequest } from '@/hooks/useRequest'
 import { useCommonStore } from '@/stores/useCommon'
 import { downLoadData } from '@/utils/table/table'
-
 import { resetTimeToMidnight } from '@/utils/common'
+import { shouldListenToEvent } from '@/utils/table/table'
+import { throttleFunc } from '@/utils/common'
+
+import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
 import router from '@/router'
+import FileUpload from '@/components/form/FileUpload.vue'
+import axiosInstance from '@/utils/axios/axiosInstance'
+
+interface uploadEvent {
+  id?: number // 更新需要传入id
+  gid: string
+  actionId: string
+  actionName: string
+  status: number
+  remark?: string
+}
+
+interface UploadOption {
+  id?: number // 更新需要传入id
+  optionId: string
+  optionName: string
+  optionType: string
+  actionId: number
+  status: number
+}
+
+interface DownLoadEvent {
+  actionId: string
+  actionName: string
+  createdAt: string
+  gid: string
+  id: number
+  remark: string
+  status: number
+  updatedAt: string
+}
+
+interface DownLoadOption {
+  actionId: number
+  createdAt: string
+  id: number
+  optionId: string
+  optionName: string
+  optionType: string
+  status: number
+  updatedAt: string
+}
+
+type GetTableReturn<T> = T extends 'all'
+  ? {
+      allEventTable: Array<DownLoadEvent>
+      allOptionsInfo: { [key: string]: Array<DownLoadOption> }
+    }
+  : T extends 'event'
+    ? { allEventTable: Array<DownLoadEvent> }
+    : T extends 'option'
+      ? { allOptionsInfo: { [key: string]: Array<DownLoadOption> } }
+      : never
 
 const { selectInfo } = useCommonStore()
 
@@ -51,39 +102,66 @@ const nowRouteName = computed(() => router.currentRoute.value.name)
  */
 const headerAddPath = (info: any) => {
   const { name, pathName } = info
+
   headerCard.value?.addPath(name, pathName)
 }
 
 /**
+ * @description:  展示上传之后的反馈信息
+ * @param {*} state 这个状态只表示是否全部成功,其余情况皆为false
+ * @param {*} name 区分是事件还是选项
+ * @return {*}
+ */
+const showUploadMsg = (failedCount: number, totalCount: number, name: string): boolean => {
+  if (failedCount < 0) throw new Error('Error!FailedCount < 0')
+
+  let state = true
+  let duration = 3000
+  let title = `${name}上传完成`
+  let position: 'top-left' | 'top-right' = 'top-left'
+  let message: string = `${name}全部上传成功`
+  let type: 'success' | 'warning' | 'error' = 'success'
+
+  // 有上传失败的情况下
+  if (failedCount > 0) {
+    state = false
+    position = 'top-right'
+    duration = 8000
+    if (failedCount === totalCount) {
+      message = `${name}全部上传失败`
+      type = 'error'
+    } else {
+      message = `${name}部分上传失败`
+      type = 'warning'
+    }
+  }
+  ElNotification({
+    type,
+    title,
+    message,
+    duration,
+    position
+  })
+
+  return state
+}
+
+/**
  * @description:  提交所有新上传的事件及选项请求
  * @param {*} reqList 请求列表
  * @param {*} msg 提示信息,用于展示上传之后返回的消息
  * @return {*}
  */
-const submitUpload = async (reqList: Array<Promise<boolean>>, msg?: string) => {
-  await Promise.allSettled(reqList).then((res) => {
-    if (
-      res.every((item) => {
-        return item.status === 'fulfilled' && item.value === true
-      })
-    ) {
-      ElNotification({
-        type: 'success',
-        title: '上传完成',
-        message: `${msg}上传成功`,
-        position: 'top-left'
-      })
-    } else {
-      ElNotification({
-        type: 'error',
-        title: '上传完成',
-        message: `${msg}部分上传失败,请检查参数`,
-        duration: 3000,
-        position: 'top-left'
-      })
-      // ElMessage.error()
-    }
+const submitUpload = async (reqList: Array<Promise<boolean>>, msg: string): Promise<boolean> => {
+  const result = await Promise.allSettled(reqList).then((res) => {
+    const failedList = res.filter((item: any) => item.value === false)
+    const failedCount = failedList.length
+    const totalCount = res.length
+    let state = showUploadMsg(failedCount, totalCount, msg)
+    return state
   })
+
+  return result
 }
 
 /**
@@ -112,7 +190,7 @@ const getTableData = async (
       let resData = JSON.parse(JSON.stringify(res))
       let result = []
       if (!resData.data) return []
-      // nowOptionList.push(...resData.data)
+
       if (resData.count > total) {
         result = await getTableData(url, otherInfo, total, limit)
       }
@@ -135,19 +213,19 @@ const getTableData = async (
 const batReqOptionsData = async (
   url: string,
   reqParams: Array<any>,
-  eventTable: Array<any>
-): Promise<Object> => {
+  eventTable: Array<DownLoadEvent>
+): Promise<{ [key: string]: Array<DownLoadOption> }> => {
   let reqList: Array<Promise<any>> = []
   let finalResult: {
-    [key: string]: Array<any>
+    [key: string]: Array<DownLoadOption>
   } = {}
 
   reqParams.map((item) => {
     reqList.push(
       getTableData(url, item)
         .then((res) => {
-          let actionId = eventTable.find((i) => i.id === item.actionId).actionId
-          finalResult[actionId] = res
+          let actionId = eventTable.find((i) => i.id === item.actionId)?.actionId
+          if (actionId) finalResult[actionId] = res
         })
         .catch((err) => {
           console.log(err)
@@ -158,37 +236,33 @@ const batReqOptionsData = async (
   return finalResult
 }
 
-type GetTableReturn<T> = T extends 'all'
-  ? { allEventTable: Array<any>; allOptionsInfo: any }
-  : T extends 'event'
-    ? { allEventTable: Array<any> }
-    : T extends 'option'
-      ? { allOptionsInfo: any }
-      : never
-
 /**
  * @description: 拿到事件数据和选项数据,首先需要拿到所有的事件列表,然后将他们的actionId抽出来形成一个列表,这个列表去作为optin的查询参数批量查询
  * @return {*} 返回事件数据和选项数据
  */
 const getAllTable = async <T extends 'all' | 'event' | 'option' = 'all'>(
-  table?: T
+  table: T = 'all' as any
 ): Promise<GetTableReturn<T>> => {
-  let allEventTable: Array<any> = [],
-    allOptionsInfo: any = null
+  let allEventTable: Array<DownLoadEvent> = [],
+    allOptionsInfo: { [key: string]: Array<DownLoadOption> } = {}
+
+  allEventTable = await getTableData(AllApi.gameActionList, { gid: selectInfo.gid })
+
+  let optionReqList = allEventTable.map((item) => {
+    return { actionId: item.id }
+  })
+  allOptionsInfo = await batReqOptionsData(
+    AllApi.gameActionOptionList,
+    optionReqList,
+    allEventTable
+  )
+  // 只需要事件列表在这里就返回
   if (table === 'event') {
-    allEventTable = await getTableData(AllApi.gameActionList, { gid: selectInfo.gid })
     return { allEventTable } as GetTableReturn<T>
   }
 
+  // 只需要选项在这里就只返回选项
   if (table === 'option') {
-    let optionReqList = allEventTable.map((item) => {
-      return { actionId: parseInt(item.id) }
-    })
-    allOptionsInfo = await batReqOptionsData(
-      AllApi.gameActionOptionList,
-      optionReqList,
-      allEventTable
-    )
     return { allOptionsInfo } as GetTableReturn<T>
   }
 
@@ -217,6 +291,117 @@ const startDownload = async () => {
   })
 }
 
+// 把开始下载方法包装一下,节流
+const throttleStartDownload = throttleFunc(startDownload, 1000)
+
+/**
+ * @description: 创建一个上传请求函数
+ * @param {*} url 请求地址
+ * @param {*} reqParams 请求参数
+ * @param {*} name 上传的是事件还是选项
+ * @return {*}
+ */
+const createUploadReqFunc = async (url: string, reqParams: any, name: string): Promise<boolean> => {
+  return axiosInstance
+    .post<string, ResponseInfo>(url, reqParams)
+    .then((res) => {
+      if (res.code !== 0) throw new Error(res.msg)
+      return res.code === 0
+    })
+    .catch((err) => {
+      console.log(err)
+      ElNotification({
+        type: 'error',
+        title: '上传失败',
+        message: `${name}上传失败,请检查参数`,
+        position: 'top-right',
+        duration: 8000
+      })
+      return false
+    })
+}
+
+/**
+ * @description: 上传所有事件
+ * @param {*} uploadEventTable 上传的事件列表
+ * @param {*} allEventTable 获取的事件列表
+ * @return {Promise<boolean>} 返回是否上传成功
+ */
+const uploadEvent = async (
+  uploadEventTable: Array<uploadEvent>,
+  allEventTable: Array<DownLoadEvent>
+): Promise<boolean> => {
+  let eventReqUrl = AllApi.setGameAction
+  let eventReqList: Array<Promise<boolean>> = []
+  uploadEventTable.map((item: uploadEvent) => {
+    let { id, ...otherInfo } = item
+    if (allEventTable.some((i: DownLoadEvent) => i.actionId === item.actionId)) {
+      eventReqUrl = AllApi.updateGameAction
+    }
+    let eventReq = createUploadReqFunc(eventReqUrl, otherInfo, item.actionName)
+    eventReqList.push(eventReq) // 统一放入请求列表中
+  })
+  return await submitUpload(eventReqList, '事件') // 等待所有的事件请求完成
+}
+
+/**
+ * @description: 上传所有选项
+ * @param {*} uploadOptionsInfo 上传的选项列表
+ * @return {*}
+ */
+const uploadOpiton = async (uploadOptionsInfo: { [key: string]: Array<UploadOption> }) => {
+  const { allEventTable, allOptionsInfo } = await getAllTable() // 重新获取所有事件列表和选项列表
+  let optionsReqList: Array<Promise<boolean>> = []
+  if (Object.keys(uploadOptionsInfo).length) {
+    // 开始上传选项
+    allEventTable.map((item) => {
+      // 在上传的事件列表中,找到有对应的事件的actionid的那一组数据
+      let uploadOptionItem = uploadOptionsInfo[item.actionId]
+      // 在现有的事件列表中,找到对应事件的actionid的那一组数据
+      let nowOptionItem = allOptionsInfo[item.actionId] // 找到所有在已有事件列表中的选项列表
+
+      // 如果有已存在的事件,并且上传的选项列表中也有对应的actionid,则开始上传
+      if (uploadOptionItem) {
+        // 对找到的那一组数据进行循环,区分出来哪些是已有的,哪些是新上传的
+        // 新上传的需要给他加上actionid,然后上传,这个actionid其实是事件列表的id字段
+        uploadOptionItem.map((i) => {
+          const { id, actionId: originalActionId, ...otherInfo } = i // 上传参数拆分出来
+          const isUpdate = nowOptionItem.some((k) => k.id === id) // 判断是否是更新选项
+
+          // 设置请求 URL 和参数
+          const optionReqUrl = isUpdate ? AllApi.updateGameActionOption : AllApi.addGameActionOption
+          const reqParams = isUpdate
+            ? { id, ...otherInfo } // 更新选项
+            : { actionId: item.id, ...otherInfo } // 新增选项
+
+          let eventReq = createUploadReqFunc(optionReqUrl, reqParams, item.actionName)
+          optionsReqList.push(eventReq) // 统一放入请求列表中
+        })
+      } else {
+        setTimeout(() => {
+          ElNotification({
+            type: 'warning',
+            title: `没有对应事件`,
+            message: `没有${item.actionName}事件,为该事件添加的选项无效`,
+            position: 'top-right',
+            duration: 8000
+          })
+        }, 0)
+      }
+    })
+
+    await submitUpload(optionsReqList, '选项') // 等待所有选项上传完成
+  } else {
+    ElNotification({
+      type: 'warning',
+      title: '没有选项被上传',
+      message: `上传选项为空`,
+      position: 'top-right',
+      duration: 8000
+    })
+  }
+}
+
 // 上传选项的时候,选项的键要设置为actionid
 // 需要先上传事件,然后上传完了,用上传的选项中的第一个(如果有)的actionid去找到对应的事件的ID(不是actionid),然后作为选项上传的id
 /**
@@ -228,83 +413,38 @@ const startDownload = async () => {
  * @param {*} data 上传的文件数据,里面包含allEventTable:所有的事件列表,allOptionsInfo:所有选项列表
  * @return {*}
  */
-const uploadSuccess = async (data: any) => {
+const uploadSuccess = async (data: {
+  allEventTable: Array<uploadEvent>
+  allOptionsInfo: { [key: string]: Array<UploadOption> }
+}) => {
   let uploadEventTable = data.allEventTable
   let uploadOptionsInfo = data.allOptionsInfo
-  let allEventTable: Array<any> = [],
-    allOptionsInfo: { [key: string]: any } = {}
+
+  let allEventTable: Array<DownLoadEvent> = []
+
   // 上传的事件列表有值,则开始上传
   ;({ allEventTable } = await getAllTable('event')) // 获取所有事件列表和选项列表
 
-  // 开始上传事件
-  let eventReqList: Array<Promise<boolean>> = []
-  let eventReqUrl = AllApi.setGameAction
-  if (Array.isArray(uploadEventTable) && uploadEventTable.length) {
+  let eventUploadResult = false
+
+  if (Array.isArray(uploadEventTable) && uploadEventTable.length > 0) {
     // 将新事件和旧事件区分,对于新事件走新增,对于旧事件走更新
-    uploadEventTable.map((item) => {
-      let { id, createdAt, updatedAt, ...otherInfo } = item
-      if (allEventTable.some((i) => i.actionId === item.actionId)) {
-        eventReqUrl = AllApi.updateGameAction
-      } else {
-        eventReqUrl = AllApi.setGameAction
-      }
-      let eventReq = axiosInstance
-        .post(eventReqUrl, otherInfo)
-        .then((res: any) => {
-          if (res.code === 0) return true
-          return false
-        })
-        .catch((err) => {
-          console.log(err)
-          return false
-        })
-      eventReqList.push(eventReq) // 统一放入请求列表中
+
+    eventUploadResult = await uploadEvent(uploadEventTable, allEventTable)
+    // 如果事件上传全部失败,那么就不要再上传选项了
+    if (eventUploadResult) {
+      uploadOpiton(uploadOptionsInfo)
+    }
+  } else {
+    ElNotification({
+      type: 'error',
+      title: '上传参数有误',
+      message: `上传参数为空,请检查参数`,
+      position: 'top-left',
+      duration: 5000
     })
-    await submitUpload(eventReqList, '事件') // 等待所有的事件请求完成
   }
-  ;({ allEventTable, allOptionsInfo } = await getAllTable()) // 重新获取所有事件列表和选项列表
-  let optionsReqList: Array<Promise<boolean>> = []
 
-  allEventTable.map((item) => {
-    // 在上传的事件列表中,找到有对应的事件的actionid的那一组数据
-    let uploadOptionItem = uploadOptionsInfo[item.actionId] as Array<any>
-    // 在现有的事件列表中,找到对应事件的actionid的那一组数据
-    let nowOptionItem = allOptionsInfo[item.actionId] as Array<any> // 找到所有在已有事件列表中的选项列表
-
-    // 如果有已存在的事件,并且上传的选项列表中也有对应的actionid,则开始上传
-    if (uploadOptionItem) {
-      // 对找到的那一组数据进行循环,区分出来哪些是已有的,哪些是新上传的
-      // 新上传的需要给他加上actionid,然后上传,这个actionid其实是事件列表的id字段
-      uploadOptionItem.map((i) => {
-        let optionReqUrl = AllApi.addGameActionOption // 选项上传的url
-        let { id, actionId, createdAt, updatedAt, ...otherInfo } = i // 上传参数拆分出来
-        let reqParams = {}
-        // 分出来哪些是新增加的选项,哪些是需要更新的选项
-        // 新增的选项需要加入的是事件列表的主键,而更新中需要加入的是option的id
-        if (nowOptionItem.some((k) => k.id === i.id)) {
-          reqParams = { id, ...otherInfo }
-
-          optionReqUrl = AllApi.updateGameActionOption
-        } else {
-          actionId = item.id
-          reqParams = { actionId, ...otherInfo }
-          optionReqUrl = AllApi.addGameActionOption
-        }
-        let optionReq = axiosInstance
-          .post(optionReqUrl, reqParams)
-          .then((res: any) => {
-            if (res.code === 0) return true
-            return false
-          })
-          .catch((err) => {
-            console.log(err)
-            return false
-          })
-        optionsReqList.push(optionReq)
-      })
-    }
-  })
-  await submitUpload(optionsReqList, '选项') // 等待所有选项上传完成
   uploadRef.value?.uploadCallback()
   eventTableRef.value?.updateData()
 }
@@ -324,7 +464,7 @@ const uploadSuccess = async (data: any) => {
     <div class="handleEvent" v-if="nowRouteName === 'EventTable'">
       <div class="fileGroup">
         <el-button class="fileBtn" color="#626aef" @click="startUpload">上传</el-button>
-        <el-button class="fileBtn" @click="startDownload">下载</el-button>
+        <el-button class="fileBtn" @click="throttleStartDownload">下载</el-button>
       </div>
     </div>
     <div class="eventTableBox">
@@ -337,11 +477,12 @@ const uploadSuccess = async (data: any) => {
           :is="Component"
           ref="eventTableRef"
           v-if="shouldListenToEvent(route.name, 'EventTable')"
-          @enterDetail="headerAddPath"
+          @enterEventDetail="headerAddPath"
         />
 
         <!-- 如果不是正常渲染其他组件 -->
-        <component v-if="route.name !== 'EventTable'" :is="Component" />
+        <component v-if="route.name === 'EventDetail'" :is="Component" />
+        <!-- <component v-else :is="Component" /> -->
       </router-view>
     </div>
     <div class="uploadFileBox">

+ 12 - 12
src/views/AppManage/EventMangeTable.vue

@@ -1,19 +1,18 @@
 <script setup lang="ts">
-import Table from '@/components/Table.vue'
-import Dialog from '@/components/common/Dialog.vue'
-import { useRequest } from '@/hooks/useRequest'
-import { useCommonStore } from '@/stores/useCommon'
-
-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 { DialogConfig } from '@/types/dialog'
+import type { TablePaginationSetting, TableFieldInfo, QueryInfo, SelectInfo } from '@/types/table'
 import type { FormField } from '@/types/form'
 
+import { useRequest } from '@/hooks/useRequest'
+import { useCommonStore } from '@/stores/useCommon'
+import { FieldSpecialEffectType, FilterType } from '@/types/table'
+import { FormFieldType } from '@/types/form'
 import { reactive, ref, watch } from 'vue'
 
+import Table from '@/components/Table.vue'
+import Dialog from '@/components/common/Dialog.vue'
 import router from '@/router'
 
 const { selectInfo } = useCommonStore()
@@ -25,7 +24,7 @@ const eventDialog = ref()
 const eventTable = ref<InstanceType<typeof Table> | null>(null)
 
 // 主要为了给面包屑导航提供信息
-const emits = defineEmits(['enterDetail', 'upload'])
+const emits = defineEmits(['enterEventDetail', 'upload'])
 
 // 表格分页设置
 const pagingConfig = reactive<TablePaginationSetting>({
@@ -124,7 +123,7 @@ const eventStatus: Array<SelectInfo> = [
 const queryInfo: Array<QueryInfo> = [
   {
     name: 'search',
-    label: '',
+    label: '事件名',
     type: FilterType.INPUT,
     placeholder: '请输入事件名搜索',
     default: ''
@@ -231,7 +230,7 @@ const dialogInfo = reactive<DialogConfig>({
  */
 const viewDetails = (row: any) => {
   if (row.id) {
-    emits('enterDetail', {
+    emits('enterEventDetail', {
       name: row.actionName,
       pathName: 'EventDetail'
     })
@@ -298,6 +297,7 @@ defineExpose({
       :need-left-tools="true"
       :open-filter-query="true"
       :need-right-tools="true"
+      :open-remoteinquiry="true"
       @add-new-item="addNewEvent"
     >
       <template #tableOperation>

+ 52 - 36
src/views/Home/Analysis/EventAnalysisDetail.vue

@@ -2,21 +2,13 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-27 17:11:23
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-13 14:41:37
+ * @LastEditTime: 2024-10-14 11:07:56
  * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\EventAnalysisDetail.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
-import Table from '@/components/Table.vue'
-import TemporalTrend from '@/components/dataAnalysis/TemporalTrend.vue'
-import { resetTimeToMidnight } from '@/utils/common'
-
-import { onMounted, reactive, ref, watch } from 'vue'
-import { useRequest } from '@/hooks/useRequest'
-import { useRoute } from 'vue-router'
-import { useAnalysis } from '@/hooks/useAnalysis'
-
+import type { TablePaginationSetting, TableFieldInfo } from '@/types/table'
 import type {
   ReqConfig,
   ResDataFieldInfo,
@@ -24,8 +16,19 @@ import type {
   TemporalTrendProps,
   TrendTableField
 } from '@/types/dataAnalysis'
-import { type TablePaginationSetting, type TableFieldInfo } from '@/types/table'
 
+import { resetTimeToMidnight } from '@/utils/common'
+import { computed, reactive, watch } from 'vue'
+import { useRequest } from '@/hooks/useRequest'
+import { useRoute } from 'vue-router'
+import { useAnalysis } from '@/hooks/useAnalysis'
+import { useCommonStore } from '@/stores/useCommon'
+
+import Table from '@/components/Table.vue'
+import TemporalTrend from '@/components/dataAnalysis/TemporalTrend.vue'
+import router from '@/router'
+
+const { selectInfo, tempMultipleChioce } = useCommonStore()
 const { updateReqConfig } = useAnalysis()
 
 interface eventDetailProps {
@@ -40,7 +43,26 @@ const props = withDefaults(defineProps<eventDetailProps>(), {
 
 const { AllApi } = useRequest()
 
-const eventId = ref(-1)
+/**
+ * @description: 通过计算属性来得到当前的eventID
+ * @return {*}
+ */
+const eventId = computed(() => {
+  const routes = useRoute()
+
+  let query_actionId = routes.query.id as string
+
+  if (query_actionId) {
+    return parseInt(query_actionId)
+  } else {
+    ElMessage.error({
+      message: '参数错误,请返回事件分析页',
+      duration: 1000
+    })
+    router.push('/home/dataAnalysis/eventAnalysisView/eventAnalysisTable')
+    return -1
+  }
+})
 
 // 表格分页设置
 const pagingConfig = reactive<TablePaginationSetting>({
@@ -137,6 +159,7 @@ const tabInfo: Array<TabInfo> = [
   }
 ]
 
+// chart所需要的props
 const chartProps = reactive<TemporalTrendProps>({
   title: '事件趋势',
   type: 2,
@@ -146,40 +169,33 @@ const chartProps = reactive<TemporalTrendProps>({
   tabInfo
 })
 
-const initParams = () => {
-  const routes = useRoute()
-  let query_actionId = routes.query.id as string
-
-  if (query_actionId) {
-    eventId.value = parseInt(query_actionId)
-  }
+/**
+ * @description: 更新请求参数
+ * @param {*} gid gid
+ * @param {*} pf 平台
+ * @param {*} startTime 开始时间
+ * @param {*} endTime 结束时间
+ * @return {*}
+ */
+const updateSelectInfo = (gid: string, pf: Array<string>, startTime: string, endTime: string) => {
+  updateReqConfig(chartReqConfig, { pf, gid, startTime, endTime })
+  updateReqConfig(tableRequestConfig, { pf, gid, startTime, endTime })
 }
 
 /**
- * @description: 监听时间变化,去重新请求数据
+ * @description: 监听选择属性的变化
  * @return {*}
  */
 watch(
-  () => [props.startTime, props.endTime, eventId],
-  ([newStart, newEnd, newId]) => {
-    updateReqConfig(chartReqConfig, {
-      id: newId,
-      startTime: newStart,
-      endTime: newEnd
-    })
-    updateReqConfig(tableRequestConfig, {
-      id: newId,
-      startTime: newStart,
-      endTime: newEnd
-    })
-    // eventTable.value.getData()
+  () => [selectInfo.gid, tempMultipleChioce.pf, props.startTime, props.endTime],
+  ([gid, pf, startTime, endTime]) => {
+    updateSelectInfo(gid as string, pf as Array<string>, startTime as string, endTime as string)
   },
   {
-    deep: true
+    deep: true,
+    immediate: true
   }
 )
-initParams()
-onMounted(() => {})
 </script>
 <template>
   <div class="eventDetail">

+ 22 - 15
src/views/Home/Analysis/EventAnalysisTable.vue

@@ -1,30 +1,32 @@
 <script setup lang="ts">
-import Table from '@/components/Table.vue'
+import type { ReqConfig } from '@/types/dataAnalysis'
+import type { TablePaginationSetting, TableFieldInfo, QueryInfo } from '@/types/table'
 
+import { FilterType } from '@/types/table'
 import { reactive, ref } from 'vue'
 import { useRequest } from '@/hooks/useRequest'
 import { useCommonStore } from '@/stores/useCommon'
 import { resetTimeToMidnight } from '@/utils/common'
+import { useAnalysis } from '@/hooks/useAnalysis'
+import { usePage } from '@/hooks/usePage'
 
-import type { ReqConfig } from '@/types/dataAnalysis'
-import {
-  type TablePaginationSetting,
-  type TableFieldInfo,
-  type QueryInfo,
-  FilterType
-} from '@/types/table'
+import Table from '@/components/Table.vue'
 import router from '@/router'
 
-import { usePage } from '@/hooks/usePage'
+const { updateReqConfig } = useAnalysis()
+
 const { watchPageChange } = usePage()
 
 const { AllApi } = useRequest()
-const { selectInfo } = useCommonStore()
+const { selectInfo, tempMultipleChioce } = useCommonStore()
 
 const eventTable = ref<InstanceType<typeof Table>>()
 
+// 是否是单选的pf
+const isSinglePf = false
+
 // 主要为了给面包屑导航提供信息
-const emits = defineEmits(['enterDetail'])
+const emits = defineEmits(['enterAnalysisDetail'])
 
 interface eventTableProps {
   startTime: string
@@ -88,6 +90,7 @@ const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
 const requestConfig = reactive<ReqConfig>({
   url: AllApi.userActionList,
   otherOptions: {
+    pf: selectInfo.pf,
     gid: selectInfo.gid,
     startTime: props.startTime,
     endTime: props.endTime
@@ -109,7 +112,7 @@ const eventTableFilterInfo: Array<QueryInfo> = [
  * @return {*}
  */
 const viewDetails = (row: any) => {
-  emits('enterDetail', {
+  emits('enterAnalysisDetail', {
     name: row.actionName,
     pathName: 'EventAnalysisDetail'
   })
@@ -137,14 +140,16 @@ const updateDate = (startTime: string, endTime: string) => {
  * @param {*} newGid 新的gid
  * @return {*}
  */
-const updateGid = (newGid: string) => {
-  requestConfig.otherOptions.gid = newGid
+const updateGid = (gid: string, pf: any) => {
+  pf = isSinglePf ? pf[0] : pf
+  updateReqConfig(requestConfig, { pf, gid })
 }
 
 const backupDate = reactive([])
 const backupSelect = reactive([])
 
-watchPageChange(() => [selectInfo.gid], backupSelect, updateGid)
+// 这里特殊一点,监听pf的时候去监听临时的pf,但是gid还是和其他一样
+watchPageChange(() => [selectInfo.gid, tempMultipleChioce.pf], backupSelect, updateGid)
 
 watchPageChange(() => [props.startTime, props.endTime], backupDate, updateDate)
 </script>
@@ -161,6 +166,8 @@ watchPageChange(() => [props.startTime, props.endTime], backupDate, updateDate)
         :pagination-config="pagingConfig"
         :table-fields-info="tableFieldsInfo"
         :need-left-tools="false"
+        :need-right-tools="true"
+        :open-remoteinquiry="false"
       >
         <template #tableOperation>
           <el-table-column label="操作" align="center">

+ 12 - 7
src/views/Home/Analysis/EventAnalysisView.vue

@@ -2,18 +2,19 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-27 17:11:23
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-13 16:05:45
+ * @LastEditTime: 2024-10-15 11:59:09
  * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\EventAnalysisView.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
-import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+import type { HeaderCardProps } from '@/types/dataAnalysis'
+
 import { resetTimeToMidnight } from '@/utils/common'
 import { shouldListenToEvent } from '@/utils/table/table'
 import { reactive, ref } from 'vue'
 
-import type { HeaderCardProps } from '@/types/dataAnalysis'
+import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
 
 // 顶部ref
 const headerCard = ref()
@@ -58,24 +59,28 @@ const dateChange = (newDate: Array<Date>) => {
         :title="headerProps.title"
         :open-date-select="headerProps.openDateSelect"
         :need-breadcrumb="true"
-        :need-pf-select="false"
+        :need-pf-select="true"
+        :is-radio="false"
         @change-date="dateChange"
       ></HeaderCard>
     </div>
 
-    <div class="content">
+    <!-- 等时间改变后再去请求,不然会请求两次 -->
+    <div class="content" v-if="startTime && endTime">
       <!-- 监听表格的跳转事件 -->
       <router-view v-slot="{ Component, route }" :startTime="startTime" :endTime="endTime">
         <!-- 是eventtable组件就去监听enterdetail事件 -->
+        <!-- 这个keepalive不要去掉,因为里面的table的监听事件是放在onactive中 -->
         <keep-alive>
           <component
             :is="Component"
             v-if="shouldListenToEvent(route.name, 'EventAnalysisTable')"
-            @enterDetail="headerAddPath"
+            @enterAnalysisDetail="headerAddPath"
           />
         </keep-alive>
         <!-- 如果不是正常渲染其他组件 -->
-        <component v-if="route.name !== 'EventAnalysisTable'" :is="Component" />
+        <component v-if="route.name === 'EventAnalysisDetail'" :is="Component" />
+        <!-- <component v-else :is="Component" /> -->
       </router-view>
     </div>
   </div>

+ 17 - 62
src/views/Home/Analysis/KeepView.vue

@@ -2,25 +2,30 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-27 17:11:23
  * @LastEditors: fxs bjnsfxs@163.com
+<<<<<<< HEAD
  * @LastEditTime: 2024-09-18 15:01:25
+=======
+ * @LastEditTime: 2024-10-15 11:59:31
+>>>>>>> develop
  * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\KeepView.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
-import { reactive, ref, toRaw } from 'vue'
-import Table from '@/components/Table.vue'
-import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
-import axiosInstance from '@/utils/axios/axiosInstance'
 import type { TablePaginationSetting, TableFieldInfo } from '@/types/table'
-
 import type { ReqConfig, HeaderCardProps } from '@/types/dataAnalysis'
+
+import { reactive, ref, toRaw } from 'vue'
 import { resetTimeToMidnight } from '@/utils/common'
 import { useRequest } from '@/hooks/useRequest'
 import { useCommonStore } from '@/stores/useCommon'
 import { useAnalysis } from '@/hooks/useAnalysis'
-
 import { usePage } from '@/hooks/usePage'
+
+import Table from '@/components/Table.vue'
+import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+import axiosInstance from '@/utils/axios/axiosInstance'
+
 const { watchPageChange } = usePage()
 
 const { updateReqConfig } = useAnalysis()
@@ -28,10 +33,8 @@ const { selectInfo } = useCommonStore()
 
 const { AllApi, analysisResCode } = useRequest()
 
-// 选择的信息
-const keepViewSelect = reactive({
-  pf: selectInfo.pf[0]
-})
+// 是否是单选的pf
+const isSinglePf = true
 
 const loading = ref(true) // 加载状态
 
@@ -51,7 +54,7 @@ const keepDataTableInfo = reactive<{
   requestConfig: {
     url: AllApi.userRemainDataBydDay,
     otherOptions: {
-      pf: keepViewSelect.pf,
+      pf: selectInfo.pf,
       gid: selectInfo.gid,
       startTime: resetTimeToMidnight(new Date()),
       endTime: resetTimeToMidnight(new Date()),
@@ -131,15 +134,6 @@ const keepDataTableInfo = reactive<{
 const keepTableData = reactive<Array<any>>([])
 
 /**
- * @description: 选择的平台改变
- * @param {*} pf  选择的平台数组,暂时只用第一个
- * @return {*}
- */
-const changePf = (pf: Array<string>) => {
-  keepViewSelect.pf = pf[0]
-}
-
-/**
  * @description: 当日期改变,需要去更新三个组件的请求参数
  * @param {*} date
  * @return {*}
@@ -170,10 +164,7 @@ const getTableData = () => {
         .then(() => {
           let data = info.data
           let newList: Array<any> = []
-          for (const [key, val] of Object.entries(data)) {
-            // 为了编译通过,不做任何事
-            if (key) {
-            }
+          for (const [_key, val] of Object.entries(data)) {
             let nVal = val as Object
             newList.push({
               ...nVal
@@ -199,50 +190,15 @@ const getTableData = () => {
  * @return {*}
  */
 const updateAllReq = (pf: string, gid: string) => {
+  pf = isSinglePf ? pf[0] : pf
   updateReqConfig(keepDataTableInfo.requestConfig, { pf, gid })
 }
 
 const backupReq = reactive([])
 const backupSelect = reactive([])
 
-watchPageChange(() => [keepViewSelect.pf, selectInfo.gid], backupSelect, updateAllReq)
+watchPageChange(() => [selectInfo.pf, selectInfo.gid], backupSelect, updateAllReq)
 watchPageChange(() => [keepDataTableInfo.requestConfig], backupReq, getTableData)
-
-// onActivated(() => {
-//   /**
-//    * @description: 单独监听一下他的变化,去手动更新数据
-//    * @return {*}
-//    */
-//   watchReq = watch(
-//     () => keepDataTableInfo.requestConfig,
-//     () => {
-//       getTableData()
-//     },
-//     { deep: true, immediate: true }
-//   )
-
-//   /**
-//    * @description: 监听pf和gid的变化,数据变化后立即重新请求所有相关数据
-//    * @tip watch监听reactive的数据时,必须以getter形式,不然会警告
-//    * @return {*}
-//    */
-
-//   watchSelectInfo = watch(
-//     () => [keepViewSelect.pf, selectInfo.gid],
-//     ([newPf, newGid]) => {
-//       updateReqConfig(keepDataTableInfo.requestConfig, { pf: newPf, gid: newGid })
-//     },
-//     {
-//       deep: true,
-//       immediate: true
-//     }
-//   )
-// })
-
-// onDeactivated(() => {
-//   watchReq()
-//   watchSelectInfo()
-// })
 </script>
 <template>
   <div class="KeepViewBox">
@@ -250,7 +206,6 @@ watchPageChange(() => [keepDataTableInfo.requestConfig], backupReq, getTableData
       <HeaderCard
         :title="headerCardInfo.title"
         :open-date-select="headerCardInfo.openDateSelect"
-        @change-pf="changePf"
         @change-date="changeDate"
       ></HeaderCard>
     </div>

+ 15 - 101
src/views/Home/Analysis/UserTrendView.vue

@@ -1,18 +1,20 @@
 <script setup lang="ts">
-import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
-import Table from '@/components/Table.vue'
-import { reactive, ref, toRaw } from 'vue'
 import type { StaticField, ReqConfig, TemporalTrendInfo } from '@/types/dataAnalysis'
 import type { TablePaginationSetting, TableFieldInfo } from '@/types/table'
 
-import StatisticText from '@/components/dataAnalysis/StatisticText.vue'
-import TemporalTrend from '@/components/dataAnalysis/TemporalTrend.vue'
+import { reactive, ref, toRaw } from 'vue'
 import { useCommonStore } from '@/stores/useCommon'
 import { useRequest } from '@/hooks/useRequest'
 import { useAnalysis } from '@/hooks/useAnalysis'
 import { resetTimeToMidnight } from '@/utils/common'
-import axiosInstance from '@/utils/axios/axiosInstance'
 import { usePage } from '@/hooks/usePage'
+
+import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+import Table from '@/components/Table.vue'
+import StatisticText from '@/components/dataAnalysis/StatisticText.vue'
+import TemporalTrend from '@/components/dataAnalysis/TemporalTrend.vue'
+import axiosInstance from '@/utils/axios/axiosInstance'
+
 const { watchPageChange } = usePage()
 
 const { updateReqConfig } = useAnalysis()
@@ -22,11 +24,8 @@ const { selectInfo } = useCommonStore()
 // 总览数据的ref
 const userTrendStaticRef = ref()
 
-// 目前选择的信息
-// 这里不太合理,应该根据返回的数据中的pf和game赋值,因为这个可能会变
-const userTrendSelectInfo = reactive({
-  pf: 'wx'
-})
+// 是否是单选的pf
+const isSinglePf = true
 
 // 总览数据的字段对应的信息
 const userTrendStaticFieldInfo: Array<StaticField> = [
@@ -61,7 +60,7 @@ const userTrendStaticFieldInfo: Array<StaticField> = [
 const userTrendDataReqConfig = reactive<ReqConfig>({
   url: AllApi.userTrendsOverview,
   otherOptions: {
-    pf: userTrendSelectInfo.pf,
+    pf: selectInfo.pf[0],
     gid: selectInfo.gid,
     startTime: resetTimeToMidnight(new Date()),
     endTime: resetTimeToMidnight(new Date())
@@ -73,7 +72,7 @@ const dataTrendInfo = reactive<TemporalTrendInfo>({
   dataReqConfig: {
     url: AllApi.userDataTrades,
     otherOptions: {
-      pf: userTrendSelectInfo.pf,
+      pf: selectInfo.pf[0],
       gid: selectInfo.gid,
       startTime: resetTimeToMidnight(new Date()),
       endTime: resetTimeToMidnight(new Date())
@@ -213,7 +212,7 @@ const detailDataTableInfo = reactive<{
   requestConfig: {
     url: AllApi.userDataTradesDetail,
     otherOptions: {
-      pf: userTrendSelectInfo.pf,
+      pf: selectInfo.pf[0],
       gid: selectInfo.gid,
       startTime: resetTimeToMidnight(new Date()),
       endTime: resetTimeToMidnight(new Date())
@@ -269,15 +268,6 @@ const detailDataTableInfo = reactive<{
 const detailDataTableData = reactive<Array<any>>([])
 
 /**
- * @description: 选择的平台改变
- * @param {*} pf  选择的平台数组,暂时只用第一个
- * @return {*}
- */
-const changePf = (pf: Array<string>) => {
-  userTrendSelectInfo.pf = pf[0]
-}
-
-/**
  * @description: 当日期改变,需要去更新三个组件的请求参数
  * @param {*} date
  * @return {*}
@@ -332,6 +322,7 @@ const getDetailData = () => {
  * @return {*}
  */
 const updateAllReq = (pf: string, gid: string) => {
+  pf = isSinglePf ? pf[0] : pf
   updateReqConfig(userTrendDataReqConfig, { pf, gid })
   updateReqConfig(dataTrendInfo.dataReqConfig, { pf, gid })
   updateReqConfig(detailDataTableInfo.requestConfig, { pf, gid })
@@ -346,91 +337,14 @@ const backupReq = reactive([]) // 保存请求参数
 
 const backupSelect = reactive([]) // 保存选择数据
 
-watchPageChange(() => [userTrendSelectInfo.pf, selectInfo.gid], backupSelect, updateAllReq)
+watchPageChange(() => [selectInfo.pf, selectInfo.gid], backupSelect, updateAllReq)
 watchPageChange(() => [detailDataTableInfo.requestConfig], backupReq, getDetailData)
-
-// onActivated(() => {
-//   /**
-//    * @description: 监听pf和gid的变化,数据变化后立即重新请求所有相关数据
-//    * @tip watch监听reactive的数据时,必须以getter形式,不然会警告
-//    * @return {*}
-//    */
-
-//   console.log('执行')
-//   if (!compareWatchData(backupSelect, { pf: userTrendSelectInfo.pf, gid: selectInfo.gid })) {
-//     console.log('compare')
-
-//     updateAllReq(userTrendSelectInfo.pf, selectInfo.gid)
-//   }
-
-//   if (!watchSelect) {
-//     watchSelect = watch(
-//       () => [userTrendSelectInfo.pf, selectInfo.gid],
-//       ([newPf, newGid]) => {
-//         console.log('change')
-//         updateAllReq(newPf, newGid)
-//       },
-//       { deep: true }
-//     )
-//   }
-
-//   if (!compareWatchData(backupReq, detailDataTableInfo.requestConfig)) {
-//     console.log('compare1')
-//     getDetailData()
-//   }
-
-//   if (!watchReq) {
-//     watchReq = watch(
-//       () => detailDataTableInfo.requestConfig,
-//       () => {
-//         if (compareWatchData(backupReq, detailDataTableInfo.requestConfig)) {
-//           console.log('change1')
-//           getDetailData()
-//         }
-//       },
-//       { deep: true }
-//     )
-//   }
-// })
-
-// onDeactivated(() => {
-//   if (watchReq) {
-//     saveWatchData(detailDataTableInfo.requestConfig, backupReq)
-//     watchReq()
-//     watchReq = null
-//   }
-
-//   if (watchSelect) {
-//     saveWatchData(
-//       {
-//         pf: userTrendSelectInfo.pf,
-//         gid: selectInfo.gid
-//       },
-//       backupSelect
-//     )
-//     watchSelect()
-//     watchSelect = null
-//   }
-// })
-
-// watch(
-//   () => test.name,
-//   (newdata) => {
-//     console.log('本地')
-//     console.log(newdata)
-//   },
-//   {
-//     deep: true
-//   }
-// )
 </script>
 <template>
   <div class="userTrendBox">
     <div class="header">
       <HeaderCard
         :open-date-select="true"
-        :default-pf="userTrendSelectInfo.pf"
-        @change-pf="changePf"
         @change-date="changeDate"
         :title="'数据总览'"
       ></HeaderCard>

+ 46 - 10
src/views/Home/InfoManage/GameManageView.vue

@@ -1,16 +1,19 @@
 <script setup lang="ts">
-import Dialog from '@/components/common/Dialog.vue'
-import Table from '@/components/Table.vue'
-
-import { type TablePaginationSetting, type TableFieldInfo } from '@/types/table'
+import type { TablePaginationSetting, TableFieldInfo, QueryInfo } 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 { onMounted, reactive, ref } from 'vue'
+import { FormFieldType } from '@/types/form'
+import { FilterType } from '@/types/table'
+import { onMounted, reactive, ref, watch } from 'vue'
 import { useRequest } from '@/hooks/useRequest'
+import { useCommonStore } from '@/stores/useCommon'
 
+import Dialog from '@/components/common/Dialog.vue'
+import Table from '@/components/Table.vue'
+
+const { allGameInfo, selectInfo } = useCommonStore()
 const { AllApi } = useRequest()
 
 const gameTableRef = ref<InstanceType<typeof Table>>()
@@ -72,6 +75,16 @@ const filedsInfo = reactive<Array<TableFieldInfo>>([
   }
 ])
 
+// 查询字段设置
+const queryInfo: Array<QueryInfo> = [
+  {
+    name: 'gameName',
+    label: '游戏名',
+    type: FilterType.INPUT,
+    placeholder: '请输入游戏名进行搜索'
+  }
+]
+
 // 表单校验规则
 const gameFormRule = reactive({
   gameName: '',
@@ -224,15 +237,37 @@ const handleEdit = (row: any) => {
   gameDialogRef.value.editForm(row)
 }
 
-const formSub = () => {
+const formSub = (formData: any, type: number) => {
+  console.log(type)
+  if (type === 0) {
+    allGameInfo.push({
+      gid: formData.gid,
+      gameName: formData.gameName
+    })
+  } else {
+    let game = allGameInfo.find((item) => item.gid === formData.gid)
+    if (game) {
+      console.log(formData.gameName)
+      game.gameName = formData.gameName
+    }
+  }
+
   gameTableRef.value?.getData()
 }
 
+watch(
+  () => selectInfo.gid,
+  () => {
+    gameTableRef.value?.resetQueryForm(false)
+  },
+  {
+    deep: true
+  }
+)
+
 onMounted(() => {
   gameTableRef.value?.getData()
 })
-
-// gameTableRef.value?.getData()
 </script>
 <template>
   <div class="gameMangeBox">
@@ -242,8 +277,9 @@ onMounted(() => {
       :need-average="false"
       :need-right-tools="true"
       :need-left-tools="true"
-      :open-filter-query="false"
+      :open-filter-query="true"
       :open-page-query="false"
+      :query-info="queryInfo"
       :table-fields-info="filedsInfo"
       :request-config="requestConfig"
       :pagination-config="paginationConfig"

+ 34 - 109
src/views/Home/InfoManage/PlayerManageView.vue

@@ -1,35 +1,29 @@
 <script setup lang="ts">
-import {
-  type TablePaginationSetting,
-  type QueryInfo,
-  FilterType,
-  type SelectInfo,
-  type TableFieldInfo,
-  FieldSpecialEffectType,
-  ColorType
-} from '@/types/table'
-
-import Dialog from '@/components/common/Dialog.vue'
-import Table from '@/components/Table.vue'
-
-import axiosInstance from '@/utils/axios/axiosInstance'
-
-import { onMounted, reactive, ref } from 'vue'
-import { ElMessageBox } from 'element-plus'
-
+import type { TablePaginationSetting, QueryInfo, SelectInfo, TableFieldInfo } from '@/types/table'
 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 { FilterType, FieldSpecialEffectType, ColorType } from '@/types/table'
+import { reactive, ref } from 'vue'
+import { ElMessageBox } from 'element-plus'
+import { FormFieldType } from '@/types/form'
 import { useRequest } from '@/hooks/useRequest'
 import { useCommonStore } from '@/stores/useCommon'
+import { useAnalysis } from '@/hooks/useAnalysis'
+
+import Dialog from '@/components/common/Dialog.vue'
+import Table from '@/components/Table.vue'
+import axiosInstance from '@/utils/axios/axiosInstance'
+
+const { updateReqConfig } = useAnalysis()
 
 const { AllApi, analysisResCode } = useRequest()
 
-const tableStore = useTableStore()
-const commonStore = useCommonStore()
+const { selectInfo } = useCommonStore()
+
+// 是否是单选pf
+// const isSinglePf = true
 
 // 表格对象
 const playerTableRef = ref()
@@ -51,8 +45,8 @@ const requestConfig = reactive({
   otherOptions: {
     offset: 0,
     limit: paginationConfig.limit,
-    gid: commonStore.selectInfo.gid,
-    pf: tableStore.playerQueryInfo.pf
+    gid: selectInfo.gid,
+    pf: selectInfo.pf[0]
   }
 })
 
@@ -76,19 +70,19 @@ const allPf: Array<SelectInfo> = [
 ]
 
 // 所有游戏信息
-const allGameInfo = reactive<Array<SelectInfo>>([])
+// const allGameInfo = reactive<Array<SelectInfo>>([])
 
 // 查询字段设置
-const queryInfo: Array<QueryInfo> = [
+const queryInfo = reactive<Array<QueryInfo>>([
   {
     name: 'pf',
     label: '平台',
     type: FilterType.SELECT,
     placeholder: '请选择平台',
     otherOption: allPf,
-    default: tableStore.playerQueryInfo.pf
+    default: selectInfo.pf[0]
   }
-]
+])
 
 // 字段信息
 const filedsInfo = reactive<Array<TableFieldInfo>>([
@@ -170,51 +164,6 @@ const filedsInfo = reactive<Array<TableFieldInfo>>([
   }
 ])
 
-// // 游戏配置对话框设置
-// 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({
   option: ''
@@ -288,6 +237,7 @@ const blockedPlayer = (row: any) => {
     })
     .catch(() => {
       // 在点击取消时他会抛出错误,这里需要去捕获一下,不然会在控制台出现
+      console.log('取消选择')
     })
 }
 
@@ -299,30 +249,9 @@ const encrypt = () => {
   playerDialogFormRef.value.encrypt('option', true, ['gid', 'userId', 'pf'])
 }
 
-// let watchGid: any = null
-
-// onActivated(() => {
-//   /**
-//    * @description: 监听gid的变化
-//    * @return {*}
-//    */
-//   watchGid = watch(
-//     () => commonStore.selectInfo.gid,
-//     (val) => {
-//       requestConfig.otherOptions.gid = val
-//     },
-//     {
-//       immediate: true
-//     }
-//   )
-// })
-
-// onDeactivated(() => {
-//   watchGid()
-// })
-
-const updateGid = (gid: any) => {
-  requestConfig.otherOptions.gid = gid
+// 表格的查询应该和全局的平台选择独立开来
+const updateSelect = (gid: any) => {
+  updateReqConfig(requestConfig, { gid })
 }
 
 const backupSelect = reactive([])
@@ -330,17 +259,7 @@ const backupSelect = reactive([])
 import { usePage } from '@/hooks/usePage'
 const { watchPageChange } = usePage()
 
-watchPageChange(() => [commonStore.selectInfo.gid], backupSelect, updateGid)
-
-onMounted(() => {
-  tableStore.allGameInfo.map((item) => {
-    allGameInfo.push({
-      name: item.gameName,
-      value: item.gid,
-      cnName: item.gameName
-    })
-  })
-})
+watchPageChange(() => [selectInfo.gid], backupSelect, updateSelect)
 </script>
 <template>
   <div class="gameMangeBox">
@@ -356,6 +275,7 @@ onMounted(() => {
       :query-info="queryInfo"
       :request-config="requestConfig"
       :pagination-config="paginationConfig"
+      :open-remoteinquiry="true"
     >
       <template #tableOperation>
         <el-table-column label="操作" align="center">
@@ -381,7 +301,12 @@ onMounted(() => {
       </template>
     </Table>
     <div class="optionDialog">
-      <Dialog @form-submit="formSub" ref="playerDialogFormRef" :config="optionDialogConfig">
+      <Dialog
+        :config-btn-text="'普通上传'"
+        @form-submit="formSub"
+        ref="playerDialogFormRef"
+        :config="optionDialogConfig"
+      >
         <template #otherBtn>
           <el-button class="operationBtn" type="warning" @click="encrypt"> 加密上传 </el-button>
         </template>

+ 16 - 28
src/views/Home/Overview/OverView.vue

@@ -1,17 +1,17 @@
 <script setup lang="ts">
-import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
-import { reactive, ref, toRaw } from 'vue'
 import type { StaticField, ReqConfig, TemporalTrendInfo } from '@/types/dataAnalysis'
 
-import StatisticText from '@/components/dataAnalysis/StatisticText.vue'
-import TemporalTrend from '@/components/dataAnalysis/TemporalTrend.vue'
+import { reactive, ref, toRaw } from 'vue'
 import { useCommonStore } from '@/stores/useCommon'
 import { useRequest } from '@/hooks/useRequest'
 import { useAnalysis } from '@/hooks/useAnalysis'
-
 import { usePage } from '@/hooks/usePage'
-const { watchPageChange } = usePage()
 
+import StatisticText from '@/components/dataAnalysis/StatisticText.vue'
+import TemporalTrend from '@/components/dataAnalysis/TemporalTrend.vue'
+import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+
+const { watchPageChange } = usePage()
 const { updateReqConfig } = useAnalysis()
 const { AllApi } = useRequest()
 const { selectInfo } = useCommonStore()
@@ -19,11 +19,8 @@ const { selectInfo } = useCommonStore()
 // 总览数据的ref
 const overviewStaticRef = ref()
 
-// 目前选择的信息
-// 这里不太合理,应该根据返回的数据中的pf和game赋值,因为这个可能会变
-const overViewSelectInfo = reactive({
-  pf: selectInfo.pf[0]
-})
+// 是否是单个pf选择
+const isSinglePf = true
 
 // 总览数据的字段对应的信息
 const overViewStaticFieldInfo: Array<StaticField> = [
@@ -53,7 +50,7 @@ const overViewStaticFieldInfo: Array<StaticField> = [
 const overViewDataReqConfig = reactive<ReqConfig>({
   url: AllApi.userSummary,
   otherOptions: {
-    pf: overViewSelectInfo.pf,
+    pf: selectInfo.pf[0],
     gid: selectInfo.gid
   }
 })
@@ -64,8 +61,8 @@ const periodInfo = reactive<TemporalTrendInfo>({
   dataReqConfig: {
     url: AllApi.timeDistributionData,
     otherOptions: {
-      pf: '',
-      gid: ''
+      pf: selectInfo.pf[0],
+      gid: selectInfo.gid
     }
   },
   tabList: toRaw([
@@ -166,8 +163,8 @@ const monthInfo = reactive<TemporalTrendInfo>({
   dataReqConfig: {
     url: AllApi.userMouthDistributionData,
     otherOptions: {
-      pf: '',
-      gid: ''
+      pf: selectInfo.pf[0],
+      gid: selectInfo.gid
     }
   },
   tabList: toRaw([
@@ -267,21 +264,13 @@ const monthInfo = reactive<TemporalTrendInfo>({
 })
 
 /**
- * @description: 选择的平台改变
- * @param {*} pf  选择的平台数组,暂时只用第一个
- * @return {*}
- */
-const changePf = (pf: Array<string>) => {
-  overViewSelectInfo.pf = pf[0]
-}
-
-/**
  * @description: 更新所有的请求接口
  * @param {*} pf 平台
  * @param {*} gid 游戏id
  * @return {*}
  */
 const updateAllReq = (pf: string, gid: string) => {
+  pf = isSinglePf ? pf[0] : pf
   updateReqConfig(overViewDataReqConfig, { pf, gid })
   updateReqConfig(periodInfo.dataReqConfig, { pf, gid })
   updateReqConfig(monthInfo.dataReqConfig, { pf, gid })
@@ -293,14 +282,13 @@ const updateAllReq = (pf: string, gid: string) => {
  * @tip watch监听reactive的数据时,必须以getter形式,不然会警告
  * @return {*}
  */
-
 const backupSelect = reactive([])
-watchPageChange(() => [overViewSelectInfo.pf, selectInfo.gid], backupSelect, updateAllReq)
+watchPageChange(() => [selectInfo.pf, selectInfo.gid], backupSelect, updateAllReq)
 </script>
 <template>
   <div class="overViewBox">
     <div class="header">
-      <HeaderCard :open-date-select="false" @change-pf="changePf" :title="'数据总览'"></HeaderCard>
+      <HeaderCard :open-date-select="false" :title="'数据总览'"></HeaderCard>
     </div>
     <div class="staticBox">
       <StatisticText

+ 83 - 22
src/views/Index.vue

@@ -2,26 +2,32 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:06:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-18 11:56:31
- * @FilePath: \Game-Backstage-Management-System\src\views\Index.vue
+ * @LastEditTime: 2024-10-21 11:57:26
+ * @FilePath: \Quantity-Creation-Management-Systemc:\Users\NINGMEI\Desktop\Game-Backstage-Management-System\src\views\Index.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
+import type { DropDownInfo } from '@/types/dataAnalysis'
+
 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'
+import { setLoginState } from '@/utils/localStorage/localStorage'
+import { removeAllToeken } from '@/utils/token/token'
+
+import router from '@/router'
+import DropDownSelection from '@/components/dataAnalysis/DropDownSelection.vue'
 
 const route = useRoute()
-const { selectInfo } = useCommonStore()
+const { selectInfo, allGameInfo, saveSelectInfo } = useCommonStore()
+
 const isCollapse = ref(false)
 const navBarSelect = ref<string>('Home')
 const siderBarOpened = ref<Array<string>>(['数据总览'])
@@ -64,8 +70,8 @@ const logOut = () => {
     message: '退出成功',
     duration: 1000
   })
-  localStorage.removeItem('token')
-  localStorage.removeItem('refreshToken')
+  setLoginState(false)
+  removeAllToeken()
   router.push('/login')
 }
 
@@ -83,6 +89,7 @@ const gameSelectInfo = reactive<DropDownInfo>({
  */
 const changeGame = (gid: any) => {
   selectInfo.gid = gid
+  saveSelectInfo()
 }
 
 /**
@@ -152,22 +159,76 @@ watch(
  * @description: 获取所有游戏列表
  * @return {*}
  */
-getAllGameInfo().then((data) => {
-  if (data) {
-    data.map((item) => {
-      gameSelectInfo.optionsList.push({
-        value: item.gid,
-        label: item.gameName
-      })
+const getGameInfo = () => {
+  getAllGameInfo()
+    .then((data) => {
+      if (data) {
+        allGameInfo.splice(0, allGameInfo.length)
+        gameSelectInfo.optionsList.splice(0, gameSelectInfo.optionsList.length)
+        data.map((item) => {
+          allGameInfo.push({
+            gid: item.gid,
+            gameName: item.gameName
+          })
+          gameSelectInfo.optionsList.push({
+            value: item.gid,
+            label: item.gameName
+          })
+        })
+        gameSelectInfo.defaultSelect = data[0].gid
+        // 去找本地的gid,如果有,就赋值,否则用请求回来的第一个gid
+
+        changeGame(selectInfo.gid)
+        gameSelectInfo.defaultSelect = selectInfo.gid
+
+        loadingState.value = true
+      } else {
+        throw new Error('游戏信息获取失败')
+      }
+    })
+    .catch((err) => {
+      console.log(err)
     })
-    gameSelectInfo.defaultSelect = data[0].gid
-    changeGame(data[0].gid)
-    loadingState.value = true
-  } else {
-    throw new Error('游戏信息获取失败')
+}
+
+/**
+ * @description: 监听游戏列表的变化
+ * @return {*}
+ */
+let watchGameListChange: () => void = () => {}
+
+/**
+ * @description: 监听加载状态的变化,加载完成的时候,给游戏列表的监听器赋值,然后把自己这个监听器摧毁
+ * @return {*}
+ */
+const watchLoadingState = watch(
+  () => loadingState,
+  (newval) => {
+    if (newval) {
+      watchGameListChange = watch(
+        () => allGameInfo,
+        (newGameInfo: Array<any>) => {
+          gameSelectInfo.optionsList.splice(0, gameSelectInfo.optionsList.length)
+          newGameInfo.forEach((item) => {
+            gameSelectInfo.optionsList.push({
+              value: item.gid,
+              label: item.gameName
+            })
+          })
+        },
+        { deep: true }
+      )
+      watchLoadingState()
+    } else {
+      watchGameListChange()
+    }
+  },
+  {
+    deep: true
   }
-})
+)
 
+getGameInfo()
 onMounted(() => {
   // 去加载所有需要的资源
   initLoadResouce(resourceInfo).then((data) => {
@@ -346,7 +407,7 @@ onMounted(() => {
   margin-right: 40px;
 }
 
-.sideBarFold {
+.sideBarFold {  
   width: 5%;
   height: 3%;
   position: absolute;

+ 45 - 29
src/views/Login/LoginView.vue

@@ -1,12 +1,16 @@
 <script setup lang="ts">
-import { onMounted, reactive } from 'vue'
 import type { RuleInfo } from '@/types/input'
+
+import { onMounted, reactive, ref } from 'vue'
 import { useRequest } from '@/hooks/useRequest'
+import { initLoadResouce } from '@/utils/resource'
+import { setToken, setRefreshToken } from '@/utils/token/token'
+import { setLoginState } from '@/utils/localStorage/localStorage'
+
 import router from '@/router'
 import axiosInstance from '@/utils/axios/axiosInstance'
 import MyButton from '@/components/form/MyButton.vue'
 import MyInput from '@/components/form/MyInput.vue'
-import { initLoadResouce } from '@/utils/resource'
 
 const { AllApi, analysisResCode } = useRequest()
 
@@ -20,32 +24,8 @@ const loginInfo = reactive<LoginInfoType>({
   password: ''
 })
 
-const userLogin = () => {
-  let vaild = Object.keys(loginInfo).every((key) => {
-    return formFieldsRules[key].rules.every((item) => item.validator())
-  })
-  if (vaild) {
-    axiosInstance
-      .post(AllApi.userLogin, loginInfo)
-      .then((data) => {
-        let result = JSON.parse(JSON.stringify(data))
-        analysisResCode(result, 'login')
-          .then(() => {
-            localStorage.setItem('token', result.token)
-            localStorage.setItem('refreshToken', result.refreshToken)
-            router.push('/')
-          })
-          .catch((err) => {
-            console.log(err)
-          })
-      })
-      .catch((err) => {
-        console.log(err)
-      })
-  } else {
-    console.log('不通过')
-  }
-}
+// 判断目前是否在登陆
+const isLogining = ref(false)
 
 const formFieldsRules = reactive<{
   [key: string]: RuleInfo
@@ -96,8 +76,44 @@ const resourceInfo: Record<string, string> = {
 // 使用blob的资源路径数组
 const blobUrlInfo = reactive<Record<string, string>>({})
 
+/**
+ * @description: 验证登录信息是否符合表单规则
+ * @param {*} loginInfo 登录信息
+ * @return {*}
+ */
+const validLoginInfo = (loginInfo: LoginInfoType): boolean => {
+  return Object.keys(loginInfo).every((key) =>
+    formFieldsRules[key].rules.every((item) => item.validator())
+  )
+}
+
+/**
+ * @description: 用户登录
+ * @return {*}
+ */
+const userLogin = async () => {
+  if (isLogining.value) return
+  isLogining.value = true
+  let vaild = validLoginInfo(loginInfo)
+  if (vaild) {
+    try {
+      let data = await axiosInstance.post(AllApi.userLogin, loginInfo)
+      let result = JSON.parse(JSON.stringify(data))
+      await analysisResCode(result, 'login')
+      setToken(result.token)
+      setRefreshToken(result.refreshToken)
+      setLoginState(true)
+      axiosInstance.defaults.headers['Authorization'] = result.token // 需要在这里设置,不然又会触发拦截器
+      router.push('/')
+    } catch (err) {
+      console.log(err)
+    } finally {
+      isLogining.value = false
+    }
+  }
+}
+
 onMounted(() => {
-  console.log('tt')
   // 去加载所有需要的资源
   initLoadResouce(resourceInfo).then((data) => {
     Object.assign(blobUrlInfo, data)

+ 8 - 1
tsconfig.app.json

@@ -1,6 +1,13 @@
 {
   "extends": "@vue/tsconfig/tsconfig.dom.json",
-  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "auto-imports.d.ts"],
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.d.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue",
+    "auto-imports.d.ts",
+    "config/**/*.ts"
+  ],
   "exclude": ["src/**/__tests__/*"],
   "compilerOptions": {
     "composite": true,