Преглед изворни кода

perf(用户行为页面、广告列表页面、事件分析页面): 更新用户行为、广告列表图表形式;事件分析页新增下载功能;现在表格会直接下载excel

fxs пре 8 месеци
родитељ
комит
f160f50a98

+ 216 - 1
package-lock.json

@@ -15,11 +15,14 @@
         "echarts": "^5.5.1",
         "element-plus": "^2.8.0",
         "fast-glob": "^3.3.2",
+        "file-saver": "^2.0.5",
         "less": "^4.2.0",
         "pinia": "^2.1.7",
+        "save": "^2.9.0",
         "vite-plugin-svg-icons": "^2.0.1",
         "vue": "^3.4.29",
-        "vue-router": "^4.3.3"
+        "vue-router": "^4.3.3",
+        "xlsx": "^0.18.5"
       },
       "devDependencies": {
         "@iconify-json/ep": "^1.1.16",
@@ -2176,6 +2179,15 @@
         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
       }
     },
+    "node_modules/adler-32": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
+      "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/ajv": {
       "version": "6.12.6",
       "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
@@ -2409,6 +2421,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/async": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz",
+      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+      "license": "MIT"
+    },
     "node_modules/async-validator": {
       "version": "4.2.5",
       "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
@@ -2632,6 +2650,19 @@
         "uc-first-array": "^1.1.10"
       }
     },
+    "node_modules/cfb": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
+      "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "crc-32": "~1.2.0"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/chalk": {
       "version": "4.1.2",
       "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
@@ -2767,6 +2798,15 @@
         "node": ">=0.8"
       }
     },
+    "node_modules/codepage": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
+      "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/collection-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -2953,6 +2993,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/crc-32": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
+      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+      "license": "Apache-2.0",
+      "bin": {
+        "crc32": "bin/crc32.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -3399,6 +3451,12 @@
         "domelementtype": "1"
       }
     },
+    "node_modules/duplexer": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz",
+      "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
+      "license": "MIT"
+    },
     "node_modules/echarts": {
       "version": "5.5.1",
       "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
@@ -4003,6 +4061,21 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/event-stream": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/event-stream/-/event-stream-4.0.1.tgz",
+      "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==",
+      "license": "MIT",
+      "dependencies": {
+        "duplexer": "^0.1.1",
+        "from": "^0.1.7",
+        "map-stream": "0.0.7",
+        "pause-stream": "^0.0.11",
+        "split": "^1.0.1",
+        "stream-combiner": "^0.2.2",
+        "through": "^2.3.8"
+      }
+    },
     "node_modules/execa": {
       "version": "5.1.1",
       "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz",
@@ -4227,6 +4300,12 @@
         "node": "^10.12.0 || >=12.0.0"
       }
     },
+    "node_modules/file-saver": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
+      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+      "license": "MIT"
+    },
     "node_modules/fill-range": {
       "version": "7.1.1",
       "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
@@ -4337,6 +4416,15 @@
         "node": ">= 6"
       }
     },
+    "node_modules/frac": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
+      "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/fragment-cache": {
       "version": "0.2.1",
       "resolved": "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -4349,6 +4437,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/from": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmmirror.com/from/-/from-0.1.7.tgz",
+      "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==",
+      "license": "MIT"
+    },
     "node_modules/fs-extra": {
       "version": "10.1.0",
       "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -5801,6 +5895,12 @@
         "lodash-es": "*"
       }
     },
+    "node_modules/lodash.assign": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/lodash.assign/-/lodash.assign-4.2.0.tgz",
+      "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==",
+      "license": "MIT"
+    },
     "node_modules/lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -5850,6 +5950,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/map-stream": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmmirror.com/map-stream/-/map-stream-0.0.7.tgz",
+      "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==",
+      "license": "MIT"
+    },
     "node_modules/map-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/map-visit/-/map-visit-1.0.0.tgz",
@@ -5968,6 +6074,12 @@
         "node": ">=6"
       }
     },
