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

news2025/6/2 17:56:55

目录

  • 背景
  • 轨迹回放 - 匀速
    • 效果图
    • `TrackPlaybackDialog.vue` 代码
    • `TMapNew.vue` 代码
  • 轨迹回放 - 非匀速
    • 效果图
    • `TrackPlaybackDialog.vue` 代码
    • `TMapNew.vue` 代码

背景

腾讯地图轨迹回放是匀速回放的,但是客户要求根据现实时间,什么时间点在某个点位

  • 【腾讯地图轨迹回放是匀速回放】:比如一段轨迹中不同时间有三四个点是在同一位置(类似警员在某一点位N秒没动,不同时间取到的点位是一个),轨迹回放的时候,停留的这个点直接经过了,而不是与实际一致轨迹停留N秒,即 轨迹回放时停留点的时间 ≠ 警员实际停留时间,所有的轨迹回放是匀速的。
  • 【客户要求根据现实时间,什么时间点在某个点位】:每两个点之间轨迹回放的时间与实际时间一致,也就是警员在某一点位N秒没动,轨迹回放的时候也要在该点停留N秒之后再继续,即 轨迹回放时停留点的时间 = 警员实际停留时间,所有的轨迹回放不是匀速的。

或者 警员有时开车有时走路,时速是不一样的,要求轨迹回放与实际时速一致,而不是一直匀速

轨迹回放 - 匀速

将所有点位一次回放完成 ----匀速

代码中只需看标注 【主要代码】 的代码,其余代码不重要

效果图

35秒匀速回放所有轨迹

在这里插入图片描述

TrackPlaybackDialog.vue 代码

