吉安感知网项目-前端
shuishen
2026-01-27 98ff0df343bd4e5d2d5e138a02c6db2b7fa9696b
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -2,8 +2,9 @@
   <div class="map-shell">
      <CommonCesiumMap ref="mapRef" class="command-cesium map-container" :dom-id="props.containerId" :active="true"
         :flat-mode="false" :terrain="true" :layer-mode="4" :contour="false" :boundary="false"
         :show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true" :cluster-height="100000"
         @ready="handleMapReady" @stage-change="handleStageChange" />
         :show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true"
         :cluster-height="CLUSTER_HEIGHT" :detail-height="DETAIL_HEIGHT" @ready="handleMapReady"
         @stage-change="handleStageChange" />
      <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">
@@ -13,7 +14,7 @@
               <div class="panel-title">图层管理</div>
               <div class="panel-content">
                  <el-tree :data="layerTree" show-checkbox default-expand-all node-key="key"
                  <el-tree class="command-tree" :data="layerTree" show-checkbox default-expand-all node-key="key"
                     :props="layerTreeProps" :default-checked-keys="defaultCheckedKeys" />
               </div>
            </div>
@@ -63,6 +64,9 @@
   createRadialGradientMaterial,
   getTexturedVertexFormat,
} from './device-map-materials'
const CLUSTER_HEIGHT = 100000
const DETAIL_HEIGHT = 10000
const props = defineProps({
   onlineDevices: {
@@ -157,6 +161,14 @@
   const latitude = Number(latitudeRaw)
   if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) return null
   return { longitude, latitude }
}
const getDeviceRange = item => {
   if (!item) return null
   const rawRange = item.effectiveRangeKm ?? item.range ?? item.coverRadiusM
   const range = Number(rawRange)
   if (!Number.isFinite(range) || range <= 0) return null
   return range
}
const ensureCockpitPrimitiveLayer = () => {
@@ -305,6 +317,20 @@
   if (aggregationSource) aggregationSource.show = visible
}
const setDroneVisibility = visible => {
   if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible
   if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible
   if (!visible && selectedTargetType.value === 'drone') {
      closePopup()
   }
}
const getStageByHeight = height => {
   if (height == null) return 'detail'
   if (height >= CLUSTER_HEIGHT) return 'cluster'
   if (height <= DETAIL_HEIGHT) return 'detail'
   return 'mid'
}
const ensureCountyCenterMap = () => {
   if (countyCenterMap.size) return
   const geojson = JSON.parse(jaGeojsonRaw)
@@ -316,24 +342,44 @@
   })
}
const buildSimulatedTrackPoints = base => {
   ensureCountyCenterMap()
   const anchor = base || { longitude: 116.397, latitude: 39.908 }
   const offsets = [
      [0, 0, 120],
      [0.02, 0.012, 150],
      [0.04, 0.006, 180],
      [0.06, -0.008, 200],
      [0.03, -0.02, 160],
      [0.0, -0.015, 140],
      [-0.02, -0.005, 120],
      [-0.01, 0.01, 130],
const buildSimulatedTrackPoints = () => {
   return [
      {
         longitude: 114.963191,
         latitude: 27.136716,
         height: 120
      },
      {
         longitude:114.957308,
         latitude: 27.138452,
         height: 120
      },
      {
         longitude:114.952,
         latitude: 27.136317,
         height: 120
      },
      {
         longitude:114.949293,
         latitude: 27.133864,
         height: 120
      },
      {
         longitude:114.944666,
         latitude: 27.130526,
         height: 120
      },
      {
         longitude:114.945909,
         latitude:27.127845,
         height: 120
      },
      {
         longitude:114.962974,
         latitude:27.136242,
         height: 120
      },
   ]
   return offsets.map(item => ({
      longitude: anchor.longitude + item[0],
      latitude: anchor.latitude + item[1],
      height: item[2],
   }))
}
@@ -422,13 +468,11 @@
const renderSimulatedDroneTrack = () => {
   if (!viewer) return
   clearDroneTrackEntities()
   ensureCountyCenterMap()
   ensureDroneTrackCollections()
   droneTrackBillboardCollection.show = detailVisible.value
   droneTrackPolylineCollection.show = detailVisible.value
   droneTrackRuntime = []
   const centers = Array.from(countyCenterMap.entries())
   const baseCenters = centers.length
      ? centers.slice(0, 4)
      : [['默认区域', { longitude: 116.397, latitude: 39.908 }]]
   const baseCenters = [['默认区域', { longitude:114.958541, latitude: 27.121917 }]]
   const segmentDuration = 6
   const baseTrackColor = Cesium.Color.fromCssColorString('red')
   baseCenters.forEach(([centerName, center], trackIndex) => {
@@ -454,7 +498,7 @@
      })
      const droneId = `drone-track-${trackIndex}`
      const billboard = droneTrackBillboardCollection.add({
         position: positions[0],
         position: positions[positions.length - 1],
         image: droneIcon,
         width: 36,
         height: 36,
@@ -511,7 +555,8 @@
      const position = getDevicePosition(item)
      if (!position) return
      const entityId = `online-device-${item.id ?? index}-${index}`
      addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle)
      const rangeMeters = getDeviceRange(item)
      addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters)
      const billboard = deviceBillboardCollection.add({
         position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
         image: equipmentIcon,
@@ -528,9 +573,20 @@
   ringFillInstancesByStyle.forEach((instances, index) => {
      if (!instances.length) return
      const ring = RING_STYLES[index]
      const alpha = ring.alpha ?? 0.64
      const innerRatio =
         typeof ring.innerRatio === 'number'
            ? ring.innerRatio
            : ring.inner && ring.outer
               ? Math.min(Math.max(ring.inner / ring.outer, 0), 0.9)
               : 0
      const material = createRadialGradientMaterial(
         Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.64),
         Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(0.64)
         Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(alpha),
         Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(alpha),
         {
            gamma: 1.7,
            innerCutoff: innerRatio,
         }
      )
      const primitive = new Cesium.GroundPrimitive({
         geometryInstances: instances,
@@ -629,7 +685,7 @@
const renderDefenseZones = zones => {
   if (!viewer) return
   clearDefenseZoneEntities()
   const result = buildZonePrimitives(zones, '#19D266', '#2AEDBF', '#012B11')
   const result = buildZonePrimitives(zones, '#72F3FF', '#72F3FF', '#0A7C88')
   defenseZonePrimitive = result.primitive
   defenseZoneOutlinePrimitive = result.outlinePrimitive
   if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value
@@ -640,7 +696,7 @@
const renderPartitions = zones => {
   if (!viewer) return
   clearPartitionEntities()
   const result = buildZonePrimitives(zones, '#FFCD2A', '#FFC609', '#583300')
   const result = buildZonePrimitives(zones, '#FFD772', '#FFD772', '#A86B00')
   partitionPrimitive = result.primitive
   partitionOutlinePrimitive = result.outlinePrimitive
   if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
@@ -693,7 +749,7 @@
            width: 66.73,
            height: 43,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            // disableDepthTestDistance: Number.POSITIVE_INFINITY,
         },
         label: {
            text: `${count}`,
@@ -706,7 +762,6 @@
            pixelOffset: new Cesium.Cartesian2(0, -35),
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
         },
      })
   })
@@ -855,13 +910,14 @@
   const showCluster = stage === 'cluster'
   setClusterVisibility(showCluster)
   setDetailVisibility(!showCluster)
   setDroneVisibility(!showCluster)
}
const handleMapReady = ({ viewer: mapViewer }) => {
   viewer = mapViewer
   ensureCockpitPrimitiveLayer()
   const height = viewer?.camera?.positionCartographic?.height
   const stage = height != null && height >= 100000 ? 'cluster' : 'detail'
   const stage = getStageByHeight(height)
   updateStageDisplay(stage)
   renderDeviceEntities(props.onlineDevices)
   loadDefenseZones()
@@ -985,87 +1041,6 @@
      flex: 1;
      overflow: auto;
   }
   ::v-deep(.el-tree) {
      background: transparent;
      color: #c3c3dd;
      font-size: 12px;
      .el-tree-node {
         line-height: 30px !important;
         .el-tree-node__content {
            padding-left: 0 !important;
            display: flex;
            align-items: center;
            height: 40px !important;
            line-height: 40px !important;
            border-bottom: 1px solid rgba(70, 70, 100, 0.5);
            box-sizing: border-box;
         }
         .el-tree-node__children {
            .el-tree-node__content {
               border: none;
            }
         }
         &:focus {
            .el-tree-node__content {
               background: transparent !important;
            }
         }
      }
   }
}
.layer-panel :deep(.el-tree-node__label) {
   color: #c3c3dd;
}
.layer-panel :deep(.el-tree-node__expand-icon) {
   order: 3;
   margin-left: auto;
}
.layer-panel :deep(.el-tree-node__expand-icon.is-leaf) {
   visibility: hidden;
}
.layer-panel :deep(.el-checkbox) {
   order: 1;
}
.layer-panel :deep(.el-tree-node__label) {
   order: 2;
}
.layer-panel :deep(.el-tree-node__expand-icon:not(.is-leaf) ~ .el-checkbox) {
   display: none;
}
.layer-panel :deep(.el-tree-node__content:hover),
.layer-panel :deep(.el-tree-node__content:focus) {
   background: transparent !important;
}
.layer-panel :deep(.el-tree-node.is-current > .el-tree-node__content) {
   background: transparent !important;
   color: #ffffff;
}
.layer-panel :deep(.el-tree-node.is-current > .el-tree-node__content .el-tree-node__label) {
   color: #ffffff;
}
.layer-panel :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
   background-color: #023aff;
   border-color: #023aff;
}
.layer-panel :deep(.el-checkbox__inner) {
   background-color: transparent;
   border-color: #a1a3d4;
}
</style>