Newer
Older
robot_dog_patrol_front / src / components / aMap / index.vue
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
import dogUrl from '../../assets/tempImages/dog.png'
import markerUrl from '../../assets/marker/marker.png'
import alarmUrl from '../../assets/marker/alarm.png'
const props = defineProps({
  patrolInfoList: Array as any,
})
// 定义巡航点的 GPS 坐标
const cruisePoints: any = ref([])

const mapContainer = ref<HTMLDivElement | null>(null)
const map: any = ref(null) // 地图实例
const markerLayer = ref<AMap.OverlayGroup | null>(null) // 巡检路线点图层
const dogLayer = ref<AMap.OverlayGroup | null>(null) // 狗实时位置图层
const alarmLayer = ref<AMap.OverlayGroup | null>(null) // 报警点图层
const dogMarker = ref<AMap.Marker | null>(null)
const dogMarkerPosition = ref() // 狗的当前位置
const markersList: any = ref([])
// 初始化地图
const initMap = async () => {
  if (!mapContainer.value) { return }
  console.log('aMapIndex: aMapSecret', window.localStorage.getItem('aMapSecret'))
  console.log('aMapIndex: aMapKey', window.localStorage.getItem('aMapKey'))
  try {
    if (!window._AMapSecurityConfig) {
      // 安全码
      window._AMapSecurityConfig = {
        securityJsCode: window.localStorage.getItem('aMapSecret'),
      }
    }
    // 加载高德地图 API
    const AMap = await AMapLoader.load({
      key: window.localStorage.getItem('aMapKey')!, // 替换为你的高德地图 API Key
      version: '2.0',
      plugins: ['AMap.Scale', 'AMap.MoveAnimation'],
    })

    // 创建地图实例
    map.value = new AMap.Map(mapContainer.value, {
      zoom: 18,
      center: [116.397428, 39.90923],
      viewMode: '2D', // 地图模式
    })
    console.log('map实例', map.value)
  }
  catch (error) {
    console.error('Failed to load AMap API:', error)
    return null
  }
}

// ----------------------------------------marker-----------------------------------------------
const markerInfoWindow = ref() // marker信息窗口
// 关闭报警信息窗口
function closeMarkerInfoWindow() {
  if (markerInfoWindow.value) {
    markerInfoWindow.value.close()
  }
}
// 移除标记图层
const removeMarkerLayer = () => {
  if (map.value && markerLayer.value) {
    map.value.remove(markerLayer.value)
    markerLayer.value = null
  }
  closeMarkerInfoWindow()
}

// 创建巡航点标记
const createMarkers = () => {
  if (!map.value) { return }
  removeMarkerLayer()
  // 创建marker图层
  if (!markerLayer.value) {
    markerLayer.value = new AMap.OverlayGroup()
  }

  cruisePoints.value.forEach((item: any, index: number) => {
    let labelText = ''
    if (index === 0) {
      labelText = '起'
    }
    else if (index === cruisePoints.value.length - 1) {
      labelText = '终'
    }
    else {
      labelText = `${index + 1}`
    }
    const markerContent = `
    <div style="width: 40px; height: 40px; position: relative; display: flex; justify-content: center;">
        <img src="${markerUrl}" style="width: 100%; height: 100%; object-fit: cover;">
        <span style="position: absolute; color: #fff; font-size: 13px;padding-top: 4px;font-weight: 600;">${labelText}</span>
    </div>
    `

    const marker = new AMap.Marker({
      position: item.point,
      content: markerContent,
      offset: new AMap.Pixel(-15, -15), // 调整标记的偏移量,使其居中显示
    })

    const content = '<div style="font-size: 16px;padding: 6px"> <div style="font-weight: bold;padding-bottom: 10px">巡检点信息</div>'
          + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">巡检路线:</label>${item.routeName}</div>`
          + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">小区名称:</label>${item.communityName}</div>`
          + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">小区地址:</label>${item.communityAddress}</div>`
          + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">点位编号:</label>${item.routeNumber}</div>`
          + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">位ㅤㅤ置:</label>${item.gpsX}, ${item.gpsY}</div>`
          + '</div>'
    // 创建 InfoWindow 并设置内容
    const infoWindow = new AMap.InfoWindow({
      content,
      offset: new AMap.Pixel(0, -30),
    })
    markerInfoWindow.value = infoWindow
    // 绑定点击事件,点击 marker 显示 InfoWindow
    marker.on('click', () => {
      infoWindow.open(map.value, marker.getPosition())
    })
    // map.value.setView(cruisePoints.value[cruisePoints.value.length - 1].point)
    markersList.value.push(marker)
    markerLayer.value.addOverlay(marker)
    map.value.setFitView(markersList.value)
  })
  map.value.add(markerLayer.value)
}

