From c53aab006b9db28e62381dba4fca4d77ec3566b0 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Sat, 17 Jan 2026 15:36:07 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/drone-command/src/assets/images/dataCockpit/legend/drone.png                            |    0 
 applications/drone-command/src/assets/images/dataCockpit/legend/equipment.png                        |    0 
 applications/drone-command/src/utils/cesium/publicCesium.js                                          |    2 
 applications/drone-command/src/assets/images/dataCockpit/legend/command-post.png                     |    0 
 applications/drone-command/src/assets/images/dataCockpit/map/equipment.png                           |    0 
 applications/drone-command/src/components/map-container/common-cesium-map.vue                        |  135 +++++++++++
 applications/drone-command/src/views/dataCockpit/index.vue                                           |   10 
 applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue                           |   42 ++
 applications/drone-command/src/utils/cesium/adminBoundary.js                                         |   88 +++++++
 applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue                             |   42 ++
 applications/task-work-order/src/utils/cesium/publicCesium.js                                        |    2 
 applications/drone-command/src/components/map-container/device-map-container.vue                     |  150 ++----------
 applications/drone-command/src/assets/images/dataCockpit/map/command-post.png                        |    0 
 applications/drone-command/src/views/dataCockpit/components/LegendBar.vue                            |  108 +++++++++
 applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue                          |   30 +
 applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue |   26 +
 applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue                           |   37 +-
 applications/mobile-web-view/src/hooks/useAreaBoundary.js                                            |    2 
 18 files changed, 491 insertions(+), 183 deletions(-)

diff --git a/applications/drone-command/src/assets/images/dataCockpit/legend/command-post.png b/applications/drone-command/src/assets/images/dataCockpit/legend/command-post.png
new file mode 100644
index 0000000..dca43d0
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/legend/command-post.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/dataCockpit/legend/drone.png b/applications/drone-command/src/assets/images/dataCockpit/legend/drone.png
new file mode 100644
index 0000000..130cf04
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/legend/drone.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/dataCockpit/legend/equipment.png b/applications/drone-command/src/assets/images/dataCockpit/legend/equipment.png
new file mode 100644
index 0000000..ec33491
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/legend/equipment.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/dataCockpit/map/command-post.png b/applications/drone-command/src/assets/images/dataCockpit/map/command-post.png
new file mode 100644
index 0000000..1d090e2
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/map/command-post.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/dataCockpit/map/equipment.png b/applications/drone-command/src/assets/images/dataCockpit/map/equipment.png
index f07291b..99f9bd2 100644
--- a/applications/drone-command/src/assets/images/dataCockpit/map/equipment.png
+++ b/applications/drone-command/src/assets/images/dataCockpit/map/equipment.png
Binary files differ
diff --git a/applications/drone-command/src/components/map-container/common-cesium-map.vue b/applications/drone-command/src/components/map-container/common-cesium-map.vue
new file mode 100644
index 0000000..eec995b
--- /dev/null
+++ b/applications/drone-command/src/components/map-container/common-cesium-map.vue
@@ -0,0 +1,135 @@
+<template>
+	<div :id="mapId" class="common-cesium-map"></div>
+</template>
+
+<script setup>
+import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
+import { PublicCesium } from '@/utils/cesium/publicCesium'
+import { loadJaAdminBoundary, removeJaAdminBoundary } from '@/utils/cesium/adminBoundary'
+
+const props = defineProps({
+	active: {
+		type: Boolean,
+		default: true,
+	},
+	domId: {
+		type: String,
+		default: '',
+	},
+	flatMode: {
+		type: Boolean,
+		default: false,
+	},
+	terrain: {
+		type: Boolean,
+		default: true,
+	},
+	layerMode: {
+		type: Number,
+		default: 4,
+	},
+	boundary: {
+		type: Boolean,
+		default: false,
+	},
+	contour: {
+		type: Boolean,
+		default: false,
+	},
+	showAdminBoundary: {
+		type: Boolean,
+		default: true,
+	},
+	zoomToBoundary: {
+		type: Boolean,
+		default: true,
+	},
+	enableStageEmit: {
+		type: Boolean,
+		default: false,
+	},
+	clusterHeight: {
+		type: Number,
+		default: 100000,
+	},
+	detailHeight: {
+		type: Number,
+		default: 10000,
+	},
+})
+
+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 adminBoundarySources = null
+let initialized = false
+let removeCameraListener = null
+
+const initMap = async () => {
+	if (initialized) return
+	initialized = true
+	viewInstance.value = new PublicCesium({
+		dom: mapId,
+		flatMode: props.flatMode,
+		terrain: props.terrain,
+		layerMode: props.layerMode,
+		boundary: props.boundary,
+		contour: props.contour,
+	})
+	viewer.value = viewInstance.value.getViewer()
+	if (props.showAdminBoundary) {
+		adminBoundarySources = await loadJaAdminBoundary(viewer.value, {
+			zoomTo: props.zoomToBoundary,
+		})
+	}
+	if (props.enableStageEmit) {
+		let lastStage = ''
+		const onCameraChanged = () => {
+			const height = viewer.value?.camera?.positionCartographic?.height
+			if (height == null) return
+			emit('height-change', height)
+			let stage = 'mid'
+			if (height >= props.clusterHeight) stage = 'cluster'
+			if (height <= props.detailHeight) stage = 'detail'
+			if (stage !== lastStage) {
+				lastStage = stage
+				emit('stage-change', stage)
+			}
+		}
+		viewer.value?.camera?.changed?.addEventListener(onCameraChanged)
+		removeCameraListener = () => viewer.value?.camera?.changed?.removeEventListener(onCameraChanged)
+	}
+	emit('ready', { viewer: viewer.value, publicCesium: viewInstance.value })
+}
+
+watch(
+	() => props.active,
+	active => {
+		if (active) initMap()
+	}
+)
+
+onMounted(() => {
+	if (props.active) initMap()
+})
+
+onBeforeUnmount(() => {
+	if (removeCameraListener) removeCameraListener()
+	removeJaAdminBoundary(viewer.value, adminBoundarySources)
+	viewInstance.value?.viewerDestroy?.()
+})
+
+const getMap = () => ({ viewer: viewer.value, publicCesium: viewInstance.value })
+const getViewer = () => viewer.value
+const getPublicCesium = () => viewInstance.value
+
+defineExpose({ getMap, getViewer, getPublicCesium })
+</script>
+
+<style scoped lang="scss">
+.common-cesium-map {
+	width: 100%;
+	height: 100%;
+}
+</style>
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 704eaf9..938bdfe 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
@@ -1,5 +1,18 @@
 <template>
