Ver código fonte

更新事件分析、事件管理界面面包屑导航,更新路由文件,更新保活逻辑,使得事件分析、管理界面的表格界面可以缓存

fxs 9 meses atrás
pai
commit
750831642f

+ 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",

+ 1 - 0
public/img/default/defaultGame.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1725591308908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4433" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M704.32 974.4H321.81333333c-77.54666667 0-150.61333333-30.4-205.76-85.54666667S30.50666667 760.64 30.50666667 683.2v-0.42666667c0-77.54666667 30.4-150.61333333 85.54666666-205.76C171.2 421.86666667 244.26666667 391.46666667 321.81333333 391.46666667h382.61333334c77.54666667 0 150.61333333 30.4 205.76 85.54666666 55.14666667 55.14666667 85.54666667 128.21333333 85.54666666 205.76v0.42666667c0 77.54666667-30.4 150.61333333-85.54666666 205.76s-128.32 85.44-205.86666667 85.44zM321.81333333 466.24c-119.36 0-216.53333333 97.17333333-216.53333333 216.53333333v0.42666667c0 119.36 97.17333333 216.53333333 216.53333333 216.53333333h382.61333334c119.36 0 216.53333333-97.17333333 216.53333333-216.53333333v-0.42666667c0-119.36-97.17333333-216.53333333-216.53333333-216.53333333H321.81333333z" fill="#ac4cfd" p-id="4434"></path><path d="M263.89333333 655.68H412.8v57.38666667H263.89333333z" fill="#ac4cfd" p-id="4435"></path><path d="M311.11466667 756.24106667l0.35093333-148.90666667 57.38666667 0.1344-0.352 148.90666667zM682.77333333 764.69333333c-45.65333333 0-82.88-37.12-82.88-82.88s37.12-82.88 82.88-82.88 82.88 37.12 82.88 82.88-37.12 82.88-82.88 82.88z m0-117.65333333c-19.2 0-34.88 15.68-34.88 34.88s15.68 34.88 34.88 34.88 34.88-15.68 34.88-34.88-15.57333333-34.88-34.88-34.88zM545.70666667 463.78666667L488.53333333 432.74666667l100.05333334-184.42666667H385.81333333l-27.84-49.38666667 86.4-143.36 55.78666667 33.49333334-56.74666667 94.18666666h199.89333334l28.58666666 48z" fill="#ac4cfd" p-id="4436"></path></svg>

+ 12 - 12
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-05 17:55:14
+ * @LastEditTime: 2024-09-06 17:47:17
  * @FilePath: \Game-Backstage-Management-System\src\components\Table.vue
  * @Description: 
  * 
