吉安感知网项目-前端
shuishen
2026-01-27 c1b252b4890ae7df1720955cb7caba184d78798f
feat:数据驾驶舱部分调整
5 files modified
3 files added
403 ■■■■ changed files
applications/drone-command/src/assets/images/dataCockpit/map/blue-map-layer.png patch | view | raw | blame | history
applications/drone-command/src/assets/images/dataCockpit/map/dz-map-layer.png patch | view | raw | blame | history
applications/drone-command/src/assets/images/dataCockpit/map/yx-map-layer.png patch | view | raw | blame | history
applications/drone-command/src/components/map-container/common-cesium-map.vue 84 ●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/device-map-container.vue 238 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/styles/common/cockpit.scss 28 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/adminBoundary.js 3 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/publicCesium.js 50 ●●●● patch | view | raw | blame | history
applications/drone-command/src/assets/images/dataCockpit/map/blue-map-layer.png
applications/drone-command/src/assets/images/dataCockpit/map/dz-map-layer.png
applications/drone-command/src/assets/images/dataCockpit/map/yx-map-layer.png
applications/drone-command/src/components/map-container/common-cesium-map.vue
@@ -3,7 +3,7 @@
</template>
<script setup>
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { onBeforeUnmount, onMounted, watch } from 'vue'
import { PublicCesium } from '@/utils/cesium/publicCesium'
import { loadJaAdminBoundary, removeJaAdminBoundary } from '@/utils/cesium/adminBoundary'
@@ -60,8 +60,13 @@
const emit = defineEmits(['ready', 'height-change', 'stage-change'])
const mapId = props.domId || `common-cesium-map-${Math.random().toString(36).slice(2, 10)}`
const viewInstance = ref(null)
const viewer = ref(null)
let viewInstance = null
let viewer = null
const ADMIN_BOUNDARY_NAMES = {
    boundary: 'jaBoundarySource',
    line: 'jaBoundaryLineSource',
    label: 'jaBoundaryLabelSource',
}
let adminBoundarySources = null
let initialized = false
let removeCameraListener = null
@@ -69,7 +74,7 @@
const initMap = async () => {
    if (initialized) return
    initialized = true
    viewInstance.value = new PublicCesium({
    viewInstance = new PublicCesium({
        dom: mapId,
        flatMode: props.flatMode,
        terrain: props.terrain,
@@ -77,16 +82,16 @@
        boundary: props.boundary,
        contour: props.contour,
    })
    viewer.value = viewInstance.value.getViewer()
    viewer = viewInstance.getViewer()
    if (props.showAdminBoundary) {
        adminBoundarySources = await loadJaAdminBoundary(viewer.value, {
        adminBoundarySources = await loadJaAdminBoundary(viewer, {
            zoomTo: props.zoomToBoundary,
        })
    }
    if (props.enableStageEmit) {
        let lastStage = ''
        const onCameraChanged = () => {
            const height = viewer.value?.camera?.positionCartographic?.height
            const height = viewer?.camera?.positionCartographic?.height
            if (height == null) return
            emit('height-change', height)
            let stage = 'mid'
@@ -97,10 +102,10 @@
                emit('stage-change', stage)
            }
        }
        viewer.value?.camera?.changed?.addEventListener(onCameraChanged)
        removeCameraListener = () => viewer.value?.camera?.changed?.removeEventListener(onCameraChanged)
        viewer?.camera?.changed?.addEventListener(onCameraChanged)
        removeCameraListener = () => viewer?.camera?.changed?.removeEventListener(onCameraChanged)
    }
    emit('ready', { viewer: viewer.value, publicCesium: viewInstance.value })
    emit('ready', { viewer, publicCesium: viewInstance })
}
watch(
@@ -116,15 +121,62 @@
onBeforeUnmount(() => {
    if (removeCameraListener) removeCameraListener()
    removeJaAdminBoundary(viewer.value, adminBoundarySources)
    viewInstance.value?.viewerDestroy?.()
    removeJaAdminBoundary(viewer, adminBoundarySources)
    adminBoundarySources = null
    viewInstance?.viewerDestroy?.()
})
const getMap = () => ({ viewer: viewer.value, publicCesium: viewInstance.value })
const getViewer = () => viewer.value
const getPublicCesium = () => viewInstance.value
const setAdminBoundaryVisible = async visible => {
    if (!viewer) return
    const removeByNames = () => {
        Object.values(ADMIN_BOUNDARY_NAMES).forEach(name => {
            const sources = viewer.dataSources.getByName(name) || []
            sources.forEach(source => viewer.dataSources.remove(source))
        })
    }
    const resolveSources = () => {
        if (adminBoundarySources) return adminBoundarySources
        const boundarySource = viewer.dataSources.getByName(ADMIN_BOUNDARY_NAMES.boundary)?.[0] ?? null
        const lineSource = viewer.dataSources.getByName(ADMIN_BOUNDARY_NAMES.line)?.[0] ?? null
        const labelSource = viewer.dataSources.getByName(ADMIN_BOUNDARY_NAMES.label)?.[0] ?? null
        if (boundarySource || lineSource || labelSource) {
            adminBoundarySources = { boundarySource, lineSource, labelSource }
        }
        return adminBoundarySources
    }
    const setVisible = (source, next) => {
        if (!source) return
        source.show = next
        source.entities?.values?.forEach(entity => {
            entity.show = next
        })
    }
    const sources = resolveSources()
    if (!visible) {
        if (sources) {
            removeJaAdminBoundary(viewer, sources)
            adminBoundarySources = null
        }
        removeByNames()
        return
    }
    if (sources) {
        const { boundarySource, lineSource, labelSource } = sources
        setVisible(boundarySource, true)
        setVisible(lineSource, true)
        setVisible(labelSource, true)
        return
    }
    adminBoundarySources = await loadJaAdminBoundary(viewer, {
        zoomTo: false,
    })
}
defineExpose({ getMap, getViewer, getPublicCesium })
const getMap = () => ({ viewer, publicCesium: viewInstance })
const getViewer = () => viewer
const getPublicCesium = () => viewInstance
defineExpose({ getMap, getViewer, getPublicCesium, setAdminBoundaryVisible })
</script>
<style scoped lang="scss">
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -11,33 +11,36 @@
                    <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 class="command-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>
@@ -64,6 +67,8 @@
    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
@@ -92,6 +97,7 @@
const mapRef = ref(null)
let viewer = null
let publicCesium = null
let cockpitPrimitiveLayer = null
const devicePickMap = new Map()
const dronePickMap = new Map()
@@ -114,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
@@ -123,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: [
@@ -151,7 +165,9 @@
            { key: 'route', label: '飞行航路' },
        ],
    },
])
 */
const adminBoundaryVisible = ref(treeCheckedKeys.value.includes('admin'))
const getDevicePosition = item => {
    const longitudeRaw = item.longitude ?? item.lng ?? item.lon
@@ -314,7 +330,12 @@
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 => {
@@ -350,33 +371,33 @@
            height: 120
        },
        {
            longitude:114.957308,
            longitude: 114.957308,
            latitude: 27.138452,
            height: 120
        },
        {
            longitude:114.952,
            longitude: 114.952,
            latitude: 27.136317,
            height: 120
        },
        {
            longitude:114.949293,
            longitude: 114.949293,
            latitude: 27.133864,
            height: 120
        },
        {
            longitude:114.944666,
            longitude: 114.944666,
            latitude: 27.130526,
            height: 120
        },
        {
            longitude:114.945909,
            latitude:27.127845,
            longitude: 114.945909,
            latitude: 27.127845,
            height: 120
        },
        {
            longitude:114.962974,
            latitude:27.136242,
            longitude: 114.962974,
            latitude: 27.136242,
            height: 120
        },
    ]
@@ -472,7 +493,7 @@
    droneTrackBillboardCollection.show = detailVisible.value
    droneTrackPolylineCollection.show = detailVisible.value
    droneTrackRuntime = []
    const baseCenters = [['默认区域', { longitude:114.958541, latitude: 27.121917 }]]
    const baseCenters = [['默认区域', { longitude: 114.958541, latitude: 27.121917 }]]
    const segmentDuration = 6
    const baseTrackColor = Cesium.Color.fromCssColorString('red')
    baseCenters.forEach(([centerName, center], trackIndex) => {
@@ -629,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)
@@ -732,7 +753,7 @@
        viewer.dataSources.add(aggregationSource)
    }
    clearAggregationEntities()
    aggregationSource.show = clusterVisible.value
    updateAggregationVisibility()
    const countMap = new Map()
        ; (list || []).forEach(item => {
            if (!item?.type) return
@@ -851,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()
}
@@ -906,6 +927,29 @@
    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)
@@ -913,9 +957,11 @@
    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 = getStageByHeight(height)
    updateStageDisplay(stage)
@@ -960,6 +1006,7 @@
    }
    cockpitPrimitiveLayer = null
    viewer = null
    publicCesium = null
})
</script>
@@ -1041,6 +1088,51 @@
        flex: 1;
        overflow: auto;
    }
    .base-map-title {
        margin-bottom: 8px;
        font-size: 12px;
        color: #ffffff;
        font-weight: 600;
    }
    .base-map-options {
        padding: 16px;
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 8px;
    }
    .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;
            }
            .base-map-label {
                color: #2ea8ff;
            }
        }
    }
}
</style>
applications/drone-command/src/styles/common/cockpit.scss
@@ -76,25 +76,12 @@
        color: #c3c3dd;
    }
    .el-tree-node__expand-icon {
        order: 3;
        margin-left: auto;
    }
    .el-tree-node__expand-icon.is-leaf {
        visibility: hidden;
    }
    .el-checkbox {
        order: 1;
    }
    .el-tree-node__label {
        order: 2;
    }
    .el-tree-node__expand-icon:not(.is-leaf)~.el-checkbox {
        display: none;
    }
    .el-tree-node__content:hover,
