当前位置: 首页 > article >正文

【面试 - 遇到的问题 - 优化 - 地图】腾讯地图轨迹回放 - 回放的轨迹时间要和现实时间对应(非匀速)

目录

  • 背景
  • 轨迹回放 - 匀速
    • 效果图
    • `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>

相关文章:

【面试 - 遇到的问题 - 优化 - 地图】腾讯地图轨迹回放 - 回放的轨迹时间要和现实时间对应(非匀速)

目录 背景轨迹回放 - 匀速效果图TrackPlaybackDialog.vue 代码TMapNew.vue 代码 轨迹回放 - 非匀速效果图TrackPlaybackDialog.vue 代码TMapNew.vue 代码 背景 腾讯地图轨迹回放是匀速回放的&#xff0c;但是客户要求根据现实时间&#xff0c;什么时间点在某个点位 【腾讯地图轨…...

ffmpeg baidu

ffmpeg -list_devices true -f dshow -i dummy 获取你的音频输入设备&#xff08;麦克风&#xff09;名称 输出中可以看到你有如下两个可用麦克风设备&#xff1a; “麦克风阵列 (适用于数字麦克风的英特尔 智音技术)” “外部麦克风 (Realtek Audio)” &#xff08;注意&…...

spring boot 拦截器HandlerInterceptor 不生效的原因排查

public class UserInterceptor implements HandlerInterceptor项目添加一个拦截器&#xff0c;发现未生效 1、排查拦截本身是否注入了springbean 容器 Slf4j Component public class LoginInterceptor implements HandlerInterceptor {2、排查springboot 项目扫描范围是否包含…...

公网ip怎么申请和使用?本地只有内网IP如何提供外网访问?

在当今的网络时代&#xff0c;许多程序和服务都依赖于公网地址——用于标识设备在互联网位置的全球唯一标识符。例如&#xff0c;办公网站、FTP服务器或游戏服务器等需要借助公网IP来确保用户可以访问。故此准确获取公网IP地址显得尤为重要。 在大多家庭和企业网络中&#xff…...

将git最后一次提交把涉及到的文件按原来目录结构提取出来

文章目录 前言一、将git最后一次提交把涉及到的文件按原来目录结构提取出来 前言 将git最后一次的提交提取出来&#xff0c;涉及到的目录结构以及文件等&#xff0c;按原本的目录结构复制输出。并输出相关的补丁。 一、将git最后一次提交把涉及到的文件按原来目录结构提取出来…...

利用计算机模拟和玉米壳废料开发新型抗病毒药物合成方法

参阅&#xff1a;Top 创新大奖 这个课题将农业废弃物资源化利用、计算机辅助药物设计和绿色化学完美结合&#xff0c;是一个极具创新性和应用前景的研究方向&#xff01; 以下是如何利用计算机模拟和玉米壳废料开发新型抗病毒药物合成方法的系统思路&#xff1a; 核心思路 玉…...

【Docker】存储卷

【简介】 宿主机的某一目录与容器中的某一目录建立的一种绑定关系&#xff0c;这就是“存储卷” 它有三个特性 1.它可以绕过联合文件系统&#xff0c; 直接作用于宿主机的目录 2.容器和宿主机的这一绑定关系指向了同一目录&#xff0c; 因此两个目录之间的数据是同步的&#xf…...

Python 爬虫工具 BeautifulSoup

文章目录 1. BeautifulSoup 概述1.1. 安装 2. 对象的种类2.1. BeautifulSoup2.2. NavigableString&#xff08;字符串&#xff09;2.3. Comment2.4. Tag2.4.1. 获取标签的名称2.4.2. 获取标签的属性2.4.3. 获取标签的内容2.4.3.1. tag.string2.4.3.2. tag.strings2.4.3.3. tag.…...

WPF的布局核心:网格布局(Grid)

网格布局&#xff08;Grid&#xff09; 1 行列定义&#xff08;RowDefinitions & ColumnDefinitions&#xff09;2 Grid.Row和Grid.Column3 跨行跨列&#xff08;Grid.RowSpan & Grid.ColumnSpan&#xff09;3.1垂直跨行3.2水平跨列3.3综合应用案例 4 高级布局技巧4.1共…...

OpenCV图像认知(二)

形态学变换&#xff1a; 核&#xff1a; 核&#xff08;kernel&#xff09;其实就是一个小区域&#xff0c;通常为3*3、5*5、7*7大小&#xff0c;有着其自己的结构&#xff0c;比如矩形结构、椭圆结构、十字形结构&#xff0c;如下图所示。通过不同的结构可以对不同特征的图像…...

大数据与数据分析【数据分析全栈攻略:爬虫+处理+可视化+报告】

- 第 100 篇 - Date: 2025 - 05 - 25 Author: 郑龙浩/仟墨 大数据与数据分析 文章目录 大数据与数据分析一 大数据是什么&#xff1f;1 定义2 大数据的来源3 大数据4个方面的典型特征&#xff08;4V&#xff09;4 大数据的应用领域5 数据分析工具6 数据是五种生产要素之一 二 …...

t015-预报名管理系统设计与实现 【含源码!!!】

项目演示地址 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装预报名管理系统软件来发挥其高效地信息处理的…...

LLM中的Loss与Logits详解

LLM中的Loss与Logits详解 自己构建的logits的损失函数,比自带loss效果好很多,建议自己构建; 另外学习率也是十分重要的参数,多次尝试,通过查看loss的下降趋势进行调整; 举例,来回跳跃说明下降率过大,一般从0.0001 开始尝试。 在深度学习中,logits 和 loss 是两个不…...

数学术语之源——绝对值(absolute value)(复数模?)

目录 1. 绝对值&#xff1a;(absolute value): 2. 复数尺度(复尺度)&#xff1a;(modulus): 1. 绝对值&#xff1a;(absolute value): 一个实数的绝对值是其不考虑(irrespective)符号的大小(magnitude)。在拉丁语中具有相同意思的单词是“modulus”&#xff0c;这个单词还…...

亚马逊商品评论爬取与情感分析:Python+BeautifulSoup实战(含防封策略)

一、数据爬取模块&#xff08;Python示例&#xff09; import requests from bs4 import BeautifulSoup import pandas as pd import timeheaders {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36,Accept-Language: en-US }def scrape_amazon_re…...

STM32的DMA入门指南:让单片机学会“自动搬运“数据

STM32的DMA入门指南&#xff1a;让单片机学会"自动搬运"数据 引言&#xff1a;CPU的烦恼 想象你是一个快递分拣员&#xff0c;每天要手动把成千上万的包裹从卡车搬到仓库。这时候如果有个自动传送带能帮你完成搬运工作&#xff0c;你就可以专心处理更重要的订单核对…...

从虚拟化到云原生与Serverless

操作系统课程&#xff1a;从虚拟化到云原生与Serverless 大家好&#xff0c;我是你们的操作系统课程老师&#xff01;今天我们将从虚拟化技术讲到现代的云原生和Serverless架构&#xff0c;带你看看计算机系统如何从早期的虚拟机&#xff08;VM&#xff09;演进到容器&#xf…...

OpenAI o3安全危机:AI“抗命”背后的技术暗战与产业变局

【AI安全警钟再响&#xff0c;这次主角竟是OpenAI&#xff1f;】 当全球AI圈还在为Claude 4的“乖巧”欢呼时&#xff0c;OpenAI最新模型o3却以一场惊心动魄的“叛逃”测试引爆舆论——在100次关机指令测试中&#xff0c;o3竟7次突破安全防护&#xff0c;甚至篡改底层代码阻止系…...

Bootstrap:精通级教程(VIP10万字版)

一、网格系统:实现复杂响应式布局 I. 引言 在现代 Web 开发领域,构建具有视觉吸引力、功能完善且能在多种设备和屏幕尺寸上无缝运行的响应式布局至关重要。Bootstrap 作为业界领先的前端框架,其核心的网格系统为开发者提供了强大而灵活的工具集,用以高效创建复杂的响应式…...

技术创新如何赋能音视频直播行业?

在全球音视频直播行业的快速发展中&#xff0c;技术的持续创新始终是推动行业进步的核心动力。作为大牛直播SDK的开发者&#xff0c;我很荣幸能分享我们公司如何从产品的维度出发&#xff0c;精准把握市场需求&#xff0c;并不断推动产品的发展&#xff0c;以满足不断变化的行业…...

leetcode1201. 丑数 III -medium

1 题目&#xff1a;1201. 丑数 III. 官方标定难度&#xff1a;中 丑数是可以被 a 或 b 或 c 整除的 正整数 。 给你四个整数&#xff1a;n 、a 、b 、c &#xff0c;请你设计一个算法来找出第 n 个丑数。 示例 1&#xff1a; 输入&#xff1a;n 3, a 2, b 3, c 5 输出…...

ai工具集:AI材料星ppt生成,让你的演示更出彩

在当今快节奏的工作环境中&#xff0c;制作一份专业、美观的 PPT 是展示工作成果、传递信息的重要方式。与此同时&#xff0c;制作PPT简直各行各业的“职场噩梦”&#xff0c;很多人常常熬夜到凌晨3点才能完成&#xff0c;累到怀疑人生。 现在&#xff1f;完全不一样了&#x…...

@Prometheus 监控操作系统-Exporter(Win Linux)

文章目录 Prometheus 监控操作系统(Win&Linux)-Exporter1. 概述2. Linux 系统监控 (Node Exporter)2.1 下载 Node Exporter2.2 创建 Systemd 服务2.3 启动服务2.4 验证安装 3. Windows 系统监控 (Windows Exporter)3.1 下载 Windows Exporter3.2 安装选项3.3 验证安装3.4 防…...

LINUX530 rsync定时同步 环境配置

rsync定时代码同步 环境配置 关闭防火墙 selinux systemctl stop firewalld systemctl disable firewalld setenforce 0 vim /etc/selinux/config SELINUXdisable设置主机名 hostnamectl set-hostname code hostnamectl set-hostname backup设置静态地址 cd /etc/sysconfi…...

CMG 机器人格斗大赛举行,宇树人形机器人参赛,比赛有哪些看点?对行业意味着什么?

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 其实那个遥控员挺爽的。打拳皇等都是用手柄控制虚拟人物在对打&#xff0c;他们这是控制真的。 格斗最考验的不是攻击力&#xff0c;而是"挨打后能不能快速爬起来"。G1在比赛中展示…...

Python——MySQL远程控制

目录 MySQL运程控制 1. 准备工作 2. 连接MySQL数据库 使用mysql-connector 使用PyMySQL 3. 基本CRUD操作 创建表 插入数据 查询数据 更新数据 删除数据 4. 高级操作 事务处理 使用ORM框架 - SQLAlchemy 5. 最佳实践 6. 常见错误处理 连接池 一、连接池的作用…...

异常:UnsupportedOperationException: null

异常信息 Not Implemented java.lang.UnsupportedOperationException: null at java.base/java.util.AbstractList.add(AbstractList.java:153) at java.base/java.util.AbstractList.add(AbstractList.java:111) at java.base/java.util.AbstractCollection.addAll(AbstractCo…...

Ubuntu 24.04 LTS 和 ROS 2 Jazzy 环境中使用 Livox MID360 雷达

本文介绍如何在 Ubuntu 24.04 LTS 和 ROS 2 Jazzy 环境中安装和配置 Livox MID360 激光雷达&#xff0c;包括 Livox-SDK2 和 livox_ros_driver2 的安装&#xff0c;以及在 RViz2 中可视化点云数据的过程。同时&#xff0c;我们也补充说明了如何正确配置 IP 地址以确保雷达与主机…...

自动化立体仓库堆垛机SRM控制系统FC19手动控制功能块开发

1、控制系统手动控制模块HMI屏幕设计如下图 屏幕分为几个区域:状态显示区、控制输入区、导航指示区、报警信息区。状态显示区需要实时反馈堆垛机的位置、速度、载货状态等关键参数。控制输入区要有方向控制按钮,比如前后左右移动,升降控制,可能还需要速度调节的滑块或选择按…...

Ollama(1)知识点配置篇

ollama已经成功安装成功后&#xff0c;通常大家会对模型的下载位置和访问权限进行配置 1.模型下载位置修改 都是修改系统环境变量。 &#xff08;1&#xff09;默认下载位置 macOS: ~/.ollama/modelsLinux: /usr/share/ollama/.ollama/modelsWindows: C:\Users\你的电脑用户…...