From f083281ab75f61390a04daa2484f330f8df4178f Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Thu, 29 Jan 2026 08:44:20 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue     |  168 ++-
 applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js               |   34 
 applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js                   |   58 +
 applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js                |   89 +
 applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js      |  218 +++
 applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue                     | 1065 +++++++++++++++++++
 applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue               |    2 
 applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js      |  162 ++
 applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js    |  198 +++
 applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue          |    9 
 applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js |  236 ++++
 applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js |  314 +++++
 applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js                  |   24 
 applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue                          |    2 
 applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js               |   34 
 applications/drone-command/env/.env.development                                                   |    4 
 applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js      |  244 ++++
 applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js    |  150 ++
 applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js      |  233 ++++
 applications/task-work-order/env/.env.development                                                 |    4 
 20 files changed, 3,182 insertions(+), 66 deletions(-)

diff --git a/applications/drone-command/env/.env.development b/applications/drone-command/env/.env.development
index 82b533b..6524616 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-26 17:29:11
+ # @LastEditTime : 2026-01-28 09:29:05
  # @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.204
+VITE_APP_URL= http://192.168.1.227
 
 #新大屏地址
 VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/'
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue
new file mode 100644
index 0000000..cc492ca
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue
@@ -0,0 +1,1065 @@
+<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
+					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">
+				<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.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>
+							<div class="val">
+								{{ getDictLabel(formData.triggerCondition, dictObj.triggeringCondition) }}
+							</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">响应机制</div>
+							<div class="val">{{ formData.responseMechanism }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">管控级别</div>
+							<div class="val">{{ getDictLabel(formData.controlLevel, dictObj.controlLevel) }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">关联派出所</div>
+							<div class="val">{{ formData.policeStationName }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div class="label">可飞行时间段</div>
+							<div class="val">{{ formatFlyDate(formData) }}</div>
+						</el-col>
+					</el-row>
+
+					<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-column prop="deviceName" label="设备名称" />
+								<el-table-column prop="deviceType" label="类型">
+									<template v-slot="{ row }">
+										{{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+									</template>
+								</el-table-column>
+							</el-table>
+						</div>
+					</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="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-form-item>
+					<el-form-item label="响应机制" prop="responseMechanism">
+						<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"
+							v-model="formData.triggerCondition" placeholder="请选择" clearable>
+							<el-option v-for="item in dictObj.triggeringCondition" :key="item.dictKey"
+								:label="item.dictValue" :value="item.dictKey" />
+						</el-select>
+					</el-form-item>
+					<el-form-item label="管控级别" prop="controlLevel">
+						<el-select class="command-select" popper-class="command-select-popper"
+							v-model="formData.controlLevel" placeholder="请选择" clearable>
+							<el-option v-for="item in dictObj.controlLevel" :key="item.dictKey" :label="item.dictValue"
+								:value="item.dictKey" />
+						</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"
+							value-format="YYYY-MM-DD HH:mm:ss" clearable />
+					</el-form-item>
+					<el-form-item label="关联派出所" prop="policeStationId">
+						<el-select class="command-select" popper-class="command-select-popper"
+							v-model="formData.policeStationId" placeholder="请选择" clearable>
+							<el-option v-for="item in policeStationOptions" :key="item.id" :label="item.stationName"
+								:value="item.id" />
+						</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">
+								关联设备
+							</div>
+
+							<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">
+								<el-table-column type="selection" width="55" :reserve-selection="true" />
+								<el-table-column prop="deviceName" label="设备名称" />
+								<el-table-column prop="deviceType" label="类型">
+									<template v-slot="{ row }">
+										{{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+									</template>
+								</el-table-column>
+							</el-table>
+						</el-form-item>
+					</div>
+				</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, 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 { 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'
+import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice'
+
+const initForm = () => ({
+	areaName: '', // 区域名称
+	areaSize: null, // 区域面积
+	areaType: '', // 区域类型
+	controlLevel: '', // 管控级别
+	deviceIds: '', // 关联设备ID
+	flyDateEnd: '', // 可飞行结束
+	flyDateStart: '', // 可飞行开始
+	latitude: null, // 区域中心纬度
+	longitude: null, // 区域中心经度
+	policeStationId: '', // 关联派出所id
+	responseMechanism: '', // 响应机制
+	triggerCondition: '', // 触发条件
+})
+
+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 route = useRoute()
+const titleEnum = ref({ edit: '编辑区域', view: '查看区域', add: '新增区域' })
+const flyDateRange = ref([]) // 飞行时间
+const deviceTableRef = ref(null)
+const searchName = ref('')
+const selectedDeviceRows = ref([])
+const policeStationOptions = ref([]) // 关联派出所
+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 pointList = []
+let viewEntity
+let shapeDisplaySource
+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),
+	flyDateStart: fieldRules(true),
+	policeStationId: fieldRules(true),
+	deviceIds: fieldRules(false),
+}
+
+function isSameDay (left, right) {
+	return (
+		left.getFullYear() === right.getFullYear() &&
+		left.getMonth() === right.getMonth() &&
+		left.getDate() === right.getDate()
+	)
+}
+
+const disabledFlyDate = time => {
+	const startOfToday = new Date()
+	startOfToday.setHours(0, 0, 0, 0)
+	return time.getTime() < startOfToday.getTime()
+}
+
+const disabledFlyTime = date => {
+	if (!date) return {}
+	const now = new Date()
+	if (!isSameDay(date, now)) return {}
+	const currentHour = now.getHours()
+	const currentMinute = now.getMinutes()
+	const currentSecond = now.getSeconds()
+	return {
+		disabledHours: () => Array.from({ length: currentHour }, (_, index) => index),
+		disabledMinutes: hour =>
+			hour === currentHour ? Array.from({ length: currentMinute }, (_, index) => index) : [],
+		disabledSeconds: (hour, minute) =>
+			hour === currentHour && minute === currentMinute
+				? Array.from({ length: currentSecond }, (_, index) => index)
+				: [],
+	}
+}
+
+// 关闭弹框
+function handleCancel () {
+	visible.value = false
+}
+
+// 提交新增/编辑
+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²,请重新绘制')
+		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 actionText = dialogMode.value === 'add' ? '新增' : '编辑'
+		saveOperationLog({
+			requestUri: route.path,
+			title: `${route.name || '区域划分'}-${actionText}`,
+			type: 1,
+		})
+		ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
+		visible.value = false
+		emit('success')
+	} finally {
+		submitting.value = false
+	}
+}
+
+watch(
+	() => flyDateRange.value,
+	() => {
+		formData.value.flyDateStart = flyDateRange.value[0] || ''
+		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 () {
+	if (!formData.value.id) return
+	const res = await fwAreaDivideDetailApi({ id: formData.value.id })
+	formData.value = res?.data?.data ?? initForm()
+	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)}`
+}
+
+// 格式化可飞行时间段
+function formatFlyDate (row) {
+	if (!row?.flyDateStart && !row?.flyDateEnd) return ''
+	return `${row.flyDateStart || '-'} ~ ${row.flyDateEnd || '-'}`
+}
+
+// 初始化地图实例
+function initMap () {
+	if (viewer) return
+	const map = mapRef.value?.getMap()
+	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 => {
+		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]],
+	])
+	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 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 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()
+			}
+		}
+		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()
+	}
+}
+
+// 查看面
+function viewPolygon () {
+	if (!formData.value?.geom) return
+	pointList = geomAnalysis(formData.value.geom)
+	const result = pointList.map(item => [item.longitude, item.latitude]).flat()
+	viewEntity = viewer.entities?.add({
+		customType: 'control_group',
+		position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
+		polyline: {
+			positions: Cesium.Cartesian3.fromDegreesArray(result),
+			clampToGround: true,
+			width: 3,
+			material: Cesium.Color.RED,
+		},
+	})
+	viewer.flyTo(viewEntity, { duration: 0 })
+}
+
+// 获取派出所列表
+async function getPoliceStationList () {
+	const res = await fwPoliceStationListApi()
+	policeStationOptions.value = res?.data?.data ?? []
+}
+
+// 获取设备列表
+async function getDeviceList () {
+	if (deviceOptions.value.length) return
+	const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id, isTrack: 1 })
+	deviceOptions.value = res?.data?.data ?? []
+	if (dialogMode.value === 'view') {
+		deviceOptions.value = deviceOptions.value.filter(item => formData.value.deviceIds.includes(item.id))
+	}
+}
+
+// 关联设备变更
+function handleDeviceSelectionChange (rows) {
+	selectedDeviceRows.value = rows
+	const ids = rows.map(item => item.id)
+	formData.value.deviceIds = ids.join(',')
+}
+
+// 同步选择状态
+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
+}
+
+// 打开弹框
+async function open ({ mode, row } = {}) {
+	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
+	} 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()
+	} else {
+		await loadDetail()
+		viewPolygon()
+	}
+	await nextTick()
+	syncDeviceSelection()
+}
+
+onMounted(() => {
+	getPoliceStationList()
+	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>
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 8d4a069..302d14e 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -1,4 +1,4 @@
-<template>
+<template>
 	<el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false"
 		:close-on-click-modal="false">
 		<div class="dialog-container">
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js
new file mode 100644
index 0000000..fdaa63d
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js
@@ -0,0 +1,34 @@
+import { DrawPolygonTool } from './draw/DrawPolygonTool'
+import { DrawRectangleTool } from './draw/DrawRectangleTool'
+import { DrawEllipseTool } from './draw/DrawEllipseTool'
+import { DrawBufferCircleTool } from './draw/DrawBufferCircleTool'
+
+const DRAW_TOOL_MAP = {
+	polygon: DrawPolygonTool,
+	rectangle: DrawRectangleTool,
+	ellipse: DrawEllipseTool,
+	buffer: DrawBufferCircleTool,
+}
+
+export class DrawManager {
+	constructor(viewer) {
+		this.viewer = viewer
+		this.activeTool = null
+	}
+
+	start(type, options = {}) {
+		this.destroy()
+		const Tool = DRAW_TOOL_MAP[type]
+		if (!Tool) return null
+		this.activeTool = new Tool(this.viewer, options)
+		this.activeTool.start()
+		return this.activeTool
+	}
+
+	destroy() {
+		if (this.activeTool) {
+			this.activeTool.destroy()
+			this.activeTool = null
+		}
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js
new file mode 100644
index 0000000..a502f88
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js
@@ -0,0 +1,34 @@
+import { EditPolygonTool } from './edit/EditPolygonTool'
+import { EditRectangleTool } from './edit/EditRectangleTool'
+import { EditEllipseTool } from './edit/EditEllipseTool'
+import { EditBufferCircleTool } from './edit/EditBufferCircleTool'
+
+const EDIT_TOOL_MAP = {
+	polygon: EditPolygonTool,
+	rectangle: EditRectangleTool,
+	ellipse: EditEllipseTool,
+	buffer: EditBufferCircleTool,
+}
+
+export class EditManager {
+	constructor(viewer) {
+		this.viewer = viewer
+		this.activeTool = null
+	}
+
+	start(type, data, options = {}) {
+		this.destroy()
+		const Tool = EDIT_TOOL_MAP[type]
+		if (!Tool) return null
+		this.activeTool = new Tool(this.viewer, options)
+		this.activeTool.start(data)
+		return this.activeTool
+	}
+
+	destroy() {
+		if (this.activeTool) {
+			this.activeTool.destroy()
+			this.activeTool = null
+		}
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js
new file mode 100644
index 0000000..a6be205
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js
@@ -0,0 +1,24 @@
+export class ToolBase {
+	constructor(viewer) {
+		this.viewer = viewer
+		this.handler = null
+		this.listeners = []
+	}
+
+	subscribe(key, listener) {
+		this.listeners.push({ key, listener })
+	}
+
+	notify(key, data) {
+		this.listeners.filter(item => item.key === key).forEach(item => item.listener(data))
+	}
+
+	removeHandler(types) {
+		if (!this.handler) return
+		if (types?.length) {
+			types.forEach(type => this.handler.removeInputAction(type))
+		}
+		this.handler.destroy()
+		this.handler = null
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js
new file mode 100644
index 0000000..9fe2151
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js
@@ -0,0 +1,58 @@
+export class MapTooltip {
+	constructor(viewer, options = {}) {
+		this.viewer = viewer
+		this.options = options
+		this.container = viewer?.container || null
+		this.tooltipEl = null
+		this.isVisible = false
+		this.init()
+	}
+
+	init() {
+		if (!this.container || this.tooltipEl) return
+		const el = document.createElement('div')
+		el.style.position = 'absolute'
+		el.style.pointerEvents = 'none'
+		el.style.padding = '6px 8px'
+		el.style.background = 'rgba(0, 0, 0, 0.65)'
+		el.style.color = '#fff'
+		el.style.fontSize = '12px'
+		el.style.borderRadius = '4px'
+		el.style.whiteSpace = 'nowrap'
+		el.style.zIndex = '1000'
+		el.style.display = 'none'
+		this.container.appendChild(el)
+		this.tooltipEl = el
+	}
+
+	show(text, position) {
+		if (!this.tooltipEl) return
+		this.tooltipEl.textContent = text || ''
+		this.tooltipEl.style.display = 'block'
+		this.isVisible = true
+		if (position) this.move(position)
+	}
+
+	move(position) {
+		if (!this.tooltipEl || !position) return
+		const offset = this.options.offset || { x: 12, y: 12 }
+		this.tooltipEl.style.left = `${position.x + offset.x}px`
+		this.tooltipEl.style.top = `${position.y + offset.y}px`
+	}
+
+	hide() {
+		if (!this.tooltipEl) return
+		this.tooltipEl.style.display = 'none'
+		this.isVisible = false
+	}
+
+	destroy() {
+		this.hide()
+		if (this.tooltipEl && this.container) {
+			this.container.removeChild(this.tooltipEl)
+		}
+		this.tooltipEl = null
+		this.container = null
+		this.viewer = null
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js
new file mode 100644
index 0000000..ca5d4a9
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js
@@ -0,0 +1,236 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { buildEllipsePositions } from '../shapeUtils'
+
+export class DrawBufferCircleTool extends ToolBase {
+	constructor(viewer) {
+		super(viewer)
+		this.tooltip = new MapTooltip(viewer)
+		this.dataSource = null
+		this.centerCartesian = null
+		this.radiusLevel1 = 0
+		this.radiusLevel2 = 0
+		this.radiusLevel3 = 0
+		this.circleLevel1Entity = null
+		this.circleLevel2Entity = null
+		this.circleLevel3Entity = null
+		this.circleLevel1OutlineEntity = null
+		this.circleLevel2OutlineEntity = null
+		this.circleLevel3OutlineEntity = null
+		this.isDrawing = false
+		this.drawStep = 0
+		this.isCompleted = false
+	}
+
+	start() {
+		this.dataSource = new Cesium.CustomDataSource('draw-buffer-circle')
+		this.viewer.dataSources.add(this.dataSource)
+		this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+		this.handler.setInputAction(click => this.handleLeftClick(click), Cesium.ScreenSpaceEventType.LEFT_CLICK)
+		this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+		this.drawStep = 0
+		this.isCompleted = false
+		this.tooltip.show('单击设置中心点', { x: 0, y: 0 })
+	}
+
+	handleLeftClick(click) {
+		if (this.isCompleted) return
+		const position = this.getPositionFromScreen(click.position)
+		if (!position) return
+
+		if (this.drawStep === 0) {
+			this.centerCartesian = position
+			this.radiusLevel1 = 1
+			this.radiusLevel2 = 1
+			this.radiusLevel3 = 1
+			this.isDrawing = true
+			this.drawStep = 1
+			this.createEntities()
+			this.tooltip.show('单击设置一级缓冲圆', click.position)
+			return
+		}
+
+		if (this.drawStep === 1) {
+			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+			this.radiusLevel1 = radius
+			this.radiusLevel2 = radius
+			this.radiusLevel3 = radius
+			this.drawStep = 2
+			this.tooltip.show('单击设置二级缓冲圆', click.position)
+			return
+		}
+
+		if (this.drawStep === 2) {
+			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+			this.radiusLevel2 = Math.max(radius, this.radiusLevel1)
+			this.radiusLevel3 = this.radiusLevel2
+			this.drawStep = 3
+			this.tooltip.show('单击设置三级缓冲圆', click.position)
+			return
+		}
+
+		if (this.drawStep === 3) {
+			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+			this.radiusLevel3 = Math.max(radius, this.radiusLevel2)
+			this.isDrawing = false
+			this.drawStep = 0
+			this.isCompleted = true
+			const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
+			this.notify('getPolygonPositions', {
+				positions,
+				meta: {
+					center: this.centerCartesian,
+					bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+				},
+			})
+			this.tooltip.hide()
+			this.clearPreviewEntities()
+		}
+	}
+
+	handleMouseMove(movement) {
+		if (this.isCompleted) return
+		this.tooltip.move(movement.endPosition)
+		if (!this.isDrawing) return
+		const position = this.getPositionFromScreen(movement.endPosition)
+		if (!position) return
+		if (this.drawStep === 1) {
+			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+			this.radiusLevel1 = radius
+			this.radiusLevel2 = radius
+			this.radiusLevel3 = radius
+			return
+		}
+		if (this.drawStep === 2) {
+			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+			this.radiusLevel2 = Math.max(radius, this.radiusLevel1)
+			this.radiusLevel3 = this.radiusLevel2
+			return
+		}
+		if (this.drawStep === 3) {
+			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+			this.radiusLevel3 = Math.max(radius, this.radiusLevel2)
+		}
+	}
+
+	createEntities() {
+		if (this.circleLevel1Entity || this.circleLevel2Entity || this.circleLevel3Entity) return
+		this.circleLevel1Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			ellipse: {
+				semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
+				semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
+				material: Cesium.Color.fromBytes(247, 20, 20, 128),
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+		this.circleLevel1OutlineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					if (!this.centerCartesian || this.radiusLevel1 <= 0) return []
+					const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel1, this.radiusLevel1)
+					return positions.length ? [...positions, positions[0]] : positions
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: Cesium.Color.fromBytes(255, 0, 0, 255),
+			},
+		})
+
+		this.circleLevel2Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			ellipse: {
+				semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
+				semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
+				material: Cesium.Color.fromBytes(255, 235, 59, 128),
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+		this.circleLevel2OutlineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					if (!this.centerCartesian || this.radiusLevel2 <= 0) return []
+					const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel2, this.radiusLevel2)
+					return positions.length ? [...positions, positions[0]] : positions
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: Cesium.Color.fromBytes(255, 235, 59, 255),
+			},
+		})
+
+		this.circleLevel3Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			ellipse: {
+				semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
+				semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
+				material: Cesium.Color.fromBytes(25, 178, 230, 128),
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+		this.circleLevel3OutlineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					if (!this.centerCartesian || this.radiusLevel3 <= 0) return []
+					const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
+					return positions.length ? [...positions, positions[0]] : positions
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: Cesium.Color.fromBytes(0, 251, 255, 255),
+			},
+		})
+	}
+
+	clearPreviewEntities() {
+		if (!this.dataSource) return
+		this.dataSource.entities.removeAll()
+		this.circleLevel1Entity = null
+		this.circleLevel2Entity = null
+		this.circleLevel3Entity = null
+		this.circleLevel1OutlineEntity = null
+		this.circleLevel2OutlineEntity = null
+		this.circleLevel3OutlineEntity = null
+	}
+
+	getSurfaceDistance(startCartesian, endCartesian) {
+		const start = Cesium.Cartographic.fromCartesian(startCartesian)
+		const end = Cesium.Cartographic.fromCartesian(endCartesian)
+		const geodesic = new Cesium.EllipsoidGeodesic(start, end)
+		return geodesic.surfaceDistance
+	}
+
+	getPositionFromScreen(screenPosition) {
+		const scene = this.viewer.scene
+		const cartesian = scene.pickPosition(screenPosition)
+		if (cartesian) return cartesian
+		return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+	}
+
+	destroy() {
+		if (this.dataSource) {
+			this.dataSource.entities.removeAll()
+			this.viewer.dataSources.remove(this.dataSource)
+			this.dataSource = null
+		}
+		if (this.handler) {
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+			this.handler.destroy()
+			this.handler = null
+		}
+		this.tooltip?.destroy()
+		this.tooltip = null
+		this.centerCartesian = null
+		this.radiusLevel1 = 0
+		this.radiusLevel2 = 0
+		this.radiusLevel3 = 0
+		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/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js
new file mode 100644
index 0000000..724288c
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js
@@ -0,0 +1,162 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { buildEllipsePositions } from '../shapeUtils'
+
+const DEFAULT_STYLE = {
+	fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+	outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+	fill: style?.fill || DEFAULT_STYLE.fill,
+	outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+export class DrawEllipseTool extends ToolBase {
+	constructor(viewer, options = {}) {
+		super(viewer)
+		this.tooltip = new MapTooltip(viewer)
+		this.dataSource = null
+		this.ellipseEntity = null
+		this.centerCartesian = null
+		this.majorPoint = null
+		this.minorPoint = null
+		this.semiMajor = 0
+		this.semiMinor = 0
+		this.isDrawing = false
+		this.drawStep = 0
+		this.isCompleted = false
+		this.style = resolveStyle(options?.style)
+	}
+
+	start() {
+		this.dataSource = new Cesium.CustomDataSource('draw-ellipse')
+		this.viewer.dataSources.add(this.dataSource)
+		this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+		this.handler.setInputAction(click => this.handleLeftClick(click), Cesium.ScreenSpaceEventType.LEFT_CLICK)
+		this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+		this.drawStep = 0
+		this.isCompleted = false
+		this.tooltip.show('单击设置中心点', { x: 0, y: 0 })
+	}
+
+	handleLeftClick(click) {
+		if (this.isCompleted) return
+		const position = this.getPositionFromScreen(click.position)
+		if (!position) return
+		if (this.drawStep === 0) {
+			this.centerCartesian = position
+			this.semiMajor = 1
+			this.semiMinor = 1
+			this.isDrawing = true
+			this.drawStep = 1
+			this.createEntities()
+			this.tooltip.show('单击设置长轴', click.position)
+			return
+		}
+		if (this.drawStep === 1) {
+			this.majorPoint = position
+			this.semiMajor = Math.max(1, this.getSurfaceDistance(this.centerCartesian, this.majorPoint))
+			this.semiMinor = this.semiMajor
+			this.drawStep = 2
+			this.tooltip.show('单击设置短轴', click.position)
+			return
+		}
+		if (this.drawStep === 2) {
+			this.minorPoint = position
+			const minorRadius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, this.minorPoint))
+			this.semiMinor = Math.min(minorRadius, this.semiMajor)
+			this.isDrawing = false
+			this.drawStep = 0
+			this.isCompleted = true
+			const positions = buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
+			this.notify('getPolygonPositions', positions)
+			this.tooltip.hide()
+			this.clearPreviewEntities()
+		}
+	}
+
+	handleMouseMove(movement) {
+		if (this.isCompleted) return
+		this.tooltip.move(movement.endPosition)
+		if (!this.isDrawing) return
+		const position = this.getPositionFromScreen(movement.endPosition)
+		if (!position) return
+		if (this.drawStep === 1) {
+			const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+			this.semiMajor = radius
+			this.semiMinor = radius
+			return
+		}
+		if (this.drawStep === 2) {
+			const minorRadius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+			this.semiMinor = Math.min(minorRadius, this.semiMajor)
+		}
+	}
+
+	createEntities() {
+		if (this.ellipseEntity) return
+		this.ellipseEntity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			ellipse: {
+				semiMajorAxis: new Cesium.CallbackProperty(() => this.semiMajor, false),
+				semiMinorAxis: new Cesium.CallbackProperty(() => this.semiMinor, false),
+				material: this.style.fill,
+				outline: true,
+				outlineColor: this.style.outline,
+				outlineWidth: 2,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+	}
+
+	getSurfaceDistance(startCartesian, endCartesian) {
+		const start = Cesium.Cartographic.fromCartesian(startCartesian)
+		const end = Cesium.Cartographic.fromCartesian(endCartesian)
+		const geodesic = new Cesium.EllipsoidGeodesic(start, end)
+		return geodesic.surfaceDistance
+	}
+
+	clearPreviewEntities() {
+		if (!this.dataSource) return
+		this.dataSource.entities.removeAll()
+		this.ellipseEntity = null
+	}
+
+	getPositionFromScreen(screenPosition) {
+		const scene = this.viewer.scene
+		const cartesian = scene.pickPosition(screenPosition)
+		if (cartesian) return cartesian
+		return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+	}
+
+	setStyle(style) {
+		this.style = resolveStyle(style)
+		if (this.ellipseEntity?.ellipse) {
+			this.ellipseEntity.ellipse.material = this.style.fill
+			this.ellipseEntity.ellipse.outlineColor = this.style.outline
+		}
+	}
+
+	destroy() {
+		if (this.dataSource) {
+			this.dataSource.entities.removeAll()
+			this.viewer.dataSources.remove(this.dataSource)
+			this.dataSource = null
+		}
+		if (this.handler) {
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+			this.handler.destroy()
+			this.handler = null
+		}
+		this.tooltip?.destroy()
+		this.tooltip = null
+		this.centerCartesian = null
+		this.majorPoint = null
+		this.minorPoint = null
+		this.isDrawing = false
+		this.drawStep = 0
+		this.isCompleted = false
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js
new file mode 100644
index 0000000..9202105
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js
@@ -0,0 +1,244 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+
+const POINT_ENTITY_NAME = 'draw-polygon-point'
+const DEFAULT_STYLE = {
+	fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+	outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+	fill: style?.fill || DEFAULT_STYLE.fill,
+	outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+export class DrawPolygonTool extends ToolBase {
+	constructor(viewer, options = {}) {
+		super(viewer)
+		this.tooltip = new MapTooltip(viewer)
+		this.dataSource = null
+		this.polygonEntity = null
+		this.polylineEntity = null
+		this.positions = []
+		this.floatPosition = null
+		this.isDrawing = false
+		this.lastMousePosition = null
+		this.style = resolveStyle(options?.style)
+	}
+
+	start() {
+		this.dataSource = new Cesium.CustomDataSource('draw-polygon')
+		this.viewer.dataSources.add(this.dataSource)
+		this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+		this.handler.setInputAction(click => this.handleLeftClick(click), Cesium.ScreenSpaceEventType.LEFT_CLICK)
+		this.handler.setInputAction(click => this.handleRightClick(click), Cesium.ScreenSpaceEventType.RIGHT_CLICK)
+		this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+		this.isDrawing = true
+		this.tooltip.show(this.getTipText(), { x: 0, y: 0 })
+	}
+
+	handleLeftClick(click) {
+		if (!this.isDrawing) return
+
+		const pickedEntity = this.viewer.scene.pick(click.position)?.id
+		if (this.isFinishTrigger(pickedEntity)) {
+			this.finishDrawing()
+			return
+		}
+		if (pickedEntity?.name === POINT_ENTITY_NAME) return
+
+		const position = this.getPositionFromScreen(click.position)
+		if (!position) return
+
+		this.addPoint(position)
+		this.tooltip.show(this.getTipText(), click.position)
+	}
+
+	handleRightClick(click) {
+		if (!this.isDrawing) return
+		const pickedEntity = this.viewer.scene.pick(click.position)?.id
+		if (!pickedEntity || pickedEntity.name !== POINT_ENTITY_NAME) return
+		const index = pickedEntity.customData?.index
+		if (typeof index !== 'number') return
+		this.removePoint(index)
+		this.tooltip.show(this.getTipText(), click.position)
+	}
+
+	handleMouseMove(movement) {
+		if (!this.isDrawing) return
+		this.tooltip.show(this.getTipText(), movement.endPosition)
+		if (this.positions.length === 0) return
+		const position = this.getPositionFromScreen(movement.endPosition)
+		if (!position) return
+		this.lastMousePosition = position
+		this.floatPosition = position
+	}
+
+	getTipText() {
+		const count = this.positions.length
+		if (count === 0) {
+			return '单击增加点'
+		}
+		if (count < 3) {
+			return '单击增加点,右击删除点'
+		}
+		return '单击增加点,右击删除点,双击结束绘制'
+	}
+
+	isFinishTrigger(pickedEntity) {
+		if (!pickedEntity || pickedEntity.name !== POINT_ENTITY_NAME) return false
+		if (this.positions.length < 3) return false
+		const index = pickedEntity.customData?.index
+		return index === this.positions.length - 1
+	}
+
+	addPoint(position) {
+		this.positions.push(position)
+		if (!this.polygonEntity) {
+			this.createEntities()
+		}
+		this.rebuildPointEntities()
+	}
+
+	removePoint(index) {
+		if (index < 0 || index >= this.positions.length) return
+		this.positions.splice(index, 1)
+		this.rebuildPointEntities()
+		if (this.positions.length === 0) {
+			this.floatPosition = null
+			this.lastMousePosition = null
+			return
+		}
+		if (this.lastMousePosition) {
+			this.floatPosition = this.lastMousePosition
+		} else {
+			this.floatPosition = this.positions[this.positions.length - 1]
+		}
+	}
+
+	createEntities() {
+		const getPreviewPositions = () => {
+			if (this.positions.length === 0) return []
+			if (!this.isDrawing || !this.floatPosition) return this.positions
+			return [...this.positions, this.floatPosition]
+		}
+
+		this.polygonEntity = this.dataSource.entities.add({
+			polygon: {
+				hierarchy: new Cesium.CallbackProperty(
+					() => new Cesium.PolygonHierarchy(getPreviewPositions()),
+					false
+				),
+				material: this.style.fill,
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				show: new Cesium.CallbackProperty(() => getPreviewPositions().length >= 3, false),
+			},
+		})
+
+		this.polylineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					const positions = getPreviewPositions()
+					if (positions.length < 2) return positions
+					if (positions.length >= 3) {
+						return [...positions, positions[0]]
+					}
+					return positions
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: this.style.outline,
+				show: new Cesium.CallbackProperty(() => getPreviewPositions().length >= 2, false),
+			},
+		})
+	}
+
+	rebuildPointEntities() {
+		this.dataSource.entities.values
+			.slice()
+			.filter(entity => entity?.name === POINT_ENTITY_NAME)
+			.forEach(entity => this.dataSource.entities.remove(entity))
+
+		this.positions.forEach((position, index) => {
+			this.dataSource.entities.add({
+				name: POINT_ENTITY_NAME,
+				position: position.clone ? position.clone() : position,
+				point: {
+					pixelSize: 12,
+					color: Cesium.Color.WHITE,
+					outlineColor: this.style.outline,
+					outlineWidth: 2,
+					heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+					disableDepthTestDistance: Number.POSITIVE_INFINITY,
+				},
+				customData: {
+					index,
+				},
+			})
+		})
+	}
+
+	finishDrawing() {
+		if (this.positions.length < 3) return
+		this.isDrawing = false
+		this.floatPosition = null
+		this.notify('getPolygonPositions', this.positions)
+		this.tooltip.hide()
+		this.clearPreviewEntities()
+	}
+
+	setStyle(style) {
+		this.style = resolveStyle(style)
+		if (this.polygonEntity?.polygon) {
+			this.polygonEntity.polygon.material = this.style.fill
+		}
+		if (this.polylineEntity?.polyline) {
+			this.polylineEntity.polyline.material = this.style.outline
+		}
+		if (this.dataSource) {
+			this.dataSource.entities.values
+				.filter(entity => entity?.name === POINT_ENTITY_NAME)
+				.forEach(entity => {
+					if (entity?.point) {
+						entity.point.outlineColor = this.style.outline
+					}
+				})
+		}
+	}
+
+	clearPreviewEntities() {
+		if (!this.dataSource) return
+		this.dataSource.entities.removeAll()
+		this.polygonEntity = null
+		this.polylineEntity = null
+	}
+
+	getPositionFromScreen(screenPosition) {
+		const scene = this.viewer.scene
+		const cartesian = scene.pickPosition(screenPosition)
+		if (cartesian) return cartesian
+		return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+	}
+
+	destroy() {
+		if (this.dataSource) {
+			this.dataSource.entities.removeAll()
+			this.viewer.dataSources.remove(this.dataSource)
+			this.dataSource = null
+		}
+		if (this.handler) {
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+			this.handler.destroy()
+			this.handler = null
+		}
+		this.tooltip?.destroy()
+		this.tooltip = null
+		this.positions = []
+		this.floatPosition = null
+		this.isDrawing = false
+		this.lastMousePosition = null
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js
new file mode 100644
index 0000000..574f6b6
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js
@@ -0,0 +1,150 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { getBoundsFromCartesians, rectanglePositionsFromBounds } from '../shapeUtils'
+
+const DEFAULT_STYLE = {
+	fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+	outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+	fill: style?.fill || DEFAULT_STYLE.fill,
+	outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+export class DrawRectangleTool extends ToolBase {
+	constructor(viewer, options = {}) {
+		super(viewer)
+		this.tooltip = new MapTooltip(viewer)
+		this.dataSource = null
+		this.rectangleEntity = null
+		this.outlineEntity = null
+		this.startPosition = null
+		this.endPosition = null
+		this.isDrawing = false
+		this.style = resolveStyle(options?.style)
+	}
+
+	start() {
+		this.dataSource = new Cesium.CustomDataSource('draw-rectangle')
+		this.viewer.dataSources.add(this.dataSource)
+		this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+
+		this.handler.setInputAction(click => this.handleLeftClick(click), Cesium.ScreenSpaceEventType.LEFT_CLICK)
+		this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+
+		this.tooltip.show('单击设置起点', { x: 0, y: 0 })
+	}
+
+	handleLeftClick(click) {
+		const position = this.getPositionFromScreen(click.position)
+		if (!position) return
+
+		if (!this.startPosition) {
+			this.startPosition = position
+			this.endPosition = position
+			this.isDrawing = true
+			this.createEntities()
+			this.tooltip.show('单击结束绘制', click.position)
+			return
+		}
+
+		if (this.isDrawing) {
+			this.endPosition = position
+			this.isDrawing = false
+			const positions = this.getRectanglePositions()
+			this.notify('getPolygonPositions', positions)
+			this.tooltip.hide()
+			this.clearPreviewEntities()
+		}
+	}
+
+	handleMouseMove(movement) {
+		if (!this.isDrawing) {
+			this.tooltip.move(movement.endPosition)
+			return
+		}
+		const position = this.getPositionFromScreen(movement.endPosition)
+		if (!position) return
+		this.endPosition = position
+		this.tooltip.move(movement.endPosition)
+	}
+
+	createEntities() {
+		if (this.rectangleEntity) return
+		const hierarchy = new Cesium.CallbackProperty(() => {
+			const positions = this.getRectanglePositions()
+			return new Cesium.PolygonHierarchy(positions)
+		}, false)
+
+		this.rectangleEntity = this.dataSource.entities.add({
+			polygon: {
+				hierarchy,
+				material: this.style.fill,
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+
+		this.outlineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					const positions = this.getRectanglePositions()
+					return positions.length ? [...positions, positions[0]] : []
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: this.style.outline,
+			},
+		})
+	}
+
+	getRectanglePositions() {
+		if (!this.startPosition || !this.endPosition) return []
+		const bounds = getBoundsFromCartesians([this.startPosition, this.endPosition])
+		return rectanglePositionsFromBounds(bounds)
+	}
+
+	clearPreviewEntities() {
+		if (!this.dataSource) return
+		this.dataSource.entities.removeAll()
+		this.rectangleEntity = null
+		this.outlineEntity = null
+	}
+
+	getPositionFromScreen(screenPosition) {
+		const scene = this.viewer.scene
+		const cartesian = scene.pickPosition(screenPosition)
+		if (cartesian) return cartesian
+		return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+	}
+
+	setStyle(style) {
+		this.style = resolveStyle(style)
+		if (this.rectangleEntity?.polygon) {
+			this.rectangleEntity.polygon.material = this.style.fill
+		}
+		if (this.outlineEntity?.polyline) {
+			this.outlineEntity.polyline.material = this.style.outline
+		}
+	}
+
+	destroy() {
+		if (this.dataSource) {
+			this.dataSource.entities.removeAll()
+			this.viewer.dataSources.remove(this.dataSource)
+			this.dataSource = null
+		}
+		if (this.handler) {
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+			this.handler.destroy()
+			this.handler = null
+		}
+		this.tooltip?.destroy()
+		this.tooltip = null
+		this.startPosition = null
+		this.endPosition = null
+		this.isDrawing = false
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js
new file mode 100644
index 0000000..1808125
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js
@@ -0,0 +1,314 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { buildEllipsePositions, cartesianToLocal } from '../shapeUtils'
+
+const normalizePoints = points =>
+	(points || []).map(point => ({
+		lng: point.lng ?? point.longitude,
+		lat: point.lat ?? point.latitude,
+	}))
+
+export class EditBufferCircleTool extends ToolBase {
+	constructor(viewer) {
+		super(viewer)
+		this.tooltip = new MapTooltip(viewer)
+		this.dataSource = null
+		this.centerCartesian = null
+		this.radiusLevel1 = 0
+		this.radiusLevel2 = 0
+		this.radiusLevel3 = 0
+		this.dragType = null
+		this.circleLevel1Entity = null
+		this.circleLevel2Entity = null
+		this.circleLevel3Entity = null
+		this.circleLevel1OutlineEntity = null
+		this.circleLevel2OutlineEntity = null
+		this.circleLevel3OutlineEntity = null
+		this.centerEntity = null
+		this.radiusLevel1Entity = null
+		this.radiusLevel2Entity = null
+		this.radiusLevel3Entity = null
+	}
+
+	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 radii = Array.isArray(data?.meta?.bufferRadii) ? data.meta.bufferRadii : null
+		if (radii?.length) {
+			const [r1, r2, r3] = radii
+			this.radiusLevel1 = Number.isFinite(r1) && r1 > 0 ? r1 : 1
+			this.radiusLevel2 = Number.isFinite(r2) && r2 > 0 ? Math.max(r2, this.radiusLevel1) : this.radiusLevel1
+			this.radiusLevel3 = Number.isFinite(r3) && r3 > 0 ? Math.max(r3, this.radiusLevel2) : this.radiusLevel2
+		} else {
+			this.radiusLevel3 = this.estimateRadius(normalized)
+			this.radiusLevel2 = this.radiusLevel3
+			this.radiusLevel1 = this.radiusLevel3
+		}
+
+		this.dataSource = new Cesium.CustomDataSource('edit-buffer-circle')
+		this.viewer.dataSources.add(this.dataSource)
+		this.createEntities()
+		this.initHandler()
+	}
+
+	estimateRadius(points) {
+		const center = Cesium.Cartesian3.clone(this.centerCartesian)
+		let max = 1
+		points.forEach(point => {
+			const cartesian = Cesium.Cartesian3.fromDegrees(point.lng, point.lat)
+			const distance = Cesium.Cartesian3.distance(center, cartesian)
+			max = Math.max(max, distance)
+		})
+		return max
+	}
+
+	initHandler() {
+		this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+		this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN)
+		this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+		this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
+		this.tooltip.show('拖动控制点编辑', { x: 0, y: 0 })
+	}
+
+	handleLeftDown(evt) {
+		const picked = this.viewer.scene.pick(evt.position)?.id
+		if (!picked) return
+		if (picked.customType === 'circle-center') this.dragType = 'center'
+		if (picked.customType === 'circle-radius-1') this.dragType = 'radius-1'
+		if (picked.customType === 'circle-radius-2') this.dragType = 'radius-2'
+		if (picked.customType === 'circle-radius-3') this.dragType = 'radius-3'
+		if (this.dragType) this.disableMapControl()
+	}
+
+	handleMouseMove(evt) {
+		this.tooltip.move(evt.endPosition)
+		if (!this.dragType) return
+		const position = this.getPositionFromScreen(evt.endPosition)
+		if (!position) return
+
+		if (this.dragType === 'center') {
+			this.centerCartesian = position
+			this.updateRadiusPoints()
+			return
+		}
+
+		const local = cartesianToLocal(this.centerCartesian, position)
+		const radius = Math.max(1, Math.sqrt(local.x * local.x + local.y * local.y))
+		if (this.dragType === 'radius-1') {
+			this.radiusLevel1 = radius
+			if (this.radiusLevel2 < this.radiusLevel1) this.radiusLevel2 = this.radiusLevel1
+			if (this.radiusLevel3 < this.radiusLevel2) this.radiusLevel3 = this.radiusLevel2
+		}
+		if (this.dragType === 'radius-2') {
+			this.radiusLevel2 = Math.max(radius, this.radiusLevel1)
+			if (this.radiusLevel3 < this.radiusLevel2) this.radiusLevel3 = this.radiusLevel2
+		}
+		if (this.dragType === 'radius-3') {
+			this.radiusLevel3 = Math.max(radius, this.radiusLevel2)
+		}
+		this.updateRadiusPoints()
+	}
+
+	handleLeftUp() {
+		if (!this.dragType) return
+		this.dragType = null
+		this.enableMapControl()
+		const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
+		this.notify('getPolygonPositions', {
+			positions,
+			meta: {
+				center: this.centerCartesian,
+				bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+			},
+		})
+	}
+
+	createEntities() {
+		this.circleLevel1Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			ellipse: {
+				semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
+				semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
+				material: Cesium.Color.fromBytes(247, 20, 20, 128),
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+		this.circleLevel1OutlineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					if (!this.centerCartesian || this.radiusLevel1 <= 0) return []
+					const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel1, this.radiusLevel1)
+					return positions.length ? [...positions, positions[0]] : positions
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: Cesium.Color.fromBytes(255, 0, 0, 255),
+			},
+		})
+
+		this.circleLevel2Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			ellipse: {
+				semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
+				semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
+				material: Cesium.Color.fromBytes(255, 235, 59, 128),
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+		this.circleLevel2OutlineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					if (!this.centerCartesian || this.radiusLevel2 <= 0) return []
+					const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel2, this.radiusLevel2)
+					return positions.length ? [...positions, positions[0]] : positions
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: Cesium.Color.fromBytes(255, 235, 59, 255),
+			},
+		})
+
+		this.circleLevel3Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			ellipse: {
+				semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
+				semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
+				material: Cesium.Color.fromBytes(25, 178, 230, 128),
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+		this.circleLevel3OutlineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					if (!this.centerCartesian || this.radiusLevel3 <= 0) return []
+					const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
+					return positions.length ? [...positions, positions[0]] : positions
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: Cesium.Color.fromBytes(0, 251, 255, 255),
+			},
+		})
+
+		this.centerEntity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			point: {
+				pixelSize: 12,
+				color: Cesium.Color.WHITE,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			},
+			customType: 'circle-center',
+		})
+
+		this.radiusLevel1Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel1), false),
+			point: {
+				pixelSize: 10,
+				color: Cesium.Color.fromBytes(255, 255, 255, 200),
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			},
+			customType: 'circle-radius-1',
+		})
+
+		this.radiusLevel2Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel2), false),
+			point: {
+				pixelSize: 10,
+				color: Cesium.Color.fromBytes(255, 255, 255, 200),
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			},
+			customType: 'circle-radius-2',
+		})
+
+		this.radiusLevel3Entity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel3), false),
+			point: {
+				pixelSize: 10,
+				color: Cesium.Color.fromBytes(255, 255, 255, 200),
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			},
+			customType: 'circle-radius-3',
+		})
+	}
+
+	updateRadiusPoints() {
+		if (this.radiusLevel1Entity) this.radiusLevel1Entity.position = this.getRadiusPoint(this.radiusLevel1)
+		if (this.radiusLevel2Entity) this.radiusLevel2Entity.position = this.getRadiusPoint(this.radiusLevel2)
+		if (this.radiusLevel3Entity) this.radiusLevel3Entity.position = this.getRadiusPoint(this.radiusLevel3)
+	}
+
+	getPositions() {
+		if (!this.centerCartesian) return []
+		return {
+			positions: buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3),
+			meta: {
+				center: this.centerCartesian,
+				bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+			},
+		}
+	}
+
+	getRadiusPoint(radius) {
+		const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian)
+		const local = new Cesium.Cartesian3(radius, 0, 0)
+		return Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3())
+	}
+
+	getPositionFromScreen(screenPosition) {
+		const scene = this.viewer.scene
+		const cartesian = scene.pickPosition(screenPosition)
+		if (cartesian) return cartesian
+		return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+	}
+
+	disableMapControl() {
+		const controller = this.viewer.scene.screenSpaceCameraController
+		controller.enableRotate = false
+		controller.enableTranslate = false
+		controller.enableZoom = false
+	}
+
+	enableMapControl() {
+		const controller = this.viewer.scene.screenSpaceCameraController
+		controller.enableRotate = true
+		controller.enableTranslate = true
+		controller.enableZoom = true
+	}
+
+	destroy() {
+		if (this.dataSource) {
+			this.dataSource.entities.removeAll()
+			this.viewer.dataSources.remove(this.dataSource)
+			this.dataSource = null
+		}
+		if (this.handler) {
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
+			this.handler.destroy()
+			this.handler = null
+		}
+		this.tooltip?.destroy()
+		this.tooltip = null
+		this.centerCartesian = null
+		this.radiusLevel1 = 0
+		this.radiusLevel2 = 0
+		this.radiusLevel3 = 0
+		this.circleLevel1OutlineEntity = null
+		this.circleLevel2OutlineEntity = null
+		this.circleLevel3OutlineEntity = null
+		this.dragType = null
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js
new file mode 100644
index 0000000..c3b2904
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js
@@ -0,0 +1,233 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import {
+	getBoundsFromLngLats,
+	getCenterFromBounds,
+	getMetersBetween,
+	buildEllipsePositions,
+	cartesianToLocal,
+} from '../shapeUtils'
+
+const DEFAULT_STYLE = {
+	fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+	outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+	fill: style?.fill || DEFAULT_STYLE.fill,
+	outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+const normalizePoints = points =>
+	(points || []).map(point => ({
+		lng: point.lng ?? point.longitude,
+		lat: point.lat ?? point.latitude,
+	}))
+
+export class EditEllipseTool extends ToolBase {
+	constructor(viewer, options = {}) {
+		super(viewer)
+		this.tooltip = new MapTooltip(viewer)
+		this.dataSource = null
+		this.centerCartesian = null
+		this.semiMajor = 0
+		this.semiMinor = 0
+		this.dragType = null
+		this.centerEntity = null
+		this.majorEntity = null
+		this.minorEntity = null
+		this.ellipseEntity = null
+		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)
+
+		this.dataSource = new Cesium.CustomDataSource('edit-ellipse')
+		this.viewer.dataSources.add(this.dataSource)
+		this.createEntities()
+		this.initHandler()
+	}
+
+	initHandler() {
+		this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+		this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN)
+		this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+		this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
+		this.tooltip.show('拖动控制点编辑', { x: 0, y: 0 })
+	}
+
+	handleLeftDown(evt) {
+		const picked = this.viewer.scene.pick(evt.position)?.id
+		if (!picked) return
+		if (picked.customType === 'ellipse-center') this.dragType = 'center'
+		if (picked.customType === 'ellipse-major') this.dragType = 'major'
+		if (picked.customType === 'ellipse-minor') this.dragType = 'minor'
+		if (this.dragType) this.disableMapControl()
+	}
+
+	handleMouseMove(evt) {
+		this.tooltip.move(evt.endPosition)
+		if (!this.dragType) return
+		const position = this.getPositionFromScreen(evt.endPosition)
+		if (!position) return
+
+		if (this.dragType === 'center') {
+			this.centerCartesian = position
+			this.updateAxisPoints()
+			return
+		}
+
+		const local = cartesianToLocal(this.centerCartesian, position)
+		if (this.dragType === 'major') {
+			this.semiMajor = Math.max(1, Math.abs(local.x))
+			if (this.semiMajor < this.semiMinor) {
+				this.semiMajor = this.semiMinor
+			}
+		}
+		if (this.dragType === 'minor') {
+			this.semiMinor = Math.max(1, Math.abs(local.y))
+			if (this.semiMinor > this.semiMajor) {
+				this.semiMinor = this.semiMajor
+			}
+		}
+		this.updateAxisPoints()
+	}
+
+	handleLeftUp() {
+		if (!this.dragType) return
+		this.dragType = null
+		this.enableMapControl()
+		const positions = buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
+		this.notify('getPolygonPositions', positions)
+	}
+
+	createEntities() {
+		this.ellipseEntity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			ellipse: {
+				semiMajorAxis: new Cesium.CallbackProperty(() => this.semiMajor, false),
+				semiMinorAxis: new Cesium.CallbackProperty(() => this.semiMinor, false),
+				material: this.style.fill,
+				outline: true,
+				outlineColor: this.style.outline,
+				outlineWidth: 2,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+
+		this.centerEntity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+			point: {
+				pixelSize: 12,
+				color: Cesium.Color.WHITE,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			},
+			customType: 'ellipse-center',
+		})
+
+		this.majorEntity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.getAxisPoint(this.semiMajor, 0), false),
+			point: {
+				pixelSize: 10,
+				color: Cesium.Color.fromBytes(255, 255, 255, 200),
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			},
+			customType: 'ellipse-major',
+		})
+
+		this.minorEntity = this.dataSource.entities.add({
+			position: new Cesium.CallbackProperty(() => this.getAxisPoint(0, this.semiMinor), false),
+			point: {
+				pixelSize: 10,
+				color: Cesium.Color.fromBytes(255, 255, 255, 200),
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			},
+			customType: 'ellipse-minor',
+		})
+	}
+
+	updateAxisPoints() {
+		if (!this.majorEntity || !this.minorEntity) return
+		this.majorEntity.position = this.getAxisPoint(this.semiMajor, 0)
+		this.minorEntity.position = this.getAxisPoint(0, this.semiMinor)
+	}
+
+	getPositions() {
+		if (!this.centerCartesian) return []
+		return buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
+	}
+
+	getAxisPoint(eastMeters, northMeters) {
+		const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian)
+		const local = new Cesium.Cartesian3(eastMeters, northMeters, 0)
+		return Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3())
+	}
+
+	getPositionFromScreen(screenPosition) {
+		const scene = this.viewer.scene
+		const cartesian = scene.pickPosition(screenPosition)
+		if (cartesian) return cartesian
+		return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+	}
+
+	setStyle(style) {
+		this.style = resolveStyle(style)
+		if (this.ellipseEntity?.ellipse) {
+			this.ellipseEntity.ellipse.material = this.style.fill
+			this.ellipseEntity.ellipse.outlineColor = this.style.outline
+		}
+	}
+
+	disableMapControl() {
+		const controller = this.viewer.scene.screenSpaceCameraController
+		controller.enableRotate = false
+		controller.enableTranslate = false
+		controller.enableZoom = false
+	}
+
+	enableMapControl() {
+		const controller = this.viewer.scene.screenSpaceCameraController
+		controller.enableRotate = true
+		controller.enableTranslate = true
+		controller.enableZoom = true
+	}
+
+	destroy() {
+		if (this.dataSource) {
+			this.dataSource.entities.removeAll()
+			this.viewer.dataSources.remove(this.dataSource)
+			this.dataSource = null
+		}
+		if (this.handler) {
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
+			this.handler.destroy()
+			this.handler = null
+		}
+		this.tooltip?.destroy()
+		this.tooltip = null
+		this.centerCartesian = null
+		this.semiMajor = 0
+		this.semiMinor = 0
+		this.dragType = null
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js
new file mode 100644
index 0000000..c6b2a13
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js
@@ -0,0 +1,218 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+
+const POINT_ENTITY_NAME = 'edit-polygon-point'
+const DEFAULT_STYLE = {
+	fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+	outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+	fill: style?.fill || DEFAULT_STYLE.fill,
+	outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+const normalizePoints = points =>
+	(points || []).map(point => ({
+		lng: point.lng ?? point.longitude,
+		lat: point.lat ?? point.latitude,
+		height: point.height ?? 0,
+	}))
+
+export class EditPolygonTool extends ToolBase {
+	constructor(viewer, options = {}) {
+		super(viewer)
+		this.tooltip = new MapTooltip(viewer)
+		this.dataSource = null
+		this.polygonEntity = null
+		this.polylineEntity = null
+		this.positions = []
+		this.isDragging = false
+		this.draggedIndex = -1
+		this.style = resolveStyle(options?.style)
+	}
+
+	start(points = []) {
+		const normalized = normalizePoints(points)
+		this.positions = normalized
+			.filter(point => point?.lng !== undefined && point?.lat !== undefined)
+			.map(point => Cesium.Cartesian3.fromDegrees(Number(point.lng), Number(point.lat), Number(point.height || 0)))
+
+		this.dataSource = new Cesium.CustomDataSource('edit-polygon')
+		this.viewer.dataSources.add(this.dataSource)
+
+		this.createEntities()
+		this.rebuildPointEntities()
+		this.initHandler()
+	}
+
+	initHandler() {
+		if (!this.viewer || this.handler) return
+		this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+		this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+		this.handler.setInputAction(click => this.handleLeftDown(click), Cesium.ScreenSpaceEventType.LEFT_DOWN)
+		this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
+		this.handler.setInputAction(click => this.handleRightClick(click), Cesium.ScreenSpaceEventType.RIGHT_CLICK)
+	}
+
+	handleLeftDown(click) {
+		const pickedEntity = this.viewer.scene.pick(click.position)?.id
+		if (!pickedEntity || pickedEntity.name !== POINT_ENTITY_NAME) return
+		const index = pickedEntity.customData?.index
+		if (typeof index !== 'number') return
+		this.isDragging = true
+		this.draggedIndex = index
+		this.disableMapControl()
+	}
+
+	handleLeftUp() {
+		if (!this.isDragging) return
+		this.isDragging = false
+		this.draggedIndex = -1
+		this.enableMapControl()
+		this.notify('getPolygonPositions', this.positions)
+	}
+
+	handleMouseMove(movement) {
+		this.tooltip.show('拖动顶点编辑,右键删除', movement.endPosition)
+		if (!this.isDragging || this.draggedIndex < 0) return
+		const position = this.getPositionFromScreen(movement.endPosition)
+		if (!position) return
+		this.positions[this.draggedIndex] = position
+		const pointEntity = this.dataSource.entities.values.find(
+			entity => entity?.name === POINT_ENTITY_NAME && entity.customData?.index === this.draggedIndex
+		)
+		if (pointEntity) {
+			pointEntity.position = position
+		}
+	}
+
+	handleRightClick(click) {
+		const pickedEntity = this.viewer.scene.pick(click.position)?.id
+		if (!pickedEntity || pickedEntity.name !== POINT_ENTITY_NAME) return
+		if (this.positions.length <= 3) return
+		const index = pickedEntity.customData?.index
+		if (typeof index !== 'number') return
+		this.positions.splice(index, 1)
+		this.rebuildPointEntities()
+		this.notify('getPolygonPositions', this.positions)
+	}
+
+	createEntities() {
+		this.polygonEntity = this.dataSource.entities.add({
+			polygon: {
+				hierarchy: new Cesium.CallbackProperty(
+					() => new Cesium.PolygonHierarchy(this.positions),
+					false
+				),
+				material: this.style.fill,
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				show: new Cesium.CallbackProperty(() => this.positions.length >= 3, false),
+			},
+		})
+
+		this.polylineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					if (this.positions.length < 2) return this.positions
+					return [...this.positions, this.positions[0]]
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: this.style.outline,
+				show: new Cesium.CallbackProperty(() => this.positions.length >= 2, false),
+			},
+		})
+	}
+
+	rebuildPointEntities() {
+		this.dataSource.entities.values
+			.slice()
+			.filter(entity => entity?.name === POINT_ENTITY_NAME)
+			.forEach(entity => this.dataSource.entities.remove(entity))
+
+		this.positions.forEach((position, index) => {
+			this.dataSource.entities.add({
+				name: POINT_ENTITY_NAME,
+				position: position.clone ? position.clone() : position,
+				point: {
+					pixelSize: 12,
+					color: Cesium.Color.WHITE,
+					outlineColor: this.style.outline,
+					outlineWidth: 2,
+					heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+					disableDepthTestDistance: Number.POSITIVE_INFINITY,
+				},
+				customData: {
+					index,
+				},
+			})
+		})
+	}
+
+	getPositions() {
+		return this.positions.slice()
+	}
+
+	getPositionFromScreen(screenPosition) {
+		const scene = this.viewer.scene
+		const cartesian = scene.pickPosition(screenPosition)
+		if (cartesian) return cartesian
+		return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+	}
+
+	setStyle(style) {
+		this.style = resolveStyle(style)
+		if (this.polygonEntity?.polygon) {
+			this.polygonEntity.polygon.material = this.style.fill
+		}
+		if (this.polylineEntity?.polyline) {
+			this.polylineEntity.polyline.material = this.style.outline
+		}
+		if (this.dataSource) {
+			this.dataSource.entities.values
+				.filter(entity => entity?.name === POINT_ENTITY_NAME)
+				.forEach(entity => {
+					if (entity?.point) {
+						entity.point.outlineColor = this.style.outline
+					}
+				})
+		}
+	}
+
+	disableMapControl() {
+		const controller = this.viewer.scene.screenSpaceCameraController
+		controller.enableRotate = false
+		controller.enableTranslate = false
+		controller.enableZoom = false
+	}
+
+	enableMapControl() {
+		const controller = this.viewer.scene.screenSpaceCameraController
+		controller.enableRotate = true
+		controller.enableTranslate = true
+		controller.enableZoom = true
+	}
+
+	destroy() {
+		if (this.dataSource) {
+			this.dataSource.entities.removeAll()
+			this.viewer.dataSources.remove(this.dataSource)
+			this.dataSource = null
+		}
+		if (this.handler) {
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK)
+			this.handler.destroy()
+			this.handler = null
+		}
+		this.tooltip?.destroy()
+		this.tooltip = null
+		this.positions = []
+		this.isDragging = false
+		this.draggedIndex = -1
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js
new file mode 100644
index 0000000..d7dc3cd
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js
@@ -0,0 +1,198 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { getBoundsFromLngLats, rectanglePositionsFromBounds } from '../shapeUtils'
+
+const DEFAULT_STYLE = {
+	fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+	outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+	fill: style?.fill || DEFAULT_STYLE.fill,
+	outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+const normalizePoints = points =>
+	(points || []).map(point => ({
+		lng: point.lng ?? point.longitude,
+		lat: point.lat ?? point.latitude,
+	}))
+
+export class EditRectangleTool extends ToolBase {
+	constructor(viewer, options = {}) {
+		super(viewer)
+		this.tooltip = new MapTooltip(viewer)
+		this.dataSource = null
+		this.bounds = null
+		this.rectangleEntity = null
+		this.outlineEntity = null
+		this.cornerEntities = []
+		this.dragIndex = null
+		this.fixedCorner = null
+		this.style = resolveStyle(options?.style)
+	}
+
+	start(points = []) {
+		const normalized = normalizePoints(points)
+		this.bounds = getBoundsFromLngLats(normalized)
+		if (!this.bounds) return
+		this.dataSource = new Cesium.CustomDataSource('edit-rectangle')
+		this.viewer.dataSources.add(this.dataSource)
+		this.createEntities()
+		this.initHandler()
+	}
+
+	initHandler() {
+		this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+		this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN)
+		this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+		this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
+		this.tooltip.show('拖动顶点编辑', { x: 0, y: 0 })
+	}
+
+	handleLeftDown(evt) {
+		const picked = this.viewer.scene.pick(evt.position)?.id
+		if (!picked || picked.customType !== 'rect-corner') return
+		this.dragIndex = picked.customData.index
+		const corners = this.getCornerCartographics()
+		this.fixedCorner = corners[(this.dragIndex + 2) % 4]
+		this.disableMapControl()
+	}
+
+	handleMouseMove(evt) {
+		this.tooltip.move(evt.endPosition)
+		if (this.dragIndex == null) return
+		const position = this.getPositionFromScreen(evt.endPosition)
+		if (!position) return
+		const moved = Cesium.Cartographic.fromCartesian(position)
+		this.bounds = {
+			west: Math.min(moved.longitude, this.fixedCorner.longitude),
+			east: Math.max(moved.longitude, this.fixedCorner.longitude),
+			south: Math.min(moved.latitude, this.fixedCorner.latitude),
+			north: Math.max(moved.latitude, this.fixedCorner.latitude),
+		}
+		this.updateCorners()
+	}
+
+	handleLeftUp() {
+		if (this.dragIndex == null) return
+		this.dragIndex = null
+		this.fixedCorner = null
+		this.enableMapControl()
+		const positions = rectanglePositionsFromBounds(this.bounds)
+		this.notify('getPolygonPositions', positions)
+	}
+
+	createEntities() {
+		const hierarchy = new Cesium.CallbackProperty(() => {
+			const positions = rectanglePositionsFromBounds(this.bounds)
+			return new Cesium.PolygonHierarchy(positions)
+		}, false)
+		this.rectangleEntity = this.dataSource.entities.add({
+			polygon: {
+				hierarchy,
+				material: this.style.fill,
+				outline: false,
+				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+			},
+		})
+		this.outlineEntity = this.dataSource.entities.add({
+			polyline: {
+				positions: new Cesium.CallbackProperty(() => {
+					const positions = rectanglePositionsFromBounds(this.bounds)
+					return positions.length ? [...positions, positions[0]] : []
+				}, false),
+				clampToGround: true,
+				width: 2,
+				material: this.style.outline,
+			},
+		})
+		this.createCorners()
+	}
+
+	createCorners() {
+		const positions = rectanglePositionsFromBounds(this.bounds)
+		this.cornerEntities = positions.map((position, index) =>
+			this.dataSource.entities.add({
+				position,
+				point: {
+					pixelSize: 12,
+					color: Cesium.Color.WHITE,
+					heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+					disableDepthTestDistance: Number.POSITIVE_INFINITY,
+				},
+				customType: 'rect-corner',
+				customData: { index },
+			})
+		)
+	}
+
+	updateCorners() {
+		const positions = rectanglePositionsFromBounds(this.bounds)
+		this.cornerEntities.forEach((entity, index) => {
+			entity.position = positions[index]
+		})
+	}
+
+	getPositions() {
+		return rectanglePositionsFromBounds(this.bounds)
+	}
+
+	getCornerCartographics() {
+		return rectanglePositionsFromBounds(this.bounds).map(position =>
+			Cesium.Cartographic.fromCartesian(position)
+		)
+	}
+
+	getPositionFromScreen(screenPosition) {
+		const scene = this.viewer.scene
+		const cartesian = scene.pickPosition(screenPosition)
+		if (cartesian) return cartesian
+		return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+	}
+
+	setStyle(style) {
+		this.style = resolveStyle(style)
+		if (this.rectangleEntity?.polygon) {
+			this.rectangleEntity.polygon.material = this.style.fill
+		}
+		if (this.outlineEntity?.polyline) {
+			this.outlineEntity.polyline.material = this.style.outline
+		}
+	}
+
+	disableMapControl() {
+		const controller = this.viewer.scene.screenSpaceCameraController
+		controller.enableRotate = false
+		controller.enableTranslate = false
+		controller.enableZoom = false
+	}
+
+	enableMapControl() {
+		const controller = this.viewer.scene.screenSpaceCameraController
+		controller.enableRotate = true
+		controller.enableTranslate = true
+		controller.enableZoom = true
+	}
+
+	destroy() {
+		if (this.dataSource) {
+			this.dataSource.entities.removeAll()
+			this.viewer.dataSources.remove(this.dataSource)
+			this.dataSource = null
+		}
+		if (this.handler) {
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+			this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
+			this.handler.destroy()
+			this.handler = null
+		}
+		this.tooltip?.destroy()
+		this.tooltip = null
+		this.bounds = null
+		this.dragIndex = null
+		this.fixedCorner = null
+		this.cornerEntities = []
+	}
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js
new file mode 100644
index 0000000..e9d001f
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js
@@ -0,0 +1,89 @@
+import * as Cesium from 'cesium'
+
+export const getBoundsFromCartesians = positions => {
+	const cartographics = positions
+		.map(pos => (pos?.longitude ? pos : Cesium.Cartographic.fromCartesian(pos)))
+		.filter(Boolean)
+
+	if (!cartographics.length) return null
+
+	let west = cartographics[0].longitude
+	let east = cartographics[0].longitude
+	let south = cartographics[0].latitude
+	let north = cartographics[0].latitude
+
+	cartographics.forEach(carto => {
+		west = Math.min(west, carto.longitude)
+		east = Math.max(east, carto.longitude)
+		south = Math.min(south, carto.latitude)
+		north = Math.max(north, carto.latitude)
+	})
+
+	return { west, east, south, north }
+}
+
+export const getBoundsFromLngLats = points => {
+	if (!points?.length) return null
+	let west = points[0].lng
+	let east = points[0].lng
+	let south = points[0].lat
+	let north = points[0].lat
+	points.forEach(point => {
+		west = Math.min(west, point.lng)
+		east = Math.max(east, point.lng)
+		south = Math.min(south, point.lat)
+		north = Math.max(north, point.lat)
+	})
+	return {
+		west: Cesium.Math.toRadians(west),
+		east: Cesium.Math.toRadians(east),
+		south: Cesium.Math.toRadians(south),
+		north: Cesium.Math.toRadians(north),
+	}
+}
+
+export const rectanglePositionsFromBounds = bounds => {
+	if (!bounds) return []
+	const { west, east, south, north } = bounds
+	return [
+		Cesium.Cartesian3.fromRadians(west, south),
+		Cesium.Cartesian3.fromRadians(east, south),
+		Cesium.Cartesian3.fromRadians(east, north),
+		Cesium.Cartesian3.fromRadians(west, north),
+	]
+}
+
+export const getCenterFromBounds = bounds => {
+	if (!bounds) return null
+	return new Cesium.Cartographic(
+		(bounds.west + bounds.east) / 2,
+		(bounds.south + bounds.north) / 2,
+		0
+	)
+}
+
+export const getMetersBetween = (start, end) => {
+	const geodesic = new Cesium.EllipsoidGeodesic(start, end)
+	return geodesic.surfaceDistance
+}
+
+export const buildEllipsePositions = (centerCartesian, semiMajor, semiMinor, steps = 60) => {
+	if (!centerCartesian || !semiMajor || !semiMinor) return []
+	const positions = []
+	const frame = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian)
+	for (let i = 0; i < steps; i += 1) {
+		const angle = (i / steps) * Math.PI * 2
+		const x = Math.cos(angle) * semiMajor
+		const y = Math.sin(angle) * semiMinor
+		const local = new Cesium.Cartesian3(x, y, 0)
+		const world = Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3())
+		positions.push(world)
+	}
+	return positions
+}
+
+export const cartesianToLocal = (centerCartesian, positionCartesian) => {
+	const frame = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian)
+	const inverse = Cesium.Matrix4.inverse(frame, new Cesium.Matrix4())
+	return Cesium.Matrix4.multiplyByPoint(inverse, positionCartesian, new Cesium.Cartesian3())
+}
diff --git a/applications/task-work-order/env/.env.development b/applications/task-work-order/env/.env.development
index fe1504c..ed8d14a 100644
--- a/applications/task-work-order/env/.env.development
+++ b/applications/task-work-order/env/.env.development
@@ -2,7 +2,7 @@
  # @Author       : yuan
  # @Date         : 2026-01-07 14:58:30
  # @LastEditors  : yuan
- # @LastEditTime : 2026-01-16 11:02:24
+ # @LastEditTime : 2026-01-28 09:21:21
  # @FilePath     : \applications\task-work-order\env\.env.development
  # @Description  :
  # Copyright 2026 OBKoro1, All Rights Reserved.
@@ -15,7 +15,7 @@
 
 #开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
 # VITE_APP_URL = https://wrj.shuixiongit.com/api
-VITE_APP_URL= http://192.168.1.204
+VITE_APP_URL= http://192.168.1.227
 
 
 
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue b/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue
index d0f5699..7757fad 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue
@@ -64,7 +64,7 @@
 						<template v-slot="{ row }">
 							<el-link  type="primary"  @click="openForm('view', row)">查看</el-link>
 <!--							<el-link  type="primary"  @click="openForm('edit', row)">编辑</el-link>-->
-							<el-link  type="primary"  @click="handleDelete(row)" v-if="!['1', '2'].includes(row.innovationStatus)">删除</el-link>
+							<el-link  type="primary"  @click="handleDelete(row)" v-if="!['1', '2'].includes(row.innovationStatus) && row.isTheSameDepartment">删除</el-link>
 						</template>
 					</el-table-column>
 				</el-table>
@@ -96,7 +96,9 @@
 	gdApplicationInnovationPageApi,
 	gdApplicationInnovationRemoveApi,
 } from '@/views/orderView/orderDataManage/appInnovation/appInnovationApi'
-
+import { useStore } from 'vuex'
+const store = useStore()
+const userDepartment = store.state.user.userInfo?.dept_id
 // 初始化查询参数
 const initSearchParams = () => ({
 	caseName: '', // 案例名称
@@ -128,6 +130,9 @@
 	loading.value = true
 	try {
 		const res = await gdApplicationInnovationPageApi(searchParams.value)
+		res?.data?.data?.records.forEach(item => {
+			item.isTheSameDepartment = String(item.createDept) === String(userDepartment)
+		})
 		list.value = res?.data?.data?.records ?? []
 		total.value = res?.data?.data?.total ?? 0
 	} finally {
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 239cb33..6d685f0 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
@@ -323,10 +323,21 @@
 }
 
 // 文件移除
+// const handleFileRemove = (file, fileListVal) => {
+// uploadedFiles.value = fileListVal
+// }
 const handleFileRemove = (file, fileListVal) => {
-uploadedFiles.value = fileListVal
+  // 从展示列表中移除
+  fileList.value = fileListVal
+  
+  // 从已上传列表中移除
+  const index = uploadedFiles.value.findIndex(item => 
+    item.attachId === file.attachId || item.name === file.name
+  )
+  if (index > -1) {
+    uploadedFiles.value.splice(index, 1)
+  }
 }
-
 // 上传前验证
 const beforeFileUpload = file => {
 	const allowedExtensions = ['.zip', '.rar', '.7z', '.tar', '.gz']
@@ -364,45 +375,53 @@
 
 // 提交数据的通用函数
 async function submitForm(objectionStatus) {
-	const isValid = await formRef.value?.validate().catch(() => false)
-	if (!isValid) return
-	 // 只在新增时校验附件是否上传
-	if (dialogMode.value === 'add' && uploadedFiles.value.length === 0) {
-		ElMessage.warning('请上传附件')
-		return
-	}
-	submitting.value = true
+  const isValid = await formRef.value?.validate().catch(() => false)
+  if (!isValid) return
+  
+  // 在编辑模式下,如果已有附件,可以不强制上传新附件
+  if (dialogMode.value === 'add' && uploadedFiles.value.length === 0) {
+    return ElMessage.warning('请上传附件')
+  }
+  
+  submitting.value = true
 
-	try {
-		// 构建符合接口要求的提交数据
-		const submitData = {
-			// 编辑模式下传递id
-			...(formData.value.id && { id: formData.value.id }),
-			title: formData.value.title,
-			objectionType: formData.value.objectionType,
-			submitter: formData.value.submitter,
-			submitterContact: formData.value.submitterContact,
-			catalogResourceName: formData.value.catalogResourceName,
-			objectionDesc: formData.value.objectionDesc,
-			objectionBasis: formData.value.objectionBasis,
-			areaCode: formData.value.areaCode || '',
-			handleUnit: formData.value.handleUnit || '',
-		  // 只在有附件时才构建附件列表
-      ...(uploadedFiles.value.length > 0 && {
-        attachmentList: uploadedFiles.value.map(file => ({
-          attachId: file.attachId,
-          attachName: file.originalName,
-        })),
-      }),
-			objectionStatus: objectionStatus
-		}
-		await gdDataObjectionSubmitApi(submitData)
-		ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
-		visible.value = false
-		emit('success')
-	} finally {
-		submitting.value = false
-	}
+  try {
+    // 构建符合接口要求的提交数据
+    const submitData = {
+      // 编辑模式下传递id
+      ...(formData.value.id && { id: formData.value.id }),
+      title: formData.value.title,
+      objectionType: formData.value.objectionType,
+      submitter: formData.value.submitter,
+      submitterContact: formData.value.submitterContact,
+      catalogResourceName: formData.value.catalogResourceName,
+      objectionDesc: formData.value.objectionDesc,
+      objectionBasis: formData.value.objectionBasis,
+      areaCode: formData.value.areaCode || '',
+      handleUnit: formData.value.handleUnit || '',
+      // 构建附件列表 - 使用当前已上传的文件(包括回显的)
+			...(uploadedFiles.value.length > 0 && {
+				attachmentList: uploadedFiles.value.map(file => ({
+					attachId: file.attachId,
+					attachName: file.originalName,
+				})),
+			}),
+      objectionStatus: objectionStatus
+    }
+    
+    // 如果是编辑且没有修改附件,可以保留原有附件ID
+    if (dialogMode.value === 'edit' && formData.value.attachIds) {
+      // 这里可以根据业务需求决定是否保留原附件ID
+      // 如果接口支持更新时重新上传附件ID列表,可以这样做
+    }
+    
+    await gdDataObjectionSubmitApi(submitData)
+    ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
+    visible.value = false
+    emit('success')
+  } finally {
+    submitting.value = false
+  }
 }
 
 // 提交新增/编辑(保存)
@@ -439,12 +458,42 @@
 }
 // 加载详情
 async function loadDetail() {
-	if (!formData.value.id) return
-	const res = await gdDataObjectionDetailApi({ id: formData.value.id })
-	formData.value = res?.data?.data ?? {}
-	if (formData.value.attachIds) {
-		getAttachDetail(formData.value.attachIds)
-	}
+  if (!formData.value.id) return
+  const res = await gdDataObjectionDetailApi({ id: formData.value.id })
+  formData.value = res?.data?.data ?? {}
+  
+  // 处理附件回显
+  if (formData.value.attachIds) {
+    await getAttachDetail(formData.value.attachIds)
+    // 回显到上传组件
+    updateFileListForDisplay()
+  }
+}
+
+// 更新文件列表用于显示
+function updateFileListForDisplay() {
+  fileList.value = []
+  uploadedFiles.value = []
+  
+  if (attachmentDetailsList.value && attachmentDetailsList.value.length > 0) {
+    attachmentDetailsList.value.forEach(attach => {
+      const fileItem = {
+        name: attach.originalName || attach.name,
+        url: attach.link, // 用于预览
+        attachId: attach.id, // 文件ID
+        originalName: attach.originalName,
+        link: attach.link
+      }
+      
+      fileList.value.push({
+        name: fileItem.name,
+        url: fileItem.url,
+        status: 'success' // 已上传状态
+      })
+      
+      uploadedFiles.value.push(fileItem)
+    })
+  }
 }
 // 获取附件详情
 async function getAttachDetail(attachIds) {
@@ -478,17 +527,22 @@
 
 // 打开弹框
 async function open({ mode = 'add', row } = {}) {
-	dialogMode.value = mode
-	// 重置文件列表
-	fileList.value = []
-	uploadedFiles.value = []
-	// 重置表单数据
-	formData.value = dialogMode.value === 'add' ? initForm() : row
-	// 重置反馈表单数据
-	feedbackFormData.value = initFeedbackForm()
-	if (dialogMode.value !== 'add') {
-		await loadDetail()
-	}
+  dialogMode.value = mode
+  // 重置文件列表
+  fileList.value = []
+  uploadedFiles.value = []
+  // 重置表单数据
+  formData.value = dialogMode.value === 'add' ? initForm() : row
+  // 重置反馈表单数据
+  feedbackFormData.value = initFeedbackForm()
+  
+  if (dialogMode.value !== 'add') {
+    // 如果是编辑,设置ID并加载详情
+    if (row && row.id) {
+      formData.value.id = row.id
+    }
+    await loadDetail()
+  }
 }
 
 defineExpose({ open })
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue b/applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
index 3d0a7cf..344d20b 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
@@ -127,8 +127,6 @@
 			item.isTheSameDepartment = String(item.createDept) === String(userDepartment)
 		})
 		list.value = res?.data?.data?.records ?? []
-		console.log('list',list.value);
-		
 		total.value = res?.data?.data?.total ?? 0
 	} finally {
 		loading.value = false

--
Gitblit v1.9.3