-	<div class="command-cesium map-container" :id="props.containerId"></div>
+	<CommonCesiumMap
+		ref="mapRef"
+		class="command-cesium map-container"
+		:dom-id="props.containerId"
+		:active="true"
+		:flat-mode="false"
+		:terrain="false"
+		:layer-mode="4"
+		:contour="false"
+		:boundary="false"
+		:show-admin-boundary="true"
+		:zoom-to-boundary="true"
+		@ready="handleMapReady"
+	/>
 	<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">
@@ -25,11 +38,10 @@
 
 <script setup>
 import * as Cesium from 'cesium'
-import { PublicCesium } from '@/utils/cesium/publicCesium'
+import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import { geomAnalysis } from '@ztzf/utils'
 import { fwDefenseZonePageApi } from '@/views/areaManage/defenseZone/defenseZoneApi'
 import { fwAreaDividePageApi } from '@/views/areaManage/partition/partitionApi'
-import jaGeojsonRaw from '@/assets/geojson/ja.geojson?raw'
 import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
 import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png'
 
@@ -54,15 +66,11 @@
 
 const DEFAULT_ZONE_PAGE_SIZE = 999
 
-let viewInstance = null
+const mapRef = ref(null)
 let viewer = null
 const deviceEntityIds = new Set()
 let defenseZoneSource = null
 let partitionSource = null
-let adminBoundarySource = null
-let adminBoundaryLabelSource = null
-let adminBoundaryLineSource = null
-let adminBoundaryFlyDone = false
 const showLayerPanel = ref(false)
 const layerWrapRef = ref(null)
 const layerTreeProps = {
@@ -127,20 +135,6 @@
 	partitionSource.entities.removeAll()
 }
 
-const clearAdminBoundaryEntities = () => {
-	if (!adminBoundarySource) return
-	adminBoundarySource.entities.removeAll()
-}
-
-const clearAdminBoundaryLabels = () => {
-	if (!adminBoundaryLabelSource) return
-	adminBoundaryLabelSource.entities.removeAll()
-}
-
-const clearAdminBoundaryLines = () => {
-	if (!adminBoundaryLineSource) return
-	adminBoundaryLineSource.entities.removeAll()
-}
 
 const RING_STYLES = [
 	{ inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] }
@@ -263,8 +257,9 @@
 			position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
 			billboard: {
 				image: equipmentIcon,
-				width: 40.34,
-				height: 40.34,
+				width: 40,
+				height: 56,
+				verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
 			},
 		})
 	})
@@ -366,83 +361,6 @@
 	}
 }
 
