From fb39a7bb69cc55d2e1ce61d47e0677aa40540f95 Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Fri, 30 Jan 2026 16:20:33 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/drone-command/src/utils/cesium/shapeTools/edit/EditEllipseTool.js                        |  105 
 applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue                  |   22 
 applications/drone-command/src/assets/images/areaMap/buffer.png                                       |    0 
 applications/drone-command/src/styles/common/cockpit.scss                                             |    9 
 applications/drone-command/src/utils/cesium/shapeTools/draw/DrawRectangleTool.js                      |    0 
 applications/drone-command/src/utils/cesium/deviceRange.js                                            |  117 +
 applications/drone-command/src/components/map-container/device-map-container.vue                      |  424 ++-
 applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue                   |    3 
 applications/drone-command/src/views/dataCockpit/components/LegendBar.vue                             |   33 
 applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js                        |    2 
 applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue         |   88 
 applications/drone-command/src/utils/cesium/shapeTools/draw/DrawBufferCircleTool.js                   |   12 
 applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue         |  133 +
 applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue                    |   43 
 applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue               |  153 
 applications/drone-command/src/assets/images/areaMap/trash.png                                        |    0 
 applications/task-work-order/src/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue        |    2 
 applications/drone-command/src/views/permissionManage/permissionDept/ShareDiaLog.vue                  |  169 +
 applications/drone-command/src/views/api.txt                                                          |  148 
 applications/drone-command/src/utils/cesium/shapeTools/index.js                                       |   13 
 applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue              |    8 
 packages/constants/areaStyles.js                                                                      |   36 
 packages/constants/index.js                                                                           |    1 
 applications/drone-command/env/.env.development                                                       |    4 
 applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue                           |   37 
 applications/drone-command/src/views/audioCall.vue                                                    |  600 +++---
 applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/FormDiaLog.vue         |    1 
 applications/drone-command/src/utils/cesium/shapeTools/ToolBase.js                                    |    0 
 applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue                            |  439 ++--
 applications/drone-command/src/views/permissionManage/permissionDept/fwDevicePerShareApi.js           |   28 
 uniapps/work-app/src/subPackages/workDetail/index.vue                                                 |   36 
 applications/drone-command/src/utils/cesium/shapeTools/shapeUtils.js                                  |    0 
 applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue                         |  671 ------
 applications/drone-command/src/views/areaManage/sceneConfig/index.vue                                 |   13 
 applications/drone-command/src/utils/cesium/shapeTools/draw/DrawEllipseTool.js                        |   11 
 applications/drone-command/src/views/areaManage/sceneManage/sceneManageApi.js                         |   38 
 applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/RefuseOrderDialog1.vue |    2 
 applications/drone-command/src/views/areaManage/sceneManage/index.vue                                 |  172 +
 applications/drone-command/src/views/permissionManage/permissionDept/index.vue                        |   39 
 applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/FormDiaLog.vue             |   90 
 applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js                        |    0 
 applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue                              | 1042 ++++++++++-
 applications/drone-command/src/assets/images/areaMap/ellipse.png                                      |    0 
 applications/drone-command/src/utils/cesium/shapeTools/edit/EditRectangleTool.js                      |    0 
 applications/drone-command/src/utils/cesium/shapeTools/EditManager.js                                 |    0 
 applications/drone-command/src/assets/images/areaMap/polygon.png                                      |    0 
 uniapps/work-app/src/pages/login/index.vue                                                            |    4 
 applications/drone-command/src/utils/cesium/shapeTools/Tooltip.js                                     |    0 
 applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue              |    2 
 applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/ApplyViewDialog.vue        |   26 
 applications/drone-command/src/utils/cesium/shapeTools/DrawManager.js                                 |    0 
 applications/drone-command/src/assets/images/areaMap/rect.png                                         |    0 
 applications/drone-command/src/views/areaManage/partition/index.vue                                   |   30 
 applications/drone-command/src/views/areaManage/sceneManage/FormDiaLog.vue                            |  553 ++++++
 applications/drone-command/src/assets/images/areaManage/positionIcon.png                              |    0 
 applications/drone-command/src/utils/cesium/shapeTools/edit/EditBufferCircleTool.js                   |   45 
 56 files changed, 3,629 insertions(+), 1,775 deletions(-)

diff --git a/applications/drone-command/env/.env.development b/applications/drone-command/env/.env.development
index 6524616..1f6d35a 100644
--- a/applications/drone-command/env/.env.development
+++ b/applications/drone-command/env/.env.development
@@ -2,7 +2,7 @@
  # @Author       : yuan
  # @Date         : 2026-01-07 14:58:30
  # @LastEditors  : yuan
- # @LastEditTime : 2026-01-28 09:29:05
+ # @LastEditTime : 2026-01-30 09:29:25
  # @FilePath     : \applications\drone-command\env\.env.development
  # @Description  : 
  # Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -16,7 +16,7 @@
 
 #开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
 # VITE_APP_URL = https://wrj.shuixiongit.com/api
-VITE_APP_URL= http://192.168.1.227
+VITE_APP_URL= http://192.168.1.168
 
 #新大屏地址
 VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/'
diff --git a/applications/drone-command/src/assets/images/areaManage/positionIcon.png b/applications/drone-command/src/assets/images/areaManage/positionIcon.png
new file mode 100644
index 0000000..a569e90
--- /dev/null
+++ b/applications/drone-command/src/assets/images/areaManage/positionIcon.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/areaMap/buffer.png b/applications/drone-command/src/assets/images/areaMap/buffer.png
new file mode 100644
index 0000000..9c39133
--- /dev/null
+++ b/applications/drone-command/src/assets/images/areaMap/buffer.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/areaMap/ellipse.png b/applications/drone-command/src/assets/images/areaMap/ellipse.png
new file mode 100644
index 0000000..548ba94
--- /dev/null
+++ b/applications/drone-command/src/assets/images/areaMap/ellipse.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/areaMap/polygon.png b/applications/drone-command/src/assets/images/areaMap/polygon.png
new file mode 100644
index 0000000..c893971
--- /dev/null
+++ b/applications/drone-command/src/assets/images/areaMap/polygon.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/areaMap/rect.png b/applications/drone-command/src/assets/images/areaMap/rect.png
new file mode 100644
index 0000000..d00b20e
--- /dev/null
+++ b/applications/drone-command/src/assets/images/areaMap/rect.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/areaMap/trash.png b/applications/drone-command/src/assets/images/areaMap/trash.png
new file mode 100644
index 0000000..acf8c69
--- /dev/null
+++ b/applications/drone-command/src/assets/images/areaMap/trash.png
Binary files differ
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 f7fb7e9..659f8ca 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
@@ -47,9 +47,9 @@
 <script setup>
 import * as Cesium from 'cesium'
 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 { buildEllipsePositions } from '@/utils/cesium/shapeTools'
+import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
+import { fwAreaDivideListApi } from '@/views/areaManage/partition/partitionApi'
 import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi'
 import { cockpitAggregationApi } from '@/api/dataCockpit'
 import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
@@ -61,17 +61,18 @@
 import DevicePopup from './components/DevicePopup.vue'
 import DronePopup from './components/DronePopup.vue'
 import {
-	RING_STYLES,
-	addDeviceRings,
 	createDroneTrackMaterial,
 	createRadialGradientMaterial,
 	getTexturedVertexFormat,
 } from './device-map-materials'
+import { getPointPositionsHeight } from '@/utils/cesium/mapUtil'
+import { createDeviceRangePrimitiveWithHeight } from '@/utils/cesium/deviceRange'
 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 POLYGON_HEIGHT_M = 0.2
 
 const props = defineProps({
 	onlineDevices: {
@@ -97,7 +98,6 @@
 })
 const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite'])
 
-const DEFAULT_ZONE_PAGE_SIZE = 999
 
 const mapRef = ref(null)
 let viewer = null
@@ -108,10 +108,8 @@
 let deviceBillboardCollection = null
 let deviceRingOutlinePrimitives = []
 let deviceRingFillPrimitives = []
-let defenseZonePrimitive = null
-let defenseZoneOutlinePrimitive = null
-let partitionPrimitive = null
-let partitionOutlinePrimitive = null
+let partitionFillPrimitives = []
+let partitionOutlinePrimitives = []
 let aggregationSource = null
 let commandPostBillboardCollection = null
 let droneTrackBillboardCollection = null
@@ -250,6 +248,7 @@
 	return range
 }
 
+
 const ensureCockpitPrimitiveLayer = () => {
 	if (!viewer) return
 	if (!cockpitPrimitiveLayer) {
@@ -280,10 +279,12 @@
 	if (!viewer) return
 	ensureCockpitPrimitiveLayer()
 	cockpitPrimitiveLayer.removeAll(false)
-	if (defenseZonePrimitive) cockpitPrimitiveLayer.add(defenseZonePrimitive)
-	if (defenseZoneOutlinePrimitive) cockpitPrimitiveLayer.add(defenseZoneOutlinePrimitive)
-	if (partitionPrimitive) cockpitPrimitiveLayer.add(partitionPrimitive)
-	if (partitionOutlinePrimitive) cockpitPrimitiveLayer.add(partitionOutlinePrimitive)
+	if (partitionFillPrimitives.length) {
+		partitionFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
+	}
+	if (partitionOutlinePrimitives.length) {
+		partitionOutlinePrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
+	}
 	if (deviceRingFillPrimitives.length) {
 		deviceRingFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
 	}
@@ -323,27 +324,16 @@
 	}
 }
 
-const clearDefenseZoneEntities = () => {
-	if (!viewer) return
-	if (defenseZonePrimitive) {
-		removeCockpitPrimitive(defenseZonePrimitive)
-		defenseZonePrimitive = null
-	}
-	if (defenseZoneOutlinePrimitive) {
-		removeCockpitPrimitive(defenseZoneOutlinePrimitive)
-		defenseZoneOutlinePrimitive = null
-	}
-}
 
 const clearPartitionEntities = () => {
 	if (!viewer) return
-	if (partitionPrimitive) {
-		removeCockpitPrimitive(partitionPrimitive)
-		partitionPrimitive = null
+	if (partitionFillPrimitives.length) {
+		partitionFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+		partitionFillPrimitives = []
 	}
-	if (partitionOutlinePrimitive) {
-		removeCockpitPrimitive(partitionOutlinePrimitive)
-		partitionOutlinePrimitive = null
+	if (partitionOutlinePrimitives.length) {
+		partitionOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+		partitionOutlinePrimitives = []
 	}
 }
 
@@ -378,10 +368,16 @@
 
 const setDetailVisibility = visible => {
 	detailVisible.value = visible
-	if (defenseZonePrimitive) defenseZonePrimitive.show = visible
-	if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = visible
-	if (partitionPrimitive) partitionPrimitive.show = visible
-	if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = visible
+	if (partitionFillPrimitives.length) {
+		partitionFillPrimitives.forEach(primitive => {
+			primitive.show = visible
+		})
+	}
+	if (partitionOutlinePrimitives.length) {
+		partitionOutlinePrimitives.forEach(primitive => {
+			primitive.show = visible
+		})
+	}
 	if (commandPostBillboardCollection) commandPostBillboardCollection.show = visible
 	if (deviceBillboardCollection) deviceBillboardCollection.show = visible
 	if (deviceRingFillPrimitives.length) {
@@ -701,7 +697,7 @@
 	reorderCockpitPrimitives()
 }
 
-const renderDeviceEntities = devices => {
+const renderDeviceEntities = async devices => {
 	if (!viewer) return
 	ensureCockpitPrimitiveLayer()
 	ensureDeviceCollections()
@@ -716,14 +712,14 @@
 	}
 	devicePickMap.clear()
 	deviceBillboardCollection.show = detailVisible.value
-	const ringFillInstancesByStyle = RING_STYLES.map(() => [])
-	const ringOutlineInstancesByStyle = RING_STYLES.map(() => [])
+	const deviceEntries = []
+	const devicePositions = []
 	devices.forEach((item, index) => {
 		const position = getDevicePosition(item)
 		if (!position) return
 		const entityId = `online-device-${item.id ?? index}-${index}`
 		const rangeMeters = getDeviceRange(item)
-		addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters)
+		devicePositions.push({ lng: position.longitude, lat: position.latitude })
 		const billboard = deviceBillboardCollection.add({
 			position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
 			image: equipmentIcon,
@@ -733,161 +729,249 @@
 		})
 		billboard.id = entityId
 		devicePickMap.set(entityId, { data: item, billboard })
+		deviceEntries.push({ position, rangeMeters, billboard })
 	})
 	if (selectedTargetType.value === 'device' && selectedDeviceBillboard && !devicePickMap.has(selectedDeviceBillboard.id)) {
 		closePopup()
 	}
-	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(alpha),
-			Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(alpha),
-			{
-				gamma: 1.7,
-				innerCutoff: innerRatio,
+	if (deviceEntries.length) {
+		const heights = await getPointPositionsHeight(devicePositions, viewer)
+		deviceEntries.forEach((entry, index) => {
+			const height = Number(heights?.[index]?.ASL)
+			const centerHeight = Number.isFinite(height) ? height : 0
+			if (entry.billboard) {
+				entry.billboard.position = Cesium.Cartesian3.fromDegrees(
+					entry.position.longitude,
+					entry.position.latitude,
+					centerHeight
+				)
 			}
-		)
-		const primitive = new Cesium.GroundPrimitive({
-			geometryInstances: instances,
-			appearance: new Cesium.MaterialAppearance({
-				material,
-				translucent: true,
-			}),
+			if (!Number.isFinite(entry.rangeMeters) || entry.rangeMeters <= 0) return
+			const primitive = createDeviceRangePrimitiveWithHeight(entry.position, entry.rangeMeters, centerHeight)
+			if (!primitive) return
+			primitive.show = detailVisible.value
+			addCockpitPrimitive(primitive)
+			deviceRingFillPrimitives.push(primitive)
 		})
-		primitive.show = detailVisible.value
-		addCockpitPrimitive(primitive)
-		deviceRingFillPrimitives.push(primitive)
-	})
-	ringOutlineInstancesByStyle.forEach(instances => {
-		if (!instances.length) return
-		const primitive = new Cesium.GroundPolylinePrimitive({
-			geometryInstances: instances,
-			appearance: new Cesium.PolylineColorAppearance(),
-		})
-		primitive.show = detailVisible.value
-		addCockpitPrimitive(primitive)
-		deviceRingOutlinePrimitives.push(primitive)
-	})
+	}
 	reorderCockpitPrimitives()
 }
 
-const getDefenseZonePositions = geom => {
-	const points = geomAnalysis(geom)
-	if (points.length < 3) return []
-	const first = points[0]
-	const last = points[points.length - 1]
-	const list =
-		first && last && first.longitude === last.longitude && first.latitude === last.latitude
-			? points.slice(0, -1)
-			: points
-	return list.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude))
+const elevatePositions = async (positions, height = POLYGON_HEIGHT_M) => {
+	if (!Array.isArray(positions) || !positions.length) return []
+	if (!viewer) return positions
+	const points = positions
+		.map(pos => {
+			const carto = Cesium.Cartographic.fromCartesian(pos)
+			if (!carto) return null
+			return {
+				lng: Cesium.Math.toDegrees(carto.longitude),
+				lat: Cesium.Math.toDegrees(carto.latitude),
+			}
+		})
+		.filter(Boolean)
+	if (!points.length) return positions
+	const heights = await getPointPositionsHeight(points, viewer)
+	return heights.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude, (item.ASL ?? 0) + height))
 }
 
-const buildZonePrimitives = (zones, lineColor, fillColor1, fillColor2) => {
-	if (!viewer) return { primitive: null, outlinePrimitive: null }
-	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,
+const getAreaTypeStyle = areaType => {
+	return AREA_TYPE_STYLE_MAP?.[String(areaType)] || DEFAULT_AREA_STYLE
+}
+
+const buildPartitionPrimitives = async shapes => {
+	if (!viewer) return { primitives: [], outlinePrimitives: [] }
+	const vertexFormat = getTexturedVertexFormat()
+	const groups = new Map()
+	const tasks = []
+		; (shapes || []).forEach(shape => {
+			if (!shape?.positions || shape.positions.length < 3) return
+			const outlineColor = shape.outlineColor
+			const fillColor = shape.fillColor
+			const key = `${outlineColor.toCssColorString()}|${fillColor.toCssColorString()}`
+			if (!groups.has(key)) {
+				groups.set(key, { outlineColor, fillColor, polygonInstances: [], lineInstances: [] })
+			}
+			const group = groups.get(key)
+			const positions = shape.positions
+			tasks.push(
+				elevatePositions(positions).then(elevatedPositions => {
+					if (!elevatedPositions.length) return
+					const polygon = new Cesium.PolygonGeometry({
+						polygonHierarchy: new Cesium.PolygonHierarchy(elevatedPositions),
+						perPositionHeight: true,
+						vertexFormat,
+					})
+					group.polygonInstances.push(
+						new Cesium.GeometryInstance({
+							geometry: polygon,
+						})
+					)
+					const linePositions =
+						elevatedPositions.length > 1
+							? [...elevatedPositions, elevatedPositions[0]]
+							: elevatedPositions
+					group.lineInstances.push(
+						new Cesium.GeometryInstance({
+							geometry: new Cesium.PolylineGeometry({
+								positions: linePositions,
+								width: 2,
+							}),
+							attributes: {
+								color: Cesium.ColorGeometryInstanceAttribute.fromColor(outlineColor),
+							},
+						})
+					)
+				})
+			)
+		})
+
+	if (tasks.length) await Promise.all(tasks)
+
+	const primitives = []
+	const outlinePrimitives = []
+	groups.forEach(group => {
+		if (group.polygonInstances.length) {
+			const material = createRadialGradientMaterial(group.outlineColor, group.fillColor, {
+				gamma: 1.7,
+				innerCutoff: 0,
 			})
-			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)
-		const baseColor2 = Cesium.Color.fromCssColorString(fillColor2)
-		const material = createRadialGradientMaterial(baseColor1.withAlpha(0.64), baseColor2.withAlpha(0.64))
-		primitive = new Cesium.GroundPrimitive({
-			geometryInstances: polygonInstances,
-			appearance: new Cesium.MaterialAppearance({
-				material,
-				translucent: true,
-			}),
-		})
-		addCockpitPrimitive(primitive)
-	}
-	let outlinePrimitive = null
-	if (lineInstances.length) {
-		outlinePrimitive = new Cesium.GroundPolylinePrimitive({
-			geometryInstances: lineInstances,
-			appearance: new Cesium.PolylineColorAppearance(),
-		})
-		addCockpitPrimitive(outlinePrimitive)
-	}
-	return { primitive, outlinePrimitive }
+			const primitive = new Cesium.Primitive({
+				geometryInstances: group.polygonInstances,
+				appearance: new Cesium.MaterialAppearance({
+					material,
+					translucent: true,
+				}),
+			})
+			addCockpitPrimitive(primitive)
+			primitives.push(primitive)
+		}
+		if (group.lineInstances.length) {
+			const outlinePrimitive = new Cesium.Primitive({
+				geometryInstances: group.lineInstances,
+				appearance: new Cesium.PolylineColorAppearance(),
+			})
+			addCockpitPrimitive(outlinePrimitive)
+			outlinePrimitives.push(outlinePrimitive)
+		}
+	})
+
+	return { primitives, outlinePrimitives }
 }
 
-const renderDefenseZones = zones => {
-	if (!viewer) return
-	clearDefenseZoneEntities()
-	const result = buildZonePrimitives(zones, '#72F3FF', '#72F3FF', '#0A7C88')
-	defenseZonePrimitive = result.primitive
-	defenseZoneOutlinePrimitive = result.outlinePrimitive
-	if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value
-	if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = detailVisible.value
-	reorderCockpitPrimitives()
+const parseGeomJson = geomJson => {
+	if (!geomJson) return null
+	if (typeof geomJson === 'object') return geomJson
+	if (typeof geomJson !== 'string') return null
+	const trimmed = geomJson.trim()
+	if (!trimmed) return null
+	try {
+		return JSON.parse(trimmed)
+	} catch (error) {}
+	return null
 }
 
-const renderPartitions = zones => {
+const normalizeShapePoint = point => {
+	if (!point) return null
+	const lng = point?.lng ?? point?.longitude
+	const lat = point?.lat ?? point?.latitude
+	const height = Number.isFinite(Number(point?.height)) ? Number(point.height) : 0
+	if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
+	return { lng: Number(lng), lat: Number(lat), height }
+}
+
+const buildShapePositions = (points = []) => {
+	const normalized = points.map(normalizeShapePoint).filter(Boolean)
+	return normalized.map(point => Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.height))
+}
+
+const getShapeDisplayPoints = shape => {
+	if (Array.isArray(shape?.displayPoints) && shape.displayPoints.length) {
+		return shape.displayPoints
+	}
+	return shape?.points || []
+}
+
+const resolvePartitionShapes = areas => {
+	const shapes = []
+		; (areas || []).forEach(area => {
+			const extList = Array.isArray(area?.fwAreaDivideExtList) ? area.fwAreaDivideExtList : []
+			extList.forEach((item, index) => {
+				const isShapePayload = item?.drawType || item?.points
+				const parsed = isShapePayload ? item : parseGeomJson(item?.geomJson)
+				if (!parsed) return
+				const shape = {
+					id: parsed?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
+					drawType: parsed?.drawType ?? 'polygon',
+					areaType: parsed?.areaType ?? item?.areaTypeKey ?? item?.areaType ?? '',
+					points: Array.isArray(parsed?.points) ? parsed.points : [],
+					displayPoints: Array.isArray(parsed?.displayPoints) ? parsed.displayPoints : null,
+					meta: parsed?.meta ?? null,
+				}
+				if (shape.drawType === 'buffer' && shape.meta?.bufferRadii?.length && shape.meta?.center) {
+					const center = shape.meta.center
+					const centerCartesian = Cesium.Cartesian3.fromDegrees(
+						center.lng,
+						center.lat,
+						center.height || 0
+					)
+					const radii = shape.meta.bufferRadii
+						.map(radius => Number(radius))
+						.filter(radius => Number.isFinite(radius) && radius > 0)
+					radii.forEach((radius, levelIndex) => {
+						const positions = buildEllipsePositions(centerCartesian, radius, radius)
+					const style = BUFFER_LEVEL_STYLES[levelIndex] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
+						if (positions.length >= 3) {
+							shapes.push({
+								positions,
+								fillColor: style.fill,
+								outlineColor: style.outline,
+							})
+						}
+					})
+					return
+				}
+				const positions = buildShapePositions(getShapeDisplayPoints(shape))
+				if (positions.length >= 3) {
+					const style = getAreaTypeStyle(shape.areaType)
+					shapes.push({
+						positions,
+						fillColor: style.fill,
+						outlineColor: style.outline,
+					})
+				}
+			})
+		})
+	return shapes
+}
+
+const renderPartitions = async zones => {
 	if (!viewer) return
 	clearPartitionEntities()
-	const result = buildZonePrimitives(zones, '#FFD772', '#FFD772', '#A86B00')
-	partitionPrimitive = result.primitive
-	partitionOutlinePrimitive = result.outlinePrimitive
-	if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
-	if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = detailVisible.value
-	reorderCockpitPrimitives()
-}
-
-const loadDefenseZones = async () => {
-	if (!viewer) return
-	try {
-		const res = await fwDefenseZonePageApi({ current: 1, size: DEFAULT_ZONE_PAGE_SIZE })
-		renderDefenseZones(res?.data?.data?.records ?? [])
-	} catch (error) {
-		renderDefenseZones([])
+	const shapes = resolvePartitionShapes(zones)
+	const result = await buildPartitionPrimitives(shapes)
+	partitionFillPrimitives = result.primitives
+	partitionOutlinePrimitives = result.outlinePrimitives
+	if (partitionFillPrimitives.length) {
+		partitionFillPrimitives.forEach(primitive => {
+			primitive.show = detailVisible.value
+		})
 	}
+	if (partitionOutlinePrimitives.length) {
+		partitionOutlinePrimitives.forEach(primitive => {
+			primitive.show = detailVisible.value
+		})
+	}
+	reorderCockpitPrimitives()
 }
 
 const loadPartitions = async () => {
 	if (!viewer) return
 	try {
-		const res = await fwAreaDividePageApi({ current: 1, size: DEFAULT_ZONE_PAGE_SIZE })
-		renderPartitions(res?.data?.data?.records ?? [])
+		const res = await fwAreaDivideListApi()
+		await renderPartitions(res?.data?.data ?? [])
 	} catch (error) {
-		renderPartitions([])
+		await renderPartitions([])
 	}
 }
 
@@ -1125,7 +1209,6 @@
 	const stage = getStageByHeight(height)
 	updateStageDisplay(stage)
 	renderDeviceEntities(props.onlineDevices)
-	loadDefenseZones()
 	loadPartitions()
 	loadAggregation()
 	loadCommandPosts()
@@ -1153,7 +1236,6 @@
 onBeforeUnmount(() => {
 	document.removeEventListener('click', handleClickOutside)
 	clearDeviceEntities()
-	clearDefenseZoneEntities()
 	clearPartitionEntities()
 	clearAggregationEntities()
 	clearCommandPostEntities()
diff --git a/applications/drone-command/src/styles/common/cockpit.scss b/applications/drone-command/src/styles/common/cockpit.scss
index 7837a9f..74e128f 100644
--- a/applications/drone-command/src/styles/common/cockpit.scss
+++ b/applications/drone-command/src/styles/common/cockpit.scss
@@ -582,6 +582,15 @@
     .el-input__wrapper {
         --el-input-text-color: #161B2C !important;
     }
+
+    .el-select__selection.is-near {
+        margin-left: 0;
+
+        .el-tag {
+            color: #ffffff;
+            background: #161B2C;
+        }
+    }
 }
 
 .command-select-popper.el-select__popper {
diff --git a/applications/drone-command/src/utils/cesium/deviceRange.js b/applications/drone-command/src/utils/cesium/deviceRange.js
new file mode 100644
index 0000000..3ed20e5
--- /dev/null
+++ b/applications/drone-command/src/utils/cesium/deviceRange.js
@@ -0,0 +1,117 @@
+import * as Cesium from 'cesium'
+import { getPointPositionsHeight } from './mapUtil'
+
+const DEVICE_RANGE_MATERIAL_TYPE = 'DeviceRangeEllipsoid'
+let deviceRangeMaterialRegistered = false
+
+export const registerDeviceRangeMaterial = () => {
+	if (deviceRangeMaterialRegistered || !Cesium?.Material) return
+	deviceRangeMaterialRegistered = true
+	Cesium.Material._materialCache.addMaterial(DEVICE_RANGE_MATERIAL_TYPE, {
+		fabric: {
+			type: DEVICE_RANGE_MATERIAL_TYPE,
+			uniforms: {
+				color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
+				speed: 3.0,
+				globalAlpha: 0.9
+			},
+			source: `
+				uniform vec4 color;
+				uniform float speed;
+				uniform float globalAlpha;
+
+				czm_material czm_getMaterial(czm_materialInput materialInput)
+				{
+				czm_material material = czm_getDefaultMaterial(materialInput);
+				vec2 st = materialInput.st;
+				
+				float time = fract(czm_frameNumber * speed / 1000.0);
+
+				if (st.t < 0.5) {
+					discard;
+				}
+
+				float alphaMars3D = abs(smoothstep(0.5, 1.0, fract(-st.t - time)));
+				alphaMars3D += 0.1;
+
+				material.alpha = alphaMars3D * globalAlpha;
+				material.diffuse = color.rgb;
+
+				return material;
+				}
+			`,
+		},
+		translucent: () => true,
+	})
+}
+
+export const createDeviceRangeMaterial = () => {
+	registerDeviceRangeMaterial()
+	return Cesium.Material.fromType(DEVICE_RANGE_MATERIAL_TYPE, {
+		color: Cesium.Color.fromBytes(0, 255, 255, 155),
+	})
+}
+
+export const getDeviceLngLat = device => {
+	const lngRaw = device?.longitude ?? device?.lng ?? device?.lon
+	const latRaw = device?.latitude ?? device?.lat
+	const lng = Number(lngRaw)
+	const lat = Number(latRaw)
+	if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null
+	return { lng, lat }
+}
+
+export const normalizeDeviceRange = value => {
+	const range = Number(value)
+	if (!Number.isFinite(range) || range <= 0) return 2000
+	return range
+}
+
+export const resolveDeviceTerrainHeight = async (viewer, position) => {
+	if (!viewer || !position) return 0
+	const lng = position.lng ?? position.longitude
+	const lat = position.lat ?? position.latitude
+	if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return 0
+	try {
+		const results = await getPointPositionsHeight([{ lng: Number(lng), lat: Number(lat) }], viewer)
+		const height = Number(results?.[0]?.ASL)
+		return Number.isFinite(height) ? height : 0
+	} catch (error) {
+		return 0
+	}
+}
+
+export const createDeviceRangePrimitiveWithHeight = (position, rangeMeters, height, material = null) => {
+	if (!position || !Number.isFinite(rangeMeters)) return null
+	const lng = position.lng ?? position.longitude
+	const lat = position.lat ?? position.latitude
+	if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
+	const center = Cesium.Cartesian3.fromDegrees(Number(lng), Number(lat), Number(height) || 0)
+	const radii = new Cesium.Cartesian3(rangeMeters, rangeMeters, rangeMeters)
+	const geometry = new Cesium.EllipsoidGeometry({
+		radii,
+		vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED?.vertexFormat
+			|| Cesium.VertexFormat.POSITION_AND_NORMAL,
+	})
+	const instance = new Cesium.GeometryInstance({
+		geometry,
+		modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(center),
+	})
+	return new Cesium.Primitive({
+		geometryInstances: instance,
+		appearance: new Cesium.MaterialAppearance({
+			material: material || createDeviceRangeMaterial(),
+			closed: true,
+			translucent: true,
+			flat: true,
+			faceForward: true,
+		}),
+		asynchronous: false
+	})
+}
+
+export const createDeviceRangePrimitive = async (viewer, position, rangeMeters, material = null) => {
+	if (!viewer) return null
+	const height = await resolveDeviceTerrainHeight(viewer, position)
+	return createDeviceRangePrimitiveWithHeight(position, rangeMeters, height, material)
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js b/applications/drone-command/src/utils/cesium/shapeTools/DrawManager.js
similarity index 100%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js
rename to applications/drone-command/src/utils/cesium/shapeTools/DrawManager.js
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js b/applications/drone-command/src/utils/cesium/shapeTools/EditManager.js
similarity index 100%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js
rename to applications/drone-command/src/utils/cesium/shapeTools/EditManager.js
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js b/applications/drone-command/src/utils/cesium/shapeTools/ToolBase.js
similarity index 100%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js
rename to applications/drone-command/src/utils/cesium/shapeTools/ToolBase.js
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js b/applications/drone-command/src/utils/cesium/shapeTools/Tooltip.js
similarity index 100%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js
rename to applications/drone-command/src/utils/cesium/shapeTools/Tooltip.js
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js b/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawBufferCircleTool.js
similarity index 95%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js
rename to applications/drone-command/src/utils/cesium/shapeTools/draw/DrawBufferCircleTool.js
index ca5d4a9..937ce48 100644
--- a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js
+++ b/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawBufferCircleTool.js
@@ -18,6 +18,9 @@
 		this.circleLevel1OutlineEntity = null
 		this.circleLevel2OutlineEntity = null
 		this.circleLevel3OutlineEntity = null
+		this.radiusPoint1 = null
+		this.radiusPoint2 = null
+		this.radiusPoint3 = null
 		this.isDrawing = false
 		this.drawStep = 0
 		this.isCompleted = false
@@ -56,6 +59,7 @@
 			this.radiusLevel1 = radius
 			this.radiusLevel2 = radius
 			this.radiusLevel3 = radius
+			this.radiusPoint1 = position
 			this.drawStep = 2
 			this.tooltip.show('单击设置二级缓冲圆', click.position)
 			return
@@ -65,6 +69,7 @@
 			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
 			this.radiusLevel2 = Math.max(radius, this.radiusLevel1)
 			this.radiusLevel3 = this.radiusLevel2
+			this.radiusPoint2 = position
 			this.drawStep = 3
 			this.tooltip.show('单击设置三级缓冲圆', click.position)
 			return
@@ -73,6 +78,7 @@
 		if (this.drawStep === 3) {
 			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
 			this.radiusLevel3 = Math.max(radius, this.radiusLevel2)
+			this.radiusPoint3 = position
 			this.isDrawing = false
 			this.drawStep = 0
 			this.isCompleted = true
@@ -82,6 +88,9 @@
 				meta: {
 					center: this.centerCartesian,
 					bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+					controlPoints: [this.centerCartesian, this.radiusPoint1, this.radiusPoint2, this.radiusPoint3].filter(
+						Boolean
+					),
 				},
 			})
 			this.tooltip.hide()
@@ -229,6 +238,9 @@
 		this.radiusLevel1 = 0
 		this.radiusLevel2 = 0
 		this.radiusLevel3 = 0
+		this.radiusPoint1 = null
+		this.radiusPoint2 = null
+		this.radiusPoint3 = null
 		this.isDrawing = false
 		this.drawStep = 0
 		this.isCompleted = false
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js b/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawEllipseTool.js
similarity index 95%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js
rename to applications/drone-command/src/utils/cesium/shapeTools/draw/DrawEllipseTool.js
index 724288c..5f48d80 100644
--- a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js
+++ b/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawEllipseTool.js
@@ -70,7 +70,16 @@
 			this.drawStep = 0
 			this.isCompleted = true
 			const positions = buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
-			this.notify('getPolygonPositions', positions)
+			this.notify('getPolygonPositions', {
+				positions,
+				meta: {
+					center: this.centerCartesian,
+					majorPoint: this.majorPoint,
+					minorPoint: this.minorPoint,
+					semiMajor: this.semiMajor,
+					semiMinor: this.semiMinor,
+				},
+			})
 			this.tooltip.hide()
 			this.clearPreviewEntities()
 		}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js b/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js
similarity index 100%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js
rename to applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js b/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawRectangleTool.js
similarity index 100%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js
rename to applications/drone-command/src/utils/cesium/shapeTools/draw/DrawRectangleTool.js
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditBufferCircleTool.js
similarity index 88%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js
rename to applications/drone-command/src/utils/cesium/shapeTools/edit/EditBufferCircleTool.js
index 1808125..61e40b4 100644
--- a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js
+++ b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditBufferCircleTool.js
@@ -9,6 +9,26 @@
 		lat: point.lat ?? point.latitude,
 	}))
 
