【面试 - 遇到的问题 - 优化 - 地图】腾讯地图轨迹回放 - 回放的轨迹时间要和现实时间对应(非匀速)
目录
- 背景
- 轨迹回放 - 匀速
- 效果图
- `TrackPlaybackDialog.vue` 代码
- `TMapNew.vue` 代码
- 轨迹回放 - 非匀速
- 效果图
- `TrackPlaybackDialog.vue` 代码
- `TMapNew.vue` 代码
背景
腾讯地图轨迹回放是匀速回放的,但是客户要求根据现实时间,什么时间点在某个点位
- 【腾讯地图轨迹回放是匀速回放】:比如一段轨迹中不同时间有三四个点是在同一位置(类似警员在某一点位N秒没动,不同时间取到的点位是一个),轨迹回放的时候,停留的这个点直接经过了,而不是与实际一致轨迹停留N秒,即 轨迹回放时停留点的时间 ≠ 警员实际停留时间,所有的轨迹回放是匀速的。
- 【客户要求根据现实时间,什么时间点在某个点位】:每两个点之间轨迹回放的时间与实际时间一致,也就是警员在某一点位N秒没动,轨迹回放的时候也要在该点停留N秒之后再继续,即 轨迹回放时停留点的时间 = 警员实际停留时间,所有的轨迹回放不是匀速的。
或者 警员有时开车有时走路,时速是不一样的,要求轨迹回放与实际时速一致,而不是一直匀速
轨迹回放 - 匀速
将所有点位一次回放完成 ----匀速
代码中只需看标注 【主要代码】 的代码,其余代码不重要
效果图
35秒匀速回放所有轨迹
TrackPlaybackDialog.vue
代码
<!-- 轨迹回放 -->
<template><el-dialogv-loading="loading":title="title + '轨迹回放'"width="90%"top="5vh":close-on-click-modal="false"destroy-on-closeappend-to-body:visible.sync="dialogVisible"@close="handleClose"><template #title><span class="el-dialog__title">{{ title + '轨迹回放' }}</span><el-button class="drawer_btn" type="primary" size="mini" @click="drawerHandler">{{ isShowDrawer ? '隐藏' : '显示' }}部门辖区/执勤区域</el-button></template><TMapNewv-if="dialogVisible"ref="TMapRef"map-name="basicInspection":id-name="'TrackPlayback' + idNameTail"@moveEnd="moveEnd"@updateNowLc="updateNowLc"/><div class="operation">回放时间:<!-- <el-time-selectv-model="startTime"placeholder="起始时间"size="mini":picker-options="startTimeOptions"/>至<el-time-selectv-model="endTime"class="end_time"placeholder="结束时间"size="mini":picker-options="endTimeOptions"/> --><el-date-pickerv-model="time"type="datetimerange"range-separator="至"start-placeholder="开始日期"end-placeholder="结束日期"size="mini"format="yyyy-MM-dd HH:mm"value-format="yyyy-MM-dd HH:mm:ss":clearable="false"/><el-button size="mini" @click="moveHandler">{{ isMove ? '停止回放' : '开始回放' }}</el-button><el-select v-model="speed" class="speed" placeholder="请选择" size="mini"><el-optionv-for="item in speedOptions":key="item.speed":label="item.speed + '倍速'":value="item.speed"/></el-select><div class="time_line"><div class="bg"><div v-for="item in timeRangeList" :key="item" class="item"><span>{{ item }}</span></div><divclass="range":style="{'--timeRangeWidth': timeRangeWidth * 100 + '%','--timeRangeLeft': timeRangeLeft * 100 + '%'}"/></div></div><el-selectv-if="trackType === '1'"v-model="sblx"class="sblx"placeholder="请选择"size="mini"><el-optionv-for="(item, index) in $common.getDic('mon_jwdc_sblx')":key="'sblx_' + index":label="item.label":value="item.value"/></el-select></div><div v-show="!!trackType" class="info"><div class="info_item"><div class="info_item_label">{{ trackType === '1' ? '民警' : '号牌号码' }}:</div><div class="info_item_value mj">{{ infoData[trackType === '1' ? 'xm' : 'hphm'] | noDataFilter('暂无') }}</div></div><div class="info_item"><div class="info_item_label">部门:</div><div class="info_item_value bm">{{ (infoData.dept_name || infoData.dwjc || infoData.dwmc) | noDataFilter('暂无') }}</div></div><div class="info_item"><div class="info_item_label">里程:</div><div class="info_item_value gw">{{ lc | noDataFilter('暂无') }}{{ lc === 0 || lc ? 'km' : '' }}</div></div><div class="info_item"><div class="info_item_label">当前行驶距离:</div><div class="info_item_value gw">{{ curPosition.distanceM | noDataFilter('-') }}米</div></div><div class="info_item"><div class="info_item_label">当前坐标:</div><div class="info_item_value gw">{{ curPosition.positionText || `[${curPosition.lat || '-'},${curPosition.lng || '-'}]` }}</div></div><div class="info_item"><div class="info_item_label">当前时间:</div><div class="info_item_value gw">{{ curPosition.gpsTime | noDataFilter('暂无') }}</div></div></div></el-dialog>
</template><script>
import {queryPoliceTrackDetail,queryCarTrackDetail,queryDutyPointList,queryPoliceTrackList,queryCarTrackList,queryAddressByLonLat
} from '@/api/taizhou/service-inspector.js'
import dayjs from 'dayjs'
import { deviceType_MOTO, setDeptArea } from '../index.js'export default {name: 'TrackPlaybackDialog',components: {},props: {// currentGlbm: {// type: Object,// required: true// }idNameTail: {type: String,default: ''}},data() {return {loading: false,title: '',trackType: '',dialogVisible: false,infoData: {},time: [dayjs().format('YYYY-MM-DD') + ' 00:00:00', dayjs().format('YYYY-MM-DD HH:mm:ss')],// startTime: '',// endTime: '',positions: [],lc: 0,isMove: false,speed: 1,isMoveStartFirst: true,timeRangeList: [...Array(24).keys()],timeRangeLeft: '0',timeRangeWidth: '0',sblx: '1',speedOptions: [{ speed: 1 },{ speed: 2 },{ speed: 3 },{ speed: 4 },{ speed: 8 },{ speed: 16 }],curPosition: {},isShowDrawer: true,dutyAreaInfo: {},deptAreaInfo: {}}},computed: {ax() {const obj = {1: queryPoliceTrackDetail,2: queryCarTrackDetail,4: queryCarTrackDetail}return obj[this.trackType]}// startTimeOptions() {// return {// start: '00:00',// step: '00:30',// end: '23:30',// maxTime: this.endTime// }// },// endTimeOptions() {// return {// start: '00:00',// step: '00:30',// end: '23:30',// minTime: this.startTime// }// }},watch: {time(val, oldVal) {if (!this.dialogVisible) returnconst [start, end] = valif (+new Date(end) - +new Date(start) > 24 * 60 * 60 * 1000) {this.$message.error('回放时间范围必须在一天内,请重新选择')this.time = oldVal} else {this.getTimeRange()this.queryTrackDetail()}},sblx() {if (!this.dialogVisible || this.trackType !== '1') returnthis.queryTrackDetail()},speed() {if (!this.dialogVisible) returnthis.isMoveStartFirst = true}},created() {},mounted() {},methods: {getTimeRange() {const [start, end] = this.timeconst startH = start.slice(11, 13) * 1const startM = start.slice(14, 16) * 1const endH = end.slice(11, 13) * 1const endM = end.slice(14, 16) * 1const isSameDay = start.slice(8, 10) === end.slice(8, 10)const allRange = 24 * 60if (!isSameDay) {this.timeRangeList = [...Array(24).keys()].map((item) => (item + startH) % 24)const timeRange = allRange - (startH * 60 + startM) + endH * 60 + endMthis.timeRangeWidth = timeRange / allRangethis.timeRangeLeft = 0} else {const timeRange = endH * 60 + endM - (startH * 60 + startM)this.timeRangeWidth = timeRange / allRangethis.timeRangeLeft = (startH * 60 + startM) / allRange}},open(data, { title = '', type = '' }) {this.title = titlethis.trackType = typeconsole.log('data--轨迹回放弹框--', data)this.dialogVisible = truethis.infoData = data.properties || data || {}this.$nextTick(() => {// const ssbm = '331002005300' // 测试数据const ssbm = this.infoData.dept_code || this.infoData.dwdm || this.infoData.ssbmsetDeptArea(this,ssbm,{ isSetCenter: false, clearArea: 'drawerRedPolygon' },(areaData) => {this.deptAreaData = areaData}) // 部门辖区/** ** 执勤区域展示 - start ****/const { dutyAreaInfo } = this.infoDataconsole.log('dutyAreaInfo----open打印', dutyAreaInfo)if (dutyAreaInfo) {const { center, drawerType, ...areaInfo } = dutyAreaInfothis.dutyAreaInfo[drawerType] = areaInfo // 保存执勤区域数据 - 便于隐藏区域后再展示区域// 回溯 - 地图中展示的警员执勤区域带过来,轨迹回放这里也展示这个执勤区域const { drawerAreaHandler } = this.$refs.TMapRefsetTimeout(() => {drawerAreaHandler(drawerType, areaInfo, drawerType)}, 1000)} else {// 否则,调接口取区域数据,然后回显区域this.queryDutyPointList() // 区域}/** ** 执勤区域展示 - end ****/})this.getTimeRange()setTimeout(() => {if (this.infoData.timeRange && this.infoData.timeRange[0] && this.infoData.timeRange[1]) {;[this.time[0], this.time[1]] = this.infoData.timeRangethis.time.push({})this.time.pop()} else {this.trackQueryHandler()}}, 200)},trackQueryHandler() {const axObj = {1: queryPoliceTrackList,2: queryCarTrackList}axObj[this.trackType]({page: 1,rows: 10,queryStartTime: this.time[0],queryEndTime: this.time[1],jh: this.infoData.jh}).then((res) => {this.$common.CheckCode(res, null, () => {const cur = (res.data.length && res.data[0]) || {}if (Object.keys(cur).length === 0) {this.queryTrackDetail()} else {this.time = [cur.kssj || '', cur.jssj || '']}})})},drawerHandler() {this.isShowDrawer = !this.isShowDrawerconst { drawerAreaHandler, clearAreaHandler } = this.$refs.TMapRefif (this.isShowDrawer) {Object.keys(this.dutyAreaInfo).forEach((key) => {drawerAreaHandler(key, this.dutyAreaInfo[key], key)})drawerAreaHandler('drawerRedPolygon', this.deptAreaData, 'drawerRedPolygon')} else {clearAreaHandler('all')}},queryTrackDetail() {this.moveEnd()let time = {}if (this.time.length) {time = {queryStartTime: this.time[0] || '',queryEndTime: this.time[1] || ''}}const { jh, deviceIndexCode, recentUserId } = this.infoDataconst paramsObj = {1: {sblx: this.sblx,jh,userId: recentUserId},2: {deviceIndexCode},4: {deviceIndexCode}}this.loading = truethis.ax({...time,...paramsObj[this.trackType]}).then((res) => {this.$common.CheckCode(res,null,() => {res = {code: 200,msg: null,data: {gpsList: [...Array(10).keys()].map((index) => {const point = ['109.62616781132476,19.07651100671366','109.52052961372806,19.000955875798667','109.52779570317193,18.889409778960484','109.43389509635472,18.804246369159575',// '109.55741914575538,18.750798850652597',// '109.4389254607205,18.687804325385454','109.4389254607205,18.687804325385454','109.4389254607205,18.687804325385454','109.5864836229498,18.66556534760466','109.72845239144965,18.73121481942799','109.74745608740011,18.87354409698365','109.8709801082789,18.91584923707182','109.80502610332212,19.07598276944228']const times = ['2025-02-12 08:00:00','2025-02-12 08:00:01','2025-02-12 08:00:05','2025-02-12 08:00:07','2025-02-12 08:00:10','2025-02-12 08:00:15','2025-02-12 08:00:20','2025-02-12 08:00:22','2025-02-12 08:00:30','2025-02-12 08:00:35']return {deviceType: '执法记录仪',deviceName: null,deptName: '事故中队',deptCode: '331002000300',recentUserId: '1000704',deviceIndexCode: 'K380772',// lng: '120.217989',lng: point[index].split(',')[0],recentUserName: '徐捷',// gpsTime: this.$dayjs(// +new Date('2025-02-12 08:00:00') + index * 3 * 1000// ).format('YYYY-MM-DD HH:mm:ss'),gpsTime: times[index],// lat: '30.212518',lat: point[index].split(',')[1],// location: '120.217989,30.212518',location: point[index],distanceM: index * 10}}),distanceKm: 108.79},timestamp: 1748573847277}this.lc = res?.data?.distanceKm || 0this.positions = (res?.data?.gpsList || []).map((item) => {const nameFieldObj = {1: 'xm',2: 'hphm',3: 'deviceName',4: 'hphm'}const nameObj = {1: item.recentUserName || '-',2: this.infoData.hphm || '-',4: this.infoData.hphm || '-'}return {...item,jd: item.lng,wd: item.lat,// type: item.gjlx,type:this.trackType === '2' && this.infoData.deviceType === deviceType_MOTO? '4': this.trackType,// name: item[nameFieldObj[item.gjlx]]name: nameObj[this.trackType]}})const { basicInspectionTrack } = this.$refs.TMapRefbasicInspectionTrack(this.positions)this.loading = false},() => {this.loading = false})}).catch(() => {this.loading = false})},queryDutyPointList() {queryDutyPointList({fzr_jh: this.infoData.jh,queryStartTime: this.time[0],queryEndTime: this.time[1]}).then((res) => {this.$common.CheckCode(res, null, () => {const typeObj = {1: 'Circle',2: 'Line',3: 'Polygon'}const listObj = {lineList: [],circleList: [],polygonList: []}res.data.forEach((item) => {const { zb, post_type } = itemconst areaList = zb? JSON.parse(zb).map((item) => new TMap.LatLng(item.y * 1, item.x * 1)): []const info = {areaList,center: areaList[0],circleCenter: areaList[0],...item}listObj[typeObj[post_type].toLowerCase() + 'List'].push(info)})Object.keys(typeObj).forEach((key) => {const type = typeObj[key]const drawerInfo = {dataList: listObj[type.toLowerCase() + 'List']}this.dutyAreaInfo[`drawer${type}`] = drawerInfothis.$refs.TMapRef.drawerAreaHandler(`drawer${type}`, drawerInfo, `drawer${type}`)})})})},moveHandler() {this.isMove = !this.isMoveconst { basicInspectionMove, basicInspectionResumeMove, resetMoveJwdlength } =this.$refs.TMapRefif (this.isMove) {if (this.isMoveStartFirst) {this.isMoveStartFirst = falseconst startTime = this.positions[0].gpsTimeconst endTime = this.positions[this.positions.length - 1].gpsTimeconst duration = dayjs(endTime).diff(dayjs(startTime), 'seconds') * 1000resetMoveJwdlength()basicInspectionMove(this.positions.map((item) => {return new TMap.LatLng(Number(item.wd), Number(item.jd))}),duration / this.speed)} else {basicInspectionResumeMove()}} else {this.positionTrans()this.pauseTrack()}},moveEnd() {this.isMove = falsethis.isMoveStartFirst = truethis.pauseTrack()},updateNowLc(index) {this.curPosition = { ...this.positions[index], positionText: '' }},pauseTrack() {this.$refs?.TMapRef?.basicInspectionPauseTrack()},positionTrans() {this.curPosition.lng &&this.curPosition.lat &&queryAddressByLonLat({longitude: this.curPosition.lng,latitude: this.curPosition.lat}).then((res) => {this.$common.CheckCode(res, null, () => {this.curPosition.positionText = res.data?.address || ''})})},handleClose() {this.pauseTrack()this.dialogVisible = falsethis.infoData = {}this.time[0] = dayjs().format('YYYY-MM-DD') + ' 00:00:00'this.time[1] = dayjs().format('YYYY-MM-DD HH:mm:ss')this.positions = []this.lc = 0this.isMove = falsethis.isMoveStartFirst = truethis.speed = 1this.timeRangeList = [...Array(24).keys()]this.sblx = '1'this.curPosition = {}this.isShowDrawer = truethis.dutyAreaInfo = {}this.deptAreaInfo = {}}}
}
</script><style lang='scss' scoped>
@import '@/styles/dialog-scss.scss';$rightInfoWidth: 240px;>>> .el-dialog {.el-dialog__header {.drawer_btn {margin-left: 10px;}}.el-dialog__body {height: 90vh;padding: 0 !important;position: relative;#TrackPlayback,[id^='TrackPlayback'] {width: 100%;height: 100%;}.z-index {z-index: 1999;}.operation {width: calc(100% - #{$rightInfoWidth} - 30px);height: 55px;line-height: 55px;padding: 0 10px;position: absolute;top: 10px;left: 0;background-color: #ffffffa3;text-align: left;display: flex;align-items: center;@extend .z-index;// .el-date-editor {// width: 120px;// &.end_time {// margin-right: 10px;// }// }.el-date-editor {width: 290px;margin-right: 10px;.el-range-input {width: calc(50% - 20px);}.el-range-separator {width: 20px;}.el-range__close-icon {width: 0;}}.speed {margin: 0 10px;width: 80px;}.sblx {margin-left: 10px;width: 100px;}.time_line {flex: 1;.bg {$size: 7px;width: 100%;height: $size;border-radius: $size;display: flex;background-color: #e4e7ed;position: relative;.item {flex: 1;position: relative;z-index: 9;&::before {content: '';position: absolute;top: 0;left: -#{$size / 2};width: $size;height: $size;background-color: #fff;border-radius: $size;}span {position: absolute;left: -10px;top: 10px;display: inline-block;width: 20px;height: 20px;line-height: 20px;text-align: center;color: #409eff;}}.range {$range_size: calc(#{$size} * 2);position: absolute;// top: -20px;width: var(--timeRangeWidth);height: $size;left: var(--timeRangeLeft);background-color: #409eff;&::before,&::after {content: '';position: absolute;// top: 0;top: -5px;width: $range_size;height: $range_size;background-color: #fff;border-radius: $range_size;border: 1px solid #c7cbd2;}&::before {left: calc((#{$range_size} / 2) * -1);}&::after {right: calc((#{$range_size} / 2) * -1);}}}}}.info {width: $rightInfoWidth;position: absolute;top: 10px;right: 20px;padding: 10px;background-color: #fff;border-radius: 8px;text-align: left;font-size: 16px;@extend .z-index;&_item {display: flex;margin-bottom: 5px;color: #333;&_label {}&_value {flex: 1;line-height: 22px;&.bm,&.gw {font-size: 12px;}}}}}
}
</style>
TMapNew.vue
代码
<!-- 腾讯地图 -->
<template><div :id="idName" class="TMap" />
</template><script>
import { mapGetters } from 'vuex'
import policeImg from '@/assets/images/TXMap/icon-auxiliary-police.png'
import carImg from '@/assets/images/TXMap/icon-police-car.png'
import motoImg from '@/assets/images/TXMap/icon-motorcycle.png'
import monitorImg from '@/assets/images/TXMap/icon-monitor.png'
import startImg from '@/assets/images/start.png'
import endImg from '@/assets/images/end.png'
import nameBg from '@/assets/images/TXMap/point_name_bg.png'export default {name: 'TMapNew',components: {},props: {mapName: {type: String,required: true},idName: {type: String,default: 'TXMapContanier'}},data() {return {map: null,curZoom: 0,setLabelZoom: 17,multiMarker: null, // 点位图标MultiLabel: null, // 点位图标顶部文字描述infoWindow: null, // 信息窗口MultiPolyline: null, // 折线 - 运动轨迹multiPolylineLayer: {}, // 多个 简单折线multiCircleLayer: {}, // 多个 简单圆multiPolygonLayer: {}, // 多个 简单多边形multiRedPolygonLayer: {}, // 多个 简单多边形 -- 部门辖区multiLabelLayer: {}, // 多个 labeltrackQueryMultiMarker: {jy: null,jc: null,jk: null},trackQueryMultiLabel: {jy: null,jc: null,jk: null},editor: null,activeType: 'marker',activeId: '', // 值格式为 6C5895CE-B42D-4E9B-A8FA-81135761CBDDmoveJwdlength: 0}},computed: {...mapGetters(['sysConfigData'])},mounted() {// this.initMap()window.onbeforeunload = () => {localStorage.removeItem('TXMapIsCanLoad')}const timer = setInterval(() => {const TXMapIsCanLoad = localStorage.getItem('TXMapIsCanLoad')if (TXMapIsCanLoad === 'true') {this.initMap()clearInterval(timer)}}, 100)},beforeDestroy() {this.map?.destroy()this.infoWindow?.close()this.map = nullthis.multiMarker?.setMap(null)this.MultiLabel?.setMap(null)this.infoWindow?.setMap(null)this.MultiPolyline?.setMap(null)this.multiPolylineLayer = {}this.multiCircleLayer = {}this.multiPolygonLayer = {}this.multiRedPolygonLayer = {}this.multiLabelLayer = {}this.trackQueryMultiMarker = {jy: null,jc: null,jk: null}this.trackQueryMultiLabel = {jy: null,jc: null,jk: null}this.editor = null},methods: {// 初始化地图initMap() {this[this.mapName + 'Init']()},setMapCenter(center) {this.map.setCenter(center)},setMapZoom(level) {this.map.setZoom(level)},// 创建wmts图层newWMTSLayer() {const url = this.sysConfigData.mon_map_wmts_urlconst { map } = thisif (!url) returnnew TMap.WMTSLayer({url, // 地图服务地址map, // 展示图层的地图对象minZoom: 3, // 最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3maxZoom: 20, // 最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20visible: true, // 是否可见,默认为truezIndex: 1, // 图层绘制顺序opacity: 0.9, // 图层透明度,默认为1params: {// OGC标准的WMTS地图服务的GetTile接口的参数layers: 'topp:raster_cgcs2000%3Ataizhou2m_cgcs2000', // 请求的图层名称tileMatrixSet: 'taizhou2m%3A11' // 瓦片矩阵数据集}})},// 标记点// point(markerId, styles, pointArr) {// this[this.mapName + 'Point'](markerId, styles, pointArr)// },// 打开弹框openInfoWindow(position, content) {this.infoWindow = new TMap.InfoWindow({map: this.map,position: new TMap.LatLng(position[0], position[1]),offset: { x: 0, y: -32 }, // 设置信息窗相对position偏移像素content: content})},/** ** 台州勤务督察 -页面地图 start ****/basicInspectionInit() {this.basicInspectionInitCommon()},setPointMapInit() {this.basicInspectionInitCommon()this.$emit('setInitPoint')this.map.on('click', (e) => {this.$emit('getPoint', e.latLng)})},setPoint({ jd, wd }, isSetCenter = false) {isSetCenter && this.setMapCenter(new TMap.LatLng(Number(jd), Number(wd)))this.MultiMarker = new TMap.MultiMarker({id: 'marker-layer',map: this.map,styles: {marker: new TMap.MarkerStyle({width: 25,height: 35,anchor: { x: 16, y: 32 }})},geometries: [{id: 'demo',styleId: 'marker',position: new TMap.LatLng(jd * 1, wd * 1),properties: {title: 'marker'}}]})},removePoint() {this.MultiMarker?.setMap(null)},basicInspectionInitCommon() {var location = (this.sysConfigData.map_location || '121.427648,28.661939').split(',')// console.log(Number(location[1]), Number(location[0]))const featuresObj = {gs: null,nw: []}this.curZoom = this.sysConfigData.map_level || 14this.map = new TMap.Map(this.idName, {zoom: this.curZoom,center: new TMap.LatLng(Number(location[1]), Number(location[0])),baseMap: {type: 'vector',// features: null // 本地跑项目用// // features: [] // 内网用features: featuresObj[this.sysConfigData.mon_map_yslx] || null}})/** ** 获取地图首次加载完成 start ****/this.map.off('tilesloaded', tilesLoad)this.map.on('tilesloaded', tilesLoad)const that = thisfunction tilesLoad() {console.log('地图加载完成')that.map.off('tilesloaded', tilesLoad)}/** ** 获取地图首次加载完成 end ****/this.newWMTSLayer()this.map.removeControl(TMap.constants.DEFAULT_CONTROL_ID.ROTATION) // 移除腾讯地图旋转控件this.map.removeControl(TMap.constants.DEFAULT_CONTROL_ID.ZOOM) // 移除腾讯地图缩放控件this.map.on('zoom_changed', (params) => {console.log('params----zoom_changed', params)this.curZoom = params.zoomif (this.curZoom > this.setLabelZoom) {!Object.keys(this.multiLabelLayer).length && this.$emit('setLabel', this.curZoom)} else {this.clearMultiLabel()}})if (this.idName === 'trackQueryTXMapContanier') {let timer = nullthis.map.on('center_changed', (params) => {clearTimeout(timer)timer = setTimeout(() => {const center = params.centerconsole.log('params----center_changed', params)console.log('center----center_changed', center)this.$emit('getPointArr', { jd: center.lng, wd: center.lat, isCenterChange: true })}, 500)})}},basicInspectionPoint(markerId, pointArr, isSetCenter = false) {// console.log('pointArr----basicInspectionPoint', pointArr)this.MultiMarker?.setMap(null)this.infoWindow?.setMap(null)if (!pointArr.length) return// 初始markerthis.MultiMarker = this.basicInspectionCommonPoint(markerId, pointArr, isSetCenter)this.MultiMarker.on('click', this.basicInspectionCommonClick)},basicInspectionPointText(markerId, pointArr) {// console.log('pointArr----basicInspectionPointText', pointArr)this.MultiLabel?.setMap(null)this.infoWindow?.setMap(null)if (!pointArr.length) return// 初始markerthis.MultiLabel = this.basicInspectionCommonPointText(pointArr)this.MultiLabel.on('click', this.basicInspectionCommonClick)},trackQueryPoint(markerId, pointArr, pointTypeArr, pointType, isSetCenter = false) {console.log('pointArr----trackQueryPoint', pointArr)const gjlxObj = {1: 'jy',2: 'jc',3: 'jk'}const gjlx = gjlxObj[pointType];['1', '2', '3'].forEach((item) => {if (!pointTypeArr.includes(item) || pointType === item) {this.trackQueryMultiMarker[gjlxObj[item]]?.setMap(null)}})// this.MultiMarker?.setMap(null)this.infoWindow?.setMap(null)if (!pointArr.length) return// 初始markerthis.trackQueryMultiMarker[gjlx] = this.basicInspectionCommonPoint(markerId,pointArr,isSetCenter)this.trackQueryMultiMarker[gjlx].on('click', this.basicInspectionCommonClick)},trackQueryPointText(markerId, pointArr, pointTypeArr, pointType) {console.log('pointArr----trackQueryPointText', pointArr)const gjlxObj = {1: 'jy',2: 'jc',3: 'jk'}const gjlx = gjlxObj[pointType];['1', '2', '3'].forEach((item) => {if (!pointTypeArr.includes(item) || pointType === item) {this.trackQueryMultiLabel[gjlxObj[item]]?.setMap(null)}})// this.MultiLabel?.setMap(null)this.infoWindow?.setMap(null)if (!pointArr.length) return// 初始markerthis.trackQueryMultiLabel[gjlx] = this.basicInspectionCommonPointText(pointArr)this.trackQueryMultiLabel[gjlx].on('click', this.basicInspectionCommonClick)},basicInspectionCommonPoint(markerId, pointArr, isSetCenter) {if (isSetCenter) {const first = pointArr[0]this.setMapCenter(new TMap.LatLng(Number(first.wd), Number(first.jd)))}return new TMap.MultiMarker({id: markerId,map: this.map,styles: {police: new TMap.MarkerStyle({width: 24,height: 40,anchor: { x: 0, y: 0 },src: policeImg}),car: new TMap.MarkerStyle({width: 50,height: 25,anchor: { x: 0, y: 0 },src: carImg}),moto: new TMap.MarkerStyle({width: 50,height: 25,anchor: { x: 0, y: 0 },src: motoImg}),monitor: new TMap.MarkerStyle({width: 40,height: 30,anchor: { x: 0, y: 0 },src: monitorImg})},geometries: pointArr.map((item, index) => {const styleIdObj = {1: 'police',2: 'car',3: 'monitor',4: 'moto'}return {id: index,// styleId: 'police',styleId: styleIdObj[item.type || '1'],position: new TMap.LatLng(item.wd, item.jd),properties: this.basicInspectionCommonProperties(item, index)}})})},basicInspectionCommonPointText(pointArr) {const commonStyle = {height: 25, // 高度anchor: { x: 15, y: 26 }, // 锚点位置src: nameBg, // 标注点图片url或base64地址color: '#fff', // 标注点文本颜色size: 14, // 标注点文本文字大小offset: { x: 0, y: 0 } // 标注点文本文字基于direction方位的偏移属性}return new TMap.MultiMarker({map: this.map,styles: {police: new TMap.MarkerStyle({width: 60, // 宽度...commonStyle}),car: new TMap.MarkerStyle({width: 100, // 宽度...commonStyle}),moto: new TMap.MarkerStyle({width: 100, // 宽度...commonStyle}),monitor: new TMap.MarkerStyle({width: 100, // 宽度...commonStyle})},geometries: pointArr.map((item, index) => {const styleIdObj = {1: 'police',2: 'car',3: 'monitor',4: 'moto'}let content = item.name || ''if (['3'].includes(item.type)) {content = content.slice(0, 5) + (content.length > 5 ? '...' : '')}return {styleId: styleIdObj[item.type || '1'],position: new TMap.LatLng(item.wd, item.jd),content,properties: this.basicInspectionCommonProperties(item, index)}})})},basicInspectionCommonProperties(item, index) {return {type: item.type || '1', // 1警员、2警车、3监控...item}},basicInspectionCommonClick(data) {console.log('data--basicInspectionCommonClick--', data)this.infoWindow?.destroy()this.$emit('pointClick', data)},basicInspectionTrack(trackArr) {this.MultiPolyline?.setMap(null)this.multiMarker?.setMap(null)if (!trackArr.length) returnconst trackStart = trackArr[0] || {}console.log('trackStart----', trackStart)this.setMapCenter(new TMap.LatLng(Number(trackStart.wd), Number(trackStart.jd)))this.MultiPolyline = new TMap.MultiPolyline({map: this.map, // 绘制到目标地图// 折线样式定义styles: {style_blue: new TMap.PolylineStyle({color: '#3777FF', // 线填充色width: 4, // 折线宽度borderWidth: 2, // 边线宽度borderColor: '#FFF', // 边线颜色lineCap: 'round', // 线端头方式eraseColor: 'rgba(190,188,188,1)'})},geometries: [{id: 'erasePath',styleId: 'style_blue',paths: trackArr.map((item) => {return new TMap.LatLng(Number(item.wd), Number(item.jd))})}]})const iconStyleObj = {1: {width: 24,height: 40,anchor: { x: 13, y: 30 },src: policeImg},2: {width: 50,height: 25,anchor: { x: 25, y: 12 },src: carImg},4: {width: 50,height: 25,anchor: { x: 25, y: 12 },src: motoImg}}const nameStyleObj = {1: {width: 60, // 宽度height: 25, // 高度anchor: { x: 30, y: 55 } // 锚点位置},2: {width: 100, // 宽度height: 25, // 高度anchor: { x: 40, y: 38 } // 锚点位置},4: {width: 100, // 宽度height: 25, // 高度anchor: { x: 40, y: 38 } // 锚点位置}}this.multiMarker = new TMap.MultiMarker({map: this.map,styles: {icon: new TMap.MarkerStyle({faceTo: 'screen',rotate: 0,...iconStyleObj[trackStart.type]}),name: new TMap.MarkerStyle({src: nameBg, // 标注点图片url或base64地址color: '#fff', // 标注点文本颜色size: 14, // 标注点文本文字大小offset: { x: 0, y: 0 }, // 标注点文本文字基于direction方位的偏移属性...nameStyleObj[trackStart.type]})},geometries: [{id: 'iconMove',styleId: 'icon',position: new TMap.LatLng(trackStart.wd, trackStart.jd)},{id: 'nameMove',styleId: 'name',position: new TMap.LatLng(trackStart.wd, trackStart.jd),content: trackStart.name,properties: trackStart}]})this.multiMarker.on('move_ended', () => {this.$emit('moveEnd')this.resetMoveJwdlength()})// 使用marker 移动接口, https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker// this.basicInspectionMove(path)this.multiMarker.on('moving', (e, passedDistance) => {let passedLatLngs = e?.iconMove?.passedLatLngs || [] // 此处取iconMove或nameMove都可以,因为这两个marker的position是相同的if (this.moveJwdlength < passedLatLngs.length) {this.moveJwdlength = passedLatLngs.lengththis.$emit('updateNowLc', this.moveJwdlength - 1)};['iconMove', 'nameMove'].forEach((key) => {if (passedLatLngs) {// 使用路线擦除接口 eraseTo, https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVectorthis.MultiPolyline.eraseTo('erasePath',passedLatLngs.length - 1,passedLatLngs[passedLatLngs.length - 1])}})})},resetMoveJwdlength() {this.moveJwdlength = 0},basicInspectionMove(path, duration) {console.log('duration----打印', duration)console.log('path--basicInspectionMove--打印', path)this.multiMarker.moveAlong({iconMove: {path,duration},nameMove: {path,duration}},{autoRotation: true})},// 暂停轨迹回放basicInspectionPauseTrack() {this.multiMarker?.pauseMove()},// 继续从暂停的轨迹开始回放轨迹basicInspectionResumeMove() {this.multiMarker?.resumeMove()},// 清除已有图形clearArea() {this.editor.select([this.activeId]) // 选中已经绘制的图形this.editor.delete() // 删除已选中图形this.activeId = ''},selectArea(id) {this.clearArea()this.activeType = idthis.editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.DRAW)this.editor.setActiveOverlay(id)},setToolsGeometryEditor() {console.log('setToolsGeometryEditor----打印')var polygon = new TMap.MultiPolygon({map: this.map})var circle = new TMap.MultiCircle({map: this.map})this.editor = new TMap.tools.GeometryEditor({// TMap.tools.GeometryEditor 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditormap: this.map, // 编辑器绑定的地图对象overlayList: [// 可编辑图层 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor#4{overlay: polygon,id: 'polygon'},{overlay: circle,id: 'circle'}],actionMode: TMap.tools.constants.EDITOR_ACTION.DRAW, // 编辑器的工作模式activeOverlayId: 'marker', // 激活图层snappable: true // 开启吸附})// 监听绘制结束事件,获取绘制几何图形this.editor.on('draw_complete', (geometry) => {// this.editor.destroy()this.editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.INTERACT)var { id, radius } = geometrythis.activeId = idconst maxRadius = this.sysConfigData.mon_map_maxRadius * 1 || 500if (!!radius && radius > maxRadius) {this.clearArea()this.$message.warning(`圆的半径超过最大限制${maxRadius}米,请重新选择区域`)this.selectArea(this.activeType)return}this.$emit('drawComplete', geometry)})// 绘制失败,返回失败信息this.editor.on('draw_error', (errInfo) => {const { errorDesc, errorType } = errInfoif (errorDesc === 'geometry illegals' && errorType === 1) {// 多边形自相交错误信息this.$message.error('仅支持简单多边形,右击取消上一标点或按Esc键取消当前绘制图案')}})},/** ** 画区域 start ****/drawerAreaHandler(funName, data, clearType) {console.log('funName----打印', funName)console.log('data----打印', data)this[funName] && this[funName](data, clearType)data.center && this.setMapCenter(data.center)},clearAreaHandler(type = 'all') {!type && (type = 'all')console.log('clearAreaHandler --- type----打印', type)const typeObj = {drawerLine: 'multiPolylineLayer',drawerCircle: 'multiCircleLayer',drawerPolygon: 'multiPolygonLayer'}const blueTypeList = ['drawerLine', 'drawerCircle', 'drawerPolygon']if (['all', 'allDutyArea'].includes(type)) {blueTypeList.forEach((drawerType) => {Object.keys(this[typeObj[drawerType]]).forEach((key) => {this[typeObj[drawerType]][key]?.setMap(null)})})}if (blueTypeList.includes(type)) {Object.keys(this[typeObj[type]]).forEach((key) => {this[typeObj[type]][key]?.setMap(null)})}if (['all', 'drawerRedPolygon'].includes(type)) {Object.keys(this.multiRedPolygonLayer).forEach((key) => {this.multiRedPolygonLayer[key]?.setMap(null)})}},drawerLine(data, clearType) {console.log('this.$cloneDeep(data)----drawerLine打印', this.$cloneDeep(data))this.clearAreaHandler(clearType)this.$cloneDeep(data).dataList.forEach((item, index) => {this.multiPolylineLayer[index] = new TMap.MultiPolyline({id: `polyline-layer-${index}`,map: this.map,geometries: [{id: `line-${index}`, // 折线唯一标识,删除时使用paths: item.areaList}]})})},drawerCircle(data, clearType) {console.log('this.$cloneDeep(data)----drawerCircle打印', this.$cloneDeep(data))this.clearAreaHandler(clearType)this.$cloneDeep(data).dataList.forEach((item, index) => {this.multiCircleLayer[index] = new TMap.MultiCircle({map: this.map,geometries: [{id: `circle-${index}`,styleId: 'circle',center: item.circleCenter,radius: item.radius}]})})},drawerPolygon(data, clearType) {console.log('this.$cloneDeep(data)----drawerPolygon打印', this.$cloneDeep(data))this.clearAreaHandler(clearType)this.$cloneDeep(data).dataList.forEach((item, index) => {this.multiPolygonLayer[index] = new TMap.MultiPolygon({id: `polygon-layer-${index}`, // 图层idmap: this.map, // 显示多边形图层的底图geometries: [{id: `polygon-${index}`, // 多边形图形数据的标志信息styleId: 'polygon', // 样式idpaths: item.areaList, // 多边形的位置信息properties: {// 多边形的属性数据title: 'polygon'}}]})})},/*** 【注】* drawerPolygon 和 drawerRedPolygon 不能合并 - 有的页面需要同时有两种样式的线(比如:部门辖区和执勤区域,两种边框展示要互不影响)*/drawerRedPolygon(data, clearType) {console.log('this.$cloneDeep(data)----drawerRedPolygon打印', this.$cloneDeep(data))this.clearAreaHandler(clearType)this.$cloneDeep(data).dataList.forEach((item, index) => {this.multiRedPolygonLayer[index] = new TMap.MultiPolygon({id: `multi-polygon-layer-${index}`, // 图层idmap: this.map, // 显示多边形图层的底图styles: {// 多边形的相关样式polygon: new TMap.PolygonStyle({color: 'rgba(0,91,255,0)', // 面填充色borderColor: 'rgba(241,30,52,1)', // 边线颜色borderWidth: 3 // 边线宽度})},geometries: [{id: 'multiPolygon', // 多边形图形数据的标志信息styleId: 'polygon', // 样式idpaths: item.areaList, // 多边形的位置信息properties: {// 多边形的属性数据title: 'multiPolygon'}}]})})},/** ** 画区域 end ****/clearMultiLabel() {Object.keys(this.multiLabelLayer).forEach((key) => {this.multiLabelLayer[key]?.setMap(null)delete this.multiLabelLayer[key]})},setMultiLabel(dataList) {this.clearMultiLabel()console.log('dataList----setMultiLabel打印', dataList)if (this.curZoom < this.setLabelZoom) return // 图层层级小于设置的图层层级时,不显示labelthis.$cloneDeep(dataList).forEach((item, index) => {this.multiLabelLayer[index] = new TMap.MultiLabel({map: this.map,styles: {label: new TMap.LabelStyle({color: '#3777FF', // 颜色属性size: 20, // 文字大小属性offset: { x: 0, y: 0 }, // 文字偏移属性单位为像素angle: 0, // 文字旋转属性alignment: 'center', // 文字水平对齐属性verticalAlignment: 'middle' // 文字垂直对齐属性})},geometries: [{id: `label-${index}`, // 点图形数据的标志信息styleId: 'label', // 样式idposition: item.position, // 标注点位置content: item.content, // 标注文本properties: {// 标注点的属性数据title: 'label'}}]})})}/** ** 台州勤务督察 -页面地图 end ****/}
}
</script><style lang='scss'>
.qwdc_card {width: 300px;background-color: #fff;// padding: 10px;text-align: left;.text_jb {background: linear-gradient(to bottom, #49befe, #3783fe); /* 从左到右渐变 */-webkit-background-clip: text; /* Safari/Chrome支持该属性 */color: transparent; /* 将文本颜色设置为透明 */}&_header {display: flex;margin-bottom: 5px;&_pic {$height: 50px;width: 40px;height: $height;margin-right: 10px;border: 1px solid #00a4ff;border-radius: 3px;background: linear-gradient(180deg, #fff, rgba(0, 121, 254, 0.07) 97%);text-align: center;&.iconfont {line-height: $height;font-size: 30px;color: #388bfd;// @extend .text_jb;}}&_info {flex: 1;&_name {// margin-bottom: 5px;font-size: 18px;white-space: pre-wrap;color: #7f7f7f;}&_bm {font-size: 14px;color: #d7d7d7;}}}&_body {&_item {margin-bottom: 5px;display: flex;&_label {color: #7f7f7f;}&_value {flex: 1;white-space: pre-wrap;line-height: 21px;font-size: 14px;color: #aaaaaa;.zt {padding: 0 5px;border: 1px solid transparent;border-radius: 3px;font-size: 12px;margin-right: 5px;color: #f59a23;border-color: #f59a23;&.success {border-color: #67c23a;color: #67c23a;}&.warning {border-color: #e6a23c;color: #e6a23c;}}}}}&_btns {padding-top: 10px;border-top: 1px solid #f2f2f2;position: relative;i {margin: 0 5px;cursor: pointer;font-size: 16px;// color: #388bfd;@extend .text_jb;}.tempMessage {position: absolute;top: -27px;left: 0;background: #000000d1;padding: 5px 10px;border-radius: 5px;color: #fff;}}
}
</style>
轨迹回放 - 非匀速
【思路】
- 把所有轨迹分成N小段,保存当前切到的索引 moveRangeIndex(用于最后判断是否所有轨迹走完);
- 每走一段,把这段数据存起来 alreadyMovePoint,用于擦除轨迹;
- 轨迹结束之后,判断 movePointLength 是否小于 所有点位数量,小于的话继续播放下一小段,
- 每一段轨迹回放前判断 moveRangeIndex 是否大于等于 所有点位数量,是的话表明所有轨迹播放完成(改变播放按钮状态),轨迹回放自动停止,并重置已经移动的点位
代码中只需看标注 【主要代码】 的代码,其余代码不重要
效果图
TrackPlaybackDialog.vue
代码
<!-- 轨迹回放 -->
<template><el-dialogv-loading="loading":title="title + '轨迹回放'"width="90%"top="5vh":close-on-click-modal="false"destroy-on-closeappend-to-body:visible.sync="dialogVisible"@close="handleClose"><template #title><span class="el-dialog__title">{{ title + '轨迹回放' }}</span><el-button class="drawer_btn" type="primary" size="mini" @click="drawerHandler">{{ isShowDrawer ? '隐藏' : '显示' }}部门辖区/执勤区域</el-button></template><TMapNewv-if="dialogVisible"ref="TMapRef"map-name="basicInspection":id-name="'TrackPlayback' + idNameTail":movePointLength="moveRangeIndex":allMovePointLength="positions.length"@continuMove="moveRange"@moveEnd="moveEnd"@updateNowLc="updateNowLc"/><div class="operation">回放时间:<el-date-pickerv-model="time"type="datetimerange"range-separator="至"start-placeholder="开始日期"end-placeholder="结束日期"size="mini"format="yyyy-MM-dd HH:mm"value-format="yyyy-MM-dd HH:mm:ss":clearable="false"/><el-button size="mini" @click="moveHandler">{{ isMove ? '停止回放' : '开始回放' }}</el-button><el-select v-model="speed" class="speed" placeholder="请选择" size="mini"><el-optionv-for="item in speedOptions":key="item.speed":label="item.speed + '倍速'":value="item.speed"/></el-select><div class="time_line"><div class="bg"><div v-for="item in timeRangeList" :key="item" class="item"><span>{{ item }}</span></div><divclass="range":style="{'--timeRangeWidth': timeRangeWidth * 100 + '%','--timeRangeLeft': timeRangeLeft * 100 + '%'}"/></div></div><el-selectv-if="trackType === '1'"v-model="sblx"class="sblx"placeholder="请选择"size="mini"><el-optionv-for="(item, index) in $common.getDic('mon_jwdc_sblx')":key="'sblx_' + index":label="item.label":value="item.value"/></el-select></div><div v-show="!!trackType" class="info"><div class="info_item"><div class="info_item_label">{{ trackType === '1' ? '民警' : '号牌号码' }}:</div><div class="info_item_value mj">{{ infoData[trackType === '1' ? 'xm' : 'hphm'] | noDataFilter('暂无') }}</div></div><div class="info_item"><div class="info_item_label">部门:</div><div class="info_item_value bm">{{ (infoData.dept_name || infoData.dwjc || infoData.dwmc) | noDataFilter('暂无') }}</div></div><div class="info_item"><div class="info_item_label">里程:</div><div class="info_item_value gw">{{ lc | noDataFilter('暂无') }}{{ lc === 0 || lc ? 'km' : '' }}</div></div><div class="info_item"><div class="info_item_label">当前行驶距离:</div><div class="info_item_value gw">{{ curPosition.distanceM | noDataFilter('-') }}米</div></div><div class="info_item"><div class="info_item_label">当前坐标:</div><div class="info_item_value gw">{{ curPosition.positionText || `[${curPosition.lat || '-'},${curPosition.lng || '-'}]` }}</div></div><div class="info_item"><div class="info_item_label">当前时间:</div><div class="info_item_value gw">{{ curPosition.gpsTime | noDataFilter('暂无') }}</div></div></div></el-dialog>
</template><script>
import {queryPoliceTrackDetail,queryCarTrackDetail,queryDutyPointList,queryPoliceTrackList,queryCarTrackList,queryAddressByLonLat
} from '@/api/taizhou/service-inspector.js'
import dayjs from 'dayjs'
import { deviceType_MOTO, setDeptArea } from '../index.js'export default {name: 'TrackPlaybackDialog',components: {},props: {idNameTail: {type: String,default: ''}},data() {return {loading: false,title: '',trackType: '',dialogVisible: false,infoData: {},time: [dayjs().format('YYYY-MM-DD') + ' 00:00:00', dayjs().format('YYYY-MM-DD HH:mm:ss')],// startTime: '',// endTime: '',positions: [],lc: 0,isMove: false,speed: 1,isMoveStartFirst: true,timeRangeList: [...Array(24).keys()],timeRangeLeft: '0',timeRangeWidth: '0',sblx: '1',speedOptions: [{ speed: 1 },{ speed: 2 },{ speed: 3 },{ speed: 4 },{ speed: 8 },{ speed: 16 }],curPosition: {},isShowDrawer: true,dutyAreaInfo: {},deptAreaInfo: {},moveRangeIndex: 0 // 当前切割轨迹回放的索引,切到了第几条数据}},computed: {ax() {const obj = {1: queryPoliceTrackDetail,2: queryCarTrackDetail,4: queryCarTrackDetail}return obj[this.trackType]}},watch: {time(val, oldVal) {if (!this.dialogVisible) returnconst [start, end] = valif (+new Date(end) - +new Date(start) > 24 * 60 * 60 * 1000) {this.$message.error('回放时间范围必须在一天内,请重新选择')this.time = oldVal} else {this.getTimeRange()this.queryTrackDetail()}},sblx() {if (!this.dialogVisible || this.trackType !== '1') returnthis.queryTrackDetail()},speed() {if (!this.dialogVisible) returnthis.isMoveStartFirst = true}},created() {},mounted() {},methods: {getTimeRange() {const [start, end] = this.timeconst startH = start.slice(11, 13) * 1const startM = start.slice(14, 16) * 1const endH = end.slice(11, 13) * 1const endM = end.slice(14, 16) * 1const isSameDay = start.slice(8, 10) === end.slice(8, 10)const allRange = 24 * 60if (!isSameDay) {this.timeRangeList = [...Array(24).keys()].map((item) => (item + startH) % 24)const timeRange = allRange - (startH * 60 + startM) + endH * 60 + endMthis.timeRangeWidth = timeRange / allRangethis.timeRangeLeft = 0} else {const timeRange = endH * 60 + endM - (startH * 60 + startM)this.timeRangeWidth = timeRange / allRangethis.timeRangeLeft = (startH * 60 + startM) / allRange}},open(data, { title = '', type = '' }) {this.title = titlethis.trackType = typeconsole.log('data--轨迹回放弹框--', data)this.dialogVisible = truethis.infoData = data.properties || data || {}this.$nextTick(() => {// const ssbm = '331002005300' // 测试数据const ssbm = this.infoData.dept_code || this.infoData.dwdm || this.infoData.ssbmsetDeptArea(this,ssbm,{ isSetCenter: false, clearArea: 'drawerRedPolygon' },(areaData) => {this.deptAreaData = areaData}) // 部门辖区/** ** 执勤区域展示 - start ****/const { dutyAreaInfo } = this.infoDataconsole.log('dutyAreaInfo----open打印', dutyAreaInfo)if (dutyAreaInfo) {const { center, drawerType, ...areaInfo } = dutyAreaInfothis.dutyAreaInfo[drawerType] = areaInfo // 保存执勤区域数据 - 便于隐藏区域后再展示区域// 回溯 - 地图中展示的警员执勤区域带过来,轨迹回放这里也展示这个执勤区域const { drawerAreaHandler } = this.$refs.TMapRefsetTimeout(() => {drawerAreaHandler(drawerType, areaInfo, drawerType)}, 1000)} else {// 否则,调接口取区域数据,然后回显区域this.queryDutyPointList() // 区域}/** ** 执勤区域展示 - end ****/})this.getTimeRange()setTimeout(() => {if (this.infoData.timeRange && this.infoData.timeRange[0] && this.infoData.timeRange[1]) {;[this.time[0], this.time[1]] = this.infoData.timeRangethis.time.push({})this.time.pop()} else {this.trackQueryHandler()}}, 200)},trackQueryHandler() {const axObj = {1: queryPoliceTrackList,2: queryCarTrackList}axObj[this.trackType]({page: 1,rows: 10,queryStartTime: this.time[0],queryEndTime: this.time[1],jh: this.infoData.jh}).then((res) => {this.$common.CheckCode(res, null, () => {const cur = (res.data.length && res.data[0]) || {}if (Object.keys(cur).length === 0) {this.queryTrackDetail()} else {this.time = [cur.kssj || '', cur.jssj || '']}})})},drawerHandler() {this.isShowDrawer = !this.isShowDrawerconst { drawerAreaHandler, clearAreaHandler } = this.$refs.TMapRefif (this.isShowDrawer) {Object.keys(this.dutyAreaInfo).forEach((key) => {drawerAreaHandler(key, this.dutyAreaInfo[key], key)})drawerAreaHandler('drawerRedPolygon', this.deptAreaData, 'drawerRedPolygon')} else {clearAreaHandler('all')}},queryTrackDetail() {this.moveEnd()let time = {}if (this.time.length) {time = {queryStartTime: this.time[0] || '',queryEndTime: this.time[1] || ''}}const { jh, deviceIndexCode, recentUserId } = this.infoDataconst paramsObj = {1: {sblx: this.sblx,jh,userId: recentUserId},2: {deviceIndexCode},4: {deviceIndexCode}}this.loading = truethis.ax({...time,...paramsObj[this.trackType]}).then((res) => {this.$common.CheckCode(res,null,() => {/** ** 【主要代码】点位数据 ****/res = {code: 200,msg: null,data: {gpsList: [...Array(10).keys()].map((index) => {const point = ['109.62616781132476,19.07651100671366','109.52052961372806,19.000955875798667','109.52779570317193,18.889409778960484','109.43389509635472,18.804246369159575',// '109.55741914575538,18.750798850652597',// '109.4389254607205,18.687804325385454',// 相同点位 --- 该点停留'109.4389254607205,18.687804325385454','109.4389254607205,18.687804325385454','109.5864836229498,18.66556534760466','109.72845239144965,18.73121481942799','109.74745608740011,18.87354409698365','109.8709801082789,18.91584923707182','109.80502610332212,19.07598276944228']const times = ['2025-02-12 08:00:00','2025-02-12 08:00:01','2025-02-12 08:00:05','2025-02-12 08:00:07','2025-02-12 08:00:10','2025-02-12 08:00:15','2025-02-12 08:00:20','2025-02-12 08:00:22','2025-02-12 08:00:30','2025-02-12 08:00:35']return {deviceType: '执法记录仪',deviceName: null,deptName: '事故中队',deptCode: '331002000300',recentUserId: '1000704',deviceIndexCode: 'K380772',// lng: '120.217989',lng: point[index].split(',')[0],recentUserName: '徐捷',// gpsTime: this.$dayjs(// +new Date('2025-02-12 08:00:00') + index * 3 * 1000// ).format('YYYY-MM-DD HH:mm:ss'),gpsTime: times[index],// lat: '30.212518',lat: point[index].split(',')[1],// location: '120.217989,30.212518',location: point[index],distanceM: index * 10}}),distanceKm: 108.79},timestamp: 1748573847277}this.lc = res?.data?.distanceKm || 0this.positions = (res?.data?.gpsList || []).map((item) => {const nameFieldObj = {1: 'xm',2: 'hphm',3: 'deviceName',4: 'hphm'}const nameObj = {1: item.recentUserName || '-',2: this.infoData.hphm || '-',4: this.infoData.hphm || '-'}return {...item,jd: item.lng,wd: item.lat,// type: item.gjlx,type:this.trackType === '2' && this.infoData.deviceType === deviceType_MOTO? '4': this.trackType,// name: item[nameFieldObj[item.gjlx]]name: nameObj[this.trackType]}})const { basicInspectionTrack } = this.$refs.TMapRefbasicInspectionTrack(this.positions) // 画折线、图标标点this.loading = false},() => {this.loading = false})}).catch(() => {this.loading = false})},queryDutyPointList() {queryDutyPointList({fzr_jh: this.infoData.jh,queryStartTime: this.time[0],queryEndTime: this.time[1]}).then((res) => {this.$common.CheckCode(res, null, () => {const typeObj = {1: 'Circle',2: 'Line',3: 'Polygon'}const listObj = {lineList: [],circleList: [],polygonList: []}res.data.forEach((item) => {const { zb, post_type } = itemconst areaList = zb? JSON.parse(zb).map((item) => new TMap.LatLng(item.y * 1, item.x * 1)): []const info = {areaList,center: areaList[0],circleCenter: areaList[0],...item}listObj[typeObj[post_type].toLowerCase() + 'List'].push(info)})Object.keys(typeObj).forEach((key) => {const type = typeObj[key]const drawerInfo = {dataList: listObj[type.toLowerCase() + 'List']}this.dutyAreaInfo[`drawer${type}`] = drawerInfothis.$refs.TMapRef.drawerAreaHandler(`drawer${type}`, drawerInfo, `drawer${type}`)})})})},/** ** 【主要代码】主要逻辑处理 start ****/moveHandler() {this.isMove = !this.isMoveconst { basicInspectionResumeMove, resetMoveJwdlength } = this.$refs.TMapRef/*** 判断是移动还是暂停*/if (this.isMove) {// 移动/*** 判断是否为第一次回放,即是不是暂停后继续回放*/if (this.isMoveStartFirst) {this.isMoveStartFirst = falsethis.moveRangeIndex = 0resetMoveJwdlength()this.moveRange()} else {basicInspectionResumeMove()}} else {// 暂停this.positionTrans() // 将当前经纬度调接口翻译为地址this.pauseTrack() // 暂停轨迹回放}},// 一段一段轨迹回放moveRange() {console.log('"moveRange"----打印', 'moveRange')const { basicInspectionMove, resetMoveJwdlength, addAlreadyMovePoint } = this.$refs.TMapRefthis.updateNowLc(this.moveRangeIndex === 0 ? 0 : this.moveRangeIndex - 1) // 每回放一段轨迹,就更新一次最新走过的点位,要展示最新走过点位的信息if (this.moveRangeIndex >= this.positions.length) {/*** 如果 moveRangeIndex 大于等于所有点位数,即所有点位已回放完,停止轨迹回放,并重置已经移动的点位*/this.moveEnd() // 轨迹回放完成resetMoveJwdlength() // 重置已经移动的点位return}const sliceLength = this.moveRangeIndex === 0 ? 2 : 1 // 每段轨迹回放要截取的点位数量(这里的轨迹划分成小段小段,其实每一小段就是两个点位组成的一条直线,而非多个点位)console.log('this.moveRangeIndex----打印', this.moveRangeIndex)const movePath = this.positions.slice(this.moveRangeIndex - (this.moveRangeIndex === 0 ? 0 : 1), // 除了第一次,其他每次都要截取前一个点this.moveRangeIndex + sliceLength) // 当前段轨迹回放的点位this.moveRangeIndex += sliceLengthconsole.log('this.moveRangeIndex----打印', this.moveRangeIndex)const startTime = movePath[0].gpsTimeconst endTime = movePath[movePath.length - 1].gpsTimeconst duration = (dayjs(endTime).diff(dayjs(startTime), 'seconds') * 1000) / this.speed // 计算轨迹回放所用时长,➗倍速const startPosition = `${movePath[0].wd},${movePath[0].jd}`const endPosition = `${movePath[movePath.length - 1].wd},${movePath[movePath.length - 1].jd}`const noMove = startPosition === endPosition // 判断该段轨迹是否为原地if (noMove) {/*** 某个时间段内(N秒)在原地没有移动,* 该段轨迹就不回放,在该点停留N秒后,继续回放下一段轨迹* 但是这段时间的点位要存起来,便于轨迹擦除*/let timer = nullclearTimeout(timer)addAlreadyMovePoint(movePath) // 保存已经过的点位timer = setTimeout(() => {// 在该点停留 N秒后,继续回放下一段轨迹this.moveRange()}, duration)} else {// 轨迹回放basicInspectionMove(movePath.map((item) => {return new TMap.LatLng(Number(item.wd), Number(item.jd))}),duration)}},moveEnd() {this.isMove = falsethis.isMoveStartFirst = truethis.pauseTrack()this.moveRangeIndex = 0},/** ** 【主要代码】主要逻辑处理 end ****/updateNowLc(index) {this.curPosition = { ...this.positions[index], positionText: '' }},pauseTrack() {this.$refs?.TMapRef?.basicInspectionPauseTrack()},positionTrans() {this.curPosition.lng &&this.curPosition.lat &&queryAddressByLonLat({longitude: this.curPosition.lng,latitude: this.curPosition.lat}).then((res) => {this.$common.CheckCode(res, null, () => {this.curPosition.positionText = res.data?.address || ''})})},handleClose() {this.pauseTrack()this.dialogVisible = falsethis.infoData = {}this.time[0] = dayjs().format('YYYY-MM-DD') + ' 00:00:00'this.time[1] = dayjs().format('YYYY-MM-DD HH:mm:ss')this.positions = []this.lc = 0this.isMove = falsethis.isMoveStartFirst = truethis.speed = 1this.timeRangeList = [...Array(24).keys()]this.sblx = '1'this.curPosition = {}this.isShowDrawer = truethis.dutyAreaInfo = {}this.deptAreaInfo = {}}}
}
</script><style lang='scss' scoped>
@import '@/styles/dialog-scss.scss';$rightInfoWidth: 240px;>>> .el-dialog {.el-dialog__header {.drawer_btn {margin-left: 10px;}}.el-dialog__body {height: 90vh;padding: 0 !important;position: relative;#TrackPlayback,[id^='TrackPlayback'] {width: 100%;height: 100%;}.z-index {z-index: 1999;}.operation {width: calc(100% - #{$rightInfoWidth} - 30px);height: 55px;line-height: 55px;padding: 0 10px;position: absolute;top: 10px;left: 0;background-color: #ffffffa3;text-align: left;display: flex;align-items: center;@extend .z-index;.el-date-editor {width: 290px;margin-right: 10px;.el-range-input {width: calc(50% - 20px);}.el-range-separator {width: 20px;}.el-range__close-icon {width: 0;}}.speed {margin: 0 10px;width: 80px;}.sblx {margin-left: 10px;width: 100px;}.time_line {flex: 1;.bg {$size: 7px;width: 100%;height: $size;border-radius: $size;display: flex;background-color: #e4e7ed;position: relative;.item {flex: 1;position: relative;z-index: 9;&::before {content: '';position: absolute;top: 0;left: -#{$size / 2};width: $size;height: $size;background-color: #fff;border-radius: $size;}span {position: absolute;left: -10px;top: 10px;display: inline-block;width: 20px;height: 20px;line-height: 20px;text-align: center;color: #409eff;}}.range {$range_size: calc(#{$size} * 2);position: absolute;// top: -20px;width: var(--timeRangeWidth);height: $size;left: var(--timeRangeLeft);background-color: #409eff;&::before,&::after {content: '';position: absolute;// top: 0;top: -5px;width: $range_size;height: $range_size;background-color: #fff;border-radius: $range_size;border: 1px solid #c7cbd2;}&::before {left: calc((#{$range_size} / 2) * -1);}&::after {right: calc((#{$range_size} / 2) * -1);}}}}}.info {width: $rightInfoWidth;position: absolute;top: 10px;right: 20px;padding: 10px;background-color: #fff;border-radius: 8px;text-align: left;font-size: 16px;@extend .z-index;&_item {display: flex;margin-bottom: 5px;color: #333;&_label {}&_value {flex: 1;line-height: 22px;&.bm,&.gw {font-size: 12px;}}}}}
}
</style>
TMapNew.vue
代码
<!-- 腾讯地图 -->
<template><div :id="idName" class="TMap" />
</template><script>
import { mapGetters } from 'vuex'
import policeImg from '@/assets/images/TXMap/icon-auxiliary-police.png'
import carImg from '@/assets/images/TXMap/icon-police-car.png'
import motoImg from '@/assets/images/TXMap/icon-motorcycle.png'
import monitorImg from '@/assets/images/TXMap/icon-monitor.png'
import startImg from '@/assets/images/start.png'
import endImg from '@/assets/images/end.png'
import nameBg from '@/assets/images/TXMap/point_name_bg.png'export default {name: 'TMapNew',components: {},props: {mapName: {type: String,required: true},idName: {type: String,default: 'TXMapContanier'},movePointLength: {type: Number,default: 0},allMovePointLength: {type: Number,default: 0}},data() {return {map: null,curZoom: 0,setLabelZoom: 17,multiMarker: null, // 点位图标MultiLabel: null, // 点位图标顶部文字描述infoWindow: null, // 信息窗口MultiPolyline: null, // 折线 - 运动轨迹multiPolylineLayer: {}, // 多个 简单折线multiCircleLayer: {}, // 多个 简单圆multiPolygonLayer: {}, // 多个 简单多边形multiRedPolygonLayer: {}, // 多个 简单多边形 -- 部门辖区multiLabelLayer: {}, // 多个 labeltrackQueryMultiMarker: {jy: null,jc: null,jk: null},trackQueryMultiLabel: {jy: null,jc: null,jk: null},editor: null,activeType: 'marker',activeId: '', // 值格式为 6C5895CE-B42D-4E9B-A8FA-81135761CBDDalreadyMovePoint: []}},computed: {...mapGetters(['sysConfigData'])},mounted() {// this.initMap()window.onbeforeunload = () => {localStorage.removeItem('TXMapIsCanLoad')}const timer = setInterval(() => {const TXMapIsCanLoad = localStorage.getItem('TXMapIsCanLoad')if (TXMapIsCanLoad === 'true') {this.initMap()clearInterval(timer)}}, 100)},beforeDestroy() {this.map?.destroy()this.infoWindow?.close()this.map = nullthis.multiMarker?.setMap(null)this.MultiLabel?.setMap(null)this.infoWindow?.setMap(null)this.MultiPolyline?.setMap(null)this.multiPolylineLayer = {}this.multiCircleLayer = {}this.multiPolygonLayer = {}this.multiRedPolygonLayer = {}this.multiLabelLayer = {}this.trackQueryMultiMarker = {jy: null,jc: null,jk: null}this.trackQueryMultiLabel = {jy: null,jc: null,jk: null}this.editor = null},methods: {// 初始化地图initMap() {this[this.mapName + 'Init']()},setMapCenter(center) {this.map.setCenter(center)},setMapZoom(level) {this.map.setZoom(level)},// 创建wmts图层newWMTSLayer() {const url = this.sysConfigData.mon_map_wmts_urlconst { map } = thisif (!url) returnnew TMap.WMTSLayer({url, // 地图服务地址map, // 展示图层的地图对象minZoom: 3, // 最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3maxZoom: 20, // 最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20visible: true, // 是否可见,默认为truezIndex: 1, // 图层绘制顺序opacity: 0.9, // 图层透明度,默认为1params: {// OGC标准的WMTS地图服务的GetTile接口的参数layers: 'topp:raster_cgcs2000%3Ataizhou2m_cgcs2000', // 请求的图层名称tileMatrixSet: 'taizhou2m%3A11' // 瓦片矩阵数据集}})},// 标记点// point(markerId, styles, pointArr) {// this[this.mapName + 'Point'](markerId, styles, pointArr)// },// 打开弹框openInfoWindow(position, content) {this.infoWindow = new TMap.InfoWindow({map: this.map,position: new TMap.LatLng(position[0], position[1]),offset: { x: 0, y: -32 }, // 设置信息窗相对position偏移像素content: content})},/** ** 台州勤务督察 -页面地图 start ****/basicInspectionInit() {this.basicInspectionInitCommon()},setPointMapInit() {this.basicInspectionInitCommon()this.$emit('setInitPoint')this.map.on('click', (e) => {this.$emit('getPoint', e.latLng)})},setPoint({ jd, wd }, isSetCenter = false) {isSetCenter && this.setMapCenter(new TMap.LatLng(Number(jd), Number(wd)))this.MultiMarker = new TMap.MultiMarker({id: 'marker-layer',map: this.map,styles: {marker: new TMap.MarkerStyle({width: 25,height: 35,anchor: { x: 16, y: 32 }})},geometries: [{id: 'demo',styleId: 'marker',position: new TMap.LatLng(jd * 1, wd * 1),properties: {title: 'marker'}}]})},removePoint() {this.MultiMarker?.setMap(null)},basicInspectionInitCommon() {var location = (this.sysConfigData.map_location || '121.427648,28.661939').split(',')// console.log(Number(location[1]), Number(location[0]))const featuresObj = {gs: null,nw: []}this.curZoom = this.sysConfigData.map_level || 14this.map = new TMap.Map(this.idName, {zoom: this.curZoom,center: new TMap.LatLng(Number(location[1]), Number(location[0])),baseMap: {type: 'vector',// features: null // 本地跑项目用// // features: [] // 内网用features: featuresObj[this.sysConfigData.mon_map_yslx] || null}})/** ** 获取地图首次加载完成 start ****/this.map.off('tilesloaded', tilesLoad)this.map.on('tilesloaded', tilesLoad)const that = thisfunction tilesLoad() {console.log('地图加载完成')that.map.off('tilesloaded', tilesLoad)}/** ** 获取地图首次加载完成 end ****/this.newWMTSLayer()this.map.removeControl(TMap.constants.DEFAULT_CONTROL_ID.ROTATION) // 移除腾讯地图旋转控件this.map.removeControl(TMap.constants.DEFAULT_CONTROL_ID.ZOOM) // 移除腾讯地图缩放控件this.map.on('zoom_changed', (params) => {console.log('params----zoom_changed', params)this.curZoom = params.zoomif (this.curZoom > this.setLabelZoom) {!Object.keys(this.multiLabelLayer).length && this.$emit('setLabel', this.curZoom)} else {this.clearMultiLabel()}})if (this.idName === 'trackQueryTXMapContanier') {let timer = nullthis.map.on('center_changed', (params) => {clearTimeout(timer)timer = setTimeout(() => {const center = params.centerconsole.log('params----center_changed', params)console.log('center----center_changed', center)this.$emit('getPointArr', { jd: center.lng, wd: center.lat, isCenterChange: true })}, 500)})}},basicInspectionPoint(markerId, pointArr, isSetCenter = false) {// console.log('pointArr----basicInspectionPoint', pointArr)this.MultiMarker?.setMap(null)this.infoWindow?.setMap(null)if (!pointArr.length) return// 初始markerthis.MultiMarker = this.basicInspectionCommonPoint(markerId, pointArr, isSetCenter)this.MultiMarker.on('click', this.basicInspectionCommonClick)},basicInspectionPointText(markerId, pointArr) {// console.log('pointArr----basicInspectionPointText', pointArr)this.MultiLabel?.setMap(null)this.infoWindow?.setMap(null)if (!pointArr.length) return// 初始markerthis.MultiLabel = this.basicInspectionCommonPointText(pointArr)this.MultiLabel.on('click', this.basicInspectionCommonClick)},trackQueryPoint(markerId, pointArr, pointTypeArr, pointType, isSetCenter = false) {console.log('pointArr----trackQueryPoint', pointArr)const gjlxObj = {1: 'jy',2: 'jc',3: 'jk'}const gjlx = gjlxObj[pointType];['1', '2', '3'].forEach((item) => {if (!pointTypeArr.includes(item) || pointType === item) {this.trackQueryMultiMarker[gjlxObj[item]]?.setMap(null)}})// this.MultiMarker?.setMap(null)this.infoWindow?.setMap(null)if (!pointArr.length) return// 初始markerthis.trackQueryMultiMarker[gjlx] = this.basicInspectionCommonPoint(markerId,pointArr,isSetCenter)this.trackQueryMultiMarker[gjlx].on('click', this.basicInspectionCommonClick)},trackQueryPointText(markerId, pointArr, pointTypeArr, pointType) {console.log('pointArr----trackQueryPointText', pointArr)const gjlxObj = {1: 'jy',2: 'jc',3: 'jk'}const gjlx = gjlxObj[pointType];['1', '2', '3'].forEach((item) => {if (!pointTypeArr.includes(item) || pointType === item) {this.trackQueryMultiLabel[gjlxObj[item]]?.setMap(null)}})// this.MultiLabel?.setMap(null)this.infoWindow?.setMap(null)if (!pointArr.length) return// 初始markerthis.trackQueryMultiLabel[gjlx] = this.basicInspectionCommonPointText(pointArr)this.trackQueryMultiLabel[gjlx].on('click', this.basicInspectionCommonClick)},basicInspectionCommonPoint(markerId, pointArr, isSetCenter) {if (isSetCenter) {const first = pointArr[0]this.setMapCenter(new TMap.LatLng(Number(first.wd), Number(first.jd)))}return new TMap.MultiMarker({id: markerId,map: this.map,styles: {police: new TMap.MarkerStyle({width: 24,height: 40,anchor: { x: 0, y: 0 },src: policeImg}),car: new TMap.MarkerStyle({width: 50,height: 25,anchor: { x: 0, y: 0 },src: carImg}),moto: new TMap.MarkerStyle({width: 50,height: 25,anchor: { x: 0, y: 0 },src: motoImg}),monitor: new TMap.MarkerStyle({width: 40,height: 30,anchor: { x: 0, y: 0 },src: monitorImg})},geometries: pointArr.map((item, index) => {const styleIdObj = {1: 'police',2: 'car',3: 'monitor',4: 'moto'}return {id: index,// styleId: 'police',styleId: styleIdObj[item.type || '1'],position: new TMap.LatLng(item.wd, item.jd),properties: this.basicInspectionCommonProperties(item, index)}})})},basicInspectionCommonPointText(pointArr) {const commonStyle = {height: 25, // 高度anchor: { x: 15, y: 26 }, // 锚点位置src: nameBg, // 标注点图片url或base64地址color: '#fff', // 标注点文本颜色size: 14, // 标注点文本文字大小offset: { x: 0, y: 0 } // 标注点文本文字基于direction方位的偏移属性}return new TMap.MultiMarker({map: this.map,styles: {police: new TMap.MarkerStyle({width: 60, // 宽度...commonStyle}),car: new TMap.MarkerStyle({width: 100, // 宽度...commonStyle}),moto: new TMap.MarkerStyle({width: 100, // 宽度...commonStyle}),monitor: new TMap.MarkerStyle({width: 100, // 宽度...commonStyle})},geometries: pointArr.map((item, index) => {const styleIdObj = {1: 'police',2: 'car',3: 'monitor',4: 'moto'}let content = item.name || ''if (['3'].includes(item.type)) {content = content.slice(0, 5) + (content.length > 5 ? '...' : '')}return {styleId: styleIdObj[item.type || '1'],position: new TMap.LatLng(item.wd, item.jd),content,properties: this.basicInspectionCommonProperties(item, index)}})})},basicInspectionCommonProperties(item, index) {return {type: item.type || '1', // 1警员、2警车、3监控...item}},basicInspectionCommonClick(data) {console.log('data--basicInspectionCommonClick--', data)this.infoWindow?.destroy()this.$emit('pointClick', data)},/** ** 【主要代码】主要逻辑处理 start ****/basicInspectionTrack(trackArr) {this.MultiPolyline?.setMap(null)this.multiMarker?.setMap(null)if (!trackArr.length) returnconst trackStart = trackArr[0] || {}console.log('trackStart----', trackStart)this.setMapCenter(new TMap.LatLng(Number(trackStart.wd), Number(trackStart.jd))) // 设置中心点// 画折线this.MultiPolyline = new TMap.MultiPolyline({map: this.map, // 绘制到目标地图// 折线样式定义styles: {style_blue: new TMap.PolylineStyle({color: '#3777FF', // 线填充色width: 4, // 折线宽度borderWidth: 2, // 边线宽度borderColor: '#FFF', // 边线颜色lineCap: 'round', // 线端头方式eraseColor: 'rgba(190,188,188,1)'})},geometries: [{id: 'erasePath',styleId: 'style_blue',paths: trackArr.map((item) => {return new TMap.LatLng(Number(item.wd), Number(item.jd))})}]})const iconStyleObj = {1: {width: 24,height: 40,anchor: { x: 13, y: 30 },src: policeImg},2: {width: 50,height: 25,anchor: { x: 25, y: 12 },src: carImg},4: {width: 50,height: 25,anchor: { x: 25, y: 12 },src: motoImg}}const nameStyleObj = {1: {width: 60, // 宽度height: 25, // 高度anchor: { x: 30, y: 55 } // 锚点位置},2: {width: 100, // 宽度height: 25, // 高度anchor: { x: 40, y: 38 } // 锚点位置},4: {width: 100, // 宽度height: 25, // 高度anchor: { x: 40, y: 38 } // 锚点位置}}// 标点记图标this.multiMarker = new TMap.MultiMarker({map: this.map,styles: {icon: new TMap.MarkerStyle({faceTo: 'screen',rotate: 0,...iconStyleObj[trackStart.type]}),name: new TMap.MarkerStyle({src: nameBg, // 标注点图片url或base64地址color: '#fff', // 标注点文本颜色size: 14, // 标注点文本文字大小offset: { x: 0, y: 0 }, // 标注点文本文字基于direction方位的偏移属性...nameStyleObj[trackStart.type]})},geometries: [{id: 'iconMove',styleId: 'icon',position: new TMap.LatLng(trackStart.wd, trackStart.jd)},{id: 'nameMove',styleId: 'name',position: new TMap.LatLng(trackStart.wd, trackStart.jd),content: trackStart.name,properties: trackStart}]})// 一段轨迹回放结束this.multiMarker.on('move_ended', () => {/*** 当前段轨迹回放完之后,通过判断 已经移动的点位长度 是否小于 总长度,来判断是否已经回放完* 如果没有播放完成,则继续播放下一段轨迹*/if (this.movePointLength <= this.allMovePointLength) {this.$emit('continuMove') // 继续回放下一段轨迹}})// 使用marker 移动接口, https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker// this.basicInspectionMove(path)this.multiMarker.on('moving', (e, passedDistance) => {let passedLatLngs = e?.iconMove?.passedLatLngs || [] // 此处取iconMove或nameMove都可以,因为这两个marker的position是相同的passedLatLngs.unshift(...this.alreadyMovePoint.slice(0, -2));['iconMove', 'nameMove'].forEach((key) => {if (passedLatLngs) {// 使用路线擦除接口 eraseTo, https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVectorthis.MultiPolyline.eraseTo('erasePath',passedLatLngs.length - 1, // 要擦除到的坐标索引 indexpassedLatLngs[passedLatLngs.length - 1] // 要擦除的点位数组,官方解释:线段 (坐标索引为[ index -1 , index ] )上擦除点的经纬度坐标( 如果这个坐标不在擦除的索引范围内,会一直擦除到坐标索引为index的点 )。只支持简单折线。)}})})},// 重置已经移动的点位为空数组resetMoveJwdlength() {this.alreadyMovePoint = []},// 将已经走过的点位存起来,用于后续的路线擦除addAlreadyMovePoint(path) {this.alreadyMovePoint.push(...path.slice(this.alreadyMovePoint.length === 0 ? 0 : 1))},basicInspectionMove(path, duration) {console.log('duration----打印', duration)console.log('path--basicInspectionMove--打印', path)this.addAlreadyMovePoint(path)this.multiMarker.moveAlong({iconMove: {path, // 要经过的点位数组duration // 轨迹回放时间,官方文档 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker#8},nameMove: {path, // 要经过的点位数组duration // 轨迹回放时间,官方文档 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker#8}},{autoRotation: true})},// 暂停轨迹回放basicInspectionPauseTrack() {this.multiMarker?.pauseMove()},// 继续从暂停的轨迹开始回放轨迹basicInspectionResumeMove() {this.multiMarker?.resumeMove()},/** ** 【主要代码】主要逻辑处理 end ****/// 清除已有图形clearArea() {this.editor.select([this.activeId]) // 选中已经绘制的图形this.editor.delete() // 删除已选中图形this.activeId = ''},selectArea(id) {this.clearArea()this.activeType = idthis.editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.DRAW)this.editor.setActiveOverlay(id)},setToolsGeometryEditor() {console.log('setToolsGeometryEditor----打印')var polygon = new TMap.MultiPolygon({map: this.map})var circle = new TMap.MultiCircle({map: this.map})this.editor = new TMap.tools.GeometryEditor({// TMap.tools.GeometryEditor 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditormap: this.map, // 编辑器绑定的地图对象overlayList: [// 可编辑图层 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor#4{overlay: polygon,id: 'polygon'},{overlay: circle,id: 'circle'}],actionMode: TMap.tools.constants.EDITOR_ACTION.DRAW, // 编辑器的工作模式activeOverlayId: 'marker', // 激活图层snappable: true // 开启吸附})// 监听绘制结束事件,获取绘制几何图形this.editor.on('draw_complete', (geometry) => {// this.editor.destroy()this.editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.INTERACT)var { id, radius } = geometrythis.activeId = idconst maxRadius = this.sysConfigData.mon_map_maxRadius * 1 || 500if (!!radius && radius > maxRadius) {this.clearArea()this.$message.warning(`圆的半径超过最大限制${maxRadius}米,请重新选择区域`)this.selectArea(this.activeType)return}this.$emit('drawComplete', geometry)})// 绘制失败,返回失败信息this.editor.on('draw_error', (errInfo) => {const { errorDesc, errorType } = errInfoif (errorDesc === 'geometry illegals' && errorType === 1) {// 多边形自相交错误信息this.$message.error('仅支持简单多边形,右击取消上一标点或按Esc键取消当前绘制图案')}})},/** ** 画区域 start ****/drawerAreaHandler(funName, data, clearType) {console.log('funName----打印', funName)console.log('data----打印', data)this[funName] && this[funName](data, clearType)data.center && this.setMapCenter(data.center)},clearAreaHandler(type = 'all') {!type && (type = 'all')console.log('clearAreaHandler --- type----打印', type)const typeObj = {drawerLine: 'multiPolylineLayer',drawerCircle: 'multiCircleLayer',drawerPolygon: 'multiPolygonLayer'}const blueTypeList = ['drawerLine', 'drawerCircle', 'drawerPolygon']if (['all', 'allDutyArea'].includes(type)) {blueTypeList.forEach((drawerType) => {Object.keys(this[typeObj[drawerType]]).forEach((key) => {this[typeObj[drawerType]][key]?.setMap(null)})})}if (blueTypeList.includes(type)) {Object.keys(this[typeObj[type]]).forEach((key) => {this[typeObj[type]][key]?.setMap(null)})}if (['all', 'drawerRedPolygon'].includes(type)) {Object.keys(this.multiRedPolygonLayer).forEach((key) => {this.multiRedPolygonLayer[key]?.setMap(null)})}},drawerLine(data, clearType) {console.log('this.$cloneDeep(data)----drawerLine打印', this.$cloneDeep(data))this.clearAreaHandler(clearType)this.$cloneDeep(data).dataList.forEach((item, index) => {this.multiPolylineLayer[index] = new TMap.MultiPolyline({id: `polyline-layer-${index}`,map: this.map,geometries: [{id: `line-${index}`, // 折线唯一标识,删除时使用paths: item.areaList}]})})},drawerCircle(data, clearType) {console.log('this.$cloneDeep(data)----drawerCircle打印', this.$cloneDeep(data))this.clearAreaHandler(clearType)this.$cloneDeep(data).dataList.forEach((item, index) => {this.multiCircleLayer[index] = new TMap.MultiCircle({map: this.map,geometries: [{id: `circle-${index}`,styleId: 'circle',center: item.circleCenter,radius: item.radius}]})})},drawerPolygon(data, clearType) {console.log('this.$cloneDeep(data)----drawerPolygon打印', this.$cloneDeep(data))this.clearAreaHandler(clearType)this.$cloneDeep(data).dataList.forEach((item, index) => {this.multiPolygonLayer[index] = new TMap.MultiPolygon({id: `polygon-layer-${index}`, // 图层idmap: this.map, // 显示多边形图层的底图geometries: [{id: `polygon-${index}`, // 多边形图形数据的标志信息styleId: 'polygon', // 样式idpaths: item.areaList, // 多边形的位置信息properties: {// 多边形的属性数据title: 'polygon'}}]})})},/*** 【注】* drawerPolygon 和 drawerRedPolygon 不能合并 - 有的页面需要同时有两种样式的线(比如:部门辖区和执勤区域,两种边框展示要互不影响)*/drawerRedPolygon(data, clearType) {console.log('this.$cloneDeep(data)----drawerRedPolygon打印', this.$cloneDeep(data))this.clearAreaHandler(clearType)this.$cloneDeep(data).dataList.forEach((item, index) => {this.multiRedPolygonLayer[index] = new TMap.MultiPolygon({id: `multi-polygon-layer-${index}`, // 图层idmap: this.map, // 显示多边形图层的底图styles: {// 多边形的相关样式polygon: new TMap.PolygonStyle({color: 'rgba(0,91,255,0)', // 面填充色borderColor: 'rgba(241,30,52,1)', // 边线颜色borderWidth: 3 // 边线宽度})},geometries: [{id: 'multiPolygon', // 多边形图形数据的标志信息styleId: 'polygon', // 样式idpaths: item.areaList, // 多边形的位置信息properties: {// 多边形的属性数据title: 'multiPolygon'}}]})})},/** ** 画区域 end ****/clearMultiLabel() {Object.keys(this.multiLabelLayer).forEach((key) => {this.multiLabelLayer[key]?.setMap(null)delete this.multiLabelLayer[key]})},setMultiLabel(dataList) {this.clearMultiLabel()console.log('dataList----setMultiLabel打印', dataList)if (this.curZoom < this.setLabelZoom) return // 图层层级小于设置的图层层级时,不显示labelthis.$cloneDeep(dataList).forEach((item, index) => {this.multiLabelLayer[index] = new TMap.MultiLabel({map: this.map,styles: {label: new TMap.LabelStyle({color: '#3777FF', // 颜色属性size: 20, // 文字大小属性offset: { x: 0, y: 0 }, // 文字偏移属性单位为像素angle: 0, // 文字旋转属性alignment: 'center', // 文字水平对齐属性verticalAlignment: 'middle' // 文字垂直对齐属性})},geometries: [{id: `label-${index}`, // 点图形数据的标志信息styleId: 'label', // 样式idposition: item.position, // 标注点位置content: item.content, // 标注文本properties: {// 标注点的属性数据title: 'label'}}]})})}/** ** 台州勤务督察 -页面地图 end ****/}
}
</script><style lang='scss'>
.qwdc_card {width: 300px;background-color: #fff;// padding: 10px;text-align: left;.text_jb {background: linear-gradient(to bottom, #49befe, #3783fe); /* 从左到右渐变 */-webkit-background-clip: text; /* Safari/Chrome支持该属性 */color: transparent; /* 将文本颜色设置为透明 */}&_header {display: flex;margin-bottom: 5px;&_pic {$height: 50px;width: 40px;height: $height;margin-right: 10px;border: 1px solid #00a4ff;border-radius: 3px;background: linear-gradient(180deg, #fff, rgba(0, 121, 254, 0.07) 97%);text-align: center;&.iconfont {line-height: $height;font-size: 30px;color: #388bfd;// @extend .text_jb;}}&_info {flex: 1;&_name {// margin-bottom: 5px;font-size: 18px;white-space: pre-wrap;color: #7f7f7f;}&_bm {font-size: 14px;color: #d7d7d7;}}}&_body {&_item {margin-bottom: 5px;display: flex;&_label {color: #7f7f7f;}&_value {flex: 1;white-space: pre-wrap;line-height: 21px;font-size: 14px;color: #aaaaaa;.zt {padding: 0 5px;border: 1px solid transparent;border-radius: 3px;font-size: 12px;margin-right: 5px;color: #f59a23;border-color: #f59a23;&.success {border-color: #67c23a;color: #67c23a;}&.warning {border-color: #e6a23c;color: #e6a23c;}}}}}&_btns {padding-top: 10px;border-top: 1px solid #f2f2f2;position: relative;i {margin: 0 5px;cursor: pointer;font-size: 16px;// color: #388bfd;@extend .text_jb;}.tempMessage {position: absolute;top: -27px;left: 0;background: #000000d1;padding: 5px 10px;border-radius: 5px;color: #fff;}}
}
</style>