吉安感知网项目-前端
shuishen
2026-02-25 4b37c1cb3ed6bc9659a347c4d6445b23cd6187b5
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -5,33 +5,35 @@
         :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 v-if="props.showLayerControl" class="layer-control-root" :class="{ collapsed: props.rightCollapsed }">
         <div class="layer-control-wrap" ref="layerWrapRef">
            <div class="layer-control" @click="toggleLayerPanel">
               <img :src="layerControlIcon" alt="图层控制" />
            </div>
            <div v-if="showLayerPanel" class="layer-panel">
               <div class="panel-title">底图切换</div>
               <div class="base-map-options">
                  <div class="base-map-card" :class="{ active: baseLayerKey === 'base-standard' }"
                     @click="handleBaseLayerSelect('base-standard')">
                     <img class="base-map-thumb standard" :src="dzIcon" alt="">
                     <div class="base-map-label">标准地图</div>
            <transition name="layer-panel-slide">
               <div v-if="showLayerPanel" class="layer-panel">
                  <div class="panel-title">底图切换</div>
                  <div class="base-map-options">
                     <div class="base-map-card" :class="{ active: baseLayerKey === 'base-standard' }"
                        @click="handleBaseLayerSelect('base-standard')">
                        <img class="base-map-thumb standard" :src="dzIcon" alt="">
                        <div class="base-map-label">标准地图</div>
                     </div>
                     <div class="base-map-card" :class="{ active: baseLayerKey === 'base-satellite' }"
                        @click="handleBaseLayerSelect('base-satellite')">
                        <img class="base-map-thumb satellite" :src="yxIcon" alt="">
                        <div class="base-map-label">卫星地图</div>
                     </div>
                  </div>
                  <div class="base-map-card" :class="{ active: baseLayerKey === 'base-satellite' }"
                     @click="handleBaseLayerSelect('base-satellite')">
                     <img class="base-map-thumb satellite" :src="yxIcon" alt="">
                     <div class="base-map-label">卫星地图</div>
                  </div>
               </div>
               <div class="panel-title">图层管理</div>
               <div class="panel-content">
                  <el-tree ref="layerTreeRef" class="command-tree map-layer-tree" :data="layerTree" show-checkbox
                     default-expand-all node-key="key" :props="layerTreeProps"
                     :default-checked-keys="treeCheckedKeys" @check="handleLayerCheck" />
                  <div class="panel-title">图层管理</div>
                  <div class="panel-content">
                     <el-tree ref="layerTreeRef" class="command-tree map-layer-tree" :data="layerTree" show-checkbox
                        default-expand-all node-key="key" :props="layerTreeProps"
                        :default-checked-keys="treeCheckedKeys" @check="handleLayerCheck" />
                  </div>
               </div>
            </div>
            </transition>
         </div>
      </div>
