From 67e49540ec28ea8854009331c02e03f2a57f72b4 Mon Sep 17 00:00:00 2001 From: chenjinsong <523037378@qq.com> Date: Mon, 26 Feb 2024 11:51:13 +0800 Subject: [PATCH] =?UTF-8?q?CN-1563=20feat:=20=E8=BD=A8=E8=BF=B9=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E5=9F=BA=E7=A1=80=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 9 +- src/main.js | 2 +- src/mock/index.js | 1 + src/mock/location.js | 282 +++++++ src/permission.js | 22 +- src/utils/api.js | 5 + src/utils/hexbinDataConverter.js | 51 ++ .../charts/entityDetail/EntityDetailMap.vue | 48 +- .../charts2/charts/entityDetail/mapStyle.js | 4 +- src/views/location/Index.vue | 744 ++++++++++++++++++ src/views/location/chartOption.js | 27 + vue.config.js | 11 + 12 files changed, 1172 insertions(+), 34 deletions(-) create mode 100644 src/mock/location.js create mode 100644 src/utils/hexbinDataConverter.js create mode 100644 src/views/location/Index.vue create mode 100644 src/views/location/chartOption.js diff --git a/package.json b/package.json index 483de1ad..9161ae87 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,19 @@ "analyz": "cross-env NODE_ENV=production npm_config_report=true npm run build" }, "dependencies": { - "@amcharts/amcharts4": "~4.10.0", + "@amcharts/amcharts4": "^4.10.38", "@amcharts/amcharts4-geodata": "^4.1.20", "@antv/g6": "^4.8.17", + "@deck.gl/mapbox": "^8.9.34", + "@deck.gl/geo-layers": "^8.9.34", + "@turf/meta": "^6.5.0", "axios": "^0.21.1", "babel-plugin-lodash": "~3.3.0", "codemirror": "^5.65.1", "core-js": "~3.31.0", + "d3-array": "^3.2.4", "dayjs": "^1.10.5", + "deck.gl": "^8.9.34", "dexie": "~3.2.0", "echarts": "^5.1.1", "element-plus": "~1.0.2-beta.71", @@ -31,7 +36,9 @@ "postcss-px2rem-exclude": "0.0.6", "sass-loader": "~8.0.2", "sass-resources-loader": "~2.2.5", + "simple-statistics": "^7.8.3", "tiny-emitter": "^2.1.0", + "turf": "^3.0.14", "vue": "~3.3.0", "vue-grid-layout": "^3.0.0-beta1", "vue-i18n": "~9.2.0", diff --git a/src/main.js b/src/main.js index dfd64f4f..f35bf44e 100644 --- a/src/main.js +++ b/src/main.js @@ -9,7 +9,7 @@ import commonMixin from '@/mixins/common' import { cancelWithChange, noData, myHighLight } from '@/utils/tools' import { ClickOutside } from 'element-plus/lib/directives' import i18n from '@/i18n' -// import '@/mock/index.js' +import '@/mock/index.js' import hljsVuePlugin from '@highlightjs/vue-plugin' import 'highlight.js/styles/color-brewer.css' import '@/assets/css/main.scss' // 样式入口 diff --git a/src/mock/index.js b/src/mock/index.js index 77ff478b..85743d99 100644 --- a/src/mock/index.js +++ b/src/mock/index.js @@ -4,3 +4,4 @@ import './dns' import './entity' import './detection' import './detectionList' +import './location' diff --git a/src/mock/location.js b/src/mock/location.js new file mode 100644 index 00000000..d43021f5 --- /dev/null +++ b/src/mock/location.js @@ -0,0 +1,282 @@ +import Mock from 'mockjs' + +const openMock = true +if (openMock) { + Mock.mock(new RegExp(BASE_CONFIG.baseUrl + 'v1/locationIntelligence/population/density.*'), 'get', function (requestObj) { + const data = [453, 782, 128, 569, 934, 231, 678, 345, 890, 23, 456, 789, 321, 567, 89, 123, 457, 781, 209, 532, 910, 276, 648, 395, 820, 54, 76, 348, 901, 287, 654, 320, 879, 193, 487, 721, 98, 234, 612, 378, 850, 45, 78, 309, 521, 965, 289, 674, 385, 809, 12, 432, 765, 349, 897, 210, 563, 945, 278, 634, 312, 846, 57, 90, 246, 701, 389, 512, 923, 65, 878, 432, 176, 543, 987, 290, 741, 365, 854, 9, 42, 756, 390, 528, 912, 256, 689, 334, 821, 78, 456, 132, 578, 990, 201, 623, 356, 888, 43, 76, 222, 712, 323, 590, 932, 645, 867, 189, 476, 1000, 501, 954, 267, 777, 388, 832, 67, 99, 421, 145, 555] + return { + msg: 'success', + code: 200, + data: { + list: data.map(d => ({ subscriberId: d, number: d })) + } + } + }) + Mock.mock(new RegExp(BASE_CONFIG.baseUrl + 'v1/locationIntelligence/map.*'), 'get', function (requestObj) { + const data = [453, 782, 128, 569, 934, 231, 678, 345, 890, 23, 456, 789, 321, 567, 89, 123, 457, 781, 209, 532, 910, 276, 648, 395, 820, 54, 76, 348, 901, 287, 654, 320, 879, 193, 487, 721, 98, 234, 612, 378, 850, 45, 78, 309, 521, 965, 289, 674, 385, 809, 12, 432, 765, 349, 897, 210, 563, 945, 278, 634, 312, 846, 57, 90, 246, 701, 389, 512, 923, 65, 878, 432, 176, 543, 987, 290, 741, 365, 854, 9, 42, 756, 390, 528, 912, 256, 689, 334, 821, 78, 456, 132, 578, 990, 201, 623, 356, 888, 43, 76, 222, 712, 323, 590, 932, 645, 867, 189, 476, 1000, 501, 954, 267, 777, 388, 832, 67, 99, 421, 145, 555] + return { + msg: 'success', + code: 200, + data: { + list: data.map(d => ({ subscriberId: d, number: d })) + } + } + }) + Mock.mock(new RegExp(BASE_CONFIG.baseUrl + 'v1/locationIntelligence/active/subscribers.*'), 'get', function (requestObj) { + const lineData = [ + [ + 1707357180, + "0" + ], + [ + 1707357240, + "67781" + ], + [ + 1707357300, + "8878" + ], + [ + 1707357360, + "71386" + ], + [ + 1707357420, + "119827" + ], + [ + 1707357480, + "63332" + ], + [ + 1707357540, + "139829" + ], + [ + 1707357600, + "12184" + ], + [ + 1707357660, + "3922" + ], + [ + 1707357720, + "41349" + ], + [ + 1707357780, + "54148" + ], + [ + 1707357840, + "2530" + ], + [ + 1707357900, + "17814" + ], + [ + 1707357960, + "0" + ], + [ + 1707358020, + "25336" + ], + [ + 1707358080, + "5684" + ], + [ + 1707358140, + "46829" + ], + [ + 1707358200, + "2546" + ], + [ + 1707358260, + "33345" + ], + [ + 1707358320, + "304847" + ], + [ + 1707358380, + "73994" + ], + [ + 1707358440, + "170837" + ], + [ + 1707358500, + "35891" + ], + [ + 1707358560, + "138272" + ], + [ + 1707358620, + "194981" + ], + [ + 1707358680, + "292987" + ], + [ + 1707358740, + "95844" + ], + [ + 1707358800, + "7424" + ], + [ + 1707358860, + "22057" + ], + [ + 1707358920, + "61791" + ], + [ + 1707358980, + "23838" + ], + [ + 1707359040, + "11664" + ], + [ + 1707359100, + "76485" + ], + [ + 1707359160, + "37542" + ], + [ + 1707359220, + "40851" + ], + [ + 1707359280, + "27611" + ], + [ + 1707359340, + "170709" + ], + [ + 1707359400, + "2290" + ], + [ + 1707359460, + "15577" + ], + [ + 1707359520, + "124024" + ], + [ + 1707359580, + "22947" + ], + [ + 1707359640, + "4131" + ], + [ + 1707359700, + "208705" + ], + [ + 1707359760, + "120407" + ], + [ + 1707359820, + "58706" + ], + [ + 1707359880, + "90036" + ], + [ + 1707359940, + "41000" + ], + [ + 1707360000, + "87489" + ], + [ + 1707360060, + "12170" + ], + [ + 1707360120, + "24611" + ], + [ + 1707360180, + "25815" + ], + [ + 1707360240, + "50903" + ], + [ + 1707360300, + "102385" + ], + [ + 1707360360, + "58121" + ], + [ + 1707360420, + "95365" + ], + [ + 1707360480, + "66674" + ], + [ + 1707360540, + "89708" + ], + [ + 1707360600, + "64098" + ], + [ + 1707360660, + "41778" + ], + [ + 1707360720, + "246241" + ], + [ + 1707360780, + "0" + ] + ] + return { + msg: 'success', + code: 200, + data: { + result: { + values: lineData + } + } + } + }) +} diff --git a/src/permission.js b/src/permission.js index f260b3d2..52d03c70 100644 --- a/src/permission.js +++ b/src/permission.js @@ -36,7 +36,24 @@ router.beforeEach(async (to, from, next) => { path: '/', name: 'home', component: () => import('@/components/layout/Home'), - children: [] + children: [ + { + path: '/temp', + component: () => import('@/Temp') + }, + { + path: '/temp2', + component: () => import('@/Temp2') + }, + { + path: '/temp3', + component: () => import('@/Temp3') + }, + { + path: '/temp4', + component: () => import('@/Temp4') + } + ] } handleRoutes(menuList, homeRoute.children) router.addRoute(homeRoute) @@ -187,6 +204,9 @@ export function handleComponent (code) { return () => import('@/views/system/Index') case 'plugin': return () => import('@/views/system/Plugin') + case 'locationMap': + case 'traceTracking': + return () => import('@/views/location/Index') default: return null } diff --git a/src/utils/api.js b/src/utils/api.js index 450285d7..c977c67f 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -351,6 +351,11 @@ export const api = { entityNew: apiVersion + '/entity/explorer/overview/new', // entity首页new数据概览 entityTotal: apiVersion + '/entity/explorer/overview/total' // entity首页total数据概览 } + }, + location: { + map: apiVersion + '/locationIntelligence/map', + density: apiVersion + '/locationIntelligence/population/density', + trend: apiVersion + '/locationIntelligence/active/subscribers' } } diff --git a/src/utils/hexbinDataConverter.js b/src/utils/hexbinDataConverter.js new file mode 100644 index 00000000..033e339d --- /dev/null +++ b/src/utils/hexbinDataConverter.js @@ -0,0 +1,51 @@ +import turf from 'turf' +import { propEach, featureReduce } from '@turf/meta' +import simpleStats from 'simple-statistics' +import { extent } from 'd3-array' + +// cellSize: 六角网格每一边的大小(公里) +export default function hexbinDataConverter (data, cellSize = 0.5) { + // 纽约的粗略地理边界框 + const bbox = [-74.24939, 40.50394, -73.701195, 40.90995] + // const bbox = [74, 18, 136, 42] + // const bbox = [74, 18, 136, 54] + + // 为给定的 bbox 和cellSize创建 hexbin(六边形) 几何形状 + const hexGrid = turf.hexGrid(bbox, cellSize) + + // 对我们的六边形几何体和数据点执行“空间连接” + const collected = turf.collect(hexGrid, data, 'data', 'data') + + // 为每个六边形设置一个唯一id + collected.features.forEach((feature, i) => { + feature.id = i + }) + // 过滤掉没有连接数据的多边形 + collected.features = collected.features.filter(d => d.properties.data.length) + + // 计算每个六边形里点的总数 count + propEach(collected, props => { + props.count = props.data.reduce((acc, cur) => acc += 1, 0) + }) + + // 将 count 拎出来 + const reduced = featureReduce(collected, (acc, cur) => { + acc.push(cur.properties.count) + return acc + }, []) + + // 用 ckmeans 算法将值按大小分成3(设计图中的颜色数量)层 + const levels = simpleStats.ckmeans(reduced, 3) + + // 将六边形层级和该层的范围添加到数据中 + propEach(collected, props => { + levels.forEach((level, index) => { + if (level.indexOf(props.count) > -1) { + props.level = index + props.levelRange = extent(level) + } + }) + }) + + return collected +} diff --git a/src/views/charts2/charts/entityDetail/EntityDetailMap.vue b/src/views/charts2/charts/entityDetail/EntityDetailMap.vue index 78574f76..01ad46a2 100644 --- a/src/views/charts2/charts/entityDetail/EntityDetailMap.vue +++ b/src/views/charts2/charts/entityDetail/EntityDetailMap.vue @@ -45,7 +45,7 @@ import axios from 'axios' import { api } from '@/utils/api' import 'maplibre-gl/dist/maplibre-gl.css' import _ from 'lodash' -import { ref } from 'vue' +import { ref, shallowRef } from 'vue' import { getNowTime, getSecond } from '@/utils/date-util' export default { @@ -71,35 +71,23 @@ export default { center: this.center, maxZoom: this.maxZoom, minZoom: this.minZoom, - zoom: 7, - transformRequest: function (url, resourceType) { - if (resourceType === 'Tile' && url.indexOf('https://api.maptiler.com/tiles/v3') > -1) { - const urlParams = url.split('.pbf')[0].split('/') - const z = urlParams[urlParams.length - 3] - const x = urlParams[urlParams.length - 2] - const y = urlParams[urlParams.length - 1] - const newUrl1 = `${window.location.protocol}//${window.location.host}/tiles/${z}/${x}/${y}.pbf` - return { - url: newUrl1, - credentials: 'include', - method: 'GET' + zoom: 7 + }) + maplibregl.addProtocol('cn', (params, callback) => { // 切片显示接口 防止跨域的问题 + fetch(`${params.url.split('://')[1]}`) + .then(t => { + if (t.status == 200) { + t.arrayBuffer().then(arr => { + callback(null, arr, null, null) + }) + } else { + callback(new Error(`Tile fetch error: ${t.statusText}`)) } - } - if (resourceType === 'SpriteJSON') { - return { - url: `${window.location.protocol}//${window.location.host}/tiles/sprite.json`, - credentials: 'include', - method: 'GET' - } - } - if (resourceType === 'SpriteImage') { - return { - url: `${window.location.protocol}//${window.location.host}/tiles/sprite.png`, - credentials: 'include', - method: 'GET' - } - } - } + }) + .catch(e => { + callback(new Error(e)) + }) + return { cancel: () => { } } }) this.queryData().then(res => { map.on('load', () => { @@ -330,11 +318,13 @@ export default { const tooltip = ref({ showTooltip: false }) + const myChart = shallowRef({}) return { timeFilter, currentPoint, tooltip, + myChart, maxZoom: 13, minZoom: 1, center: [116.38, 39.9] diff --git a/src/views/charts2/charts/entityDetail/mapStyle.js b/src/views/charts2/charts/entityDetail/mapStyle.js index 6c4390cd..3b0d7a0b 100644 --- a/src/views/charts2/charts/entityDetail/mapStyle.js +++ b/src/views/charts2/charts/entityDetail/mapStyle.js @@ -900,8 +900,8 @@ export default { }], metadata: { 'maptiler:copyright': 'This style was generated on MapTiler Cloud. Usage outside of MapTiler Cloud or MapTiler Server requires valid MapTiler Data package: https://www.maptiler.com/data/ -- please contact us.' }, - glyphs: '/tiles/fonts/{fontstack}/{range}.pbf?key=rB2y2a2rG8i9SEjOXQXl', - sprite: 'https://www.maptiler.com/tiles/sprite', + glyphs: 'cn:///tiles/fonts/{fontstack}/{range}.pbf', + sprite: 'cn:///tiles/sprite', bearing: 0, pitch: 0, center: [130, -20], diff --git a/src/views/location/Index.vue b/src/views/location/Index.vue new file mode 100644 index 00000000..908432ea --- /dev/null +++ b/src/views/location/Index.vue @@ -0,0 +1,744 @@ + + + + + + + + + + + + + + + + + + Population Density + + + + Active Subscribers + + 19334 + -{{valueToRangeValue(0.39542, unitTypes.percent).join(' ')}} + + + + Followed Subscribers + + + + + + + + + + MSISDN + 1-555-7777000 + + + + Group + Terrorist + + + Info + Leader + + + Location + China, Shanghai + + + + + + + + + + + + + + + + + + + + + + + + HEX + MSISDN + CID + + + {{currentPolygon.hexId}} + + + + + + Number + {{currentPolygon.count}} + + + Locals + {{currentPolygon.count}} + + + Visitors + {{currentPolygon.count}} + + + Roamers + {{currentPolygon.count}} + + + + + Group + Terrorist + + + Info + Leader + + + Location + China, Shanghai + + Trace Tracking + + + + Location Area Code + 12 + + + Mobile Network Code + 1 + + + Signal Strength + 4 + + + Communication Type + 4G + + + Number of Online + 1442 + + + Location + China, Shanghai + + + + + + + + + diff --git a/src/views/location/chartOption.js b/src/views/location/chartOption.js new file mode 100644 index 00000000..679bb652 --- /dev/null +++ b/src/views/location/chartOption.js @@ -0,0 +1,27 @@ +export const pieOption = { + tooltip: { + trigger: 'item' + }, + legend: { + right: 'right', + top: 'middle', + orient: 'vertical', + itemHeight: 6, + itemWidth: 16, + itemGap: 4, + left: 140, + formatter: function (name) { + return '' + } + }, + series: [ + { + name: 'Pie chart', + type: 'pie', + center: [75, '50%'], + label: { show: false }, + radius: [27, 42], + data: [] + } + ] +} diff --git a/vue.config.js b/vue.config.js index 9d6a1361..dd2b58ce 100644 --- a/vue.config.js +++ b/vue.config.js @@ -24,5 +24,16 @@ module.exports = { lintOnSave: true, devServer: { port: 80 + }, + configureWebpack: { + module: { + rules: [ + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto' + } + ] + } } }