diff --git a/src/utils/api.js b/src/utils/api.js index 44352144..0a65fc4a 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -357,7 +357,8 @@ export const api = { density: apiVersion + '/locationIntelligence/population/density', trend: apiVersion + '/locationIntelligence/active/trend', count: apiVersion + '/locationIntelligence/active/count', - baseStation: apiVersion + '/locationIntelligence/baseStation' + baseStation: apiVersion + '/locationIntelligence/baseStation', + followedSubscriber: apiVersion + '/locationIntelligence/followed/subscribers' } } diff --git a/src/views/location/Index.vue b/src/views/location/Index.vue index cf13a6f1..e971c1ff 100644 --- a/src/views/location/Index.vue +++ b/src/views/location/Index.vue @@ -24,61 +24,61 @@
-
-
-
{{$t('locationIntelligence.populationDensity')}}
-
-
-
-
-
-
{{legend.start}}~{{legend.end}}
-
{{legend.count}}
+
+
+
{{$t('locationIntelligence.populationDensity')}}
+
+
+
+
+
+
{{legend.start}}~{{legend.end}}
+
{{legend.count}}
+
-
-
-
{{$t('locationIntelligence.activeSubscribers')}}
-
-
{{activeCount}}
-
-{{valueToRangeValue(activeCountChain, unitTypes.percent).join(' ')}}
+
+
{{$t('locationIntelligence.activeSubscribers')}}
+
+
{{activeCount}}
+
-{{valueToRangeValue(activeCountChain, unitTypes.percent).join(' ')}}
+
+
-
-
-
Followed Subscribers
-
- +
-
-
+
@@ -100,6 +100,7 @@
+
@@ -183,7 +184,7 @@ import { useRoute, useRouter } from 'vue-router' import { urlParamsHandler, overwriteUrl } from '@/utils/tools' import axios from 'axios' import { api } from '@/utils/api' -import { h3ToGeoBoundary } from 'h3-js' +import { h3ToGeo, h3ToGeoBoundary } from 'h3-js' export default { name: 'Location', @@ -201,6 +202,7 @@ export default { methods: { valueToRangeValue, async initMap () { + console.info(h3ToGeo('8931aa42cb7ffff')) const _this = this const map = new maplibregl.Map({ container: 'analysisMap', @@ -234,6 +236,7 @@ export default { map.on('load', async function () { console.info('map loaded') /* 地图色块 */ + _this.updateBoundaryBox() const hexagonData = await _this.queryHexagon() // 将查到的h3hexagon数据转为geojson const polygonSourceData = _this.hexagonDataConverter(hexagonData) @@ -257,12 +260,16 @@ export default { /* 地图上的基站 */ const baseStationData = await _this.queryBaseStation() + _this.renderMarker(baseStationData, _this.tooltipType.baseStation) + /* 地图上的人 */ + const mapFollowedSubscriberData = await _this.queryMapFollowedSubscriber() + _this.renderMarker(mapFollowedSubscriberData, _this.tooltipType.human) /* 右侧关注列表 */ /* 右上角折线图 */ - _this.renderActiveSubscribersLine() + await _this.renderActiveSubscribersLine() }) }, async renderDensityPie () { @@ -272,7 +279,7 @@ export default { this.loading.pieLoading = true try { const response = await axios.get(api.location.density, { params }) - const densityData = response.data.data.list + const densityData = response.data.data // 按值的大小分为5组,并计算各组数量和颜色 this.pieValueRamp = this.calculateValueRamp(densityData) const option = _.cloneDeep(pieOption) @@ -301,15 +308,15 @@ export default { } this.loading.lineLoading = true try { - const curCountResponse = await axios.get(api.location.count, { ...params, cycle: 0, data: { cycle: 0 } })// 当前周期活跃用户总数 - const preCountResponse = await axios.get(api.location.count, { ...params, cycle: 1, data: { cycle: 1 } })// 上一周期活跃用户总数 + const curCountResponse = await axios.get(api.location.count, { params: { ...params, cycle: 0 } })// 当前周期活跃用户总数 + const preCountResponse = await axios.get(api.location.count, { params: { ...params, cycle: 1 } })// 上一周期活跃用户总数 this.activeCount = curCountResponse.data.data.total const preActiveCount = preCountResponse.data.data.total if (preActiveCount !== 0) { - this.activeCountChain = (this.activeCount - preActiveCount) / preActiveCount + this.activeCountChain = preActiveCount !== '0' ? (this.activeCount - preActiveCount) / preActiveCount : '' } const trendResponse = await axios.get(api.location.trend, { params }) - const activeSubscribersData = trendResponse.data.data.result.values + const activeSubscribersData = trendResponse.data.data.result const option = _.cloneDeep(appListChartOption) option.series[0].data = activeSubscribersData.map(d => { return [d[0], d[1], unitTypes.number] @@ -334,7 +341,7 @@ export default { this.loading.hexagonLoading = true try { const response = await axios.get(api.location.map, { params }) - return response.data.data.list + return response.data.data } catch (e) { this.errorMsgHandler(e) console.error(e) @@ -346,8 +353,34 @@ export default { async queryBaseStation () { this.loading.baseStationLoading = true try { - const response = await axios.get(api.location.baseStation) - return response.data.data.list + // const response = await axios.get(api.location.baseStation) + const response = [ + { + longitude: 116.38, + latitude: 39.9 + }, + { + longitude: 116.39, + latitude: 39.9 + }, + { + longitude: 116.383, + latitude: 39.886 + }, + { + longitude: 116.378, + latitude: 39.902 + }, + { + longitude: 116.369, + latitude: 39.91 + }, + { + longitude: 116.38, + latitude: 39.91 + } + ] + return response // response.data.data.list } catch (e) { this.errorMsgHandler(e) console.error(e) @@ -356,9 +389,76 @@ export default { } return [] }, + async queryMapFollowedSubscriber () { + this.loading.timeBarLoading = true + console.info(this.timeFilter) + console.info(this.minuteTimeFilter) + try { + const response = await axios.get(api.location.followedSubscriber, { params: this.minuteTimeFilter }) + return response.data.data.list + } catch (e) { + this.errorMsgHandler(e) + console.error(e) + } finally { + this.loading.timeBarLoading = false + } + return [] + }, + renderMarker (data, type) { + let svg + if (type === this.tooltipType.baseStation) { + svg = '' + } else if (type === this.tooltipType.human) { + svg = '' + } + data.forEach(marker => { + const el = document.createElement('div') + el.className = `map-marker map-marker--${type}` + el.innerHTML = svg + // 鼠标事件,控制tooltip显示和marker尺寸 + el.addEventListener('mouseenter', e => { + this.markerDom = el + if (!this.tooltip.mouseInMarkerOrTooltip) { + this.tooltip.x = e.clientX + 15 - e.offsetX + this.tooltip.y = e.clientY + 15 - e.offsetY + } + this.tooltip.mouseInMarkerOrTooltip = true + this.tooltip.type = this.tooltipType.baseStation + this.tooltip.showMarkerTooltip = true + el.classList.add('map-marker--hover') + }) + el.addEventListener('mouseleave', event => { + const tooltipDom = document.getElementById('tooltip') + if (!tooltipDom.contains(event.relatedTarget)) { + el.classList.remove('map-marker--hover') + this.tooltip.mouseInMarkerOrTooltip = false + this.tooltip.showMarkerTooltip = false + } + }) + + new maplibregl.Marker({ element: el }) + .setLngLat([marker.longitude, marker.latitude]) + .addTo(this.mapChart) + }) + }, + updateBoundaryBox () { + const boundaryBox = this.mapChart.getBounds() + /*this.boundaryBox = { + maxLongitude: boundaryBox.getEast(), + maxLatitude: boundaryBox.getNorth(), + minLongitude: boundaryBox.getWest(), + minLatitude: boundaryBox.getSouth() + }*/ + this.boundaryBox = { + maxLongitude: 140, + maxLatitude: 50, + minLongitude: 100, + minLatitude: 30 + } + }, // 先使用min=0的等宽分组法,若后续出现特大或特小的异常值导致等宽分组效果不理想,考虑用分位数分组法 calculateValueRamp (data) { - const max = _.maxBy(data, d => d.number) + const max = _.maxBy(data, d => Number(d.number)) const maxLength = String(max.number).length const maxLegend = Math.ceil(max.number / Math.pow(10, maxLength - 1)) * Math.pow(10, maxLength - 1) const result = [] @@ -395,7 +495,7 @@ export default { } }, getHexagonFillColor (number) { - const ramp = this.pieValueRamp.filter(r => number >= r.start && number < r.end) + const ramp = this.pieValueRamp.filter(r => Number(number) >= r.start && Number(number) <= r.end) if (ramp.length > 0) { return ramp[0].color.split(',').map(n => Number(n)) } @@ -413,7 +513,7 @@ export default { hoverTrigger('hexGrid', _this.currentPolygon.id, false) }) this.mapChart.on('mousemove', 'hexagon', ({ point, originalEvent, features }) => { - if (!_this.tooltip.mouseIsInMarker) { + if (!_this.tooltip.mouseInMarkerOrTooltip) { _this.tooltip.showPolygonTooltip = true _this.tooltip.type = _this.tooltipType.hexagon if (_this.tooltip.type === _this.tooltipType.hexagon && _this.currentPolygon.id && _this.currentPolygon.id !== features[0].id) { @@ -434,10 +534,16 @@ export default { } }, tooltipMouseEnter () { + this.tooltip.mouseInMarkerOrTooltip = true }, tooltipMouseMove () { }, - tooltipMouseLeave () { + tooltipMouseLeave (event) { + if (this.markerDom && !this.markerDom.contains(event.relatedTarget)) { + this.tooltip.mouseInMarkerOrTooltip = false + this.tooltip.showMarkerTooltip = false + this.markerDom.classList.remove('map-marker--hover') + } }, reload (startTime, endTime, dateRangeValue) { this.timeFilter = { startTime: getSecond(startTime), endTime: getSecond(endTime), dateRangeValue: dateRangeValue } @@ -467,6 +573,9 @@ export default { } }, watch: { + activeTab (n) { + + } }, computed: { tooltipHeaderColor () { @@ -520,7 +629,7 @@ export default { timeFilter.value.startTime = parseInt(startTimeParam) timeFilter.value.endTime = parseInt(endTimeParam) } - const minuteTimeFilter = ref({ startTime: timeFilter.value.startTime - 60, endTime: timeFilter.value.endTime }) + const minuteTimeFilter = ref({ startTime: timeFilter.value.endTime - 60, endTime: timeFilter.value.endTime }) const tooltip = ref({ type: '' }) @@ -556,6 +665,7 @@ export default { pieValueRamp, // 饼图数值坡度,动态获取 boundaryBox, // 查六边形的范围,minLongitude、maxLongitude、minLatitude、maxLatitude mapChart, // 地图对象 + markerDom: shallowRef(null), // 记录当前鼠标悬停的marker的dom pieChart, // 饼图对象 pieOption, lineChart, // 折线图对象 @@ -643,17 +753,41 @@ export default { border-radius: 50%; cursor: default; padding: 0; + transition: height .1s linear, width .1s linear; + + svg { + transition: height .1s linear, width .1s linear; + } &.map-marker--human { background-color: #233447; + + svg { + width: 14px; + height: 14px; + } } &.map-marker--base-station { background-color: #585B5F; + + svg { + width: 12px; + height: 12px; + } } &.map-marker--hover { width: 36px; height: 36px; border: 2px solid rgba(255,255,255,1); + + &.map-marker--human svg { + width: 23px; + height: 23px; + } + &.map-marker--base-station svg { + width: 20px; + height: 20px; + } } } }