@@ -77,6 +79,7 @@
const DETAIL_HEIGHT = 10000
const POLYGON_HEIGHT_M = 0.2
const DRONE_TRACK_DURATION_S = 30 * 60
const FIXED_DRONE_TRACK_IDS = ['6000000000602', '6000000000601']
const props = defineProps({
   allDevices: {
@@ -87,7 +90,7 @@
      type: Array,
      default: () => [],
   },
   leftCollapsed: {
   rightCollapsed: {
      type: Boolean,
      default: false,
   },
@@ -302,10 +305,10 @@
   if (deviceRingOutlinePrimitives.length) {
      deviceRingOutlinePrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
   }
   if (commandPostBillboardCollection) cockpitPrimitiveLayer.add(commandPostBillboardCollection)
   if (deviceBillboardCollection) cockpitPrimitiveLayer.add(deviceBillboardCollection)
   if (droneTrackPolylineCollection) cockpitPrimitiveLayer.add(droneTrackPolylineCollection)
   if (droneTrackBillboardCollection) cockpitPrimitiveLayer.add(droneTrackBillboardCollection)
   if (deviceBillboardCollection) cockpitPrimitiveLayer.add(deviceBillboardCollection)
   if (commandPostBillboardCollection) cockpitPrimitiveLayer.add(commandPostBillboardCollection)
}
const clearDeviceEntities = () => {
@@ -465,7 +468,7 @@
         },
         {
            longitude: 114.963974,
            latitude:  27.132677,
            latitude: 27.132677,
            height: 320
         },
         {
@@ -512,22 +515,22 @@
            height: 400
         },
         {
            longitude: 114.925836,
            longitude: 114.925836,
            latitude: 27.096102,
            height: 400
         },
         {
            longitude: 114.928384,
            longitude: 114.928384,
            latitude: 27.095866,
            height: 400
         },
         {
            longitude: 114.929874,
            longitude: 114.929874,
            latitude: 27.096095,
            height: 400
         },
         {
            longitude: 114.931048,
            longitude: 114.931048,
            latitude: 27.097666,
            height: 400
         },
@@ -628,11 +631,11 @@
      const pos = isEntityPosition
         ? getBillboardPosition(track.billboard)
         : Cesium.Cartesian3.lerp(
               track.positions[seg],
               track.positions[seg + 1],
               ratio,
               new Cesium.Cartesian3()
           )
            track.positions[seg],
            track.positions[seg + 1],
            ratio,
            new Cesium.Cartesian3()
         )
      if (!pos) return
      if (!isEntityPosition) {
         track.billboard.position = pos
@@ -718,7 +721,9 @@
   droneTrackPolylineCollection.show = detailVisible.value
   droneTrackRuntime = []
   const baseTrackColor = Cesium.Color.fromCssColorString('red')
      ; (list || []).forEach((item, trackIndex) => {
      ; (FIXED_DRONE_TRACK_IDS || []).forEach((trackId, trackIndex) => {
         const item = (list || []).find(entry => String(entry?.id) === String(trackId))
         if (!item) return
         const position = getDevicePosition(item)
         if (!position) return
         const points = buildSimulatedTrackPoints({ ...position, height: item.flightHeightM, trackIndex })
@@ -742,7 +747,7 @@
            width: 3,
            material: trackMaterial,
         })
         const droneId = `drone-alarm-${item?.alarmRecordId ?? item?.id ?? trackIndex}`
         const droneId = `drone-alarm-${trackId}`
         const startTime = viewer.clock.startTime
         const stopTime = viewer.clock.stopTime
         const positionProperty = new Cesium.SampledPositionProperty()
@@ -845,6 +850,7 @@
            width: 40,
            height: 56,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
         })
         billboard.id = entry.entityId
         devicePickMap.set(entry.entityId, { data: entry.data, billboard })
@@ -973,7 +979,7 @@
   if (!trimmed) return null
   try {
      return JSON.parse(trimmed)
   } catch (error) {}
   } catch (error) { }
   return null
}
@@ -1026,7 +1032,7 @@
                  .filter(radius => Number.isFinite(radius) && radius > 0)
               radii.forEach((radius, levelIndex) => {
                  const positions = buildEllipsePositions(centerCartesian, radius, radius)
               const style = BUFFER_LEVEL_STYLES[levelIndex] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
                  const style = BUFFER_LEVEL_STYLES[levelIndex] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
                  if (positions.length >= 3) {
                     shapes.push({
                        positions,
@@ -1234,6 +1240,7 @@
            width: 40,
            height: 56,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
         })
      })
   }
@@ -1285,7 +1292,7 @@
)
watch(
   () => props.leftCollapsed,
   () => props.rightCollapsed,
   isCollapsed => {
      if (isCollapsed) showLayerPanel.value = false
   }
@@ -1326,7 +1333,7 @@
   setDroneVisibility(!showCluster)
}
const handleMapReady = ({ viewer: mapViewer, publicCesium: mapPublic }) => {
const  handleMapReady = async ({ viewer: mapViewer, publicCesium: mapPublic }) => {
   if (mapReadyHandled) return
   mapReadyHandled = true
   viewer = mapViewer
@@ -1336,10 +1343,10 @@
   const height = viewer?.camera?.positionCartographic?.height
   const stage = getStageByHeight(height)
   updateStageDisplay(stage)
   await loadCommandPosts()
   renderDeviceEntities(props.allDevices)
   loadPartitions()
   loadAggregation()
   loadCommandPosts()
   renderSimulatedDroneTrack(props.alarmDrones)
   initDeviceClickHandler()
}
@@ -1377,6 +1384,20 @@
   viewer = null
   publicCesium = null
})
const getMap = () => {
   return mapRef.value?.getMap()
}
// 暴露给父组件调用
const flyTo = (options) => {
   mapRef.value?.flyToPoint(options)
}
defineExpose({
   getMap,
   flyTo
})
</script>
<style lang="scss" scoped>
@@ -1396,14 +1417,14 @@
.layer-control-root {
   position: absolute;
   left: 337px;
   bottom: 22px;
   z-index: 9;
   right: 337px;
   top: 95px;
   z-index: 11;
   transition: transform 0.3s ease-in-out;
   pointer-events: none;
   &.collapsed {
      transform: translateX(-317px);
      transform: translateX(317px);
   }
}
@@ -1430,12 +1451,13 @@
   display: flex;
   flex-direction: column;
   position: absolute;
   left: 66px;
   bottom: 0;
   right: 66px;
   top: 0;
   width: 160px;
   max-height: 442px;
   background: #191932;
   border-radius: 8px 8px 8px 8px;
   z-index: 99;
   .panel-title {
      padding: 0 16px;
@@ -1504,4 +1526,16 @@
      }
   }
}
.layer-panel-slide-enter-active,
.layer-panel-slide-leave-active {
   transition: transform 0.24s ease, opacity 0.24s ease;
   transform-origin: right center;
}
.layer-panel-slide-enter-from,
.layer-panel-slide-leave-to {
   transform: translateX(24px);
   opacity: 0;
}
</style>