+    "node_modules/mingo": {
+      "version": "6.5.6",
+      "resolved": "https://registry.npmmirror.com/mingo/-/mingo-6.5.6.tgz",
+      "integrity": "sha512-XV89xbTakngi/oIEpuq7+FXXYvdA/Ht6aAsNTuIl8zLW1jfv369Va1PPWod1UTa/cqL0pC6LD2P6ggBcSSeH+A==",
+      "license": "MIT"
+    },
     "node_modules/minimatch": {
       "version": "9.0.5",
       "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
@@ -6615,6 +6727,18 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/pause-stream": {
+      "version": "0.0.11",
+      "resolved": "https://registry.npmmirror.com/pause-stream/-/pause-stream-0.0.11.tgz",
+      "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==",
+      "license": [
+        "MIT",
+        "Apache2"
+      ],
+      "dependencies": {
+        "through": "~2.3"
+      }
+    },
     "node_modules/picocolors": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.1.tgz",
@@ -7342,6 +7466,18 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/save": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmmirror.com/save/-/save-2.9.0.tgz",
+      "integrity": "sha512-eg8+g8CjvehE/2C6EbLdtK1pINVD27pcJLj4M9PjWWhoeha/y5bWf4dp/0RF+OzbKTcG1bae9qi3PAqiR8CJTg==",
+      "license": "ISC",
+      "dependencies": {
+        "async": "^3.2.2",
+        "event-stream": "^4.0.1",
+        "lodash.assign": "^4.2.0",
+        "mingo": "^6.1.0"
+      }
+    },
     "node_modules/sax": {
       "version": "1.4.1",
       "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz",
@@ -7699,6 +7835,18 @@
       "deprecated": "See https://github.com/lydell/source-map-url#deprecated",
       "license": "MIT"
     },
+    "node_modules/split": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/split/-/split-1.0.1.tgz",
+      "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
+      "license": "MIT",
+      "dependencies": {
+        "through": "2"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/split-string": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/split-string/-/split-string-3.1.0.tgz",
@@ -7736,6 +7884,18 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/ssf": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
+      "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "frac": "~1.1.2"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/stable": {
       "version": "0.1.8",
       "resolved": "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz",
@@ -7794,6 +7954,16 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/stream-combiner": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmmirror.com/stream-combiner/-/stream-combiner-0.2.2.tgz",
+      "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==",
+      "license": "MIT",
+      "dependencies": {
+        "duplexer": "~0.1.1",
+        "through": "~2.3.4"
+      }
+    },
     "node_modules/strict-uri-encode": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
@@ -8270,6 +8440,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz",
+      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+      "license": "MIT"
+    },
     "node_modules/tmatch": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/tmatch/-/tmatch-2.0.1.tgz",
