Răsfoiți Sursa

feat(事件分析页面): 新增展开行功能

fxs 1 lună în urmă
părinte
comite
f49ad134d6

+ 23 - 23
package-lock.json

@@ -2467,9 +2467,9 @@
       }
     },
     "node_modules/axios": {
-      "version": "1.7.4",
-      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.4.tgz",
-      "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
+      "version": "1.8.4",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.8.4.tgz",
+      "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
       "license": "MIT",
       "dependencies": {
         "follow-redirects": "^1.15.6",
@@ -6157,9 +6157,9 @@
       "license": "MIT"
     },
     "node_modules/nanoid": {
-      "version": "3.3.7",
-      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "version": "3.3.11",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
       "funding": [
         {
           "type": "github",
@@ -6740,9 +6740,9 @@
       }
     },
     "node_modules/picocolors": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.1.tgz",
-      "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
       "license": "ISC"
     },
     "node_modules/picomatch": {
@@ -6863,9 +6863,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.4.41",
-      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.41.tgz",
-      "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
+      "version": "8.5.3",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz",
+      "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
       "funding": [
         {
           "type": "opencollective",
@@ -6882,9 +6882,9 @@
       ],
       "license": "MIT",
       "dependencies": {
-        "nanoid": "^3.3.7",
-        "picocolors": "^1.0.1",
-        "source-map-js": "^1.2.0"
+        "nanoid": "^3.3.8",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
       },
       "engines": {
         "node": "^10 || ^12 || >=14"
@@ -7795,9 +7795,9 @@
       }
     },
     "node_modules/source-map-js": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz",
-      "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
       "license": "BSD-3-Clause",
       "engines": {
         "node": ">=0.10.0"
@@ -9112,14 +9112,14 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.4.1",
-      "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.1.tgz",
-      "integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==",
+      "version": "5.4.17",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.17.tgz",
+      "integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==",
       "license": "MIT",
       "dependencies": {
         "esbuild": "^0.21.3",
-        "postcss": "^8.4.41",
-        "rollup": "^4.13.0"
+        "postcss": "^8.4.43",
+        "rollup": "^4.20.0"
       },
       "bin": {
         "vite": "bin/vite.js"

+ 157 - 2
src/components/table/CustomTable.vue

@@ -31,6 +31,7 @@ import TableFilterForm from '@/components/table/TableFilterForm/TableFilterForm.
 import axiosInstance from '@/utils/axios/axiosInstance.ts'
 import TableTools from './TableTools.vue'
 import TableColumn from './TableColumn/TableColumn.vue'
+import type { ResponseInfo } from '@/types/res.ts'
 
 type TableFilterFormRef = InstanceType<typeof TableFilterForm>
 
@@ -61,6 +62,25 @@ const loading = ref(false)
 // 表格数据
 const tableData: Array<any> = reactive([])
 
+// 展开表格数据
+const expandTableData: Record<string, Array<any>> = reactive({})
+
+// 分页展开表格数据
+const expandPaginationConfig = reactive<Record<string, TablePaginationSetting>>({})
+
+const expandPageData = computed(() => {
+  const nowActive = activeExpandTablePageConfig.value
+  const { currentPage, limit } = expandPaginationConfig[nowActive]
+  if (!expandTableData[nowActive]) return []
+
+  // const data = expandTableData[nowActive].slice((currentPage - 1) * limit, currentPage * limit)
+
+  return expandTableData[nowActive].slice((currentPage - 1) * limit, currentPage * limit)
+})
+
+// 现在使用的分页
+const activeExpandTablePageConfig = ref<string>('')
+
 // 查询表单的数据
 const queryFormData = reactive<{ [key: string]: any }>({})
 
@@ -117,6 +137,13 @@ if (!props.openRemoteReqData && props.dataList) {
  */
 const getData = async (): Promise<[Array<any>, number]> => {
   try {
+    for (let [k, v] of Object.entries(expandTableData)) {
+      if (v) {
+        delete expandTableData[k]
+      }
+    }
+    expandRowKeys.value = []
+    // expandPageData.value.splice(0, expandPageData.value.length)
     // 使用传入数据源
     // 如果使用前端查询,则需要传入dataList作为数据源
     if (!props.openRemoteReqData) {
@@ -431,6 +458,77 @@ onMounted(() => {
     // changeTableFooterPos()
   })
 })
+
+const expandRowKeys = ref<string[]>([])
+
+const getRowKey = (row: any) => {
+  const id = row['actionId'] ?? row['id']
+  if (!id) {
+    console.warn('请检查表格数据,没有可用的ID 作为RowKey,当前使用随机key')
+    return createRowKey()
+  }
+  return id + ''
+}
+
+const handleExpand = async (row: any, expandedRows: any[]) => {
+  if (!props.expandConfig) return
+
+  let id = row['actionId'] ?? row['id']
+  if (!id) {
+    console.warn('请检查表格数据,没有可用的ID 作为RowKey,当前使用随机key')
+    id = createRowKey()
+  }
+
+  // expandRowKeys.value.push(id)
+  if (expandedRows.length) {
+    //展开
+    expandRowKeys.value = [] //先干掉之前展开的行
+    if (row) {
+      expandRowKeys.value.push(id) //push新的行 (原理有点类似防抖)
+    }
+  } else {
+    expandRowKeys.value = [] //折叠 就清空expand-row-keys对应的数组
+  }
+
+  activeExpandTablePageConfig.value = id
+  if (!expandPaginationConfig[id]) {
+    expandPaginationConfig[id] = {
+      currentPage: 1,
+      limit: 10,
+      total: 0,
+      pageSizeList: [10, 20, 30]
+    }
+  }
+
+  // 有数据后就不要重新请求数据
+  // 这里只判断是否已经请求过了,而不判断是否有数据,防止重复请求
+  if (expandTableData[id] !== undefined) return
+  const params = {
+    ...props.expandConfig.expandReqConfig.otherOptions,
+    actionId: id + ''
+  }
+  const res = (await axiosInstance.post(
+    props.expandConfig.expandReqConfig.url,
+    params
+  )) as ResponseInfo
+  if (res.code !== 0) {
+    ElMessage.error('获取展开信息失败')
+    return
+  }
+  expandTableData[id] = res.data
+  expandPaginationConfig[id].total = res.data.length
+}
+
+const handleExpandPageChange = (page: number) => {
+  const config = expandPaginationConfig[activeExpandTablePageConfig.value]
+  config.currentPage = page
+}
+
+const handleExpandSizeChange = (size: number) => {
+  const config = expandPaginationConfig[activeExpandTablePageConfig.value]
+  config.currentPage = 1
+  config.limit = size
+}
 </script>
 
 <template>
@@ -451,7 +549,7 @@ onMounted(() => {
     <!--    <div class="chartContainer" v-if="needChart && props.chartOptions">-->
     <!--      <PieBorderRadius :options="props.chartOptions"></PieBorderRadius>-->
     <!--    </div>-->
-    <slot name="chart" v-if="$slots.chart"> </slot>
+    <slot name="chart" v-if="$slots.chart"></slot>
 
     <div class="tableTools">
       <TableTools
@@ -490,9 +588,11 @@ onMounted(() => {
         class="tableBody"
         :cell-style="tableCellStyle"
         v-loading="openRemoteReqData ? loading : props.loadingState"
-        :row-key="createRowKey()"
+        :row-key="getRowKey"
+        :expand-row-keys="expandRowKeys"
         @sort-change="tableSortChange"
         @query="throttleGetData"
+        @expand-change="handleExpand"
         table-layout="auto"
       >
         <el-table-column
@@ -502,6 +602,50 @@ onMounted(() => {
           type="index"
           :index="computedRowIndex"
         />
+        <el-table-column align="center" show-overflow-tooltip type="expand">
+          <template #default="rowInfo" v-if="props.needExpand">
+            <el-table :data="expandPageData">
+              <template v-for="child in props.expandConfig?.expandField" :key="child.name">
+                <el-table-column
+                  :prop="child.name"
+                  :label="child.cnName"
+                  align="center"
+                  show-overflow-tooltip
+                  v-if="child.isShow"
+                  :sortable="child.needSort ? 'custom' : false"
+                >
+                  <template v-slot="scope">
+                    <TableColumn
+                      :column-config="child"
+                      :row="scope.row"
+                      :need-average="props.needAverage"
+                    ></TableColumn>
+                  </template>
+                  <!--                              {{ expandTableData[rowInfo.row.actionId] }}-->
+                  <!--                  <TableColumn-->
+                  <!--                    :column-config="child"-->
+                  <!--                    :row="rowInfo.row"-->
+                  <!--                    :need-average="rowInfo.needAverage"-->
+                  <!--                  ></TableColumn>-->
+                </el-table-column>
+              </template>
+            </el-table>
+            <div class="expandPageConfig">
+              <el-pagination
+                class="userTablePagination"
+                background
+                :page-size="expandPaginationConfig[rowInfo.row.actionId].limit"
+                :page-sizes="expandPaginationConfig[rowInfo.row.actionId].pageSizeList"
+                table-layout="fixed"
+                layout="prev, pager, next ,jumper ,sizes,total,"
+                :total="expandPaginationConfig[rowInfo.row.actionId].total"
+                :current-page="expandPaginationConfig[rowInfo.row.actionId].currentPage"
+                @current-change="handleExpandPageChange"
+                @size-change="handleExpandSizeChange"
+              />
+            </div>
+          </template>
+        </el-table-column>
         <template v-for="item in tableFieldsInfo" :key="item.name">
           <el-table-column
             :prop="item.name"
@@ -662,6 +806,17 @@ onMounted(() => {
   justify-content: center;
 }
 
+.expandPageConfig {
+  /*width: 98%;*/
+  /*height: 600px;*/
+  box-sizing: border-box;
+  width: 98%;
+  margin: 0 auto;
+  padding: 1% 0;
+  display: flex;
+  justify-content: center;
+}
+
 .leftToolBtn {
   margin-right: 5px;
 }

+ 1 - 0
src/hooks/useRequest.ts

@@ -62,6 +62,7 @@ export function useRequest() {
     userActionDetailDistribution: `/user/userActionDetailDistribution`, // 事件统计趋势图
     userActionDetail: `/user/userActionDetail`, // 事件统计详情
     userActionList: `/user/userActionList`, // 游戏事件统计列表
+    userActionListExpand: `/user/userActionOptionList`, // 事件选项统计详情
 
     // 用户转化条件
     gameConditionList: `/user/gameConditionList`, // 广告列表

+ 1 - 2
src/hooks/useTable.ts

@@ -224,7 +224,7 @@ export function useTable(
   /**
    * @description: 下载表格数据
    */
-  const downLoadTable = (headerMap: Record<string, string>) => {
+  const downLoadTable = (headerMap: Record<string, string> = {}) => {
     // downLoadData(generateRandomFileName(), JSON.parse(JSON.stringify(tableData)))
     const exportData = tableData
       .slice(1)
@@ -254,7 +254,6 @@ export function useTable(
       },
       ...exportData
     ]
-    console.log(exportData)
     const workbook = xlsx.utils.book_new()
     const worksheet = xlsx.utils.json_to_sheet(newData, {
       header: header,

+ 5 - 0
src/types/table.ts

@@ -90,6 +90,11 @@ export interface PropsParams {
   needRowindex?: boolean // 是否需要行号
   needAverage?: boolean // 是否需要均值功能
   needTotal?: boolean // 是否需要总计
+  needExpand?: boolean // 是否需要展开功能
+  expandConfig?: {
+    expandReqConfig: ReqConfig // 展开数据请求配置
+    expandField: Array<TableFieldInfo> // 展开字段
+  } // 展开配置
   // needChart?: boolean // 是否需要图表功能
   // chartOptions?: EChartsOption // 图表配置
   totalFunc?: (data: any, total: number) => any[] // 计算总计的函数

+ 1 - 1
src/views/Home/AdvertisingData/AdvertisingList.vue

@@ -20,7 +20,7 @@ import HeaderCard from '@/components/dataAnalysis/HeaderCard.vue'
 import { useRequest } from '@/hooks/useRequest'
 import { useCommonStore } from '@/stores/useCommon'
 import type { HeaderCardProps } from '@/types/dataAnalysis'
-import { reactive, type Ref, ref } from 'vue'
+import { reactive, ref } from 'vue'
 
 import { FilterType, type QueryInfo, type SelectInfo } from '@/types/table'
 

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

@@ -115,6 +115,61 @@ const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
   }
 ])
 
+const tableExpandFieldsInfo = reactive<Array<TableFieldInfo>>([
+  {
+    name: 'id',
+    cnName: 'ID',
+    isShow: false,
+    needSort: false
+  },
+  {
+    name: 'actionId',
+    cnName: '选项ID',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'actionName',
+    cnName: '选项名',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'actionCount',
+    cnName: '选项执行次数',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'actionUserCount',
+    cnName: '操作用户数',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'activeUserRate',
+    cnName: '活跃用户率',
+    isShow: true,
+    needSort: false
+  },
+  {
+    name: 'loginActiveRate',
+    cnName: '登录活跃率',
+    isShow: true,
+    needSort: false
+  }
+])
+
+const expandRequestConfig = reactive<ReqConfig>({
+  url: AllApi.userActionListExpand,
+  otherOptions: {
+    actionId: '',
+    gid: selectInfo.gid,
+    startTime: props.startTime,
+    endTime: props.endTime
+  }
+})
+
 // 表格请求配置
 const requestConfig = reactive<ReqConfig>({
   url: AllApi.userActionList,
@@ -161,6 +216,8 @@ const viewDetails = (row: any) => {
 const updateDate = (startTime: string, endTime: string) => {
   requestConfig.otherOptions.startTime = startTime
   requestConfig.otherOptions.endTime = endTime
+  expandRequestConfig.otherOptions.startTime = startTime
+  expandRequestConfig.otherOptions.endTime = endTime
 }
 
 /**
@@ -171,6 +228,7 @@ const updateDate = (startTime: string, endTime: string) => {
 const updateSelect = (gid: string, pf: any) => {
   pf = isSinglePf ? pf[0] : pf
   updateReqConfig(requestConfig, { pf, gid })
+  updateReqConfig(expandRequestConfig, { gid })
 }
 
 const backupDate = reactive([])
@@ -194,6 +252,11 @@ watchPageChange(() => [props.startTime, props.endTime], backupDate, updateDate)
         :pagination-config="pagingConfig"
         :table-fields-info="tableFieldsInfo"
         :tools="tableToolsConfig"
+        :need-expand="true"
+        :expand-config="{
+          expandField: tableExpandFieldsInfo,
+          expandReqConfig: expandRequestConfig
+        }"
       >
         <template #tableOperation>
           <el-table-column label="操作" align="center">