-const loadAdminBoundary = async () => {
-	if (!viewer) return
-	try {
-		if (adminBoundarySource) {
-			viewer.dataSources.remove(adminBoundarySource)
-			adminBoundarySource = null
-		}
-		if (adminBoundaryLabelSource) {
-			viewer.dataSources.remove(adminBoundaryLabelSource)
-			adminBoundaryLabelSource = null
-		}
-		if (adminBoundaryLineSource) {
-			viewer.dataSources.remove(adminBoundaryLineSource)
-			adminBoundaryLineSource = null
-		}
-		const geojson = JSON.parse(jaGeojsonRaw)
-		adminBoundarySource = await Cesium.GeoJsonDataSource.load(geojson, {
-			stroke: Cesium.Color.fromCssColorString('#FFFFFF'),
-			strokeWidth: 2,
-			fill: Cesium.Color.fromCssColorString('#FFFFFF').withAlpha(0.05),
-			clampToGround: true,
-		})
-		viewer.dataSources.add(adminBoundarySource)
-		if (!adminBoundaryFlyDone) {
-			adminBoundaryFlyDone = true
-			viewer.flyTo(adminBoundarySource, {
-				duration: 0,
-				offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-75), 0),
-			})
-		}
-
-		adminBoundaryLineSource = new Cesium.CustomDataSource('adminBoundaryLineSource')
-		adminBoundarySource.entities.values.forEach(entity => {
-			const polygon = entity.polygon
-			if (!polygon) return
-			const hierarchy = polygon.hierarchy?.getValue?.(viewer.clock.currentTime)
-			const positions = hierarchy?.positions
-			if (!positions?.length) return
-			adminBoundaryLineSource.entities.add({
-				polyline: {
-					positions,
-					clampToGround: true,
-					width: 2,
-					material: Cesium.Color.WHITE,
-				},
-			})
-		})
-		viewer.dataSources.add(adminBoundaryLineSource)
-
-		adminBoundaryLabelSource = new Cesium.CustomDataSource('adminBoundaryLabelSource')
-		geojson.features?.forEach(feature => {
-			const name = feature?.properties?.name
-			const point = feature?.properties?.centroid || feature?.properties?.center
-			if (!name || !Array.isArray(point) || point.length < 2) return
-			adminBoundaryLabelSource.entities.add({
-				position: Cesium.Cartesian3.fromDegrees(point[0], point[1]),
-				label: {
-					text: name,
-					font: '14px Source Han Sans CN',
-					fillColor: Cesium.Color.WHITE,
-					outlineColor: Cesium.Color.BLACK.withAlpha(0.6),
-					outlineWidth: 2,
-					style: Cesium.LabelStyle.FILL_AND_OUTLINE,
-					heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
-					verticalOrigin: Cesium.VerticalOrigin.CENTER,
-					horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
-					disableDepthTestDistance: Number.POSITIVE_INFINITY,
-				},
-			})
-		})
-		viewer.dataSources.add(adminBoundaryLabelSource)
-	} catch (error) {
-		clearAdminBoundaryEntities()
-		clearAdminBoundaryLabels()
-		clearAdminBoundaryLines()
-	}
-}
 
 watch(
 	() => props.onlineDevices,
@@ -464,6 +382,13 @@
 	showLayerPanel.value = !showLayerPanel.value
 }
 