@@ -9265,6 +9441,24 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/wmf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
+      "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/word": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
+      "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/word-wrap": {
       "version": "1.2.5",
       "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -9300,6 +9494,27 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/xlsx": {
+      "version": "0.18.5",
+      "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
+      "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "cfb": "~1.2.1",
+        "codepage": "~1.15.0",
+        "crc-32": "~1.2.1",
+        "ssf": "~0.11.2",
+        "wmf": "~1.0.1",
+        "word": "~0.3.0"
+      },
+      "bin": {
+        "xlsx": "bin/xlsx.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/xml-name-validator": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz",

+ 4 - 1
package.json

@@ -22,11 +22,14 @@
     "echarts": "^5.5.1",
     "element-plus": "^2.8.0",
     "fast-glob": "^3.3.2",
+    "file-saver": "^2.0.5",
     "less": "^4.2.0",
     "pinia": "^2.1.7",
+    "save": "^2.9.0",
     "vite-plugin-svg-icons": "^2.0.1",
     "vue": "^3.4.29",
-    "vue-router": "^4.3.3"
+    "vue-router": "^4.3.3",
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@iconify-json/ep": "^1.1.16",

+ 14 - 0
src/components/echarts/PieBorderRadius.vue

@@ -38,6 +38,20 @@ const initOptions = () => {
   pieChart.value?.setOption(options, true)
 }
 
+const startLoading = () => {
+  pieChart.value?.showLoading()
+  console.log('执行')
+}
+
+const stopLoading = () => {
+  pieChart.value?.hideLoading()
+}
+
+defineExpose({
+  startLoading,
+  stopLoading
+})
+
 watch(
   () => props.options,
   () => {

+ 1 - 1
src/components/table/CustomTable.vue

@@ -458,7 +458,7 @@ onMounted(() => {
         :table-fields-info="props.tableFieldsInfo"
         :tools-config="props.tools"
         @add="emits('addNewItem')"
-        @download="downLoadTable"
+        @download="downLoadTable(props.tools?.headerMap)"
         @refresh="throttleGetData"
       ></TableTools>
     </div>

+ 43 - 4
src/hooks/useTable.ts

@@ -10,10 +10,12 @@ import type { ResponseInfo } from '@/types/res'
 import type { TableFieldInfo, TablePaginationSetting } from '@/types/table'
 
 import { initLoadResource } from '@/utils/resource'
-import { reactive } from 'vue'
-import { downLoadData } from '@/utils/table/table'
+import { reactive, toRaw } from 'vue'
+// import { downLoadData } from '@/utils/table/table'
 import { generateRandomFileName } from '@/utils/common'
 
+import * as xlsx from 'xlsx'
+
 import axiosInstance from '../utils/axios/axiosInstance'
 import type { ReqConfig } from '@/types/dataAnalysis'
 
@@ -222,8 +224,45 @@ export function useTable(
   /**
    * @description: 下载表格数据
    */
-  const downLoadTable = () => {
-    downLoadData(generateRandomFileName(), JSON.parse(JSON.stringify(tableData)))
+  const downLoadTable = (headerMap: Record<string, string>) => {
+    // downLoadData(generateRandomFileName(), JSON.parse(JSON.stringify(tableData)))
+    const exportData = tableData
+      .slice(1)
+      .flat()
+      .map((item) => {
+        const rawData = toRaw(item)
+        // 遍历对象的键,删除不在 validKeys 中的键
+        Object.keys(item).forEach((key) => {
+          if (!(key in headerMap)) {
+            delete item[key]
+          }
+        })
+        return rawData
+      })
+    // 表头设置
+    // exportData.unshift(headerZh)
+
+    const header = Object.keys(headerMap)
+    const newData = [
+      {
+        actionId: '事件ID',
+        actionName: '事件名称',
+        actionCount: '操作次数',
+        actionUserCount: '操作用户数',
+        activeUserRate: '活跃设备发生率',
+        loginActiveRate: '每次启动发生数'
+      },
+      ...exportData
+    ]
+    console.log(exportData)
+    const workbook = xlsx.utils.book_new()
+    const worksheet = xlsx.utils.json_to_sheet(newData, {
+      header: header,
+      skipHeader: true
+    })
+
+    xlsx.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
+    return xlsx.writeFile(workbook, generateRandomFileName() + '.xlsx')
   }
 
   return {

+ 89 - 72
src/hooks/useTableChart.ts

@@ -4,6 +4,9 @@ import axiosInstance from '@/utils/axios/axiosInstance.ts'
 import type { ResponseInfo } from '@/types/res.ts'
 import type { QueryInfo } from '@/types/table.ts'
 import { generateUniqueColors } from '@/utils/common'
+import PieBorderRadius from '@/components/echarts/PieBorderRadius.vue'
+
+type MChartType = InstanceType<typeof PieBorderRadius>
 
 interface AdChartItem {
   name: string
@@ -31,7 +34,8 @@ export function useTableChart(
   queryFormData: Ref<any>,
   filterInfo: Array<QueryInfo>,
   chartNeedFields: Array<string>,
-  isPie: Ref<boolean>
+  isPie: Ref<boolean>,
+  chartInstance: Ref<MChartType | null>
 ) {
   const chartInfo = ref<ChartInfo>({
     PieChart: [],
@@ -45,60 +49,52 @@ export function useTableChart(
   })
 
   const updateChartData = async () => {
-    if (chartNeedFields.length > 0) {
-      const hasFilter = chartNeedFields.every((item) => {
-        return queryFormData.value[item] === ''
-      })
-      if (hasFilter) {
-        const tip = filterInfo
-          .filter((item) => {
-            return chartNeedFields.includes(item.name as any)
-          })
-          .map((item) => {
-            return item.label
+    try {
+      if (chartNeedFields.length > 0) {
+        const hasFilter = chartNeedFields.every((item) => {
+          return queryFormData.value[item] === ''
+        })
+        if (hasFilter) {
+          const tip = filterInfo
+            .filter((item) => {
+              return chartNeedFields.includes(item.name as any)
+            })
+            .map((item) => {
+              return item.label
+            })
+            .join(',')
+          ElMessage.warning(`${tip}请至少输入一个筛选条件`)
+          return
+        }
+      }
+      if (chartInstance.value) {
+        chartInstance.value.startLoading()
+      }
+      const params = {} as any
+      Object.assign(params, queryFormData.value)
+      if (queryFormData.value.createTime) {
+        params.createTime = queryFormData.value.createTime
+          .map((item: any) => {
+            return new Date(item).getTime().toString()
           })
           .join(',')
-        ElMessage.warning(`${tip}请至少输入一个筛选条件`)
-        return
       }
-    }
-
-    const params = {} as any
-    Object.assign(params, queryFormData.value)
-    if (queryFormData.value.createTime) {
-      params.createTime = queryFormData.value.createTime
-        .map((item: any) => {
-          return new Date(item).getTime().toString()
-        })
-        .join(',')
-    }
 
-    const res = (await axiosInstance.post(url, params)) as ResponseInfo
-    if (res.code !== 0) {
-      ElMessage.error('获取数据失败')
-      return
-    }
+      const res = (await axiosInstance.post(url, params)) as ResponseInfo
+      if (res.code !== 0) {
+        ElMessage.error('获取数据失败')
+        return
+      }
 
-    const data = res.data as Array<{
-      name: string
-      count: number
-      sumType0: number
-      sumType1: number
-      sumType2: number
-    }> | null
+      const data = res.data as Array<{
+        name: string
+        count: number
+        sumType0: number
+        sumType1: number
+        sumType2: number
+      }> | null
 
-    if (!data) {
-      chartInfo.value.PieChart = []
-      chartInfo.value.BarChart = {
-        labelList: [],
-        valueList: [],
-        sumType0List: [],
-        sumType1List: [],
-        sumType2List: []
-      }
-    } else {
-      const isAllZero = data.every((item) => item.count === 0)
-      if (isAllZero) {
+      if (!data) {
         chartInfo.value.PieChart = []
         chartInfo.value.BarChart = {
           labelList: [],
@@ -107,22 +103,40 @@ export function useTableChart(
           sumType1List: [],
           sumType2List: []
         }
-        return
-      }
+      } else {
+        const isAllZero = data.every((item) => item.count === 0)
+        if (isAllZero) {
+          chartInfo.value.PieChart = []
+          chartInfo.value.BarChart = {
+            labelList: [],
+            valueList: [],
+            sumType0List: [],
+            sumType1List: [],
+            sumType2List: []
+          }
+          return
+        }
 
-      chartInfo.value.PieChart = data.map((item) => ({
-        name: item.name,
-        value: item.count,
-        sumType0: item.sumType0 || 0,
-        sumType1: item.sumType1 || 0,
-        sumType2: item.sumType2 || 0
-      }))
-      chartInfo.value.BarChart = {
-        labelList: data.map((item) => item.name),
-        valueList: data.map((item) => item.count),
-        sumType0List: data.map((item) => item.sumType0 || 0),
-        sumType1List: data.map((item) => item.sumType1 || 0),
-        sumType2List: data.map((item) => item.sumType2 || 0)
+        chartInfo.value.PieChart = data.map((item) => ({
+          name: item.name,
+          value: item.count,
+          sumType0: item.sumType0 || 0,
+          sumType1: item.sumType1 || 0,
+          sumType2: item.sumType2 || 0
+        }))
+        chartInfo.value.BarChart = {
+          labelList: data.map((item) => item.name),
+          valueList: data.map((item) => item.count),
+          sumType0List: data.map((item) => item.sumType0 || 0),
+          sumType1List: data.map((item) => item.sumType1 || 0),
+          sumType2List: data.map((item) => item.sumType2 || 0)
+        }
+      }
+    } catch (err) {
+      console.error(err)
+    } finally {
+      if (chartInstance.value) {
+        chartInstance.value.stopLoading()
       }
     }
   }
@@ -315,7 +329,6 @@ export function useTableChart(
 
   const barChartOptions = computed<EChartsOption>(() => {
     const barChartInfo = chartInfo.value.BarChart
-    const colors = generateUniqueColors(barChartInfo.labelList.length)
     return {
       tooltip: {
         trigger: 'axis',
@@ -332,7 +345,7 @@ export function useTableChart(
         axisLabel: {
           interval: 30,
           // rotate: 30,
-          // width: 80, // 增加标签宽度
+          // width: 10, // 增加标签宽度
           overflow: 'truncate' // 过长时显示省略号
         }
       },
@@ -345,26 +358,30 @@ export function useTableChart(
       },
       dataZoom: [
         {
+          type: 'slider',
           show: true,
-          start: 94,
-          end: 100
+          start: 0,
+          end: 100,
+          handleSize: 8
         },
         {
           type: 'inside',
-          start: 94,
+          start: 0,
           end: 100
         },
         {
+          type: 'slider',
           show: true,
           yAxisIndex: 0,
           filterMode: 'empty',
-          width: 30,
-          height: '80%',
+          width: 12,
+          height: '70%',
+          handleSize: 8,
           showDataShadow: false,
           left: '93%'
         }
       ],
-      color: colors,
+      color: '#9FE080',
       grid: {
         top: '12%',
         left: '1%',

+ 169 - 98
src/hooks/useUserBehaviorChart.ts

@@ -4,7 +4,8 @@ import axiosInstance from '@/utils/axios/axiosInstance.ts'
 import type { ResponseInfo } from '@/types/res.ts'
 import type { QueryInfo } from '@/types/table.ts'
 import { generateUniqueColors } from '@/utils/common'
-
+import PieBorderRadius from '@/components/echarts/PieBorderRadius.vue'
+type MChartType = InstanceType<typeof PieBorderRadius>
 interface UserBehaviorChartItem {
   name: string
   value: number
@@ -23,7 +24,8 @@ export function useUserBehaviorChart(
   queryFormData: Ref<any>,
   filterInfo: Array<QueryInfo>,
   chartNeedFields: Array<string>,
-  isPie: Ref<boolean>
+  isPie: Ref<boolean>,
+  chartInstance: Ref<MChartType | null>
 ) {
   const chartInfo = ref<ChartInfo>({
     PieChart: [],
@@ -34,142 +36,211 @@ export function useUserBehaviorChart(
   })
 
   const updateChartData = async () => {
-    if (chartNeedFields.length > 0) {
-      const hasFilter = chartNeedFields.every((item) => {
-        return queryFormData.value[item] === ''
-      })
-      if (hasFilter) {
-        const tip = filterInfo
-          .filter((item) => {
-            return chartNeedFields.includes(item.name as any)
-          })
-          .map((item) => {
-            return item.label
+    try {
+      if (chartNeedFields.length > 0) {
+        const hasFilter = chartNeedFields.every((item) => {
+          return queryFormData.value[item] === ''
+        })
+        if (hasFilter) {
+          const tip = filterInfo
+            .filter((item) => {
+              return chartNeedFields.includes(item.name as any)
+            })
+            .map((item) => {
+              return item.label
+            })
+            .join(',')
+          ElMessage.warning(`${tip}请至少输入一个筛选条件`)
+          return
+        }
+      }
+
+      if (chartInstance.value) {
+        chartInstance.value.startLoading()
+      }
+
+      const params = {} as any
+      Object.assign(params, queryFormData.value)
+      if (queryFormData.value.createTime) {
+        params.createTime = queryFormData.value.createTime
+          .map((item: any) => {
+            return new Date(item).getTime().toString()
           })
           .join(',')
-        ElMessage.warning(`${tip}请至少输入一个筛选条件`)
-        return
       }
-    }
 
-    const params = {} as any
-    Object.assign(params, queryFormData.value)
-    if (queryFormData.value.createTime) {
-      params.createTime = queryFormData.value.createTime
-        .map((item: any) => {
-          return new Date(item).getTime().toString()
-        })
-        .join(',')
-    }
-
-    const res = (await axiosInstance.post(url, params)) as ResponseInfo
-    if (res.code !== 0) {
-      ElMessage.error('获取数据失败')
-      return
-    }
+      const res = (await axiosInstance.post(url, params)) as ResponseInfo
+      if (res.code !== 0) {
+        ElMessage.error('获取数据失败')
+        return
+      }
 
-    const data = res.data as Array<{
-      name: string
-      count: number
-    }> | null
+      const data = res.data as Array<{
+        name: string
+        count: number
+      }> | null
 
-    if (!data) {
-      chartInfo.value.PieChart = []
-      chartInfo.value.BarChart = {
-        labelList: [],
-        valueList: []
-      }
-    } else {
-      const isAllZero = data.every((item) => item.count === 0)
-      if (isAllZero) {
+      if (!data) {
         chartInfo.value.PieChart = []
         chartInfo.value.BarChart = {
           labelList: [],
           valueList: []
         }
-        return
-      }
+      } else {
+        const isAllZero = data.every((item) => item.count === 0)
+        if (isAllZero) {
+          chartInfo.value.PieChart = []
+          chartInfo.value.BarChart = {
+            labelList: [],
+            valueList: []
+          }
+          return
+        }
 
-      chartInfo.value.PieChart = data.map((item) => ({
-        name: item.name,
-        value: item.count
-      }))
-      chartInfo.value.BarChart = {
-        labelList: data.map((item) => item.name),
-        valueList: data.map((item) => item.count)
+        chartInfo.value.PieChart = data.map((item) => ({
+          name: item.name,
+          value: item.count
+        }))
+        chartInfo.value.BarChart = {
+          labelList: data.map((item) => item.name),
+          valueList: data.map((item) => item.count)
+        }
+      }
+    } catch (err) {
+      console.log(err)
+    } finally {
+      console.log(chartInstance)
+      if (chartInstance.value) {
+        chartInstance.value.stopLoading()
       }
     }
   }
 
   const pieChartOptions = computed<EChartsOption>(() => {
     const colors = generateUniqueColors(chartInfo.value.PieChart.length)
+    const pieData = chartInfo.value.PieChart
+    const seriesData: {
+      [key: string]: any[]
+    } = {}
+    pieData.forEach((item) => {
+      const name = item.name.split('&&')[0]
+      if (!seriesData[name]) {
+        seriesData[name] = []
+      }
+      seriesData[name].push(item)
+    })
+    const resultSeries: any[] = []
+    let count = 1
+    for (const [k, v] of Object.entries(seriesData)) {
+      console.log(`${count * 5}% `, `${count * 10}% `)
+      resultSeries.push({
+        name: k,
+        type: 'pie',
+        radius: [`${count * 10}% `, `${(count + 1) * 10}% `],
+        center: ['50%', '50%'],
+        avoidLabelOverlap: false,
+        itemStyle: {
+          borderRadius: 10,
+          borderColor: '#fff',
+          borderWidth: 1
+        },
+        label: {
+          show: false,
+          position: 'center'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 20,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: v
+      })
+      count++
+    }
+    console.log(resultSeries)
     return {
       color: colors,
+      // tooltip: {
+      //   trigger: 'item',
+      //   formatter: (params: any) => {
+      //     const data = chartInfo.value.PieChart[params.dataIndex]
+      //     return `${data.name}<br/>
+      //             数量: ${data.value}`
+      //   }
+      // },
       tooltip: {
         trigger: 'item',
-        formatter: (params: any) => {
-          const data = chartInfo.value.PieChart[params.dataIndex]
-          return `${data.name}<br/>
-                  数量: ${data.value}`
-        }
+        formatter: '{a} <br/>{b}: {c} ({d})'
       },
       legend: {
-        top: '5%',
-        left: 'center',
-        itemGap: 20,
-        textStyle: {
-          fontSize: 12
-        }
+        type: 'scroll',
+        orient: 'vertical',
+        right: 10,
+        top: 20,
+        bottom: 20
+        // top: '8%',
+        // left: 'center',
+        // itemGap: 35,
+        // textStyle: {
+        //   fontSize: 14
+        // },
+        // padding: [0, 50]
       },
-      series: [
-        {
-          name: '用户行为',
-          type: 'pie',
-          radius: ['40%', '70%'],
-          center: ['50%', '50%'],
-          avoidLabelOverlap: false,
-          itemStyle: {
-            borderRadius: 10,
-            borderColor: '#fff',
-            borderWidth: 2
-          },
-          label: {
-            show: false,
-            position: 'center'
-          },
-          emphasis: {
-            label: {
-              show: true,
-              fontSize: 20,
-              fontWeight: 'bold'
-            }
-          },
-          labelLine: {
-            show: false
-          },
-          data: chartInfo.value.PieChart
-        }
-      ]
+      series: resultSeries
     }
   })
 
   const barChartOptions = computed<EChartsOption>(() => {
     const barChartInfo = chartInfo.value.BarChart
-    const colors = generateUniqueColors(barChartInfo.labelList.length)
     return {
       xAxis: {
         type: 'category',
         data: barChartInfo.labelList,
         axisLabel: {
-          interval: 0,
-          rotate: 30
+          interval: 30,
+          // rotate: 30,
+          // width: 10, // 增加标签宽度
+          overflow: 'truncate' // 过长时显示省略号
         }
       },
       yAxis: {
         type: 'log',
-        min: 1
+        min: 1,
+        axisLabel: {
+          formatter: (value: number) => value.toLocaleString()
+        }
       },
-      color: colors,
+      dataZoom: [
+        {
+          type: 'slider',
+          show: true,
+          start: 0,
+          end: 100,
+          handleSize: 8
+        },
+        {
+          type: 'inside',
+          start: 0,
+          end: 100
+        },
+        {
+          type: 'slider',
+          show: true,
+          yAxisIndex: 0,
+          filterMode: 'empty',
+          width: 12,
+          height: '70%',
+          handleSize: 8,
+          showDataShadow: false,
+          left: '93%'
+        }
+      ],
+      color: '#9FE080',
       tooltip: {
         trigger: 'axis',
         axisPointer: {

+ 1 - 0
src/types/table.ts

@@ -77,6 +77,7 @@ export interface TableFieldInfo {
 export interface TableToolsConfig {
   add?: boolean
   download?: boolean
+  headerMap?: Record<string, string>
   refresh?: boolean
   filterFields?: boolean
 }

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

@@ -73,6 +73,7 @@ export const downLoadData = (fileName: string, info: any) => {
   const result = {
     ...info
   }
+  console.log(result)
   const blob = new Blob([JSON.stringify(result)], { type: 'application/json' })
   const url = URL.createObjectURL(blob)
   const a = document.createElement('a')

+ 8 - 3
src/views/Home/AdvertisingData/AdvertisingList.vue

@@ -20,14 +20,18 @@ 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, ref } from 'vue'
+import { reactive, type Ref, ref } from 'vue'
 
 import { FilterType, type QueryInfo, type SelectInfo } from '@/types/table'
 
 import TableFilterForm from '@/components/table/TableFilterForm/TableFilterForm.vue'
 import { useTableChart } from '@/hooks/useTableChart.ts'
 import { usePage } from '@/hooks/usePage.ts'
+import PieBorderRadius from '@/components/echarts/PieBorderRadius.vue'
 
+type MChartType = InstanceType<typeof PieBorderRadius>
+
+const chartRef = ref<MChartType | null>(null)
 const { tempMultipleChoice, selectInfo } = useCommonStore()
 const { AllApi } = useRequest()
 
@@ -132,7 +136,8 @@ const { updateChartData, chartOptions } = useTableChart(
   queryFormData,
   filterInfo,
   chartNeedFields,
-  isPie
+  isPie,
+  chartRef
 )
 </script>
 
@@ -164,7 +169,7 @@ const { updateChartData, chartOptions } = useTableChart(
             </el-radio-group>
           </div>
           <div class="chartDisplay">
-            <PieBorderRadius :options="chartOptions"></PieBorderRadius>
+            <PieBorderRadius ref="chartRef" :options="chartOptions"></PieBorderRadius>
           </div>
         </div>
       </div>

+ 11 - 3
src/views/Home/Analysis/EventAnalysisTable.vue

@@ -64,7 +64,15 @@ const tableToolsConfig: TableToolsConfig = {
   add: false,
   filterFields: true,
   refresh: true,
-  download: false
+  download: true,
+  headerMap: {
+    actionId: '事件ID',
+    actionName: '事件名称',
+    actionCount: '操作次数',
+    actionUserCount: '操作用户数',
+    activeUserRate: '活跃设备发生率',
+    loginActiveRate: '每次启动发生数'
+  }
 }
 
 // 表格字段信息
@@ -95,13 +103,13 @@ const tableFieldsInfo = reactive<Array<TableFieldInfo>>([
   },
   {
     name: 'activeUserRate',
-    cnName: '活跃用户率',
+    cnName: '活跃设备发生率',
     isShow: true,
     needSort: false
   },
   {
     name: 'loginActiveRate',
-    cnName: '登录活跃率',
+    cnName: '每次启动发生数',
     isShow: true,
     needSort: false
   }

+ 8 - 2
src/views/Home/Analysis/UserBehavior.vue

@@ -19,6 +19,9 @@ import { useCommonStore } from '@/stores/useCommon'
 import TableFilterForm from '@/components/table/TableFilterForm/TableFilterForm.vue'
 import { useUserBehaviorChart } from '@/hooks/useUserBehaviorChart'
 import { usePage } from '@/hooks/usePage.ts'
+import PieBorderRadius from '@/components/echarts/PieBorderRadius.vue'
+
+type MChartType = InstanceType<typeof PieBorderRadius>
 
 interface ChartQuery {
   /** 游戏ID */
@@ -51,6 +54,8 @@ const { selectInfo } = useCommonStore()
 
 const isPie = ref(true)
 
+const chartRef = ref<MChartType | null>(null)
+
 const queryFormData = ref<ChartQuery>({
   gid: selectInfo.gid,
   pf: selectInfo.pf[0],
@@ -201,7 +206,8 @@ const { updateChartData, chartOptions } = useUserBehaviorChart(
   queryFormData,
   filterInfo,
   chartNeedFields,
-  isPie
+  isPie,
+  chartRef
 )
 </script>
 
@@ -228,7 +234,7 @@ const { updateChartData, chartOptions } = useUserBehaviorChart(
             </el-radio-group>
           </div>
           <div class="chartDisplay">
-            <PieBorderRadius :options="chartOptions"></PieBorderRadius>
+            <PieBorderRadius ref="chartRef" :options="chartOptions"></PieBorderRadius>
           </div>
         </div>
       </div>