+const resolveLngLat = point => {
+	if (!point) return null
+	const lng = point.lng ?? point.longitude
+	const lat = point.lat ?? point.latitude
+	if (Number.isFinite(lng) && Number.isFinite(lat)) {
+		return { lng: Number(lng), lat: Number(lat), height: Number(point.height) || 0 }
+	}
+	return null
+}
+
+const resolveCartesian = point => {
+	if (!point) return null
+	if (Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z)) {
+		return new Cesium.Cartesian3(point.x, point.y, point.z)
+	}
+	const lngLat = resolveLngLat(point)
+	if (!lngLat) return null
+	return Cesium.Cartesian3.fromDegrees(lngLat.lng, lngLat.lat, lngLat.height || 0)
+}
+
 export class EditBufferCircleTool extends ToolBase {
 	constructor(viewer) {
 		super(viewer)
@@ -34,10 +54,15 @@
 	start(data = []) {
 		const inputPoints = Array.isArray(data?.points) ? data.points : data
 		const normalized = normalizePoints(inputPoints)
-		if (!normalized.length) return
-		const centerLng = normalized.reduce((sum, p) => sum + p.lng, 0) / normalized.length
-		const centerLat = normalized.reduce((sum, p) => sum + p.lat, 0) / normalized.length
-		this.centerCartesian = Cesium.Cartesian3.fromDegrees(centerLng, centerLat)
+		const metaCenter = resolveCartesian(data?.meta?.center)
+		if (metaCenter) {
+			this.centerCartesian = metaCenter
+		} else {
+			if (!normalized.length) return
+			const centerLng = normalized.reduce((sum, p) => sum + p.lng, 0) / normalized.length
+			const centerLat = normalized.reduce((sum, p) => sum + p.lat, 0) / normalized.length
+			this.centerCartesian = Cesium.Cartesian3.fromDegrees(centerLng, centerLat)
+		}
 		const radii = Array.isArray(data?.meta?.bufferRadii) ? data.meta.bufferRadii : null
 		if (radii?.length) {
 			const [r1, r2, r3] = radii
@@ -124,6 +149,12 @@
 			meta: {
 				center: this.centerCartesian,
 				bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+				controlPoints: [
+					this.centerCartesian,
+					this.getRadiusPoint(this.radiusLevel1),
+					this.getRadiusPoint(this.radiusLevel2),
+					this.getRadiusPoint(this.radiusLevel3),
+				],
 			},
 		})
 	}
@@ -256,6 +287,12 @@
 			meta: {
 				center: this.centerCartesian,
 				bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+				controlPoints: [
+					this.centerCartesian,
+					this.getRadiusPoint(this.radiusLevel1),
+					this.getRadiusPoint(this.radiusLevel2),
+					this.getRadiusPoint(this.radiusLevel3),
+				],
 			},
 		}
 	}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditEllipseTool.js
similarity index 66%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js
rename to applications/drone-command/src/utils/cesium/shapeTools/edit/EditEllipseTool.js
index c3b2904..228eae3 100644
--- a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js
+++ b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditEllipseTool.js
@@ -24,6 +24,36 @@
 		lat: point.lat ?? point.latitude,
 	}))
 