+const handleMapReady = ({ viewer: mapViewer }) => {
+	viewer = mapViewer
+	renderDeviceEntities(props.onlineDevices)
+	loadDefenseZones()
+	loadPartitions()
+}
+
 const handleClickOutside = event => {
 	if (!showLayerPanel.value) return
 	const target = event.target
@@ -473,19 +398,8 @@
 
 onMounted(() => {
 	document.addEventListener('click', handleClickOutside)
-	viewInstance = new PublicCesium({
-		dom: props.containerId,
-		flatMode: false,
-		terrain: false,
-		layerMode: 4,
-		contour: false,
-	})
-
-	viewer = viewInstance.getViewer()
-	renderDeviceEntities(props.onlineDevices)
-	loadDefenseZones()
-	loadPartitions()
-	loadAdminBoundary()
+	const map = mapRef.value?.getMap()
+	if (map?.viewer) handleMapReady(map)
 })
 
 onBeforeUnmount(() => {
@@ -493,14 +407,6 @@
 	clearDeviceEntities()
 	clearDefenseZoneEntities()
 	clearPartitionEntities()
-	clearAdminBoundaryEntities()
-	clearAdminBoundaryLabels()
-	clearAdminBoundaryLines()
-	if (viewInstance) {
-		viewInstance?.viewerDestroy()
-		viewInstance = null
-	}
-
 	viewer = null
 })
 </script>
diff --git a/applications/drone-command/src/utils/cesium/adminBoundary.js b/applications/drone-command/src/utils/cesium/adminBoundary.js
new file mode 100644
index 0000000..381dc7d
--- /dev/null
+++ b/applications/drone-command/src/utils/cesium/adminBoundary.js
@@ -0,0 +1,88 @@
+import * as Cesium from 'cesium'
+import jaGeojsonRaw from '@/assets/geojson/ja.geojson?raw'
+
+const DEFAULT_OPTIONS = {
+	strokeColor: '#00F9EC',
+	strokeWidth: 2,
+	fillColor: '#FFFFFF',
+	fillAlpha: 0.05,
+	labelFont: '14px Source Han Sans CN',
+	labelFill: '#FFFFFF',
+	labelOutline: '#18305C',
+	labelOutlineAlpha: 1,
+	zoomTo: true,
+}
+
+export const loadJaAdminBoundary = async (viewer, options = {}) => {
+	if (!viewer) return null
+	const config = { ...DEFAULT_OPTIONS, ...options }
+	const geojson = JSON.parse(jaGeojsonRaw)
+
+	const boundarySource = await Cesium.GeoJsonDataSource.load(geojson, {
+		stroke: Cesium.Color.fromCssColorString(config.strokeColor),
+		strokeWidth: config.strokeWidth,
+		fill: Cesium.Color.fromCssColorString(config.fillColor).withAlpha(config.fillAlpha),
+		clampToGround: true,
+	})
+	viewer.dataSources.add(boundarySource)
+
+	const lineSource = new Cesium.CustomDataSource('jaBoundaryLineSource')
+	boundarySource.entities.values.forEach(entity => {
+		const polygon = entity.polygon
+		if (!polygon) return
+		const hierarchy = polygon.hierarchy?.getValue?.(viewer.clock.currentTime)
+		const positions = hierarchy?.positions
+		if (!positions?.length) return
+		lineSource.entities.add({
+			polyline: {
+				positions,
+				clampToGround: true,
+				width: config.strokeWidth,
+				material: Cesium.Color.fromCssColorString(config.strokeColor),
+			},
+		})
+	})
+	viewer.dataSources.add(lineSource)
+
+	const labelSource = new Cesium.CustomDataSource('jaBoundaryLabelSource')
+	geojson.features?.forEach(feature => {
+		const name = feature?.properties?.name
+		const point = feature?.properties?.centroid || feature?.properties?.center
+		if (!name || !Array.isArray(point) || point.length < 2) return
+		labelSource.entities.add({
+			position: Cesium.Cartesian3.fromDegrees(point[0], point[1]),
+			label: {
+				text: name,
+				font: config.labelFont,
+				fillColor: Cesium.Color.fromCssColorString(config.labelFill),
+				outlineColor: Cesium.Color.fromCssColorString(config.labelOutline).withAlpha(
+					config.labelOutlineAlpha
+				),
+				outlineWidth: 2,
+				style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				verticalOrigin: Cesium.VerticalOrigin.CENTER,
+				horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			},
+		})
+	})
+	viewer.dataSources.add(labelSource)
+
+	if (config.zoomTo) {
+		await viewer.flyTo(boundarySource, {
+			duration: 0,
+			offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-75), 0),
+		})
+	}
+
+	return { boundarySource, lineSource, labelSource }
+}
+
+export const removeJaAdminBoundary = (viewer, sources) => {
+	if (!viewer || !sources) return
+	const { boundarySource, lineSource, labelSource } = sources
+	if (boundarySource) viewer.dataSources.remove(boundarySource)
+	if (lineSource) viewer.dataSources.remove(lineSource)
+	if (labelSource) viewer.dataSources.remove(labelSource)
+}
diff --git a/applications/drone-command/src/utils/cesium/publicCesium.js b/applications/drone-command/src/utils/cesium/publicCesium.js
index e0fe177..830c3f0 100644
--- a/applications/drone-command/src/utils/cesium/publicCesium.js
+++ b/applications/drone-command/src/utils/cesium/publicCesium.js
@@ -100,7 +100,7 @@
 			dockOptions = {},
 			boundaryChange,
 			terrainLoadCallback,
-			boundaryColor = '#7FFFD4',
+			boundaryColor = '#00F9EC',
 			dockRangeType = 1,
 			useDockHeight = false
 		} = options
diff --git a/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
index d93446e..31dcb77 100644
--- a/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/defenseZone/FormDiaLog.vue
@@ -2,7 +2,15 @@
 	<el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false" :close-on-click-modal="false">
 		<div class="dialog-container">
 			<div class="left-container">
