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) } } } } applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue
@@ -47,7 +47,6 @@ class="gd-select" popper-class="gd-tree-select-popper" v-model="formData.areaCode" :data="regionOptions" node-key="id" :props="{ value: 'id', @@ -58,6 +57,9 @@ clearable filterable :check-strictly="true" lazy :load="loadRegionNode" :render-after-expand="false" /> </el-form-item> </el-col> @@ -111,6 +113,7 @@ import { agenciesSubmitApi, agenciesDetailApi, regionLazyTreeApi } from './agenciesApi' // 初始化表单数据 @@ -180,8 +183,53 @@ await loadDetail() } } // 懒加载获取区域节点数据 async function loadRegionNode(node, resolve) { try { const parentCode = node.data?.id || '360800000000' const res = await regionLazyTreeApi({ parentCode }) const nodes = res?.data?.data || [] // 处理返回的节点数据 const processedNodes = nodes.map(item => { return { id: item.id || item.value, name: item.name || item.title || item.label, hasChildren: item.hasChildren || false } }) resolve(processedNodes) } catch (error) { console.error('获取区域数据失败:', error) resolve([]) } } // 获取初始区域数据(根节点) async function getRegionList() { try { // 懒加载模式下,只需要初始化根节点 const res = await regionLazyTreeApi({ parentCode: '360800000000' }) const nodes = res?.data?.data || [] // 处理根节点数据 regionOptions.value = nodes.map(item => { return { id: item.id || item.value, name: item.name || item.title || item.label, hasChildren: item.hasChildren || false } }) } catch (error) { console.error('获取区域数据失败:', error) ElMessage.error('获取区域数据失败') } } defineExpose({ open }) onMounted(() => { getRegionList() }) </script> <style scoped lang="scss"> applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue
@@ -26,7 +26,6 @@ class="gd-select gray" popper-class="gd-tree-select-popper" v-model="searchParams.areaCode" :data="regionOptions" node-key="id" :props="treeSelectProps" placeholder="请选择" @@ -34,7 +33,9 @@ filterable @change="handleSearch" :check-strictly="true" lazy :load="loadRegionNode" :render-after-expand="false" /> </el-form-item> @@ -192,37 +193,47 @@ }) } // 递归获取区域树形数据 async function getRegionTree(parentCode = '360800000000') { // 懒加载获取区域节点数据 async function loadRegionNode(node, resolve) { try { const parentCode = node.data?.id || '360800000000' const res = await regionLazyTreeApi({ parentCode }) const nodes = res?.data?.data || [] // 如果节点有子节点,递归获取子节点 for (let node of nodes) { if (node.hasChildren) { node.children = await getRegionTree(node.id) } else { node.children = [] // 处理返回的节点数据 const processedNodes = nodes.map(item => { return { id: item.id || item.value, name: item.name || item.title || item.label, hasChildren: item.hasChildren || false } node.name = node.name || node.title || node.label // 确保 id 属性存在 node.id = node.id || node.value } return nodes }) resolve(processedNodes) } catch (error) { console.error('获取区域数据失败:', error) return [] resolve([]) } } // 获取完整的区域树形数据 // 获取初始区域数据(根节点) async function getRegionList() { try { regionOptions.value = await getRegionTree() console.log('区域树形数据:', regionOptions.value) // 懒加载模式下,只需要初始化根节点 const res = await regionLazyTreeApi({ parentCode: '360800000000' }) const nodes = res?.data?.data || [] // 处理根节点数据 regionOptions.value = nodes.map(item => { return { id: item.id || item.value, name: item.name || item.title || item.label, hasChildren: item.hasChildren || false } }) } catch (error) { ElMessage.error('获取区域数据失败') console.error('获取区域数据失败:', error) } }