@@ -122,6 +109,21 @@
    }
}
.map-layer-tree {
    .el-tree-node__expand-icon:not(.is-leaf)~.el-checkbox {
        display: none !important;
    }
    .el-tree-node__expand-icon {
        order: 3;
        margin-left: auto;
    }
    .el-tree-node__expand-icon.is-leaf {
        visibility: hidden;
    }
}
.command-cascader {
    height: 40px;
applications/drone-command/src/utils/cesium/adminBoundary.js
@@ -24,6 +24,7 @@
        fill: Cesium.Color.fromCssColorString(config.fillColor).withAlpha(config.fillAlpha),
        clampToGround: true,
    })
    boundarySource.name = 'jaBoundarySource'
    viewer.dataSources.add(boundarySource)
    const lineSource = new Cesium.CustomDataSource('jaBoundaryLineSource')
@@ -42,6 +43,7 @@
            },
        })
    })
    lineSource.name = 'jaBoundaryLineSource'
    viewer.dataSources.add(lineSource)
    const labelSource = new Cesium.CustomDataSource('jaBoundaryLabelSource')
@@ -67,6 +69,7 @@
            },
        })
    })
    labelSource.name = 'jaBoundaryLabelSource'
    viewer.dataSources.add(labelSource)
    if (config.zoomTo) {
applications/drone-command/src/utils/cesium/publicCesium.js
@@ -114,6 +114,9 @@
        this.handlerRightEvents = { handler: null }
        this.handlerMouseMoveEvents = { handler: null }
        this.viewer = null
        this.terrainProvider = null
        this.terrainLoading = null
        this.terrainEnabled = false
        this.globalBaseMapLayers = []
        this.boundary = null
        this.init(options)
@@ -177,16 +180,7 @@
        this.viewer?.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) // 禁用双击
        if (terrain) {
            // 使用公司地形
            Cesium.CesiumTerrainProvider.fromUrl(`${import.meta.env.VITE_APP_TERRAIN_URL}ja_terrain`, {
                requestVertexNormals: true, // 启用地形法线增强立体感
                requestWaterMask: true, // 启用水体遮罩效果
            }).then(terrainProvider => {
                this.viewer.terrainProvider = terrainProvider
                terrainLoadCallback?.()
            })
        }
        this.setTerrainVisible(terrain, terrainLoadCallback)
        this.viewer.scene.screenSpaceCameraController.maximumZoomDistance = 4500000
        this.switchLayers(layerMode)
@@ -203,6 +197,40 @@
    getViewer () {
        return this.viewer
    }
    async ensureTerrainProvider () {
        if (this.terrainProvider) return this.terrainProvider
        if (this.terrainLoading) return this.terrainLoading
        this.terrainLoading = Cesium.CesiumTerrainProvider.fromUrl(
            `${import.meta.env.VITE_APP_TERRAIN_URL}ja_terrain`,
            {
                requestVertexNormals: true, // 启用地形法线增强立体感
                requestWaterMask: true, // 启用水体遮罩效果
            }
        ).then(provider => {
            this.terrainProvider = provider
            return provider
        })
        return this.terrainLoading
    }
    async setTerrainVisible (visible, terrainLoadCallback) {
        if (!this.viewer) return
        if (!visible) {
            this.viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider()
            this.terrainEnabled = false
            return
        }
        try {
            const provider = await this.ensureTerrainProvider()
            if (!this.viewer) return
            this.viewer.terrainProvider = provider
            this.terrainEnabled = true
            terrainLoadCallback?.()
        } catch (error) {
            this.terrainEnabled = false
        }
    }
    // 销毁viewer
@@ -876,4 +904,4 @@
            this.viewer?.imageryLayers.remove(imageryLayers)
        }
    }
}
}