-				<div class="leftMap command-cesium" id="mapContainer"></div>
+				<CommonCesiumMap
+					ref="mapRef"
+					class="leftMap command-cesium"
+					:active="visible"
+					:flat-mode="false"
+					:terrain="true"
+					:layer-mode="4"
+					:boundary="false"
+				/>
 			</div>
 
 			<div class="right-container">
@@ -92,7 +100,7 @@
 import { fwDefenseZoneDetailApi, fwDefenseZoneSubmitApi } from './defenseZoneApi'
 import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi'
 import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
-import { PublicCesium } from '@/utils/cesium/publicCesium'
+import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import { DrawPolygon } from '@/utils/cesium/DrawPolygon'
 import { cartesian3Convert } from '@/utils/cesium/mapUtil'
 import * as turf from '@turf/turf'
@@ -119,6 +127,7 @@
 const sceneList = ref([]) // 关联场景列表
 const selectedSceneRows = ref([]) // 选中场景
 const dictObj = inject('dictObj')
+const mapRef = ref(null)
 let viewer
 let drawPolygonExample
 let pointList = []
@@ -209,14 +218,9 @@
 
 // 初始化地图实例
 function initMap () {
-	const publicCesiumInstance = new PublicCesium({
-		dom: 'mapContainer',
-		flatMode: false,
-		terrain: true,
-		layerMode: 4,
-		boundary: false,
-	})
-	viewer = publicCesiumInstance.getViewer()
+	if (viewer) return
+	const map = mapRef.value?.getMap()
+	viewer = map?.viewer || null
 }
 
 // 绘制完成回调
@@ -253,6 +257,22 @@
 		noClosedList.map(item => ({ lng: item.longitude, lat: item.latitude }))
 	)
 	drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
+	if (noClosedList.length) {
+		const result = noClosedList.map(item => [item.longitude, item.latitude]).flat()
+		const tempEntity = viewer.entities?.add({
+			position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
+			polyline: {
+				positions: Cesium.Cartesian3.fromDegreesArray(result),
+				clampToGround: true,
+				width: 1,
+				material: Cesium.Color.TRANSPARENT,
+			},
+		})
+		if (tempEntity) {
+			viewer.flyTo(tempEntity, { duration: 0 })
+			viewer.entities.remove(tempEntity)
+		}
+	}
 }
 
 // 查看面
@@ -270,7 +290,7 @@
 			material: Cesium.Color.RED,
 		},
 	})
-	viewer.flyTo(mian)
+	viewer.flyTo(mian, { duration: 0 })
 }
 
 // 打开弹框
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 0b11930..e46084d 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -3,7 +3,15 @@
 		:close-on-click-modal="false">
 		<div class="dialog-container">
 			<div class="left-container">
-				<div class="leftMap command-cesium" id="mapContainer"></div>
+				<CommonCesiumMap
+					ref="mapRef"
+					class="leftMap command-cesium"
+					:active="visible"
+					:flat-mode="false"
+					:terrain="true"
+					:layer-mode="4"
+					:boundary="false"
+				/>
 			</div>
 
 			<div class="right-container">
@@ -159,7 +167,7 @@
 import { ElMessage } from 'element-plus'
 import { fwAreaDivideDetailApi, fwAreaDivideSubmitApi } from './partitionApi'
 import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
-import { PublicCesium } from '@/utils/cesium/publicCesium'
+import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import { DrawPolygon } from '@/utils/cesium/DrawPolygon'
 import { cartesian3Convert } from '@/utils/cesium/mapUtil'
 import * as turf from '@turf/turf'
@@ -197,6 +205,7 @@
 const policeStationOptions = ref([]) // 关联派出所
 const deviceOptions = ref([]) // 关联设备
 const dictObj = inject('dictObj')
+const mapRef = ref(null)
 let viewer
 let drawPolygonExample
 let pointList = []
@@ -265,14 +274,9 @@
 
 // 初始化地图实例
 function initMap () {
-	const publicCesiumInstance = new PublicCesium({
-		dom: 'mapContainer',
-		flatMode: false,
-		terrain: true,
-		layerMode: 4,
-		boundary: false,
-	})
-	viewer = publicCesiumInstance.getViewer()
+	if (viewer) return
+	const map = mapRef.value?.getMap()
+	viewer = map?.viewer || null
 }
 
 // 绘制完成回调
@@ -311,6 +315,22 @@
 		pointList.map(item => ({ lng: item.longitude, lat: item.latitude }))
 	)
 	drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
+	if (pointList1.length) {
+		const result = pointList1.map(item => [item.longitude, item.latitude]).flat()
+		const tempEntity = viewer.entities?.add({
+			position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
+			polyline: {
+				positions: Cesium.Cartesian3.fromDegreesArray(result),
+				clampToGround: true,
+				width: 1,
+				material: Cesium.Color.TRANSPARENT,
+			},
+		})
+		if (tempEntity) {
+			viewer.flyTo(tempEntity, { duration: 0 })
+			viewer.entities.remove(tempEntity)
+		}
+	}
 }
 
 // 查看面
