diff --git a/src/assets/css/components/views/charts2/entityDetailSubscriberMap.scss b/src/assets/css/components/views/charts2/entityDetailSubscriberMap.scss index d8b5c7ea..f5a58696 100644 --- a/src/assets/css/components/views/charts2/entityDetailSubscriberMap.scss +++ b/src/assets/css/components/views/charts2/entityDetailSubscriberMap.scss @@ -1,6 +1,9 @@ .subscriber-map { height: 100%; + svg { + fill: #fff; + } .subscriber-map-header { height: 34px; padding-bottom: 10px; @@ -32,6 +35,202 @@ .panel-chart__no-data { height: calc(100% - 46px); } + + .analysis-statistics { + width: 310px; + overflow-y: auto; + //padding-right: 5px; + + .analysis-statistics__subscribers { + padding-left: 20px; + + .analysis-statistics__subscriber { + margin-bottom: 10px; + + &:last-of-type { + margin-bottom: 0; + } + + background-color: #F7F7F7; + border-left: 1px solid rgb(226,229,236); + border-radius: 2px; + + .subscriber__body { + padding: 0 12px; + margin-top: -10px; + .body__item { + display: flex; + + .item__label { + padding-right: 10px; + text-align: right; + width: 60px; + font-size: 12px; + color: #353636; + } + .item__value { + font-size: 12px; + font-weight: bold; + color: #233447; + } + } + + .body-item-record { + margin-top: 10px; + + .item-record__header { + font-family: Helvetica; + font-size: 16px; + color: #353636; + font-weight: 400; + height: 38px; + line-height: 38px; + } + .item-record__info { + + } + .item-record__timeline { + margin-left: 6px; + + .el-timeline { + padding-left: 0; + //min-height: 300px; + height: 360px; + overflow: auto; + + &.el-timeline--hide { + } + + .el-timeline-item { + padding-bottom: 0; + .el-timeline-item__tail { + border-left: 2px dotted #cccccc; + margin-left: 2px; + } + .el-timeline-item__node--normal { + background-image: radial-gradient(#DE3434 20%, transparent); + outline: #F7F7F7 solid 6px; + margin-left: 2px; + } + } + .el-timeline-item:last-child { + padding-bottom: 0; + } + } + .timeline__info { + display: flex; + padding-bottom: 10px; + + .timeline__info--circle { + display: flex; + flex-direction: column; + .info__circle { + width: 17px; + height: 17px; + margin-left: -2px; + border-radius: 50%; + background-image: radial-gradient(#DE3434 20%, transparent); + outline: rgba(222,52,52,0.30) solid 4px; + margin-top: 2px; + } + .info__line { + border-left: 2px #cccccc dotted; + height: 34px; + margin-left: 6px; + transition: all 0.2s; + } + } + .timeline__info--item { + padding-left: 13px; + display: flex; + flex-direction: column; + font-size: 12px; + color: #666666; + + .info--item__value { + color: #333; + font-weight: 500; + margin-left: 4px; + } + } + } + .timeline__item { + display: flex; + flex-direction: column; + font-size: 12px; + color: #666666; + + .item__value { + color: #333; + font-weight: 500; + margin-left: 4px; + } + } + .item-record__btn, .item-record__btn-disabled { + padding-right: 6px; + cursor: pointer; + text-align: center; + } + .item-record__btn-disabled { + cursor: no-drop; + } + .scroll-view { + width: 100%; + height: 384px; + overflow-y: scroll; + position: relative; + + .scroll__item { + width: 100%; + height: 58px; + display: flex; + + .item-circle { + display: flex; + flex-direction: column; + .circle-circle { + width: 10px; + height: 10px; + margin-left: 2px; + border-radius: 50%; + background-color: #DE3434; + } + .circle-line { + border-left: 2px #cccccc dotted; + height: 34px; + margin-left: 6px; + margin-top: 6px; + transition: all 0.2s; + } + } + + .item-content { + display: flex; + flex-direction: column; + font-size: 12px; + color: #666666; + padding-left: 16px; + + .item__value { + color: #333; + font-weight: 500; + margin-left: 4px; + } + } + } + .scroll-list { + position: absolute; + top: 0; + left: 0; + cursor: pointer; + } + } + } + } + } + } + } + } } .subscriber-map-point-tooltip { diff --git a/src/views/charts2/charts/entityDetail/EntityDetailMap.vue b/src/views/charts2/charts/entityDetail/EntityDetailMap.vue index 01ad46a2..e97a82d7 100644 --- a/src/views/charts2/charts/entityDetail/EntityDetailMap.vue +++ b/src/views/charts2/charts/entityDetail/EntityDetailMap.vue @@ -13,9 +13,55 @@
- - -
+ + +
+ + +
+
+
+ + +
+
+
{{ $t('location.trackRecord') }}
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+ {{$t('overall.location')}}: {{record.subscriberLongitude}}, {{record.subscriberLatitude}} +
+
+ {{ $t('location.timeOfArrival') }}: {{dateFormatByAppearance(Number(record.time))}} +
+
+ {{ $t('location.residenceTime') }}: {{record.stayTime}} +
+
+
+
+
+
+
+
+
+
+
@@ -46,7 +92,9 @@ import { api } from '@/utils/api' import 'maplibre-gl/dist/maplibre-gl.css' import _ from 'lodash' import { ref, shallowRef } from 'vue' -import { getNowTime, getSecond } from '@/utils/date-util' +import { getNowTime, getSecond, dateFormatByAppearance } from '@/utils/date-util' +import unitConvert from '@/utils/unit-convert' +import { unitTypes } from '@/utils/constants' export default { name: 'EntityDetailMap', @@ -57,12 +105,14 @@ export default { }, mounted () { this.initMap() + this.queryTraceTracking() }, unmounted () { - this.myChart && this.myChart.remove() + this.myChart && this.myChart?.remove?.() this.myChart = null }, methods: { + dateFormatByAppearance, async initMap () { const _this = this const map = new maplibregl.Map({ @@ -142,7 +192,7 @@ export default { }) }, queryData () { - /*return new Promise((resolve, reject) => { + /* return new Promise((resolve, reject) => { resolve({ status: 200, data: { @@ -182,7 +232,7 @@ export default { } } }) - })*/ + }) */ const params = { resource: this.entity.entityName, startTime: getSecond(this.timeFilter.startTime), @@ -305,6 +355,104 @@ export default { }).finally(() => { this.toggleLoading(false) }) + }, + async queryTraceTracking () { + const params = { + ...this.timeFilter, + subscriberIds: `'${this.entity.entityName}'`, + level: this.mapLevel + } + this.trackingSubscriber.subscriberId = this.entity.entityName + try { + const response = await axios.get(api.location.tracking, { params }) + + if (response.data.data.result) { + const find = response.data.data.result.find(item => item.subscriberId === this.trackingSubscriber.subscriberId) + if (find) { + this.trackingSubscriber.trackRecords = find.trackRecords + } else { + this.trackingSubscriber.trackRecords = [] + } + this.trackingSubscriber.show = false + } else { + this.trackingSubscriber.trackRecords = [] + } + + // 计算停留时间 + if (this.trackingSubscriber.trackRecords.length > 0) { + this.trackingNoData = false + this.showTrackingError = false + const trackRecords = this.trackingSubscriber.trackRecords + // 初始化时间线可视范围角标 + if (trackRecords.length < 7) { + this.trackingSubscriber.scrollStartIndex = 0 + this.trackingSubscriber.scrollEndIndex = trackRecords.length + } else { + this.trackingSubscriber.scrollStartIndex = 0 + this.trackingSubscriber.scrollEndIndex = 7 + } + + if (trackRecords && trackRecords.length > 0) { + for (let i = 0; i < trackRecords.length; i++) { + if (i > 0) { + if ((trackRecords[i - 1].subscriberLongitude === trackRecords[i].subscriberLongitude) && (trackRecords[i - 1].subscriberLatitude === trackRecords[i].subscriberLatitude)) { + // 如果连续两条地址重复,则将时间累加,并将上一条删除,键值-1继续循环 + if (i > 1 && trackRecords[i - 2]) { + const stayTime = unitConvert(trackRecords[i - 2].time - trackRecords[i].time, unitTypes.time, 's') + if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) { + stayTime[0] = Number(stayTime[0]).toFixed(0) + } + trackRecords[i].stayTime = stayTime.join(' ') + } else { + // 数据只有2条,或者第1条和第2条地点重复,删除1,合并时间 + const stayTime = unitConvert(trackRecords[i - 1].time - trackRecords[i].time, unitTypes.time, 's') + if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) { + stayTime[0] = Number(stayTime[0]).toFixed(0) + } + trackRecords[i].stayTime = stayTime.join(' ') + } + trackRecords.splice(i - 1, 1) + i = i - 1 + } else { + const stayTime = unitConvert(trackRecords[i - 1].time - trackRecords[i].time, unitTypes.time, 's') + if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) { + stayTime[0] = Number(stayTime[0]).toFixed(0) + } + trackRecords[i].stayTime = stayTime.join(' ') + } + if (i === trackRecords.length - 1) { + // 初始化数据时,重置偏移量和列表高度 + this.trackingSubscriber.startOffset = 0 + this.trackingSubscriber.listHeight = i * this.scrollInfo.itemSize + } + } else { + trackRecords[i].stayTime = '-' + } + } + } + this.trackingSubscriber.trackRecords = trackRecords + } else { + this.trackingNoData = true + } + } catch (e) { + this.showTrackingError = true + this.trackingErrorMsg = this.errorMsgHandler(e) + console.error(e) + } finally { + // this.loading.trackingMapLoading = false + } + }, + onScroll (e) { + // 当前滚动位置 + const scrollTop = e.target.scrollTop + // 列表开始索引 + const startIndex = Math.floor(scrollTop / this.scrollInfo.itemSize) || 1 + // 列表结束索引 + const endIndex = Math.ceil((scrollTop + this.scrollInfo.containerHeight) / this.scrollInfo.itemSize) + this.trackingSubscriber.scrollStartIndex = startIndex + this.trackingSubscriber.scrollEndIndex = endIndex + // 列表距离顶部距离 + this.trackingSubscriber.startOffset = scrollTop - (scrollTop % this.scrollInfo.itemSize) } }, setup () { @@ -319,6 +467,25 @@ export default { showTooltip: false }) const myChart = shallowRef({}) + const mapLevel = ref('2') + const trackingSubscriber = ref({ + subscriberId: '', + show: false, + showLine: false, + scrollStartIndex: 0, + scrollEndIndex: 7, + startOffset: 0, + listHeight: 0, + trackRecords: [] + }) + const trackingSubscriberList = ref([]) + const scrollInfo = ref({ + itemSize: 50, + containerHeight: 360 + }) + const trackingNoData = ref(true) + const showTrackingError = ref(false) + const trackingErrorMsg = ref('') return { timeFilter, @@ -327,7 +494,14 @@ export default { myChart, maxZoom: 13, minZoom: 1, - center: [116.38, 39.9] + center: [116.38, 39.9], + mapLevel, + trackingSubscriber, + trackingSubscriberList, + scrollInfo, + trackingNoData, + showTrackingError, + trackingErrorMsg } } }