吉安感知网项目-前端
张含笑
2026-01-17 1c00acc80086877ace3cd5869a2e54e224d6a567
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -1,5 +1,18 @@
<template>
   <div class="command-cesium map-container" :id="props.containerId"></div>
   <CommonCesiumMap
      ref="mapRef"
      class="command-cesium map-container"
      :dom-id="props.containerId"
      :active="true"
      :flat-mode="false"
      :terrain="false"
      :layer-mode="4"
      :contour="false"
      :boundary="false"
      :show-admin-boundary="true"
      :zoom-to-boundary="true"
      @ready="handleMapReady"
   />
   <div v-if="props.showLayerControl" class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
      <div class="layer-control-wrap" ref="layerWrapRef">
         <div class="layer-control" @click="toggleLayerPanel">
@@ -25,7 +38,10 @@
<script setup>
import * as Cesium from 'cesium'
import { PublicCesium } from '@/utils/cesium/publicCesium'
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import { geomAnalysis } from '@ztzf/utils'
import { fwDefenseZonePageApi } from '@/views/areaManage/defenseZone/defenseZoneApi'
import { fwAreaDividePageApi } from '@/views/areaManage/partition/partitionApi'
import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png'
@@ -48,9 +64,13 @@
   },
})
let viewInstance = null
const DEFAULT_ZONE_PAGE_SIZE = 999
const mapRef = ref(null)
let viewer = null
const deviceEntityIds = new Set()
let defenseZoneSource = null
let partitionSource = null
const showLayerPanel = ref(false)
const layerWrapRef = ref(null)
const layerTreeProps = {
@@ -105,10 +125,19 @@
   deviceEntityIds.clear()
}
const clearDefenseZoneEntities = () => {
   if (!defenseZoneSource) return
   defenseZoneSource.entities.removeAll()
}
const clearPartitionEntities = () => {
   if (!partitionSource) return
   partitionSource.entities.removeAll()
}
const RING_STYLES = [
   { inner: 0, outer: 5000, gradient: ['#FF361C', '#360B00'] },
   { inner: 5000, outer: 8000, gradient: ['#FFC609', '#583300'] },
   { inner: 8000, outer: 10000, gradient: ['#2AEDBF', '#012B11'] },
   { inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] }
]
const MATERIAL_TYPE = 'RadialGradientMaterial'
@@ -235,6 +264,103 @@
   })
}
const getDefenseZonePositions = geom => {
   const points = geomAnalysis(geom)
   if (points.length < 3) return []
   const first = points[0]
   const last = points[points.length - 1]
   const list =
      first && last && first.longitude === last.longitude && first.latitude === last.latitude
         ? points.slice(0, -1)
         : points
   return list.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude))
}
const renderZonePolygons = ({ zones, source, idPrefix, lineColor, fillGradient }) => {
   if (!viewer || !source) return
   registerRadialGradientMaterial()
   const borderColor = Cesium.Color.fromCssColorString(lineColor)
   const fillColorStart = Cesium.Color.fromCssColorString(fillGradient[0]).withAlpha(0.34)
   const fillColorEnd = Cesium.Color.fromCssColorString(fillGradient[1]).withAlpha(0.34)
   const fillMaterial = new RadialGradientMaterialProperty(fillColorStart, fillColorEnd)
   zones.forEach((zone, index) => {
      if (!zone?.geom) return
      const positions = getDefenseZonePositions(zone.geom)
      if (!positions.length) return
      const entityId = `${idPrefix}-${zone.id ?? index}`
      const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions
      source.entities.add({
         id: entityId,
         polygon: {
            hierarchy: new Cesium.PolygonHierarchy(positions),
            material: fillMaterial,
            outline: true,
            outlineColor: borderColor,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
         },
         polyline: {
            positions: linePositions,
            clampToGround: true,
            width: 2,
            material: borderColor,
         },
      })
   })
}
const renderDefenseZones = zones => {
   if (!viewer) return
   if (!defenseZoneSource) {
      defenseZoneSource = new Cesium.CustomDataSource('defenseZoneSource')
      viewer.dataSources.add(defenseZoneSource)
   }
   clearDefenseZoneEntities()
   renderZonePolygons({
      zones,
      source: defenseZoneSource,
      idPrefix: 'defense-zone',
      lineColor: '#19D266',
      fillGradient: ['#2AEDBF', '#012B11'],
   })
}
const renderPartitions = zones => {
   if (!viewer) return
   if (!partitionSource) {
      partitionSource = new Cesium.CustomDataSource('partitionSource')
      viewer.dataSources.add(partitionSource)
   }
   clearPartitionEntities()
   renderZonePolygons({
      zones,
      source: partitionSource,
      idPrefix: 'partition-zone',
      lineColor: '#FFCD2A',
      fillGradient: ['#FFC609', '#583300'],
   })
}
const loadDefenseZones = async () => {
   if (!viewer) return
   try {
      const res = await fwDefenseZonePageApi({ current: 1, size: DEFAULT_ZONE_PAGE_SIZE })
      renderDefenseZones(res?.data?.data?.records ?? [])
   } catch (error) {
      renderDefenseZones([])
   }
}
const loadPartitions = async () => {
   if (!viewer) return
   try {
      const res = await fwAreaDividePageApi({ current: 1, size: DEFAULT_ZONE_PAGE_SIZE })
      renderPartitions(res?.data?.data?.records ?? [])
   } catch (error) {
      renderPartitions([])
   }
}
watch(
   () => props.onlineDevices,
   devices => {
@@ -255,6 +381,13 @@
   showLayerPanel.value = !showLayerPanel.value
}
const handleMapReady = ({ viewer: mapViewer }) => {
   viewer = mapViewer
   renderDeviceEntities(props.onlineDevices)
   loadDefenseZones()
   loadPartitions()
}
const handleClickOutside = event => {
   if (!showLayerPanel.value) return
   const target = event.target
@@ -264,26 +397,15 @@
onMounted(() => {
   document.addEventListener('click', handleClickOutside)
   viewInstance = new PublicCesium({
      dom: props.containerId,
      flatMode: false,
      terrain: false,
      layerMode: 4,
      contour: false,
   })
   viewer = viewInstance.getViewer()
   renderDeviceEntities(props.onlineDevices)
   const map = mapRef.value?.getMap()
   if (map?.viewer) handleMapReady(map)
})
onBeforeUnmount(() => {
   document.removeEventListener('click', handleClickOutside)
   clearDeviceEntities()
   if (viewInstance) {
      viewInstance?.viewerDestroy()
      viewInstance = null
   }
   clearDefenseZoneEntities()
   clearPartitionEntities()
   viewer = null
})
</script>
@@ -299,14 +421,14 @@
.layer-control-root {
   position: absolute;
   left: 411px;
   left: 337px;
   bottom: 22px;
   z-index: 9;
   transition: transform 0.3s ease-in-out;
   pointer-events: none;
   &.collapsed {
      transform: translateX(-401px);
      transform: translateX(-317px);
   }
}
@@ -318,8 +440,8 @@
}
.layer-control {
   width: 34px;
   height: 34px;
   width: 46px;
   height: 46px;
   cursor: pointer;
   img {
@@ -333,7 +455,7 @@
   display: flex;
   flex-direction: column;
   position: absolute;
   left: 46px;
   left: 66px;
   bottom: 0;
   width: 160px;
   max-height: 442px;
@@ -370,6 +492,7 @@
         line-height: 30px !important;
         .el-tree-node__content {
            padding-left: 0 !important;
            display: flex;
            align-items: center;
            height: 40px !important;
@@ -381,6 +504,12 @@
         .el-tree-node__children {
            .el-tree-node__content {
               border: none;
            }
         }
         &:focus {
            .el-tree-node__content {
               background: transparent !important;
            }
         }
      }
@@ -412,7 +541,8 @@
   display: none;
}
.layer-panel :deep(.el-tree-node__content:hover) {
.layer-panel :deep(.el-tree-node__content:hover),
.layer-panel :deep(.el-tree-node__content:focus) {
   background: transparent !important;
}