// 创建连线
const createPolylines = () => {
  if (!map.value || !markerLayer.value) { return }
  for (let i = 1; i < cruisePoints.value.length; i++) {
    const polyline = new AMap.Polyline({
      path: [cruisePoints.value[i - 1].point, cruisePoints.value[i].point],
      strokeColor: '#FF33FF',
      strokeWeight: 2,
      strokeOpacity: 1,
      lineJoin: 'round',
      lineCap: 'round',
      zIndex: 50,
    })
    markerLayer.value.addOverlay(polyline)
  }
}

// 创建标记和连线并添加到图层
const createMarkersAndPolylines = () => {
  if (!map.value) { return }
  markerLayer.value = new AMap.OverlayGroup()
  createMarkers()
  createPolylines()
  map.value.add(markerLayer.value)
}

// --------------------------------------------dog图层------------------------------------------------
// 移除狗图层
const removeDogLayer = () => {
  if (map.value && dogLayer.value) {
    map.value.remove(dogLayer.value)
    dogLayer.value = null
  }
}

// 创建移动标记并添加到 dogLayer 图层
const createDogMarker = () => {
  if (!map.value) { return }
  // 创建 dogLayer 图层
  dogLayer.value = new AMap.OverlayGroup()
  map.value.add(dogLayer.value)
  const movingMarkerContent = `
    <div style="width: 50px; height: 50px;">
      <img src="${dogUrl}" style="width: 100%; height: 100%; object-fit: cover;">
    </div>
  `

  const movingMarker = new AMap.Marker({
    position: cruisePoints.value[0].point,
    content: movingMarkerContent,
    offset: new AMap.Pixel(-15, -15),
  })
  dogMarkerPosition.value = cruisePoints.value[0].point
  dogLayer.value.addOverlay(movingMarker)
  dogMarker.value = movingMarker
}

// 开始移动标记
const startMoving = (e: any, endPoint: string[]) => {
  if (!dogMarker.value) {
    console.error('移动标记不存在')
    return
  }
  console.log('点击开始移动', cruisePoints.value)
  if (map.value && dogLayer.value && dogMarker.value) {
    const path = [dogMarkerPosition.value, endPoint]
    console.log('移动路径', path)
    dogMarker.value.moveAlong(path, {
      // speed: 10000, // 移动速度为 x米/秒
      loop: false, // 不循环移动
      duration: window.localStorage.getItem('dogWalkTime') || 2000, // 动画时长
      autoRotation: false, // 标记根据移动方向自动旋转
    })
    dogMarkerPosition.value = endPoint
  }
}
// --------------------------------------------报警点图层------------------------------------------------
const alarmInfoWindow = ref() // 报警信息窗口
// 创建报警点图层
const createAlarmLayer = () => {
  if (!alarmLayer.value) {
    alarmLayer.value = new AMap.OverlayGroup()
    map.value.add(alarmLayer.value)
  }
}
// 移除报警点图层
const removeAlarmLayer = () => {
  if (map.value && alarmLayer.value) {
    map.value.remove(alarmLayer.value)
    alarmLayer.value = null
  }
  closeAlarmInfoWindow()
}

