From 2dbd3103d91934a6ec6ef48baa288b56a4843472 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Mon, 12 Jan 2026 19:41:26 +0800
Subject: [PATCH] feat: 完成区域

---
 applications/drone-command/src/utils/cesium/DrawPolygon.js                |   70 ++--
 applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue  |  379 +++++++++++++++++++++++++++++++
 applications/drone-command/src/views/areaManage/partition/index.vue       |  179 ++++++++++++++
 applications/drone-command/src/views/areaManage/partition/partitionApi.js |   55 ++++
 applications/drone-command/src/utils/cesium/mapUtil.js                    |   17 -
 5 files changed, 646 insertions(+), 54 deletions(-)

diff --git a/applications/drone-command/src/utils/cesium/DrawPolygon.js b/applications/drone-command/src/utils/cesium/DrawPolygon.js
index 59cbf6c..6c37a35 100644
--- a/applications/drone-command/src/utils/cesium/DrawPolygon.js
+++ b/applications/drone-command/src/utils/cesium/DrawPolygon.js
@@ -88,18 +88,18 @@
 	// ============ 发布订阅机制 ============
 
 	// 外部订阅数据变化
-	subscribe (key, listener) {
+	subscribe(key, listener) {
 		this.listeners.push({ key, listener })
 	}
 
 	// 通知订阅者
-	notify (key, data) {
+	notify(key, data) {
 		this.listeners.filter(subscriber => subscriber.key === key).forEach(subscriber => subscriber.listener(data))
 	}
 
 	// ============ 绘制相关 ============
 	// 编辑图斑
-	editThePatch (data) {
+	editThePatch(data) {
 		this.isPreviewMode = data
 		// 关闭编辑能力时隐藏端点/中点,并清空中点,避免残留交互
 		if (!this.isPreviewMode) {
@@ -117,12 +117,12 @@
 		}
 	}
 	// 删除测区
-	deleteTheArea (data) {
+	deleteTheArea(data) {
 		this.isDeleteTheArea = data
 	}
 
 	// 开始绘制
-	startDrawing () {
+	startDrawing() {
 		this.drawingMode = true
 		this.curPolygon = new Cesium.PolygonHierarchy()
 
@@ -150,7 +150,7 @@
 	}
 
 	// 创建多边形实体(含边界线)
-	createPolygonEntity () {
+	createPolygonEntity() {
 		this.polygonEntity = this.editPolygonDataSource.entities.add({
 			name: DrawPolygon.ENTITY_NAMES.POLYGON,
 			id: DrawPolygon.ENTITY_NAMES.POLYGON_ID,
@@ -174,7 +174,7 @@
 	}
 
 	// 创建端点实体
-	createPointEntity (position, isAdd) {
+	createPointEntity(position, isAdd) {
 		const pointIndex = isAdd ? this.curPolygon.positions.length - 2 : this.curPolygon.positions.length - 1
 
 		this.editPolygonPointDataSource.entities.add({
@@ -193,7 +193,7 @@
 		})
 	}
 
-	createMidPointEntity (startInd, position) {
+	createMidPointEntity(startInd, position) {
 		// 使用 CallbackProperty 让中点位置随端点拖拽实时计算
 		// startInd 表示该中点属于边:(startInd) -> (startInd + 1)
 		const updatePosition = () => {
@@ -224,7 +224,7 @@
 		})
 	}
 
-	rebuildEditPoints () {
+	rebuildEditPoints() {
 		// 插入/删除端点后:端点实体 id 与 customData.ind 需要全量重建,保证索引与 positions 一致
 		if (!this.isPreviewMode) return
 		if (!this.editPolygonPointDataSource || !this.curPolygon?.positions) return
@@ -248,7 +248,7 @@
 		})
 	}
 
-	rebuildMidPoints () {
+	rebuildMidPoints() {
 		// 仅在“顶点数量变化/切换编辑态”时维护中点实体数量
 		// 拖拽过程中中点位置由 CallbackProperty 自动更新,不需要重建
 		if (!this.isPreviewMode) return
@@ -286,7 +286,7 @@
 		})
 	}
 
-	insertPointFromMidPoint (midPointEntity) {
+	insertPointFromMidPoint(midPointEntity) {
 		// 点击中点:在对应边上插入一个新端点(白点),并立即进入拖拽态
 		if (!this.isPreviewMode) return
 		if (!this.editingMode) return
@@ -319,7 +319,7 @@
 		this.notify('getPolygonPositions', this.curPolygon.positions)
 	}
 	// 清除不在范围内的点
-	removeLastInvalidPoint () {
+	removeLastInvalidPoint() {
 		const posLen = this.curPolygon.positions.length
 		if (posLen === 0) return
 
@@ -348,7 +348,7 @@
 		}
 	}
 	// 添加一个点
-	addPosition (position, isAdd = true) {
+	addPosition(position, isAdd = true) {
 		// 第一个点要重复压入一次,形成动态绘制效果
 		if (this.curPolygon.positions.length === 0 && isAdd) {
 			this.curPolygon.positions.push(position.clone())
@@ -372,7 +372,7 @@
 	// ============ 鼠标事件 ============
 
 	// 鼠标左键按下(选中端点拖动)
-	handleLeftDown (movement) {
+	handleLeftDown(movement) {
 		if (!this.editingMode) return
 
 		const pickedEntity = this.viewer.scene.pick(movement.position)?.id
@@ -393,7 +393,7 @@
 	}
 
 	// 鼠标左键抬起(拖拽结束)
-	handleLeftUp () {
+	handleLeftUp() {
 		if (!(this.editingMode && this.curPolygon?.positions && this.draggedEntity)) return
 
 		if (this.currentDragPointIsValid && this.isDragging) {
@@ -426,7 +426,7 @@
 	}
 
 	// 鼠标移动
-	handleMouseMove (movement) {
+	handleMouseMove(movement) {
 		if (!this.drawingMode && !this.editingMode) return
 
 		const cartesian = this.viewer.scene.pickPosition(movement.endPosition)
@@ -464,7 +464,7 @@
 	}
 
 	// 鼠标左键点击
-	handleLeftClick (click) {
+	handleLeftClick(click) {
 		this.removeMenuPopup()
 
 		const pickedAllEntity = this.viewer.scene.drillPick(click.position).filter(i => i.id)
@@ -520,7 +520,7 @@
 	}
 
 	// 鼠标右键点击(弹出菜单)
-	handleRightClick (click) {
+	handleRightClick(click) {
 		const that = this
 		if (that.drawingMode) return
 
@@ -556,7 +556,7 @@
 	// ============ 删除相关 ============
 
 	// 删除所有实体
-	removeEntities () {
+	removeEntities() {
 		if (this.editPolygonDataSource) {
 			this.editPolygonDataSource.entities.removeAll()
 			this.editPolygonDataSource = null
@@ -577,7 +577,7 @@
 	}
 
 	// 完成绘制
-	finishDrawing () {
+	finishDrawing() {
 		this.curPolygon.positions.pop()
 
 		if (this.curPolygon.positions.length >= 3) {
@@ -603,20 +603,20 @@
 	}
 
 	// 删除多边形
-	delPolygon () {
+	delPolygon() {
 		this.removeEntities()
 		this.removeMenuPopup()
 		this.notify('getPolygonPositions', [])
 		this.startDrawing()
 	}
 	// 删除图斑
-	delSpot () {
+	delSpot() {
 		this.removeEntities()
 		this.isPreviewMode = true
 		this.startDrawing()
 	}
 	// 删除端点
-	delPoint () {
+	delPoint() {
 		if (this.curPolygon.positions.length <= 3) {
 			this.removeMenuPopup()
 			return ElMessage.warning('端点不可少于3个')
@@ -634,7 +634,7 @@
 	// ============ 工具方法 ============
 
 	// 创建右键菜单
-	createMenuPopup (type = 'polygon') {
+	createMenuPopup(type = 'polygon') {
 		const menuPopupVBox = document.createElement('div')
 		menuPopupVBox.id = 'planarPolygonEdit'
 		menuPopupVBox.className = 'planar-polygon-edit-tooltip'
@@ -660,7 +660,7 @@
 	}
 
 	// 移除菜单
-	removeMenuPopup () {
+	removeMenuPopup() {
 		const that = this
 		if (that.menuPopup) {
 			that.menuPopup.removeEventListener('click', that.delPolygon)
@@ -672,13 +672,13 @@
 	}
 
 	// 更新多边形样式(正常/错误)
-	updatePolygonAppearance (polygonColor, lineColor) {
+	updatePolygonAppearance(polygonColor, lineColor) {
 		this.polygonEntity.polygon.material = polygonColor
 		this.polygonEntity.polyline.material = lineColor
 	}
 
 	// 禁用地图交互
-	disableMapControl () {
+	disableMapControl() {
 		const controller = this.viewer.scene.screenSpaceCameraController
 		controller.enableRotate = false
 		controller.enableTranslate = false
@@ -686,7 +686,7 @@
 	}
 
 	// 启用地图交互
-	enableMapControl () {
+	enableMapControl() {
 		const controller = this.viewer.scene.screenSpaceCameraController
 		controller.enableRotate = true
 		controller.enableTranslate = true
@@ -694,7 +694,7 @@
 	}
 
 	// 检查多边形是否自交
-	curDragPointIsValid (positions) {
+	curDragPointIsValid(positions) {
 		if (positions.length < 3) return true
 
 		const cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray(positions)
@@ -710,12 +710,12 @@
 		return intersections.features.length === 0
 	}
 	// 初始化已有多边形
-	async initPolygon (viewer, positions, isPurePreview = false) {
+	async initPolygon(viewer, positions, isPurePreview = false) {
 		this.initHandler(viewer)
 		this.isPureSpotPreview = isPurePreview
 		this.startDrawing()
 		let newPosition = positions.map(item => {
-			return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item?.height||0))
+			return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item?.height || 0))
 		})
 		// 预览航线的时候调用
 		if (!this.isPureSpotPreview) {
@@ -737,7 +737,7 @@
 			duration: 0.5,
 		})
 
-		const pointList = await getPointPositionsHeight(positions, viewer)
+		let pointList = await getPointPositionsHeight(positions, viewer)
 		flyVisual({ positionsData: pointList.map(item => [item.lng, item.lat, item.ASL]), viewer })
 
 		this.drawingMode = false
@@ -748,7 +748,7 @@
 	}
 
 	// 初始化事件处理器
-	initHandler (viewer) {
+	initHandler(viewer) {
 		this.viewer = viewer
 		this.startDrawing()
 
@@ -771,7 +771,7 @@
 	}
 
 	// 移除事件处理器
-	removeHandler () {
+	removeHandler() {
 		if (this.handler) {
 			const eventTypes = [
 				Cesium.ScreenSpaceEventType.LEFT_DOWN,
@@ -792,7 +792,7 @@
 	/**
 	 * 销毁实例,释放资源
 	 */
-	destroy () {
+	destroy() {
 		if (!this.viewer) return
 
 		this.removeMenuPopup()
diff --git a/applications/drone-command/src/utils/cesium/mapUtil.js b/applications/drone-command/src/utils/cesium/mapUtil.js
index 117ada6..89bf787 100644
--- a/applications/drone-command/src/utils/cesium/mapUtil.js
+++ b/applications/drone-command/src/utils/cesium/mapUtil.js
@@ -346,31 +346,24 @@
 	return new Promise((resolve, reject) => {
 		if (!data || !data.length) {
 			resolve([])
-
 			return
 		}
-
 		// 假设 viewer 已经初始化并且 terrainProvider 是有效的
 		const terrainProvider = viewer?.terrainProvider
-
 		// 创建 Cartographic 对象
 		const cartographics = data.map(item => {
 			const { lng, lat } = item
-
 			return Cesium.Cartographic.fromDegrees(Number(lng), Number(lat))
 		})
-
 		// 获取地形数据的Promise
 		const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cartographics)
-
 		// 使用 Cesium.when 处理 Promise
 		promise
 			.then(function (updatedPositions) {
-				// updatedPositions 是一个数组,包含更新后的 Cartographic 对象
+				//  是一个数组,包含更新后的 Cartographic 对象
 				const newPosition = updatedPositions.map((item, index) => {
 					const longitude = Cesium.Math.toDegrees(item.longitude)
 					const latitude = Cesium.Math.toDegrees(item.latitude)
-
 					let pointData = {
 						...data[index],
 						longitude,
@@ -378,18 +371,14 @@
 						ASL: Number(item.height),
 						customHeight: Number(item.height),
 					}
-
 					if (droneHeight) pointData.TH = Number(item.height) + Number(droneHeight)
-
 					return pointData
 				})
-
 				resolve(newPosition)
-				// 在这里,你可以使用 height 值进行后续操作
 			})
 			.catch(function (error) {
-				// console.error('获取高程时发生错误:', error);
-				reject(error)
+				console.log('获取高程时发生错误:', error)
+				resolve(data.map(item => ({ ...item, ASL: 0, customHeight: 0, longitude: item.lng, latitude: item.lat })))
 			})
 	})
 }
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
new file mode 100644
index 0000000..c5b99ab
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -0,0 +1,379 @@
+<template>
+	<el-dialog v-model="visible" :title="titleEnum[dialogMode]" :close-on-click-modal="false" width="80%">
+		<div class="bodyBox">
+			<div class="leftMap ztzf-cesium" id="mapContainer"></div>
+			<div class="rightInfo">
+				<div v-if="readonly">
+					<el-row>
+						<el-col :span="24">
+							<div>区域名称: {{ formData.areaName }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div>区域位置: {{ formatLocation(formData) }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div>区域面积: {{ formData.areaSize }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div>区域类型: {{ getOptionLabel(formData.areaType, areaTypeOptions) }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div>触发条件: {{ formData.triggerCondition }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div>响应机制: {{ formData.responseMechanism }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div>管控级别: {{ getOptionLabel(formData.controlLevel, controlLevelOptions) }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div>关联派出所: {{ formData.policeStationName }}</div>
+						</el-col>
+						<el-col :span="24">
+							<div>可飞行时间段: {{ formatFlyDate(formData) }}</div>
+						</el-col>
+					</el-row>
+				</div>
+				<el-form v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly" label-width="100px">
+					<el-row>
+						<el-col :span="24">
+							<el-form-item label="区域名称" prop="areaName">
+								<el-input v-model="formData.areaName" maxlength="50" placeholder="请输入" clearable />
+							</el-form-item>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="区域位置" prop="longitude">
+								<div>{{ formatLocation(formData) }}</div>
+							</el-form-item>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="区域面积" prop="areaSize">
+								{{ formData.areaSize }}
+							</el-form-item>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="区域类型" prop="areaType">
+								<el-select v-model="formData.areaType" placeholder="请选择" clearable>
+									<el-option
+										v-for="item in areaTypeOptions"
+										:key="item.value"
+										:label="item.label"
+										:value="item.value"
+									/>
+								</el-select>
+							</el-form-item>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="响应机制" prop="responseMechanism">
+								<el-input v-model="formData.responseMechanism" maxlength="200" placeholder="请输入" clearable />
+							</el-form-item>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="触发条件" prop="triggerCondition">
+								<el-input v-model="formData.triggerCondition" maxlength="200" placeholder="请输入" clearable />
+							</el-form-item>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="管控级别" prop="controlLevel">
+								<el-select v-model="formData.controlLevel" placeholder="请选择" clearable>
+									<el-option
+										v-for="item in controlLevelOptions"
+										:key="item.value"
+										:label="item.label"
+										:value="item.value"
+									/>
+								</el-select>
+							</el-form-item>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="可飞行时段" prop="flyDateRange">
+								<el-date-picker
+									v-model="flyDateRange"
+									type="datetimerange"
+									range-separator="至"
+									start-placeholder="开始时间"
+									end-placeholder="结束时间"
+									value-format="YYYY-MM-DD HH:mm:ss"
+									clearable
+								/>
+							</el-form-item>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="关联派出所" prop="policeStationId">
+								<el-select 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>
+						</el-col>
+						<el-col :span="24">
+							<el-form-item label="关联设备" prop="deviceIds">
+								<el-select v-model="deviceIdList" multiple collapse-tags placeholder="请选择" clearable>
+									<el-option v-for="item in deviceOptions" :key="item.id" :label="item.deviceName" :value="item.id" />
+								</el-select>
+							</el-form-item>
+						</el-col>
+					</el-row>
+				</el-form>
+			</div>
+		</div>
+		<template #footer>
+			<el-button @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+			<el-button v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit">
+				确定
+			</el-button>
+		</template>
+	</el-dialog>
+</template>
+
+<script setup>
+import { computed, nextTick, onMounted, ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { fwAreaDivideDetailApi, fwAreaDivideSubmitApi, fwDevicePageApi, fwPoliceStationPageApi } from './partitionApi'
+import { fieldRules } from '@ztzf/utils'
+import { PublicCesium } from '@/utils/cesium/publicCesium'
+import { DrawPolygon } from '@/utils/cesium/DrawPolygon'
+import { cartesian3Convert } from '@/utils/cesium/mapUtil'
+import * as turf from '@turf/turf'
+import * as Cesium from 'cesium'
+
+const initForm = () => ({
+	areaName: '', // 区域名称
+	areaSize: null, // 区域面积
+	areaType: '', // 区域类型
+	controlLevel: '', // 管控级别
+	deviceIds: '', // 关联设备ID
+	flyDateEnd: '', // 可飞行结束
+	flyDateStart: '', // 可飞行开始
+	latitude: null, // 区域中心纬度
+	longitude: null, // 区域中心经度
+	policeStationId: '', // 关联派出所id
+	responseMechanism: '', // 响应机制
+	triggerCondition: '', // 触发条件
+})
+
+const areaTypeOptions = [
+	{ label: '报警区', value: '报警区' },
+	{ label: '重点管制区', value: '重点管制区' },
+]
+const controlLevelOptions = [
+	{ label: '日常', value: '日常' },
+	{ label: '重点', value: '重点' },
+	{ label: '严密', value: '严密' },
+]
+
+const emit = defineEmits(['success'])
+const formRef = ref(null) // 表单实例
+const formData = ref(initForm()) // 表单数据
+const visible = defineModel() // 弹框显隐
+const dialogMode = ref('add') // 弹框模式
+const submitting = ref(false) // 提交中
+const readonly = computed(() => dialogMode.value === 'view')
+const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' })
+const flyDateRange = ref([]) // 飞行时间
+const deviceIdList = ref([]) // 关联设备ID
+const policeStationOptions = ref([]) // 关联派出所
+const deviceOptions = ref([]) // 关联设备
+let viewer
+let drawPolygonExample
+let pointList = []
+
+const rules = {
+	areaName: fieldRules(true, 50),
+	areaType: fieldRules(true, 0),
+	controlLevel: fieldRules(true, 0),
+}
+
+// 关闭弹框
+function handleCancel() {
+	visible.value = false
+}
+
+// 提交新增/编辑
+async function handleSubmit() {
+	const isValid = await formRef.value?.validate().catch(() => false)
+	if (!isValid) return
+	submitting.value = true
+	try {
+		const [start, end] = flyDateRange.value ?? []
+		formData.value.flyDateStart = start || ''
+		formData.value.flyDateEnd = end || ''
+		formData.value.deviceIds = deviceIdList.value.length ? deviceIdList.value.join(',') : ''
+		let str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
+		formData.value.geom = `POLYGON((${str})) | 4326`
+		await fwAreaDivideSubmitApi(formData.value)
+		ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
+		visible.value = false
+		emit('success')
+	} finally {
+		submitting.value = false
+	}
+}
+
+// 加载详情
+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)
+	deviceIdList.value = formData.value.deviceIds ? formData.value.deviceIds.split(',') : []
+}
+
+// 格式化区域位置
+function formatLocation(row) {
+	if (row?.longitude == null || row?.latitude == null) return ''
+	return `${row.longitude}, ${row.latitude}`
+}
+
+// 格式化可飞行时间段
+function formatFlyDate(row) {
+	if (!row?.flyDateStart && !row?.flyDateEnd) return ''
+	return `${row.flyDateStart || '-'} ~ ${row.flyDateEnd || '-'}`
+}
+
+// 获取选项文本
+function getOptionLabel(value, options) {
+	const match = options.find(item => item.value === value)
+	return match?.label ?? value ?? ''
+}
+
+// 初始化地图实例
+function initMap() {
+	const publicCesiumInstance = new PublicCesium({
+		dom: 'mapContainer',
+		flatMode: false,
+		terrain: true,
+		layerMode: 4,
+		boundary: false,
+	})
+	viewer = publicCesiumInstance.getViewer()
+}
+
+// 绘制完成回调
+const drawFinished = async data => {
+	pointList = _.cloneDeep(data).map(item => {
+		const val = cartesian3Convert(item, viewer)
+		return { ...val, lng: val.longitude, lat: val.latitude }
+	})
+	const polygon = turf.polygon([
+		[...pointList.map(item => [item.longitude, item.latitude]), [pointList[0].longitude, pointList[0].latitude]],
+	])
+	const center = turf.centerOfMass(polygon)
+	formData.value.areaSize = _.round(turf.area(polygon), 2)
+	formData.value.longitude = center.geometry.coordinates[0]
+	formData.value.latitude = center.geometry.coordinates[1]
+}
+
+// 新增模式绘制
+function addPolygon() {
+	drawPolygonExample = new DrawPolygon(viewer)
+	drawPolygonExample.initHandler(viewer)
+	drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
+}
+
+// 解析经纬度
+function getLonLat(str) {
+	if (!str) return []
+	return str
+		.replace('POLYGON((', '')
+		.replace(')) | 4326', '')
+		.split(',')
+		.map(pair => {
+			const [lat, lon] = pair.trim().split(' ').map(Number)
+			return [lon, lat]
+		})
+}
+
+// 编辑面
+function editPolygon() {
+	if (!formData.value?.geom) return
+	const grouped = getLonLat(formData.value.geom)
+	grouped.pop()
+	pointList = grouped.map(item => ({ longitude: item[0], latitude: item[1] }))
+	drawPolygonExample?.destroy()
+	drawPolygonExample = new DrawPolygon(viewer)
+	drawPolygonExample.initPolygon(
+		viewer,
+		grouped.map(item => ({ lng: item[0], lat: item[1] }))
+	)
+	drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
+}
+
+// 查看面
+function viewPolygon() {
+	if (!formData.value?.geom) return
+	const grouped = getLonLat(formData.value.geom)
+	grouped.pop()
+	pointList = grouped.map(item => ({ longitude: item[0], latitude: item[1] }))
+	const result = grouped.flat().flat()
+	const mian = 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(mian)
+}
+
+// 获取派出所列表
+async function getPoliceStationList() {
+	const res = await fwPoliceStationPageApi({ current: 1, size: 9999 })
+	policeStationOptions.value = res?.data?.data?.records ?? []
+}
+
+// 获取设备列表
+async function getDeviceList() {
+	const res = await fwDevicePageApi({ current: 1, size: 9999 })
+	deviceOptions.value = res?.data?.data?.records ?? []
+}
+
+// 打开弹框
+async function open({ mode, row } = {}) {
+	dialogMode.value = mode || 'add'
+	formData.value = dialogMode.value === 'add' ? initForm() : row
+	await nextTick()
+	initMap()
+	if (dialogMode.value === 'add') {
+		addPolygon()
+	} else if (dialogMode.value === 'edit') {
+		await loadDetail()
+		editPolygon()
+	} else {
+		await loadDetail()
+		viewPolygon()
+	}
+}
+
+onMounted(() => {
+	getPoliceStationList()
+	getDeviceList()
+})
+
+defineExpose({
+	open,
+})
+</script>
+
+<style scoped lang="scss">
+.bodyBox {
+	display: flex;
+	height: 800px;
+	.leftMap {
+		width: 70%;
+		height: 100%;
+	}
+	.rightInfo {
+		width: 30%;
+		height: 100%;
+		overflow: auto;
+	}
+}
+</style>
diff --git a/applications/drone-command/src/views/areaManage/partition/index.vue b/applications/drone-command/src/views/areaManage/partition/index.vue
index 14a6b62..11bbd56 100644
--- a/applications/drone-command/src/views/areaManage/partition/index.vue
+++ b/applications/drone-command/src/views/areaManage/partition/index.vue
@@ -1,9 +1,178 @@
 <template>
-   <basic-container>
-    区域划分
-  </basic-container>
+	<basic-container>
+		<el-form ref="queryParamsRef" :model="searchParams">
+			<el-row :gutter="16">
+				<el-col :span="4">
+					<el-form-item label="区域名称" prop="areaName">
+						<el-input v-model="searchParams.areaName" placeholder="请输入" clearable @clear="handleSearch" />
+					</el-form-item>
+				</el-col>
+				<el-col :span="4">
+					<el-form-item label="区域类型" prop="areaType">
+						<el-select v-model="searchParams.areaType" placeholder="请选择" clearable @change="handleSearch">
+							<el-option v-for="item in areaTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="4">
+					<el-form-item>
+						<el-button @click="resetForm">重置</el-button>
+						<el-button type="primary" @click="handleSearch">查询</el-button>
+					</el-form-item>
+				</el-col>
+			</el-row>
+		</el-form>
+		<div>
+			<el-button type="primary" @click="openForm('add')">新增</el-button>
+			<el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+		</div>
+		<el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
+			<el-table-column type="selection" width="55" />
+			<el-table-column type="index" width="60" label="序号" />
+			<el-table-column prop="areaName" label="区域名称" />
+			<el-table-column label="区域位置">
+				<template v-slot="{ row }">
+					{{ formatLocation(row) }}
+				</template>
+			</el-table-column>
+			<el-table-column prop="areaSize" label="区域面积" />
+			<el-table-column prop="areaType" label="区域类型">
+				<template v-slot="{ row }">
+					{{ getOptionLabel(row.areaType, areaTypeOptions) }}
+				</template>
+			</el-table-column>
+			<el-table-column prop="triggerCondition" label="触发条件" />
+			<el-table-column prop="responseMechanism" label="响应机制" />
+			<el-table-column prop="controlLevel" label="管控级别">
+				<template v-slot="{ row }">
+					{{ getOptionLabel(row.controlLevel, controlLevelOptions) }}
+				</template>
+			</el-table-column>
+			<el-table-column prop="policeStationName" label="关联派出所" />
+			<el-table-column label="可飞行时间段">
+				<template v-slot="{ row }">
+					{{ formatFlyDate(row) }}
+				</template>
+			</el-table-column>
+			<el-table-column label="操作">
+				<template v-slot="{ row }">
+					<el-link @click="openForm('view', row)" type="primary">查看</el-link>
+					<el-link @click="openForm('edit', row)" type="warning">编辑</el-link>
+					<el-link @click="handleDelete(row)" type="danger">删除</el-link>
+				</template>
+			</el-table-column>
+		</el-table>
+		<div>
+			<el-pagination
+				v-model:current-page="searchParams.current"
+				v-model:page-size="searchParams.size"
+				:total="total"
+				@change="getList"
+			/>
+		</div>
+
+		<FormDiaLog v-if="dialogVisible" v-model="dialogVisible" ref="dialogRef" @success="getList" />
+	</basic-container>
 </template>
+
 <script setup>
+import { nextTick, onMounted, ref } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { fwAreaDividePageApi, fwAreaDivideRemoveApi } from './partitionApi'
+import FormDiaLog from './FormDiaLog.vue'
+
+const initSearchParams = () => ({
+	areaName: '', // 区域名称
+	areaType: '', // 区域类型
+	current: 1, // 当前页
+	size: 10, // 每页大小
+})
+
+const areaTypeOptions = [
+	{ label: '报警区', value: '报警区' },
+	{ label: '重点管制区', value: '重点管制区' },
+]
+const controlLevelOptions = [
+	{ label: '日常', value: '日常' },
+	{ label: '重点', value: '重点' },
+	{ label: '严密', value: '严密' },
+]
+
+const searchParams = ref(initSearchParams()) // 查询参数
+const total = ref(0) // 总条数
+const loading = ref(false) // 列表加载中
+const list = ref([]) // 列表数据
+const selectedIds = ref([]) // 勾选的ID列表
+const queryParamsRef = ref(null) // 查询表单实例
+const dialogRef = ref(null) // 弹框实例
+const dialogVisible = ref(false)
+
+// 获取列表
+async function getList() {
+	loading.value = true
+	try {
+		const res = await fwAreaDividePageApi(searchParams.value)
+		list.value = res?.data?.data?.records ?? []
+		total.value = res?.data?.data?.total ?? 0
+	} finally {
+		loading.value = false
+	}
+}
+
+// 查询
+function handleSearch() {
+	searchParams.value.current = 1
+	getList()
+}
+
+// 重置查询
+function resetForm() {
+	queryParamsRef.value?.resetFields()
+	searchParams.value.current = 1
+	getList()
+}
+
+// 删除
+async function handleDelete(row) {
+	const tips = row ? '该条' : '选中的项'
+	await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
+	const ids = row ? row.id : selectedIds.value.join(',')
+	await fwAreaDivideRemoveApi({ ids })
+	ElMessage.success('删除成功')
+	selectedIds.value = []
+	getList()
+}
+
+// 勾选值设置
+function handleSelectionChange(rows) {
+	selectedIds.value = rows.map(item => item.id)
+}
+
+function formatLocation(row) {
+	if (row?.longitude == null || row?.latitude == null) return ''
+	return `${row.longitude}, ${row.latitude}`
+}
+
+function formatFlyDate(row) {
+	if (!row?.flyDateStart && !row?.flyDateEnd) return ''
+	return `${row.flyDateStart || '-'} ~ ${row.flyDateEnd || '-'}`
+}
+
+function getOptionLabel(value, options) {
+	const match = options.find(item => item.value === value)
+	return match?.label ?? value ?? ''
+}
+
+// 新增/编辑/查看 弹框
+function openForm(mode, row) {
+	dialogVisible.value = true
+	nextTick(() => {
+		dialogRef.value?.open({ mode, row })
+	})
+}
+
+onMounted(() => {
+	getList()
+})
 </script>
-<style scoped lang="scss">
-</style>
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/areaManage/partition/partitionApi.js b/applications/drone-command/src/views/areaManage/partition/partitionApi.js
new file mode 100644
index 0000000..aee6603
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/partitionApi.js
@@ -0,0 +1,55 @@
+import request from '@/axios'
+
+// 查list
+export const fwAreaDividePageApi = params => {
+	return request({
+		url: `/drone-fw/area/fwAreaDivide/page`,
+		method: 'get',
+		params,
+	})
+}
+
+// 增加或更新
+export const fwAreaDivideSubmitApi = data => {
+	return request({
+		url: `/drone-fw/area/fwAreaDivide/submit`,
+		method: 'post',
+		data,
+	})
+}
+
+//删除
+export const fwAreaDivideRemoveApi = params => {
+	return request({
+		url: `/drone-fw/area/fwAreaDivide/remove`,
+		method: 'post',
+		params,
+	})
+}
+
+//详情
+export const fwAreaDivideDetailApi = params => {
+	return request({
+		url: `/drone-fw/area/fwAreaDivide/detail`,
+		method: 'get',
+		params,
+	})
+}
+
+// 关联派出所列表
+export const fwPoliceStationPageApi = params => {
+	return request({
+		url: `/drone-fw/area/fwPoliceStation/page`,
+		method: 'get',
+		params,
+	})
+}
+
+// 关联设备列表
+export const fwDevicePageApi = params => {
+	return request({
+		url: `/drone-fw/device/fwDevice/page`,
+		method: 'get',
+		params,
+	})
+}

--
Gitblit v1.9.3