From 76ad0866ab16079b054590cfd0dcc10c439f526b Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Thu, 29 Jan 2026 10:27:48 +0800
Subject: [PATCH] feat:区域划分调整

---
 applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue |  767 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 724 insertions(+), 43 deletions(-)

diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 302d14e..c253bbf 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -12,6 +12,54 @@
 					: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">
@@ -132,6 +180,28 @@
 						</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="类型">
+								<template v-slot="{ row }">
+									{{ getDictLabel(row.areaType, dictObj.areaType) }}
+								</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">
@@ -173,15 +243,17 @@
 <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 } from './shapeTools/DrawManager'
+import { EditManager } from './shapeTools/EditManager'
 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'
@@ -219,9 +291,39 @@
 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 drawPolygonExample
+let drawManager
+let editManager
+let activeTool
 let pointList = []
+let viewEntity
+let shapeDisplaySource
+const activeToolMode = ref('')
 
 const rules = {
 	areaName: fieldRules(true, 50),
@@ -274,6 +376,13 @@
 
 // 提交新增/编辑
 async function handleSubmit () {
+	if (!shapeList.value.length) {
+		ElMessage.warning('区域不可为空,请先绘制区域')
+		return
+	}
+
+	console.log(shapeList.value, 1111111)
+
 	const isValid = await formRef.value?.validate().catch(() => false)
 	if (!isValid) return
 	if (dialogMode.value === 'add' && Number(formData.value.areaSize) < 0.5) {
@@ -306,6 +415,24 @@
 		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 () {
@@ -334,14 +461,260 @@
 	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 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 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 (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
+		}
+
+		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 = []
+		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 => {
 		const val = cartesian3Convert(item, viewer)
 		return { ...val, lng: val.longitude, lat: val.latitude }
 	})
+	applyPolygonMetrics(pointList)
+	const controlPoints = resolveControlPoints(meta, currentShapeType.value)
+	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 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: formData.value.areaType,
+		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 applyPolygonMetrics(points) {
+	if (!points?.length) return
 	const polygon = turf.polygon([
-		[...pointList.map(item => [item.longitude, item.latitude]), [pointList[0].longitude, pointList[0].latitude]],
+		[...points.map(item => [item.longitude, item.latitude]), [points[0].longitude, points[0].latitude]],
 	])
 	const center = turf.centerOfMass(polygon)
 	const areaSqm = turf.area(polygon) // 平方米
@@ -350,41 +723,163 @@
 	formData.value.latitude = center.geometry.coordinates[1]
 }
 
-// 新增模式绘制
-function addPolygon () {
-	drawPolygonExample = new DrawPolygon(viewer)
-	drawPolygonExample.initHandler(viewer)
-	drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
+function getDetectAreaTypeKey () {
+	const areaTypeOptions = Array.isArray(dictObj?.value?.areaType)
+		? dictObj.value.areaType
+		: dictObj?.areaType
+
+
+	console.log(areaTypeOptions, 1)
+
+	if (!Array.isArray(areaTypeOptions)) return ''
+	const target = areaTypeOptions.find(item => item?.dictKey === '1')
+	return target?.dictKey ?? ''
 }
 
-// 编辑面
-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)
+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') {
+		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()
+			}
 		}
+		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
+	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)
+	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()
 	}
 }
 
@@ -393,7 +888,7 @@
 	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 +898,7 @@
 			material: Cesium.Color.RED,
 		},
 	})
-	viewer.flyTo(mian, { duration: 0 })
+	viewer.flyTo(viewEntity, { duration: 0 })
 }
 
 // 获取派出所列表
@@ -449,14 +944,32 @@
 	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') {
-		addPolygon()
+		// default no draw mode
 	} else if (dialogMode.value === 'edit') {
 		await loadDetail()
-		editPolygon()
+		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()
 	} else {
 		await loadDetail()
 		viewPolygon()
@@ -467,7 +980,10 @@
 
 onMounted(() => {
 	getPoliceStationList()
-	getDeviceList()
+})
+
+onBeforeUnmount(() => {
+	hideTypePanel()
 })
 
 defineExpose({
@@ -475,4 +991,169 @@
 })
 </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 {
+	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 {
+	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>

--
Gitblit v1.9.3