@@ -328,7 +348,7 @@
 			material: Cesium.Color.RED,
 		},
 	})
-	viewer.flyTo(mian)
+	viewer.flyTo(mian, { duration: 0 })
 }
 
 // 获取派出所列表
diff --git a/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
index 588d0b9..e6ac8dd 100644
--- a/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
@@ -63,7 +63,15 @@
 			:close-on-click-modal="false" destroy-on-close @closed="positionClosed">
 			<div class="dialog-container">
 				<div class="left-container">
-					<div class="leftMap command-cesium" id="mapContainer"></div>
+					<CommonCesiumMap
+						ref="mapRef"
+						class="leftMap command-cesium"
+						:active="visible1"
+						:flat-mode="false"
+						:terrain="true"
+						:layer-mode="4"
+						:boundary="false"
+					/>
 				</div>
 			</div>
 
@@ -80,7 +88,7 @@
 import { ElMessage } from 'element-plus'
 import { fwPoliceStationDetailApi, fwPoliceStationSubmitApi } from './precinctInfoApi'
 import { fieldRules } from '@ztzf/utils'
-import { PublicCesium } from '@/utils/cesium/publicCesium'
+import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import * as Cesium from 'cesium'
 import axios from 'axios'
 
@@ -103,6 +111,7 @@
 const readonly = computed(() => dialogMode.value === 'view')
 const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
 const tempLocation = ref({ longitude: null, latitude: null })