@@ -201,7 +201,6 @@ const queryTableData = () => {
  */
 const resetQueryForm = (formEl: FormInstance | undefined) => {
   if (!formEl) return
-  console.log(formEl)
   clearReactiveData(queryFormData)
   // formEl?.resetFields()
 }
@@ -333,8 +332,7 @@ const initReqConfig = () => {
  * @return {*}
  */
 const tableSortChange = (data: { column: any; prop: string; order: any }) => {
-  let { prop, order } = { ...data }
-  console.log(prop, order)
+  let { order } = { ...data }
   if (order === 'ascending') order = 'asc'
   else if (order === 'descending') order = 'desc'
   else order = ''
@@ -349,7 +347,6 @@ const tableSortChange = (data: { column: any; prop: string; order: any }) => {
  * @return {*}
  */
 const deleteRow = (url: string, filedsInfo: any) => {
-  console.log(url, filedsInfo)
   axiosInstance
     .post(url, { ...filedsInfo })
     .then((data) => {
@@ -378,8 +375,6 @@ onMounted(() => {
   if (!props.openPageQuery) {
     getData()
   }
-
-  console.log(queryFormData)
 })
 </script>
 
@@ -440,7 +435,7 @@ onMounted(() => {
       </div>
     </div>
     <!-- 分割线 -->
-    <el-divider class="partition" content-position="center" />
+    <!-- <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')">
@@ -487,7 +482,7 @@ onMounted(() => {
             align="center"
             show-overflow-tooltip
             v-if="item.isShow"
-            :sortable="item.needSort ? 'custorm' : false"
+            :sortable="item.needSort === true ? 'custorm' : false"
           >
             <template v-slot="scope">
               <!-- tag类 -->
@@ -642,9 +637,12 @@ onMounted(() => {
 <style scoped>
 .tableContent {
   margin: 0 auto;
-  width: 98%;
+  width: 100%;
 
   /* height: 100%; */
+  box-shadow:
+    0 4px 8px 0 rgba(0, 0, 0, 0.02),
+    0 1px 3px 0 rgba(0, 0, 0, 0.02);
 }
 .filterBox,
 .tableBox {
@@ -668,7 +666,7 @@ onMounted(() => {
   display: flex;
   align-items: center;
   color: black;
-  font-size: 20px;
+  font-size: 16px;
   font-weight: bold;
   /* background-color: lightblue; */
   /* margin-bottom: 1%; */
@@ -748,8 +746,10 @@ onMounted(() => {
 }
 
 .userTablePaginationBox {
+  box-sizing: border-box;
   width: 98%;
-  margin: 0.5% auto;
+  margin: 0% auto;
+  padding: 1% 0;
   display: flex;
   justify-content: center;
 }

+ 1 - 1
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-08-28 12:25:54
+ * @LastEditTime: 2024-09-06 11:52:17
  * @FilePath: \Game-Backstage-Management-System\src\components\dataAnalysis\DropDownSelection.vue
  * @Description: 下拉选择框,可用于分类字段或者切换平台等
  * 

+ 109 - 7
src/components/dataAnalysis/HeaderCard.vue

@@ -7,13 +7,16 @@
 -->
 
 <script setup lang="ts">
+import router from '@/router'
 import DropDownSelection from './DropDownSelection.vue'
 import type { DropDownInfo, HeaderCardProps } from '@/types/dataAnalysis'
-import { onMounted, ref } from 'vue'
+import { computed, onMounted, reactive, ref, watch } from 'vue'
 
 const props = withDefaults(defineProps<HeaderCardProps>(), {
   openDateSelect: false,
-  defaultPf: 'web'
+  defaultPf: 'web',
+  needPfSelect: true,
+  needBreadcrumb: false
 })
 
 const emits = defineEmits(['changePf', 'changeDate'])
@@ -92,16 +95,98 @@ const disableDate = (time: Date) => {
   return time.getTime() > Date.now()
 }
 
+const breadcrumbList = reactive<
+  Array<{
+    title: string
+    pathName: string
+  }>
+>([])
+
+// 是否可点击
+const breadcrumbCanClick = computed(() => breadcrumbList.length > 1)
+
+// 返回总览
+const goBack = (index: number) => {
+  if (breadcrumbCanClick) {
+    router.push(breadcrumbList[index].pathName).then(() => {
+      breadcrumbList.splice(index + 1, breadcrumbList.length - 1)
+    })
+  }
+}
+
+/**
+ * @description: 添加导航栏的信息
+ * @param {*} title 标题
+ * @param {*} pathName 对应的路由name
+ * @return {*}
+ */
+const addPath = (title: string, pathName: string) => {
+  breadcrumbList.push({ title, pathName })
+}
+
+/**
+ * @description: 当跳转到其他页面的时候,就需要清除掉这里的面包屑导航
+ * @return {*}
+ */
+const clearBreadcrumb = () => {
+  let nowName = router.currentRoute.value.name
+  // 如果现在是第一个导航,那么清除掉,这个是为了当直接点击左侧导航栏进入table页的时候可以清空
+  // 第二个条件是当切换到其他导航的时候,也给他清空掉
+  if (
+    nowName === breadcrumbList[0].pathName ||
+    breadcrumbList.every((item) => item.pathName !== nowName)
+  ) {
+    breadcrumbList.splice(1, breadcrumbList.length - 1)
+  }
+}
+
+const watchRoute = watch(
+  () => [router.currentRoute.value],
+  () => {
+    clearBreadcrumb()
+  },
+  { deep: true }
+)
+
+if (!props.needBreadcrumb) watchRoute()
+
+defineExpose({
+  addPath,
+  clearBreadcrumb
+})
+
 onMounted(() => {
+  breadcrumbList.push({
+    title: props.title,
+    pathName: router.currentRoute.value.name as string
+  })
   dateChange(selectDate.value)
 })
 </script>
 
 <template>
   <div class="headerCard">
-    <span class="title">{{ title }}</span>
-    <el-divider direction="vertical" />
-    <div class="selectBox">
+    <p class="titleBox">
+      <span
+        @click="breadcrumbCanClick ? goBack(index) : ''"
+        v-for="(item, index) in breadcrumbList"
+        :class="[
+          index === breadcrumbList.length - 1 ? 'activeCrumbs' : 'noActive',
+          breadcrumbCanClick ? 'canClick' : 'unAble'
+        ]"
+      >
+        <span v-if="index === 0" class="titleContent">
+          {{ item.title }}
+        </span>
+        <span v-else>
+          <span class="divLine">/</span>
+          <span class="titleContent">{{ item.title }}</span>
+        </span>
+      </span>
+    </p>
+
+    <div class="selectBox" v-if="props.needPfSelect">
+      <el-divider direction="vertical" />
       <div class="selectItem">
         <DropDownSelection
           @changeSelect="changePf"
@@ -140,12 +225,25 @@ onMounted(() => {
   background-color: white;
 }
 
-.title {
-  font-size: 16px;
+.canClick {
+  cursor: pointer;
+}
+
+.unAble {
+  cursor: default;
+}
+
+.activeCrumbs {
+  font-size: 18px;
   color: #17233d;
   font-weight: 600;
 }
 
+.noActive {
+  font-size: 16px;
+  color: rgba(23, 35, 61, 0.55);
+}
+
 .selectBox {
   display: inline-flex;
 }
@@ -185,4 +283,8 @@ onMounted(() => {
 .disableSelect:hover {
   cursor: no-drop;
 }
+
+.divLine {
+  margin: 0 5px;
+}
 </style>

+ 9 - 4
src/components/dataAnalysis/TemporalTrend.vue

@@ -16,7 +16,13 @@ import StatisticText from './StatisticText.vue'
 
 import axiosInstance from '@/utils/axios/axiosInstance'
 
-const props = defineProps<TemporalTrendProps>()
+const props = withDefaults(defineProps<TemporalTrendProps>(), {
+  type: 1,
+  needChangeExpress: true,
+  selectExpressForm: 1,
+  needTable: true,
+  needCharts: true
+})
 const activeTab = ref<string>('') // 激活的Tab
 const iconSize = ref(20) // 图标的尺寸
 
@@ -101,7 +107,8 @@ const createTableField = () => {
     tableFieldsInfo.push({
       name: key,
       cnName: value as string,
-      isShow: true
+      isShow: true,
+      needSort: false
     })
   }
 }
@@ -181,7 +188,6 @@ const setCacheData = (isUse: boolean = false) => {
     if (cachedData) {
       ;({ tableFieldsInfo, tableDataList, paginationConfig, statisticFieldsInfo, chartInfo } =
         JSON.parse(JSON.stringify(cachedData)))
-      console.log(statisticFieldsInfo)
     } else {
       console.warn('No cached data found for the current tab.')
     }
@@ -313,7 +319,6 @@ watch(
 )
 
 onMounted(() => {
-  // if (!props.waitTimeSelect) getData(1)
   getData(1)
 
   if (props.tabInfo) activeTab.value = props.tabInfo[0].name

+ 1 - 1
src/components/echarts/TimeLineChart.vue

@@ -190,7 +190,7 @@ onMounted(() => {
      * @return {*}
      */
     // window.addEventListener('resize', debounceFunc(changeSize, 500))
-    const debounceChangeSize = debounceFunc(changeSize, 500)
+    const debounceChangeSize = debounceFunc(changeSize, 200)
     const ro = new ResizeObserver(debounceChangeSize)
     ro.observe(chart.value)
   })

+ 2 - 1
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-05 17:12:43
+ * @LastEditTime: 2024-09-06 10:21:30
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useRequest.ts
  * @Description:
  *
@@ -22,6 +22,7 @@ export function useRequest() {
   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`,
 
     getGameTable: `${baseIp}/user/getGidConfig`, // 获取游戏列表
     getUserTable: `${baseIp}/user/userList`, // 获取用户列表

+ 1 - 1
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-08-31 17:35:14
+ * @LastEditTime: 2024-09-06 15:52:27
  * @FilePath: \Game-Backstage-Management-System\src\hooks\useTable.ts
  * @Description:
  *

+ 1 - 2
src/router/appManage.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:24:58
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-04 17:53:20
+ * @LastEditTime: 2024-09-06 14:54:04
  * @FilePath: \Game-Backstage-Management-System\src\router\appManage.ts
  * @Description:
  *
@@ -46,7 +46,6 @@ export default [
           {
             path: 'eventTable',
             name: 'EventTable',
-
             component: () => import('@/views/AppManage/EventMangeTable.vue')
           }
         ]

+ 26 - 2
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-04 09:17:00
+ * @LastEditTime: 2024-09-06 17:25:34
  * @FilePath: \Game-Backstage-Management-System\src\router\home.ts
  * @Description:
  *
@@ -54,7 +54,7 @@ export default [
         ]
       },
       {
-        path: 'infoManage',
+        path: 'dataAnalysis',
         cnName: '数据分析',
         icon: 'DataAnalysis',
         showChild: true,
@@ -78,6 +78,30 @@ export default [
               activeMenu: 'userTrendView',
               needKeepAlive: true
             }
+          },
+          {
+            path: 'eventAnalysisView',
+            name: 'EventAnalysisView',
+            cnName: '事件分析',
+            showChild: false,
+            redirect: '/home/dataAnalysis/eventAnalysisView/eventAnalysisTable',
+            component: () => import('@/views/Home/Analysis/EventAnalysisView.vue'),
+            meta: {
+              activeMenu: 'eventAnalysisView',
+              needKeepAlive: true
+            },
+            children: [
+              {
+                path: 'eventAnalysisTable',
+                name: 'EventAnalysisTable',
+                component: () => import('@/views/Home/Analysis/EventAnalysisTable.vue')
+              },
+              {
+                path: 'eventAnalysisDetail',
+                name: 'EventAnalysisDetail',
+                component: () => import('@/views/Home/Analysis/EventAnalysisDetail.vue')
+              }
+            ]
           }
         ]
       }

+ 1 - 3
src/router/index.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:06:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-05 17:23:03
+ * @LastEditTime: 2024-09-06 16:59:23
  * @FilePath: \Game-Backstage-Management-System\src\router\index.ts
  * @Description:
  *
@@ -17,8 +17,6 @@ import AppManage from './appManage'
 
 const routes = [
   ...LoginRoutes,
-  // ...HomeRoutes,
-  // ...AppManage,
   {
     path: '/index',
     name: 'Index',

+ 10 - 10
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-03 11:27:57
+ * @LastEditTime: 2024-09-06 09:47:13
  * @FilePath: \Game-Backstage-Management-System\src\types\dataAnalysis.ts
  * @Description:用于dataAnalysis相关组件的type
  *
@@ -54,7 +54,7 @@ export interface StaticDataInfo {
 export interface TabInfo {
   name: string // tab的字段
   tabTitle: string // tab的标题
-  type: number // 到时候需要获取数据的url
+  type: number // 请求参数中,需要的字段,用来区分不同的请求
 }
 
 /**
@@ -82,18 +82,16 @@ export interface TrendTableField {
 
 // 时间趋势组件所需要的props
 export interface TemporalTrendProps {
-  waitTimeSelect: boolean // 是否需要等待时间组件选择完成后请求
-
   tabInfo?: Array<TabInfo> // 用于切换的tab的信息
 
-  type: number // 趋势组件的类型,1为小时段,2为日期统计
-  needChangeExpress: boolean // 是否需要切换表格/图表
-  selectExpressForm: number // 默认的表现形式,1为图表,2为表格
+  type?: number // 趋势组件的类型,1为小时段,2为日期统计
+  needChangeExpress?: boolean // 是否需要切换表格/图表
+  selectExpressForm?: number // 默认的表现形式,1为图表,2为表格
 
-  needTable: boolean // 是否需要表格
-  tableFieldsInfo: TrendTableField // 表格的字段信息
+  needTable?: boolean // 是否需要表格
+  tableFieldsInfo: TrendTableField // 表格的字段信息,必填,因为legend的字段也依赖这里
 
-  needCharts: boolean // 是否需要图表
+  needCharts?: boolean // 是否需要图表
 
   resDataFieldsInfo: ResDataFieldInfo // 返回的数据中每个字段的分类信息
   requestConfig: ReqConfig // 请求参数配置,需要监听,一旦变化,需要重新获取数据
@@ -107,6 +105,8 @@ export interface HeaderCardProps {
   title: string // title信息
   defaultPf?: string // 默认选择的pf
   openDateSelect?: boolean // 是否开启时间选择
+  needPfSelect?: boolean // 是否需要平台选择
+  needBreadcrumb?: boolean // 是否需要面包屑导航
 }
 
 // 趋势图组件需要的信息

+ 2 - 2
src/utils/axios/axiosInstance.ts

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 17:18:52
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-08-31 12:55:24
+ * @LastEditTime: 2024-09-05 18:07:10
  * @FilePath: \Game-Backstage-Management-System\src\utils\axios\axiosInstance.ts
  * @Description:
  *
@@ -68,7 +68,7 @@ axiosInstance.interceptors.response.use(
       message: '服务器错误,请稍后再试',
       duration: 1500
     })
-    router.push('/login')
+
     return Promise.reject(error)
   }
 )

+ 10 - 1
src/utils/table/table.ts

@@ -1,7 +1,6 @@
 import { useTableStore } from '@/stores/useTable'
 import { useRequest } from '@/hooks/useRequest'
 import axiosInstance from '../axios/axiosInstance'
-
 const { AllApi } = useRequest()
 
 // 拿到所有游戏的信息
@@ -35,3 +34,13 @@ export const getAllGameInfo = async () => {
     throw new Error('获取游戏列表失败')
   }
 }
+
+/**
+ * @description: 根据路由的name判断这个组件是否需要监听事件
+ * @param {string} routeName 路由name,类型是来自RouteRecordNameGeneric类型
+ * @return {*}
+ */
+export const shouldListenToEvent = (routeName: string | symbol | undefined, mathName: string) => {
+  console.log(routeName, mathName)
+  return routeName === mathName
+}

+ 55 - 30
src/views/AppManage/BaseInfoView.vue

@@ -1,55 +1,62 @@
 <script setup lang="ts">
 import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+
 import { reactive, onMounted, ref } 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'
+
+const { AllApi } = useRequest()
+const { selectInfo } = useCommonStore()
 
 interface AppInfo {
-  name: string
-  pf: string
-  appType: string
-  createDate: string
-  AppletsID: string
-  AppID: string
+  gameName: string
+  gid: string
+  ttAppid: string // 抖音App ID
+  ttSecret: string // 抖音App Secret
+  wxAppid: string // 微信App ID
+  wxSecret: string // 微信App Secret
   img: string
 }
 
 // 返回的APP信息
 const appInfo = reactive<AppInfo>({
-  img: '/img/default/defaultGameImg.svg',
-  name: '消消乐',
-  pf: '微信',
-  appType: '游戏娱乐',
-  createDate: '2024-1-1',
-  AppletsID: 'ASJDFJAHSJDF',
-  AppID: 'A12312312412JAHSJDF'
+  gameName: '',
+  gid: '',
+  ttAppid: '',
+  ttSecret: '',
+  wxAppid: '',
+  wxSecret: '',
+  img: '/img/default/defaultGame.svg'
 })
 
-// 信息对应的字段
 const fieldsInfo = {
-  name: '应用名称',
-  pf: '应用平台',
-  appType: '游戏平台',
-  createDate: '创建日期',
-  AppletsID: '小程序ID',
-  AppID: 'App ID'
+  gameName: '游戏名称',
+
+  ttAppid: '抖音App ID',
+  ttSecret: '抖音App Secret',
+  wxAppid: '微信App ID',
+  wxSecret: '微信App Secret',
+  gid: '游戏ID'
 }
 
 // 资源的加载路径
 const resourceInfo: Record<string, string> = {
-  defaultHead: `/img/default/defaultHead.png`
+  defaultHead: `/img/default/defaultGame.svg`
 }
 
 // 使用blob的资源路径信息
 const blobUrlInfo = reactive<Record<string, string>>({})
 
-const AppID = ref<Array<HTMLSpanElement> | null>(null)
+const Gid = ref<Array<HTMLSpanElement> | null>(null)
 
-const copyAppID = () => {
-  copyText(appInfo.AppID).then(() => {
-    if (AppID.value) {
+const copyGid = () => {
+  copyText(appInfo.gid).then(() => {
+    if (Gid.value) {
       const range = document.createRange()
-      range.selectNodeContents(AppID.value[0])
+      range.selectNodeContents(Gid.value[0])
 
       const selection = window.getSelection()
       if (selection) {
@@ -60,23 +67,41 @@ const copyAppID = () => {
   })
 }
 
+const getGameInfo = () => {
+  axiosInstance
+    .post(AllApi.getGameTable, {
+      appSecret: '6YJSuc50uJ18zj45'
+    })
+    .then((data) => {
+      let info = data.data
+      let nowGame = info.find((item: any) => {
+        console.log(item.gid, selectInfo.gid)
+        return item.gid === selectInfo.gid
+      })
+      console.log(nowGame)
+      Object.assign(appInfo, nowGame)
+    })
+}
+
 onMounted(() => {
   // 去加载所有需要的资源
   initLoadResouce(resourceInfo).then((data) => {
     Object.assign(blobUrlInfo, data)
   })
+
+  getGameInfo()
 })
 </script>
 
 <template>
   <div class="baseInfo">
-    <HeaderCard :title="'基本信息'"></HeaderCard>
+    <HeaderCard :title="'基本信息'" :need-pf-select="false"></HeaderCard>
     <div class="body">
       <div class="info">
         <div class="infoItem" v-for="[key, val] in Object.entries(fieldsInfo)">
           <span class="itmeTitle">{{ val }}</span>
           <span class="text" :ref="key">{{ appInfo[key as keyof AppInfo] }}</span>
-          <span class="copy" v-if="key === 'AppID'" @click="copyAppID">复制</span>
+          <span class="copy" v-if="key === 'gid'" @click="copyGid">复制</span>
         </div>
       </div>
       <div class="photo">
@@ -135,7 +160,7 @@ onMounted(() => {
 
 .itmeTitle {
   display: inline-block;
-  width: 80px;
+  width: 110px;
   text-align: left;
   padding-right: 0;
   font-size: 14px;
@@ -147,7 +172,7 @@ onMounted(() => {
   font-weight: 600;
   color: #1c2438;
   font-size: 14px;
-  margin-left: 106px;
+  margin-left: 30px;
   line-height: 20px;
 }
 

+ 4 - 0
src/views/AppManage/EventDetailsView.vue

@@ -540,4 +540,8 @@ onMounted(() => {})
 .eventForm {
   padding: 24px;
 }
+
+.operationBtn {
+  margin-right: 5px;
+}
 </style>

+ 38 - 50
src/views/AppManage/EventManageView.vue

@@ -2,23 +2,26 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-09-02 17:57:15
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-05 12:28:01
+ * @LastEditTime: 2024-09-06 17:47:59
  * @FilePath: \Game-Backstage-Management-System\src\views\AppManage\EventManageView.vue
  * @Description: 
  * 
 -->
 <script setup lang="ts">
-import { reactive, onMounted, computed } from 'vue'
+import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+import { shouldListenToEvent } from '@/utils/table/table'
+import { onMounted, ref } from 'vue'
 
-import router from '@/router'
-const breadcrumbList = reactive<Array<string>>([])
+const headerCard = ref<typeof HeaderCard>()
 
-// 是否可点击
-const breadcrumbCanClick = computed(() => breadcrumbList.length > 0)
-
-// 返回总览
-const goBack = () => {
-  router.push('/appManage/EventManage')
+/**
+ * @description: 进入详情页,触发headercard的添加事件,增加一个面包屑导航
+ * @param {*} info 传入的信息
+ * @return {*}
+ */
+const headerAddPath = (info: any) => {
+  const { name, pathName } = info
+  headerCard.value?.addPath(name, pathName)
 }
 
 onMounted(() => {})
@@ -27,18 +30,29 @@ onMounted(() => {})
 <template>
   <div class="enventManage">
     <div class="breadcrumbBox">
-      <span
-        :class="['breadcrumbItem', breadcrumbCanClick ? 'noActived' : 'actived']"
-        @click="goBack"
-      >
-        事件管理
-      </span>
-      <span class="breadcrumbItem itemActived" v-for="item in breadcrumbList">
-        <span class="separator">/</span> {{ item }}
-      </span>
+      <HeaderCard
+        ref="headerCard"
+        :need-breadcrumb="true"
+        :title="'事件管理'"
+        :need-pf-select="false"
+      ></HeaderCard>
     </div>
     <div class="eventTableBox">
-      <router-view></router-view>
+      <!-- 监听表格的跳转事件 -->
+      <router-view v-slot="{ Component, route }">
+        <!-- 是eventtable组件就去监听enterdetail事件 -->
+        <!-- 注释也不要写到keep-alive里面,会报错 -->
+        <keep-alive>
+          <component
+            :is="Component"
+            v-if="shouldListenToEvent(route.name, 'EventTable')"
+            @enterDetail="headerAddPath"
+          />
+        </keep-alive>
+
+        <!-- 如果不是正常渲染其他组件 -->
+        <component v-if="route.name !== 'EventTable'" :is="Component" />
+      </router-view>
     </div>
   </div>
 </template>
@@ -58,38 +72,12 @@ onMounted(() => {})
   font-size: 16px;
   color: #17233d;
   font-weight: 600;
-  padding: 0 24px;
+  /* padding: 0 24px; */
   line-height: 64px;
 }
 
-.separator {
-  color: rgba(23, 35, 61, 0.55);
-}
-
-.itemActived {
-  font-size: 16px;
-  color: rgb(23, 35, 61);
-  font-weight: 500;
-}
-
-.actived {
-  font-size: 16px;
-  color: #17233d !important;
-  font-weight: 600;
-  line-height: 64px;
-  cursor: auto;
-}
-
-.noActived {
-  line-height: 64px;
-  font-size: 16px;
-  color: rgba(23, 35, 61, 0.55);
-  cursor: pointer;
-}
-
-.breadcrumbItem {
-  line-height: 64px;
-  font-size: 16px;
-  color: rgba(23, 35, 61, 0.55);
+.eventTableBox {
+  box-sizing: border-box;
+  padding: 0px 24px;
 }
 </style>

+ 16 - 9
src/views/AppManage/EventMangeTable.vue

@@ -19,6 +19,9 @@ const { AllApi } = useRequest()
 
 const eventDialog = ref()
 
+// 主要为了给面包屑导航提供信息
+const emits = defineEmits(['enterDetail'])
+
 // 表格分页设置
 const pagingConfig = reactive<TablePaginationSetting>({
   limit: 20,
@@ -200,12 +203,17 @@ const dialogInfo = reactive<DialogConfig>({
 })
 
 /**
- * @description: 查看详情
+ * @description: 查看详情,同时触发事件给面包屑导航
  * @param {*} row 行信息
  * @return {*}
  */
 const viewDetails = (row: any) => {
   if (row.id) {
+    emits('enterDetail', {
+      name: row.actionName,
+      pathName: 'EventDetail'
+    })
+
     router.push({
       name: 'EventDetail',
       query: { id: row.id }
@@ -242,14 +250,9 @@ onMounted(() => {})
       <template #tableOperation>
         <el-table-column label="操作" align="center">
           <template #default="scope">
-            <el-button
-              size="small"
-              type="primary"
-              @click="viewDetails(scope.row)"
-              class="operationBtn"
+            <el-text class="operationBtn" type="primary" @click="viewDetails(scope.row)"
+              >详情</el-text
             >
-              详情
-            </el-button>
           </template>
         </el-table-column>
       </template>
@@ -260,4 +263,8 @@ onMounted(() => {})
   </div>
 </template>
 
-<style scoped></style>
+<style scoped>
+.operationBtn {
+  cursor: pointer;
+}
+</style>

+ 187 - 0
src/views/Home/Analysis/EventAnalysisDetail.vue

@@ -0,0 +1,187 @@
+<!--
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-08-27 17:11:23
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-09-06 11:34:04
+ * @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 { reactive } from 'vue'
+import { useRequest } from '@/hooks/useRequest'
+
+import type {
+  ReqConfig,
+  ResDataFieldInfo,
+  TabInfo,
+  TemporalTrendProps,
+  TrendTableField
+} from '@/types/dataAnalysis'
+import {
+  type TablePaginationSetting,
+  type TableFieldInfo,
+  FieldSpecialEffectType
+} from '@/types/table'
+
+const { AllApi } = useRequest()
+
+// 表格分页设置
+const pagingConfig = reactive<TablePaginationSetting>({
+  limit: 20,
+  currentPage: 1,
+  total: 0,
+  pagesizeList: [20, 30]
+})
+
+// 表格字段信息
+const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
+  {
+    name: 'eventAlias',
+    cnName: '事件别名',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'eventId',
+    cnName: '事件ID',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'platform',
+    cnName: '平台',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'usageStatus',
+    cnName: '用量状态',
+    isShow: true,
+    needSort: false,
+    specialEffect: {
+      type: FieldSpecialEffectType.STATE,
+      othnerInfo: {
+        text: ['启用', '禁用']
+      }
+    }
+  },
+  {
+    name: 'eventCount',
+    cnName: '事件数',
+    isShow: true,
+    needSort: true
+  },
+  {
+    name: 'eventDevices',
+    cnName: '事件达成设备数',
+    isShow: true,
+    needSort: true
+  },
+  {
+    name: 'activeDeviceRate',
+    cnName: '活跃设备发生率',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'eventPerStartup',
+    cnName: '每启动发生次数',
+    isShow: true,
+    needSort: true
+  }
+])
+
+// 表格请求配置
+const tableRequestConfig = reactive<ReqConfig>({
+  url: AllApi.mockEvent,
+  otherOptions: {}
+})
+
+// 图表信息
+// 返回数据中字段分类
+const resDataField: ResDataFieldInfo = {
+  xAxis: 'eventCount', // X轴字段为日期
+  values: ['eventCount'] // Y轴字段为事件数
+}
+
+// 图表中表格的字段信息
+const chartTableField: TrendTableField = {
+  eventCount: '事件数'
+}
+
+// 请求参数配置
+const chartReqConfig = reactive<ReqConfig>({
+  url: AllApi.mockDate,
+  otherOptions: {}
+})
+
+// 图表用于切换的tab
+const tabInfo: Array<TabInfo> = [
+  {
+    name: 'eventCount',
+    tabTitle: '事件数',
+    type: 1
+  },
+  {
+    name: 'eventCount2',
+    tabTitle: '事件数2',
+    type: 2
+  }
+]
+
+const chartProps = reactive<TemporalTrendProps>({
+  title: '事件趋势',
+  type: 2,
+  tableFieldsInfo: chartTableField,
+  requestConfig: chartReqConfig,
+  resDataFieldsInfo: resDataField,
+  tabInfo
+})
+</script>
+<template>
+  <div class="eventDetail">
+    <div class="chart">
+      <TemporalTrend
+        :title="chartProps.title"
+        :type="chartProps.type"
+        :table-fields-info="chartProps.tableFieldsInfo"
+        :request-config="chartProps.requestConfig"
+        :res-data-fields-info="chartProps.resDataFieldsInfo"
+        :tab-info="chartProps.tabInfo"
+        :need-change-express="false"
+      ></TemporalTrend>
+    </div>
+    <div class="table">
+      <div class="boxHeader">
+        <span class="headerTitle">事件详情</span>
+      </div>
+      <el-divider />
+      <Table
+        :pagination-config="pagingConfig"
+        :table-fields-info="tableFieldsInfo"
+        :request-config="tableRequestConfig"
+      ></Table>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.eventDetail {
+  width: 100%;
+  background-color: white;
+}
+.table {
+  padding: 0 24px;
+  box-sizing: border-box;
+}
+
+.headerTitle {
+  font-size: 14px;
+  color: #1c2438;
+  font-weight: 600;
+}
+</style>

+ 146 - 0
src/views/Home/Analysis/EventAnalysisTable.vue

@@ -0,0 +1,146 @@
+<script setup lang="ts">
+import Table from '@/components/Table.vue'
+
+import { reactive } from 'vue'
+import { useRequest } from '@/hooks/useRequest'
+
+import type { ReqConfig } from '@/types/dataAnalysis'
+import {
+  type TablePaginationSetting,
+  type TableFieldInfo,
+  FieldSpecialEffectType
+} from '@/types/table'
+import router from '@/router'
+
+const { AllApi } = useRequest()
+
+// 主要为了给面包屑导航提供信息
+const emits = defineEmits(['enterDetail'])
+
+// 表格分页设置
+const pagingConfig = reactive<TablePaginationSetting>({
+  limit: 20,
+  currentPage: 1,
+  total: 0,
+  pagesizeList: [20, 30]
+})
+
+// 表格字段信息
+const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
+  {
+    name: 'eventAlias',
+    cnName: '事件别名',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'eventId',
+    cnName: '事件ID',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'platform',
+    cnName: '平台',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'usageStatus',
+    cnName: '用量状态',
+    isShow: true,
+    needSort: false,
+    specialEffect: {
+      type: FieldSpecialEffectType.STATE,
+      othnerInfo: {
+        text: ['启用', '禁用']
+      }
+    }
+  },
+  {
+    name: 'eventCount',
+    cnName: '事件数',
+    isShow: true,
+    needSort: true
+  },
+  {
+    name: 'eventDevices',
+    cnName: '事件达成设备数',
+    isShow: true,
+    needSort: true
+  },
+  {
+    name: 'activeDeviceRate',
+    cnName: '活跃设备发生率',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'eventPerStartup',
+    cnName: '每启动发生次数',
+    isShow: true,
+    needSort: true
+  }
+])
+
+// 表格请求配置
+const requestConfig = reactive<ReqConfig>({
+  url: AllApi.mockEvent,
+  otherOptions: {}
+})
+
+/**
+ * @description: 查看详情
+ * @param {*} row 行信息
+ * @return {*}
+ */
+const viewDetails = (row: any) => {
+  emits('enterDetail', {
+    name: row.eventAlias,
+    pathName: 'EventAnalysisDetail'
+  })
+  router.push({
+    name: 'EventAnalysisDetail',
+    query: {}
+  })
+}
+</script>
+<template>
+  <div class="eventTable">
+    <div class="content">
+      <Table
+        :need-rowindex="false"
+        :request-config="requestConfig"
+        :open-page-query="false"
+        :pagination-config="pagingConfig"
+        :table-fields-info="tableFieldsInfo"
+        :need-left-tools="false"
+      >
+        <template #tableOperation>
+          <el-table-column label="操作" align="center">
+            <template #default="scope">
+              <el-text class="operationBtn" type="primary" @click="viewDetails(scope.row)"
+                >详情</el-text
+              >
+            </template>
+          </el-table-column>
+        </template>
+      </Table>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.eventTable {
+  width: 100%;
+  background-color: white;
+}
+
+.content {
+  width: 100%;
+}
+
+.operationBtn {
+  cursor: pointer;
+}
+</style>

+ 73 - 0
src/views/Home/Analysis/EventAnalysisView.vue

@@ -0,0 +1,73 @@
+<!--
+ * @Author: fxs bjnsfxs@163.com
+ * @Date: 2024-08-27 17:11:23
+ * @LastEditors: fxs bjnsfxs@163.com
+ * @LastEditTime: 2024-09-06 17:49:01
+ * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\EventAnalysisView.vue
+ * @Description: 
+ * 
+-->
+<script setup lang="ts">
+import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
+import { shouldListenToEvent } from '@/utils/table/table'
+import { reactive, ref } from 'vue'
+
+import type { HeaderCardProps } from '@/types/dataAnalysis'
+
+const headerCard = ref()
+
+// 头部组件需要的 props
+const headerProps = reactive<HeaderCardProps>({
+  title: '事件分析',
+  defaultPf: 'web',
+  openDateSelect: true
+})
+
+/**
+ * @description: 进入详情页,触发headercard的添加事件,增加一个面包屑导航
+ * @param {*} info 传入的信息
+ * @return {*}
+ */
+const headerAddPath = (info: any) => {
+  const { name, pathName } = info
+  headerCard.value?.addPath(name, pathName)
+}
+</script>
+<template>
+  <div class="eventAnalysis">
+    <div class="header">
+      <HeaderCard
+        ref="headerCard"
+        :title="headerProps.title"
+        :default-pf="headerProps.defaultPf"
+        :open-date-select="headerProps.openDateSelect"
+        :need-breadcrumb="true"
+      ></HeaderCard>
+    </div>
+    <div class="content">
+      <!-- 监听表格的跳转事件 -->
+      <router-view v-slot="{ Component, route }">
+        <!-- 是eventtable组件就去监听enterdetail事件 -->
+        <keep-alive>
+          <component
+            :is="Component"
+            v-if="shouldListenToEvent(route.name, 'EventAnalysisTable')"
+            @enterDetail="headerAddPath"
+          />
+        </keep-alive>
+        <!-- 如果不是正常渲染其他组件 -->
+        <component v-if="route.name !== 'EventAnalysisTable'" :is="Component" />
+      </router-view>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.eventAnalysis {
+  width: 98%;
+  margin: 1% auto;
+  box-sizing: border-box;
+  /* background-color: white; */
+  /* border: 1px solid #e5e6eb; */
+}
+</style>

+ 2 - 1
src/views/Home/Analysis/KeepView.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-27 17:11:23
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-05 17:06:19
+ * @LastEditTime: 2024-09-05 18:08:29
  * @FilePath: \Game-Backstage-Management-System\src\views\Home\Analysis\KeepView.vue
  * @Description: 
  * 
@@ -183,6 +183,7 @@ const getTableData = () => {
         })
     })
     .catch((err) => {
+      loading.value = false
       throw new Error(err)
     })
 }

+ 25 - 24
src/views/Index.vue

@@ -2,7 +2,7 @@
  * @Author: fxs bjnsfxs@163.com
  * @Date: 2024-08-20 14:06:49
  * @LastEditors: fxs bjnsfxs@163.com
- * @LastEditTime: 2024-09-05 17:28:40
+ * @LastEditTime: 2024-09-06 17:38:45
  * @FilePath: \Game-Backstage-Management-System\src\views\Index.vue
  * @Description: 
  * 
@@ -26,6 +26,7 @@ const isCollapse = ref(false)
 const navBarSelect = ref<string>('Home')
 const siderBarOpened = ref<Array<string>>(['数据总览'])
 const siderBar = ref()
+const loadingState = ref(false) // 用来标记必要信息的加载状态
 
 // 路由信息,同时也是侧边栏生成的依据信息
 const menuList = reactive<Array<any>>([])
@@ -75,9 +76,6 @@ const gameSelectInfo = reactive<DropDownInfo>({
   optionsList: []
 })
 
-// 游戏信息是否加载成功
-const gameinfoLoad = ref(false)
-
 /**
  * @description: 更新整个页面的游戏选择
  * @param {*} gid 游戏id
@@ -97,29 +95,12 @@ const changeNavBar = (val: string) => {
 
   router.push(`/${val}`)
   createdMenuList()
-  console.log(menuList)
   let title = navBarMenuList.find((item) => item.name === val)?.title
   if (title) {
     siderBarOpened.value.splice(0, 1, title)
   }
 }
 
-/**
- * @description: 获取所有游戏列表
- * @return {*}
- */
-getAllGameInfo().then((data) => {
-  if (data) {
-    data.map((item) => {
-      gameSelectInfo.optionsList.push({
-        value: item.gid,
-        label: item.gameName
-      })
-    })
-  }
-  gameinfoLoad.value = true
-})
-
 // 资源的加载路径
 const resourceInfo: Record<string, string> = {
   logo: `/img/logo.svg`,
@@ -167,6 +148,26 @@ watch(
   }
 )
 
+/**
+ * @description: 获取所有游戏列表
+ * @return {*}
+ */
+getAllGameInfo().then((data) => {
+  if (data) {
+    data.map((item) => {
+      gameSelectInfo.optionsList.push({
+        value: item.gid,
+        label: item.gameName
+      })
+    })
+    gameSelectInfo.defaultSelect = data[0].gid
+    changeGame(data[0].gid)
+    loadingState.value = true
+  } else {
+    throw new Error('游戏信息获取失败')
+  }
+})
+
 onMounted(() => {
   // 去加载所有需要的资源
   initLoadResouce(resourceInfo).then((data) => {
@@ -177,7 +178,7 @@ onMounted(() => {
 
 <template>
   <el-config-provider :locale="zhCn">
-    <div class="body">
+    <div class="body" v-if="loadingState">
       <div class="navBarBox">
         <div class="logoBox">
           <el-image :fit="'fill'" class="logoImg" :src="blobUrlInfo.logo"></el-image>
@@ -276,13 +277,13 @@ onMounted(() => {
           <keep-alive>
             <component
               :is="Component"
-              :key="route.path"
+              :key="route.meta.activeMenu"
               v-if="route.meta.needKeepAlive == true"
             ></component>
           </keep-alive>
           <component
             :is="Component"
-            :key="route.path"
+            :key="route.meta.activeMenu"
             v-if="route.meta.needKeepAlive == false"
           ></component>
         </router-view>