<!-- 轨迹回放 -->
<template>
  <el-dialog
    v-loading="loading"
    :title="title + '轨迹回放'"
    width="90%"
    top="5vh"
    :close-on-click-modal="false"
    destroy-on-close
    append-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>
    <TMapNew
      v-if="dialogVisible"
      ref="TMapRef"
      map-name="basicInspection"
      :id-name="'TrackPlayback' + idNameTail"
      @moveEnd="moveEnd"
      @updateNowLc="updateNowLc"
    />
    <div class="operation">
      回放时间:
      <!-- <el-time-select
        v-model="startTime"
        placeholder="起始时间"
        size="mini"
        :picker-options="startTimeOptions"
      />
      至
      <el-time-select
        v-model="endTime"
        class="end_time"
        placeholder="结束时间"
        size="mini"
        :picker-options="endTimeOptions"
      /> -->

      <el-date-picker
        v-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-option
          v-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>
          <div
            class="range"
            :style="{
              '--timeRangeWidth': timeRangeWidth * 100 + '%',
              '--timeRangeLeft': timeRangeLeft * 100 + '%'
            }"
          />
        </div>
      </div>

      <el-select
        v-if="trackType === '1'"
        v-model="sblx"
        class="sblx"
        placeholder="请选择"
        size="mini"
      >
        <el-option
          v-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) return
      const [start, end] = val
      if (+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') return

      this.queryTrackDetail()
    },
    speed() {
      if (!this.dialogVisible) return
      this.isMoveStartFirst = true
    }
  },

  created() {},

  mounted() {},

  methods: {
    getTimeRange() {
      const [start, end] = this.time
      const startH = start.slice(11, 13) * 1
      const startM = start.slice(14, 16) * 1
      const endH = end.slice(11, 13) * 1
      const endM = end.slice(14, 16) * 1

      const isSameDay = start.slice(8, 10) === end.slice(8, 10)

      const allRange = 24 * 60

      if (!isSameDay) {
        this.timeRangeList = [...Array(24).keys()].map((item) => (item + startH) % 24)

        const timeRange = allRange - (startH * 60 + startM) + endH * 60 + endM
        this.timeRangeWidth = timeRange / allRange
        this.timeRangeLeft = 0
      } else {
        const timeRange = endH * 60 + endM - (startH * 60 + startM)
        this.timeRangeWidth = timeRange / allRange
        this.timeRangeLeft = (startH * 60 + startM) / allRange
      }
    },
    open(data, { title = '', type = '' }) {
      this.title = title
      this.trackType = type
      console.log('data--轨迹回放弹框--', data)
      this.dialogVisible = true
      this.infoData = data.properties || data || {}

      this.$nextTick(() => {
        // const ssbm = '331002005300' // 测试数据
        const ssbm = this.infoData.dept_code || this.infoData.dwdm || this.infoData.ssbm

        setDeptArea(
          this,
          ssbm,
          { isSetCenter: false, clearArea: 'drawerRedPolygon' },
          (areaData) => {
            this.deptAreaData = areaData
          }
        ) // 部门辖区

        /** ** 执勤区域展示 - start ****/
        const { dutyAreaInfo } = this.infoData
        console.log('dutyAreaInfo----open打印', dutyAreaInfo)
        if (dutyAreaInfo) {
          const { center, drawerType, ...areaInfo } = dutyAreaInfo
          this.dutyAreaInfo[drawerType] = areaInfo // 保存执勤区域数据 - 便于隐藏区域后再展示区域
          // 回溯 - 地图中展示的警员执勤区域带过来,轨迹回放这里也展示这个执勤区域
          const { drawerAreaHandler } = this.$refs.TMapRef
          setTimeout(() => {
            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.timeRange
          this.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.isShowDrawer
      const { drawerAreaHandler, clearAreaHandler } = this.$refs.TMapRef
      if (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.infoData

      const paramsObj = {
        1: {
          sblx: this.sblx,
          jh,
          userId: recentUserId
        },
        2: {
          deviceIndexCode
        },
        4: {
          deviceIndexCode
        }
      }

      this.loading = true

      this.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 || 0
              this.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.TMapRef
              basicInspectionTrack(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 } = item
            const 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}`] = drawerInfo

            this.$refs.TMapRef.drawerAreaHandler(`drawer${type}`, drawerInfo, `drawer${type}`)
          })
        })
      })
    },

    moveHandler() {
      this.isMove = !this.isMove

      const { basicInspectionMove, basicInspectionResumeMove, resetMoveJwdlength } =
        this.$refs.TMapRef

      if (this.isMove) {
        if (this.isMoveStartFirst) {
          this.isMoveStartFirst = false
          const startTime = this.positions[0].gpsTime
          const endTime = this.positions[this.positions.length - 1].gpsTime
          const duration = dayjs(endTime).diff(dayjs(startTime), 'seconds') * 1000
          resetMoveJwdlength()
          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 = false
      this.isMoveStartFirst = true
      this.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 = false
      this.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 = 0
      this.isMove = false
      this.isMoveStartFirst = true
      this.speed = 1
      this.timeRangeList = [...Array(24).keys()]
      this.sblx = '1'
      this.curPosition = {}
      this.isShowDrawer = true
      this.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: {}, // 多个 label
      trackQueryMultiMarker: {
        jy: null,
        jc: null,
        jk: null
      },
      trackQueryMultiLabel: {
        jy: null,
        jc: null,
        jk: null
      },
      editor: null,
      activeType: 'marker',
      activeId: '', // 值格式为 6C5895CE-B42D-4E9B-A8FA-81135761CBDD
      moveJwdlength: 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 = null
    this.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_url
      const { map } = this
      if (!url) return
      new TMap.WMTSLayer({
        url, // 地图服务地址
        map, // 展示图层的地图对象
        minZoom: 3, // 最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3
        maxZoom: 20, // 最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20
        visible: true, // 是否可见,默认为true
        zIndex: 1, // 图层绘制顺序
        opacity: 0.9, // 图层透明度,默认为1
        params: {
          // 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 || 14

      this.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 = this
      function 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.zoom
        if (this.curZoom > this.setLabelZoom) {
          !Object.keys(this.multiLabelLayer).length && this.$emit('setLabel', this.curZoom)
        } else {
          this.clearMultiLabel()
        }
      })

      if (this.idName === 'trackQueryTXMapContanier') {
        let timer = null
        this.map.on('center_changed', (params) => {
          clearTimeout(timer)
          timer = setTimeout(() => {
            const center = params.center
            console.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

      // 初始marker
      this.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

      // 初始marker
      this.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

      // 初始marker
      this.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

      // 初始marker
      this.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) return

      const 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.length
          this.$emit('updateNowLc', this.moveJwdlength - 1)
        }
        ;['iconMove', 'nameMove'].forEach((key) => {
          if (passedLatLngs) {
            // 使用路线擦除接口 eraseTo, https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVector
            this.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 = id

      this.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/glDocEditor
        map: 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 } = geometry
        this.activeId = id

        const maxRadius = this.sysConfigData.mon_map_maxRadius * 1 || 500
        if (!!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 } = errInfo
        if (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}`, // 图层id
          map: this.map, // 显示多边形图层的底图
          geometries: [
            {
              id: `polygon-${index}`, // 多边形图形数据的标志信息
              styleId: 'polygon', // 样式id
              paths: 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}`, // 图层id
          map: 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', // 样式id
              paths: 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 // 图层层级小于设置的图层层级时,不显示label

      this.$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', // 样式id
              position: 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-dialog
    v-loading="loading"
    :title="title + '轨迹回放'"
    width="90%"
    top="5vh"
    :close-on-click-modal="false"
    destroy-on-close
    append-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>
    <TMapNew
      v-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-picker
        v-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-option
          v-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>
          <div
            class="range"
            :style="{
              '--timeRangeWidth': timeRangeWidth * 100 + '%',
              '--timeRangeLeft': timeRangeLeft * 100 + '%'
            }"
          />
        </div>
      </div>

      <el-select
        v-if="trackType === '1'"
        v-model="sblx"
        class="sblx"
        placeholder="请选择"
        size="mini"
      >
        <el-option
          v-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) return
      const [start, end] = val
      if (+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') return

      this.queryTrackDetail()
    },
    speed() {
      if (!this.dialogVisible) return
      this.isMoveStartFirst = true
    }
  },

  created() {},

  mounted() {},

  methods: {
    getTimeRange() {
      const [start, end] = this.time
      const startH = start.slice(11, 13) * 1
      const startM = start.slice(14, 16) * 1
      const endH = end.slice(11, 13) * 1
      const endM = end.slice(14, 16) * 1

      const isSameDay = start.slice(8, 10) === end.slice(8, 10)

      const allRange = 24 * 60

      if (!isSameDay) {
        this.timeRangeList = [...Array(24).keys()].map((item) => (item + startH) % 24)

        const timeRange = allRange - (startH * 60 + startM) + endH * 60 + endM
        this.timeRangeWidth = timeRange / allRange
        this.timeRangeLeft = 0
      } else {
        const timeRange = endH * 60 + endM - (startH * 60 + startM)
        this.timeRangeWidth = timeRange / allRange
        this.timeRangeLeft = (startH * 60 + startM) / allRange
      }
    },
    open(data, { title = '', type = '' }) {
      this.title = title
      this.trackType = type
      console.log('data--轨迹回放弹框--', data)
      this.dialogVisible = true
      this.infoData = data.properties || data || {}

      this.$nextTick(() => {
        // const ssbm = '331002005300' // 测试数据
        const ssbm = this.infoData.dept_code || this.infoData.dwdm || this.infoData.ssbm

        setDeptArea(
          this,
          ssbm,
          { isSetCenter: false, clearArea: 'drawerRedPolygon' },
          (areaData) => {
            this.deptAreaData = areaData
          }
        ) // 部门辖区

        /** ** 执勤区域展示 - start ****/
        const { dutyAreaInfo } = this.infoData
        console.log('dutyAreaInfo----open打印', dutyAreaInfo)
        if (dutyAreaInfo) {
          const { center, drawerType, ...areaInfo } = dutyAreaInfo
          this.dutyAreaInfo[drawerType] = areaInfo // 保存执勤区域数据 - 便于隐藏区域后再展示区域
          // 回溯 - 地图中展示的警员执勤区域带过来,轨迹回放这里也展示这个执勤区域
          const { drawerAreaHandler } = this.$refs.TMapRef
          setTimeout(() => {
            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.timeRange
          this.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.isShowDrawer
      const { drawerAreaHandler, clearAreaHandler } = this.$refs.TMapRef
      if (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.infoData

      const paramsObj = {
        1: {
          sblx: this.sblx,
          jh,
          userId: recentUserId
        },
        2: {
          deviceIndexCode
        },
        4: {
          deviceIndexCode
        }
      }

      this.loading = true

      this.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 || 0
              this.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.TMapRef
              basicInspectionTrack(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 } = item
            const 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}`] = drawerInfo

            this.$refs.TMapRef.drawerAreaHandler(`drawer${type}`, drawerInfo, `drawer${type}`)
          })
        })
      })
    },

    /** ** 【主要代码】主要逻辑处理 start ****/
    moveHandler() {
      this.isMove = !this.isMove

      const { basicInspectionResumeMove, resetMoveJwdlength } = this.$refs.TMapRef

      /**
       * 判断是移动还是暂停
       */
      if (this.isMove) {
        // 移动
        /**
         * 判断是否为第一次回放,即是不是暂停后继续回放
         */
        if (this.isMoveStartFirst) {
          this.isMoveStartFirst = false

          this.moveRangeIndex = 0
          resetMoveJwdlength()
          this.moveRange()
        } else {
          basicInspectionResumeMove()
        }
      } else {
        // 暂停
        this.positionTrans() // 将当前经纬度调接口翻译为地址
        this.pauseTrack() // 暂停轨迹回放
      }
    },

    // 一段一段轨迹回放
    moveRange() {
      console.log('"moveRange"----打印', 'moveRange')
      const { basicInspectionMove, resetMoveJwdlength, addAlreadyMovePoint } = this.$refs.TMapRef

      this.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 += sliceLength

      console.log('this.moveRangeIndex----打印', this.moveRangeIndex)

      const startTime = movePath[0].gpsTime
      const endTime = movePath[movePath.length - 1].gpsTime
      const 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 = null
        clearTimeout(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 = false
      this.isMoveStartFirst = true
      this.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 = false
      this.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 = 0
      this.isMove = false
      this.isMoveStartFirst = true
      this.speed = 1
      this.timeRangeList = [...Array(24).keys()]
      this.sblx = '1'
      this.curPosition = {}
      this.isShowDrawer = true
      this.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: {}, // 多个 label
      trackQueryMultiMarker: {
        jy: null,
        jc: null,
        jk: null
      },
      trackQueryMultiLabel: {
        jy: null,
        jc: null,
        jk: null
      },
      editor: null,
      activeType: 'marker',
      activeId: '', // 值格式为 6C5895CE-B42D-4E9B-A8FA-81135761CBDD
      alreadyMovePoint: []
    }
  },
  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 = null
    this.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_url
      const { map } = this
      if (!url) return
      new TMap.WMTSLayer({
        url, // 地图服务地址
        map, // 展示图层的地图对象
        minZoom: 3, // 最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3
        maxZoom: 20, // 最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20
        visible: true, // 是否可见,默认为true
        zIndex: 1, // 图层绘制顺序
        opacity: 0.9, // 图层透明度,默认为1
        params: {
          // 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 || 14

      this.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 = this
      function 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.zoom
        if (this.curZoom > this.setLabelZoom) {
          !Object.keys(this.multiLabelLayer).length && this.$emit('setLabel', this.curZoom)
        } else {
          this.clearMultiLabel()
        }
      })

      if (this.idName === 'trackQueryTXMapContanier') {
        let timer = null
        this.map.on('center_changed', (params) => {
          clearTimeout(timer)
          timer = setTimeout(() => {
            const center = params.center
            console.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

      // 初始marker
      this.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

      // 初始marker
      this.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

      // 初始marker
      this.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

      // 初始marker
      this.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) return

      const 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/glDocVector
            this.MultiPolyline.eraseTo(
              'erasePath',
              passedLatLngs.length - 1, // 要擦除到的坐标索引 index
              passedLatLngs[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 = id

      this.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/glDocEditor
        map: 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 } = geometry
        this.activeId = id

        const maxRadius = this.sysConfigData.mon_map_maxRadius * 1 || 500
        if (!!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 } = errInfo
        if (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}`, // 图层id
          map: this.map, // 显示多边形图层的底图
          geometries: [
            {
              id: `polygon-${index}`, // 多边形图形数据的标志信息
              styleId: 'polygon', // 样式id
              paths: 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}`, // 图层id
          map: 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', // 样式id
              paths: 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 // 图层层级小于设置的图层层级时,不显示label

      this.$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', // 样式id
              position: 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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2393990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

【Docker】存储卷

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

OpenCV图像认知(二)

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

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…

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…

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在比赛中展示…

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

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

Ollama(1)知识点配置篇

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

VMware Workstation虚拟系统设置双网口

一.设置windows11系统VMware Network Adapter VMnet1。 1.进入到网络和Internet -> 高级网络设置 2.找到VMware Network Adapter VMnet1&#xff0c;进入到“更多配置选项”并“编辑”。 3.进入到属性&#xff0c;双击“Interenet协议版本4&#xff08;TCP/IPv4&#xff…

山洪灾害声光电监测预警解决方案

一、方案背景 我国是一个多山的国家&#xff0c;山丘区面积约占国土面积的三分之二。每年汛期&#xff0c;受暴雨等因素影响&#xff0c;极易引发山洪和泥石流。山洪、泥石流地质灾害具有突发性、流速快、流量大、物质容量大和破坏力强等特点&#xff0c;一旦发生&#xff0c;将…

【Rust模式与匹配】Rust模式与匹配深入探索与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

electron安装报错处理

electron安装报错 解决方法&#xff1a; 修改 C:\Users\用户名.npmrc下配置文件 添加代码 electron_mirrorhttps://cdn.npmmirror.com/binaries/electron/ electron_builder_binaries_mirrorhttps://npmmirror.com/mirrors/electron-builder-binaries/最后代码 registryhtt…