diff --git a/src/assets/css/components/views/charts2/entityDetailSubscriberMap.scss b/src/assets/css/components/views/charts2/entityDetailSubscriberMap.scss index 1732e126..bb60ee17 100644 --- a/src/assets/css/components/views/charts2/entityDetailSubscriberMap.scss +++ b/src/assets/css/components/views/charts2/entityDetailSubscriberMap.scss @@ -23,9 +23,47 @@ .subscriber-map { height: 100%; width: 100%; + + .maplibregl-canvas:focus-visible { + outline: none; + } } .panel-chart__no-data { height: calc(100% - 46px); } } + + .subscriber-map-point-tooltip { + position: fixed; + background-color: white; + width: 200px; + border: 1px solid #C5C5C5; + box-shadow: -1px 1px 10px -1px rgba(205,205,205,0.85); + border-radius: 2px; + + .subscriber-map-point-tooltip__time { + padding: 10px; + font-size: 12px; + color: #353636; + font-weight: bold; + } + .subscriber-map-point-tooltip__coordinates { + padding: 0 10px 10px; + + .subscriber-map-point-tooltip__coordinate { + display: flex; + + .coordinate__label { + width: 115px; + font-size: 12px; + color: #575757; + } + .coordinate__value { + font-size: 12px; + color: #353636; + font-weight: bold; + } + } + } + } } diff --git a/src/utils/constants.js b/src/utils/constants.js index aef62996..98b7748f 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -98,7 +98,7 @@ export const entityType = { app: 'app', domain: 'domain', ip: 'ip', - subscribe: 'subscribe' + subscriber: 'subscriber' } export const knowledgeCardUpdateRecordType = { @@ -153,7 +153,7 @@ export const entityDetailTabConfig = [ ] }, { - name: 'subscribe', + name: 'subscriber', config: [ { name: entityDetailTabsName.deviceInformation, label: 'entities.deviceInformation', icon: 'cn-icon cn-icon-device-info', tag: 0 }, { name: entityDetailTabsName.accountInformation, label: 'entities.accountInformation', icon: 'cn-icon cn-icon-account-info', tag: 0 }, diff --git a/src/views/charts2/charts/entityDetail/EntityDetailMap.vue b/src/views/charts2/charts/entityDetail/EntityDetailMap.vue index 019a3bc1..8d78bc21 100644 --- a/src/views/charts2/charts/entityDetail/EntityDetailMap.vue +++ b/src/views/charts2/charts/entityDetail/EntityDetailMap.vue @@ -17,6 +17,21 @@
+ + +
+
{{dateFormatByAppearance(currentPoint.stat_time)}}
+
+
+
{{$t('overall.longitude')}}
+
{{currentPoint.longitude}}
+
+
+
{{$t('overall.latitude')}}
+
{{currentPoint.latitude}}
+
+
+
@@ -28,9 +43,10 @@ import maplibregl from 'maplibre-gl' import mapStyle from './mapStyle' import axios from 'axios' import { api } from '@/utils/api' +import 'maplibre-gl/dist/maplibre-gl.css' import _ from 'lodash' import { ref } from 'vue' -import { getNowTime } from '@/utils/date-util' +import { getNowTime, getSecond } from '@/utils/date-util' export default { name: 'EntityDetailMap', @@ -39,22 +55,19 @@ export default { ChartError, ChartNoData }, - data () { - return { - } - }, mounted () { this.initMap() }, methods: { - initMap () { + async initMap () { + const _this = this const map = new maplibregl.Map({ container: 'subscriberMap', style: mapStyle, - center: [116.39, 39.9], - zoom: 9, - maxZoom: 12, - minZoom: 4, + 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('/') @@ -85,52 +98,102 @@ export default { } }) - /* axios.get(`${api.entity.locationTrack}?resource=${this.entity.entityName}`).then(res => { - - }) */ - this.toggleLoading(false) - const res = { - status: 200, - data: { - data: { - result: [ - { - stat_time: 1701259999, - subscriber_id: '883849', - subscriber_longitude: 116.29, - subscriber_latitude: 39.8 - }, - { - stat_time: 1701269999, - subscriber_id: '883849', - subscriber_longitude: 116.34, - subscriber_latitude: 39.85 - }, - { - stat_time: 1701279999, - subscriber_id: '883849', - subscriber_longitude: 116.29, - subscriber_latitude: 39.9 - }, - { - stat_time: 1701299999, - subscriber_id: '883849', - subscriber_longitude: 116.39, - subscriber_latitude: 39.9 - }, - { - stat_time: 1701289999, - subscriber_id: '883849', - subscriber_longitude: 116.3, - subscriber_latitude: 39.97 + this.queryData().then(res => { + this.showError = false + const result = _.get(res, 'data.data.result', []).sort((a, b) => a.stat_time - b.stat_time) + const route = this.generateRouteGEOJSON(result) + const points = this.generatePointsGEOJSON(result) + map.on('load', () => { + map.jumpTo({ center: this.computeMapCenter(result) }) + map.zoomTo(this.computeMapZoom(result)) + map.loadImage( + `${window.location.protocol}//${window.location.host}/images/entity-detail/track-point.png`, + (error, image) => { + if (error) throw error + map.addImage('trace-point', image) + map.addSource('points', { + type: 'geojson', + data: points + }) + map.addLayer(this.generatePointsLayer()) } - ] - } - } - } - const result = _.get(res, 'data.data.result', []).sort((a, b) => a.stat_time - b.stat_time) + ) + map.addSource('route', { + type: 'geojson', + lineMetrics: true, + data: route + }) + map.addLayer(this.generateRouteLayer()) - const route = { + this.myChart = map + + // point的鼠标事件 + map.on('mouseenter', 'points', () => { + _this.tooltip.showTooltip = true + }) + map.on('mouseleave', 'points', () => { + _this.tooltip.showTooltip = false + }) + map.on('mousemove', 'points', ({ point, originalEvent, features }) => { + _this.tooltip.x = originalEvent.clientX + 10 + _this.tooltip.y = originalEvent.clientY - 95 + _this.currentPoint = { ...features[0].properties } + }) + }) + }).catch(e => { + this.showError = true + console.error(e) + this.errorMsg = this.errorMsgHandler(e) + }).finally(() => { + this.toggleLoading(false) + }) + }, + queryData () { + return new Promise((resolve, reject) => { + resolve({ + status: 200, + data: { + data: { + result: [ + { + stat_time: 1701259999, + subscriber_id: '883849', + subscriber_longitude: 116.29, + subscriber_latitude: 39.8 + }, + { + stat_time: 1701269999, + subscriber_id: '883849', + subscriber_longitude: 116.34, + subscriber_latitude: 39.85 + }, + { + stat_time: 1701279999, + subscriber_id: '883849', + subscriber_longitude: 116.29, + subscriber_latitude: 39.9 + }, + { + stat_time: 1701299999, + subscriber_id: '883849', + subscriber_longitude: 116.39, + subscriber_latitude: 39.9 + }, + { + stat_time: 1701289999, + subscriber_id: '883849', + subscriber_longitude: 116.3, + subscriber_latitude: 39.97 + } + ] + } + } + }) + }) + // return axios.get(`${api.entity.locationTrack}?resource=${this.entity.entityName}`) + }, + generateRouteGEOJSON (result) { + return { type: 'FeatureCollection', features: [ { @@ -142,8 +205,9 @@ export default { } ] } - - const points = { + }, + generatePointsGEOJSON (result) { + return { type: 'FeatureCollection', features: result.map((r, index) => { return { @@ -154,68 +218,91 @@ export default { }, properties: { index: index + 1, - stat_time: r.stat_time + stat_time: r.stat_time, + longitude: r.subscriber_longitude, + latitude: r.subscriber_latitude } } }) } - - map.on('load', () => { - map.loadImage( - `${window.location.protocol}//${window.location.host}/images/entity-detail/track-point.png`, - (error, image) => { - if (error) throw error - map.addImage('trace-point', image) - map.addSource('points', { - type: 'geojson', - data: points - }) - map.addLayer({ - id: 'points', - type: 'symbol', - source: 'points', - layout: { - 'icon-image': 'trace-point', - 'icon-size': 0.7, - 'icon-offset': [0, -20], - 'text-field': ['get', 'index'], - 'text-font': ['Noto Sans Bold'], - 'text-offset': [0, -1.1], - 'text-size': 13 - }, - paint: { - 'text-color': '#FFF' - } - }) - } - ) - map.addSource('route', { - type: 'geojson', - lineMetrics: true, - data: route - }) - map.addLayer({ - id: 'route', - source: 'route', - type: 'line', - paint: { - 'line-color': '#E26154', - 'line-width': 4, - 'line-gradient': [ - 'interpolate', - ['linear'], - ['line-progress'], - 0, - '#F4B32F', - 1, - '#E26154' - ] - }, - layout: { - 'line-cap': 'round', - 'line-join': 'round' - } - }) + }, + generateRouteLayer () { + return { + id: 'route', + source: 'route', + type: 'line', + paint: { + 'line-color': '#E26154', + 'line-width': 4, + 'line-gradient': [ + 'interpolate', + ['linear'], + ['line-progress'], + 0, + '#F4B32F', + 1, + '#E26154' + ] + }, + layout: { + 'line-cap': 'round', + 'line-join': 'round' + } + } + }, + generatePointsLayer () { + return { + id: 'points', + type: 'symbol', + source: 'points', + layout: { + 'icon-image': 'trace-point', + 'icon-size': 0.7, + 'icon-offset': [0, -20], + 'text-field': ['get', 'index'], + 'text-font': ['Noto Sans Bold'], + 'text-offset': [0, -1.1], + 'text-size': 13 + }, + paint: { + 'text-color': '#FFF' + } + } + }, + computeMapCenter (result) { + const longitude = result.map(r => r.subscriber_longitude) + const latitude = result.map(r => r.subscriber_latitude) + return [ + _.floor((_.max(longitude) + _.min(longitude)) / 2, 6), + _.floor((_.max(latitude) + _.min(latitude)) / 2, 6) + ] + }, + computeMapZoom (result) { + const longitude = result.map(r => r.subscriber_longitude) + const latitude = result.map(r => r.subscriber_latitude) + const longitudeRange = _.max(longitude) - _.min(longitude) + const latitudeRange = _.max(latitude) - _.min(latitude) + const max = _.max([this.minZoom, 7 - Math.log2(_.max([longitudeRange, latitudeRange]))]) + return _.min([max, this.maxZoom]) + }, + reload (startTime, endTime, dateRangeValue) { + this.toggleLoading(true) + this.timeFilter = { startTime: getSecond(startTime), endTime: getSecond(endTime), dateRangeValue: dateRangeValue } + this.queryData().then(res => { + this.showError = false + const result = _.get(res, 'data.data.result', []).sort((a, b) => a.stat_time - b.stat_time) + const route = this.generateRouteGEOJSON(result) + const points = this.generatePointsGEOJSON(result) + this.myChart.getSource('route').setData(route) + this.myChart.getSource('points').setData(points) + this.myChart.jumpTo({ center: this.computeMapCenter(result) }) + this.myChart.zoomTo(this.computeMapZoom(result)) + }).catch(e => { + this.showError = true + console.error(e) + this.errorMsg = this.errorMsgHandler(e) + }).finally(() => { + this.toggleLoading(false) }) } }, @@ -225,8 +312,19 @@ export default { const { startTime, endTime } = getNowTime(dateRangeValue) timeFilter.value.startTime = startTime timeFilter.value.endTime = endTime + + const currentPoint = ref({}) + const tooltip = ref({ + showTooltip: false + }) + return { - timeFilter + timeFilter, + currentPoint, + tooltip, + maxZoom: 11, + minZoom: 1, + center: [116.38, 39.9] } } } diff --git a/src/views/entityExplorer/EntityDetail.vue b/src/views/entityExplorer/EntityDetail.vue index 7079f5ac..766330ba 100644 --- a/src/views/entityExplorer/EntityDetail.vue +++ b/src/views/entityExplorer/EntityDetail.vue @@ -39,7 +39,7 @@ export default { entityData.appName = query.entityName break } - case 'subscribe': { + case 'subscriber': { panelType = panelTypeAndRouteMapping.subscribeEntityDetail entityData.appName = query.entityName break