+const mapRef = ref(null)
 
 const rules = {
 	address: fieldRules(true, 50),
@@ -186,6 +195,7 @@
 
 let viewer
 let redPointEntity
+let leftClickBound = false
 function setMapPoint (longitude, latitude) {
 	if (!viewer || longitude == null || latitude == null) return
 	if (!redPointEntity) {
@@ -225,15 +235,12 @@
 async function selectLocation () {
 	visible1.value = true
 	await nextTick()
-	const publicCesiumInstance = new PublicCesium({
-		dom: 'mapContainer',
-		flatMode: false,
-		terrain: true,
-		layerMode: 4,
-		boundary: false,
-	})
-	publicCesiumInstance.addLeftClickEvent(null, LeftClickEvent)
-	viewer = publicCesiumInstance.getViewer()
+	const map = mapRef.value?.getMap()
+	viewer = map?.viewer || null
+	if (viewer && !leftClickBound) {
+		map.publicCesium?.addLeftClickEvent?.(null, LeftClickEvent)
+		leftClickBound = true
+	}
 	if (formData.value.longitude != null && formData.value.latitude != null) {
 		tempLocation.value = { longitude: formData.value.longitude, latitude: formData.value.latitude }
 		setMapPoint(formData.value.longitude, formData.value.latitude)
@@ -264,6 +271,7 @@
 	}
 	visible1.value = false
 }
+
 // 打开弹框
 async function open ({ mode, row } = {}) {
 	dialogMode.value = mode || 'add'
diff --git a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
index f61f46d..199e92d 100644
--- a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
@@ -2,7 +2,15 @@
 	<el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false" :close-on-click-modal="false">
 		<div class="dialog-container">
 			<div class="left-container">
-				<div class="leftMap command-cesium" id="mapContainer"></div>
+				<CommonCesiumMap
+					ref="mapRef"
+					class="leftMap command-cesium"
+					:active="visible"
+					:flat-mode="false"
+					:terrain="true"
+					:layer-mode="4"
+					:boundary="false"
+				/>
 			</div>
 			<div class="right-container">
 				<div class="header">
@@ -135,7 +143,7 @@
 import { ElMessage } from 'element-plus'
 import { fwDefenseSceneDetailApi, fwDefenseSceneSubmitApi } from './sceneConfigApi'
 import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
-import { PublicCesium } from '@/utils/cesium/publicCesium'
+import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import * as Cesium from 'cesium'
 import { fwAreaDivideListApi } from '../partition/partitionApi'
 import { DrawPolygon } from '@/utils/cesium/DrawPolygon'
@@ -166,8 +174,10 @@
 const areaList = ref([]) // 关联区域列表
 const selectedAreaRows = ref([]) //选中列表
 const dictObj = inject('dictObj')
+const mapRef = ref(null)
 let viewer
 let redPointEntity
+let leftClickBound = false
 
 const rules = {
 	sceneName: fieldRules(true, 50),
@@ -285,19 +295,18 @@
 let geometricSource
 // 初始化地图实例
 function initMap () {
-	const publicCesiumInstance = new PublicCesium({
-		dom: 'mapContainer',
-		flatMode: false,
-		terrain: true,
-		layerMode: 4,
-		boundary: false,
-	})
-	if (!readonly.value) {
-		publicCesiumInstance.addLeftClickEvent(null, LeftClickEvent)
+	if (viewer) return
+	const map = mapRef.value?.getMap()
+	if (!map?.viewer) return
+	viewer = map.viewer
+	if (!readonly.value && !leftClickBound) {
+		map.publicCesium?.addLeftClickEvent?.(null, LeftClickEvent)
+		leftClickBound = true
 	}
-	viewer = publicCesiumInstance.getViewer()
-	geometricSource = new Cesium.CustomDataSource('geometricSource')
-	viewer.dataSources.add(geometricSource)
+	if (!geometricSource) {
+		geometricSource = new Cesium.CustomDataSource('geometricSource')
+		viewer.dataSources.add(geometricSource)
+	}
 }
 
 function LeftClickEvent (click) {
diff --git a/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue b/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
new file mode 100644
index 0000000..d115225
--- /dev/null
+++ b/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
@@ -0,0 +1,108 @@
+<!--
+ * @Author       : yuan
+ * @Date         : 2026-01-17 14:51:39
+ * @LastEditors  : yuan
+ * @LastEditTime : 2026-01-17 15:13:50
+ * @FilePath     : \applications\drone-command\src\views\dataCockpit\components\LegendBar.vue
+ * @Description  : 
+ * Copyright 2026 OBKoro1, All Rights Reserved. 
+ * 2026-01-17 14:51:39
+-->
+<template>
+	<div class="legend-bar">
+		<div class="legend-title" data-text="图例">图例</div>
+		<div class="legend-item">
+			<span class="legend-color defense"></span>
+			<span class="legend-text" data-text="防区">防区</span>
+		</div>
+		<div class="legend-item">
+			<span class="legend-color partition"></span>
+			<span class="legend-text" data-text="区域">区域</span>
+		</div>
+		<div class="legend-item">
+			<img class="legend-icon drone" :src="droneIcon" alt="无人机" />
+			<span class="legend-text" data-text="无人机">无人机</span>
+		</div>
+		<div class="legend-item">
+			<img class="legend-icon" :src="commandPostIcon" alt="指挥点" />
+			<span class="legend-text" data-text="指挥点">指挥点</span>
+		</div>
+		<div class="legend-item">
+			<img class="legend-icon" :src="equipmentIcon" alt="设备" />
+			<span class="legend-text" data-text="设备">设备</span>
+		</div>
+	</div>
+</template>
+
+<script setup>
+import droneIcon from '@/assets/images/dataCockpit/legend/drone.png'
+import commandPostIcon from '@/assets/images/dataCockpit/legend/command-post.png'
+import equipmentIcon from '@/assets/images/dataCockpit/legend/equipment.png'
+</script>
+
+<style scoped lang="scss">
+.legend-bar {
+	display: flex;
+	align-items: center;
+	gap: 20px;
+	padding: 10px 16px;
+
+	font-family: Source Han Sans CN, Source Han Sans CN;
+	font-size: 14px;
+	color: #FFFFFF;
+	text-align: center;
+	font-style: normal;
+	text-transform: none;
+}
+
+.legend-title {
+	font-weight: 800;
+}
+
+.legend-item {
+	display: flex;
+	align-items: center;
+	gap: 8px;
+	font-size: 12px;
+}
+
+.legend-title,
+.legend-text {
+	position: relative;
+	white-space: nowrap;
+
+	text-shadow:
+    -1px -1px 0 black,
+     1px -1px 0 black,
+    -1px  1px 0 black,
+     1px  1px 0 black;
+}
+
+.legend-color {
+	width: 30px;
+	height: 30px;
+	border-radius: 0;
+}
+
+.legend-color.defense {
+	background: radial-gradient(50% 50% at 50% 50%, #2aedbf 0%, #012b11 100%);
+	border: 1px solid #19d266;
+}
+
+.legend-color.partition {
+	background: radial-gradient(50% 50% at 50% 50%, #ffc609 0%, #583300 100%);
+	border: 1px solid #ffcd2a;
+}
+
+.legend-icon {
+	width: 30px;
+	height: 42px;
+	display: block;
+}
+
+.legend-icon.drone {
+	width: 30px;
+	height: 30px;
+	display: block;
+}
+</style>
diff --git a/applications/drone-command/src/views/dataCockpit/index.vue b/applications/drone-command/src/views/dataCockpit/index.vue
index 0568cef..7f44448 100644
--- a/applications/drone-command/src/views/dataCockpit/index.vue
+++ b/applications/drone-command/src/views/dataCockpit/index.vue
@@ -18,6 +18,7 @@
     <LeftContainer class="left-container" v-model:activeId="leftActiveId" v-model:collapsed="leftCollapsed" />  
     <RightContainer class="right-container" v-model:collapsed="rightCollapsed" v-model:onlineDevices="onlineDevices" />  
     <CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" />  
+    <LegendBar class="legend-container" />
   </div>
 </template>
 
@@ -26,6 +27,7 @@
   import LeftContainer from './components/LeftContainer.vue';
   import RightContainer from './components/RightContainer.vue';
   import CenterContainer from './components/CenterContainer.vue';
+  import LegendBar from './components/LegendBar.vue';
 
   const leftActiveId = ref(1);
   const leftCollapsed = ref(false);
@@ -85,4 +87,12 @@
     right: 17px;
     background: linear-gradient( 270deg, rgba(17,23,34,0.96) 0.16%, rgba(17,23,34,0.56) 57.57%, rgba(4,30,37,0) 100%);
   }
+
+  .legend-container {
+    position: absolute;
+    left: 50%;
+    bottom: 22px;
+    transform: translateX(-50%);
+    z-index: 9;
+  }
 </style>
diff --git a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
index e3caeb2..e787566 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
@@ -9,7 +9,15 @@
 	>
 		<div class="dialog-container">
 			<div class="left-container">
-				<div class="leftMap command-cesium" id="detectionRangeMap"></div>
+				<CommonCesiumMap
+					ref="mapRef"
+					class="leftMap command-cesium"
+					:active="visible"
+					:flat-mode="false"
+					:terrain="true"
+					:layer-mode="4"
+					:boundary="false"
+				/>
 			</div>
 
 			<div class="right-container">
@@ -138,7 +146,7 @@
 	detectionRangePageApi,
 	detectionRangeSubmitApi,
 } from '@/api/detectionCountermeasure/detectionRange'
-import { PublicCesium } from '@/utils/cesium/publicCesium'
+import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import * as Cesium from 'cesium'
 import deviceZcImg from '@/assets/images/dataCockpit/device-zc.svg'
 
@@ -161,6 +169,7 @@
 const submitting = ref(false) // 提交中
 const deviceOptions = ref([])
 const dialogReadonly = computed(() => dialogMode.value === 'view')
+const mapRef = ref(null)
 let viewer
 let redPointEntity
 let rangeCircleEntity
@@ -338,17 +347,12 @@
 
 function initMap() {
 	if (viewer) return
-	const publicCesiumInstance = new PublicCesium({
-		dom: 'detectionRangeMap',
-		flatMode: false,
-		terrain: true,
-		layerMode: 4,
-		boundary: false,
-	})
+	const map = mapRef.value?.getMap()
+	if (!map?.viewer) return
+	viewer = map.viewer
 	if (!dialogReadonly.value) {
-		publicCesiumInstance.addLeftClickEvent(null, handleMapClick)
+		map.publicCesium?.addLeftClickEvent?.(null, handleMapClick)
 	}
-	viewer = publicCesiumInstance.getViewer()
 }
 
 function handleMapClick(click) {
diff --git a/applications/mobile-web-view/src/hooks/useAreaBoundary.js b/applications/mobile-web-view/src/hooks/useAreaBoundary.js
index c62c508..d824c29 100644
--- a/applications/mobile-web-view/src/hooks/useAreaBoundary.js
+++ b/applications/mobile-web-view/src/hooks/useAreaBoundary.js
@@ -71,7 +71,7 @@
 		geoJsonLayer && map.removeLayer(geoJsonLayer)
 		geoJsonLayer = L.geoJSON(row.gJson, {
 			style: feature => ({
-				color: '#37D2D5', // 边界线颜色
+				color: '#00F9EC', // 边界线颜色
 				weight: 2, // 边界线宽度
 				fillOpacity: 0, // 填充透明度
 			}),
diff --git a/applications/task-work-order/src/utils/cesium/publicCesium.js b/applications/task-work-order/src/utils/cesium/publicCesium.js
index e0fe177..830c3f0 100644
--- a/applications/task-work-order/src/utils/cesium/publicCesium.js
+++ b/applications/task-work-order/src/utils/cesium/publicCesium.js
@@ -100,7 +100,7 @@
 			dockOptions = {},
 			boundaryChange,
 			terrainLoadCallback,
-			boundaryColor = '#7FFFD4',
+			boundaryColor = '#00F9EC',
 			dockRangeType = 1,
 			useDockHeight = false
 		} = options

--
Gitblit v1.9.3