吉安感知网项目-前端
shuishen
2026-01-17 86c2d61ba9d33f1886cfc904dcaf17f033e4c80a
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -1,5 +1,5 @@
<template>
   <div class="ztzf-cesium map-container" :id="props.containerId"></div>
   <div class="command-cesium map-container" :id="props.containerId"></div>
   <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">
@@ -26,6 +26,10 @@
<script setup>
import * as Cesium from 'cesium'
import { PublicCesium } from '@/utils/cesium/publicCesium'
import { geomAnalysis } from '@ztzf/utils'
import { fwDefenseZonePageApi } from '@/views/areaManage/defenseZone/defenseZoneApi'
import { fwAreaDividePageApi } from '@/views/areaManage/partition/partitionApi'
import jaGeojsonRaw from '@/assets/geojson/ja.geojson?raw'
import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png'
@@ -48,9 +52,17 @@
   },
})
const DEFAULT_ZONE_PAGE_SIZE = 999
let viewInstance = null
let viewer = null
const deviceEntityIds = new Set()
let defenseZoneSource = null
let partitionSource = null
let adminBoundarySource = null
let adminBoundaryLabelSource = null
let adminBoundaryLineSource = null
let adminBoundaryFlyDone = false
const showLayerPanel = ref(false)
const layerWrapRef = ref(null)
const layerTreeProps = {
@@ -105,10 +117,33 @@
   deviceEntityIds.clear()
}
const clearDefenseZoneEntities = () => {
   if (!defenseZoneSource) return
   defenseZoneSource.entities.removeAll()
}
const clearPartitionEntities = () => {
   if (!partitionSource) return
   partitionSource.entities.removeAll()
}
const clearAdminBoundaryEntities = () => {
   if (!adminBoundarySource) return
   adminBoundarySource.entities.removeAll()
}
const clearAdminBoundaryLabels = () => {
   if (!adminBoundaryLabelSource) return
   adminBoundaryLabelSource.entities.removeAll()
}
const clearAdminBoundaryLines = () => {
   if (!adminBoundaryLineSource) return
   adminBoundaryLineSource.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 +270,180 @@
   })
}
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([])
   }
}
const loadAdminBoundary = async () => {
   if (!viewer) return
   try {
      if (adminBoundarySource) {
         viewer.dataSources.remove(adminBoundarySource)
         adminBoundarySource = null
      }
      if (adminBoundaryLabelSource) {
         viewer.dataSources.remove(adminBoundaryLabelSource)
         adminBoundaryLabelSource = null
      }
      if (adminBoundaryLineSource) {
         viewer.dataSources.remove(adminBoundaryLineSource)
         adminBoundaryLineSource = null
      }
      const geojson = JSON.parse(jaGeojsonRaw)
      adminBoundarySource = await Cesium.GeoJsonDataSource.load(geojson, {
         stroke: Cesium.Color.fromCssColorString('#FFFFFF'),
         strokeWidth: 2,
         fill: Cesium.Color.fromCssColorString('#FFFFFF').withAlpha(0.05),
         clampToGround: true,
      })
      viewer.dataSources.add(adminBoundarySource)
      if (!adminBoundaryFlyDone) {
         adminBoundaryFlyDone = true
         viewer.flyTo(adminBoundarySource, {
            duration: 0,
            offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-75), 0),
         })
      }
      adminBoundaryLineSource = new Cesium.CustomDataSource('adminBoundaryLineSource')
      adminBoundarySource.entities.values.forEach(entity => {
         const polygon = entity.polygon
         if (!polygon) return
         const hierarchy = polygon.hierarchy?.getValue?.(viewer.clock.currentTime)
         const positions = hierarchy?.positions
         if (!positions?.length) return
         adminBoundaryLineSource.entities.add({
            polyline: {
               positions,
               clampToGround: true,
               width: 2,
               material: Cesium.Color.WHITE,
            },
         })
      })
      viewer.dataSources.add(adminBoundaryLineSource)
      adminBoundaryLabelSource = new Cesium.CustomDataSource('adminBoundaryLabelSource')
      geojson.features?.forEach(feature => {
         const name = feature?.properties?.name
         const point = feature?.properties?.centroid || feature?.properties?.center
         if (!name || !Array.isArray(point) || point.length < 2) return
         adminBoundaryLabelSource.entities.add({
            position: Cesium.Cartesian3.fromDegrees(point[0], point[1]),
            label: {
               text: name,
               font: '14px Source Han Sans CN',
               fillColor: Cesium.Color.WHITE,
               outlineColor: Cesium.Color.BLACK.withAlpha(0.6),
               outlineWidth: 2,
               style: Cesium.LabelStyle.FILL_AND_OUTLINE,
               heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
               verticalOrigin: Cesium.VerticalOrigin.CENTER,
               horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
               disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
         })
      })
      viewer.dataSources.add(adminBoundaryLabelSource)
   } catch (error) {
      clearAdminBoundaryEntities()
      clearAdminBoundaryLabels()
      clearAdminBoundaryLines()
   }
}
watch(
   () => props.onlineDevices,
   devices => {
@@ -274,11 +483,19 @@
   viewer = viewInstance.getViewer()
   renderDeviceEntities(props.onlineDevices)
   loadDefenseZones()
   loadPartitions()
   loadAdminBoundary()
})
onBeforeUnmount(() => {
   document.removeEventListener('click', handleClickOutside)
   clearDeviceEntities()
   clearDefenseZoneEntities()
   clearPartitionEntities()
   clearAdminBoundaryEntities()
   clearAdminBoundaryLabels()
   clearAdminBoundaryLines()
   if (viewInstance) {
      viewInstance?.viewerDestroy()
      viewInstance = null
@@ -299,14 +516,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 +535,8 @@
}
.layer-control {
   width: 34px;
   height: 34px;
   width: 46px;
   height: 46px;
   cursor: pointer;
   img {
@@ -333,7 +550,7 @@
   display: flex;
   flex-direction: column;
   position: absolute;
   left: 46px;
   left: 66px;
   bottom: 0;
   width: 160px;
   max-height: 442px;
@@ -370,6 +587,7 @@
         line-height: 30px !important;
         .el-tree-node__content {
            padding-left: 0 !important;
            display: flex;
            align-items: center;
            height: 40px !important;
@@ -381,6 +599,12 @@
         .el-tree-node__children {
            .el-tree-node__content {
               border: none;
            }
         }
         &:focus {
            .el-tree-node__content {
               background: transparent !important;
            }
         }
      }
@@ -412,7 +636,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;
}