+const resolveLngLat = point => {
+	if (!point) return null
+	const lng = point.lng ?? point.longitude
+	const lat = point.lat ?? point.latitude
+	if (Number.isFinite(lng) && Number.isFinite(lat)) {
+		return { lng: Number(lng), lat: Number(lat), height: Number(point.height) || 0 }
+	}
+	return null
+}
+
+const resolveCartesian = point => {
+	if (!point) return null
+	if (Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z)) {
+		return new Cesium.Cartesian3(point.x, point.y, point.z)
+	}
+	const lngLat = resolveLngLat(point)
+	if (!lngLat) return null
+	return Cesium.Cartesian3.fromDegrees(lngLat.lng, lngLat.lat, lngLat.height || 0)
+}
+
+const resolveCartographic = point => {
+	if (!point) return null
+	if (Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z)) {
+		return Cesium.Cartographic.fromCartesian(point)
+	}
+	const lngLat = resolveLngLat(point)
+	if (!lngLat) return null
+	return Cesium.Cartographic.fromDegrees(lngLat.lng, lngLat.lat, lngLat.height || 0)
+}
+
 export class EditEllipseTool extends ToolBase {
 	constructor(viewer, options = {}) {
 		super(viewer)
@@ -40,22 +70,43 @@
 		this.style = resolveStyle(options?.style)
 	}
 
-	start(points = []) {
-		const normalized = normalizePoints(points)
-		const bounds = getBoundsFromLngLats(normalized)
-		const center = getCenterFromBounds(bounds)
-		if (!center) return
-		this.centerCartesian = Cesium.Cartesian3.fromRadians(center.longitude, center.latitude)
-		const eastWest = getMetersBetween(
-			new Cesium.Cartographic(bounds.west, center.latitude),
-			new Cesium.Cartographic(bounds.east, center.latitude)
-		)
-		const northSouth = getMetersBetween(
-			new Cesium.Cartographic(center.longitude, bounds.south),
-			new Cesium.Cartographic(center.longitude, bounds.north)
-		)
-		this.semiMajor = Math.max(1, eastWest / 2)
-		this.semiMinor = Math.max(1, northSouth / 2)
+	start(data = []) {
+		const inputPoints = Array.isArray(data?.points) ? data.points : data
+		const meta = data?.meta || null
+		const metaCenter = resolveCartesian(meta?.center)
+		if (metaCenter) {
+			this.centerCartesian = metaCenter
+			const centerCarto = Cesium.Cartographic.fromCartesian(metaCenter)
+			const majorFromMeta = Number.isFinite(meta?.semiMajor) ? Number(meta.semiMajor) : 0
+			const minorFromMeta = Number.isFinite(meta?.semiMinor) ? Number(meta.semiMinor) : 0
+			const majorPoint = resolveCartographic(meta?.majorPoint)
+			const minorPoint = resolveCartographic(meta?.minorPoint)
+			const majorDistance =
+				majorFromMeta || (majorPoint ? getMetersBetween(centerCarto, majorPoint) : 0)
+			const minorDistance =
+				minorFromMeta || (minorPoint ? getMetersBetween(centerCarto, minorPoint) : 0)
+			if (majorDistance > 0) this.semiMajor = Math.max(1, majorDistance)
+			if (minorDistance > 0) this.semiMinor = Math.max(1, minorDistance)
+			if (this.semiMinor > this.semiMajor) this.semiMinor = this.semiMajor
+		}
+
+		if (!this.centerCartesian || !this.semiMajor || !this.semiMinor) {
+			const normalized = normalizePoints(inputPoints)
+			const bounds = getBoundsFromLngLats(normalized)
+			const center = getCenterFromBounds(bounds)
+			if (!center) return
+			this.centerCartesian = Cesium.Cartesian3.fromRadians(center.longitude, center.latitude)
+			const eastWest = getMetersBetween(
+				new Cesium.Cartographic(bounds.west, center.latitude),
+				new Cesium.Cartographic(bounds.east, center.latitude)
+			)
+			const northSouth = getMetersBetween(
+				new Cesium.Cartographic(center.longitude, bounds.south),
+				new Cesium.Cartographic(center.longitude, bounds.north)
+			)
+			this.semiMajor = Math.max(1, eastWest / 2)
+			this.semiMinor = Math.max(1, northSouth / 2)
+		}
 
 		this.dataSource = new Cesium.CustomDataSource('edit-ellipse')
 		this.viewer.dataSources.add(this.dataSource)
@@ -113,7 +164,16 @@
 		this.dragType = null
 		this.enableMapControl()
 		const positions = buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
-		this.notify('getPolygonPositions', positions)
+		this.notify('getPolygonPositions', {
+			positions,
+			meta: {
+				center: this.centerCartesian,
+				majorPoint: this.getAxisPoint(this.semiMajor, 0),
+				minorPoint: this.getAxisPoint(0, this.semiMinor),
+				semiMajor: this.semiMajor,
+				semiMinor: this.semiMinor,
+			},
+		})
 	}
 
 	createEntities() {
@@ -172,7 +232,16 @@
 
 	getPositions() {
 		if (!this.centerCartesian) return []
-		return buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
+		return {
+			positions: buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor),
+			meta: {
+				center: this.centerCartesian,
+				majorPoint: this.getAxisPoint(this.semiMajor, 0),
+				minorPoint: this.getAxisPoint(0, this.semiMinor),
+				semiMajor: this.semiMajor,
+				semiMinor: this.semiMinor,
+			},
+		}
 	}
 
 	getAxisPoint(eastMeters, northMeters) {
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js
similarity index 98%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js
rename to applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js
index c6b2a13..0e5cd6e 100644
--- a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js
+++ b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js
@@ -74,7 +74,7 @@
 	}
 
 	handleMouseMove(movement) {
-		this.tooltip.show('拖动顶点编辑,右键删除', movement.endPosition)
+		this.tooltip.show('拖动顶点编辑,右键删除点', movement.endPosition)
 		if (!this.isDragging || this.draggedIndex < 0) return
 		const position = this.getPositionFromScreen(movement.endPosition)
 		if (!position) return
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditRectangleTool.js
similarity index 100%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js
rename to applications/drone-command/src/utils/cesium/shapeTools/edit/EditRectangleTool.js
diff --git a/applications/drone-command/src/utils/cesium/shapeTools/index.js b/applications/drone-command/src/utils/cesium/shapeTools/index.js
new file mode 100644
index 0000000..89f1ecc
--- /dev/null
+++ b/applications/drone-command/src/utils/cesium/shapeTools/index.js
@@ -0,0 +1,13 @@
+export { DrawManager } from './DrawManager'
+export { EditManager } from './EditManager'
+export { buildEllipsePositions, getBoundsFromCartesians, getBoundsFromLngLats, rectanglePositionsFromBounds, getCenterFromBounds, getMetersBetween, cartesianToLocal } from './shapeUtils'
+export { DrawPolygonTool } from './draw/DrawPolygonTool'
+export { DrawRectangleTool } from './draw/DrawRectangleTool'
+export { DrawEllipseTool } from './draw/DrawEllipseTool'
+export { DrawBufferCircleTool } from './draw/DrawBufferCircleTool'
+export { EditPolygonTool } from './edit/EditPolygonTool'
+export { EditRectangleTool } from './edit/EditRectangleTool'
+export { EditEllipseTool } from './edit/EditEllipseTool'
+export { EditBufferCircleTool } from './edit/EditBufferCircleTool'
+export { ToolBase } from './ToolBase'
+export { MapTooltip as Tooltip } from './Tooltip'
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js b/applications/drone-command/src/utils/cesium/shapeTools/shapeUtils.js
similarity index 100%
rename from applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js
rename to applications/drone-command/src/utils/cesium/shapeTools/shapeUtils.js
diff --git a/applications/drone-command/src/views/api.txt b/applications/drone-command/src/views/api.txt
index 22ac09a..5480053 100644
--- a/applications/drone-command/src/views/api.txt
+++ b/applications/drone-command/src/views/api.txt
@@ -1,98 +1,21 @@
 
 
-## 重点关注
+## 列表-不带分页
 
 
-**接口地址**:`/drone-fw/record/fwAlarmFavorite/save`
+**接口地址**:`/area/fwAreaDivide/list`
 
 
-**请求方式**:`POST`
+**请求方式**:`GET`
 
 
-**请求数据类型**:`application/json`
+**请求数据类型**:`application/x-www-form-urlencoded`
 
 
 **响应数据类型**:`*/*`
 
 
-**接口描述**:<p>传入fwAlarmFavorite</p>
-
-
-
-**请求示例**:
-
-
-```javascript
-{
-  "alarmRecordId": 0
-}
-```
-
-
-**请求参数**:
-
-
-**请求参数**:
-
-
-| 参数名称 | 参数说明 | 请求类型    | 是否必须 | 数据类型 | schema |
-| -------- | -------- | ----- | -------- | -------- | ------ |
-|fwAlarmFavorite|告警记录关注表|body|true|FwAlarmFavorite对象|FwAlarmFavorite对象|
-|&emsp;&emsp;alarmRecordId|告警记录id||false|integer(int64)||
-
-
-**响应状态**:
-
-
-| 状态码 | 说明 | schema |
-| -------- | -------- | ----- | 
-|200|OK|R|
-|201|Created||
-|401|Unauthorized||
-|403|Forbidden||
-|404|Not Found||
-
-
-**响应参数**:
-
-
-| 参数名称 | 参数说明 | 类型 | schema |
-| -------- | -------- | ----- |----- | 
-|code|状态码|integer(int32)|integer(int32)|
-|data|承载数据|object||
-|msg|返回消息|string||
-|success|是否成功|boolean||
-
-
-**响应示例**:
-```javascript
-{
-	"code": 0,
-	"data": {},
-	"msg": "",
-	"success": true
-}
-```
-
-
-
-
-## 取消关注
-
-
-**接口地址**:`/drone-fw/record/fwAlarmFavorite/remove`
-
-
-**请求方式**:`POST`
-
-
-**请求数据类型**:`application/json`
-
-
-**响应数据类型**:`*/*`
-
-
-**接口描述**:<p>传入ids</p>
+**接口描述**:<p>传入filterSelected或sceneId或areaTypeKeys或isSetSceneManage或flyTime</p>
 
 
 
@@ -104,7 +27,11 @@
 
 | 参数名称 | 参数说明 | 请求类型    | 是否必须 | 数据类型 | schema |
 | -------- | -------- | ----- | -------- | -------- | ------ |
-|ids|主键集合|query|true|string||
+|areaTypeKeys|区域类型key集合(逗号分隔)|query|false|string||
+|filterSelected|是否过滤已被选择的数据(1过滤 0不过滤)|query|false|integer(int32)||
+|flyTime|时间筛选(yyyy-MM-dd HH:mm:ss)|query|false|string(date-time)||
+|isSetSceneManage|是否按场景管理过滤(1是 0或空否)|query|false|integer(int32)||
+|sceneId|场景id|query|false|integer(int64)||
 
 
 **响应状态**:
@@ -112,8 +39,7 @@
 
 | 状态码 | 说明 | schema |
 | -------- | -------- | ----- | 
-|200|OK|R|
-|201|Created||
+|200|OK|R«List«FwAreaDivideVO»»|
 |401|Unauthorized||
 |403|Forbidden||
 |404|Not Found||
@@ -125,7 +51,28 @@
 | 参数名称 | 参数说明 | 类型 | schema |
 | -------- | -------- | ----- |----- | 
 |code|状态码|integer(int32)|integer(int32)|
-|data|承载数据|object||
+|data|承载数据|array|FwAreaDivideVO|
+|&emsp;&emsp;areaCode|区域编码|string||
+|&emsp;&emsp;areaName|区域名称|string||
+|&emsp;&emsp;areaTypeKeys|区域面的类型集合逗号隔开|string||
+|&emsp;&emsp;controlLevel|管控级别:日常/重点/严密|string||
+|&emsp;&emsp;deviceIds|关联设备ID,逗号分隔|string||
+|&emsp;&emsp;flyDateEnd|可飞行日期时间-结束|string(date-time)||
+|&emsp;&emsp;flyDateStart|可飞行日期时间-开始|string(date-time)||
+|&emsp;&emsp;fwAreaDivideExtList|面数据|array|FwAreaDivideExtVO|
+|&emsp;&emsp;&emsp;&emsp;areaCode|区域编码|string||
+|&emsp;&emsp;&emsp;&emsp;areaDivideId|关联主表区域划分ID|integer||
+|&emsp;&emsp;&emsp;&emsp;areaTypeKey|区域类型数字字典key|string||
+|&emsp;&emsp;&emsp;&emsp;areaTypeValue|区域类型数字字典value|string||
+|&emsp;&emsp;&emsp;&emsp;geomJson|几何JSON|string||
+|&emsp;&emsp;&emsp;&emsp;id|主键id|integer||
+|&emsp;&emsp;id|主键id|integer(int64)||
+|&emsp;&emsp;policeStationContactPerson|派出所联系人|string||
+|&emsp;&emsp;policeStationContactPhone|派出所联系电话|string||
+|&emsp;&emsp;policeStationId|关联派出所id|string||
+|&emsp;&emsp;policeStationName|派出所名称|string||
+|&emsp;&emsp;responseMechanism|响应机制内容|string||
+|&emsp;&emsp;triggerCondition|触发条件|string||
 |msg|返回消息|string||
 |success|是否成功|boolean||
 
@@ -134,7 +81,34 @@
 ```javascript
 {
 	"code": 0,
-	"data": {},
+	"data": [
+		{
+			"areaCode": "",
+			"areaName": "",
+			"areaTypeKeys": "",
+			"controlLevel": "",
+			"deviceIds": "",
+			"flyDateEnd": "",
+			"flyDateStart": "",
+			"fwAreaDivideExtList": [
+				{
+					"areaCode": "",
+					"areaDivideId": 0,
+					"areaTypeKey": "",
+					"areaTypeValue": "",
+					"geomJson": "",
+					"id": 0
+				}
+			],
+			"id": 0,
+			"policeStationContactPerson": "",
+			"policeStationContactPhone": "",
+			"policeStationId": "",
+			"policeStationName": "",
+			"responseMechanism": "",
+			"triggerCondition": ""
+		}
+	],
 	"msg": "",
 	"success": true
 }
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue
index cc492ca..302d14e 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue
@@ -12,54 +12,6 @@
 					:layer-mode="4"
 					:boundary="false"
 				/>
-				<div
-					v-if="showTypePanel && !readonly"
-					class="shape-type-panel"
-				>
-					<div class="panel-header">
-						<span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span>
-						<el-icon class="panel-close" @click.stop="handleTypePanelClose"><Close /></el-icon>
-					</div>
-					<div class="panel-body">
-						<span class="panel-label">区域类型</span>
-						<el-select
-							class="command-select"
-							popper-class="command-select-popper"
-							v-model="formData.areaType"
-							placeholder="请选择"
-							@change="handleAreaTypeChange"
-						>
-							<el-option
-								v-for="item in dictObj.areaType"
-								:key="item.dictKey"
-								:label="item.dictValue"
-								:value="item.dictKey"
-							/>
-						</el-select>
-					</div>
-				</div>
-				<div class="map-toolbar">
-					<button class="tool-btn" type="button" @click="handleDraw('polygon')">
-						<span class="tool-icon polygon"></span>
-						<span class="tool-text">多边形</span>
-					</button>
-					<button class="tool-btn" type="button" @click="handleDraw('rectangle')">
-						<span class="tool-icon rect"></span>
-						<span class="tool-text">矩形</span>
-					</button>
-					<button class="tool-btn" type="button" @click="handleDraw('ellipse')">
-						<span class="tool-icon ellipse"></span>
-						<span class="tool-text">椭圆</span>
-					</button>
-					<button class="tool-btn" type="button" @click="handleDraw('buffer')">
-						<span class="tool-icon buffer"></span>
-						<span class="tool-text">缓冲圆</span>
-					</button>
-					<button class="tool-btn danger" type="button" @click="handleClearShape">
-						<span class="tool-icon trash"></span>
-						<span class="tool-text">删除</span>
-					</button>
-				</div>
 			</div>
 
 			<div class="right-container">
@@ -180,28 +132,6 @@
 						</el-select>
 					</el-form-item>
 
-					<div class="shape-table-container">
-						<div class="shape-table-title">绘制区域列表</div>
-						<el-table class="command-table" :data="shapeList" row-key="id" height="360">
-							<el-table-column prop="areaType" label="类型" width="120">
-								<template v-slot="{ row }">
-									{{ getDictLabel(row.areaType, dictObj.areaType) }}
-								</template>
-							</el-table-column>
-							<el-table-column prop="drawType" label="绘制类型" width="120">
-								<template v-slot="{ row }">
-									{{ drawTypeLabelMap[row.drawType] || '-' }}
-								</template>
-							</el-table-column>
-							<el-table-column label="操作" class-name="operation-btns">
-								<template v-slot="{ row }">
-									<el-link @click="handleShapeEdit(row)">编辑</el-link>
-									<el-link @click="handleShapeDelete(row)">删除</el-link>
-								</template>
-							</el-table-column>
-						</el-table>
-					</div>
-
 					<div class="search-table-container">
 						<div class="search-box">
 							<div class="label">
@@ -243,17 +173,15 @@
 <script setup>
 import { Close } from '@element-plus/icons-vue'
 
-import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
+import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
 import { ElMessage } from 'element-plus'
 import { fwAreaDivideDetailApi, fwAreaDivideSubmitApi } from './partitionApi'
 import { saveOperationLog } from '@ztzf/apis'
 import { useRoute } from 'vue-router'
 import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
 import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
-import { DrawManager } from './shapeTools/DrawManager'
-import { EditManager } from './shapeTools/EditManager'
+import { DrawPolygon } from '@/utils/cesium/DrawPolygon'
 import { cartesian3Convert } from '@/utils/cesium/mapUtil'
-import { buildEllipsePositions } from './shapeTools/shapeUtils'
 import * as turf from '@turf/turf'
 import * as Cesium from 'cesium'
 import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
@@ -291,39 +219,9 @@
 const deviceOptions = ref([]) // 关联设备
 const dictObj = inject('dictObj')
 const mapRef = ref(null)
-const currentShapeType = ref('polygon')
-const shapeList = ref([])
-const activeShapeId = ref(null)
-const drawTypeLabelMap = {
-	polygon: '多边形',
-	rectangle: '矩形',
-	ellipse: '椭圆',
-	buffer: '缓冲圆',
-}
-const showTypePanel = ref(false)
-const suppressTypePanelOnce = ref(false)
-const areaTypeStyleMap = {
-	'1': {
-		fill: Cesium.Color.fromBytes(25, 178, 230, 128),
-		outline: Cesium.Color.fromBytes(0, 251, 255, 255),
-	},
-	'2': {
-		fill: Cesium.Color.fromBytes(255, 235, 59, 128),
-		outline: Cesium.Color.fromBytes(255, 235, 59, 255),
-	},
-	'3': {
-		fill: Cesium.Color.fromBytes(247, 20, 20, 128),
-		outline: Cesium.Color.fromBytes(255, 0, 0, 255),
-	},
-}
 let viewer
-let drawManager
-let editManager
-let activeTool
+let drawPolygonExample
 let pointList = []
-let viewEntity
-let shapeDisplaySource
-const activeToolMode = ref('')
 
 const rules = {
 	areaName: fieldRules(true, 50),
@@ -408,24 +306,6 @@
 		formData.value.flyDateEnd = flyDateRange.value[1] || ''
 	}
 )
-watch(
-	() => visible.value,
-	val => {
-		if (!val) {
-			viewEntity && viewer?.entities?.remove(viewEntity)
-			viewEntity = null
-			clearActiveTool()
-			hideTypePanel()
-			clearShapeDisplay()
-		}
-	}
-)
-watch(
-	() => formData.value.areaType,
-	val => {
-		updateActiveShapeAreaType(val)
-	}
-)
 
 // 加载详情
 async function loadDetail () {
@@ -454,191 +334,14 @@
 	viewer = map?.viewer || null
 }
 
-function ensureShapeDisplaySource () {
-	if (!viewer) return null
-	if (!shapeDisplaySource) {
-		shapeDisplaySource = new Cesium.CustomDataSource('partition-shape-display')
-		viewer.dataSources.add(shapeDisplaySource)
-	}
-	return shapeDisplaySource
-}
-
-function clearShapeDisplay () {
-	if (!shapeDisplaySource) return
-	shapeDisplaySource.entities.removeAll()
-}
-
-function normalizeShapePoint (point) {
-	const lng = point?.lng ?? point?.longitude
-	const lat = point?.lat ?? point?.latitude
-	if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
-	return {
-		lng: Number(lng),
-		lat: Number(lat),
-		height: Number.isFinite(Number(point?.height)) ? Number(point.height) : 0,
-	}
-}
-
-function buildShapePositions (points = []) {
-	const normalized = points.map(normalizeShapePoint).filter(Boolean)
-	return normalized.map(point =>
-		Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.height)
-	)
-}
-
-function getAreaTypeStyle (areaType) {
-	return areaTypeStyleMap?.[String(areaType)] || {
-		fill: Cesium.Color.fromBytes(45, 140, 240, 99),
-		outline: Cesium.Color.fromBytes(45, 140, 240, 255),
-	}
-}
-
-function renderShapeList () {
-	if (!viewer || !visible.value || readonly.value) {
-		clearShapeDisplay()
-		return
-	}
-	const dataSource = ensureShapeDisplaySource()
-	if (!dataSource) return
-	dataSource.entities.removeAll()
-	shapeList.value.forEach(shape => {
-		if (!shape?.points?.length) return
-		if (activeToolMode.value === 'edit' && shape.id === activeShapeId.value) return
-		if (shape.drawType === 'buffer' && shape.meta?.bufferRadii?.length && shape.meta?.center) {
-			const center = shape.meta.center
-			const centerCartesian = Cesium.Cartesian3.fromDegrees(center.lng, center.lat, center.height || 0)
-			const [radius1, radius2, radius3] = shape.meta.bufferRadii
-			const radii = [radius1, radius2, radius3].filter(item => Number.isFinite(item) && item > 0)
-			if (radii.length) {
-				const styles = [
-					{
-						fill: Cesium.Color.fromBytes(247, 20, 20, 128),
-						outline: Cesium.Color.fromBytes(255, 0, 0, 255),
-					},
-					{
-						fill: Cesium.Color.fromBytes(255, 235, 59, 128),
-						outline: Cesium.Color.fromBytes(255, 235, 59, 255),
-					},
-					{
-						fill: Cesium.Color.fromBytes(25, 178, 230, 128),
-						outline: Cesium.Color.fromBytes(0, 251, 255, 255),
-					},
-				]
-				radii.forEach((radius, index) => {
-					const style = styles[index] || styles[styles.length - 1]
-					dataSource.entities.add({
-						name: 'shape-display',
-						customData: { shapeId: shape.id, drawType: shape.drawType, level: index + 1 },
-						position: centerCartesian,
-						ellipse: {
-							semiMajorAxis: radius,
-							semiMinorAxis: radius,
-							material: style.fill,
-							outline: false,
-							heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
-						},
-					})
-					const positions = buildEllipsePositions(centerCartesian, radius, radius)
-					dataSource.entities.add({
-						name: 'shape-display',
-						customData: { shapeId: shape.id, drawType: shape.drawType, level: index + 1, outline: true },
-						polyline: {
-							positions: positions.length ? [...positions, positions[0]] : positions,
-							clampToGround: true,
-							width: 2,
-							material: style.outline,
-						},
-					})
-				})
-			}
-			return
-		}
-
-		const positions = buildShapePositions(shape.points)
-		if (positions.length < 3) return
-		const style = getAreaTypeStyle(shape.areaType)
-		dataSource.entities.add({
-			name: 'shape-display',
-			customData: { shapeId: shape.id, drawType: shape.drawType },
-			polygon: {
-				hierarchy: new Cesium.PolygonHierarchy(positions),
-				material: style.fill,
-				outline: false,
-				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
-			},
-			polyline: {
-				positions: positions.length ? [...positions, positions[0]] : positions,
-				clampToGround: true,
-				width: 2,
-				material: style.outline,
-			},
-		})
-	})
-}
-
 // 绘制完成回调
 const drawFinished = async data => {
-	const positions = Array.isArray(data?.positions) ? data.positions : data
-	if (!Array.isArray(positions) || positions.length < 3) {
-		pointList = []
-		formData.value.areaSize = null
-		formData.value.longitude = null
-		formData.value.latitude = null
-		hideTypePanel()
-		return
-	}
-	const meta = data?.meta
-	const bufferRadii = Array.isArray(meta?.bufferRadii) ? meta.bufferRadii : null
-	const centerCartesian = meta?.center
-	const centerLngLat = centerCartesian ? cartesian3Convert(centerCartesian, viewer) : null
-	pointList = _.cloneDeep(positions).map(item => {
+	pointList = _.cloneDeep(data).map(item => {
 		const val = cartesian3Convert(item, viewer)
 		return { ...val, lng: val.longitude, lat: val.latitude }
 	})
-	applyPolygonMetrics(pointList)
-	if (!formData.value.areaType) {
-		const detectAreaType = getDetectAreaTypeKey()
-		if (detectAreaType) {
-			formData.value.areaType = detectAreaType
-		}
-	}
-	if (!suppressTypePanelOnce.value) {
-		showTypePanelAtCurrent()
-	}
-	suppressTypePanelOnce.value = false
-
-	if (!activeShapeId.value) {
-		activeShapeId.value = `shape_${Date.now()}_${Math.random().toString(16).slice(2, 6)}`
-	}
-	const targetIndex = shapeList.value.findIndex(item => item.id === activeShapeId.value)
-	const shapePayload = {
-		id: activeShapeId.value,
-		areaType: formData.value.areaType,
-		drawType: currentShapeType.value,
-		points: _.cloneDeep(pointList),
-		meta: bufferRadii && centerLngLat
-			? {
-				bufferRadii,
-				center: {
-					lng: centerLngLat.longitude,
-					lat: centerLngLat.latitude,
-					height: centerLngLat.height ?? 0,
-				},
-			}
-			: null,
-	}
-	if (targetIndex === -1) {
-		shapeList.value.push(shapePayload)
-	} else {
-		shapeList.value.splice(targetIndex, 1, shapePayload)
-	}
-	renderShapeList()
-}
-
-function applyPolygonMetrics(points) {
-	if (!points?.length) return
 	const polygon = turf.polygon([
-		[...points.map(item => [item.longitude, item.latitude]), [points[0].longitude, points[0].latitude]],
+		[...pointList.map(item => [item.longitude, item.latitude]), [pointList[0].longitude, pointList[0].latitude]],
 	])
 	const center = turf.centerOfMass(polygon)
 	const areaSqm = turf.area(polygon) // 平方米
@@ -647,152 +350,41 @@
 	formData.value.latitude = center.geometry.coordinates[1]
 }
 
-function getDetectAreaTypeKey () {
-	const areaTypeOptions = Array.isArray(dictObj?.value?.areaType)
-		? dictObj.value.areaType
-		: dictObj?.areaType
-	if (!Array.isArray(areaTypeOptions)) return ''
-	const target = areaTypeOptions.find(item => item?.dictKey === '1')
-	return target?.dictKey ?? ''
+// 新增模式绘制
+function addPolygon () {
+	drawPolygonExample = new DrawPolygon(viewer)
+	drawPolygonExample.initHandler(viewer)
+	drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
 }
 
-function showTypePanelAtCurrent () {
-	if (readonly.value) return
-	showTypePanel.value = true
-}
-
-function hideTypePanel () {
-	showTypePanel.value = false
-}
-
-function handleTypePanelClose () {
-	hideTypePanel()
-	if (readonly.value) return
-	if (activeToolMode.value !== 'edit') return
-	const positions = activeTool?.getPositions?.()
-	if (Array.isArray(positions) && positions.length >= 3) {
-		suppressTypePanelOnce.value = true
-		drawFinished(positions)
-	}
-	clearActiveTool()
-}
-
-function handleAreaTypeChange (value) {
-	updateActiveShapeAreaType(value)
-}
-
-function updateActiveShapeAreaType (value) {
-	if (currentShapeType.value !== 'buffer') {
-		activeTool?.setStyle?.(getAreaTypeStyle(value))
-	}
-	if (!activeShapeId.value) return
-	const targetIndex = shapeList.value.findIndex(item => item.id === activeShapeId.value)
-	if (targetIndex === -1) return
-	const target = shapeList.value[targetIndex]
-	shapeList.value.splice(targetIndex, 1, {
-		...target,
-		areaType: value,
-	})
-	renderShapeList()
-}
-
-// Tool lifecycle
-function clearActiveTool () {
-	activeTool?.destroy?.()
-	activeTool = null
-	drawManager?.destroy()
-	editManager?.destroy()
-	activeToolMode.value = ''
-	renderShapeList()
-}
-
-function startDraw (type) {
-	currentShapeType.value = type
-	activeToolMode.value = 'draw'
-	drawManager ||= new DrawManager(viewer)
-	activeTool = drawManager.start(type, { style: getAreaTypeStyle(formData.value.areaType) })
-	activeTool?.subscribe('getPolygonPositions', drawFinished)
-}
-
-function startEdit (type, points) {
-	currentShapeType.value = type
-	activeToolMode.value = 'edit'
-	editManager ||= new EditManager(viewer)
-	activeTool = editManager.start(type, points, { style: getAreaTypeStyle(formData.value.areaType) })
-	activeTool?.subscribe('getPolygonPositions', drawFinished)
-}
-
-function handleDraw (type) {
-	if (readonly.value) return
-	if (!viewer) return
-	clearActiveTool()
-	viewEntity && viewer?.entities?.remove(viewEntity)
-	viewEntity = null
-	hideTypePanel()
-	renderShapeList()
-	if (dialogMode.value === 'edit' && pointList.length >= 3) {
-		const editPoints = _.cloneDeep(pointList)
-		if (editPoints.length > 1) {
-			const first = editPoints[0]
-			const last = editPoints[editPoints.length - 1]
-			if (first.longitude === last.longitude && first.latitude === last.latitude) {
-				editPoints.pop()
-			}
+// 编辑面
+function editPolygon () {
+	if (!formData.value?.geom) return
+	pointList = geomAnalysis(formData.value.geom)
+	drawPolygonExample?.destroy()
+	drawPolygonExample = new DrawPolygon(viewer)
+	let pointList1 = _.cloneDeep(pointList)
+	pointList1.pop()
+	drawPolygonExample.initPolygon(
+		viewer,
+		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)
 		}
-		activeShapeId.value = activeShapeId.value || (shapeList.value[0]?.id ?? null)
-		startEdit(type, editPoints)
-		return
-	}
-	const defaultAreaType = getDetectAreaTypeKey()
-	if (defaultAreaType) {
-		formData.value.areaType = defaultAreaType
-	}
-	activeShapeId.value = null
-	startDraw(type)
-}
-
-function handleClearShape () {
-	if (readonly.value) return
-	clearActiveTool()
-	viewEntity && viewer?.entities?.remove(viewEntity)
-	viewEntity = null
-	pointList = []
-	shapeList.value = []
-	activeShapeId.value = null
-	formData.value.areaSize = null
-	formData.value.longitude = null
-	formData.value.latitude = null
-	hideTypePanel()
-	clearShapeDisplay()
-}
-
-function handleShapeEdit (row) {
-	if (!row?.points?.length) return
-	if (activeToolMode.value === 'edit' && activeShapeId.value === row.id) return
-	activeShapeId.value = row.id
-	currentShapeType.value = row.drawType
-	formData.value.areaType = row.areaType
-	pointList = _.cloneDeep(row.points)
-	clearActiveTool()
-	const editPayload = row.drawType === 'buffer' ? { points: row.points, meta: row.meta } : row.points
-	startEdit(row.drawType, editPayload)
-	applyPolygonMetrics(pointList)
-	showTypePanelAtCurrent()
-	renderShapeList()
-}
-
-function handleShapeDelete (row) {
-	if (!row) return
-	shapeList.value = shapeList.value.filter(item => item.id !== row.id)
-	renderShapeList()
-	if (activeShapeId.value === row.id) {
-		activeShapeId.value = null
-		clearActiveTool()
-		pointList = []
-		formData.value.areaSize = null
-		formData.value.longitude = null
-		formData.value.latitude = null
-		hideTypePanel()
 	}
 }
 
@@ -801,7 +393,7 @@
 	if (!formData.value?.geom) return
 	pointList = geomAnalysis(formData.value.geom)
 	const result = pointList.map(item => [item.longitude, item.latitude]).flat()
-	viewEntity = viewer.entities?.add({
+	const mian = viewer.entities?.add({
 		customType: 'control_group',
 		position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
 		polyline: {
@@ -811,7 +403,7 @@
 			material: Cesium.Color.RED,
 		},
 	})
-	viewer.flyTo(viewEntity, { duration: 0 })
+	viewer.flyTo(mian, { duration: 0 })
 }
 
 // 获取派出所列表
@@ -857,32 +449,14 @@
 	dialogMode.value = mode || 'add'
 	formData.value = dialogMode.value === 'add' ? initForm() : row
 	selectedDeviceRows.value = []
-	shapeList.value = []
-	activeShapeId.value = null
 	await nextTick()
 	initMap()
-	clearShapeDisplay()
-	viewEntity && viewer?.entities?.remove(viewEntity)
-	viewEntity = null
-	clearActiveTool()
 	await getDeviceList()
 	if (dialogMode.value === 'add') {
-		// default no draw mode
+		addPolygon()
 	} else if (dialogMode.value === 'edit') {
 		await loadDetail()
-		pointList = geomAnalysis(formData.value.geom) || []
-		if (pointList.length) {
-			activeShapeId.value = `shape_${Date.now()}_${Math.random().toString(16).slice(2, 6)}`
-			shapeList.value = [
-				{
-					id: activeShapeId.value,
-					areaType: formData.value.areaType,
-					drawType: currentShapeType.value,
-					points: _.cloneDeep(pointList),
-				},
-			]
-		}
-		viewPolygon()
+		editPolygon()
 	} else {
 		await loadDetail()
 		viewPolygon()
@@ -896,170 +470,9 @@
 	getDeviceList()
 })
 
-onBeforeUnmount(() => {
-	hideTypePanel()
-})
-
 defineExpose({
 	open,
 })
 </script>
 
-<style scoped lang="scss">
-.left-container {
-	position: relative;
-}
-
-.shape-type-panel {
-	position: absolute;
-	right: 16px;
-	bottom: 16px;
-	z-index: 6;
-	min-width: 260px;
-	background: rgba(22, 33, 44, 0.9);
-	border: 1px solid rgba(255, 255, 255, 0.15);
-	border-radius: 8px;
-	box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
-	backdrop-filter: blur(6px);
-	color: #e5e7f0;
-}
-
-.panel-header {
-	display: flex;
-	align-items: center;
-	justify-content: space-between;
-	padding: 8px 12px;
-	border-bottom: 1px solid rgba(255, 255, 255, 0.12);
-	font-size: 13px;
-	font-weight: 600;
-}
-
-.panel-close {
-	cursor: pointer;
-	color: rgba(255, 255, 255, 0.7);
-}
-
-.panel-body {
-	display: flex;
-	align-items: center;
-	gap: 8px;
-	padding: 10px 12px 12px;
-}
-
-.panel-label {
-	font-size: 12px;
-	color: rgba(255, 255, 255, 0.75);
-	white-space: nowrap;
-}
-
-.map-toolbar {
-	position: absolute;
-	top: 64px;
-	right: 16px;
-	display: flex;
-	flex-direction: column;
-	gap: 10px;
-	padding: 10px 8px;
-	background: rgba(19, 28, 36, 0.75);
-	backdrop-filter: blur(4px);
-	border-radius: 8px;
-	box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2);
-}
-
-.tool-btn {
-	display: flex;
-	flex-direction: column;
-	align-items: center;
-	gap: 6px;
-	width: 64px;
-	padding: 8px 6px;
-	background: rgba(255, 255, 255, 0.08);
-	border: 1px solid rgba(255, 255, 255, 0.18);
-	border-radius: 6px;
-	color: #ffffff;
-	font-size: 12px;
-	line-height: 1;
-	cursor: pointer;
-	transition: all 0.2s ease;
-}
-
-.tool-btn:hover {
-	background: rgba(255, 255, 255, 0.16);
-	border-color: rgba(255, 255, 255, 0.35);
-	transform: translateY(-1px);
-}
-
-.tool-btn:active {
-	transform: translateY(0);
-}
-
-.tool-btn.danger {
-	border-color: rgba(255, 112, 112, 0.6);
-	color: #ffb3b3;
-}
-
-.tool-icon {
-	position: relative;
-	width: 20px;
-	height: 20px;
-	border: 2px solid #ffffff;
-	border-radius: 3px;
-}
-
-.tool-icon.polygon {
-	border-radius: 4px;
-	transform: rotate(15deg);
-}
-
-.tool-icon.rect {
-	border-radius: 2px;
-}
-
-.tool-icon.ellipse {
-	border-radius: 50%;
-}
-
-.tool-icon.buffer {
-	border-radius: 50%;
-	box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
-}
-
-.tool-icon.trash {
-	border: none;
-	box-shadow: none;
-}
-
-.tool-icon.trash::before,
-.tool-icon.trash::after {
-	content: "";
-	position: absolute;
-	left: 4px;
-	right: 4px;
-	background: #ffffff;
-}
-
-.tool-icon.trash::before {
-	top: 4px;
-	height: 10px;
-	border-radius: 2px;
-}
-
-.tool-icon.trash::after {
-	top: 1px;
-	height: 3px;
-	border-radius: 2px;
-}
-
-.shape-table-container {
-	margin-top: 8px;
-	padding: 8px;
-	border-radius: 8px;
-	background: rgba(27, 34, 56, 0.6);
-}
-
-.shape-table-title {
-	margin-bottom: 8px;
-	color: #e5e7f0;
-	font-size: 13px;
-}
-</style>
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 302d14e..92c6ef8 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -1,24 +1,62 @@
 <template>
-	<el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false"
-		:close-on-click-modal="false">
+	<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">
-				<CommonCesiumMap
-					ref="mapRef"
-					class="leftMap command-cesium"
-					:active="visible"
-					:flat-mode="false"
-					:terrain="true"
-					:layer-mode="4"
-					:boundary="false"
-				/>
+				<CommonCesiumMap ref="mapRef" class="leftMap command-cesium" :active="visible" :flat-mode="false"
+					:terrain="true" :layer-mode="4" :boundary="false" />
+				<div v-if="showTypePanel && !readonly" class="shape-type-panel">
+					<div class="panel-header">
+						<span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span>
+						<el-icon class="panel-close" @click.stop="handleTypePanelClose">
+							<Close />
+						</el-icon>
+					</div>
+					<div class="panel-body">
+						<span class="panel-label">区域类型</span>
+						<el-select class="command-select" popper-class="command-select-popper" v-model="activeAreaType"
+							placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags :multiple="currentShapeType === 'buffer'"
+							@change="handleAreaTypeChange">
+							<el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue"
+								:value="item.dictKey" />
+						</el-select>
+					</div>
+				</div>
+				<div class="map-toolbar">
+					<el-tooltip content="多边形" placement="left">
+						<button class="tool-btn" type="button" @click="handleDraw('polygon')">
+							<img class="tool-icon-image" :src="polygonIcon" alt="多边形" />
+						</button>
+					</el-tooltip>
+					<el-tooltip content="矩形" placement="left">
+						<button class="tool-btn" type="button" @click="handleDraw('rectangle')">
+							<img class="tool-icon-image" :src="rectIcon" alt="矩形" />
+						</button>
+					</el-tooltip>
+					<el-tooltip content="椭圆" placement="left">
+						<button class="tool-btn" type="button" @click="handleDraw('ellipse')">
+							<img class="tool-icon-image" :src="ellipseIcon" alt="椭圆" />
+						</button>
+					</el-tooltip>
+					<el-tooltip content="缓冲圆" placement="left">
+						<button class="tool-btn" type="button" @click="handleDraw('buffer')">
+							<img class="tool-icon-image" :src="bufferIcon" alt="缓冲圆" />
+						</button>
+					</el-tooltip>
+					<el-tooltip content="删除" placement="left">
+						<button class="tool-btn danger" type="button" @click="handleClearShape">
+							<img class="tool-icon-image" :src="trashIcon" alt="删除" />
+						</button>
+					</el-tooltip>
+				</div>
 			</div>
 
 			<div class="right-container">
 				<div class="header">
 					<span>{{ titleEnum[dialogMode] }}</span>
-				
-					<el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
+
+					<el-icon class="close-btn" @click.stop="visible = false">
+						<Close />
+					</el-icon>
 				</div>
 
 				<div class="content" v-if="readonly">
@@ -27,18 +65,6 @@
 						<el-col :span="24">
 							<div class="label">区域名称</div>
 							<div class="val">{{ formData.areaName }}</div>
-						</el-col>
-						<el-col :span="24">
-							<div class="label">区域位置</div>
-							<div class="val">{{ formatLocation(formData) }}</div>
-						</el-col>
-						<el-col :span="24">
-							<div class="label">区域面积</div>
-							<div class="val">{{ formData.areaSize || '-' }}k㎡</div>
-						</el-col>
-						<el-col :span="24">
-							<div class="label">区域类型</div>
-							<div class="val">{{ getDictLabel(formData.areaType, dictObj.areaType) }}</div>
 						</el-col>
 						<el-col :span="24">
 							<div class="label">触发条件</div>
@@ -64,11 +90,29 @@
 						</el-col>
 					</el-row>
 
+					<div class="detail-title">绘制区域列表</div>
+					<div class="command-table-container">
+						<div class="command-table-content">
+							<el-table class="command-table" :data="shapeList" row-key="id">
+								<el-table-column prop="areaType" label="区域类型">
+									<template v-slot="{ row }">
+										{{ resolveShapeAreaTypeLabel(row) }}
+									</template>
+								</el-table-column>
+								<el-table-column prop="drawType" label="绘制类型">
+									<template v-slot="{ row }">
+										{{ drawTypeLabelMap[row.drawType] || '-' }}
+									</template>
+								</el-table-column>
+							</el-table>
+						</div>
+					</div>
+
 					<div class="detail-title">关联设备</div>
 					<div class="command-table-container">
 						<div class="command-table-content">
-							<el-table class="command-table" ref="deviceTableRef" :data="deviceOptions"
-								row-key="id">
+							<el-table class="command-table" ref="deviceTableRef" :data="deviceOptions" row-key="id"
+								@row-click="handleDeviceRowClick">
 								<el-table-column prop="deviceName" label="设备名称" />
 								<el-table-column prop="deviceType" label="类型">
 									<template v-slot="{ row }">
@@ -83,25 +127,12 @@
 				<el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly"
 					label-width="96px">
 					<el-form-item label="区域名称" prop="areaName">
-						<el-input class="command-input" v-model="formData.areaName" maxlength="50"
-							placeholder="请输入" clearable />
-					</el-form-item>
-					<el-form-item label="区域位置" prop="longitude">
-						<div class="val">{{ formatLocation(formData) }}</div>
-					</el-form-item>
-					<el-form-item label="区域面积" prop="areaSize">
-						<div class="val">{{ formData.areaSize || '-' }}k㎡</div>
-					</el-form-item>
-					<el-form-item label="区域类型" prop="areaType">
-						<el-select class="command-select" popper-class="command-select-popper"
-							v-model="formData.areaType" placeholder="请选择" clearable>
-							<el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue"
-								:value="item.dictKey" />
-						</el-select>
+						<el-input class="command-input" v-model="formData.areaName" maxlength="50" placeholder="请输入"
+							clearable />
 					</el-form-item>
 					<el-form-item label="响应机制" prop="responseMechanism">
-						<el-input class="command-input" v-model="formData.responseMechanism"
-							maxlength="200" placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.responseMechanism" maxlength="200"
+							placeholder="请输入" clearable />
 					</el-form-item>
 					<el-form-item label="触发条件" prop="triggerCondition">
 						<el-select class="command-select" popper-class="command-select-popper"
@@ -118,10 +149,9 @@
 						</el-select>
 					</el-form-item>
 					<el-form-item label="可飞行时段" prop="flyDateStart">
-						<el-date-picker class="command-date-picker"
-							popper-class="command-date-picker-popper" v-model="flyDateRange"
-							type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"
-							:disabled-date="disabledFlyDate" :disabled-time="disabledFlyTime"
+						<el-date-picker class="command-date-picker" popper-class="command-date-picker-popper"
+							v-model="flyDateRange" type="datetimerange" range-separator="至" start-placeholder="开始时间"
+							end-placeholder="结束时间" :disabled-date="disabledFlyDate" :disabled-time="disabledFlyTime"
 							value-format="YYYY-MM-DD HH:mm:ss" clearable />
 					</el-form-item>
 					<el-form-item label="关联派出所" prop="policeStationId">
@@ -132,20 +162,41 @@
 						</el-select>
 					</el-form-item>
 
+					<div class="shape-table-container">
+						<div class="shape-table-title">绘制区域列表</div>
+						<el-table class="command-table" :data="shapeList" row-key="id" height="auto">
+							<el-table-column prop="areaType" label="区域类型">
+								<template v-slot="{ row }">
+									{{ resolveShapeAreaTypeLabel(row) }}
+								</template>
+							</el-table-column>
+							<el-table-column prop="drawType" label="绘制类型">
+								<template v-slot="{ row }">
+									{{ drawTypeLabelMap[row.drawType] || '-' }}
+								</template>
+							</el-table-column>
+							<el-table-column label="操作" class-name="operation-btns" width="120">
+								<template v-slot="{ row }">
+									<el-link @click="handleShapeEdit(row)">编辑</el-link>
+									<el-link @click="handleShapeDelete(row)">删除</el-link>
+								</template>
+							</el-table-column>
+						</el-table>
+					</div>
+
 					<div class="search-table-container">
 						<div class="search-box">
 							<div class="label">
 								关联设备
 							</div>
 
-							<el-input class="command-input" v-model="searchName" placeholder="请输入"
-								clearable></el-input>
+							<el-input class="command-input" v-model="searchName" placeholder="请输入" clearable></el-input>
 						</div>
 
 						<el-form-item prop="deviceIds" label-width="0">
 							<el-table class="command-table" ref="deviceTableRef"
 								:data="deviceOptions.filter(item => item.deviceName.includes(searchName))" row-key="id"
-								@selection-change="handleDeviceSelectionChange">
+								@selection-change="handleDeviceSelectionChange" @row-click="handleDeviceRowClick">
 								<el-table-column type="selection" width="55" :reserve-selection="true" />
 								<el-table-column prop="deviceName" label="设备名称" />
 								<el-table-column prop="deviceType" label="类型">
@@ -159,7 +210,9 @@
 				</el-form>
 
 				<div class="footer">
-					<el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+					<el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' :
+						'取消'
+					}}</el-button>
 					<el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
 						:disabled="submitting" @click="handleSubmit">
 						确定
@@ -173,30 +226,37 @@
 <script setup>
 import { Close } from '@element-plus/icons-vue'
 
-import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
+import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
 import { ElMessage } from 'element-plus'
 import { fwAreaDivideDetailApi, fwAreaDivideSubmitApi } from './partitionApi'
 import { saveOperationLog } from '@ztzf/apis'
 import { useRoute } from 'vue-router'
 import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
 import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
-import { DrawPolygon } from '@/utils/cesium/DrawPolygon'
+import { DrawManager, EditManager, buildEllipsePositions } from '@/utils/cesium/shapeTools'
 import { cartesian3Convert } from '@/utils/cesium/mapUtil'
-import * as turf from '@turf/turf'
+import {
+	createDeviceRangePrimitive,
+	getDeviceLngLat,
+	normalizeDeviceRange,
+	resolveDeviceTerrainHeight,
+} from '@/utils/cesium/deviceRange'
 import * as Cesium from 'cesium'
+import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
 import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
 import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice'
+import polygonIcon from '@/assets/images/areaMap/polygon.png'
+import rectIcon from '@/assets/images/areaMap/rect.png'
+import ellipseIcon from '@/assets/images/areaMap/ellipse.png'
+import bufferIcon from '@/assets/images/areaMap/buffer.png'
+import trashIcon from '@/assets/images/areaMap/trash.png'
 
 const initForm = () => ({
 	areaName: '', // 区域名称
-	areaSize: null, // 区域面积
-	areaType: '', // 区域类型
 	controlLevel: '', // 管控级别
 	deviceIds: '', // 关联设备ID
 	flyDateEnd: '', // 可飞行结束
 	flyDateStart: '', // 可飞行开始
-	latitude: null, // 区域中心纬度
-	longitude: null, // 区域中心经度
 	policeStationId: '', // 关联派出所id
 	responseMechanism: '', // 响应机制
 	triggerCondition: '', // 触发条件
@@ -219,14 +279,31 @@
 const deviceOptions = ref([]) // 关联设备
 const dictObj = inject('dictObj')
 const mapRef = ref(null)
+const currentShapeType = ref('polygon')
+const activeAreaType = ref('')
+const shapeList = ref([])
+const activeShapeId = ref(null)
+const drawTypeLabelMap = {
+	polygon: '多边形',
+	rectangle: '矩形',
+	ellipse: '椭圆',
+	buffer: '缓冲圆',
+}
+const bufferAreaTypeValue = '1,2,3'
+const showTypePanel = ref(false)
+const suppressTypePanelOnce = ref(false)
 let viewer
-let drawPolygonExample
+let drawManager
+let editManager
+let activeTool
 let pointList = []
+let viewEntity
+let shapeDisplaySource
+const deviceRangePrimitiveMap = new Map()
+const activeToolMode = ref('')
 
 const rules = {
 	areaName: fieldRules(true, 50),
-	longitude: fieldRules(true),
-	areaType: fieldRules(true),
 	responseMechanism: fieldRules(true, 200),
 	triggerCondition: fieldRules(true, 200),
 	controlLevel: fieldRules(true),
@@ -274,17 +351,33 @@
 
 // 提交新增/编辑
 async function handleSubmit () {
-	const isValid = await formRef.value?.validate().catch(() => false)
-	if (!isValid) return
-	if (dialogMode.value === 'add' && Number(formData.value.areaSize) < 0.5) {
-		ElMessage.warning('区域面积不能小于0.5km²,请重新绘制')
+	if (!shapeList.value.length) {
+		ElMessage.warning('区域不可为空,请先绘制区域')
 		return
 	}
+
+	const isValid = await formRef.value?.validate().catch(() => false)
+	if (!isValid) return
 	submitting.value = true
 	try {
-		let str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
-		formData.value.geom = `POLYGON((${str}))`
-		await fwAreaDivideSubmitApi(formData.value)
+		const areaDivide = { ...formData.value }
+		delete areaDivide.geom
+		const areaDivideExtList = shapeList.value.map(shape => {
+			const areaTypeKey = shape?.areaType ?? ''
+			const areaTypeValue = getDictLabel(areaTypeKey, resolveAreaTypeOptions())
+			return {
+				id: shape?.extId,
+				areaDivideId: areaDivide?.id ?? '',
+				areaCode: areaDivide?.areaCode ?? '',
+				areaTypeKey,
+				areaTypeValue,
+				geomJson: JSON.stringify(shape),
+			}
+		})
+		await fwAreaDivideSubmitApi({
+			areaDivide,
+			areaDivideExtList,
+		})
 		const actionText = dialogMode.value === 'add' ? '新增' : '编辑'
 		saveOperationLog({
 			requestUri: route.path,
@@ -306,19 +399,43 @@
 		formData.value.flyDateEnd = flyDateRange.value[1] || ''
 	}
 )
+watch(
+	() => visible.value,
+	val => {
+		if (!val) {
+			viewEntity && viewer?.entities?.remove(viewEntity)
+			viewEntity = null
+			clearActiveTool()
+			hideTypePanel()
+			clearShapeDisplay()
+			clearDeviceRangePrimitives()
+		}
+	}
+)
+watch(
+	() => activeAreaType.value,
+	val => {
+		updateActiveShapeAreaType(val)
+	}
+)
 
 // 加载详情
 async function loadDetail () {
 	if (!formData.value.id) return
 	const res = await fwAreaDivideDetailApi({ id: formData.value.id })
-	formData.value = res?.data?.data ?? initForm()
+	const detail = res?.data?.data ?? {}
+	const nextForm = initForm()
+	Object.keys(nextForm).forEach(key => {
+		if (detail[key] !== undefined) {
+			nextForm[key] = detail[key]
+		}
+	})
+	if (detail?.id) {
+		nextForm.id = detail.id
+	}
+	formData.value = nextForm
 	flyDateRange.value = [formData.value.flyDateStart, formData.value.flyDateEnd].filter(Boolean)
-}
-
-// 格式化区域位置
-function formatLocation (row) {
-	if (row?.longitude == null || row?.latitude == null) return ''
-	return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
+	syncShapeListFromDetail(detail)
 }
 
 // 格式化可飞行时间段
@@ -334,66 +451,531 @@
 	viewer = map?.viewer || null
 }
 
+function ensureShapeDisplaySource () {
+	if (!viewer) return null
+	if (!shapeDisplaySource) {
+		shapeDisplaySource = new Cesium.CustomDataSource('partition-shape-display')
+		viewer.dataSources.add(shapeDisplaySource)
+	}
+	return shapeDisplaySource
+}
+
+function clearDeviceRangePrimitives () {
+	if (!viewer || !deviceRangePrimitiveMap.size) return
+	for (const primitive of deviceRangePrimitiveMap.values()) {
+		viewer.scene.primitives.remove(primitive)
+	}
+	deviceRangePrimitiveMap.clear()
+}
+
+async function renderDeviceRangeSingle (device) {
+	if (!viewer) return
+	clearDeviceRangePrimitives()
+	const position = getDeviceLngLat(device)
+	if (!position) return
+	const rangeMeters = normalizeDeviceRange(device.effectiveRangeKm)
+	const primitive = await createDeviceRangePrimitive(viewer, position, rangeMeters)
+	if (!primitive) return
+	deviceRangePrimitiveMap.set(String(device?.id ?? Date.now()), primitive)
+	viewer.scene.primitives.add(primitive)
+}
+
+async function renderDeviceRanges (rows) {
+	if (!viewer) return
+	const selectedRows = Array.isArray(rows) ? rows : []
+	const nextIds = new Set(selectedRows.map(row => String(row?.id)))
+	for (const [id, primitive] of deviceRangePrimitiveMap.entries()) {
+		if (!nextIds.has(id)) {
+			viewer.scene.primitives.remove(primitive)
+			deviceRangePrimitiveMap.delete(id)
+		}
+	}
+	const pendingRows = selectedRows.filter(row => {
+		const id = String(row?.id)
+		return id && !deviceRangePrimitiveMap.has(id)
+	})
+	if (!pendingRows.length) return
+	const primitives = await Promise.all(
+		pendingRows.map(async row => {
+			const position = getDeviceLngLat(row)
+			if (!position) return { row, primitive: null }
+			const rangeMeters = normalizeDeviceRange(row.effectiveRangeKm)
+			const primitive = await createDeviceRangePrimitive(viewer, position, rangeMeters)
+			return { row, primitive }
+		})
+	)
+	primitives.forEach(({ row, primitive }) => {
+		if (!primitive) return
+		const id = String(row?.id)
+		deviceRangePrimitiveMap.set(id, primitive)
+		viewer.scene.primitives.add(primitive)
+	})
+}
+
+async function flyToDevice (device) {
+	if (!viewer) return
+	const position = getDeviceLngLat(device)
+	if (!position) return
+	const height = await resolveDeviceTerrainHeight(viewer, position)
+	const rangeMeters = normalizeDeviceRange(device?.effectiveRangeKm)
+	const destination = Cesium.Cartesian3.fromDegrees(position.lng, position.lat, height + rangeMeters * 3)
+	viewer.camera.flyTo({
+		destination,
+		orientation: {
+			heading: viewer.camera.heading,
+			pitch: Cesium.Math.toRadians(-90),
+			roll: 0,
+		},
+		duration: 0.6,
+	})
+}
+
+function clearShapeDisplay () {
+	if (!shapeDisplaySource) return
+	shapeDisplaySource.entities.removeAll()
+}
+
+function normalizeShapePoint (point) {
+	const lng = point?.lng ?? point?.longitude
+	const lat = point?.lat ?? point?.latitude
+	if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
+	return {
+		lng: Number(lng),
+		lat: Number(lat),
+		height: Number.isFinite(Number(point?.height)) ? Number(point.height) : 0,
+	}
+}
+
+function resolveLngLatPoint (point) {
+	if (!point) return null
+	const lng = point?.lng ?? point?.longitude
+	const lat = point?.lat ?? point?.latitude
+	if (Number.isFinite(Number(lng)) && Number.isFinite(Number(lat))) {
+		return {
+			lng: Number(lng),
+			lat: Number(lat),
+			height: Number.isFinite(Number(point?.height)) ? Number(point.height) : 0,
+		}
+	}
+	if (!viewer || !Number.isFinite(point?.x) || !Number.isFinite(point?.y) || !Number.isFinite(point?.z)) {
+		return null
+	}
+	const val = cartesian3Convert(point, viewer)
+	return {
+		lng: val.longitude,
+		lat: val.latitude,
+		height: Number.isFinite(val.height) ? val.height : 0,
+	}
+}
+
+function resolveControlPoints (meta, drawType) {
+	if (!meta) return []
+	if (Array.isArray(meta?.controlPoints)) {
+		return meta.controlPoints.map(resolveLngLatPoint).filter(Boolean)
+	}
+	if (drawType === 'ellipse') {
+		return [meta.center, meta.majorPoint, meta.minorPoint].map(resolveLngLatPoint).filter(Boolean)
+	}
+	if (drawType === 'buffer') {
+		return [meta.center, meta.radiusPoint1, meta.radiusPoint2, meta.radiusPoint3]
+			.map(resolveLngLatPoint)
+			.filter(Boolean)
+	}
+	return []
+}
+
+function buildShapePositions (points = []) {
+	const normalized = points.map(normalizeShapePoint).filter(Boolean)
+	return normalized.map(point =>
+		Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.height)
+	)
+}
+
+function getShapeDisplayPoints (shape) {
+	if (Array.isArray(shape?.displayPoints) && shape.displayPoints.length) {
+		return shape.displayPoints
+	}
+	return shape?.points || []
+}
+
+function getAreaTypeStyle (areaType) {
+	return AREA_TYPE_STYLE_MAP?.[String(areaType)] || DEFAULT_AREA_STYLE
+}
+
+function renderShapeList () {
+	if (!viewer || !visible.value) {
+		clearShapeDisplay()
+		return
+	}
+	const dataSource = ensureShapeDisplaySource()
+	if (!dataSource) return
+	dataSource.entities.removeAll()
+	shapeList.value.forEach(shape => {
+		if (activeToolMode.value === 'edit' && shape.id === activeShapeId.value) return
+		if (shape.drawType === 'buffer' && shape.meta?.bufferRadii?.length && shape.meta?.center) {
+			const center = shape.meta.center
+			const centerCartesian = Cesium.Cartesian3.fromDegrees(center.lng, center.lat, center.height || 0)
+			const [radius1, radius2, radius3] = shape.meta.bufferRadii
+			const radii = [radius1, radius2, radius3].filter(item => Number.isFinite(item) && item > 0)
+			if (radii.length) {
+				radii.forEach((radius, index) => {
+					const style = BUFFER_LEVEL_STYLES[index] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
+					dataSource.entities.add({
+						name: 'shape-display',
+						customData: { shapeId: shape.id, drawType: shape.drawType, level: index + 1 },
+						position: centerCartesian,
+						ellipse: {
+							semiMajorAxis: radius,
+							semiMinorAxis: radius,
+							material: style.fill,
+							outline: false,
+							heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+						},
+					})
+					const positions = buildEllipsePositions(centerCartesian, radius, radius)
+					dataSource.entities.add({
+						name: 'shape-display',
+						customData: { shapeId: shape.id, drawType: shape.drawType, level: index + 1, outline: true },
+						polyline: {
+							positions: positions.length ? [...positions, positions[0]] : positions,
+							clampToGround: true,
+							width: 2,
+							material: style.outline,
+						},
+					})
+				})
+			}
+			return
+		}
+
+		if (!shape?.points?.length) return
+		const positions = buildShapePositions(getShapeDisplayPoints(shape))
+		if (positions.length < 3) return
+		const style = getAreaTypeStyle(shape.areaType)
+		dataSource.entities.add({
+			name: 'shape-display',
+			customData: { shapeId: shape.id, drawType: shape.drawType },
+			polygon: {
+				hierarchy: new Cesium.PolygonHierarchy(positions),
+				material: style.fill,
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+			polyline: {
+				positions: positions.length ? [...positions, positions[0]] : positions,
+				clampToGround: true,
+				width: 2,
+				material: style.outline,
+			},
+		})
+	})
+}
+
 // 绘制完成回调
 const drawFinished = async data => {
-	pointList = _.cloneDeep(data).map(item => {
+	const positions = Array.isArray(data?.positions) ? data.positions : data
+	if (!Array.isArray(positions) || positions.length < 3) {
+		pointList = []
+		hideTypePanel()
+		return
+	}
+	const meta = data?.meta
+	const bufferRadii = Array.isArray(meta?.bufferRadii) ? meta.bufferRadii : null
+	const centerCartesian = meta?.center
+	const centerLngLat = centerCartesian ? cartesian3Convert(centerCartesian, viewer) : null
+	pointList = _.cloneDeep(positions).map(item => {
 		const val = cartesian3Convert(item, viewer)
 		return { ...val, lng: val.longitude, lat: val.latitude }
 	})
-	const polygon = turf.polygon([
-		[...pointList.map(item => [item.longitude, item.latitude]), [pointList[0].longitude, pointList[0].latitude]],
-	])
-	const center = turf.centerOfMass(polygon)
-	const areaSqm = turf.area(polygon) // 平方米
-	formData.value.areaSize = _.round(areaSqm / 1_000_000, 2) // 平方千米
-	formData.value.longitude = center.geometry.coordinates[0]
-	formData.value.latitude = center.geometry.coordinates[1]
-}
-
-// 新增模式绘制
-function addPolygon () {
-	drawPolygonExample = new DrawPolygon(viewer)
-	drawPolygonExample.initHandler(viewer)
-	drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
-}
-
-// 编辑面
-function editPolygon () {
-	if (!formData.value?.geom) return
-	pointList = geomAnalysis(formData.value.geom)
-	drawPolygonExample?.destroy()
-	drawPolygonExample = new DrawPolygon(viewer)
-	let pointList1 = _.cloneDeep(pointList)
-	pointList1.pop()
-	drawPolygonExample.initPolygon(
-		viewer,
-		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)
+	const controlPoints = resolveControlPoints(meta, currentShapeType.value)
+	if (!activeAreaType.value) {
+		const detectAreaType = getDetectAreaTypeKey()
+		if (detectAreaType) {
+			activeAreaType.value = detectAreaType
 		}
+	}
+	if (currentShapeType.value === 'buffer') {
+		activeAreaType.value = bufferAreaTypeValue.split(',')
+	}
+	if (!suppressTypePanelOnce.value) {
+		showTypePanelAtCurrent()
+	}
+	suppressTypePanelOnce.value = false
+
+	if (!activeShapeId.value) {
+		activeShapeId.value = `shape_${Date.now()}_${Math.random().toString(16).slice(2, 6)}`
+	}
+	const targetIndex = shapeList.value.findIndex(item => item.id === activeShapeId.value)
+	const displayPoints = ['ellipse', 'buffer'].includes(currentShapeType.value)
+		? _.cloneDeep(pointList)
+		: null
+	let shapeMeta = null
+	if (currentShapeType.value === 'buffer' && bufferRadii?.length && centerLngLat) {
+		shapeMeta = {
+			bufferRadii,
+			center: { lng: centerLngLat.longitude, lat: centerLngLat.latitude, height: centerLngLat.height ?? 0 },
+		}
+		if (controlPoints.length) {
+			shapeMeta.controlPoints = controlPoints
+		}
+	} else if (currentShapeType.value === 'ellipse') {
+		const centerPoint = resolveLngLatPoint(meta?.center)
+		const majorPoint = resolveLngLatPoint(meta?.majorPoint)
+		const minorPoint = resolveLngLatPoint(meta?.minorPoint)
+		if (centerPoint || majorPoint || minorPoint) {
+			shapeMeta = {
+				center: centerPoint,
+				majorPoint,
+				minorPoint,
+				semiMajor: Number.isFinite(Number(meta?.semiMajor)) ? Number(meta.semiMajor) : null,
+				semiMinor: Number.isFinite(Number(meta?.semiMinor)) ? Number(meta.semiMinor) : null,
+			}
+			if (controlPoints.length) {
+				shapeMeta.controlPoints = controlPoints
+			}
+		}
+	}
+	const shapePayload = {
+		id: activeShapeId.value,
+		areaType: currentShapeType.value === 'buffer' ? bufferAreaTypeValue : activeAreaType.value,
+		drawType: currentShapeType.value,
+		points: ['ellipse', 'buffer'].includes(currentShapeType.value) && controlPoints.length
+			? controlPoints
+			: _.cloneDeep(pointList),
+		displayPoints,
+		meta: shapeMeta,
+	}
+	if (targetIndex === -1) {
+		shapeList.value.push(shapePayload)
+	} else {
+		shapeList.value.splice(targetIndex, 1, shapePayload)
+	}
+	renderShapeList()
+}
+
+function getDetectAreaTypeKey () {
+	const areaTypeOptions = resolveAreaTypeOptions()
+	if (!Array.isArray(areaTypeOptions)) return ''
+	const target = areaTypeOptions.find(item => item?.dictKey === '1')
+	return target?.dictKey ?? ''
+}
+
+function resolveAreaTypeOptions () {
+	return Array.isArray(dictObj?.value?.areaType)
+		? dictObj.value.areaType
+		: dictObj?.areaType
+}
+
+function resolveShapeAreaTypeLabel (shape) {
+	return getDictLabel(shape?.areaType, resolveAreaTypeOptions())
+}
+
+function parseGeomJson (geomJson) {
+	if (!geomJson) return null
+	if (typeof geomJson === 'object') return geomJson
+	if (typeof geomJson !== 'string') return null
+	const trimmed = geomJson.trim()
+	if (!trimmed) return null
+	try {
+		return JSON.parse(trimmed)
+	} catch (error) {
+		const legacyPoints = geomAnalysis(trimmed)
+		if (Array.isArray(legacyPoints) && legacyPoints.length >= 3) {
+			return { drawType: 'polygon', points: legacyPoints }
+		}
+	}
+	return null
+}
+
+function syncShapeListFromDetail (detail) {
+	const extList = Array.isArray(detail?.fwAreaDivideExtList) ? detail.fwAreaDivideExtList : []
+	if (!extList.length) {
+		shapeList.value = []
+		activeAreaType.value = ''
+		currentShapeType.value = 'polygon'
+		return
+	}
+	shapeList.value = extList.map((item, index) => {
+		const isShapePayload = item?.drawType || item?.points
+		if (isShapePayload) {
+			const areaType = item?.drawType === 'buffer'
+				? bufferAreaTypeValue
+				: (item?.areaType ?? '')
+			return {
+				id: item?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
+				extId: item?.extId ?? item?.id,
+				areaType,
+				drawType: item?.drawType ?? 'polygon',
+				points: Array.isArray(item?.points) ? item.points : [],
+				displayPoints: Array.isArray(item?.displayPoints) ? item.displayPoints : null,
+				meta: item?.meta ?? null,
+			}
+		}
+		const parsed = parseGeomJson(item?.geomJson) || {}
+		const drawType = parsed?.drawType || 'polygon'
+		const areaType = drawType === 'buffer'
+			? bufferAreaTypeValue
+			: (item?.areaTypeKey ?? parsed?.areaType ?? '')
+		const points = Array.isArray(parsed?.points) ? parsed.points : []
+		const displayPoints = Array.isArray(parsed?.displayPoints) ? parsed.displayPoints : null
+		const meta = parsed?.meta ?? null
+		return {
+			id: parsed?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
+			extId: item?.id,
+			areaType,
+			drawType,
+			points,
+			displayPoints,
+			meta,
+		}
+	})
+	activeAreaType.value = shapeList.value[0]?.drawType === 'buffer'
+		? bufferAreaTypeValue.split(',')
+		: (shapeList.value[0]?.areaType ?? '')
+	currentShapeType.value = shapeList.value[0]?.drawType ?? 'polygon'
+}
+
+function showTypePanelAtCurrent () {
+	if (readonly.value) return
+	showTypePanel.value = true
+}
+
+function hideTypePanel () {
+	showTypePanel.value = false
+}
+
+function handleTypePanelClose () {
+	hideTypePanel()
+	if (readonly.value) return
+	if (activeToolMode.value !== 'edit') return
+	const positions = activeTool?.getPositions?.()
+	if (Array.isArray(positions) && positions.length >= 3) {
+		suppressTypePanelOnce.value = true
+		drawFinished(positions)
+	}
+	if (positions?.positions?.length >= 3) {
+		suppressTypePanelOnce.value = true
+		drawFinished(positions)
+	}
+	clearActiveTool()
+}
+
+function handleAreaTypeChange (value) {
+	updateActiveShapeAreaType(value)
+}
+
+function updateActiveShapeAreaType (value) {
+	if (currentShapeType.value === 'buffer') return
+	activeTool?.setStyle?.(getAreaTypeStyle(value))
+	if (!activeShapeId.value) return
+	const targetIndex = shapeList.value.findIndex(item => item.id === activeShapeId.value)
+	if (targetIndex === -1) return
+	const target = shapeList.value[targetIndex]
+	shapeList.value.splice(targetIndex, 1, {
+		...target,
+		areaType: value,
+	})
+	renderShapeList()
+}
+
+// Tool lifecycle
+function clearActiveTool () {
+	activeTool?.destroy?.()
+	activeTool = null
+	drawManager?.destroy()
+	editManager?.destroy()
+	activeToolMode.value = ''
+	renderShapeList()
+}
+
+function startDraw (type) {
+	currentShapeType.value = type
+	activeToolMode.value = 'draw'
+	drawManager ||= new DrawManager(viewer)
+	activeTool = drawManager.start(type, { style: getAreaTypeStyle(activeAreaType.value) })
+	activeTool?.subscribe('getPolygonPositions', drawFinished)
+}
+
+function startEdit (type, points) {
+	currentShapeType.value = type
+	activeToolMode.value = 'edit'
+	editManager ||= new EditManager(viewer)
+	activeTool = editManager.start(type, points, { style: getAreaTypeStyle(activeAreaType.value) })
+	activeTool?.subscribe('getPolygonPositions', drawFinished)
+}
+
+function handleDraw (type) {
+	if (readonly.value) return
+	if (!viewer) return
+	clearActiveTool()
+	viewEntity && viewer?.entities?.remove(viewEntity)
+	viewEntity = null
+	hideTypePanel()
+	renderShapeList()
+	pointList = []
+	const defaultAreaType = getDetectAreaTypeKey()
+	if (type === 'buffer') {
+		activeAreaType.value = bufferAreaTypeValue
+	} else if (defaultAreaType) {
+		activeAreaType.value = defaultAreaType
+	}
+	activeShapeId.value = null
+	startDraw(type)
+}
+
+function handleClearShape () {
+	if (readonly.value) return
+	clearActiveTool()
+	viewEntity && viewer?.entities?.remove(viewEntity)
+	viewEntity = null
+	pointList = []
+	shapeList.value = []
+	activeShapeId.value = null
+	activeAreaType.value = ''
+	hideTypePanel()
+	clearShapeDisplay()
+}
+
+function handleShapeEdit (row) {
+	if (!row?.points?.length) return
+	if (activeToolMode.value === 'edit' && activeShapeId.value === row.id) return
+	activeShapeId.value = row.id
+	currentShapeType.value = row.drawType
+	activeAreaType.value = row.drawType === 'buffer' ? bufferAreaTypeValue.split(',') : row.areaType
+	const displayPoints = getShapeDisplayPoints(row)
+	pointList = _.cloneDeep(displayPoints)
+	clearActiveTool()
+	const editPayload = ['buffer', 'ellipse'].includes(row.drawType)
+		? { points: displayPoints, meta: row.meta }
+		: row.points
+	startEdit(row.drawType, editPayload)
+	showTypePanelAtCurrent()
+	renderShapeList()
+}
+
+function handleShapeDelete (row) {
+	if (!row) return
+	shapeList.value = shapeList.value.filter(item => item.id !== row.id)
+	renderShapeList()
+	if (activeShapeId.value === row.id) {
+		activeShapeId.value = null
+		clearActiveTool()
+		pointList = []
+		hideTypePanel()
 	}
 }
 
 // 查看面
 function viewPolygon () {
+	if (!viewer) return
+	if (shapeList.value.length) {
+		renderShapeList()
+		return
+	}
 	if (!formData.value?.geom) return
 	pointList = geomAnalysis(formData.value.geom)
 	const result = pointList.map(item => [item.longitude, item.latitude]).flat()
-	const mian = viewer.entities?.add({
+	viewEntity = viewer.entities?.add({
 		customType: 'control_group',
 		position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
 		polyline: {
@@ -403,7 +985,7 @@
 			material: Cesium.Color.RED,
 		},
 	})
-	viewer.flyTo(mian, { duration: 0 })
+	viewer.flyTo(viewEntity, { duration: 0 })
 }
 
 // 获取派出所列表
@@ -427,21 +1009,42 @@
 	selectedDeviceRows.value = rows
 	const ids = rows.map(item => item.id)
 	formData.value.deviceIds = ids.join(',')
+	renderDeviceRanges(rows)
+	const target = rows[rows.length - 1]
+	if (target) {
+		void flyToDevice(target)
+	}
+}
+
+function handleDeviceRowClick (row) {
+	if (readonly.value) {
+		renderDeviceRangeSingle(row)
+		void flyToDevice(row)
+		return
+	}
+	renderDeviceRanges(selectedDeviceRows.value)
+	void flyToDevice(row)
 }
 
 // 同步选择状态
 function syncDeviceSelection () {
-	if (!deviceTableRef.value) return
-	deviceTableRef.value.clearSelection()
 	const rows = []
 	const arr = formData.value.deviceIds.split(',')
 	deviceOptions.value.forEach(row => {
 		if (arr.includes(row.id)) {
-			deviceTableRef.value.toggleRowSelection(row, true)
 			rows.push(row)
 		}
 	})
 	selectedDeviceRows.value = rows
+	if (readonly.value) {
+		renderDeviceRanges(selectedDeviceRows.value)
+		return
+	}
+	if (!deviceTableRef.value) return
+	deviceTableRef.value.clearSelection()
+	rows.forEach(row => {
+		deviceTableRef.value.toggleRowSelection(row, true)
+	})
 }
 
 // 打开弹框
@@ -449,25 +1052,40 @@
 	dialogMode.value = mode || 'add'
 	formData.value = dialogMode.value === 'add' ? initForm() : row
 	selectedDeviceRows.value = []
+	shapeList.value = []
+	activeShapeId.value = null
+	activeAreaType.value = ''
+	pointList = []
 	await nextTick()
 	initMap()
+	clearShapeDisplay()
+	clearDeviceRangePrimitives()
+	viewEntity && viewer?.entities?.remove(viewEntity)
+	viewEntity = null
+	clearActiveTool()
 	await getDeviceList()
 	if (dialogMode.value === 'add') {
-		addPolygon()
+		// default no draw mode
 	} else if (dialogMode.value === 'edit') {
 		await loadDetail()
-		editPolygon()
+		viewPolygon()
 	} else {
 		await loadDetail()
 		viewPolygon()
 	}
 	await nextTick()
 	syncDeviceSelection()
+	if (!readonly.value) {
+		renderDeviceRanges(selectedDeviceRows.value)
+	}
 }
 
 onMounted(() => {
 	getPoliceStationList()
-	getDeviceList()
+})
+
+onBeforeUnmount(() => {
+	hideTypePanel()
 })
 
 defineExpose({
@@ -475,4 +1093,168 @@
 })
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.left-container {
+	position: relative;
+}
+
+.shape-type-panel {
+	position: absolute;
+	right: 16px;
+	bottom: 16px;
+	z-index: 6;
+	min-width: 260px;
+	background: rgba(22, 33, 44, 0.9);
+	border: 1px solid rgba(255, 255, 255, 0.15);
+	border-radius: 8px;
+	box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
+	backdrop-filter: blur(6px);
+	color: #e5e7f0;
+}
+
+.panel-header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 8px 12px;
+	border-bottom: 1px solid rgba(255, 255, 255, 0.12);
+	font-size: 13px;
+	font-weight: 600;
+}
+
+.panel-close {
+	cursor: pointer;
+	color: rgba(255, 255, 255, 0.7);
+}
+
+.panel-body {
+	display: flex;
+	align-items: center;
+	gap: 8px;
+	padding: 10px 12px 12px;
+}
+
+.panel-label {
+	font-size: 12px;
+	color: rgba(255, 255, 255, 0.75);
+	white-space: nowrap;
+}
+
+.map-toolbar {
+	position: absolute;
+	top: 64px;
+	right: 16px;
+	display: flex;
+	flex-direction: column;
+	gap: 10px;
+	padding: 10px 8px;
+	background: rgba(19, 28, 36, 0.75);
+	backdrop-filter: blur(4px);
+	border-radius: 8px;
+	box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2);
+}
+
+.tool-btn {
+	padding: 0;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	gap: 6px;
+	background: rgba(255, 255, 255, 0.08);
+	border: 1px solid rgba(255, 255, 255, 0.18);
+	border-radius: 8px;
+	color: #ffffff;
+	font-size: 12px;
+	line-height: 1;
+	cursor: pointer;
+	transition: all 0.2s ease;
+	overflow: hidden;
+}
+
+.tool-btn:hover {
+	background: rgba(255, 255, 255, 0.16);
+	border-color: rgba(255, 255, 255, 0.35);
+	transform: translateY(-1px);
+}
+
+.tool-btn:active {
+	transform: translateY(0);
+}
+
+.tool-btn.danger {
+	border-color: rgba(255, 112, 112, 0.6);
+	color: #ffb3b3;
+}
+
+.tool-icon {
+	position: relative;
+}
+
+.tool-icon-image {
+	width: 36px;
+	height: 36px;
+}
+
+.tool-icon.polygon {
+	border-radius: 4px;
+	transform: rotate(15deg);
+}
+
+.tool-icon.rect {
+	border-radius: 2px;
+}
+
+.tool-icon.ellipse {
+	border-radius: 50%;
+}
+
+.tool-icon.buffer {
+	border-radius: 50%;
+	box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
+}
+
+.tool-icon.trash {
+	border: none;
+	box-shadow: none;
+}
+
+.tool-icon.trash::before,
+.tool-icon.trash::after {
+	content: "";
+	position: absolute;
+	left: 4px;
+	right: 4px;
+	background: #ffffff;
+}
+
+.tool-icon.trash::before {
+	top: 4px;
+	height: 10px;
+	border-radius: 2px;
+}
+
+.tool-icon.trash::after {
+	top: 1px;
+	height: 3px;
+	border-radius: 2px;
+}
+
+.shape-table-container {
+	margin-bottom: 20px;
+
+	.shape-table-title {
+		margin-bottom: 20px;
+		display: flex;
+		align-items: center;
+		justify-content: flex-start;
+		padding-right: 1.2rem;
+		font-family: Source Han Sans CN, Source Han Sans CN;
+		font-weight: 500;
+		font-size: 1.4rem;
+		color: #D4D5D7;
+		font-style: normal;
+		text-transform: none;
+		box-sizing: border-box;
+	}
+}
+</style>
diff --git a/applications/drone-command/src/views/areaManage/partition/index.vue b/applications/drone-command/src/views/areaManage/partition/index.vue
index bd3f8ae..9f81fd7 100644
--- a/applications/drone-command/src/views/areaManage/partition/index.vue
+++ b/applications/drone-command/src/views/areaManage/partition/index.vue
@@ -5,10 +5,9 @@
 				<el-input class="command-input" v-model="searchParams.areaName" placeholder="请输入"
 					clearable @clear="handleSearch" />
 			</el-form-item>
-
-			<el-form-item label="区域类型" prop="areaType">
+			<el-form-item label="区域类型" prop="areaTypeKeys">
 				<el-select class="command-select" popper-class="command-select-popper"
-					v-model="searchParams.areaType" placeholder="请选择" clearable @change="handleSearch">
+					v-model="searchParams.areaTypeKeys" placeholder="请选择" clearable @change="handleSearch">
 					<el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue"
 						:value="item.dictKey" />
 				</el-select>
@@ -31,30 +30,24 @@
 					<el-table-column type="selection" width="46" />
 					<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
 					<el-table-column prop="areaName" show-overflow-tooltip width="150" label="区域名称" />
-					<el-table-column show-overflow-tooltip width="160" label="区域位置">
+					<el-table-column prop="areaTypeKeys" show-overflow-tooltip width="196" label="区域类型">
 						<template v-slot="{ row }">
-							{{ formatLocation(row) }}
+							{{ getDictLabel(row.areaTypeKeys, dictObj.areaType) }}
 						</template>
 					</el-table-column>
-					<el-table-column prop="areaSize" show-overflow-tooltip width="130" label="区域面积(k㎡)" />
-					<el-table-column prop="areaType" show-overflow-tooltip width="120" label="区域类型">
-						<template v-slot="{ row }">
-							{{ getDictLabel(row.areaType, dictObj.areaType) }}
-						</template>
-					</el-table-column>
-					<el-table-column prop="triggerCondition" show-overflow-tooltip width="130" label="触发条件">
+					<el-table-column prop="triggerCondition" show-overflow-tooltip label="触发条件">
 						<template v-slot="{ row }">
 							{{ getDictLabel(row.triggerCondition, dictObj.triggeringCondition) }}
 						</template>
 					</el-table-column>
-					<el-table-column prop="responseMechanism" show-overflow-tooltip width="130" label="响应机制" />
-					<el-table-column prop="controlLevel" show-overflow-tooltip width="120" label="管控级别">
+					<el-table-column prop="responseMechanism" show-overflow-tooltip label="响应机制" />
+					<el-table-column prop="controlLevel" show-overflow-tooltip label="管控级别">
 						<template v-slot="{ row }">
 							{{ getDictLabel(row.controlLevel, dictObj.controlLevel) }}
 						</template>
 					</el-table-column>
 					<el-table-column prop="policeStationName" show-overflow-tooltip label="关联派出所" />
-					<el-table-column show-overflow-tooltip label="可飞行时间段">
+					<el-table-column show-overflow-tooltip width="300" label="可飞行时间段">
 						<template v-slot="{ row }">
 							{{ formatFlyDate(row) }}
 						</template>
@@ -93,7 +86,7 @@
 
 const initSearchParams = () => ({
 	areaName: '', // 区域名称
-	areaType: '', // 区域类型
+	areaTypeKeys: '', // 区域类型
 	current: 1, // 当前页
 	size: 10, // 每页大小
 })
@@ -157,11 +150,6 @@
 // 勾选值设置
 function handleSelectionChange (rows) {
 	selectedIds.value = rows.map(item => item.id)
-}
-
-function formatLocation (row) {
-	if (row?.longitude == null || row?.latitude == null) return ''
-	return `${row.longitude}, ${row.latitude}`
 }
 
 function formatFlyDate (row) {
diff --git a/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
index 064f7ad..50e15ee 100644
--- a/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/precinctInfo/FormDiaLog.vue
@@ -120,6 +120,7 @@
 import axios from 'axios'
 import { saveOperationLog } from '@ztzf/apis'
 import { useRoute } from 'vue-router'
+import positionIcon from '@/assets/images/areaManage/positionIcon.png'
 
 const initForm = () => ({
 	address: '', // 位置
@@ -185,7 +186,7 @@
 let redPointEntity
 let leftClickBound = false
 const labelParams = {
-	font: '16px',
+	font: '18px',
 	fillColor: Cesium.Color.WHITE, // 文字颜色:白色
 	backgroundColor: Cesium.Color.BLACK, //背景颜色
 	backgroundPadding: new Cesium.Cartesian2(5, 5), // 水平/垂直内边距(像素)
@@ -200,17 +201,17 @@
 }
 
 function LeftClickEvent(click) {
-	const pos = click.position // Cartesian2 屏幕坐标
-	// 屏幕坐标 -> 椭球面坐标(不考虑地形/模型)
-	const cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
-	if (!cartesian3) {
-		return { longitude: 112, latitude: 23 }
+	const pos = click.position
+	// 优先使用 pickPosition 获取贴地坐标(包含地形高度)
+	let cartesian3 = viewer.scene.pickPosition(pos)
+	// 如果 pickPosition 失败,回退到 pickEllipsoid
+	if (!cartesian3 || !Cesium.defined(cartesian3)) {
+		cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
 	}
-	// 椭球面坐标 -> 经纬度
+	if (!cartesian3) return
 	const carto = Cesium.Cartographic.fromCartesian(cartesian3)
 	const longitude = Cesium.Math.toDegrees(carto.longitude)
 	const latitude = Cesium.Math.toDegrees(carto.latitude)
-	// 直接更新 formData
 	formData.value.longitude = _.round(longitude, 6)
 	formData.value.latitude = _.round(latitude, 6)
 	getLocationName(longitude, latitude)
@@ -219,14 +220,16 @@
 	if (!redPointEntity) {
 		redPointEntity = viewer.entities.add({
 			position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
-			point: {
-				color: Cesium.Color.RED,
-				pixelSize: 10,
+			billboard: {
+				image: positionIcon,
+				width: 20,
+				height: 20,
+				verticalOrigin: Cesium.VerticalOrigin.CENTER,
 				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
 			},
 			label: {
 				text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
-				...labelParams
+				...labelParams,
 			},
 		})
 	} else {
@@ -240,14 +243,16 @@
 	if (!redPointEntity) {
 		redPointEntity = viewer.entities.add({
 			position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
-			point: {
-				color: Cesium.Color.RED,
-				pixelSize: 10,
+			billboard: {
+				image: positionIcon,
+				width: 20,
+				height: 20,
+				verticalOrigin: Cesium.VerticalOrigin.CENTER,
 				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
 			},
 			label: {
 				text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
-				...labelParams
+				...labelParams,
 			},
 		})
 	} else {
diff --git a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
index e546c27..43d3a82 100644
--- a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
@@ -15,7 +15,7 @@
 			<div class="right-container">
 				<div class="header">
 					<span>{{ titleEnum[dialogMode] }}</span>
-				
+
 					<el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
 				</div>
 
@@ -23,32 +23,16 @@
 					<div class="detail-title">场景详情</div>
 					<el-row>
 						<el-col :span="24">
-							<div class="label">场景名称</div>
+							<div class="label">场景配置名称</div>
 							<div class="val">{{ formData.sceneName }}</div>
-						</el-col>
-						<el-col :span="24">
-							<div class="label">指挥点位置</div>
-							<div class="val">{{ formatLocation(formData) }}</div>
 						</el-col>
 						<el-col :span="24">
 							<div class="label">场景类型</div>
 							<div class="val">{{ getDictLabel(formData.sceneType, dictObj.CommandSceneType) }}</div>
 						</el-col>
 						<el-col :span="24">
-							<div class="label">负责人</div>
-							<div class="val">{{ formData.defenseLeader }}</div>
-						</el-col>
-						<el-col :span="24">
-							<div class="label">负责人电话</div>
-							<div class="val">{{ formData.leaderPhone }}</div>
-						</el-col>
-						<el-col :span="24">
 							<div class="label">设备模式</div>
 							<div class="val">{{ getDictLabel(formData.deviceMode, dictObj.deviceMode) }}</div>
-						</el-col>
-						<el-col :span="24">
-							<div class="label">防控面积</div>
-							<div class="val">{{ formatDefenseArea(formData.defenseArea) }}</div>
 						</el-col>
 					</el-row>
 
@@ -57,9 +41,9 @@
 						<div class="command-table-content">
 							<el-table class="command-table" ref="areaTableRef" :data="areaList" row-key="id">
 								<el-table-column prop="areaName" label="区域名称" />
-								<el-table-column prop="areaType" label="区域类型">
+								<el-table-column prop="areaTypeKeys" label="区域类型">
 									<template v-slot="{ row }">
-										{{ getDictLabel(row.areaType, dictObj.areaType) }}
+										{{ getDictLabel(row.areaTypeKeys, dictObj.areaType) }}
 									</template>
 								</el-table-column>
 							</el-table>
@@ -67,60 +51,77 @@
 					</div>
 				</div>
 
-				<el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly"
-					label-width="96px">
-					<el-form-item label="场景名称" prop="sceneName">
-						<el-input class="command-input" v-model="formData.sceneName" maxlength="50"
-							placeholder="请输入" clearable />
-					</el-form-item>
-					<el-form-item label="指挥点位置" prop="longitude">
-						<div class="val">{{ formatLocation(formData) }}</div>
+				<el-form
+					class="dialog-form"
+					v-else
+					ref="formRef"
+					:model="formData"
+					:rules="rules"
+					:disabled="readonly"
+					label-width="96px"
+				>
+					<el-form-item label="场景配置名称" prop="sceneName">
+						<el-input
+							class="command-input"
+							v-model="formData.sceneName"
+							maxlength="50"
+							placeholder="请输入"
+							clearable
+						/>
 					</el-form-item>
 					<el-form-item label="场景类型" prop="sceneType">
-						<el-select class="command-select" popper-class="command-select-popper"
-							v-model="formData.sceneType" placeholder="请选择" clearable>
-							<el-option v-for="item in dictObj.CommandSceneType" :key="item.dictKey" :label="item.dictValue"
-								:value="item.dictKey" />
+						<el-select
+							class="command-select"
+							popper-class="command-select-popper"
+							v-model="formData.sceneType"
+							placeholder="请选择"
+							clearable
+						>
+							<el-option
+								v-for="item in dictObj.CommandSceneType"
+								:key="item.dictKey"
+								:label="item.dictValue"
+								:value="item.dictKey"
+							/>
 						</el-select>
-					</el-form-item>
-					<el-form-item label="负责人" prop="defenseLeader">
-						<el-input class="command-input" v-model="formData.defenseLeader" maxlength="50"
-							placeholder="请输入" clearable />
-					</el-form-item>
-					<el-form-item label="负责人电话" prop="leaderPhone">
-						<el-input class="command-input" v-model="formData.leaderPhone" maxlength="50"
-							placeholder="请输入" clearable />
 					</el-form-item>
 					<el-form-item label="设备模式" prop="deviceMode">
-						<el-select class="command-select" popper-class="command-select-popper"
-							v-model="formData.deviceMode" placeholder="请选择" clearable>
-							<el-option v-for="item in dictObj.deviceMode" :key="item.dictKey" :label="item.dictValue"
-								:value="item.dictKey" />
+						<el-select
+							class="command-select"
+							popper-class="command-select-popper"
+							v-model="formData.deviceMode"
+							placeholder="请选择"
+							clearable
+						>
+							<el-option
+								v-for="item in dictObj.deviceMode"
+								:key="item.dictKey"
+								:label="item.dictValue"
+								:value="item.dictKey"
+							/>
 						</el-select>
-					</el-form-item>
-					<el-form-item label="防控面积" prop="defenseArea">
-						<div class="val">{{ formatDefenseArea(formData.defenseArea) }}</div>
 					</el-form-item>
 
 					<div class="search-table-container">
 						<div class="search-box">
-							<div class="label">
-								关联区域
-							</div>
+							<div class="label">关联区域</div>
 
-							<el-input class="command-input" v-model="searchName" placeholder="请输入"
-								clearable></el-input>
+							<el-input class="command-input" v-model="searchName" placeholder="请输入" clearable></el-input>
 						</div>
 
 						<el-form-item prop="areaDivideIds" label-width="0">
-							<el-table class="command-table" ref="areaTableRef"
-								:data="areaList.filter(item => item.areaName.includes(searchName))" row-key="id"
-								@selection-change="handleAreaSelectionChange">
+							<el-table
+								class="command-table"
+								ref="areaTableRef"
+								:data="areaList.filter(item => item.areaName.includes(searchName))"
+								row-key="id"
+								@selection-change="handleAreaSelectionChange"
+							>
 								<el-table-column type="selection" width="55" :reserve-selection="true" />
 								<el-table-column prop="areaName" show-overflow-tooltip label="区域名称" />
-								<el-table-column prop="areaType" show-overflow-tooltip label="区域类型">
+								<el-table-column prop="areaTypeKeys" show-overflow-tooltip label="区域类型">
 									<template v-slot="{ row }">
-										{{ getDictLabel(row.areaType, dictObj.areaType) }}
+										{{ getDictLabel(row.areaTypeKeys, dictObj.areaType) }}
 									</template>
 								</el-table-column>
 							</el-table>
@@ -129,9 +130,17 @@
 				</el-form>
 
 				<div class="footer">
-					<el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
-					<el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
-						:disabled="submitting" @click="handleSubmit">
+					<el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">
+						{{ readonly ? '关闭' : '取消' }}
+					</el-button>
+					<el-button
+						color="#284FE3"
+						v-if="!readonly"
+						type="primary"
+						:loading="submitting"
+						:disabled="submitting"
+						@click="handleSubmit"
+					>
 						确定
 					</el-button>
 				</div>
@@ -143,29 +152,24 @@
 <script setup>
 import { Close } from '@element-plus/icons-vue'
 
-import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
+import { computed, inject, nextTick, ref, watch } from 'vue'
 import { ElMessage } from 'element-plus'
 import { fwDefenseSceneDetailApi, fwDefenseSceneSubmitApi } from './sceneConfigApi'
-import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
+import { fieldRules, getDictLabel } from '@ztzf/utils'
 import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import * as Cesium from 'cesium'
 import { fwAreaDivideDetailApi, fwAreaDivideListApi } from '../partition/partitionApi'
-import { DrawPolygon } from '@/utils/cesium/DrawPolygon'
-import commandPost from '@/assets/images/dataCockpit/legend/command-post.png'
+import { buildEllipsePositions } from '@/utils/cesium/shapeTools'
 import { saveOperationLog } from '@ztzf/apis'
 import { useRoute } from 'vue-router'
+import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
 
 const initForm = () => ({
-	sceneName: '', // 场景名称
+	sceneName: '', // 场景配置名称
 	sceneType: '', // 场景类型
-	defenseLeader: '', // 负责人
-	leaderPhone: '', // 负责人电话
 	deviceMode: '', // 设备模式
 	areaDivideIds: '', // 关联区域ID
 	areaCount: 0, // 区域数量
-	defenseArea: 0, // 防控面积
-	latitude: null, // 指挥点纬度
-	longitude: null, // 指挥点经度
 })
 
 const emit = defineEmits(['success'])
@@ -184,26 +188,22 @@
 const mapRef = ref(null)
 const route = useRoute()
 let viewer
-let redPointEntity
-let leftClickBound = false
+let areaDisplaySource
 
 const rules = {
 	sceneName: fieldRules(true, 50),
 	sceneType: fieldRules(true),
-	defenseLeader: fieldRules(true, 50),
-	leaderPhone: fieldRules(true, 50),
 	deviceMode: fieldRules(true),
-	longitude: fieldRules(true),
 	areaDivideIds: fieldRules(false),
 }
 
 // 关闭弹框
-function handleCancel () {
+function handleCancel() {
 	visible.value = false
 }
 
 // 提交新增/编辑
-async function handleSubmit () {
+async function handleSubmit() {
 	const isValid = await formRef.value?.validate().catch(() => false)
 	if (!isValid) return
 	submitting.value = true
@@ -211,7 +211,6 @@
 		const ids = selectedAreaRows.value.map(item => item.id)
 		formData.value.areaDivideIds = ids.join(',')
 		formData.value.areaCount = ids.length
-		formData.value.defenseArea = calcDefenseArea(selectedAreaRows.value)
 		await fwDefenseSceneSubmitApi(formData.value)
 		saveOperationLog({
 			requestUri: route.path,
@@ -227,65 +226,190 @@
 }
 
 // 加载详情
-async function loadDetail () {
+async function loadDetail() {
 	if (!formData.value.id) return
 	const res = await fwDefenseSceneDetailApi({ id: formData.value.id })
 	formData.value = res?.data?.data ?? initForm()
 }
 
 // 获取区域列表
-async function getAreaList () {
+async function getAreaList() {
 	if (areaList.value.length) return
 	const res = await fwAreaDivideListApi({ filterSelected: 1, sceneId: formData.value.id })
 	areaList.value = res?.data?.data ?? []
 }
 
 // 关联区域变更
-async function handleAreaSelectionChange (rows) {
-	const selectedRows = await ensureAreaGeom(rows)
+async function handleAreaSelectionChange(rows) {
+	const selectedRows = await ensureAreaExtList(rows)
 	selectedAreaRows.value = selectedRows
 	const ids = selectedRows.map(item => item.id)
 	formData.value.areaDivideIds = ids.join(',')
 	formData.value.areaCount = ids.length
-	formData.value.defenseArea = calcDefenseArea(selectedRows)
 }
 
 // 渲染面
-function renderingSurface () {
-	console.log(geometricSource, 11111111)
-
-	if (!geometricSource) {
-		initMap()
+function ensureAreaDisplaySource() {
+	if (!viewer) return null
+	if (!areaDisplaySource) {
+		areaDisplaySource = new Cesium.CustomDataSource('scene-area-display')
+		viewer.dataSources.add(areaDisplaySource)
 	}
-	if (!geometricSource) return
-	geometricSource && geometricSource.entities.removeAll()
-	selectedAreaRows.value.forEach(item => {
-		if (item.geom) {
-			const pointList = geomAnalysis(item.geom)
-			const result = pointList.map(item => [item.longitude, item.latitude]).flat()
-			geometricSource.entities?.add({
-				customType: 'control_group',
-				position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
+	return areaDisplaySource
+}
+
+function clearAreaDisplay() {
+	if (!areaDisplaySource) return
+	areaDisplaySource.entities.removeAll()
+}
+
+function normalizeShapePoint(point) {
+	if (!point) return null
+	const lng = point?.lng ?? point?.longitude
+	const lat = point?.lat ?? point?.latitude
+	const height = Number.isFinite(Number(point?.height)) ? Number(point.height) : 0
+	if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
+	return { lng: Number(lng), lat: Number(lat), height }
+}
+
+function buildShapePositions(points = []) {
+	const normalized = points.map(normalizeShapePoint).filter(Boolean)
+	return normalized.map(point => Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.height))
+}
+
+function getShapeDisplayPoints(shape) {
+	if (Array.isArray(shape?.displayPoints) && shape.displayPoints.length) {
+		return shape.displayPoints
+	}
+	return shape?.points || []
+}
+
+function getAreaTypeStyle(areaType) {
+	return AREA_TYPE_STYLE_MAP?.[String(areaType)] || DEFAULT_AREA_STYLE
+}
+
+function parseGeomJson(geomJson) {
+	if (!geomJson) return null
+	if (typeof geomJson === 'object') return geomJson
+	if (typeof geomJson !== 'string') return null
+	const trimmed = geomJson.trim()
+	if (!trimmed) return null
+	try {
+		return JSON.parse(trimmed)
+	} catch (error) {
+		return null
+	}
+}
+
+function resolveAreaShapes(area) {
+	const extList = Array.isArray(area?.fwAreaDivideExtList) ? area.fwAreaDivideExtList : []
+	if (!extList.length) return []
+	return extList
+		.map((item, index) => {
+			const isShapePayload = item?.drawType || item?.points
+			if (isShapePayload) {
+				return {
+					id: item?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
+					drawType: item?.drawType ?? 'polygon',
+					areaType: item?.areaType ?? item?.areaTypeKey ?? '',
+					points: Array.isArray(item?.points) ? item.points : [],
+					displayPoints: Array.isArray(item?.displayPoints) ? item.displayPoints : null,
+					meta: item?.meta ?? null,
+				}
+			}
+			const parsed = parseGeomJson(item?.geomJson)
+			if (!parsed) return null
+			return {
+				id: parsed?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
+				drawType: parsed?.drawType ?? 'polygon',
+				areaType: item?.areaTypeKey ?? parsed?.areaType ?? '',
+				points: Array.isArray(parsed?.points) ? parsed.points : [],
+				displayPoints: Array.isArray(parsed?.displayPoints) ? parsed.displayPoints : null,
+				meta: parsed?.meta ?? null,
+			}
+		})
+		.filter(Boolean)
+}
+
+function renderSelectedAreas() {
+	if (!visible.value) {
+		clearAreaDisplay()
+		return
+	}
+	if (!viewer) initMap()
+	const dataSource = ensureAreaDisplaySource()
+	if (!dataSource) return
+	dataSource.entities.removeAll()
+	selectedAreaRows.value.forEach(area => {
+		const shapes = resolveAreaShapes(area)
+		shapes.forEach(shape => {
+			if (shape.drawType === 'buffer' && shape.meta?.bufferRadii?.length && shape.meta?.center) {
+				const center = shape.meta.center
+				const centerCartesian = Cesium.Cartesian3.fromDegrees(center.lng, center.lat, center.height || 0)
+				const radii = shape.meta.bufferRadii
+					.map(radius => Number(radius))
+					.filter(radius => Number.isFinite(radius) && radius > 0)
+				radii.forEach((radius, index) => {
+					const style = BUFFER_LEVEL_STYLES[index] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
+					dataSource.entities.add({
+						name: 'scene-area-display',
+						customData: { drawType: shape.drawType, level: index + 1 },
+						position: centerCartesian,
+						ellipse: {
+							semiMajorAxis: radius,
+							semiMinorAxis: radius,
+							material: style.fill,
+							outline: false,
+							heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+						},
+					})
+					const positions = buildEllipsePositions(centerCartesian, radius, radius)
+					dataSource.entities.add({
+						name: 'scene-area-display',
+						customData: { drawType: shape.drawType, level: index + 1, outline: true },
+						polyline: {
+							positions: positions.length ? [...positions, positions[0]] : positions,
+							clampToGround: true,
+							width: 2,
+							material: style.outline,
+						},
+					})
+				})
+				return
+			}
+			const positions = buildShapePositions(getShapeDisplayPoints(shape))
+			if (positions.length < 3) return
+			const style = getAreaTypeStyle(shape.areaType)
+			dataSource.entities.add({
+				name: 'scene-area-display',
+				customData: { drawType: shape.drawType },
+				polygon: {
+					hierarchy: new Cesium.PolygonHierarchy(positions),
+					material: style.fill,
+					outline: false,
+					heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				},
 				polyline: {
-					positions: Cesium.Cartesian3.fromDegreesArray(result),
+					positions: positions.length ? [...positions, positions[0]] : positions,
 					clampToGround: true,
-					width: 3,
-					material: Cesium.Color.RED,
+					width: 2,
+					material: style.outline,
 				},
 			})
-		}
+		})
 	})
 }
 
-watch(() => selectedAreaRows.value, renderingSurface)
+watch(() => selectedAreaRows.value, renderSelectedAreas)
+watch(
+	() => visible.value,
+	val => {
+		if (!val) clearAreaDisplay()
+	}
+)
 
-function calcDefenseArea (rows) {
-	const total = rows.reduce((sum, item) => sum + (Number(item.areaSize) || 0), 0)
-	return _.round(total, 2)
-}
-
-async function ensureAreaGeom (rows) {
-	const missingRows = rows.filter(row => row?.id && !row.geom)
+async function ensureAreaExtList(rows) {
+	const missingRows = rows.filter(row => row?.id && !Array.isArray(row?.fwAreaDivideExtList))
 	if (!missingRows.length) return rows
 	const detailList = await Promise.all(
 		missingRows.map(row =>
@@ -294,9 +418,7 @@
 				.catch(() => null)
 		)
 	)
-	const detailMap = new Map(
-		detailList.filter(Boolean).map(item => [String(item.id), item])
-	)
+	const detailMap = new Map(detailList.filter(Boolean).map(item => [String(item.id), item]))
 	if (!detailMap.size) return rows
 	areaList.value = areaList.value.map(item => {
 		const detail = detailMap.get(String(item.id))
@@ -309,7 +431,7 @@
 }
 
 // 同步关联区域
-function syncAreaSelection () {
+async function syncAreaSelection() {
 	if (!areaTableRef.value) return
 	areaTableRef.value.clearSelection()
 	const rows = []
@@ -317,99 +439,32 @@
 	const arr = Array.isArray(areaDivideIds)
 		? areaDivideIds.map(item => String(item))
 		: String(areaDivideIds || '')
-			.split(',')
-			.filter(Boolean)
+				.split(',')
+				.filter(Boolean)
 	areaList.value.forEach(row => {
 		if (arr.includes(String(row.id))) {
 			areaTableRef.value.toggleRowSelection(row, true)
 			rows.push(row)
 		}
 	})
-	selectedAreaRows.value = rows
-	if (!rows.length) return
-	formData.value.areaCount = rows.length
-	formData.value.defenseArea = calcDefenseArea(rows)
+	const ensuredRows = await ensureAreaExtList(rows)
+	selectedAreaRows.value = ensuredRows
+	if (!ensuredRows.length) return
+	formData.value.areaCount = ensuredRows.length
 }
 
-function formatDefenseArea (value) {
-	if (value == null || value === '') return ''
-	return `${value}k㎡`
-}
-
-// 格式化指挥点位置
-function formatLocation (row) {
-	if (row?.longitude == null || row?.latitude == null) return ''
-	return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
-}
-
-let geometricSource
 // 初始化地图实例
-function initMap () {
+function initMap() {
 	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
-	}
-	if (!geometricSource) {
-		geometricSource = new Cesium.CustomDataSource('geometricSource')
-		viewer.dataSources.add(geometricSource)
-	}
 }
 
-function LeftClickEvent (click) {
-	const pos = click.position
-	const cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
-	if (!cartesian3) return
-	const carto = Cesium.Cartographic.fromCartesian(cartesian3)
-	const longitude = Cesium.Math.toDegrees(carto.longitude)
-	const latitude = Cesium.Math.toDegrees(carto.latitude)
-	formData.value.longitude = _.round(longitude, 6)
-	formData.value.latitude = _.round(latitude, 6)
-	setMapPoint(formData.value.longitude, formData.value.latitude)
-}
-
-function setMapPoint (longitude, latitude) {
-	if (!viewer || longitude == null || latitude == null) return
-	if (!redPointEntity) {
-		redPointEntity = viewer.entities.add({
-			position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
-			billboard: {
-				image: commandPost, // 这里替换为你的图片路径或变量 deviceZcImg
-				width: 40,
-				height: 56,
-				verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 设置图片底部对齐坐标点
-			},
-			label: {
-				text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
-				font: '14px',
-				fillColor: Cesium.Color.WHITE,
-				backgroundColor: Cesium.Color.BLACK.withAlpha(0.6),
-				backgroundPadding: new Cesium.Cartesian2(5, 5),
-				showBackground: true,
-				style: Cesium.LabelStyle.FILL_AND_OUTLINE,
-				horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
-				verticalOrigin: Cesium.VerticalOrigin.CENTER,
-				outlineWidth: 1,
-				outlineColor: Cesium.Color.WHITE,
-				pixelOffset: new Cesium.Cartesian2(0, -72),
-				eyeOffset: new Cesium.Cartesian3(0, 0, -20),
-			},
-		})
-	} else {
-		redPointEntity.position = Cesium.Cartesian3.fromDegrees(longitude, latitude)
-		redPointEntity.label.text = `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
-	}
-}
-
-// 打开弹框
-async function open ({ mode, row } = {}) {
+async function open({ mode, row } = {}) {
 	dialogMode.value = mode || 'add'
 	formData.value = dialogMode.value === 'add' ? initForm() : row
 	selectedAreaRows.value = []
-	redPointEntity = null
 	await nextTick()
 	initMap()
 	await getAreaList()
@@ -417,14 +472,8 @@
 		await loadDetail()
 	}
 	await nextTick()
-	syncAreaSelection()
-	setMapPoint(formData.value.longitude, formData.value.latitude)
+	await syncAreaSelection()
 }
-
-onMounted(() => {
-	getAreaList()
-})
-
 defineExpose({
 	open,
 })
diff --git a/applications/drone-command/src/views/areaManage/sceneConfig/index.vue b/applications/drone-command/src/views/areaManage/sceneConfig/index.vue
index 817b481..75de78d 100644
--- a/applications/drone-command/src/views/areaManage/sceneConfig/index.vue
+++ b/applications/drone-command/src/views/areaManage/sceneConfig/index.vue
@@ -1,7 +1,7 @@
 <template>
 	<basic-container>
 		<el-form ref="queryParamsRef" :model="searchParams" class="command-page-history-search">
-			<el-form-item label="场景名称" prop="sceneName">
+			<el-form-item label="场景配置名称" prop="sceneName">
 				<el-input
 					class="command-input"
 					v-model="searchParams.sceneName"
@@ -46,19 +46,12 @@
 					<el-table-column type="selection" width="46" />
 					<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
 
-					<el-table-column prop="sceneName" show-overflow-tooltip label="场景名称" />
+					<el-table-column prop="sceneName" show-overflow-tooltip label="场景配置名称" />
 					<el-table-column prop="sceneType" show-overflow-tooltip label="场景类型">
 						<template v-slot="{ row }">
 							{{ getDictLabel(row.sceneType, dictObj.CommandSceneType) }}
 						</template>
 					</el-table-column>
-					<el-table-column show-overflow-tooltip label="指挥点位置">
-						<template v-slot="{ row }">
-							{{ formatLocation(row) }}
-						</template>
-					</el-table-column>
-					<el-table-column prop="defenseLeader" show-overflow-tooltip label="防控负责人" />
-					<el-table-column prop="leaderPhone" show-overflow-tooltip label="防控负责人电话" />
 					<el-table-column prop="areaCount" show-overflow-tooltip label="区域数量" />
 					<el-table-column prop="counterDeviceCount" show-overflow-tooltip label="反制设备数量" />
 					<el-table-column prop="detectDeviceCount" show-overflow-tooltip label="探测设备数量" />
@@ -100,7 +93,7 @@
 import { useRoute } from 'vue-router'
 
 const initSearchParams = () => ({
-	sceneName: '', // 场景名称
+	sceneName: '', // 场景配置名称
 	sceneType: '', // 场景类型
 	current: 1, // 当前页
 	size: 10, // 每页大小
diff --git a/applications/drone-command/src/views/areaManage/sceneManage/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/sceneManage/FormDiaLog.vue
new file mode 100644
index 0000000..e3818e6
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/sceneManage/FormDiaLog.vue
@@ -0,0 +1,553 @@
+<template>
+	<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">
+				<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">
+					<span>{{ titleEnum[dialogMode] }}</span>
+
+					<el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
+				</div>
+
+				<div class="content" v-if="readonly">
+					<div class="detail-title">场景管理详情</div>
+					<el-row>
+						<el-col :span="24">
+							<div class="label">场景名称</div>
+							<div class="val">{{ formData.sceneName }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">负责人</div>
+							<div class="val">{{ formData.defenseLeader }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">负责人电话</div>
+							<div class="val">{{ formData.leaderPhone }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">有效时间</div>
+							<div class="val">{{ formatEffectiveDate(formData) }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">指挥点位置</div>
+							<div class="val">{{ formatLocation(formData) }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">关联场景配置</div>
+							<div class="val">{{ getSceneConfigName(formData.defenseSceneId) }}</div>
+						</el-col>
+					</el-row>
+				</div>
+
+				<el-form
+					class="dialog-form"
+					v-else
+					ref="formRef"
+					:model="formData"
+					:rules="rules"
+					:disabled="readonly"
+					label-width="110px"
+				>
+					<el-form-item label="场景名称" prop="sceneName">
+						<el-input
+							class="command-input"
+							v-model="formData.sceneName"
+							maxlength="50"
+							placeholder="请输入"
+							clearable
+						/>
+					</el-form-item>
+					<el-form-item label="关联场景配置" prop="defenseSceneId">
+						<el-select
+							class="command-select"
+							popper-class="command-select-popper"
+							v-model="formData.defenseSceneId"
+							placeholder="请选择"
+							clearable
+						>
+							<el-option v-for="item in sceneConfigList" :key="item.id" :label="item.sceneName" :value="item.id" />
+						</el-select>
+					</el-form-item>
+					<el-form-item label="负责人" prop="defenseLeader">
+						<el-input
+							class="command-input"
+							v-model="formData.defenseLeader"
+							maxlength="50"
+							placeholder="请输入"
+							clearable
+						/>
+					</el-form-item>
+					<el-form-item label="负责人电话" prop="leaderPhone">
+						<el-input
+							class="command-input"
+							v-model="formData.leaderPhone"
+							maxlength="50"
+							placeholder="请输入"
+							clearable
+						/>
+					</el-form-item>
+					<el-form-item label="有效时间范围" prop="effectiveDateStart">
+						<el-date-picker
+							class="command-date-picker"
+							popper-class="command-date-picker-popper"
+							v-model="effectiveDateRange"
+							type="datetimerange"
+							range-separator="至"
+							start-placeholder="开始时间"
+							end-placeholder="结束时间"
+							value-format="YYYY-MM-DD HH:mm:ss"
+							clearable
+						/>
+					</el-form-item>
+					<el-form-item label="指挥点位置" prop="longitude">
+						<div class="val">{{ formatLocation(formData) }}</div>
+					</el-form-item>
+				</el-form>
+				<div class="footer">
+					<el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">
+						{{ readonly ? '关闭' : '取消' }}
+					</el-button>
+					<el-button
+						color="#284FE3"
+						v-if="!readonly"
+						type="primary"
+						:loading="submitting"
+						:disabled="submitting"
+						@click="handleSubmit"
+					>
+						确定
+					</el-button>
+				</div>
+			</div>
+		</div>
+	</el-dialog>
+</template>
+
+<script setup>
+import { Close } from '@element-plus/icons-vue'
+
+import { computed, nextTick, onMounted, ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { fwDefenseSceneManageDetailApi, fwDefenseSceneManageSubmitApi } from './sceneManageApi'
+import { fwDefenseSceneListApi } from '../sceneConfig/sceneConfigApi'
+import { fieldRules } from '@ztzf/utils'
+import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
+import * as Cesium from 'cesium'
+import commandPost from '@/assets/images/dataCockpit/legend/command-post.png'
+import { saveOperationLog } from '@ztzf/apis'
+import { useRoute } from 'vue-router'
+import { fwAreaDivideDetailApi, fwAreaDivideListApi } from '../partition/partitionApi'
+import { buildEllipsePositions } from '@/utils/cesium/shapeTools'
+import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
+
+// 初始化表单数据
+const initForm = () => ({
+	sceneName: '', // 场景名称
+	defenseLeader: '', // 防控负责人
+	leaderPhone: '', // 防控负责人电话
+	effectiveDateStart: '', // 有效时间-开始
+	effectiveDateEnd: '', // 有效时间-结束
+	longitude: null, // 指挥点经度
+	latitude: null, // 指挥点纬度
+	defenseSceneId: '', // 关联场景配置ID
+})
+
+const emit = defineEmits(['success'])
+const formRef = ref(null) // 表单实例
+const formData = ref(initForm()) // 表单数据
+const visible = defineModel() // 弹框显隐
+const dialogMode = ref('add') // 弹框模式
+const submitting = ref(false) // 提交中
+const readonly = computed(() => dialogMode.value === 'view')
+const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
+const effectiveDateRange = ref([]) // 有效时间范围
+const sceneConfigList = ref([]) // 场景配置列表
+const areaList = ref([]) // ????
+const mapRef = ref(null)
+const route = useRoute()
+let viewer
+let redPointEntity
+let areaDisplaySource
+let leftClickBound = false
+
+// 表单验证规则
+const rules = {
+	sceneName: fieldRules(true, 50),
+	defenseLeader: fieldRules(true, 50),
+	leaderPhone: fieldRules(true, 50),
+	effectiveDateStart: fieldRules(true),
+	longitude: fieldRules(true),
+	defenseSceneId: fieldRules(true),
+}
+
+// 关闭弹框
+function handleCancel() {
+	visible.value = false
+}
+
+// 提交新增/编辑
+async function handleSubmit() {
+	const isValid = await formRef.value?.validate().catch(() => false)
+	if (!isValid) return
+	submitting.value = true
+	try {
+		await fwDefenseSceneManageSubmitApi(formData.value)
+		saveOperationLog({
+			requestUri: route.path,
+			title: `${route.name || '场景管理'}-${dialogMode.value === 'add' ? '新增' : '编辑'}`,
+			type: 1,
+		})
+		ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
+		visible.value = false
+		emit('success')
+	} finally {
+		submitting.value = false
+	}
+}
+
+// 监听有效时间范围变化,同步到表单数据
+watch(
+	() => effectiveDateRange.value,
+	() => {
+		formData.value.effectiveDateStart = effectiveDateRange.value?.[0] || ''
+		formData.value.effectiveDateEnd = effectiveDateRange.value?.[1] || ''
+	}
+)
+
+// 加载详情
+async function loadDetail() {
+	if (!formData.value.id) return
+	const res = await fwDefenseSceneManageDetailApi({ id: formData.value.id })
+	formData.value = res?.data?.data ?? initForm()
+	// 同步有效时间范围
+	effectiveDateRange.value = [formData.value.effectiveDateStart, formData.value.effectiveDateEnd].filter(Boolean)
+}
+
+// 获取场景配置列表
+// ?????
+function ensureAreaDisplaySource() {
+	if (!viewer) return null
+	if (!areaDisplaySource) {
+		areaDisplaySource = new Cesium.CustomDataSource('scene-manage-area-display')
+		viewer.dataSources.add(areaDisplaySource)
+	}
+	return areaDisplaySource
+}
+
+function clearAreaDisplay() {
+	if (!areaDisplaySource) return
+	areaDisplaySource.entities.removeAll()
+}
+
+function normalizeShapePoint(point) {
+	if (!point) return null
+	const lng = point?.lng ?? point?.longitude
+	const lat = point?.lat ?? point?.latitude
+	const height = Number.isFinite(Number(point?.height)) ? Number(point.height) : 0
+	if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
+	return { lng: Number(lng), lat: Number(lat), height }
+}
+
+function buildShapePositions(points = []) {
+	const normalized = points.map(normalizeShapePoint).filter(Boolean)
+	return normalized.map(point => Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.height))
+}
+
+function getShapeDisplayPoints(shape) {
+	if (Array.isArray(shape?.displayPoints) && shape.displayPoints.length) {
+		return shape.displayPoints
+	}
+	return shape?.points || []
+}
+
+function getAreaTypeStyle(areaType) {
+	return AREA_TYPE_STYLE_MAP?.[String(areaType)] || DEFAULT_AREA_STYLE
+}
+
+function parseGeomJson(geomJson) {
+	if (!geomJson) return null
+	if (typeof geomJson === 'object') return geomJson
+	if (typeof geomJson !== 'string') return null
+	const trimmed = geomJson.trim()
+	if (!trimmed) return null
+	try {
+		return JSON.parse(trimmed)
+	} catch (error) {
+		return null
+	}
+}
+
+function resolveAreaShapes(area) {
+	const extList = Array.isArray(area?.fwAreaDivideExtList) ? area.fwAreaDivideExtList : []
+	if (!extList.length) return []
+	return extList
+		.map((item, index) => {
+			const isShapePayload = item?.drawType || item?.points
+			if (isShapePayload) {
+				return {
+					id: item?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
+					drawType: item?.drawType ?? 'polygon',
+					areaType: item?.areaType ?? item?.areaTypeKey ?? '',
+					points: Array.isArray(item?.points) ? item.points : [],
+					displayPoints: Array.isArray(item?.displayPoints) ? item.displayPoints : null,
+					meta: item?.meta ?? null,
+				}
+			}
+			const parsed = parseGeomJson(item?.geomJson)
+			if (!parsed) return null
+			return {
+				id: parsed?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
+				drawType: parsed?.drawType ?? 'polygon',
+				areaType: item?.areaTypeKey ?? parsed?.areaType ?? '',
+				points: Array.isArray(parsed?.points) ? parsed.points : [],
+				displayPoints: Array.isArray(parsed?.displayPoints) ? parsed.displayPoints : null,
+				meta: parsed?.meta ?? null,
+			}
+		})
+		.filter(Boolean)
+}
+
+function renderSceneAreas() {
+	if (!visible.value) {
+		clearAreaDisplay()
+		return
+	}
+	if (!viewer) initMap()
+	const dataSource = ensureAreaDisplaySource()
+	if (!dataSource) return
+	dataSource.entities.removeAll()
+	areaList.value.forEach(area => {
+		const shapes = resolveAreaShapes(area)
+		shapes.forEach(shape => {
+			if (shape.drawType === 'buffer' && shape.meta?.bufferRadii?.length && shape.meta?.center) {
+				const center = shape.meta.center
+				const centerCartesian = Cesium.Cartesian3.fromDegrees(center.lng, center.lat, center.height || 0)
+				const radii = shape.meta.bufferRadii
+					.map(radius => Number(radius))
+					.filter(radius => Number.isFinite(radius) && radius > 0)
+				radii.forEach((radius, index) => {
+					const style = BUFFER_LEVEL_STYLES[index] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
+					dataSource.entities.add({
+						name: 'scene-manage-area-display',
+						customData: { drawType: shape.drawType, level: index + 1 },
+						position: centerCartesian,
+						ellipse: {
+							semiMajorAxis: radius,
+							semiMinorAxis: radius,
+							material: style.fill,
+							outline: false,
+							heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+						},
+					})
+					const positions = buildEllipsePositions(centerCartesian, radius, radius)
+					dataSource.entities.add({
+						name: 'scene-manage-area-display',
+						customData: { drawType: shape.drawType, level: index + 1, outline: true },
+						polyline: {
+							positions: positions.length ? [...positions, positions[0]] : positions,
+							clampToGround: true,
+							width: 2,
+							material: style.outline,
+						},
+					})
+				})
+				return
+			}
+			const positions = buildShapePositions(getShapeDisplayPoints(shape))
+			if (positions.length < 3) return
+			const style = getAreaTypeStyle(shape.areaType)
+			dataSource.entities.add({
+				name: 'scene-manage-area-display',
+				customData: { drawType: shape.drawType },
+				polygon: {
+					hierarchy: new Cesium.PolygonHierarchy(positions),
+					material: style.fill,
+					outline: false,
+					heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				},
+				polyline: {
+					positions: positions.length ? [...positions, positions[0]] : positions,
+					clampToGround: true,
+					width: 2,
+					material: style.outline,
+				},
+			})
+		})
+	})
+}
+
+async function ensureAreaExtList(rows) {
+	const missingRows = rows.filter(row => row?.id && !Array.isArray(row?.fwAreaDivideExtList))
+	if (!missingRows.length) return rows
+	const detailList = await Promise.all(
+		missingRows.map(row =>
+			fwAreaDivideDetailApi({ id: row.id })
+				.then(res => res?.data?.data)
+				.catch(() => null)
+		)
+	)
+	const detailMap = new Map(detailList.filter(Boolean).map(item => [String(item.id), item]))
+	if (!detailMap.size) return rows
+	return rows.map(item => {
+		const detail = detailMap.get(String(item.id))
+		return detail ? { ...item, ...detail } : item
+	})
+}
+
+// ??????????
+async function loadAreaList(sceneId) {
+	if (!sceneId) {
+		areaList.value = []
+		clearAreaDisplay()
+		return
+	}
+	const res = await fwAreaDivideListApi({ sceneId })
+	const list = res?.data?.data ?? []
+	areaList.value = await ensureAreaExtList(list)
+	renderSceneAreas()
+}
+
+async function getSceneConfigList() {
+	if (sceneConfigList.value.length) return
+	const res = await fwDefenseSceneListApi({unbound: 1})
+	sceneConfigList.value = res?.data?.data ?? []
+}
+
+// 获取场景配置名称
+function getSceneConfigName(id) {
+	if (!id) return ''
+	const item = sceneConfigList.value.find(item => item.id === id)
+	return item?.sceneName || id
+}
+
+// 格式化指挥点位置
+function formatLocation(row) {
+	if (row?.longitude == null || row?.latitude == null) return ''
+	return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
+}
+
+// 格式化有效时间
+function formatEffectiveDate(row) {
+	if (!row?.effectiveDateStart && !row?.effectiveDateEnd) return ''
+	return `${row.effectiveDateStart || '-'} ~ ${row.effectiveDateEnd || '-'}`
+}
+
+// 初始化地图实例
+function initMap() {
+	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
+	}
+}
+
+// 地图左键点击事件
+async function LeftClickEvent(click) {
+	const pos = click.position
+	// 优先使用 pickPosition 获取贴地坐标(包含地形高度)
+	let cartesian3 = viewer.scene.pickPosition(pos)
+	// 如果 pickPosition 失败,回退到 pickEllipsoid
+	if (!cartesian3 || !Cesium.defined(cartesian3)) {
+		cartesian3 = viewer.camera.pickEllipsoid(pos, viewer.scene.globe.ellipsoid)
+	}
+	if (!cartesian3) return
+	const carto = Cesium.Cartographic.fromCartesian(cartesian3)
+	const longitude = Cesium.Math.toDegrees(carto.longitude)
+	const latitude = Cesium.Math.toDegrees(carto.latitude)
+	formData.value.longitude = _.round(longitude, 6)
+	formData.value.latitude = _.round(latitude, 6)
+	setMapPoint(formData.value.longitude, formData.value.latitude)
+	await loadAreaList(formData.value.defenseSceneId)
+}
+
+// 设置地图点位
+function setMapPoint(longitude, latitude) {
+	if (!viewer || longitude == null || latitude == null) return
+	if (!redPointEntity) {
+		redPointEntity = viewer.entities.add({
+			position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
+			billboard: {
+				image: commandPost,
+				width: 40,
+				height: 56,
+				verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+			label: {
+				text: `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`,
+				font: '14px',
+				fillColor: Cesium.Color.WHITE,
+				backgroundColor: Cesium.Color.BLACK.withAlpha(0.6),
+				backgroundPadding: new Cesium.Cartesian2(5, 5),
+				showBackground: true,
+				style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+				horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
+				verticalOrigin: Cesium.VerticalOrigin.CENTER,
+				outlineWidth: 1,
+				outlineColor: Cesium.Color.WHITE,
+				pixelOffset: new Cesium.Cartesian2(0, -72),
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+	} else {
+		redPointEntity.position = Cesium.Cartesian3.fromDegrees(longitude, latitude)
+		redPointEntity.label.text = `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
+	}
+}
+
+// 打开弹框
+async function open({ mode, row } = {}) {
+	dialogMode.value = mode || 'add'
+	formData.value = dialogMode.value === 'add' ? initForm() : { ...row }
+	effectiveDateRange.value = []
+	redPointEntity = null
+	await nextTick()
+	initMap()
+	await getSceneConfigList()
+	if (dialogMode.value !== 'add') {
+		await loadDetail()
+	}
+	await nextTick()
+	setMapPoint(formData.value.longitude, formData.value.latitude)
+	await loadAreaList(formData.value.defenseSceneId)
+}
+
+onMounted(() => {
+	getSceneConfigList()
+})
+
+watch(
+	() => formData.value.defenseSceneId,
+	sceneId => {
+		if (!visible.value) return
+		loadAreaList(sceneId)
+	}
+)
+
+watch(
+	() => visible.value,
+	val => {
+		if (!val) clearAreaDisplay()
+	}
+)
+
+
+defineExpose({
+	open,
+})
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/areaManage/sceneManage/index.vue b/applications/drone-command/src/views/areaManage/sceneManage/index.vue
new file mode 100644
index 0000000..f9f4689
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/sceneManage/index.vue
@@ -0,0 +1,172 @@
+<template>
+	<basic-container>
+		<el-form ref="queryParamsRef" :model="searchParams" class="command-page-history-search">
+			<el-form-item label="场景名称" prop="sceneName">
+				<el-input
+					class="command-input"
+					v-model="searchParams.sceneName"
+					placeholder="请输入"
+					clearable
+					@clear="handleSearch"
+				/>
+			</el-form-item>
+
+			<el-form-item class="history-search-actions">
+				<el-button :icon="RefreshRight" @click="resetForm"></el-button>
+				<el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
+			</el-form-item>
+		</el-form>
+
+		<div class="command-table-toolbar">
+			<el-button :icon="Plus" color="#284FE3" type="primary" @click="openForm('add')">新增</el-button>
+			<el-button :icon="Delete" color="#1A2652" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+		</div>
+
+		<div class="command-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
+			<div class="command-table-content command-table-content-bg">
+				<el-table class="command-table" :data="list" @selection-change="handleSelectionChange">
+					<el-table-column type="selection" width="46" />
+					<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
+
+					<el-table-column prop="sceneName" show-overflow-tooltip label="场景名称" />
+					<el-table-column show-overflow-tooltip label="指挥点经度纬度">
+						<template v-slot="{ row }">
+							{{ formatLocation(row) }}
+						</template>
+					</el-table-column>
+					<el-table-column prop="defenseLeader" show-overflow-tooltip label="防控负责人" />
+					<el-table-column prop="leaderPhone" show-overflow-tooltip label="防控负责人电话" />
+					<el-table-column show-overflow-tooltip label="有效时间" width="300">
+						<template v-slot="{ row }">
+							{{ formatEffectiveDate(row) }}
+						</template>
+					</el-table-column>
+					<el-table-column prop="defenseSceneName" show-overflow-tooltip label="关联场景配置" />
+					<el-table-column label="操作" class-name="operation-btns">
+						<template v-slot="{ row }">
+							<el-link @click="openForm('view', row)">查看</el-link>
+							<el-link @click="openForm('edit', row)">编辑</el-link>
+							<el-link @click="handleDelete(row)">删除</el-link>
+						</template>
+					</el-table-column>
+				</el-table>
+			</div>
+
+			<div class="command-table-pagination">
+				<el-pagination
+					popper-class="command-select-popper"
+					v-model:current-page="searchParams.current"
+					v-model:page-size="searchParams.size"
+					layout="total, prev, pager, next, sizes"
+					:total="total"
+					@change="getList"
+				/>
+			</div>
+		</div>
+
+		<FormDiaLog v-if="dialogVisible" v-model="dialogVisible" ref="dialogRef" @success="getList" />
+	</basic-container>
+</template>
+
+<script setup>
+import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue'
+import { nextTick, onMounted, ref } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { fwDefenseSceneManagePageApi, fwDefenseSceneManageRemoveApi } from './sceneManageApi'
+import FormDiaLog from './FormDiaLog.vue'
+import { saveOperationLog } from '@ztzf/apis'
+import { useRoute } from 'vue-router'
+
+// 初始化查询参数
+const initSearchParams = () => ({
+	sceneName: '', // 场景名称
+	current: 1, // 当前页
+	size: 10, // 每页大小
+})
+
+const searchParams = ref(initSearchParams()) // 查询参数
+const total = ref(0) // 总条数
+const loading = ref(true) // 列表加载中
+const list = ref([]) // 列表数据
+const selectedIds = ref([]) // 勾选的ID列表
+const queryParamsRef = ref(null) // 查询表单实例
+const dialogRef = ref(null) // 弹框实例
+const dialogVisible = ref(false)
+const route = useRoute()
+
+// 获取列表数据
+async function getList() {
+	loading.value = true
+	try {
+		const res = await fwDefenseSceneManagePageApi(searchParams.value)
+		list.value = res?.data?.data?.records ?? []
+		total.value = res?.data?.data?.total ?? 0
+	} finally {
+		loading.value = false
+	}
+}
+
+// 查询
+function handleSearch() {
+	searchParams.value.current = 1
+	getList()
+}
+
+// 重置查询
+function resetForm() {
+	queryParamsRef.value?.resetFields()
+	searchParams.value.current = 1
+	getList()
+}
+
+// 删除
+async function handleDelete(row) {
+	const tips = row ? '该条' : '选中的项'
+	await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', {
+		type: 'warning',
+		customClass: 'command-page-view-message-box',
+		confirmButtonClass: 'command-message-box-confirm',
+		cancelButtonClass: 'command-message-box-cancel',
+	})
+	const ids = row ? row.id : selectedIds.value.join(',')
+	await fwDefenseSceneManageRemoveApi({ ids })
+	saveOperationLog({
+		requestUri: route.path,
+		title: `${route.name || '场景管理'}-删除`,
+		type: 1,
+	})
+	ElMessage.success('删除成功')
+	selectedIds.value = []
+	getList()
+}
+
+// 勾选值设置
+function handleSelectionChange(rows) {
+	selectedIds.value = rows.map(item => item.id)
+}
+
+// 格式化指挥点位置
+function formatLocation(row) {
+	if (row?.longitude == null || row?.latitude == null) return ''
+	return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
+}
+
+// 格式化有效时间
+function formatEffectiveDate(row) {
+	if (!row?.effectiveDateStart && !row?.effectiveDateEnd) return ''
+	return `${row.effectiveDateStart || '-'} ~ ${row.effectiveDateEnd || '-'}`
+}
+
+// 新增/编辑/查看 弹框
+function openForm(mode, row) {
+	dialogVisible.value = true
+	nextTick(() => {
+		dialogRef.value?.open({ mode, row })
+	})
+}
+
+onMounted(() => {
+	getList()
+})
+</script>
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/areaManage/sceneManage/sceneManageApi.js b/applications/drone-command/src/views/areaManage/sceneManage/sceneManageApi.js
new file mode 100644
index 0000000..77464c3
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/sceneManage/sceneManageApi.js
@@ -0,0 +1,38 @@
+import request from '@/axios'
+
+// 分页查询场景管理列表
+export const fwDefenseSceneManagePageApi = params => {
+	return request({
+		url: `/drone-fw/area/fwDefenseSceneManage/page`,
+		method: 'get',
+		params: { descs: 'update_time', ...params },
+	})
+}
+
+// 获取场景管理详情
+export const fwDefenseSceneManageDetailApi = params => {
+	return request({
+		url: `/drone-fw/area/fwDefenseSceneManage/detail`,
+		method: 'get',
+		params,
+	})
+}
+
+// 新增或修改场景管理
+export const fwDefenseSceneManageSubmitApi = data => {
+	return request({
+		url: `/drone-fw/area/fwDefenseSceneManage/submit`,
+		method: 'post',
+		data,
+	})
+}
+
+// 删除场景管理
+export const fwDefenseSceneManageRemoveApi = params => {
+	return request({
+		url: `/drone-fw/area/fwDefenseSceneManage/remove`,
+		method: 'post',
+		params,
+	})
+}
+
diff --git a/applications/drone-command/src/views/audioCall.vue b/applications/drone-command/src/views/audioCall.vue
index 55de074..90910ac 100644
--- a/applications/drone-command/src/views/audioCall.vue
+++ b/applications/drone-command/src/views/audioCall.vue
@@ -1,439 +1,439 @@
 <template>
 	<div style="padding:16px">
-	  <h2>1v1 语音通话 WebRTC Demo(对齐后端 ChatMessage,JS)</h2>
-  
-	  <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
-		<span>我的 userId:</span>
-		<input v-model="uid" style="width:120px" />
-  
-		<span>对方 userId:</span>
-		<input v-model="peerUid" style="width:120px" />
-  
-		<button @click="connectWS" :disabled="connected">连接WS</button>
-  
-		<button @click="requestCall" :disabled="!connected || state!=='idle'">呼叫</button>
-  
-		<button v-if="state==='ringing'" @click="acceptCall">接听</button>
-		<button v-if="state==='ringing'" @click="rejectCall">拒绝</button>
-  
-		<button @click="hangup" :disabled="!connected || (state!=='calling' && state!=='in-call')">挂断</button>
-  
-		<strong>状态:{{ state }}</strong>
-		<strong v-if="state==='in-call'">通话时长:{{ durationText }}</strong>
-	  </div>
-  
-	  <div style="margin-top:8px;color:#666">
-		<small>提示:若 getUserMedia 不可用,请用 https 或 localhost 访问页面。</small>
-	  </div>
-  
-	  <pre style="margin-top:12px;max-height:280px;overflow:auto;">{{ logText }}</pre>
-  
-	  <audio ref="remoteAudio" autoplay></audio>
+		<h2>1v1 语音通话 WebRTC Demo(对齐后端 ChatMessage,JS)</h2>
+
+		<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
+			<span>我的 userId:</span>
+			<input v-model="uid" style="width:120px" />
+
+			<span>对方 userId:</span>
+			<input v-model="peerUid" style="width:120px" />
+
+			<button @click="connectWS" :disabled="connected">连接WS</button>
+
+			<button @click="requestCall" :disabled="!connected || state!=='idle'">呼叫</button>
+
+			<button v-if="state==='ringing'" @click="acceptCall">接听</button>
+			<button v-if="state==='ringing'" @click="rejectCall">拒绝</button>
+
+			<button @click="hangup" :disabled="!connected || (state!=='calling' && state!=='in-call')">挂断</button>
+
+			<strong>状态:{{ state }}</strong>
+			<strong v-if="state==='in-call'">通话时长:{{ durationText }}</strong>
+		</div>
+
+		<div style="margin-top:8px;color:#666">
+			<small>提示:若 getUserMedia 不可用,请用 https 或 localhost 访问页面。</small>
+		</div>
+
+		<pre style="margin-top:12px;max-height:280px;overflow:auto;">{{ logText }}</pre>
+
+		<audio ref="remoteAudio" autoplay></audio>
 	</div>
-  </template>
-  
-  <script setup>
-  import { ref, computed, onBeforeUnmount } from 'vue'
-  
-  /** ✅ 你的 WS 地址前缀(后面拼 userId) */
-  const WS_BASE = 'wss://域名/ws/chat?userId='
-  
-  const uid = ref('2')
-  const peerUid = ref('3')
-  
-  const connected = ref(false)
-  const state = ref('idle') // idle | calling | ringing | in-call
-  const logText = ref('')
-  const remoteAudio = ref(null)
-  
-  let ws = null
-  let pc = null
-  let localStream = null
-  const pendingCandidates = []
-  
-  // 来电暂存
-  let incomingFrom = null
-  let acceptedByMe = false // 我是否已经点了接听
-  let offeredByPeer = false // 是否已收到对方 offer(用于流程判断)
-  
-  // 计时
-  let timer = null
-  const seconds = ref(0)
-  const durationText = computed(() => {
+</template>
+
+<script setup>
+import { ref, computed, onBeforeUnmount } from 'vue'
+
+/** ✅ 你的 WS 地址前缀(后面拼 userId) */
+const WS_BASE = 'wss://域名/ws/chat?userId='
+
+const uid = ref('2')
+const peerUid = ref('3')
+
+const connected = ref(false)
+const state = ref('idle') // idle | calling | ringing | in-call
+const logText = ref('')
+const remoteAudio = ref(null)
+
+let ws = null
+let pc = null
+let localStream = null
+const pendingCandidates = []
+
+// 来电暂存
+let incomingFrom = null
+let acceptedByMe = false // 我是否已经点了接听
+let offeredByPeer = false // 是否已收到对方 offer(用于流程判断)
+
+// 计时
+let timer = null
+const seconds = ref(0)
+const durationText = computed(() => {
 	const m = String(Math.floor(seconds.value / 60)).padStart(2, '0')
 	const s = String(seconds.value % 60).padStart(2, '0')
 	return `${m}:${s}`
-  })
-  function startTimer() {
+})
+function startTimer() {
 	stopTimer()
 	seconds.value = 0
 	timer = setInterval(() => seconds.value++, 1000)
-  }
-  function stopTimer() {
+}
+function stopTimer() {
 	if (timer) clearInterval(timer)
 	timer = null
-  }
-  
-  // 心跳
-  let pingTimer = null
-  function startPing() {
+}
+
+// 心跳
+let pingTimer = null
+function startPing() {
 	stopPing()
 	// 每 25 秒发一次 ping(你后端有 PING/PONG)
 	pingTimer = setInterval(() => {
-	  if (connected.value) send('ping', 'system', null)
+		if (connected.value) send('ping', 'system', null)
 	}, 25000)
-  }
-  function stopPing() {
+}
+function stopPing() {
 	if (pingTimer) clearInterval(pingTimer)
 	pingTimer = null
-  }
-  
-  function log(...args) {
+}
+
+function log(...args) {
 	const line = args.map(v => (typeof v === 'string' ? v : JSON.stringify(v))).join(' ')
 	logText.value += `[${new Date().toLocaleTimeString()}] ${line}\n`
 	console.log(...args)
-  }
-  
-  /**
-   * ✅ 严格按后端 ChatMessage 字段发:
-   * { type, from, to, payload, timestamp }
-   * 注意:后端会用 session 的 userId 覆盖 from,但我们带上也没坏处
-   */
-  function send(type, to, payload) {
+}
+
+/**
+ * ✅ 严格按后端 ChatMessage 字段发:
+ * { type, from, to, payload, timestamp }
+ * 注意:后端会用 session 的 userId 覆盖 from,但我们带上也没坏处
+ */
+function send(type, to, payload) {
 	if (!type) return
 	if (!ws || ws.readyState !== WebSocket.OPEN) return
-  
+
 	const msg = {
-	  type,                 // "call/accept/busy/offer/answer/candidate/hangup/ping"
-	  from: String(uid.value),
-	  to: String(to),
-	  payload: payload ?? null,
-	  timestamp: Date.now(),
+		type,                 // "call/accept/busy/offer/answer/candidate/hangup/ping"
+		from: String(uid.value),
+		to: String(to),
+		payload: payload ?? null,
+		timestamp: Date.now(),
 	}
 	ws.send(JSON.stringify(msg))
-  }
-  
-  /* ---------------- WebRTC ---------------- */
-  
-  function createPC() {
+}
+
+/* ---------------- WebRTC ---------------- */
+
+function createPC() {
 	pc = new RTCPeerConnection({
-	  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
+		iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
 	})
-  
+
 	pc.onicecandidate = (e) => {
-	  if (e.candidate) {
-		send('candidate', peerUid.value, { candidate: e.candidate.toJSON ? e.candidate.toJSON() : e.candidate })
-	  }
+		if (e.candidate) {
+			send('candidate', peerUid.value, { candidate: e.candidate.toJSON ? e.candidate.toJSON() : e.candidate })
+		}
 	}
-  
+
 	pc.ontrack = (e) => {
-	  log('✅ 收到远端音频流')
-	  const stream = e.streams[0]
-	  if (remoteAudio.value) {
-		remoteAudio.value.srcObject = stream
-		remoteAudio.value.play().catch(() => {})
-	  }
+		log('✅ 收到远端音频流')
+		const stream = e.streams[0]
+		if (remoteAudio.value) {
+			remoteAudio.value.srcObject = stream
+			remoteAudio.value.play().catch(() => {})
+		}
 	}
-  
+
 	pc.onconnectionstatechange = () => {
-	  log('pc.connectionState =', pc.connectionState)
+		log('pc.connectionState =', pc.connectionState)
 	}
-  }
-  
-  async function getLocalAudio() {
+}
+
+async function getLocalAudio() {
 	if (localStream) return localStream
-  
+
 	if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
-	  log('❌ 当前环境不支持 getUserMedia:请使用 https 或 localhost 打开页面')
-	  throw new Error('getUserMedia not available')
+		log('❌ 当前环境不支持 getUserMedia:请使用 https 或 localhost 打开页面')
+		throw new Error('getUserMedia not available')
 	}
-  
+
 	try {
-	  localStream = await navigator.mediaDevices.getUserMedia({ audio: true })
-	  log('✅ 已获取本地麦克风')
-	  return localStream
+		localStream = await navigator.mediaDevices.getUserMedia({ audio: true })
+		log('✅ 已获取本地麦克风')
+		return localStream
 	} catch (e) {
-	  log('❌ 获取麦克风失败:', String(e))
-	  throw e
+		log('❌ 获取麦克风失败:', String(e))
+		throw e
 	}
-  }
-  
-  /** ✅ 关键:只 addTrack 一次,避免 “sender already exists” */
-  async function ensureLocalTracksAdded() {
+}
+
+/** ✅ 关键:只 addTrack 一次,避免 “sender already exists” */
+async function ensureLocalTracksAdded() {
 	if (!pc) createPC()
 	const stream = await getLocalAudio()
-  
+
 	const existingTrackIds = new Set(
-	  pc.getSenders().map(s => (s.track ? s.track.id : null)).filter(Boolean)
+		pc.getSenders().map(s => (s.track ? s.track.id : null)).filter(Boolean)
 	)
-  
+
 	stream.getTracks().forEach(track => {
-	  if (!existingTrackIds.has(track.id)) {
-		pc.addTrack(track, stream)
-		log('addTrack ok:', track.id)
-	  } else {
-		// log('skip addTrack:', track.id)
-	  }
+		if (!existingTrackIds.has(track.id)) {
+			pc.addTrack(track, stream)
+			log('addTrack ok:', track.id)
+		} else {
+			// log('skip addTrack:', track.id)
+		}
 	})
-  
+
 	return stream
-  }
-  
-  async function flushCandidates() {
+}
+
+async function flushCandidates() {
 	if (!pc || !pc.remoteDescription) return
 	while (pendingCandidates.length) {
-	  const c = pendingCandidates.shift()
-	  try {
-		await pc.addIceCandidate(new RTCIceCandidate(c))
-	  } catch (e) {
-		log('addIceCandidate failed:', String(e))
-	  }
+		const c = pendingCandidates.shift()
+		try {
+			await pc.addIceCandidate(new RTCIceCandidate(c))
+		} catch (e) {
+			log('addIceCandidate failed:', String(e))
+		}
 	}
-  }
-  
-  /* ---------------- 信令处理 ---------------- */
-  
-  async function onSignal(msg) {
+}
+
+/* ---------------- 信令处理 ---------------- */
+
+async function onSignal(msg) {
 	if (!msg) return
 	const t = (msg.type || '').toString()
-  
+
 	// ✅ 忽略后端回显给自己的消息(你的后端会回显 sender)
 	if (msg.from && String(msg.from) === String(uid.value)) {
-	  return
+		return
 	}
-  
+
 	// system 消息:login/pong/busy 仅记录
 	if (msg.from === 'system') {
-	  log('system msg:', msg)
-	  return
-	}
-  
-	if (!t) return
-  
-	if (t === 'call') {
-	  // 收到来电:弹接听按钮
-	  incomingFrom = String(msg.from)
-	  peerUid.value = incomingFrom
-	  acceptedByMe = false
-	  offeredByPeer = false
-  
-	  state.value = 'ringing'
-	  log('📞 收到来电 call,来自', incomingFrom)
-	  return
-	}
-  
-	if (t === 'accept') {
-	  // 主叫收到 accept:开始 offer
-	  log('✅ 对方接听 accept,开始建立连接(offer)')
-	  await startOffer()
-	  return
-	}
-  
-	if (t === 'busy') {
-	  log('❌ 对方忙线/拒绝')
-	  cleanup(false)
-	  return
-	}
-  
-	if (t === 'offer') {
-	  // 被叫收到 offer:只有点击接听后才真正应答(你要求“先点接听”)
-	  offeredByPeer = true
-	  incomingFrom = String(msg.from)
-	  peerUid.value = incomingFrom
-  
-	  if (!acceptedByMe) {
-		state.value = 'ringing'
-		log('📩 收到 offer(但未接听),已等待用户点击接听')
-		// 暂存 offer 到一个变量
-		lastOfferSdp = msg.payload?.sdp
+		log('system msg:', msg)
 		return
-	  }
-  
-	  // 已接听:直接 answer
-	  await answerOffer(msg.payload?.sdp, incomingFrom)
-	  return
 	}
-  
+
+	if (!t) return
+
+	if (t === 'call') {
+		// 收到来电:弹接听按钮
+		incomingFrom = String(msg.from)
+		peerUid.value = incomingFrom
+		acceptedByMe = false
+		offeredByPeer = false
+
+		state.value = 'ringing'
+		log('📞 收到来电 call,来自', incomingFrom)
+		return
+	}
+
+	if (t === 'accept') {
+		// 主叫收到 accept:开始 offer
+		log('✅ 对方接听 accept,开始建立连接(offer)')
+		await startOffer()
+		return
+	}
+
+	if (t === 'busy') {
+		log('❌ 对方忙线/拒绝')
+		cleanup(false)
+		return
+	}
+
+	if (t === 'offer') {
+		// 被叫收到 offer:只有点击接听后才真正应答(你要求“先点接听”)
+		offeredByPeer = true
+		incomingFrom = String(msg.from)
+		peerUid.value = incomingFrom
+
+		if (!acceptedByMe) {
+			state.value = 'ringing'
+			log('📩 收到 offer(但未接听),已等待用户点击接听')
+			// 暂存 offer 到一个变量
+			lastOfferSdp = msg.payload?.sdp
+			return
+		}
+
+		// 已接听:直接 answer
+		await answerOffer(msg.payload?.sdp, incomingFrom)
+		return
+	}
+
 	if (t === 'answer') {
-	  log('✅ 收到 answer,通话建立')
-	  if (!pc) createPC()
-	  await pc.setRemoteDescription(new RTCSessionDescription(msg.payload?.sdp))
-	  await flushCandidates()
-  
-	  state.value = 'in-call'
-	  startTimer()
-	  return
+		log('✅ 收到 answer,通话建立')
+		if (!pc) createPC()
+		await pc.setRemoteDescription(new RTCSessionDescription(msg.payload?.sdp))
+		await flushCandidates()
+
+		state.value = 'in-call'
+		startTimer()
+		return
 	}
-  
+
 	if (t === 'candidate') {
-	  const c = msg.payload?.candidate
-	  if (!c) return
-	  if (pc && pc.remoteDescription) {
-		await pc.addIceCandidate(new RTCIceCandidate(c))
-	  } else {
-		pendingCandidates.push(c)
-	  }
-	  return
+		const c = msg.payload?.candidate
+		if (!c) return
+		if (pc && pc.remoteDescription) {
+			await pc.addIceCandidate(new RTCIceCandidate(c))
+		} else {
+			pendingCandidates.push(c)
+		}
+		return
 	}
-  
+
 	if (t === 'hangup') {
-	  log('📴 对方挂断')
-	  cleanup(false)
-	  return
+		log('📴 对方挂断')
+		cleanup(false)
+		return
 	}
-  
+
 	if (t === 'pong') {
-	  // 心跳响应
-	  // log('pong')
-	  return
+		// 心跳响应
+		// log('pong')
+		return
 	}
-  }
-  
-  // 暂存 offer(用于“先点接听再 answer”)
-  let lastOfferSdp = null
-  
-  /* ---------------- 业务操作 ---------------- */
-  
-  function connectWS() {
+}
+
+// 暂存 offer(用于“先点接听再 answer”)
+let lastOfferSdp = null
+
+/* ---------------- 业务操作 ---------------- */
+
+function connectWS() {
 	if (ws) ws.close()
-  
+
 	ws = new WebSocket(WS_BASE + encodeURIComponent(uid.value))
-  
+
 	ws.onopen = () => {
-	  connected.value = true
-	  log('WS 已连接 userId=', uid.value)
-	  startPing()
+		connected.value = true
+		log('WS 已连接 userId=', uid.value)
+		startPing()
 	}
-  
+
 	ws.onclose = () => {
-	  connected.value = false
-	  log('WS 已关闭')
-	  stopPing()
+		connected.value = false
+		log('WS 已关闭')
+		stopPing()
 	}
-  
+
 	ws.onerror = (e) => log('WS error', e)
-  
+
 	ws.onmessage = (e) => {
-	  const msg = JSON.parse(e.data)
-	  log('收到信令', msg)
-	  onSignal(msg)
+		const msg = JSON.parse(e.data)
+		log('收到信令', msg)
+		onSignal(msg)
 	}
-  }
-  
-  function requestCall() {
+}
+
+function requestCall() {
 	state.value = 'calling'
 	incomingFrom = null
 	acceptedByMe = false
 	offeredByPeer = false
 	lastOfferSdp = null
-  
+
 	send('call', peerUid.value, null)
 	log('➡️ 发起呼叫 call 给', peerUid.value)
-  }
-  
-  function acceptCall() {
+}
+
+function acceptCall() {
 	if (!incomingFrom) return
 	acceptedByMe = true
-  
+
 	// 先告诉对方我接听了(后端会把双方置忙)
 	send('accept', incomingFrom, null)
 	log('✅ 已点击接听,发送 accept 给', incomingFrom)
-  
+
 	state.value = 'calling'
-  
+
 	// 如果 offer 已经提前到达(我们暂存了),立刻 answer
 	if (lastOfferSdp) {
-	  answerOffer(lastOfferSdp, incomingFrom)
-	  lastOfferSdp = null
+		answerOffer(lastOfferSdp, incomingFrom)
+		lastOfferSdp = null
 	}
-  }
-  
-  function rejectCall() {
+}
+
+function rejectCall() {
 	if (!incomingFrom) return
-  
+
 	// 你后端没有 reject,用 busy 表示拒绝/不可接听
 	send('busy', incomingFrom, null)
 	log('❌ 已拒绝来电,发送 busy 给', incomingFrom)
-  
+
 	incomingFrom = null
 	acceptedByMe = false
 	offeredByPeer = false
 	lastOfferSdp = null
 	state.value = 'idle'
-  }
-  
-  async function startOffer() {
+}
+
+async function startOffer() {
 	if (!pc) createPC()
-  
+
 	// ✅ 不会重复 addTrack
 	await ensureLocalTracksAdded()
-  
+
 	const offer = await pc.createOffer()
 	await pc.setLocalDescription(offer)
-  
+
 	send('offer', peerUid.value, { sdp: pc.localDescription })
 	log('➡️ 已发送 offer 给', peerUid.value)
-  }
-  
-  async function answerOffer(offerSdp, from) {
+}
+
+async function answerOffer(offerSdp, from) {
 	if (!offerSdp) {
-	  log('❌ offer sdp 为空,无法接听')
-	  return
+		log('❌ offer sdp 为空,无法接听')
+		return
 	}
 	if (!pc) createPC()
-  
+
 	await pc.setRemoteDescription(new RTCSessionDescription(offerSdp))
 	await flushCandidates()
-  
+
 	await ensureLocalTracksAdded()
-  
+
 	const answer = await pc.createAnswer()
 	await pc.setLocalDescription(answer)
-  
+
 	send('answer', from, { sdp: pc.localDescription })
 	log('⬅️ 已发送 answer 给', from)
-  
+
 	state.value = 'in-call'
 	startTimer()
-  }
-  
-  function hangup() {
+}
+
+function hangup() {
 	cleanup(true)
-  }
-  
-  /* ---------------- 清理 ---------------- */
-  
-  function cleanup(sendToPeer) {
+}
+
+/* ---------------- 清理 ---------------- */
+
+function cleanup(sendToPeer) {
 	try {
-	  if (sendToPeer) send('hangup', peerUid.value, null)
+		if (sendToPeer) send('hangup', peerUid.value, null)
 	} catch {}
-  
+
 	stopTimer()
-  
+
 	if (pc) {
-	  pc.getSenders().forEach(s => s.track && s.track.stop())
-	  pc.close()
+		pc.getSenders().forEach(s => s.track && s.track.stop())
+		pc.close()
 	}
 	if (localStream) {
-	  localStream.getTracks().forEach(t => t.stop())
+		localStream.getTracks().forEach(t => t.stop())
 	}
-  
+
 	pc = null
 	localStream = null
 	pendingCandidates.length = 0
-  
+
 	incomingFrom = null
 	acceptedByMe = false
 	offeredByPeer = false
 	lastOfferSdp = null
-  
+
 	state.value = 'idle'
-  }
-  
-  onBeforeUnmount(() => {
+}
+
+onBeforeUnmount(() => {
 	cleanup(false)
 	stopPing()
 	if (ws) ws.close()
-  })
-  </script>
+})
+</script>
   
\ No newline at end of file
diff --git a/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue b/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
index 52535aa..0ed4b0d 100644
--- a/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
@@ -2,7 +2,7 @@
  * @Author       : yuan
  * @Date         : 2026-01-17 14:51:39
  * @LastEditors  : yuan
- * @LastEditTime : 2026-01-17 15:13:50
+ * @LastEditTime : 2026-01-30 15:20:56
  * @FilePath     : \applications\drone-command\src\views\dataCockpit\components\LegendBar.vue
  * @Description  : 
  * Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -12,12 +12,16 @@
 	<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>
+			<span class="legend-color detect-area"></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>
+			<span class="legend-color alarm-area"></span>
+			<span class="legend-text" data-text="报警区">报警区</span>
+		</div>
+		<div class="legend-item">
+			<span class="legend-color key-control-area"></span>
+			<span class="legend-text" data-text="重点管制区">重点管制区</span>
 		</div>
 		<div class="legend-item">
 			<img class="legend-icon drone" :src="droneIcon" alt="无人机" />
@@ -84,16 +88,27 @@
 	border-radius: 0;
 }
 
-.legend-color.defense {
-	background: radial-gradient(50% 50% at 50% 50%, rgba(114, 243, 255, 0.45) 0%, rgba(10, 124, 136, 0.55) 100%);
-	border: 1px solid rgba(114, 243, 255, 0.85);
-}
 
 .legend-color.partition {
 	background: radial-gradient(50% 50% at 50% 50%, rgba(255, 215, 114, 0.5) 0%, rgba(168, 107, 0, 0.6) 100%);
 	border: 1px solid rgba(255, 215, 114, 0.9);
 }
 
+.legend-color.detect-area {
+	background-color: rgba(25, 178, 230, 0.5);
+	border: 1px solid rgb(0, 251, 255);
+}
+
+.legend-color.alarm-area {
+	background-color: rgba(255, 235, 59, 0.5);
+	border: 1px solid rgb(255, 235, 59);
+}
+
+.legend-color.key-control-area {
+	background-color: rgba(247, 20, 20, 0.5);
+	border: 1px solid rgb(255, 0, 0);
+}
+
 .legend-icon {
 	width: 30px;
 	height: 42px;
diff --git a/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue b/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
index d983ceb..d9adc8c 100644
--- a/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
+++ b/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
@@ -175,7 +175,7 @@
 const emit = defineEmits(['success'])
 const formRef = ref(null)
 const formData = ref(initForm())
-const visible = ref(false)
+const visible = defineModel()
 const dialogMode = ref('add')
 const submitting = ref(false)
 const isAddChild = ref(false)
@@ -283,7 +283,6 @@
 
 async function open({ mode, row, parent } = {}) {
 	dialogMode.value = mode || 'add'
-	visible.value = true
 	isAddChild.value = mode === 'add' && !!parent?.id
 	await getDeptTree().then(res => {
 		if (deptTree) {
diff --git a/applications/drone-command/src/views/permissionManage/permissionDept/ShareDiaLog.vue b/applications/drone-command/src/views/permissionManage/permissionDept/ShareDiaLog.vue
new file mode 100644
index 0000000..793751e
--- /dev/null
+++ b/applications/drone-command/src/views/permissionManage/permissionDept/ShareDiaLog.vue
@@ -0,0 +1,169 @@
+<template>
+	<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="right-container" style="width: 100%">
+				<div class="header">
+					<span>设备共享</span>
+					<el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
+				</div>
+
+				<div class="content">
+					<div class="command-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
+						<div class="command-table-content">
+							<el-table
+								ref="tableRef"
+								class="command-table"
+								:data="deviceList"
+								row-key="id"
+								@selection-change="handleSelectionChange"
+							>
+								<el-table-column type="selection" width="55" :reserve-selection="true" />
+								<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
+								<el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" />
+								<el-table-column prop="deviceSn" show-overflow-tooltip label="设备SN" />
+								<el-table-column prop="deviceType" show-overflow-tooltip label="设备类型">
+									<template v-slot="{ row }">
+										{{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+									</template>
+								</el-table-column>
+								<el-table-column prop="belongDeptName" show-overflow-tooltip label="所属部门" />
+							</el-table>
+						</div>
+					</div>
+				</div>
+
+				<div class="footer">
+					<el-button color="#2B2B4C" @click="handleCancel">取消</el-button>
+					<el-button color="#284FE3" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
+						确定
+					</el-button>
+				</div>
+			</div>
+		</div>
+	</el-dialog>
+</template>
+
+<script setup>
+import { Close } from '@element-plus/icons-vue'
+import { ref, nextTick } from 'vue'
+import { ElMessage } from 'element-plus'
+import { nonDeptAreaDevicesApi, fwDevicePerShareSaveApi, fwDevicePerShareRemoveApi } from './fwDevicePerShareApi'
+import { getDictionaryByCode } from '@/api/system/dictbiz'
+import { getDictLabel } from '@ztzf/utils'
+
+const emit = defineEmits(['success'])
+const visible = defineModel() // 弹框显隐
+const loading = ref(false) // 加载中
+const submitting = ref(false) // 提交中
+const deviceList = ref([]) // 设备列表
+const tableRef = ref(null) // 表格实例
+const selectedRows = ref([]) // 当前选中的行
+const originalSharedIds = ref([]) // 原始已共享的设备ID集合
+const originalSharedMap = ref({}) // 原始已共享的设备ID与shareId的映射
+const currentDeptId = ref(null) // 当前操作的部门ID
+const dictObj = ref({
+	deviceType: [], // 设备类型
+})
+
+// 获取字典
+function getDictList() {
+	getDictionaryByCode('deviceType').then(res => {
+		dictObj.value = res.data.data
+	})
+}
+
+// 选中变化
+function handleSelectionChange(rows) {
+	selectedRows.value = rows
+}
+
+// 关闭弹框
+function handleCancel() {
+	visible.value = false
+}
+
+// 获取设备列表
+async function getDeviceList() {
+	loading.value = true
+	try {
+		const res = await nonDeptAreaDevicesApi({ loanToDeptId: currentDeptId.value })
+		deviceList.value = res?.data?.data ?? []
+		// 记录原始已共享的设备ID和shareId映射
+		const sharedDevices = deviceList.value.filter(item => item.isShared)
+		originalSharedIds.value = sharedDevices.map(item => item.id)
+		originalSharedMap.value = sharedDevices.reduce((map, item) => {
+			map[item.id] = item.shareId
+			return map
+		}, {})
+		// 回显已共享的设备勾选状态
+		nextTick(() => {
+			deviceList.value.forEach(item => {
+				if (item.isShared) {
+					tableRef.value?.toggleRowSelection(item, true)
+				}
+			})
+		})
+	} finally {
+		loading.value = false
+	}
+}
+
+// 提交
+async function handleSubmit() {
+	submitting.value = true
+	try {
+		const currentSelectedIds = selectedRows.value.map(item => item.id)
+
+		// 计算需要新增的设备(当前选中但原来未共享)
+		const toAddDevices = selectedRows.value.filter(item => !originalSharedIds.value.includes(item.id))
+
+		// 计算需要删除的设备(原来共享但当前未选中),使用shareId
+		const toRemoveShareIds = originalSharedIds.value
+			.filter(id => !currentSelectedIds.includes(id))
+			.map(id => originalSharedMap.value[id])
+			.filter(Boolean)
+
+		const promises = []
+
+		// 调用新增接口
+		if (toAddDevices.length > 0) {
+			const addData = toAddDevices.map(item => ({
+				deviceId: item.id,
+				deviceSn: item.deviceSn,
+				loanFromDeptId: item.belongDept,
+				loanToDeptId: currentDeptId.value,
+			}))
+			promises.push(fwDevicePerShareSaveApi(addData))
+		}
+
+		// 调用删除接口
+		if (toRemoveShareIds.length > 0) {
+			promises.push(fwDevicePerShareRemoveApi({ ids: toRemoveShareIds.join(',') }))
+		}
+
+		if (promises.length > 0) {
+			await Promise.all(promises)
+			ElMessage.success('保存成功')
+		} else {
+			ElMessage.info('未做任何修改')
+		}
+
+		visible.value = false
+		emit('success')
+	} finally {
+		submitting.value = false
+	}
+}
+
+// 打开弹框
+async function open({ row } = {}) {
+	currentDeptId.value = row?.id
+	selectedRows.value = []
+	getDictList()
+	await getDeviceList()
+}
+
+defineExpose({ open })
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/permissionManage/permissionDept/fwDevicePerShareApi.js b/applications/drone-command/src/views/permissionManage/permissionDept/fwDevicePerShareApi.js
new file mode 100644
index 0000000..2194414
--- /dev/null
+++ b/applications/drone-command/src/views/permissionManage/permissionDept/fwDevicePerShareApi.js
@@ -0,0 +1,28 @@
+import request from '@/axios'
+
+// 查询非本部门区域的设备
+export const nonDeptAreaDevicesApi = params => {
+	return request({
+		url: `/drone-fw/device/fwDevice/nonDeptAreaDevices`,
+		method: 'get',
+		params,
+	})
+}
+
+// 新增设备共享
+export const fwDevicePerShareSaveApi = data => {
+	return request({
+		url: `/drone-fw/device/fwDevicePerShare/save`,
+		method: 'post',
+		data,
+	})
+}
+
+// 删除设备共享
+export const fwDevicePerShareRemoveApi = params => {
+	return request({
+		url: `/drone-fw/device/fwDevicePerShare/remove`,
+		method: 'post',
+		params,
+	})
+}
diff --git a/applications/drone-command/src/views/permissionManage/permissionDept/index.vue b/applications/drone-command/src/views/permissionManage/permissionDept/index.vue
index 9930be6..160df0b 100644
--- a/applications/drone-command/src/views/permissionManage/permissionDept/index.vue
+++ b/applications/drone-command/src/views/permissionManage/permissionDept/index.vue
@@ -62,13 +62,14 @@
 					<el-table-column prop="sort" show-overflow-tooltip width="80" label="排序" />
 					<el-table-column prop="bingId" show-overflow-tooltip width="100" label="组织id" />
 					<el-table-column prop="areaName" show-overflow-tooltip width="180" label="行政区划" />
-					
+
 					<el-table-column label="操作" class-name="operation-btns" width="220">
 						<template v-slot="{ row }">
 							<el-link @click="handleView(row)">查看</el-link>
 							<el-link @click="handleEdit(row)">编辑</el-link>
 							<el-link @click="handleAddChild(row)">新增子项</el-link>
 							<el-link type="danger" @click="handleDelete(row)">删除</el-link>
+							<el-link type="danger" @click="handleShare(row)">共享</el-link>
 						</template>
 					</el-table-column>
 				</el-table>
@@ -86,18 +87,20 @@
 			</div>
 		</div>
 
-		<FormDiaLog ref="dialogRef" @success="getList" />
+		<FormDiaLog ref="dialogRef" @success="getList" v-model="visible" v-if="visible" />
+		<ShareDiaLog ref="shareDialogRef" @success="getList" v-model="shareVisible" v-if="shareVisible" />
 	</basic-container>
 </template>
 
 <script setup>
 import { Delete, Plus, RefreshRight, Search } from '@element-plus/icons-vue'
-import { onMounted, provide, ref } from 'vue'
+import { nextTick, onMounted, provide, ref } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { getDictionary } from '@/api/system/dict'
 import { getChildLazyTree, getDeptTree, getLazyList, remove } from '@/api/system/dept'
 import { getDictLabel } from '@ztzf/utils'
 import FormDiaLog from './FormDiaLog.vue'
+import ShareDiaLog from './ShareDiaLog.vue'
 import { saveOperationLog } from '@ztzf/apis'
 import { useRoute } from 'vue-router'
 
@@ -110,11 +113,14 @@
 
 const searchParams = ref(initSearchParams())
 const loading = ref(true)
+const visible = ref(false)
 const list = ref([])
 const total = ref(0)
 const selectedIds = ref([])
 const queryParamsRef = ref(null)
 const dialogRef = ref(null)
+const shareDialogRef = ref(null)
+const shareVisible = ref(false)
 const dictObj = ref({
 	org_category: [],
 	org_nature: [],
@@ -148,19 +154,38 @@
 }
 
 function handleAdd() {
-	dialogRef.value?.open({ mode: 'add' })
+	visible.value = true
+	nextTick(() => {
+		dialogRef.value?.open({ mode: 'add' })
+	})
 }
 
 function handleAddChild(row) {
-	dialogRef.value?.open({ mode: 'add', parent: row })
+	visible.value = true
+	nextTick(() => {
+		dialogRef.value?.open({ mode: 'add', parent: row })
+	})
 }
 
 function handleView(row) {
-	dialogRef.value?.open({ mode: 'view', row: { ...row } })
+	visible.value = true
+	nextTick(() => {
+		dialogRef.value?.open({ mode: 'view', row: { ...row } })
+	})
 }
 
 function handleEdit(row) {
-	dialogRef.value?.open({ mode: 'edit', row: { ...row } })
+	visible.value = true
+	nextTick(() => {
+		dialogRef.value?.open({ mode: 'edit', row: { ...row } })
+	})
+}
+
+function handleShare(row) {
+	shareVisible.value = true
+	nextTick(() => {
+		shareDialogRef.value?.open({ row })
+	})
 }
 
 async function handleDelete(row) {
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/FormDiaLog.vue
index d236c6a..33d2751 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/FormDiaLog.vue
@@ -6,6 +6,7 @@
 		@closed="visible = false"
 		destroy-on-close
 		:close-on-click-modal="false"
+		width="800px"
 	>
 		<div class="detail-container" v-if="dialogReadonly">
 			<el-row class="detail-row-view">
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue
index e6dd00b..d68afc9 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue
@@ -218,6 +218,42 @@
 <!--				<el-button color="#F2F3F5" :loading="submitting" :disabled="submitting" @click="visible = false">关闭</el-button>-->
 			</template>
 		</template>
+
+		<!-- 申请弹框 -->
+		<el-dialog
+			class="gd-dialog"
+			v-model="showApplyDialog"
+			title="申请"
+			@closed="handleApplyDialogClosed"
+			width="500px"
+			destroy-on-close
+			:close-on-click-modal="false"
+		>
+			<el-form class="gd-dialog-form" ref="applyFormRef" :model="applyFormData" :rules="applyRules">
+				<el-row>
+					<el-col :span="24">
+						<el-form-item label="申请对象部门" prop="applyTargetDeptId">
+							<el-tree-select
+								class="gd-select"
+								popper-class="gd-tree-select-popper"
+								v-model="applyFormData.applyTargetDeptId"
+								node-key="id"
+								:data="deptTree"
+								:props="treeProps"
+								check-strictly
+								clearable
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+			</el-form>
+			<template #footer>
+				<el-button color="#F2F3F5" @click="showApplyDialog = false">取消</el-button>
+				<el-button class="" color="#4C34FF" :loading="applying" :disabled="applying" @click="handleApplySubmit">
+					确认
+				</el-button>
+			</template>
+		</el-dialog>
 	</el-dialog>
 </template>
 
@@ -283,6 +319,14 @@
 const attachmentDetailsList = ref([])
 const uploading = ref(false) // 文件上传中状态
 
+// 申请弹框相关
+const showApplyDialog = ref(false)
+const applyFormRef = ref(null)
+const applying = ref(false)
+const applyFormData = ref({
+	applyTargetDeptId: ''
+})
+
 // 表单校验规则
 const rules = {
 	title: fieldRules(true),
@@ -292,7 +336,7 @@
 	catalogResourceName: fieldRules(true),
 	objectionDesc: fieldRules(true),
 	objectionBasis: fieldRules(true),
-	// attachId: dialogMode.value === 'add' ? fieldRules(true) : fieldRules(false)
+	attachId: fieldRules(true)
 
 }
 
@@ -300,6 +344,11 @@
 const feedbackRules = {
 	otherObjectionDetail: fieldRules(true),
 	reviewOpinion: fieldRules(true),
+}
+
+// 申请弹框校验规则
+const applyRules = {
+	applyTargetDeptId: fieldRules(true),
 }
 
 // 自定义上传请求
@@ -323,9 +372,6 @@
 }
 
 // 文件移除
-// const handleFileRemove = (file, fileListVal) => {
-// uploadedFiles.value = fileListVal
-// }
 const handleFileRemove = (file, fileListVal) => {
   // 从展示列表中移除
   fileList.value = fileListVal
@@ -375,6 +421,7 @@
 
 // 提交数据的通用函数
 async function submitForm(objectionStatus) {
+formData.value.attachId = uploadedFiles.value.length > 0 ? uploadedFiles.value.length : ''
   const isValid = await formRef.value?.validate().catch(() => false)
   if (!isValid) return
   
@@ -408,13 +455,6 @@
 			}),
       objectionStatus: objectionStatus
     }
-    
-    // 如果是编辑且没有修改附件,可以保留原有附件ID
-    if (dialogMode.value === 'edit' && formData.value.attachIds) {
-      // 这里可以根据业务需求决定是否保留原附件ID
-      // 如果接口支持更新时重新上传附件ID列表,可以这样做
-    }
-    
     await gdDataObjectionSubmitApi(submitData)
     ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
     visible.value = false
@@ -429,9 +469,66 @@
 	await submitForm('0')
 }
 
-// 申请提交
+// 申请弹框关闭处理
+function handleApplyDialogClosed() {
+	applyFormData.value.applyTargetDeptId = ''
+}
+
+// 申请
 async function handleApply() {
-	await submitForm('1')
+     formData.value.attachId = uploadedFiles.value.length > 0 ? uploadedFiles.value.length : ''
+	 // 先验证主表单
+	 const mainFormValid = await formRef.value?.validate().catch(() => false)
+	 if (!mainFormValid) return
+
+	 showApplyDialog.value = true
+}
+
+// 申请提交
+async function handleApplySubmit() {
+	const isValid = await applyFormRef.value?.validate().catch(() => false)
+	if (!isValid) return
+	applying.value = true
+	try {
+		// 构建申请数据
+		const applyData = {
+			...formData.value,
+			objectionStatus: '1',
+			applyTargetDeptId: applyFormData.value.applyTargetDeptId
+		}
+		
+		// 构建符合接口要求的提交数据
+		const submitData = {
+			// 编辑模式下传递id
+			...(applyData.id && { id: applyData.id }),
+			title: applyData.title,
+			objectionType: applyData.objectionType,
+			submitter: applyData.submitter,
+			submitterContact: applyData.submitterContact,
+			catalogResourceName: applyData.catalogResourceName,
+			objectionDesc: applyData.objectionDesc,
+			objectionBasis: applyData.objectionBasis,
+			areaCode: applyData.areaCode || '',
+			handleUnit: applyData.handleUnit || '',
+			// 构建附件列表 - 使用当前已上传的文件(包括回显的)
+			...(uploadedFiles.value.length > 0 && {
+				attachmentList: uploadedFiles.value.map(file => ({
+					attachId: file.attachId,
+					attachName: file.originalName,
+				})),
+			}),
+			objectionStatus: applyData.objectionStatus,
+			applyTargetDeptId: applyData.applyTargetDeptId
+		}
+		
+		await gdDataObjectionSubmitApi(submitData)
+		ElMessage.success('申请成功')
+		showApplyDialog.value = false
+		visible.value = false
+		emit('success')
+	} finally {
+		applying.value = false
+	}
 }
 
 // 处理异议反馈提交
@@ -509,8 +606,6 @@
     ElMessage.warning('没有可下载的附件')
     return
   }
-
-
   // 顺序下载每个附件
   for (let i = 0; i < attachmentDetailsList.value.length; i++) {
     const attach = attachmentDetailsList.value[i]
@@ -521,10 +616,6 @@
     }
 
     try {
-      // 显示当前下载的文件信息
-      const currentFileName = attach.originalName || attach.name || `附件_${i + 1}`
-   
-      
       const response = await fetch(attach.link)
       const blob = await response.blob()
       const url = window.URL.createObjectURL(blob)
@@ -568,8 +659,6 @@
   }
   
 }
-
-
 // 打开弹框
 async function open({ mode = 'add', row } = {}) {
   dialogMode.value = mode
@@ -580,6 +669,8 @@
   formData.value = dialogMode.value === 'add' ? initForm() : row
   // 重置反馈表单数据
   feedbackFormData.value = initFeedbackForm()
+  // 重置申请弹框数据
+  applyFormData.value.applyTargetDeptId = ''
   
   if (dialogMode.value !== 'add') {
     // 如果是编辑,设置ID并加载详情
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue b/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue
index fc0cc14..10a457d 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/index.vue
@@ -59,7 +59,7 @@
 							{{ getDictLabel(row.objectionStatus, dictObj.objectionStatus) }}
 						</template>
 					</el-table-column>
-					<el-table-column prop="catalogResourceName" width="130" show-overflow-tooltip  label="目录/申请资源名称" />
+					<el-table-column prop="catalogResourceName" width="140" show-overflow-tooltip  label="目录/申请资源名称" />
 					<el-table-column prop="otherObjectionDetail" show-overflow-tooltip label="其他异议详情" />
 					<el-table-column prop="submitter" show-overflow-tooltip label="问题提交人" />
 					<el-table-column prop="submitterContact" show-overflow-tooltip width="120" label="提交人联系方式" />
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/ApplyViewDialog.vue b/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/ApplyViewDialog.vue
index 006b0ac..6f482ac 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/ApplyViewDialog.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/ApplyViewDialog.vue
@@ -68,7 +68,7 @@
 						</el-row>
 				</div>
 				<!--				申请中-->
-				<div v-if="detailDemandStatus === '1' && !requesterProvider">
+				<div v-if="detailDemandStatus === '1' && (permissionList.approvedBtn || permissionList.rejectBtn)">
 					<div class="detail-title" :style="{ marginTop:pxToRem(10)}">数据上传</div>
 					<div class="upload-container">
 						<el-upload
@@ -106,16 +106,16 @@
 			</div>
 		</div>
 		<template #footer>
-			<template v-if="detailDemandStatus === '1' && !requesterProvider">
-				<el-button color="#F2F3F5" :loading="submitting || uploading" :disabled="submitting || uploading" @click="rejectTheApplication">拒绝申请</el-button>
-				<el-button class="" color="#4C34FF" :loading="submitting || uploading" :disabled="submitting || uploading" @click="approvedByTheReview">审核通过</el-button>
+			<template v-if="detailDemandStatus === '1' ">
+				<el-button v-if="permissionList.rejectBtn" color="#F2F3F5" :loading="submitting || uploading" :disabled="submitting || uploading" @click="rejectTheApplication">拒绝申请</el-button>
+				<el-button v-if="permissionList.approvedBtn" class="" color="#4C34FF" :loading="submitting || uploading" :disabled="submitting || uploading" @click="approvedByTheReview">审核通过</el-button>
 			</template>
 			<template v-if="detailDemandStatus === '2'">
 				<el-button class="" color="#4C34FF" @click="downloadAllFiles">全部下载</el-button>
 			</template>
-			<template v-if="detailDemandStatus === '3'">
-				<el-button class="" color="#4C34FF" @click="visible =false">关闭</el-button>
-			</template>
+<!--			<template v-if="detailDemandStatus === '3'">-->
+<!--				<el-button class="" color="#4C34FF" @click="visible =false">关闭</el-button>-->
+<!--			</template>-->
 		</template>
 	</el-dialog>
 
@@ -142,7 +142,7 @@
 import AuditRecord from '@/views/orderView/orderDataManage/supplyAdd/auditRecord.vue' // 导入新组件
 import { useStore } from 'vuex'
 const store = useStore()
-const requesterProvider = computed(() => store.state.user.userInfo?.role_id === '2014158512610869250')
+const permission = computed(() => store.state.user.permission);
 const emit = defineEmits(['success'])
 const dictObj = inject('dictObj')
 const deptTree = inject('deptTree')
@@ -156,6 +156,16 @@
 const responseData = ref(null)
 const submitting = ref(false) // 提交中状态
 const uploading = ref(false) // 文件上传中状态
+function validData(value, defaultValue) {
+	return value !== undefined ? value : defaultValue
+}
+const permissionList = computed(() => {
+	return {
+		addBtn: validData(permission.value.orderData_addDemand, false),
+		rejectBtn: validData(permission.value.orderData_rejectApplication, false),
+		approvedBtn: validData(permission.value.orderData_approved, false),
+	}
+})
 // 自定义上传请求函数
 const handleHttpRequest = async (options) => {
 	uploading.value = true
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/FormDiaLog.vue
index d6ae8f9..9935c07 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/FormDiaLog.vue
@@ -8,7 +8,7 @@
 		:close-on-click-modal="false"
 	>
 		<div class="detail-container" v-if="dialogReadonly">
-			<el-row class="detail-row-view">
+			<el-row class="detail-row-view supplyAddLabel">
 				<el-col :span="12">
 					<div class="label">需求名称</div>
 					<div class="val">{{ formData.demandName }}</div>
@@ -194,9 +194,45 @@
 			>
 				保存
 			</el-button>
-			<el-button color="#4C34FF" :loading="submitting" :disabled="submitting" @click="handleApply">申请</el-button>
+			<el-button color="#4C34FF" :loading="submitting" :disabled="submitting" @click="handleApply" >申请</el-button>
 
 		</template>
+
+		<!-- 申请弹框 -->
+		<el-dialog
+			class="gd-dialog"
+			v-model="showApplyDialog"
+			title="申请"
+			@closed="handleApplyDialogClosed"
+			width="500px"
+			destroy-on-close
+			:close-on-click-modal="false"
+		>
+			<el-form class="gd-dialog-form" ref="applyFormRef" :model="applyFormData" :rules="applyRules">
+				<el-row>
+					<el-col :span="24">
+						<el-form-item label="申请对象部门" prop="applyTargetDeptId">
+							<el-tree-select
+								class="gd-select"
+								popper-class="gd-tree-select-popper"
+								v-model="applyFormData.applyTargetDeptId"
+								node-key="id"
+								:data="deptTree"
+								:props="treeProps"
+								check-strictly
+								clearable
+							/>
+						</el-form-item>
+					</el-col>
+				</el-row>
+			</el-form>
+			<template #footer>
+				<el-button color="#F2F3F5" @click="showApplyDialog = false">取消</el-button>
+				<el-button class="" color="#4C34FF" :loading="applying" :disabled="applying" @click="handleApplySubmit">
+					确认
+				</el-button>
+			</template>
+		</el-dialog>
 	</el-dialog>
 </template>
 
@@ -240,6 +276,14 @@
 const dialogReadonly = computed(() => dialogMode.value === 'view')
 const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
 
+// 申请弹框相关
+const showApplyDialog = ref(false)
+const applyFormRef = ref(null)
+const applying = ref(false)
+const applyFormData = ref({
+	applyTargetDeptId: ''
+})
+
 const rules = {
 	demandName: fieldRules(true),
 	demandType: fieldRules(true),
@@ -253,6 +297,11 @@
 	responsibleDeptId: fieldRules(true),
 	dataSource: fieldRules(true),
 	demandInfo: fieldRules(true),
+}
+
+// 申请弹框校验规则
+const applyRules = {
+	applyTargetDeptId: fieldRules(true),
 }
 // 根据部门ID获取部门名称
 function getDeptNameById(deptId, deptList) {
@@ -295,19 +344,37 @@
 	}
 }
 
-// 申请提交
+// 申请弹框关闭处理
+function handleApplyDialogClosed() {
+	applyFormData.value.applyTargetDeptId = ''
+}
+// 申请
 async function handleApply() {
-	const isValid = await formRef.value?.validate().catch(() => false)
+	 // 先验证主表单
+	 const mainFormValid = await formRef.value?.validate().catch(() => false)
+	 if (!mainFormValid) return
+
+	 showApplyDialog.value = true
+ }
+// 申请提交
+async function handleApplySubmit() {
+	const isValid = await applyFormRef.value?.validate().catch(() => false)
 	if (!isValid) return
-	submitting.value = true
+	applying.value = true
 	try {
-		formData.value.demandStatus = '1'
-		await gdSupplyDemandSubmitApi(formData.value)
+		// 构建申请数据
+		const applyData = {
+			...formData.value,
+			demandStatus: '1',
+			applyTargetDeptId: applyFormData.value.applyTargetDeptId
+		}
+		await gdSupplyDemandSubmitApi(applyData)
 		ElMessage.success('申请成功')
+		showApplyDialog.value = false
 		visible.value = false
 		emit('success')
 	} finally {
-		submitting.value = false
+		applying.value = false
 	}
 }
 
@@ -334,3 +401,10 @@
 
 defineExpose({ open })
 </script>
+<style scoped lang="scss">
+.supplyAddLabel {
+	.label {
+		width: 120px !important;
+	}
+}
+</style>
\ No newline at end of file
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue b/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue
index 76d54e2..ac44ba2 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/supplyAdd/index.vue
@@ -35,7 +35,7 @@
 			</el-form-item>
 		</el-form>
 
-		<div class="gd-table-toolbar">
+		<div class="gd-table-toolbar" v-if="permissionList.addBtn">
 			<el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">供需填报新增</el-button>
 		</div>
 
@@ -79,9 +79,8 @@
 					<el-table-column label="操作" class-name="operation-btns" width="150">
 						<template v-slot="{ row }">
 							<el-link  type="primary"  @click="openForm('view', row)">查看</el-link>
-							<!-- 只对非审批状态显示编辑和删除按钮 -->
-							<el-link  type="primary"  @click="openForm('edit', row)" v-if="!['1', '2', '3'].includes(row.demandStatus)">编辑</el-link>
-							<el-link  type="primary"  @click="handleDelete(row)" v-if="!['1', '2', '3'].includes(row.demandStatus)">删除</el-link>
+							<el-link  type="primary"  @click="openForm('edit', row)" v-if="!['1', '2', '3'].includes(row.demandStatus) && permissionList.addBtn">编辑</el-link>
+							<el-link  type="primary"  @click="handleDelete(row)" v-if="!['1', '2', '3'].includes(row.demandStatus) && permissionList.deleteBtn">删除</el-link>
 						</template>
 					</el-table-column>
 				</el-table>
@@ -116,7 +115,9 @@
 	gdSupplyDemandPageApi,
 	gdSupplyDemandRemoveApi,
 } from '@/views/orderView/orderDataManage/supplyAdd/supplyAddApi'
-
+import { useStore } from 'vuex'
+const store = useStore()
+const permission = computed(() => store.state.user.permission);
 // 初始化查询参数
 const initSearchParams = () => ({
 	demandName: '', // 需求名称
@@ -151,6 +152,17 @@
 provide('detailDemandStatus', detailDemandStatus)
 const reasonForRejection = ref('') //拒绝原因
 provide('reasonForRejection', reasonForRejection)
+
+function validData(value, defaultValue) {
+	return value !== undefined ? value : defaultValue
+}
+const permissionList = computed(() => {
+	return {
+		addBtn: validData(permission.value.orderData_addDemand, false),
+		deleteBtn: validData(permission.value.orderData_delete, false),
+	}
+})
+
 // 根据部门ID获取部门名称
 function getDeptNameById(deptId, deptList) {
 	if (!deptId || !Array.isArray(deptList)) return ''
diff --git a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/RefuseOrderDialog1.vue b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/RefuseOrderDialog1.vue
index 50b19e9..08b4a6f 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/RefuseOrderDialog1.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/RefuseOrderDialog1.vue
@@ -2,7 +2,7 @@
 	<el-dialog
 		class="gd-dialog"
 		v-model="visible"
-		title="编辑"
+		:title="titleEnum[dialogMode]"
 		@closed="visible = false"
 		destroy-on-close
 		:close-on-click-modal="false"
diff --git a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
index 2c62033..b2fa57a 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
@@ -205,34 +205,35 @@
 		</div>
 
 		<template #footer>
-			<template v-if="requester">
-				<el-button v-if="['1', '4'].includes(taskStatus)" @click="viewDescription" color="#F2F3F5">
-					{{ gdStatusObj[taskStatus].reason }}
-				</el-button>
-				<el-button @click="statusChange(3)" v-if="taskStatus === '3'" color="#4C34FF">撤回任务</el-button>
+			<el-button v-if="['1', '4', '7'].includes(taskStatus)" @click="viewDescription" color="#F2F3F5">
+				{{ gdStatusObj[taskStatus].reason }}
+			</el-button>
+			<el-button @click="statusChange(3)" v-if="taskStatus === '3' && permission.flyOrder_revoke" color="#4C34FF">
+				撤回任务
+			</el-button>
+
+			<template v-if="permission.flyOrder_controlAcceptance">
 				<el-button @click="refusalToAccept" v-if="taskStatus === '6'" color="#F2F3F5">拒绝验收</el-button>
 				<el-button @click="statusChange(6)" v-if="taskStatus === '6'" color="#4C34FF">验收通过</el-button>
-				<el-button
-					v-if="!dialogReadonly || ['1', '4'].includes(taskStatus)"
-					:loading="submitting"
-					:disabled="submitting"
-					color="#4C34FF"
-					@click="handleSubmit"
-				>
-					提交
-				</el-button>
 			</template>
-			<template v-else>
-				<el-button v-if="['7'].includes(taskStatus)" @click="viewDescription" color="#F2F3F5">
-					{{ gdStatusObj[taskStatus].reason }}
-				</el-button>
+			<el-button
+				v-if="!dialogReadonly || (['1', '4'].includes(taskStatus) && permission.flyOrder_add)"
+				:loading="submitting"
+				:disabled="submitting"
+				color="#4C34FF"
+				@click="handleSubmit"
+			>
+				提交
+			</el-button>
+			<template v-if="permission.flyOrder_controlSignFor">
 				<el-button v-if="taskStatus === '0'" @click="addDescription" color="#F2F3F5">拒绝签收</el-button>
 				<el-button v-if="taskStatus === '0'" @click="statusChange(1)" color="#4C34FF">签收</el-button>
+			</template>
+			<template v-if="permission.flyOrder_controlReview">
 				<el-button v-if="taskStatus === '3'" @click="addDescription" color="#F2F3F5">驳回</el-button>
 				<el-button v-if="taskStatus === '3'" @click="statusChange(4)" color="#4C34FF">同意</el-button>
 			</template>
 		</template>
-
 		<RefuseOrderDialog1
 			ref="refuseOrderDialogRef"
 			v-if="rejectVisible"
@@ -252,11 +253,11 @@
 import { pxToRem } from '@/utils/rem'
 import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import { gdTaskResultListApi } from '@/views/orderView/orderManage/clueEvents/achievementApi'
-import RefuseOrderDialog from '@/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue'
 import RefuseOrderDialog1 from '@/views/orderView/orderManage/inspectionRequest/RefuseOrderDialog1.vue'
 import { Check } from '@element-plus/icons-vue'
 import { queryAirById } from '@/api/zkxt'
 import * as Cesium from 'cesium'
+import { useStore } from 'vuex'
 
 // 初始化表单数据
 const initForm = () => ({
@@ -270,11 +271,12 @@
 	taskDesc: '',
 	workOrderId: null,
 })
+const store = useStore()
+const permission = computed(() => store.state.user.permission)
 
 const rejectVisible = ref(false)
 const mapRef = ref(null)
 const dictObj = inject('dictObj')
-const requester = inject('requester')
 const emit = defineEmits(['success', 'refusalAccept'])
 const formRef = ref(null)
 const formData = ref(initForm())
@@ -372,7 +374,7 @@
 
 // 获取飞手列表
 async function getFlyerList() {
-	const res = await gdFlyerPageApi({ size: 999,current: 1 })
+	const res = await gdFlyerPageApi({ size: 999, current: 1 })
 	flyerList.value = res?.data?.data?.records ?? []
 }
 
@@ -429,7 +431,14 @@
 	} finally {
 	}
 }
-
+import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
+let arrowLineMaterialProperty = new ArrowLineMaterialProperty({
+	color: new Cesium.Color(128 / 255, 215 / 255, 255 / 255, 1),
+	directionColor: new Cesium.Color(1, 1, 1, 1),
+	outlineColor: new Cesium.Color(1, 1, 1, 1),
+	outlineWidth: 0,
+	speed: 5,
+})
 // 获取航线详情
 function getAirDetails() {
 	queryAirById(formData.value.patrolRouteUrl).then(res => {
@@ -440,8 +449,8 @@
 			polyline: {
 				positions: Cesium.Cartesian3.fromDegreesArray(result),
 				clampToGround: true,
-				width: 3,
-				material: Cesium.Color.RED,
+				width: 4,
+				material: arrowLineMaterialProperty,
 			},
 		})
 		// 渲染起点(蓝色)
@@ -514,23 +523,22 @@
 </script>
 
 <style lang="scss" scoped>
-
 /* 时间线样式 */
 :deep(.gd-timeline) {
- padding-left: 90px;
+	padding-left: 90px;
 }
 
 :deep(.el-timeline-item) {
-  padding-bottom: 20px;
-  .item-content {
- 	position: relative;
-			.flowName {
-				width: 80px;
-				position: absolute;
-				left: -120px;
-				top: 0px;
-			}
-  }
+	padding-bottom: 20px;
+	.item-content {
+		position: relative;
+		.flowName {
+			width: 80px;
+			position: absolute;
+			left: -120px;
+			top: 0px;
+		}
+	}
 }
 .imgBox {
 	display: flex;
@@ -547,8 +555,8 @@
 	}
 }
 :deep(.el-timeline-item__timestamp) {
-  font-size: 12px;
-  color: #999;
-  margin-top: 2px;
+	font-size: 12px;
+	color: #999;
+	margin-top: 2px;
 }
 </style>
diff --git a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
index 08fcba2..bfaf131 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/index.vue
@@ -72,7 +72,7 @@
 		</el-form>
 
 		<div class="gd-table-toolbar">
-			<el-button v-if="requester" :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">新增</el-button>
+			<el-button v-if="permission.flyOrder_add" :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')">拆分工单</el-button>
 		</div>
 
 		<div class="gd-table-container" v-loading="loading">
@@ -139,7 +139,8 @@
 import { airlineListApi } from '@/api/zkxt'
 
 const store = useStore()
-const requester = computed(() => store.state.user.userInfo?.role_id === '2014158512610869250')
+const permission = computed(() => store.state.user.permission)
+
 // 初始化查询参数
 const initSearchParams = () => ({
 	patrolTaskName: '', // 任务名称
@@ -169,7 +170,6 @@
 })
 const activeName = ref('all')
 provide('dictObj', dictObj)
-provide('requester', requester)
 
 // 获取列表
 async function getList() {
@@ -222,7 +222,7 @@
 function viewDiaLogView(row) {
 	viewDiaLogVisible.value = true
 	nextTick(() => {
-		let mode = ['1', '4'].includes(row.taskStatus) && requester.value ? 'edit' : 'view'
+		let mode = ['1', '4'].includes(row.taskStatus) && permission.value.flyOrder_add ? 'edit' : 'view'
 		viewDiaLogRef.value?.open({ mode, row })
 	})
 }
diff --git a/applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
index 94c76cd..a9e3736 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
@@ -176,64 +176,80 @@
 			</div>
 			<div class="rightBox" v-if="!dialogReadonly">
 				<div class="detail-title">推荐设备</div>
-				<template v-if="formData.deviceLoadDemand && pointList.length">
-					<el-table
-						ref="deviceTableRef"
-						:data="deviceList"
-						row-key="id"
-						@selection-change="handleDeviceSelectionChange"
-					>
-						<el-table-column type="selection" width="55" :reserve-selection="true" />
-						<el-table-column prop="nickname" label="设备名称" />
-						<el-table-column prop="deviceName" label="设备型号" />
-						<el-table-column prop="devicePayload" label="设备负载" />
-					</el-table>
-				</template>
-				<div v-else class="tipSelect">请先选择设备需求并在地图上绘制工单范围</div>
+				<el-table
+					:empty-text="(formData.deviceLoadDemand && pointList.length) ? '暂无数据' : '请先选择设备需求并在地图上绘制工单范围'"
+					ref="deviceTableRef"
+					:data="(formData.deviceLoadDemand && pointList.length) ? deviceList : []"
+					row-key="id"
+					@selection-change="handleDeviceSelectionChange"
+				>
+					<el-table-column type="selection" width="55" :reserve-selection="true" />
+					<el-table-column prop="nickname" label="设备名称" />
+					<el-table-column prop="deviceName" label="设备型号" />
+					<el-table-column prop="devicePayload" label="设备负载" />
+				</el-table>
 			</div>
 		</div>
 
 		<template #footer>
-			<template v-if="requester">
-				<template v-if="!suddenlyEdit">
-					<el-button
-						v-if="['11', '21', '23', '24', '25', '31', '60'].includes(gdStatus)"
-						@click="viewDescription"
-						color="#F2F3F5"
-					>
-						{{ gdStatusObj[gdStatus].reason }}
-					</el-button>
-					<el-button @click="addDescription" v-if="['20', '24', '25'].includes(gdStatus)" color="#F2F3F5">
-						申请取消
-					</el-button>
-					<el-button color="#4C34FF" @click="requestModification" v-if="['20', '25'].includes(gdStatus)">
-						申请修改
-					</el-button>
-				</template>
+			<el-button
+				v-if="['11', '21', '23','31', '60'].includes(gdStatus)"
+				@click="viewDescription"
+				color="#F2F3F5"
+			>
+				{{ gdStatusObj[gdStatus].reason }}
+			</el-button>
+
+			<template v-if="!suddenlyEdit">
 				<el-button
-					v-if="['11'].includes(gdStatus) || suddenlyEdit || (!dialogReadonly && !formData.id)"
-					:loading="submitting"
-					:disabled="submitting"
-					color="#4C34FF"
-					@click="handleSubmit"
+					@click="addDescription"
+					v-if="['20'].includes(gdStatus) && permission.order_applyCancel"
+					color="#F2F3F5"
 				>
-					{{ gdStatus === '11' || !gdStatus ? '发布' : '提交修改' }}
+					申请取消
+				</el-button>
+				<el-button
+					color="#4C34FF"
+					@click="requestModification"
+					v-if="['20'].includes(gdStatus) && permission.order_applyNegotiation"
+				>
+					申请修改
 				</el-button>
 			</template>
-			<template v-else>
-				<el-button v-if="['21', '23'].includes(gdStatus)" @click="viewDescription" color="#F2F3F5">
-					{{ gdStatusObj[gdStatus].reason }}
-				</el-button>
-				<el-button v-if="gdStatus === '50'" @click="addDescription" color="#F2F3F5">结算</el-button>
+			<el-button
+				v-if="(['11'].includes(gdStatus) || suddenlyEdit || !formData.id) && permission.order_release"
+				:loading="submitting"
+				:disabled="submitting"
+				color="#4C34FF"
+				@click="handleSubmit"
+			>
+				{{ gdStatus === '11' || !gdStatus ? '发布' : '提交修改' }}
+			</el-button>
+			<el-button v-if="gdStatus === '50' && permission.order_settlement" @click="addDescription" color="#F2F3F5">
+				结算
+			</el-button>
+			<template v-if="permission.order_controlOrder">
 				<el-button v-if="gdStatus === '10'" @click="addDescription" color="#F2F3F5">拒绝接单</el-button>
 				<el-button v-if="gdStatus === '10'" @click="statusChange(1)" color="#4C34FF">接单</el-button>
-				<el-button v-if="gdStatus === '22'" @click="addDescription" color="#F2F3F5">不同意修改</el-button>
-				<el-button v-if="gdStatus === '22'" @click="statusChange(7)" color="#4C34FF">同意修改</el-button>
-				<el-button v-if="gdStatus === '21'" @click="addDescription" color="#F2F3F5">不同意取消</el-button>
-				<el-button v-if="gdStatus === '21'" @click="statusChange(5)" color="#4C34FF">同意取消</el-button>
-				<el-button v-if="gdStatus === '30'" @click="addDescription" color="#4C34FF">协商修改</el-button>
-				<el-button v-if="gdStatus === '31'" @click="statusChange(10)" color="#4C34FF">同意</el-button>
 			</template>
+			<template v-if="permission.order_controlEdit">
+				<el-button v-if="gdStatus === '22'" @click="statusChange(8)" color="#F2F3F5">驳回</el-button>
+				<el-button v-if="gdStatus === '22'" @click="statusChange(7)" color="#4C34FF">同意</el-button>
+			</template>
+			<template v-if="permission.order_controlCancel">
+				<el-button v-if="gdStatus === '21'" @click="statusChange(6)" color="#F2F3F5">驳回</el-button>
+				<el-button v-if="gdStatus === '21'" @click="statusChange(5)" color="#4C34FF">同意</el-button>
+			</template>
+			<el-button v-if="gdStatus === '30' && permission.order_applyNegotiation" @click="addDescription" color="#4C34FF">
+				协商修改
+			</el-button>
+			<el-button
+				v-if="gdStatus === '31' && permission.order_controlNegotiation"
+				@click="statusChange(10)"
+				color="#4C34FF"
+			>
+				同意
+			</el-button>
 		</template>
 
 		<RefuseOrderDialog
@@ -246,7 +262,7 @@
 </template>
 
 <script setup>
-import { computed, ref, onMounted, nextTick } from 'vue'
+import { computed, ref, nextTick } from 'vue'
 import { ElMessage } from 'element-plus'
 import { dateRangeFormat, fieldRules, flyVisual, geomAnalysis, getDictLabel } from '@ztzf/utils'
 import {
@@ -265,6 +281,12 @@
 import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
 import { gdPatrolTaskPageApi } from '@/views/orderView/orderManage/inspectionRequest/inspectionRequestApi'
 import { Check } from '@element-plus/icons-vue'
+import droneIcon from '@/assets/images/orderView/orderManage/drone.png'
+import droneActiveIcon from '@/assets/images/orderView/orderManage/droneActive.png'
+import { useStore } from 'vuex'
+const store = useStore()
+const permission = computed(() => store.state.user.permission)
+
 // 初始化表单数据
 const initForm = () => ({
 	workOrderName: '',
@@ -297,32 +319,25 @@
 let viewer
 let drawPolygonExample
 let pointList = ref([])
-const requester = inject('requester')
 const suddenlyEdit = ref(false)
 const processList = ref([])
 let viewPlane
 const hasPatrolTaskList = computed(() => ['30', '40', '50', '60'].includes(String(formData.value.workOrderStatus)))
-import droneIcon from '@/assets/images/orderView/orderManage/drone.png'
-import droneActiveIcon from '@/assets/images/orderView/orderManage/droneActive.png'
 
 const gdStatusObj = {
-	'10': { reason: '拒绝原因', operationType: '2' },
-	'11': { reason: '拒绝原因' },
+	'10': { reason: '驳回原因', operationType: '2' },
+	'11': { reason: '驳回原因' },
 	'20': { reason: '取消原因', operationType: '3' },
-	'21': { reason: '原因', operationType: '6' },
-	'22': { reason: '驳回原因', operationType: '8' },
 	'23': { reason: '取消原因' },
-	'24': { reason: '驳回原因', operationType: '3' },
-	'25': { reason: '驳回原因', operationType: '3' },
-	'30': { reason: '修改原因', operationType: '9' },
-	'31': { reason: '修改原因' },
+	'30': { reason: '协商修改原因', operationType: '9' },
+	'31': { reason: '协商修改原因' },
 	'40': { reason: '' },
 	'50': { reason: '情况说明', operationType: '11' },
 	'60': { reason: '情况说明' },
 }
 
 // 工单状态:0草稿、10发布中_接单中、11发布中_拒绝接单、20响应中_待拆分、21响应中_申请取消、22响应中_申请修改、23响应中_已取消、
-// 24响应中_拒绝取消、25响应中_拒绝修改 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、50验收通过_待结算、60结算完成_已结算
+// 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、50验收通过_待结算、60结算完成_已结算
 const gdStatus = computed(() => formData.value?.workOrderStatus)
 
 const rules = {
@@ -595,9 +610,7 @@
 		const image = entity.billboard?.image?._value
 		if ((image === droneIcon || image === droneActiveIcon) && entity.label) {
 			const device = deviceList.value.find(d => d.nickname === entity.label.text._value)
-			entity.billboard.image = device && selectedIds.includes(String(device.id))
-				? droneActiveIcon
-				: droneIcon
+			entity.billboard.image = device && selectedIds.includes(String(device.id)) ? droneActiveIcon : droneIcon
 		}
 	})
 }
@@ -622,6 +635,18 @@
 </script>
 
 <style lang="scss" scoped>
+.el-table{
+	height: 0;
+	flex: 1;
+	:deep(){
+		.el-scrollbar__view{
+			height: 100%;
+		}
+		.el-table__empty-text{
+			line-height: normal;
+		}
+	}
+}
 .content {
 	display: flex;
 	gap: 0 20px;
@@ -643,6 +668,8 @@
 
 	.rightBox {
 		width: 350px;
+		display: flex;
+		flex-direction: column;
 		.title {
 			font-weight: 500;
 			font-size: 14px;
diff --git a/applications/task-work-order/src/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue b/applications/task-work-order/src/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue
index adfa2b9..4a28286 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/orderManage/RefuseOrderDialog.vue
@@ -2,7 +2,7 @@
 	<el-dialog
 		class="gd-dialog"
 		v-model="visible"
-		title="编辑"
+		:title="titleEnum[dialogMode]"
 		@closed="visible = false"
 		destroy-on-close
 		:close-on-click-modal="false"
diff --git a/applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue b/applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
index 7864d2f..da197d7 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
@@ -72,17 +72,31 @@
 		</el-form>
 
 		<div class="gd-table-toolbar">
-<!--			<el-button color="#F2F3F5" @click="requester = !requester">我是{{ requester ? '需求方' : '服务方' }}</el-button>-->
-<!--			<el-button color="#F2F3F5" @click="previewVisible = true">预览</el-button>-->
 
-			<el-button :icon="Plus" color="#4C34FF" type="primary" @click="openForm('add')" v-if="requester">新增</el-button>
-			<el-button :icon="Delete" color="#4C34FF" :disabled="!selectedIds.length" @click="handleDelete()" v-if="requester">删除</el-button>
+			<el-button
+				v-if="permission.order_release"
+				:icon="Plus"
+				color="#4C34FF"
+				type="primary"
+				@click="openForm('add')"
+			>
+				新增
+			</el-button>
+<!--			<el-button
+				:icon="Delete"
+				color="#4C34FF"
+				:disabled="!selectedIds.length"
+				@click="handleDelete()"
+				v-if="permission.order_delete"
+			>
+				删除
+			</el-button>-->
 		</div>
 
 		<div class="gd-table-container" v-loading="loading">
 			<div class="gd-table-content gd-table-content-bg">
 				<el-table class="gd-table" :data="list" @selection-change="handleSelectionChange">
-					<el-table-column type="selection" width="46" v-if="requester"/>
+					<el-table-column type="selection" width="46" v-if="permission.order_delete" />
 					<el-table-column type="index" width="64" label="序号" />
 					<el-table-column prop="workOrderName" show-overflow-tooltip label="工单名称" />
 					<el-table-column prop="workOrderCode" show-overflow-tooltip label="工单编号" />
@@ -112,9 +126,9 @@
 					<el-table-column prop="createTime" show-overflow-tooltip label="工单创建时间" />
 					<el-table-column label="操作" class-name="operation-btns">
 						<template v-slot="{ row }">
-							<el-link  type="primary"  @click="openFormChange(row)">查看</el-link>
+							<el-link type="primary" @click="openFormChange(row)">查看</el-link>
 							<!--<el-link  type="primary"  @click="openForm('edit', row)">编辑</el-link>-->
-							<el-link  type="primary"  @click="handleDelete(row)" v-if="requester">删除</el-link>
+<!--							<el-link type="primary" @click="handleDelete(row)" v-if="permission.order_delete">删除</el-link>-->
 						</template>
 					</el-table-column>
 				</el-table>
@@ -132,7 +146,7 @@
 			</div>
 		</div>
 
-		<PreviewFiles v-model="previewVisible" :src="testDock" type="docx"/>
+		<PreviewFiles v-model="previewVisible" :src="testDock" type="docx" />
 		<FormDiaLog ref="dialogRef" @success="getList" v-if="dialogVisible" v-model="dialogVisible" />
 	</basic-container>
 </template>
@@ -151,8 +165,10 @@
 import { useStore } from 'vuex'
 
 const store = useStore()
-const requester = computed(() => store.state.user.userInfo?.role_id === '2014158512610869250')
 const activeName = ref('all')
+
+const permission = computed(() => store.state.user.permission)
+console.log(permission.value, 'permission')
 
 // 初始化查询参数
 const initSearchParams = () => ({
@@ -164,7 +180,6 @@
 	current: 1, // 当前页
 	size: 10, // 每页大小
 })
-// const requester = ref(true)
 const searchParams = ref(initSearchParams()) // 查询参数
 const dateRange = ref([]) // 执行时间范围
 const total = ref(0) // 总条数
@@ -181,8 +196,6 @@
 	workOrderStatus: [], // 工单状态
 })
 provide('dictObj', dictObj)
-provide('requester', requester)
-
 
 // 获取列表
 async function getList() {
@@ -230,13 +243,13 @@
 	})
 }
 // 工单状态:0草稿、10发布中_接单中、11发布中_拒绝接单、20响应中_待拆分、
-// 21响应中_申请取消、22响应中_申请修改、23响应中_已取消、24响应中_拒绝取消、
-// 25响应中_拒绝修改 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、
+// 21响应中_申请取消、22响应中_申请修改、23响应中_已取消、
+// 30执行中_待全部完成、31执行中_协商修改、40完成待验_待全部验收、
 // 50验收通过_待结算、60结算完成_已结算
 function openFormChange(row) {
 	dialogVisible.value = true
 	nextTick(() => {
-		const mode = ['11'].includes(row.workOrderStatus) && requester.value ? 'edit' : 'view'
+		const mode = ['11'].includes(row.workOrderStatus) && permission.value.order_release ? 'edit' : 'view'
 		dialogRef.value?.open({ mode, row })
 	})
 }
diff --git a/packages/constants/areaStyles.js b/packages/constants/areaStyles.js
new file mode 100644
index 0000000..bcf6093
--- /dev/null
+++ b/packages/constants/areaStyles.js
@@ -0,0 +1,36 @@
+import * as Cesium from 'cesium'
+
+export const AREA_TYPE_STYLE_MAP = {
+	'1': {
+		fill: Cesium.Color.fromBytes(25, 178, 230, 128),
+		outline: Cesium.Color.fromBytes(0, 251, 255, 255),
+	},
+	'2': {
+		fill: Cesium.Color.fromBytes(255, 235, 59, 128),
+		outline: Cesium.Color.fromBytes(255, 235, 59, 255),
+	},
+	'3': {
+		fill: Cesium.Color.fromBytes(247, 20, 20, 128),
+		outline: Cesium.Color.fromBytes(255, 0, 0, 255),
+	},
+}
+
+export const DEFAULT_AREA_STYLE = {
+	fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+	outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+
+export const BUFFER_LEVEL_STYLES = [
+	{
+		fill: Cesium.Color.fromBytes(247, 20, 20, 128),
+		outline: Cesium.Color.fromBytes(255, 0, 0, 255),
+	},
+	{
+		fill: Cesium.Color.fromBytes(255, 235, 59, 128),
+		outline: Cesium.Color.fromBytes(255, 235, 59, 255),
+	},
+	{
+		fill: Cesium.Color.fromBytes(25, 178, 230, 128),
+		outline: Cesium.Color.fromBytes(0, 251, 255, 255),
+	},
+]
diff --git a/packages/constants/index.js b/packages/constants/index.js
index 05fd3f4..24549e7 100644
--- a/packages/constants/index.js
+++ b/packages/constants/index.js
@@ -6,3 +6,4 @@
 export * from './publicEnums';
 export * from './workEnums';
 export * from './fwDevice';
+export * from './areaStyles';
diff --git a/uniapps/work-app/src/pages/login/index.vue b/uniapps/work-app/src/pages/login/index.vue
index 9e37a79..dfd006d 100644
--- a/uniapps/work-app/src/pages/login/index.vue
+++ b/uniapps/work-app/src/pages/login/index.vue
@@ -82,8 +82,8 @@
   });
 }
 const loginFormGd = ref({
-  username: "admin",
-  password: "admin",
+  username: "zhx",
+  password: "123456",
 });
 async function submit() {
 
diff --git a/uniapps/work-app/src/subPackages/workDetail/index.vue b/uniapps/work-app/src/subPackages/workDetail/index.vue
index 71ed67e..1244b81 100644
--- a/uniapps/work-app/src/subPackages/workDetail/index.vue
+++ b/uniapps/work-app/src/subPackages/workDetail/index.vue
@@ -4,7 +4,8 @@
 		<!-- 自定义导航栏 -->
 		<u-navbar title="工单详情" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick">
 			<template #right>
-				<div class="share-btn" @click="onShareClick"></div>
+        <div v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg"  @click="onShareClick"/></div>
+				<div v-else class="share-btn" @click="onShareClick"></div>
 			</template>
 		</u-navbar>
 		<div class="detailTop">
@@ -71,7 +72,7 @@
         <up-button type="primary" color="#1D6FE9" text="确认" @click="confirmTheTicket"></up-button>
       </div>
       <div class="btngroups" v-if="workDetailData.eventStatus === 3">
-        <up-button type="primary" color="#1D6FE9" text="已确认" ></up-button>
+        <up-button type="primary" color="#1D6FE9" text="已确认" @click="confirmedHandle"></up-button>
       </div>
     </div>
     <!-- 分享弹出层 -->
@@ -102,7 +103,6 @@
 
 <script setup>
 // import { getShowImg, getSmallImg } from '@/utils/util'
-import { ref, computed } from 'vue'
 import {getGddetailedData,backGdApi} from '/src/api/work/index.js'
 import dayjs from 'dayjs'
 const formatDate = dateString => {
@@ -111,9 +111,12 @@
 const eventNum = ref('')
 // 工单内容
 const workDetailData = ref({})
+// 环境判断
+const isH5 = ref(false)
 onLoad(async options => {
   eventNum.value = options.id
   await getDataList(options.id)
+  checkEnvironment()
 })
 const getDataList = async val => {
   const params = {
@@ -122,6 +125,19 @@
   const res = await getGddetailedData(params)
   const response = res.data.data
   workDetailData.value = response
+}
+// 检查运行环境
+const checkEnvironment = () => {
+  const systemInfo = uni.getSystemInfoSync()
+  // #ifdef APP-PLUS
+  isH5.value = false
+  // #endif
+
+  // #ifdef H5
+  isH5.value = true
+  // #endif
+
+  console.log('当前环境:', isH5.value ? 'H5' : 'App')
 }
 // 图片预览
 const previewImage = index => {
@@ -180,6 +196,10 @@
       uni.navigateBack()
     })
   })
+}
+// 已确认
+const confirmedHandle = () => {
+  uni.navigateBack()
 }
 // 分享模态框状态
 const showShareModal = ref(false)
@@ -312,12 +332,22 @@
   }
 }  // 分享按钮样式
   .share-btn {
+    // #ifdef APP-PLUS
     width: 40rpx;
     height: 40rpx;
     background-image: url('/static/images/work/share.svg');
     background-size: contain;
     background-repeat: no-repeat;
     background-position: center;
+    // #endif
+  }
+  .tapShare-icon {
+    width: 40rpx;
+    height: 40rpx;
+    img {
+      width: 40rpx;
+      height: 40rpx;
+    }
   }
   .detailTop {
   // #ifdef APP-PLUS

--
Gitblit v1.9.3