// 绘制报警点
const createAlarmMarkers = (pointInfo: any) => {
  if (!map.value) { return }
  removeAlarmLayer()
  createAlarmLayer()
  const alarmPoint = [pointInfo.gpsX, pointInfo.gpsY]
  const alarmMarkerContent = `
    <div style="width: 40px; height: 40px; position: relative; display: flex; justify-content: center;">
        <img src="${alarmUrl}" style="width: 100%; height: 100%; object-fit: cover;">
    </div>
    `

  const alarmMarker = new AMap.Marker({
    position: alarmPoint,
    content: alarmMarkerContent,
    offset: new AMap.Pixel(-15, -15), // 调整标记的偏移量,使其居中显示
  })
  const contentNoImg = '<div style="font-size: 16px;padding: 6px"> <div style="font-weight: bold;padding-bottom: 10px; color: #d81e06">报警信息</div>'
        + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">小区名称:</label>${pointInfo.communityName}</div>`
        + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">小区地址:</label>${pointInfo.communityAddress}</div>`
        + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">点位编号:</label>${pointInfo.routeNumber}</div>`
        + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">巡检内容:</label>${pointInfo.patrolContent}</div>`
        + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">甲烷浓度:</label>${pointInfo.gas}ppm.m</div>`
        + `<div style="font-size: 14px;padding-bottom: 6px"><label style="font-weight: bold;padding-right: 16px">位ㅤㅤ置:</label>${pointInfo.gpsX}, ${pointInfo.gpsY}</div>`
        + '</div>'

  const contentImg = `
    <div style="font-size: 16px; padding: 6px; display: flex;">
        <div style="width: 300px;">
            <div style="white-space: noWrap;font-weight: bold; padding-bottom: 10px; color: #d81e06">报警信息</div>
            <div style="font-size: 14px; padding-bottom: 6px"><label style="font-weight: bold; padding-right: 16px">小区名称:</label>${pointInfo.communityName}</div>
            <div style="font-size: 14px; padding-bottom: 6px"><label style="font-weight: bold; padding-right: 16px">小区地址:</label>${pointInfo.communityAddress}</div>
            <div style="white-space: noWrap;font-size: 14px; padding-bottom: 6px"><label style="font-weight: bold; padding-right: 16px">点位编号:</label>${pointInfo.routeNumber}</div>
            <div style="font-size: 14px; padding-bottom: 6px"><label style="font-weight: bold; padding-right: 16px">巡检内容:</label>${pointInfo.patrolContent}</div>
            <div style="font-size: 14px; padding-bottom: 6px"><label style="font-weight: bold; padding-right: 16px">甲烷浓度:</label>${pointInfo.gas}ppm.m</div>
            <div style="white-space: noWrap;font-size: 14px; padding-bottom: 6px"><label style="font-weight: bold; padding-right: 16px">位ㅤㅤ置:</label>${pointInfo.gpsX}, ${pointInfo.gpsY}</div>
        </div>
        <div style="margin-left: 10px;flex: 1; display: flex; justify-content: center; align-items: center;">
          <img src="${pointInfo.picture}" style="width: 300px; height: 200px;" alt="报警图片">
        </div>
    </div>
  `

  const content = pointInfo.picture ? contentImg : contentNoImg
  // 创建 InfoWindow 并设置内容
  const infoWindow = new AMap.InfoWindow({
    content,
    offset: new AMap.Pixel(0, -30),
  })
  alarmInfoWindow.value = infoWindow
  infoWindow.open(map.value, alarmMarker.getPosition()) // 自动打开InfoWindow

  // 绑定点击事件,点击 marker 显示
  alarmMarker.on('click', () => {
    infoWindow.open(map.value, alarmMarker.getPosition())
  })
  alarmLayer.value.addOverlay(alarmMarker)
  map.value.setCenter(alarmPoint)
  map.value.setZoom(17) // 设置合适的缩放级别
  map.value.add(alarmLayer.value)
}

// 关闭报警信息窗口
function closeAlarmInfoWindow() {
  if (alarmInfoWindow.value) {
    alarmInfoWindow.value.close()
    if (markersList.value.length && map.value) {
      map.value.setFitView(markersList.value)
    }
  }
}
// -----------------------------------------------------------------------------------------------

watch(() => props.patrolInfoList, async (newVal) => {
  console.log('监听到巡检点变化', newVal)

  if (!map.value) {
    await initMap()
  }
  if (Array.isArray(newVal) && newVal.length) {
    cruisePoints.value = newVal.map((item: any) => {
      return {
        ...item,
        point: [Number(item.gpsX), Number(item.gpsY)],
      }
    })
    createMarkersAndPolylines()
  }
}, { deep: true, immediate: true })

onMounted(async () => {
  await initMap()
})
// ----------------------- 以下是暴露的方法内容 ----------------------------
defineExpose({
  createMarkers,
  createPolylines,
  removeMarkerLayer,
  createDogMarker,
  startMoving,
  createAlarmMarkers,
  closeAlarmInfoWindow, // 关闭报警信息窗口
  removeAlarmLayer, // 移除报警图层
})
</script>

<template>
  <div ref="mapContainer" style="width: 100%; height: 500px;" />
</template>