From c1b252b4890ae7df1720955cb7caba184d78798f Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Tue, 27 Jan 2026 11:32:32 +0800
Subject: [PATCH] feat:数据驾驶舱部分调整
---
applications/drone-command/src/components/map-container/device-map-container.vue | 405 +++++++++++++++++++++++++++++++++------------------------
1 files changed, 236 insertions(+), 169 deletions(-)
diff --git a/applications/drone-command/src/components/map-container/device-map-container.vue b/applications/drone-command/src/components/map-container/device-map-container.vue
index a8f8566..49bd52b 100644
--- a/applications/drone-command/src/components/map-container/device-map-container.vue
+++ b/applications/drone-command/src/components/map-container/device-map-container.vue
@@ -2,41 +2,45 @@
<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">
<img :src="layerControlIcon" alt="图层控制" />
</div>
<div v-if="showLayerPanel" class="layer-panel">
- <div class="panel-title">图层管理</div>
+ <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="panel-title">图层管理</div>
<div class="panel-content">
- <el-tree :data="layerTree" show-checkbox default-expand-all node-key="key"
- :props="layerTreeProps" :default-checked-keys="defaultCheckedKeys" />
+ <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>
</div>
- <DevicePopup
- :visible="popupVisible && !isDronePopup"
- :position="popupPosition"
- :device="selectedDevice"
- @close="closePopup"
- />
+ <DevicePopup :visible="popupVisible && !isDronePopup" :position="popupPosition" :device="selectedDevice"
+ @close="closePopup" />
- <DronePopup
- :visible="popupVisible && isDronePopup"
- :position="popupPosition"
- :drone="selectedDevice"
- :favorite="Boolean(selectedDevice?.isFavorite)"
- @close="closePopup"
- @toggle-favorite="toggleDroneFavorite"
- @signal="handleDroneSignal"
- @counter="handleDroneCounter"
- />
+ <DronePopup :visible="popupVisible && isDronePopup" :position="popupPosition" :drone="selectedDevice"
+ :favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="toggleDroneFavorite"
+ @signal="handleDroneSignal" @counter="handleDroneCounter" />
</div>
</template>
@@ -63,6 +67,11 @@
createRadialGradientMaterial,
getTexturedVertexFormat,
} from './device-map-materials'
+import dzIcon from '@/assets/images/dataCockpit/map/dz-map-layer.png'
+import yxIcon from '@/assets/images/dataCockpit/map/yx-map-layer.png'
+
+const CLUSTER_HEIGHT = 100000
+const DETAIL_HEIGHT = 10000
const props = defineProps({
onlineDevices: {
@@ -88,6 +97,7 @@
const mapRef = ref(null)
let viewer = null
+let publicCesium = null
let cockpitPrimitiveLayer = null
const devicePickMap = new Map()
const dronePickMap = new Map()
@@ -110,6 +120,7 @@
const countyCenterMap = new Map()
const showLayerPanel = ref(false)
const layerWrapRef = ref(null)
+const layerTreeRef = ref(null)
const selectedDevice = ref(null)
const selectedTargetType = ref('device')
let selectedDeviceBillboard = null
@@ -119,17 +130,24 @@
label: 'label',
children: 'children',
}
-const defaultCheckedKeys = ['global', 'city-base']
+const baseLayerKeys = ['base-standard', 'base-satellite']
+const defaultCheckedKeys = ['ja-terrain', 'admin', 'city-base']
+const baseLayerKey = ref('base-satellite')
+const treeCheckedKeys = ref([...defaultCheckedKeys])
const layerTree = ref([
{
key: 'base',
label: '地理信息图层',
children: [
- { key: 'global', label: '全球地形' },
+ { key: 'ja-terrain', label: '吉安地形' },
{ key: 'admin', label: '行政区划' },
],
},
- {
+
+])
+
+/**
+ * {
key: 'city',
label: '城市CIM图层',
children: [
@@ -147,7 +165,9 @@
{ key: 'route', label: '飞行航路' },
],
},
-])
+ */
+
+const adminBoundaryVisible = ref(treeCheckedKeys.value.includes('admin'))
const getDevicePosition = item => {
const longitudeRaw = item.longitude ?? item.lng ?? item.lon
@@ -157,6 +177,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 = () => {
@@ -302,7 +330,26 @@
const setClusterVisibility = visible => {
clusterVisible.value = visible
- if (aggregationSource) aggregationSource.show = visible
+ updateAggregationVisibility()
+}
+
+const updateAggregationVisibility = () => {
+ if (!aggregationSource) return
+ aggregationSource.show = clusterVisible.value && adminBoundaryVisible.value
+}
+
+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 = () => {
@@ -316,24 +363,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 +489,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 +519,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 +576,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 +594,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,
@@ -573,34 +650,34 @@
const polygonInstances = []
const lineInstances = []
const texturedVertexFormat = getTexturedVertexFormat()
- ; (zones || []).forEach((zone, index) => {
- if (!zone?.geom) return
- const positions = getDefenseZonePositions(zone.geom)
- if (!positions.length) return
- const polygon = new Cesium.PolygonGeometry({
- polygonHierarchy: new Cesium.PolygonHierarchy(positions),
- vertexFormat: texturedVertexFormat,
+ ; (zones || []).forEach((zone, index) => {
+ if (!zone?.geom) return
+ const positions = getDefenseZonePositions(zone.geom)
+ if (!positions.length) return
+ const polygon = new Cesium.PolygonGeometry({
+ polygonHierarchy: new Cesium.PolygonHierarchy(positions),
+ vertexFormat: texturedVertexFormat,
+ })
+ polygonInstances.push(
+ new Cesium.GeometryInstance({
+ geometry: polygon,
+ })
+ )
+ const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions
+ lineInstances.push(
+ new Cesium.GeometryInstance({
+ geometry: new Cesium.GroundPolylineGeometry({
+ positions: linePositions,
+ width: 2,
+ }),
+ attributes: {
+ color: Cesium.ColorGeometryInstanceAttribute.fromColor(
+ Cesium.Color.fromCssColorString(lineColor)
+ ),
+ },
+ })
+ )
})
- polygonInstances.push(
- new Cesium.GeometryInstance({
- geometry: polygon,
- })
- )
- const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions
- lineInstances.push(
- new Cesium.GeometryInstance({
- geometry: new Cesium.GroundPolylineGeometry({
- positions: linePositions,
- width: 2,
- }),
- attributes: {
- color: Cesium.ColorGeometryInstanceAttribute.fromColor(
- Cesium.Color.fromCssColorString(lineColor)
- ),
- },
- })
- )
- })
let primitive = null
if (polygonInstances.length) {
const baseColor1 = Cesium.Color.fromCssColorString(fillColor1)
@@ -629,7 +706,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 +717,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
@@ -676,7 +753,7 @@
viewer.dataSources.add(aggregationSource)
}
clearAggregationEntities()
- aggregationSource.show = clusterVisible.value
+ updateAggregationVisibility()
const countMap = new Map()
; (list || []).forEach(item => {
if (!item?.type) return
@@ -693,7 +770,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 +783,6 @@
pixelOffset: new Cesium.Cartesian2(0, -35),
disableDepthTestDistance: Number.POSITIVE_INFINITY,
- heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
})
})
@@ -796,17 +872,17 @@
ensureCommandPostCollection()
commandPostBillboardCollection.removeAll()
commandPostBillboardCollection.show = detailVisible.value
- ; (list || []).forEach((item, index) => {
- const position = getDevicePosition(item)
- if (!position) return
- commandPostBillboardCollection.add({
- position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
- image: commandPostIcon,
- width: 40,
- height: 56,
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+ ; (list || []).forEach((item, index) => {
+ const position = getDevicePosition(item)
+ if (!position) return
+ commandPostBillboardCollection.add({
+ position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
+ image: commandPostIcon,
+ width: 40,
+ height: 56,
+ verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+ })
})
- })
reorderCockpitPrimitives()
}
@@ -851,17 +927,43 @@
showLayerPanel.value = !showLayerPanel.value
}
+const applyLayerVisibility = checkedKeys => {
+ publicCesium?.switchLayers?.(baseLayerKey.value === 'base-standard' ? 0 : 4)
+ const showTerrain = checkedKeys.includes('ja-terrain')
+ publicCesium?.setTerrainVisible?.(showTerrain)
+ const showAdmin = checkedKeys.includes('admin')
+ adminBoundaryVisible.value = showAdmin
+ mapRef.value?.setAdminBoundaryVisible?.(showAdmin)
+ updateAggregationVisibility()
+}
+
+const handleLayerCheck = (_data, state) => {
+ const checkedKeys = state?.checkedKeys ?? layerTreeRef.value?.getCheckedKeys?.() ?? []
+ treeCheckedKeys.value = checkedKeys
+ applyLayerVisibility(checkedKeys)
+}
+
+const handleBaseLayerSelect = key => {
+ if (!baseLayerKeys.includes(key)) return
+ if (baseLayerKey.value === key) return
+ baseLayerKey.value = key
+ applyLayerVisibility(treeCheckedKeys.value)
+}
+
const updateStageDisplay = stage => {
const showCluster = stage === 'cluster'
setClusterVisibility(showCluster)
setDetailVisibility(!showCluster)
+ setDroneVisibility(!showCluster)
}
-const handleMapReady = ({ viewer: mapViewer }) => {
+const handleMapReady = ({ viewer: mapViewer, publicCesium: mapPublic }) => {
viewer = mapViewer
+ publicCesium = mapPublic
ensureCockpitPrimitiveLayer()
+ applyLayerVisibility(treeCheckedKeys.value)
const height = viewer?.camera?.positionCartographic?.height
- const stage = height != null && height >= 100000 ? 'cluster' : 'detail'
+ const stage = getStageByHeight(height)
updateStageDisplay(stage)
renderDeviceEntities(props.onlineDevices)
loadDefenseZones()
@@ -904,6 +1006,7 @@
}
cockpitPrimitiveLayer = null
viewer = null
+ publicCesium = null
})
</script>
@@ -986,86 +1089,50 @@
overflow: auto;
}
- ::v-deep(.el-tree) {
- background: transparent;
- color: #c3c3dd;
+ .base-map-title {
+ margin-bottom: 8px;
font-size: 12px;
+ color: #ffffff;
+ font-weight: 600;
+ }
- .el-tree-node {
- line-height: 30px !important;
+ .base-map-options {
+ padding: 16px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+ }
- .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;
+ .base-map-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ border-radius: 6px;
+ color: #d8e6ff;
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+
+ .base-map-thumb {
+ width: 46px;
+ height: 46px;
+ border-radius: 4px;
+ background-size: cover;
+ background-position: center;
+ box-sizing: border-box;
+ }
+
+ &.active {
+ .base-map-thumb {
+ border: 2px solid #2ea8ff;
}
- .el-tree-node__children {
- .el-tree-node__content {
- border: none;
- }
- }
-
- &:focus {
- .el-tree-node__content {
- background: transparent !important;
- }
+ .base-map-label {
+ color: #2ea8ff;
}
}
}
}
-
-.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>
-
--
Gitblit v1.9.3