Bläddra i källkod

更新事件的上传和下载功能;更新事件分析页面接口;更新趋势图组件props;更新保活逻辑,现在只有当切换游戏的时候,部分页面才会重新请求;修复图片重复加载的bug

fxs 8 månader sedan
förälder
incheckning
156c6213b9

+ 3 - 1
components.d.ts

@@ -15,6 +15,7 @@ declare module 'vue' {
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDivider: typeof import('element-plus/es')['ElDivider']
+    ElDrawer: typeof import('element-plus/es')['ElDrawer']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
@@ -36,9 +37,10 @@ declare module 'vue' {
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']
-    ElTex: typeof import('element-plus/es')['ElTex']
     ElText: typeof import('element-plus/es')['ElText']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
+    FileUpload: typeof import('./src/components/form/FileUpload.vue')['default']
     FilterPopover: typeof import('./src/components/toolsBtn/FilterPopover.vue')['default']
     Form: typeof import('./src/components/form/Form.vue')['default']
     HeaderCard: typeof import('./src/components/dataAnalysis/HeaderCard.vue')['default']

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
   "private": true,
   "type": "module",
   "scripts": {
-    "dev": "vite",
+    "dev": "vite --host 0.0.0.0",
     "build": "run-p type-check \"build-only {@}\" --",
     "preview": "vite preview",
     "build-only": "vite build",

+ 66 - 10
src/components/Table.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 18:16:18
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 17:56:10
+ * @LastEditTime: 2024-09-11 17:05:33
  * @FilePath: \Game-Backstage-Management-System\src\components\Table.vue
  * @Description: 
  * 
@@ -13,7 +13,7 @@ import type { ReqConfig } from '@/types/dataAnalysis'
 import { FilterType, FieldSpecialEffectType } from '@/types/table'
 import { initLoadResouce } from '@/utils/resource'
 
-import { computed, onMounted, reactive, ref, watch } from 'vue'
+import { computed, onMounted, reactive, ref, toRaw, watch } from 'vue'
 import { useTable } from '@/hooks/useTable'
 
 import FilterPopover from './toolsBtn/FilterPopover.vue'
@@ -39,11 +39,13 @@ const props = withDefaults(defineProps<PropsParams>(), {
   needLeftTools: false,
   needRightTools: false,
   openFilterQuery: false,
-  openPageQuery: false
+  openPageQuery: false,
+  needUpload: false,
+  needDownLoad: false
 })
 
 // 父组件触发的方法
-const emits = defineEmits(['addNewItem'])
+const emits = defineEmits(['addNewItem', 'upload', 'downLoad', 'loadSuccess'])
 
 // 加载动画
 const loading = ref(false)
@@ -131,6 +133,7 @@ const getData = () => {
       tableData.splice(0, tableData.length, ...props.dataList)
       paginationConfig2.total = props.paginationConfig.total
       loading.value = false
+      emits('loadSuccess', tableData)
       resolve(true)
     } else {
       loading.value = true
@@ -146,6 +149,7 @@ const getData = () => {
         // 查询时要根据是否开启分页查询传入对应参数
         getTableData(reqconfig.url, reqconfig.otherOptions, props.openPageQuery)
           .then(() => {
+            emits('loadSuccess', tableData)
             resolve(true)
           })
           .catch((err) => {
@@ -303,6 +307,15 @@ const changeDataList = watch(
   }
 )
 
+// 监听日期的变化,
+const watchDateChange = watch(
+  () => [props.requestConfig?.otherOptions.startTime, props.requestConfig?.otherOptions.endTime],
+  () => {
+    getData()
+  },
+  { deep: true }
+)
+
 /**
  * @description: 创建row-key优化表格性能
  * @return {*}
@@ -311,6 +324,11 @@ const createRowKey = () => {
   return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
 }
 
+//如果没有日期就取消掉
+if (!props.requestConfig?.otherOptions.startTime && !props.requestConfig?.otherOptions.endTime) {
+  watchDateChange()
+}
+
 // 没传入datalist则取消该监听
 if (!props.dataList) {
   changeDataList()
@@ -370,11 +388,29 @@ const deleteRow = (url: string, filedsInfo: any) => {
     })
 }
 
+/**
+ * @description: 下载表格数据
+ * @return {*}
+ */
+const downLoadTable = () => {
+  emits('downLoad', JSON.parse(JSON.stringify(tableData)))
+}
+
+/**
+ * @description: 外部获取数据
+ * @return {*}
+ */
+const outGetTableData = () => {
+  return toRaw(tableData).flat()
+}
+
 // 定义暴露出去的方法
 defineExpose({
   getData,
   resetTableData,
-  deleteRow
+  deleteRow,
+  downLoadTable,
+  outGetTableData
 })
 
 onMounted(() => {
@@ -453,12 +489,28 @@ onMounted(() => {
     <!-- <el-divider class="partition" content-position="center" /> -->
     <div class="tableTools">
       <div class="leftTools">
-        <el-button v-if="needLeftTools" type="primary" color="#165dff" @click="emits('addNewItem')">
-          <el-icon><Plus /></el-icon>新增
-        </el-button>
+        <div class="leftToolsGroup" v-if="needLeftTools" style="display: flex">
+          <el-button class="leftToolBtn" color="#165dff" @click="emits('addNewItem')">
+            <el-icon><Plus /></el-icon>新增
+          </el-button>
+          <el-button
+            class="leftToolBtn"
+            color="#626aef"
+            @click="emits('upload', outGetTableData())"
+            v-if="needUpload"
+          >
+            <el-icon><Upload /></el-icon>上传
+          </el-button>
+        </div>
       </div>
       <div class="rightTools" v-if="needRightTools">
-        <el-button color="#f0f1f3" size="default" class="rightToolsItem">
+        <el-button
+          v-if="needDownload"
+          color="#f0f1f3"
+          size="default"
+          class="rightToolsItem"
+          @click="downLoadTable"
+        >
           <el-icon><Download /></el-icon>下载
         </el-button>
 
@@ -749,7 +801,7 @@ onMounted(() => {
 }
 
 .rightTools {
-  width: 10%;
+  width: 5%;
 }
 
 .tableBox {
@@ -779,4 +831,8 @@ onMounted(() => {
   color: #515b6f;
   font-weight: 400;
 }
+
+.leftToolBtn {
+  margin-right: 5px;
+}
 </style>

+ 8 - 1
src/components/common/Dialog.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-04 11:21:05
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 12:09:08
+ * @LastEditTime: 2024-09-09 18:02:59
  * @FilePath: \Game-Backstage-Management-System\src\components\common\Dialog.vue
  * @Description: 
  * 
@@ -82,6 +82,13 @@ onMounted(() => {
   addUrl.value = props.config.reqConfig.url // 保存一下新增的URL
 })
 
+/**
+ * @description: 对字段进行加密
+ * @param {*} fields  字段名
+ * @param {*} useFormField  是否对表单的字段加密
+ * @param {*} encryptMsg  加密的消息
+ * @return {*}
+ */
 const encrypt = (fields: string, useFormField: boolean, encryptMsg: Array<string>) => {
   dialogFormRef.value?.encryptData(fields, useFormField, encryptMsg).finally(() => {
     dialogConfig.dialogVisible = false

+ 6 - 11
src/components/common/WithIconSelect.vue

@@ -2,17 +2,11 @@
 import { onMounted, ref, reactive, computed } from 'vue'
 import { initLoadResouce } from '@/utils/resource'
 import type { DropdownInstance } from 'element-plus'
-
-interface DropdownItem {
-  value: string
-  icon: string
-  label: string
-  isSelected: boolean
-}
+import type { IconDropdownItem } from '@/types/dataAnalysis'
 
 interface DropdownInfo {
   isRadio?: boolean
-  slectInfo: Array<DropdownItem>
+  slectInfo: Array<IconDropdownItem>
 }
 
 /**
@@ -45,7 +39,7 @@ const resourceInfo: Record<string, string> = props.slectInfo.reduce(
 const blobUrlInfo = reactive<Record<string, string>>({})
 
 // 备份信息
-const backupInfo = reactive<Array<DropdownItem>>([])
+const backupInfo = reactive<Array<IconDropdownItem>>([])
 
 /**
  * @description: 确认选择
@@ -103,6 +97,7 @@ const selectPf = (item: any) => {
 
 onMounted(() => {
   // 去加载所有需要的资源
+
   initLoadResouce(resourceInfo).then((data) => {
     Object.assign(blobUrlInfo, data)
   })
@@ -123,7 +118,7 @@ onMounted(() => {
             <el-image
               v-if="item.isSelected"
               style="width: 20px; height: 20px; margin-right: 5px"
-              :src="item.icon"
+              :src="blobUrlInfo[item.value]"
               :fit="'cover'"
             />
           </span>
@@ -144,7 +139,7 @@ onMounted(() => {
           >
             <el-image
               style="width: 20px; height: 20px; margin-right: 5px"
-              :src="item.icon"
+              :src="blobUrlInfo[item.value]"
               :fit="'cover'"
             />
             <!-- 禁用掉原生的点击事件,自己实现点击 -->

+ 5 - 73
src/components/dataAnalysis/HeaderCard.vue

@@ -8,61 +8,19 @@
 
 <script setup lang="ts">
 import router from '@/router'
-import DropDownSelection from './DropDownSelection.vue'
-import type { DropDownInfo, HeaderCardProps } from '@/types/dataAnalysis'
+import type { HeaderCardProps, IconDropdownItem } from '@/types/dataAnalysis'
 import { computed, onMounted, reactive, ref, watch } from 'vue'
 import WithIconSelect from '@/components/common/WithIconSelect.vue'
 
 const props = withDefaults(defineProps<HeaderCardProps>(), {
   openDateSelect: false,
-  defaultPf: 'web',
+  defaultPf: 'wx',
   needPfSelect: true,
   needBreadcrumb: false
 })
 
 const emits = defineEmits(['changePf', 'changeDate'])
 
-// 平台下拉框信息
-const platFormOptionInfo: DropDownInfo = {
-  defaultSelect: props.defaultPf,
-  title: '请选择平台',
-  optionsList: [
-    {
-      value: 'web',
-      label: 'Web'
-    },
-    {
-      value: 'wx',
-      label: '微信'
-    },
-    {
-      value: 'tt',
-      label: '抖音'
-    }
-  ]
-}
-// {
-//   wx: '/img/platformIcon/wx.svg',
-//   tt: '/img/platformIcon/tt.svg',
-//   web: '/img/platformIcon/web.svg'
-// }
-// const dropDownInfo = {
-//   selectInfo: [
-//     {
-//       icon: '/img/platformIcon/wx.svg',
-//       value: 'wx',
-//       label: '微信',
-//       isSelected: true
-//     },
-//     {
-//       icon: '/img/platformIcon/tt.svg',
-//       value: 'tt',
-//       label: '抖音',
-//       isSelected: true
-//     }
-//   ]
-// }
-
 // 快速选择日期
 const shortcuts = [
   {
@@ -102,11 +60,6 @@ const dateChange = (val: any) => {
   emits('changeDate', val)
 }
 
-// 平台变化
-const changePf = (val: any) => {
-  emits('changePf', val)
-}
-
 // 控制日期范围
 /**
  * @description: 禁止选取今天之后的日期
@@ -185,25 +138,18 @@ onMounted(() => {
   dateChange(selectDate.value)
 })
 
-interface DropdownItem {
-  value: string
-  icon: string
-  label: string
-  isSelected: boolean
-}
-
-const selectInfo = reactive<Array<DropdownItem>>([
+const selectInfo = reactive<Array<IconDropdownItem>>([
   {
     value: 'web',
     icon: '/img/platformIcon/web.svg',
     label: '网页',
-    isSelected: true
+    isSelected: false
   },
   {
     value: 'wx',
     icon: '/img/platformIcon/wx.svg',
     label: '微信',
-    isSelected: false
+    isSelected: true
   },
   {
     value: 'tt',
@@ -216,14 +162,6 @@ const selectInfo = reactive<Array<DropdownItem>>([
 const changePlatForm = (val: Array<any>) => {
   emits('changePf', val)
 }
-
-watch(
-  () => selectInfo,
-  (newval) => {
-    console.log('new')
-    console.log(newval)
-  }
-)
 </script>
 
 <template>
@@ -251,12 +189,6 @@ watch(
       <el-divider direction="vertical" />
       <div class="selectItem">
         <WithIconSelect @change-pf="changePlatForm" :slect-info="selectInfo"></WithIconSelect>
-        <!-- <DropDownSelection
-          @changeSelect="changePf"
-          :defaultSelect="platFormOptionInfo.defaultSelect"
-          :title="platFormOptionInfo.title"
-          :optionsList="platFormOptionInfo.optionsList"
-        ></DropDownSelection> -->
       </div>
     </div>
     <div v-if="props.openDateSelect" class="datePicker">

+ 1 - 1
src/components/dataAnalysis/StatisticText.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-26 13:57:37
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-02 15:50:11
+ * @LastEditTime: 2024-09-11 15:00:44
  * @FilePath: \Game-Backstage-Management-System\src\components\dataAnalysis\StatisticText.vue
  * @Description: 用于展示统计数据,如总览页面上方的总览数据
  * 

+ 14 - 8
src/components/dataAnalysis/TemporalTrend.vue

@@ -15,9 +15,6 @@ import TimeLineChart from '../echarts/TimeLineChart.vue'
 import StatisticText from './StatisticText.vue'
 
 import axiosInstance from '@/utils/axios/axiosInstance'
-import { useRequest } from '@/hooks/useRequest'
-
-const { analysisResCode } = useRequest()
 
 interface CacheData {
   paginationConfig: TablePaginationSetting
@@ -32,7 +29,8 @@ const props = withDefaults(defineProps<TemporalTrendProps>(), {
   needChangeExpress: true,
   selectExpressForm: 1,
   needTable: true,
-  needCharts: true
+  needCharts: true,
+  needDownload: false
 })
 const activeTab = ref<string>('') // 激活的Tab
 const iconSize = ref(20) // 图标的尺寸
@@ -102,10 +100,13 @@ const changeSelectShape = (name: number) => {
  * @description: 生成表格字段信息,必须要调用,因为legend的字段也依赖这里
  * @return {*}
  */
+
 const createTableField = () => {
   //  生成表格的字段信息
+
   tableFieldsInfo.splice(0, tableFieldsInfo.length)
-  for (const [key, value] of Object.entries(props.tableFieldsInfo)) {
+
+  for (const [key, value] of Object.entries(props.tableFieldsInfo[activeTab.value])) {
     // 根据传入的信息,生成table需要的字段信息
     tableFieldsInfo.push({
       name: key,
@@ -125,8 +126,9 @@ const createTableData = (data: any) => {
   // 图表的x轴信息
 
   const xInfo = Object.keys(data[props.resDataFieldsInfo['xAxis']])
+
   const valInfo: any = {}
-  // 把所有的表格需要的数据假如valInfo,其中index字段需要单独拿出来用x轴的信息填充
+  // 把所有的表格需要的数据加入valInfo,其中index字段需要单独拿出来用x轴的信息填充
   props.resDataFieldsInfo['values'].map((key: string) => {
     valInfo[key] = Object.values(data[key])
   })
@@ -233,13 +235,16 @@ const isLegalData = (data: any): boolean => {
 const getData = async (type: number) => {
   loadDataState.state = false
   loadDataState.loading = true
+
   axiosInstance
     .post(requestInfo.url, { ...requestInfo.otherOptions, type })
     .then((info) => {
       let data = info.data
       loadDataState.state = true
 
+      // 判断返回的数据是否合法,不合法直接全置为空
       if (!isLegalData(data)) {
+        console.log('不合法')
         paginationConfig = reactive({ ...initPageConfig })
 
         chartInfo = reactive<OptionsProps>({ ...initChartInfo })
@@ -309,6 +314,7 @@ Object.assign(requestInfo, props.requestConfig)
  * @description: 监听请求参数的变化,有变化了之后重新请求数据,并且把tab置为第一个
  * @return {*}
  */
+
 watch(
   () => [requestInfo.otherOptions],
   () => {
@@ -322,7 +328,7 @@ watch(
 )
 
 onMounted(() => {
-  getData(1)
+  // getData(1)
 
   if (props.tabInfo) activeTab.value = props.tabInfo[0].name
 })
@@ -401,7 +407,7 @@ onMounted(() => {
             /></el-icon>
           </span>
         </span>
-        <span class="toolItem">
+        <span class="toolItem" v-if="needDownload">
           <el-icon :size="iconSize"><Download /></el-icon>
         </span>
       </div>

+ 206 - 0
src/components/form/FileUpload.vue

@@ -0,0 +1,206 @@
+<script setup lang="ts">
+import { reactive, ref } from 'vue'
+import { ElMessage, genFileId } from 'element-plus'
+import type { UploadInstance, UploadRawFile } from 'element-plus'
+
+interface UploadProps {
+  title: string
+}
+
+const uploadRef = ref<UploadInstance>()
+
+// 上传成功的对外回调
+const emits = defineEmits(['uploadSuccess'])
+
+// props
+defineProps<UploadProps>()
+
+/**
+ * @description: 上传文件相关的所有信息
+ * @return {*}
+ */
+const uploadInfo = reactive({
+  uploadVisible: false
+})
+
+/**
+ * @description: 上传前对文件的处理
+ * @return {*}
+ */
+const beforeUpload = (file: UploadRawFile) => {
+  if (!file) return false // 取消上传
+  let state = false
+  let reader = new FileReader()
+  reader.readAsText(file)
+  reader.onload = function () {
+    try {
+      const finnalResult = reader.result as string
+      //   console.log(JSON.parse(finnalResult))
+      emits('uploadSuccess', JSON.parse(finnalResult))
+      state = true
+    } catch {
+      ElMessage.error('文件内容不是JSON格式,上传失败')
+    }
+  }
+  return state // 继续上传
+}
+
+/**
+ * @description: 准备开始上传
+ * @return {*}
+ */
+const startUpload = () => {
+  uploadInfo.uploadVisible = true
+}
+
+/**
+ * @description: 取消上传
+ * @return {*}
+ */
+const cancelUpload = () => {
+  uploadInfo.uploadVisible = false
+}
+
+/**
+ * @description: 确认上传
+ * @return {*}
+ */
+const confirmUpload = () => {
+  uploadRef.value?.submit()
+}
+
+/**
+ * @description:处理超出文件数量限制后的方法,这里是直接覆盖掉
+ * @param {*} files 上传的文件
+ * @return {*}
+ */
+const handleExceed = (files: UploadRawFile[]) => {
+  uploadRef.value!.clearFiles()
+  const file = files[0] as UploadRawFile
+  file.uid = genFileId()
+  uploadRef.value!.handleStart(file)
+}
+const tipVisisble = ref(false)
+const openTip = () => {
+  tipVisisble.value = true
+}
+
+defineExpose({
+  startUpload
+})
+</script>
+
+<template>
+  <div class="uploadFile">
+    <el-dialog align-center v-model="uploadInfo.uploadVisible" title="文件上传" width="500">
+      <div class="uploadBox">
+        <el-upload
+          :on-exceed="handleExceed"
+          :before-upload="beforeUpload"
+          accept=".json"
+          class="upload-demo"
+          drag
+          action="#"
+          :auto-upload="false"
+          :limit="1"
+          ref="uploadRef"
+        >
+          <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+          <div class="el-upload__text">拖拽上传或者<em>点击文件上传</em></div>
+          <template #tip>
+            <div class="el-upload__tip">
+              请上传一个JSON格式文件,上传格式请点击<span @click="openTip" class="openTip"
+                >这里</span
+              >查看。<br />
+            </div>
+          </template>
+        </el-upload>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="confirmUpload"> 确定 </el-button>
+          <el-button @click="cancelUpload">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <el-drawer v-model="tipVisisble" :direction="'rtl'">
+      <template #header>
+        <h4>上传文件格式</h4>
+      </template>
+      <template #default>
+        <div>
+          <p>请提供以下格式的json文件</p>
+          <br />
+          <p>
+            <b>allEventTable</b>用于存放所有需要更新或者需要新增的事件,<b>以数组形式传入.</b>
+
+            如果是需要更新的事件,需要提供<b>id</b>字段,否则不需要。如果不传入id字段则<b
+              >默认为新增</b
+            >
+          </p>
+          <br />
+          <p>
+            <b>allOptionsInfo</b
+            >用于存放所有的事件选项,以<b>对象</b>形式传入,<b>对象的键为该选项所对应的事件的actionId</b>,
+            同样,新增的选项不需要传入id字段,如果不传入id字段则<b>默认为新增</b>
+          </p>
+          <pre>
+                    <code>
+{
+    "allEventTable": [
+      // 更新
+        {
+            "id": 1,
+            "gid": "1200",
+            "actionId": "123",
+            "actionName": "第二关过关",
+            "status": 0,
+            "remark": "t"
+        },
+      // 新增
+        {
+            "gid": "1200",
+            "actionId": "123",
+            "actionName": "第二关过关",
+            "status": 0,
+            "remark": "t"
+        }
+    ],
+    // 选项的键需要对应对应事件的actionId
+    "allOptionsInfo": {
+      // 更新  
+        "button": [
+            {
+                "id": 1,
+                "optionId": "test",
+                "optionName": "test",
+                "optionType": "string",
+                "status": 0,
+            }
+        ],
+        // 新增
+        "123":[
+            {
+                "optionId": "ba",
+                "optionName": "bgbb",
+                "optionType": "int",
+                "status": 0
+            }
+        ]
+    }
+}
+
+                    </code>
+                </pre>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
+<style scoped>
+.openTip {
+  color: #409eff;
+  cursor: pointer;
+}
+</style>

+ 24 - 11
src/components/form/Form.vue

@@ -3,7 +3,7 @@ import type { FormInstance } from 'element-plus'
 import type { FormConfig } from '@/types/form'
 
 import { FormFieldType } from '@/types/form'
-import { reactive, onMounted, ref } from 'vue'
+import { reactive, ref } from 'vue'
 import { useForm } from '@/hooks/useForm'
 import CryptoJS from 'crypto-js'
 
@@ -86,20 +86,21 @@ const resumeFormData = () => {
   Object.assign(formData, backupData)
 }
 
+/**
+ * @description: 清除表单的验证信息
+ * @return {*}
+ */
 const clearValid = () => {
   formRef.value?.clearValidate()
 }
 
-onMounted(() => {
-  // nextTick(() => {
-
-  // })
-  console.log(formData)
-})
-
 /**
- * @description: 加密数据
- * @param {*} fields 字段名称
+ * @description: 用于加密数据
+ * 当使用表单的数据加密的时候,encryptMsg是表单的字段,会根据表单对应字段的数据进行加密
+ * 反之,则直接使用传入的信息进行加密
+ * @param {*} fields  加密的字段
+ * @param {*} useFormField  是否使用表单的数据生成加密消息
+ * @param {*} encryptMsg  加密的消息或者是需要加密的表单的字段名
  * @return {*}
  */
 const encryptData = (fields: string, useFormField: boolean, encryptMsg: Array<string>) => {
@@ -120,6 +121,14 @@ const encryptData = (fields: string, useFormField: boolean, encryptMsg: Array<st
   })
 }
 
+/**
+ * @description: 获取表单的数据
+ * @return {*}
+ */
+const getFormData = () => {
+  return JSON.parse(JSON.stringify(formData))
+}
+
 const emits = defineEmits(['subForm'])
 
 defineExpose({
@@ -129,7 +138,8 @@ defineExpose({
   encryptData,
   backupFormData,
   resumeFormData,
-  clearValid
+  clearValid,
+  getFormData
 })
 </script>
 
@@ -156,6 +166,7 @@ defineExpose({
             v-if="item.type === FormFieldType.INPUT"
             v-model="formData[item.name]"
             autocomplete="off"
+            :id="item.name"
           />
           <el-select
             style="width: 300px"
@@ -163,6 +174,7 @@ defineExpose({
             v-model="formData[item.name]"
             :placeholder="item.otherOptions.placeholder"
             size="default"
+            :id="item.name"
           >
             <el-option
               v-for="val in item.otherOptions.options"
@@ -172,6 +184,7 @@ defineExpose({
             />
           </el-select>
           <el-input
+            :id="item.name"
             v-if="item.type === FormFieldType.RICHTEXT"
             v-model="formData[item.name]"
             style="width: 300px; margin-bottom: 20px"

+ 3 - 2
src/hooks/useAnalysis.ts

@@ -2,8 +2,8 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:15:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-08-28 10:55:54
- * @FilePath: \Game-Backstage-Management-System\src\hooks\useTable.ts
+ * @LastEditTime: 2024-09-11 15:27:44
+ * @FilePath: \Game-Backstage-Management-System\src\hooks\useAnalysis.ts
  * @Description:
  *
  */
@@ -19,6 +19,7 @@ export function useAnalysis() {
       config.otherOptions[k] = newVal[k]
     })
   }
+
   return {
     updateReqConfig
   }

+ 51 - 0
src/hooks/usePage.ts

@@ -0,0 +1,51 @@
+/*
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-09-10 10:31:42
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-09-11 16:28:26
+ * @FilePath: \Game-Backstage-Management-System\src\hooks\usePage.ts
+ * @Description:
+ *
+ */
+import { onActivated, onDeactivated, watch } from 'vue'
+import { compareWatchData, saveWatchData } from '@/utils/common'
+
+export function usePage() {
+  const watchPageChange = (
+    watchData: () => Array<any>,
+    bakcupData: any,
+    cb: (...argus: any[]) => void
+  ) => {
+    let watchStop: (() => void) | null = null
+
+    onActivated(() => {
+      const currentData = watchData()
+      if (!compareWatchData(bakcupData, currentData)) {
+        cb(...currentData)
+      }
+
+      if (!watchStop) {
+        watchStop = watch(
+          () => watchData(),
+          (newData) => {
+            cb(...newData)
+          },
+          { deep: true }
+        )
+      }
+    })
+
+    onDeactivated(() => {
+      if (watchStop) {
+        const currentData = watchData()
+        saveWatchData(currentData, bakcupData)
+        watchStop()
+        watchStop = null
+      }
+    })
+  }
+
+  return {
+    watchPageChange
+  }
+}

+ 3 - 2
src/hooks/useRequest.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:24:06
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 16:35:50
+ * @LastEditTime: 2024-09-11 15:46:58
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useRequest.ts
  * @Description:
  *
@@ -61,7 +61,8 @@ export function useRequest() {
 
     // 事件分析
     userActionDetailDistribution: `${baseIp}/user/userActionDetailDistribution`, // 事件统计趋势图
-    userActionDetail: `${baseIp}/user/userActionDetail` // 事件统计详情
+    userActionDetail: `${baseIp}/user/userActionDetail`, // 事件统计详情
+    userActionList: `${baseIp}/user/userActionList` // 游戏事件统计列表
   }
 
   const analysisResCode = (data: AxiosResponse, kind?: string): Promise<ResponseInfo> => {

+ 2 - 2
src/hooks/useTable.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-09 14:32:30
+ * @LastEditTime: 2024-09-11 16:13:14
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useTable.ts
  * @Description:
  *
@@ -29,7 +29,7 @@ export function useTable(tableData: Array<any>, paginationSetting: TablePaginati
           if (isPagination) {
             tableData[paginationSetting.currentPage] = data
           } else {
-            tableData.splice(0, tableData.length, ...data)
+            if (data.length > 0) tableData.splice(0, tableData.length, ...data)
           }
 
           // 如果有的接口没有返回count属性,就需要自己写

+ 13 - 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-06 09:47:13
+ * @LastEditTime: 2024-09-12 16:17:36
  * @FilePath: \Game-Backstage-Management-System\src\types\dataAnalysis.ts
  * @Description:用于dataAnalysis相关组件的type
  *
@@ -77,11 +77,15 @@ export interface ResDataFieldInfo {
 // 表格字段
 // key需要与上方的resDataField的values字段对应
 export interface TrendTableField {
-  [key: string]: string
+  [key: string]: {
+    [key: string]: any
+  }
 }
 
 // 时间趋势组件所需要的props
 export interface TemporalTrendProps {
+  needDownload?: boolean // 是否需要下载
+
   tabInfo?: Array<TabInfo> // 用于切换的tab的信息
 
   type?: number // 趋势组件的类型,1为小时段,2为日期统计
@@ -117,3 +121,10 @@ export interface TemporalTrendInfo {
   trendTableFields: TrendTableField // 表格的字段,同时也作为legend的一部分
   chartsStaticField?: Array<StaticField> // 统计字段的信息
 }
+
+export interface IconDropdownItem {
+  value: string
+  icon: string
+  label: string
+  isSelected: boolean
+}

+ 3 - 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-05 15:51:33
+ * @LastEditTime: 2024-09-11 17:02:07
  * @FilePath: \Game-Backstage-Management-System\src\types\table.ts
  * @Description:
  *
@@ -80,6 +80,8 @@ export interface PropsParams {
   needRowindex?: boolean // 是否需要行号
   needAverage?: boolean // 是否需要均值功能
   needLeftTools?: boolean // 是否需要左侧的工具栏
+  needUpload?: boolean // 是否需要上传功能
+  needDownload?: boolean // 是否需要下载功能
   needRightTools?: boolean // 是否需要右侧工具栏
   openFilterQuery?: boolean // 是否开启上方查询功能
   openPageQuery?: boolean // 是否开启分页查询

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

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-26 15:46:42
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 17:55:03
+ * @LastEditTime: 2024-09-11 15:38:12
  * @FilePath: \Game-Backstage-Management-System\src\utils\common\index.ts
  * @Description:
  *
@@ -122,3 +122,28 @@ export const clearReactiveData = (data: Record<string, any>) => {
     delete data[key]
   })
 }
+
+/**
+ * @description: 用于保存watch切换之前的数据
+ * @param {any} data  需要保存的数据
+ * @param {any} store 需要保存到的对象
+ * @return {*}
+ */
+export const saveWatchData = (data: any, store: any) => {
+  if (Array.isArray(data)) {
+    // 这里需要深拷贝,否则会导致引用问题
+    store.splice(0, store.length, ...JSON.parse(JSON.stringify(data)))
+  } else {
+    Object.assign(store, JSON.parse(JSON.stringify(data)))
+  }
+}
+
+/**
+ * @description: 用于比较watch切换之前的数据
+ * @param {any} data 需要比较的数据
+ * @param {any} store 需要比较到的对象
+ * @return {*}  boolean
+ */
+export const compareWatchData = (data: any, store: any): boolean => {
+  return JSON.stringify(data) === JSON.stringify(store)
+}

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

@@ -1,3 +1,12 @@
+/*
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-08-31 14:51:20
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-09-11 16:50:46
+ * @FilePath: \Game-Backstage-Management-System\src\utils\resource\index.ts
+ * @Description:
+ *
+ */
 // 缓存对象
 const resourceCache: Record<string, string> = {}
 
@@ -9,8 +18,11 @@ const resourceCache: Record<string, string> = {}
  * @returns 返回blob对象的url
  */
 export const loadResource = (url: string): Promise<string> => {
+  // console.log(resourceCache, url)
+
   // 检查是否已经存储了这个资源,已经缓存了就直接返回
   if (resourceCache[url]) {
+    // alert(JSON.stringify(resourceCache[url]))
     return Promise.resolve(resourceCache[url])
   }
 
@@ -61,7 +73,6 @@ export const initLoadResouce = (
         resultObj[key] = value // 如果成功,存入结果对象中
       }
     })
-
     return resultObj // 返回所有成功的资源
   })
 }

+ 19 - 0
src/utils/table/table.ts

@@ -43,3 +43,22 @@ export const getAllGameInfo = async () => {
 export const shouldListenToEvent = (routeName: string | symbol | undefined, mathName: string) => {
   return routeName === mathName
 }
+
+/**
+ * @description: 下载文件信息
+ * @param {string} fileName 指定文件名
+ * @param {any} info  需要下载的信息
+ * @return {*}
+ */
+export const downLoadData = (fileName: string, info: any) => {
+  let result = {
+    ...info
+  }
+  let blob = new Blob([JSON.stringify(result)], { type: 'application/json' })
+  let url = URL.createObjectURL(blob)
+  let a = document.createElement('a')
+  a.href = url
+  a.download = fileName
+  a.click()
+  URL.revokeObjectURL(url)
+}

+ 0 - 1
src/views/AppManage/BaseInfoView.vue

@@ -75,7 +75,6 @@ const changeGameInfo = () => {
     return item.gid === selectInfo.gid
   })
   Object.assign(appInfo, nowGame)
-  console.log(nowGame)
 }
 
 const getGameInfo = () => {

+ 29 - 17
src/views/AppManage/EventDetailsView.vue

@@ -2,9 +2,11 @@
 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 { ElMessage, ElMessageBox, type FormRules } from 'element-plus'
 import type { FormConfig } from '@/types/form'
 import {
   type TablePaginationSetting,
@@ -19,6 +21,8 @@ import axiosInstance from '@/utils/axios/axiosInstance'
 
 const { AllApi, analysisResCode } = useRequest()
 
+const emits = defineEmits(['tableDataLoaded', 'upload'])
+
 // 事件表单对象
 const eventFormRef = ref()
 
@@ -38,9 +42,17 @@ const eventEditState = ref(false)
 // 属性对话框
 const attrDialog = ref()
 
+// 上传ref
+const uploadRef = ref<InstanceType<typeof FileUpload> | null>(null)
+
+// 上传的信息
+const uploadInfo = reactive({
+  dialogTitle: '上传事件信息'
+})
+
 // 表单规则字段
 const ruleForm = reactive({
-  actionId: '',
+  // actionId:"",
   actionName: '',
   status: 1,
   remark: ''
@@ -48,10 +60,10 @@ const ruleForm = reactive({
 
 // 表单规则
 const rules = reactive<FormRules<typeof ruleForm>>({
-  actionId: [
-    { required: true, message: '事件ID是必填项', trigger: 'blur' },
-    { min: 1, max: 10, message: '事件ID长度必须在1到10之间', trigger: 'blur' }
-  ],
+  // actionId: [
+  //   { required: true, message: '事件ID是必填项', trigger: 'blur' },
+  //   { min: 1, max: 10, message: '事件ID长度必须在1到10之间', trigger: 'blur' }
+  // ],
   actionName: [
     { required: true, message: '事件名称是必填项', trigger: 'blur' },
     { min: 5, max: 20, message: '事件名称长度必须在5到20之间', trigger: 'blur' }
@@ -68,11 +80,11 @@ const rules = reactive<FormRules<typeof ruleForm>>({
 
 // 表单字段信息
 const FormFields: Array<FormField> = [
-  {
-    name: 'actionId',
-    cnName: '事件ID',
-    type: FormFieldType.INPUT
-  },
+  // {
+  //   name: 'actionId',
+  //   cnName: '事件ID',
+  //   type: FormFieldType.INPUT
+  // },
   {
     name: 'actionName',
     cnName: '事件名',
@@ -419,13 +431,8 @@ const initParams = () => {
   }
 }
 
+// 初始化参数
 initParams()
-
-/**
- * @description:
- * @return {*}
- */
-onMounted(() => {})
 </script>
 
 <template>
@@ -459,11 +466,13 @@ onMounted(() => {})
     </div>
     <div class="list">
       <Table
+        :need-right-tools="true"
         :need-left-tools="true"
         :open-page-query="true"
         :pagination-config="pageConfig"
         :table-fields-info="tableFieldConfig"
         :request-config="tableReqConfig"
+        :need-download="true"
         @add-new-item="addNewAttr"
         ref="optionTableRef"
       >
@@ -496,6 +505,9 @@ onMounted(() => {})
   <div class="addAttrDialo">
     <Dialog ref="attrDialog" :config="dialogConfig" @form-submit="formSub"></Dialog>
   </div>
+  <div class="uploadFileBox">
+    <FileUpload ref="uploadRef" :title="uploadInfo.dialogTitle"></FileUpload>
+  </div>
 </template>
 
 <style scoped>

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

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-02 17:57:15
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 17:58:41
+ * @LastEditTime: 2024-09-12 15:05:20
  * @FilePath: \Game-Backstage-Management-System\src\views\AppManage\EventManageView.vue
  * @Description: 
  * 
@@ -10,10 +10,33 @@
 <script setup lang="ts">
 import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
 import { shouldListenToEvent } from '@/utils/table/table'
-import { onMounted, ref } from 'vue'
+import { onMounted, ref, reactive, toRaw } from 'vue'
+import { ElMessage } from 'element-plus'
+import FileUpload from '@/components/form/FileUpload.vue'
+import axiosInstance from '@/utils/axios/axiosInstance'
+import { useRequest } from '@/hooks/useRequest'
+import { useCommonStore } from '@/stores/useCommon'
+import { downLoadData } from '@/utils/table/table'
+import { resetTimeToMidnight } from '@/utils/common'
 
+const { selectInfo } = useCommonStore()
+
+const { AllApi } = useRequest()
+
+// 头部ref
 const headerCard = ref<typeof HeaderCard>()
 
+// 上传ref
+const uploadRef = ref<InstanceType<typeof FileUpload> | null>(null)
+
+// 上传的信息
+const uploadInfo = reactive({
+  dialogTitle: '上传事件信息'
+})
+
+// 现在所有的选项列表
+// const nowOptionList: Array<any> = []
+
 /**
  * @description: 进入详情页,触发headercard的添加事件,增加一个面包屑导航
  * @param {*} info 传入的信息
@@ -24,6 +47,247 @@ const headerAddPath = (info: any) => {
   headerCard.value?.addPath(name, pathName)
 }
 
+/**
+ * @description: 提交所有新上传的事件及选项请求
+ * @param {*} reqList 请求列表
+ * @return {*}
+ */
+const submitUpload = async (reqList: Array<Promise<boolean>>) => {
+  await Promise.allSettled(reqList).then((res) => {
+    if (
+      res.every((item) => {
+        console.log(item)
+        return item.status === 'fulfilled' && item.value === true
+      })
+    ) {
+      ElMessage.success('上传成功')
+    } else {
+      ElMessage.error('部分上传失败,请检查参数')
+    }
+  })
+}
+
+/**
+ * @description: 请求表格数据,如果一次请求没有拿到所有的数据,则再请求一次
+ * @param {*} url 请求地址
+ * @param {*} otherInfo 请求参数
+ * @param {*} offset 偏移量
+ * @param {*} limit 请求数量
+ * @return {*}
+ */
+const getTableData = async (
+  url: string,
+  otherInfo: any,
+  offset = 0,
+  limit = 10000
+): Promise<Array<any>> => {
+  let total = limit + offset // 目前请求到的总数
+  let finalResult = []
+  finalResult = await axiosInstance
+    .post(url, {
+      offset,
+      limit,
+      ...otherInfo
+    })
+    .then(async (res) => {
+      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)
+      }
+      return [...resData.data, ...result]
+    })
+    .catch((err) => {
+      console.log(err)
+      return []
+    })
+  return finalResult
+}
+
+/**
+ * @description: 批量请求
+ * @param {*} url 请求地址
+ * @param {*} reqParams 请求参数列表
+ * @return {*}
+ */
+const batReqOptionsData = async (
+  url: string,
+  reqParams: Array<any>,
+  eventTable: Array<any>
+): Promise<Object> => {
+  let reqList: Array<Promise<any>> = []
+  let finalResult: {
+    [key: string]: Array<any>
+  } = {}
+
+  reqParams.map((item) => {
+    reqList.push(
+      getTableData(url, item)
+        .then((res) => {
+          let actionId = eventTable.find((i) => i.id === item.actionId).actionId
+          finalResult[actionId] = res
+        })
+        .catch((err) => {
+          console.log(err)
+        })
+    )
+  })
+  await Promise.all(reqList)
+  return finalResult
+}
+
+/**
+ * @description: 拿到事件数据和选项数据
+ * @param {*} needTable 需要拿到哪些数据,默认是all,即所有数据。'event'表示只拿事件数据,'option'表示只拿选项数据
+ * @return {*} 返回事件数据和选项数据
+ */
+const getAllTable = async (): Promise<{ allEventTable: Array<any>; allOptionsInfo: any }> => {
+  let allEventTable = await getTableData(AllApi.gameActionList, { gid: selectInfo.gid })
+  let optionReqList = allEventTable.map((item) => {
+    return { actionId: parseInt(item.id) }
+  })
+  let allOptionsInfo = await batReqOptionsData(
+    AllApi.gameActionOptionList,
+    optionReqList,
+    allEventTable
+  )
+  return { allEventTable, allOptionsInfo }
+}
+
+/**
+ * @description: 开始上传
+ * @param{Array<any>} tableData  表格数据
+ * @return {*}
+ */
+const startUpload = async () => {
+  if (uploadRef.value) {
+    uploadRef.value.startUpload()
+  }
+}
+
+/**
+ * @description: 开始下载
+ * @return {*}
+ */
+const startDownload = async () => {
+  let { allEventTable, allOptionsInfo } = await getAllTable()
+  downLoadData(`allevents_${resetTimeToMidnight(new Date())}`, {
+    allEventTable,
+    allOptionsInfo
+  })
+}
+
+// 上传选项的时候,选项的键要设置为actionid
+// 需要先上传事件,然后上传完了,用上传的选项中的第一个(如果有)的actionid去找到对应的事件的ID(不是actionid),然后作为选项上传的id
+
+const uploadSuccess2 = async (data: any) => {
+  let uploadEventTable = data.allEventTable
+  let uploadOptionsInfo = data.allOptionsInfo
+
+  // 上传的事件列表有值,则开始上传
+  let { allEventTable, allOptionsInfo } = await getAllTable() // 获取所有事件列表和选项列表
+  // console.log(allEventTable, allOptionsInfo, uploadOptionsInfo)
+  let newEventKeys: Array<any> = [] // 拿到所有需要新上传的事件
+  Object.keys(uploadOptionsInfo).map((item) => {
+    if (allEventTable.every((i) => i.actionId !== item)) {
+      newEventKeys.push(item)
+    }
+  })
+
+  // 1、新事件、新选项
+  // 2、旧事件、新选项 + 旧选项
+
+  let eventReqList: Array<Promise<boolean>> = []
+  let eventReqUrl = AllApi.setGameAction
+  if (Array.isArray(uploadEventTable) && uploadEventTable.length) {
+    uploadEventTable.map((item) => {
+      let { id, createdAt, updatedAt, ...otherInfo } = item
+      if (allEventTable.some((i) => i.actionId === item.actionId)) {
+        eventReqUrl = AllApi.updateGameAction
+        console.log('更新一个事件', item)
+      } else {
+        eventReqUrl = AllApi.setGameAction
+        console.log('新增一个事件', item)
+      }
+      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)
+    })
+    await submitUpload(eventReqList)
+  }
+  ;({ allEventTable, allOptionsInfo } = await getAllTable()) // 获取所有事件列表和选项列表
+  let optionsReqList: Array<Promise<boolean>> = []
+  // console.log(allEventTable)
+
+  allEventTable.map((item) => {
+    let uploadOptionItem = uploadOptionsInfo[item.actionId] as Array<any> // 找到所有在已有事件列表中的选项列表
+    let nowOptionItem = allOptionsInfo[item.actionId] as Array<any> // 找到所有在已有事件列表中的选项列表
+
+    if (uploadOptionItem) {
+      uploadOptionItem.map((i) => {
+        let optionReqUrl = AllApi.addGameActionOption // 选项上传的url
+        let { id, actionId, createdAt, updatedAt, ...otherInfo } = i // 上传参数拆分出来
+        let reqParams = {}
+        if (newEventKeys.find((k) => k === item.actionId)) {
+          // 是新增的事件的选项
+          console.log('全新增')
+          actionId = item.id
+          console.log(actionId)
+          optionReqUrl = AllApi.addGameActionOption
+          reqParams = { actionId, ...otherInfo }
+          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)
+        } else {
+          // 是已有事件的选项
+          // 分出来哪些是新增加的选项,哪些是需要更新的选项
+          if (nowOptionItem.some((k) => k.id === i.id)) {
+            reqParams = { id, ...otherInfo }
+
+            optionReqUrl = AllApi.updateGameActionOption
+            console.log('更新一个选项', i)
+          } else {
+            actionId = item.id
+            reqParams = { actionId, ...otherInfo }
+            optionReqUrl = AllApi.addGameActionOption
+            console.log('新增一个选项', i)
+          }
+          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)
+}
+
 onMounted(() => {})
 </script>
 
@@ -37,6 +301,12 @@ onMounted(() => {})
         :need-pf-select="false"
       ></HeaderCard>
     </div>
+    <div class="handleEvent">
+      <div class="fileGroup">
+        <el-button class="fileBtn" color="#626aef" @click="startUpload">上传</el-button>
+        <el-button class="fileBtn" @click="startDownload">下载</el-button>
+      </div>
+    </div>
     <div class="eventTableBox">
       <!-- 监听表格的跳转事件 -->
       <router-view v-slot="{ Component, route }">
@@ -53,6 +323,13 @@ onMounted(() => {})
         <component v-if="route.name !== 'EventTable'" :is="Component" />
       </router-view>
     </div>
+    <div class="uploadFileBox">
+      <FileUpload
+        @upload-success="uploadSuccess2"
+        ref="uploadRef"
+        :title="uploadInfo.dialogTitle"
+      ></FileUpload>
+    </div>
   </div>
 </template>
 
@@ -79,4 +356,32 @@ onMounted(() => {})
   box-sizing: border-box;
   padding: 0px 24px;
 }
+
+.handleEvent {
+  width: 100%;
+  background-color: white;
+  box-sizing: border-box;
+  height: 48px;
+  padding: 0 24px;
+  font-size: 16px;
+  font-weight: 600;
+  position: relative;
+  display: flex;
+  align-items: center;
+  /* justify-content: flex-end; */
+}
+
+.fileGroup {
+  position: absolute;
+  right: 5%;
+  width: 12%;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: space-around;
+}
+
+.fileBtn {
+  /* color: white; */
+  /* margin-left: 50px; */
+}
 </style>

+ 5 - 4
src/views/AppManage/EventMangeTable.vue

@@ -22,10 +22,10 @@ const { AllApi } = useRequest()
 
 const eventDialog = ref()
 
-const eventTable = ref()
+const eventTable = ref<InstanceType<typeof Table> | null>(null)
 
 // 主要为了给面包屑导航提供信息
-const emits = defineEmits(['enterDetail'])
+const emits = defineEmits(['enterDetail', 'upload'])
 
 // 表格分页设置
 const pagingConfig = reactive<TablePaginationSetting>({
@@ -258,7 +258,9 @@ const addNewEvent = () => {
  * @return {*}
  */
 const subForm = () => {
-  eventTable.value.getData()
+  if (eventTable.value) {
+    eventTable.value.getData()
+  }
 }
 
 watch(
@@ -282,7 +284,6 @@ onMounted(() => {})
       :table-fields-info="tableFieldsInfo"
       :query-info="queryInfo"
       :open-filter-query="true"
-      :need-left-tools="true"
       @add-new-item="addNewEvent"
     >
       <template #tableOperation>

+ 20 - 10
src/views/Home/Analysis/EventAnalysisDetail.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-27 17:11:23
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 15:36:46
+ * @LastEditTime: 2024-09-12 16:25:55
  * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\EventAnalysisDetail.vue
  * @Description: 
  * 
@@ -97,13 +97,20 @@ const tableRequestConfig = reactive<ReqConfig>({
 // 图表信息
 // 返回数据中字段分类
 const resDataField: ResDataFieldInfo = {
-  xAxis: 'data', // X轴字段为日期
-  values: ['data'] // Y轴字段为事件数
+  xAxis: 'list', // X轴字段为日期
+  values: ['list'] // Y轴字段为事件数
 }
 
 // 图表中表格的字段信息
 const chartTableField: TrendTableField = {
-  data: '事件数'
+  eventCount: {
+    index: '日期',
+    list: '事件数'
+  },
+  activeEventCount: {
+    index: '日期',
+    list: '事件达成设备数'
+  }
 }
 
 // 请求参数配置
@@ -124,8 +131,8 @@ const tabInfo: Array<TabInfo> = [
     type: 1
   },
   {
-    name: 'eventCount2',
-    tabTitle: '事件数2',
+    name: 'activeEventCount',
+    tabTitle: '事件达成设备数',
     type: 2
   }
 ]
@@ -153,22 +160,25 @@ const initParams = () => {
  * @return {*}
  */
 watch(
-  () => [props.startTime, props.endTime],
-  ([newStart, newEnd]) => {
+  () => [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()
+  },
+  {
+    deep: true
   }
 )
-
 initParams()
-
 onMounted(() => {})
 </script>
 <template>

+ 50 - 60
src/views/Home/Analysis/EventAnalysisTable.vue

@@ -1,23 +1,20 @@
 <script setup lang="ts">
 import Table from '@/components/Table.vue'
 
-import { reactive, watch, ref } from 'vue'
+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 type { ReqConfig } from '@/types/dataAnalysis'
-import {
-  type TablePaginationSetting,
-  type TableFieldInfo,
-  FieldSpecialEffectType
-} from '@/types/table'
+import { type TablePaginationSetting, type TableFieldInfo } from '@/types/table'
 import router from '@/router'
 
+import { usePage } from '@/hooks/usePage'
+const { watchPageChange } = usePage()
+
 const { AllApi } = useRequest()
 const { selectInfo } = useCommonStore()
-const { updateReqConfig } = useAnalysis()
 
 const eventTable = ref()
 
@@ -45,18 +42,6 @@ const pagingConfig = reactive<TablePaginationSetting>({
 // 表格字段信息
 const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
   {
-    name: 'id',
-    cnName: 'ID',
-    isShow: true,
-    needSort: true
-  },
-  {
-    name: 'gid',
-    cnName: '游戏ID',
-    isShow: true,
-    needSort: false
-  },
-  {
     name: 'actionId',
     cnName: '事件ID',
     isShow: true,
@@ -69,46 +54,34 @@ const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
     needSort: false
   },
   {
-    name: 'status',
-    cnName: '事件状态',
-    isShow: true,
-    needSort: false,
-    specialEffect: {
-      type: FieldSpecialEffectType.STATE,
-      othnerInfo: {
-        text: ['已使用', '已停用']
-      }
-    }
-  },
-  {
-    name: 'eventCount',
-    cnName: '事件数',
+    name: 'actionCount',
+    cnName: '操作次数',
     isShow: true,
-    needSort: true
+    needSort: false
   },
   {
-    name: 'eventDevices',
-    cnName: '事件达成设备数',
+    name: 'actionUserCount',
+    cnName: '操作用户数',
     isShow: true,
-    needSort: true
+    needSort: false
   },
   {
-    name: 'activeDeviceRate',
-    cnName: '活跃设备发生率',
+    name: 'activeUserRate',
+    cnName: '活跃用户率',
     isShow: true,
     needSort: false
   },
   {
-    name: 'eventPerStartup',
-    cnName: '每启动发生次数',
+    name: 'loginActiveRate',
+    cnName: '登录活跃率',
     isShow: true,
-    needSort: true
+    needSort: false
   }
 ])
 
 // 表格请求配置
 const requestConfig = reactive<ReqConfig>({
-  url: AllApi.gameActionList,
+  url: AllApi.userActionList,
   otherOptions: {
     gid: selectInfo.gid,
     startTime: props.startTime,
@@ -117,28 +90,13 @@ const requestConfig = reactive<ReqConfig>({
 })
 
 /**
- * @description: 监听事件变化,去重新请求数据
- * @return {*}
- */
-watch(
-  () => [props.startTime, props.endTime],
-  ([newStart, newEnd]) => {
-    updateReqConfig(requestConfig, {
-      startTime: newStart,
-      endTime: newEnd
-    })
-    eventTable.value.getData()
-  }
-)
-
-/**
  * @description: 查看详情
  * @param {*} row 行信息
  * @return {*}
  */
 const viewDetails = (row: any) => {
   emits('enterDetail', {
-    name: row.eventAlias,
+    name: row.actionName,
     pathName: 'EventAnalysisDetail'
   })
   router.push({
@@ -148,6 +106,38 @@ const viewDetails = (row: any) => {
     }
   })
 }
+
+/**
+ * @description: 监听事件变化,去重新请求数据
+ * @return {*}
+ */
+
+/**
+ * @description: 更新时间
+ * @param {*} startTime 开始时间
+ * @param {*} endTime 结束时间
+ * @return {*}
+ */
+const updateDate = (startTime: string, endTime: string) => {
+  requestConfig.otherOptions.startTime = startTime
+  requestConfig.otherOptions.endTime = endTime
+}
+
+/**
+ * @description: 更新Gid
+ * @param {*} newGid 新的gid
+ * @return {*}
+ */
+const updateGid = (newGid: string) => {
+  requestConfig.otherOptions.gid = newGid
+}
+
+const backupDate = reactive([])
+const backupSelect = reactive([])
+
+watchPageChange(() => [selectInfo.gid], backupSelect, updateGid)
+
+watchPageChange(() => [props.startTime, props.endTime], backupDate, updateDate)
 </script>
 <template>
   <div class="eventTable">

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

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-27 17:11:23
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-09 16:38:48
+ * @LastEditTime: 2024-09-12 16:04:20
  * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\EventAnalysisView.vue
  * @Description: 
  * 
@@ -21,7 +21,7 @@ const headerCard = ref()
 // 头部组件需要的 props
 const headerProps = reactive<HeaderCardProps>({
   title: '事件分析',
-  defaultPf: 'web',
+  defaultPf: 'wx',
   openDateSelect: true
 })
 
@@ -37,6 +37,7 @@ const endTime = ref()
  */
 const headerAddPath = (info: any) => {
   const { name, pathName } = info
+
   headerCard.value?.addPath(name, pathName)
 }
 
@@ -49,15 +50,6 @@ const dateChange = (newDate: Array<Date>) => {
   startTime.value = resetTimeToMidnight(newDate[0])
   endTime.value = resetTimeToMidnight(newDate[1])
 }
-
-/**
- * @description: 选择的平台改变
- * @param {*} pf  选择的平台数组,暂时只用第一个
- * @return {*}
- */
-const pfChange = (pf: Array<string>) => {
-  console.log(pf)
-}
 </script>
 <template>
   <div class="eventAnalysis">
@@ -68,8 +60,8 @@ const pfChange = (pf: Array<string>) => {
         :default-pf="headerProps.defaultPf"
         :open-date-select="headerProps.openDateSelect"
         :need-breadcrumb="true"
+        :need-pf-select="false"
         @change-date="dateChange"
-        @change-pf="pfChange"
       ></HeaderCard>
     </div>
     <div class="content">

+ 55 - 23
src/views/Home/Analysis/KeepView.vue

@@ -2,13 +2,13 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-27 17:11:23
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-05 18:08:29
+ * @LastEditTime: 2024-09-11 15:32:00
  * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\KeepView.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
-import { watch, reactive, ref, toRaw } from 'vue'
+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'
@@ -20,6 +20,9 @@ import { useRequest } from '@/hooks/useRequest'
 import { useCommonStore } from '@/stores/useCommon'
 import { useAnalysis } from '@/hooks/useAnalysis'
 
+import { usePage } from '@/hooks/usePage'
+const { watchPageChange } = usePage()
+
 const { updateReqConfig } = useAnalysis()
 const { selectInfo } = useCommonStore()
 
@@ -27,7 +30,7 @@ const { AllApi, analysisResCode } = useRequest()
 
 // 选择的信息
 const keepViewSelect = reactive({
-  pf: 'web'
+  pf: 'wx'
 })
 
 const loading = ref(true) // 加载状态
@@ -51,7 +54,8 @@ const keepDataTableInfo = reactive<{
       pf: keepViewSelect.pf,
       gid: selectInfo.gid,
       startTime: resetTimeToMidnight(new Date()),
-      endTime: resetTimeToMidnight(new Date())
+      endTime: resetTimeToMidnight(new Date()),
+      type: 1
     }
   },
   tableFieldsInfo: toRaw([
@@ -189,28 +193,56 @@ const getTableData = () => {
 }
 
 /**
- * @description: 监听pf和gid的变化,数据变化后立即重新请求所有相关数据
- * @tip watch监听reactive的数据时,必须以getter形式,不然会警告
+ * @description: 更新所有监听req的参数
+ * @param {*} pf  新pf
+ * @param {*} gid 新gid
  * @return {*}
  */
-watch(
-  () => [keepViewSelect.pf, selectInfo.gid],
-  ([newPf, newGid]) => {
-    updateReqConfig(keepDataTableInfo.requestConfig, { pf: newPf, gid: newGid })
-  }
-)
+const updateAllReq = (pf: string, gid: string) => {
+  updateReqConfig(keepDataTableInfo.requestConfig, { pf, gid })
+}
 
-/**
- * @description: 单独监听一下他的变化,去手动更新数据
- * @return {*}
- */
-watch(
-  () => keepDataTableInfo.requestConfig,
-  () => {
-    getTableData()
-  },
-  { deep: true }
-)
+const backupReq = reactive([])
+const backupSelect = reactive([])
+
+watchPageChange(() => [keepViewSelect.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">

+ 122 - 28
src/views/Home/Analysis/UserTrendView.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
 import Table from '@/components/Table.vue'
-import { onMounted, reactive, watch, ref, toRaw } from 'vue'
+import { reactive, ref, toRaw } from 'vue'
 import type { StaticField, ReqConfig, TemporalTrendInfo } from '@/types/dataAnalysis'
 import type { TablePaginationSetting, TableFieldInfo } from '@/types/table'
 
@@ -12,6 +12,8 @@ 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'
+const { watchPageChange } = usePage()
 
 const { updateReqConfig } = useAnalysis()
 const { AllApi, analysisResCode } = useRequest()
@@ -23,7 +25,7 @@ const userTrendStaticRef = ref()
 // 目前选择的信息
 // 这里不太合理,应该根据返回的数据中的pf和game赋值,因为这个可能会变
 const userTrendSelectInfo = reactive({
-  pf: 'web'
+  pf: 'wx'
 })
 
 // 总览数据的字段对应的信息
@@ -126,7 +128,30 @@ const dataTrendInfo = reactive<TemporalTrendInfo>({
     values: ['imeDistribution'] // 值所在的字段
   }),
   trendTableFields: toRaw({
-    imeDistribution: '新增'
+    newUser: {
+      index: '日期',
+      imeDistribution: '新增用户'
+    },
+    dailyActiveUser: {
+      index: '日期',
+      imeDistribution: '日活跃用户'
+    },
+    weeklyActiveUser: {
+      index: '日期',
+      imeDistribution: '周活跃用户'
+    },
+    detailDatalyActiveUser: {
+      index: '日期',
+      imeDistribution: '月活跃用户'
+    },
+    launchCount: {
+      index: '日期',
+      imeDistribution: '启动次数'
+    },
+    dailyUsageTime: {
+      index: '日期',
+      imeDistribution: '单日使用时长'
+    }
   })
 })
 
@@ -202,20 +227,6 @@ const detailDataTableInfo = reactive<{
 const detailDataTableData = reactive<Array<any>>([])
 
 /**
- * @description: 监听pf和gid的变化,数据变化后立即重新请求所有相关数据
- * @tip watch监听reactive的数据时,必须以getter形式,不然会警告
- * @return {*}
- */
-watch(
-  () => [userTrendSelectInfo.pf, selectInfo.gid],
-  ([newPf, newGid]) => {
-    updateReqConfig(userTrendDataReqConfig, { pf: newPf, gid: newGid })
-    updateReqConfig(dataTrendInfo.dataReqConfig, { pf: newPf, gid: newGid })
-    updateReqConfig(detailDataTableInfo.requestConfig, { pf: newPf, gid: newGid })
-  }
-)
-
-/**
  * @description: 选择的平台改变
  * @param {*} pf  选择的平台数组,暂时只用第一个
  * @return {*}
@@ -267,25 +278,109 @@ const getDetailData = () => {
           })
         }
         detailDataTableData.splice(0, detailDataTableData.length, ...newList)
-        console.log(newList.length)
         detailDataTableInfo.paginationConfig.total = newList.length
       })
     })
 }
 
 /**
- * @description: 单独监听一下他的变化,去手动更新数据
+ * @description: 更新所有监听req的参数
+ * @param {*} pf  新pf
+ * @param {*} gid 新gid
  * @return {*}
  */
-watch(
-  () => detailDataTableInfo.requestConfig,
-  () => {
-    getDetailData()
-  },
-  { deep: true }
-)
+const updateAllReq = (pf: string, gid: string) => {
+  updateReqConfig(userTrendDataReqConfig, { pf, gid })
+  updateReqConfig(dataTrendInfo.dataReqConfig, { pf, gid })
+  updateReqConfig(detailDataTableInfo.requestConfig, { pf, gid })
+}
+
+/**
+ * @description: 监听变化,去手动更新数据
+ * @return {*}
+ */
+
+const backupReq = reactive([]) // 保存请求参数
+
+const backupSelect = reactive([]) // 保存选择数据
+
+watchPageChange(() => [userTrendSelectInfo.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
+//   }
+// })
 
-onMounted(() => {})
+// watch(
+//   () => test.name,
+//   (newdata) => {
+//     console.log('本地')
+//     console.log(newdata)
+//   },
+//   {
+//     deep: true
+//   }
+// )
 </script>
 <template>
   <div class="userTrendBox">
@@ -307,7 +402,6 @@ onMounted(() => {})
     </div>
     <div class="dataTrendBox">
       <TemporalTrend
-        :wait-time-select="true"
         :need-charts="true"
         :table-fields-info="dataTrendInfo.trendTableFields"
         :res-data-fields-info="dataTrendInfo.resDataField"

+ 33 - 7
src/views/Home/InfoManage/PlayerManageView.vue

@@ -14,7 +14,7 @@ import Table from '@/components/Table.vue'
 
 import axiosInstance from '@/utils/axios/axiosInstance'
 
-import { onMounted, reactive, ref, watch } from 'vue'
+import { onMounted, reactive, ref } from 'vue'
 import { ElMessageBox } from 'element-plus'
 
 import type { FormRules } from 'element-plus'
@@ -299,12 +299,38 @@ const encrypt = () => {
   playerDialogFormRef.value.encrypt('option', true, ['gid', 'userId', 'pf'])
 }
 
-watch(
-  () => commonStore.selectInfo.gid,
-  (val) => {
-    requestConfig.otherOptions.gid = val
-  }
-)
+// 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 backupSelect = reactive([])
+
+import { usePage } from '@/hooks/usePage'
+const { watchPageChange } = usePage()
+
+watchPageChange(() => [commonStore.selectInfo.gid], backupSelect, updateGid)
 
 onMounted(() => {
   tableStore.allGameInfo.map((item) => {

+ 66 - 27
src/views/Home/Overview/OverView.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
-import { onMounted, reactive, watch, ref, toRaw } from 'vue'
+import { reactive, ref, toRaw } from 'vue'
 import type { StaticField, ReqConfig, TemporalTrendInfo } from '@/types/dataAnalysis'
 
 import StatisticText from '@/components/dataAnalysis/StatisticText.vue'
@@ -9,6 +9,9 @@ import { useCommonStore } from '@/stores/useCommon'
 import { useRequest } from '@/hooks/useRequest'
 import { useAnalysis } from '@/hooks/useAnalysis'
 
+import { usePage } from '@/hooks/usePage'
+const { watchPageChange } = usePage()
+
 const { updateReqConfig } = useAnalysis()
 const { AllApi } = useRequest()
 const { selectInfo } = useCommonStore()
@@ -19,7 +22,7 @@ const overviewStaticRef = ref()
 // 目前选择的信息
 // 这里不太合理,应该根据返回的数据中的pf和game赋值,因为这个可能会变
 const overViewSelectInfo = reactive({
-  pf: 'web'
+  pf: 'wx'
 })
 
 // 总览数据的字段对应的信息
@@ -55,13 +58,14 @@ const overViewDataReqConfig = reactive<ReqConfig>({
   }
 })
 
+// 这里不要给pf和gid赋值,不然趋势图组件监听不到gid和pf的变化,不会请求数据
 // 分时段组件所需要的信息
 const periodInfo = reactive<TemporalTrendInfo>({
   dataReqConfig: {
     url: AllApi.timeDistributionData,
     otherOptions: {
-      pf: overViewSelectInfo.pf,
-      gid: selectInfo.gid
+      pf: '',
+      gid: ''
     }
   },
   tabList: toRaw([
@@ -103,9 +107,21 @@ const periodInfo = reactive<TemporalTrendInfo>({
     values: ['today', 'yesterday'] // 值所在的字段
   }),
   trendTableFields: toRaw({
-    index: '时间段',
-    today: '今日',
-    yesterday: '昨日'
+    newUser: {
+      index: '时间段',
+      today: '今日新增',
+      yesterday: '昨日新增'
+    },
+    activeUser: {
+      index: '时间段',
+      today: '今日活跃',
+      yesterday: '昨日活跃'
+    },
+    launchCount: {
+      index: '时间段',
+      today: '今日启动',
+      yesterday: '昨日启动'
+    }
   })
 })
 
@@ -114,8 +130,8 @@ const monthInfo = reactive<TemporalTrendInfo>({
   dataReqConfig: {
     url: AllApi.userMouthDistributionData,
     otherOptions: {
-      pf: overViewSelectInfo.pf,
-      gid: selectInfo.gid
+      pf: '',
+      gid: ''
     }
   },
   tabList: toRaw([
@@ -162,26 +178,30 @@ const monthInfo = reactive<TemporalTrendInfo>({
     values: ['timeDistribution'] // 值所在的字段
   }),
   trendTableFields: toRaw({
-    index: '日期',
-    timeDistribution: '新增'
+    newUser: {
+      index: '日期',
+      timeDistribution: '新增用户'
+    },
+    activeUser: {
+      index: '日期',
+      timeDistribution: '活跃用户'
+    },
+    launchCount: {
+      index: '日期',
+      timeDistribution: '启动次数'
+    },
+    deviceDuration: {
+      index: '日期',
+      timeDistribution: '单设备时长'
+    },
+    retentionRate: {
+      index: '日期',
+      timeDistribution: '留存率'
+    }
   })
 })
 
 /**
- * @description: 监听pf和gid的变化,数据变化后立即重新请求所有相关数据
- * @tip watch监听reactive的数据时,必须以getter形式,不然会警告
- * @return {*}
- */
-watch(
-  () => [overViewSelectInfo.pf, selectInfo.gid],
-  ([newPf, newGid]) => {
-    updateReqConfig(overViewDataReqConfig, { pf: newPf, gid: newGid })
-    updateReqConfig(periodInfo.dataReqConfig, { pf: newPf, gid: newGid })
-    updateReqConfig(monthInfo.dataReqConfig, { pf: newPf, gid: newGid })
-  }
-)
-
-/**
  * @description: 选择的平台改变
  * @param {*} pf  选择的平台数组,暂时只用第一个
  * @return {*}
@@ -190,7 +210,27 @@ const changePf = (pf: Array<string>) => {
   overViewSelectInfo.pf = pf[0]
 }
 
-onMounted(() => {})
+/**
+ * @description: 更新所有的请求接口
+ * @param {*} pf 平台
+ * @param {*} gid 游戏id
+ * @return {*}
+ */
+const updateAllReq = (pf: string, gid: string) => {
+  updateReqConfig(overViewDataReqConfig, { pf, gid })
+  updateReqConfig(periodInfo.dataReqConfig, { pf, gid })
+  updateReqConfig(monthInfo.dataReqConfig, { pf, gid })
+}
+
+/**
+ * @description: 监听pf和gid的变化,数据变化后立即重新请求所有相关数据
+ *    当没有激活时,则取消监听
+ * @tip watch监听reactive的数据时,必须以getter形式,不然会警告
+ * @return {*}
+ */
+
+const backupSelect = reactive([])
+watchPageChange(() => [overViewSelectInfo.pf, selectInfo.gid], backupSelect, updateAllReq)
 </script>
 <template>
   <div class="overViewBox">
@@ -211,7 +251,6 @@ onMounted(() => {})
     </div>
     <div class="periodTrendBox">
       <TemporalTrend
-        :wait-time-select="false"
         :need-charts="true"
         :table-fields-info="periodInfo.trendTableFields"
         :res-data-fields-info="periodInfo.resDataField"