vue3使用腾讯地图实现socket+实时折线 和 地图轨迹

48 阅读4分钟

## 进入地图两种情况,状态2显示实时折现,其他的显示地图轨迹

<script setup lang="ts">
import { getDictListApi } from '@/service/api/common';
import { ref, watch, nextTick, onBeforeUnmount } from 'vue';
import { WS_TRANSPORTATION_GPS_URL } from '@/config';
import { gettransportGpsListApi } from '@/service/api/purchaseOrder';
import { throttle } from 'lodash-es'
 
 
const status = defineModel('status');
 
const orderDetails: any = defineModel('orderDetails');
 
const emit = defineEmits(['update:mileage', 'update:paths']);
 
let map = <any>(null);
 
const TMap = (window as any).TMap;
 
let multiPolyline: any = null;
 
const startAddressList = ref<any[]>([]);
 
const endAddressList = ref<any[]>([]);
 
const start = ref([])
 
const end = ref([])
 
const ws = ref<any>(null);
 
const polyArray = ref<any[]>([]);
 
const initMap = async () => {
  // 如果status等于3表示运输中,否则需要获取订单的轨迹
  if (status.value === 3) {
    getSockeData()
    transportationInit()
  } else {
    trajectoryInit()
  }
};
 
// 路线
const transportationInit = () => {
 
  initMapData()
 
  // 添加开始和结束点标记
  addStartAndEndMarker()
 
  //创建 MultiPolyline
  multiPolylineInit()
 
}
 
// 轨迹
const trajectoryInit = async () => {
 
  initMapData()
 
  // 添加开始和结束点标记
  addStartAndEndMarker()
 
  // 移动轨迹路线
  moveTrajectory()
 
}
 
// 初始化地图
const initMapData = () => {
  const center = new TMap.LatLng(39.80, 109.97); // 设置中心点坐标
 
  // 初始化地图
  map = new TMap.Map('container', {
    center: center, // 设置地图中心点坐标
    zoom: 17.2, // 设置地图缩放级别
    pitch: 43.5, // 设置俯仰角
    rotation: 45 // 设置地图旋转角度
  });
}
 
const getSockeData = () => {
 
  // 建立连接
  ws.value = new WebSocket(`${WS_TRANSPORTATION_GPS_URL}?transportOrderId=${orderDetails.value.transportOrderId}`)
 
  // 连接成功
  ws.value.onopen = () => {
    console.log('WebSocket 连接成功')
  }
 
  // 接收消息
  ws.value.onmessage = (event: any) => {
    const jsonData = JSON.parse(event.data)
 
    if (jsonData.type === 'GPS_DATA') {
 
      // 如果jsonData.data是数组
      if (jsonData.data && Array.isArray(jsonData.data)) {
        changeJsonDataArray(jsonData)
      }
 
      // 如果jsonData.data是对象
      if (jsonData.data && typeof jsonData.data === 'object' && !Array.isArray(jsonData.data)) {
        changeJsonDataObject(jsonData)
      }
 
    }
 
    // 连接关闭
    ws.value.onclose = () => {
      console.log('WebSocket 连接关闭')
    }
 
    // 出错
    ws.value.onerror = (error: any) => {
      console.error('WebSocket 错误:', error)
    }
  }
 
}
 
// jsonData.data是数组的情况
const changeJsonDataArray = (jsonData: any) => {
 
  emit('update:mileage', jsonData.data[jsonData.data.length - 1].totalDistance)
  emit('update:paths', jsonData.data[jsonData.data.length - 1].currentGpsPoint)
 
  // 全部点位
  let latLngArray = [];
 
  if (jsonData.data.length > 0) {
    latLngArray = jsonData.data.map((item: any) => {
      return [
        item.currentGpsPoint.latitude,
        item.currentGpsPoint.longitude
      ]
    })
  }
 
  // 开始点位
  const startPoint = [start.value[0], start.value[1]];
 
  polyArray.value = [startPoint, ...latLngArray]
 
  if (multiPolyline) {
    multiPolyline.updateGeometries({//新的折线添加到图层中
      'id': 'pl_1',
      'styleId': 'style_blue',
      'paths': polyArray.value.map(p => new TMap.LatLng(p[0], p[1]))
    })
 
    // 数组情况下,使用最后一个
    const newCenter = new TMap.LatLng(polyArray.value[polyArray.value.length - 1][0], polyArray.value[polyArray.value.length - 1][1]);
    map.setCenter(newCenter);
  }
}
 
// jsonData.data是对象的情况
const changeJsonDataObject = (jsonData: any) => {
  emit('update:mileage', jsonData.data.totalDistance)
  emit('update:paths', jsonData.data.currentGpsPoint)
 
  if (multiPolyline) {
    polyArray.value.push([jsonData.data.currentGpsPoint.latitude, jsonData.data.currentGpsPoint.longitude])
 
    multiPolyline.updateGeometries({
      'id': 'pl_1',
      'styleId': 'style_blue',
      'paths': polyArray.value.map(p => new TMap.LatLng(p[0], p[1]))
    })
 
    // 对象情况下
    const newCenter = new TMap.LatLng(jsonData.data.currentGpsPoint.latitude, jsonData.data.currentGpsPoint.longitude);
    map.setCenter(newCenter);
  }
}
 
// 获取起始地和目的地
const getStartAndEnd = async () => {
  try {
    const res1 = await getDictListApi('start_address')
    startAddressList.value = res1.data.data
 
    const res2 = await getDictListApi('aim_address')
    endAddressList.value = res2.data.data
 
  } catch (error) {
    startAddressList.value = []
    endAddressList.value = []
    console.log(error)
  }
}
 
// 创建 MultiPolyline
const multiPolylineInit = () => {
  multiPolyline = new TMap.MultiPolyline({
    id: 'polyline-layer', //图层唯一标识
    map: map,//设置折线图层显示到哪个地图实例中
    //折线样式定义
    styles: {
      'style_blue': new TMap.PolylineStyle({
        'color': '#3777FF', //线填充色
        'width': 6, //折线宽度
        'borderWidth': 5, //边线宽度
        'borderColor': '#FFF', //边线颜色
        'lineCap': 'butt' //线端头方式
      }),
    },
    //折线数据定义
    geometries: [
      {
        id: 'pl_1',//折线唯一标识,删除时使用
        styleId: 'style_blue',//绑定样式名
        paths: [
          new TMap.LatLng(start.value[0], start.value[1]),
          new TMap.LatLng(start.value[0], start.value[1]),
        ]
      },
    ]
  })
 
}
 
// 添加开始和结束点标记
const addStartAndEndMarker = () => {
 
  start.value = startAddressList.value.find((item: any) => Number(item.dictValue) === Number(orderDetails.value.startAddress)).remark.split(',').map(Number)
 
  end.value = endAddressList.value.find((item: any) => Number(item.dictValue) === Number(orderDetails.value.aimAddress)).remark.split(',').map(Number)
 
  new TMap.MultiMarker({
    map: map,
    styles: {
      startStyle: new TMap.MarkerStyle({
        width: 40,
        height: 40,
        anchor: { x: 15, y: 40 },
        src: 'https://minio.zkny.top/zkny-bucket-sm-modules-system/image/2025/09/18/94074516-57f0-47cf-92b1-316d266485a2.png' // 自定义图标(开始点)
      }),
      endStyle: new TMap.MarkerStyle({
        width: 40,
        height: 40,
        anchor: { x: 15, y: 40 },
        src: 'https://minio.zkny.top/zkny-bucket-sm-modules-system/image/2025/09/18/c335e841-42ae-4fff-8594-f74e780b0c3c.png' // 自定义图标(结束点)
      })
    },
    geometries: [
      {
        id: 'start',
        styleId: 'startStyle',
        position: new TMap.LatLng(start.value[0], start.value[1])
      },
      {
        id: 'end',
        styleId: 'endStyle',
        position: new TMap.LatLng(end.value[0], end.value[1])
      }
    ]
  });
 
}
 
const emitPaths = throttle((lat: number, lng: number) => {
  emit('update:paths', { latitude: lat, longitude: lng })
}, 1000) // 1000ms 一次
 
// 移动轨迹路线
const moveTrajectory = async () => {
  try {
    const res = await gettransportGpsListApi(orderDetails.value.transportOrderId)
 
    if (res.data.code === 200) {
      // 获取轨迹点
      const path = res.data.data.map((item: any) => {
        return new TMap.LatLng(item.currentGpsPoint.latitude, item.currentGpsPoint.longitude);
      });
 
      // 往轨迹点前面添加开始,最后添加结束点位
      path.unshift(new TMap.LatLng(start.value[0], start.value[1]))
 
      path.push(new TMap.LatLng(end.value[0], end.value[1]))
 
      // 创建折线
      new TMap.MultiPolyline({
        id: 'polyline-layer', //图层唯一标识
        map: map,//设置折线图层显示到哪个地图实例中
        //折线样式定义
        styles: {
          'style_blue': new TMap.PolylineStyle({
            'color': '#3777FF', //线填充色
            'width': 6, //折线宽度
            'borderWidth': 5, //边线宽度
            'borderColor': '#FFF', //边线颜色
            'lineCap': 'butt' //线端头方式
          }),
        },
        //折线数据定义
        geometries: [
          {
            id: 'pl_1',//折线唯一标识,删除时使用
            styleId: 'style_blue',//绑定样式名
            paths: path
          },
        ]
      })
 
      //创建mareker(小车)
      var marker = new TMap.MultiMarker({
        map,
        styles: { //样式设置
          'car-down': new TMap.MarkerStyle({
            'width': 40,  //小车图片宽度(像素)
            'height': 40, //高度
            'anchor': {   //图片中心的像素位置(小车会保持车头朝前,会以中心位置进行转向)
              x: 20, y: 20,
            },
            'faceTo': 'map',  //取’map’让小车贴于地面,faceTo取值说明请见下文图示
            'rotate': 180,    //初始小车朝向(正北0度,逆时针一周为360度,180为正南)
            'src': 'https://minio.zkny.top/zkny-bucket-sm-modules-system/image/2025/09/19/6f013209-fb57-45c4-b9c1-72ad216a8d9e.png',   //小车图片(图中小车车头向上,即正北0度)
          })
        },
        geometries: [{    //小车marker的位置信息
          id: 'car',      //因MultiMarker支持包含多个点标记,因此要给小车一个id
          styleId: 'car-down',    //绑定样式
          position: new TMap.LatLng(start.value[0], start.value[1]),//初始坐标位置
        }]
      });
 
      //调用moveAlong,实现小车移动
      marker.moveAlong({
        "car": {    //设置让"car"沿"path"移动,速度70公里/小时
          path,
          speed: 70
        }
      }, {
        autoRotation: true   //车头始终向前(沿路线自动旋转)
      }
      )
 
      marker.on('moving', (evt: any) => {
        if (evt.car && evt.car.passedLatLngs.length > 0) {
          const carPos = evt.car.passedLatLngs[evt.car.passedLatLngs.length - 1]
          map.setCenter(carPos)  // 视角跟随小车
 
          const paths = {
            latitude: carPos.lat,
            longitude: carPos.lng
          }
 
          emitPaths(paths.latitude, paths.longitude)
 
        }
      })
    }
  } catch (error) {
    console.log(error)
  }
}
 
watch(orderDetails, async () => {
  if (map) {
    map.destroy();
    map = null;
  }
 
  await nextTick();
  await getStartAndEnd()
  initMap();
 
}, { immediate: true })
 
onBeforeUnmount(() => {
  console.log('关闭WebSocket')
  ws.value?.close()
})
 
</script>
 
<template>
  <div class="wh-full relative">
    <div id="container" class="h-full w-full"></div>
  </div>
</template>
 
<style></style>