From 1731a7c28db644bb862eb54089d15fe3991fd27c Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Mon, 02 Feb 2026 14:08:20 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue |  241 ++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 220 insertions(+), 21 deletions(-)

diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 9f490c4..9083c47 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -3,7 +3,7 @@
 		<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" />
+					:terrain="true" :layer-mode="4" :boundary="false" :zoom-to-boundary="dialogMode === 'add'" />
 				<div v-if="showTypePanel && !readonly" class="shape-type-panel">
 					<div class="panel-header">
 						<span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span>
@@ -14,8 +14,8 @@
 					<div class="panel-body">
 						<span class="panel-label">区域类型</span>
 						<el-select class="command-select" popper-class="command-select-popper" v-model="activeAreaType"
-							placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags :multiple="currentShapeType === 'buffer'"
-							@change="handleAreaTypeChange">
+							placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags
+							:multiple="currentShapeType === 'buffer'" @change="handleAreaTypeChange">
 							<el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue"
 								:value="item.dictKey" />
 						</el-select>
@@ -226,14 +226,19 @@
 <script setup>
 import { Close } from '@element-plus/icons-vue'
 
-import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
+import { computed, inject, nextTick, onBeforeUnmount, 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, EditManager, buildEllipsePositions } from '@/utils/cesium/shapeTools'
+import {
+	DrawManager,
+	EditManager,
+	addBufferRadiusLabelEntity,
+	buildEllipsePositions,
+} from '@/utils/cesium/shapeTools'
 import { cartesian3Convert } from '@/utils/cesium/mapUtil'
 import {
 	createDeviceRangePrimitive,
@@ -243,8 +248,8 @@
 } from '@/utils/cesium/deviceRange'
 import * as Cesium from 'cesium'
 import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
-import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
-import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice'
+import { fwPoliceStationQueryByPolygonsApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
+import { fwDeviceListByPolygonsApi } from '@/views/basicManage/deviceStock/fwDevice'
 import polygonIcon from '@/assets/images/areaMap/polygon.png'
 import rectIcon from '@/assets/images/areaMap/rect.png'
 import ellipseIcon from '@/assets/images/areaMap/ellipse.png'
@@ -302,8 +307,13 @@
 const deviceRangePrimitiveMap = new Map()
 const activeToolMode = ref('')
 const suppressDeviceFly = ref(false)
+const isDialogInitializing = ref(false)
 let lastSelectedDeviceIds = []
 let deviceRangeRenderToken = 0
+let deviceListTimer = null
+let deviceListRequestToken = 0
+let policeStationListTimer = null
+let policeStationRequestToken = 0
 
 const rules = {
 	areaName: fieldRules(true, 50),
@@ -406,6 +416,14 @@
 	() => visible.value,
 	val => {
 		if (!val) {
+			if (deviceListTimer) {
+				clearTimeout(deviceListTimer)
+				deviceListTimer = null
+			}
+			if (policeStationListTimer) {
+				clearTimeout(policeStationListTimer)
+				policeStationListTimer = null
+			}
 			viewEntity && viewer?.entities?.remove(viewEntity)
 			viewEntity = null
 			clearActiveTool()
@@ -420,6 +438,15 @@
 	val => {
 		updateActiveShapeAreaType(val)
 	}
+)
+watch(
+	() => shapeList.value,
+	() => {
+		if (isDialogInitializing.value) return
+		queueDeviceListRefresh()
+		queuePoliceStationRefresh()
+	},
+	{ deep: true }
 )
 
 // 加载详情
@@ -469,6 +496,103 @@
 		viewer.scene.primitives.remove(primitive)
 	}
 	deviceRangePrimitiveMap.clear()
+}
+
+function clearDeviceListState () {
+	deviceOptions.value = []
+	selectedDeviceRows.value = []
+	formData.value.deviceIds = ''
+	if (deviceTableRef.value) {
+		deviceTableRef.value.clearSelection()
+	}
+}
+
+function clearPoliceStationState () {
+	policeStationOptions.value = []
+	formData.value.policeStationId = ''
+}
+
+function formatWktNumber (value) {
+	if (!Number.isFinite(Number(value))) return null
+	return Number(value).toFixed(6)
+}
+
+function isSameWktPoint (left, right) {
+	if (!left || !right) return false
+	return Math.abs(left.lat - right.lat) < 0.000001 && Math.abs(left.lng - right.lng) < 0.000001
+}
+
+function buildPolygonWktFromPoints (points) {
+	if (!Array.isArray(points) || points.length < 3) return null
+	const ring = points
+		.map(resolveLngLatPoint)
+		.filter(Boolean)
+		.map(point => ({ lng: point.lng, lat: point.lat }))
+	if (ring.length < 3) return null
+	const first = ring[0]
+	const last = ring[ring.length - 1]
+	if (!isSameWktPoint(first, last)) {
+		ring.push({ ...first })
+	}
+	const coordText = ring
+		.map(point => {
+			const lng = formatWktNumber(point.lng)
+			const lat = formatWktNumber(point.lat)
+			if (lat === null || lng === null) return null
+			return `${lng} ${lat}`
+		})
+		.filter(Boolean)
+		.join(', ')
+	if (!coordText) return null
+	return `POLYGON((${coordText}))`
+}
+
+function buildEllipseWktFromMeta (center, semiMajor, semiMinor) {
+	if (!viewer) return null
+	const resolvedCenter = resolveLngLatPoint(center)
+	if (!resolvedCenter) return null
+	if (!Number.isFinite(Number(semiMajor)) || !Number.isFinite(Number(semiMinor))) return null
+	const centerCartesian = Cesium.Cartesian3.fromDegrees(
+		resolvedCenter.lng,
+		resolvedCenter.lat,
+		resolvedCenter.height || 0
+	)
+	const positions = buildEllipsePositions(centerCartesian, Number(semiMajor), Number(semiMinor))
+	if (!positions.length) return null
+	const points = positions
+		.map(item => {
+			const converted = cartesian3Convert(item, viewer)
+			return { lng: converted.longitude, lat: converted.latitude }
+		})
+		.filter(Boolean)
+	return buildPolygonWktFromPoints(points)
+}
+
+function resolveShapePolygonsWkt () {
+	const polygons = []
+	shapeList.value.forEach(shape => {
+		if (!shape) return
+		if (shape.drawType === 'buffer') {
+			const radii = Array.isArray(shape?.meta?.bufferRadii)
+				? shape.meta.bufferRadii.filter(item => Number.isFinite(item) && item > 0)
+				: []
+			const maxRadius = radii.length ? Math.max(...radii) : null
+			const wkt = buildEllipseWktFromMeta(shape?.meta?.center, maxRadius, maxRadius)
+			if (wkt) polygons.push(wkt)
+			return
+		}
+		if (shape.drawType === 'ellipse') {
+			const wkt = buildEllipseWktFromMeta(shape?.meta?.center, shape?.meta?.semiMajor, shape?.meta?.semiMinor)
+			if (wkt) {
+				polygons.push(wkt)
+				return
+			}
+		}
+		const points = getShapeDisplayPoints(shape)
+		const wkt = buildPolygonWktFromPoints(points)
+		if (wkt) polygons.push(wkt)
+	})
+	return polygons
 }
 
 async function renderDeviceRangeSingle (device) {
@@ -649,6 +773,10 @@
 							width: 2,
 							material: style.outline,
 						},
+					})
+					addBufferRadiusLabelEntity(dataSource, centerCartesian, radius, {
+						name: 'shape-display',
+						customData: { drawType: 'buffer', label: 'radius' },
 					})
 				})
 			}
@@ -1060,18 +1188,77 @@
 }
 
 // 获取派出所列表
-async function getPoliceStationList () {
-	const res = await fwPoliceStationListApi()
+function queuePoliceStationRefresh () {
+	if (!visible.value) return
+	if (policeStationListTimer) {
+		clearTimeout(policeStationListTimer)
+	}
+	policeStationListTimer = setTimeout(() => {
+		policeStationListTimer = null
+		void getPoliceStationListByPolygons()
+	}, 200)
+}
+
+async function getPoliceStationListByPolygons () {
+	if (!visible.value) return
+	const polygons = resolveShapePolygonsWkt()
+	if (!polygons.length) {
+		clearPoliceStationState()
+		return
+	}
+	policeStationRequestToken += 1
+	const requestToken = policeStationRequestToken
+	const res = await fwPoliceStationQueryByPolygonsApi({ polygons })
+	if (requestToken !== policeStationRequestToken) return
 	policeStationOptions.value = res?.data?.data ?? []
+	await nextTick()
+	const matched = policeStationOptions.value.find(
+		item => String(item.id) === String(formData.value.policeStationId)
+	)
+	if (!matched) {
+		formData.value.policeStationId = ''
+	}
 }
 
 // 获取设备列表
-async function getDeviceList () {
-	if (deviceOptions.value.length) return
-	const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id, isTrack: 1 })
+function queueDeviceListRefresh () {
+	if (!visible.value) return
+	if (deviceListTimer) {
+		clearTimeout(deviceListTimer)
+	}
+	deviceListTimer = setTimeout(() => {
+		deviceListTimer = null
+		void getDeviceListByPolygons()
+	}, 200)
+}
+
+async function getDeviceListByPolygons () {
+	if (!visible.value) return
+	const polygons = resolveShapePolygonsWkt()
+	if (!polygons.length) {
+		clearDeviceListState()
+		return
+	}
+	deviceListRequestToken += 1
+	const requestToken = deviceListRequestToken
+	const res = await fwDeviceListByPolygonsApi({
+		isAreaSelect: 1,
+		isTrack: 1,
+		polygons,
+	})
+	if (requestToken !== deviceListRequestToken) return
 	deviceOptions.value = res?.data?.data ?? []
-	if (dialogMode.value === 'view') {
-		deviceOptions.value = deviceOptions.value.filter(item => formData.value.deviceIds.includes(item.id))
+	if (dialogMode.value === 'view' && formData.value.deviceIds) {
+		const targetIds = formData.value.deviceIds.split(',')
+		deviceOptions.value = deviceOptions.value.filter(item => targetIds.includes(String(item.id)))
+	}
+	await nextTick()
+	const prevSuppressDeviceFly = suppressDeviceFly.value
+	suppressDeviceFly.value = true
+	syncDeviceSelection()
+	suppressDeviceFly.value = prevSuppressDeviceFly
+	if (!readonly.value) {
+		renderDeviceRanges(selectedDeviceRows.value)
 	}
 }
 
@@ -1105,14 +1292,16 @@
 
 // 同步选择状态
 function syncDeviceSelection () {
+	if (isDialogInitializing.value && !deviceOptions.value.length) return
 	const rows = []
-	const arr = formData.value.deviceIds.split(',')
+	const arr = formData.value.deviceIds ? formData.value.deviceIds.split(',') : []
 	deviceOptions.value.forEach(row => {
-		if (arr.includes(row.id)) {
+		if (arr.includes(String(row.id))) {
 			rows.push(row)
 		}
 	})
 	selectedDeviceRows.value = rows
+	formData.value.deviceIds = rows.map(item => item.id).join(',')
 	if (readonly.value) {
 		renderDeviceRanges(selectedDeviceRows.value)
 		return
@@ -1126,9 +1315,12 @@
 
 // 打开弹框
 async function open ({ mode, row } = {}) {
+	isDialogInitializing.value = true
 	dialogMode.value = mode || 'add'
 	formData.value = dialogMode.value === 'add' ? initForm() : row
 	selectedDeviceRows.value = []
+	deviceOptions.value = []
+	policeStationOptions.value = []
 	shapeList.value = []
 	activeShapeId.value = null
 	activeAreaType.value = ''
@@ -1140,8 +1332,8 @@
 	viewEntity && viewer?.entities?.remove(viewEntity)
 	viewEntity = null
 	clearActiveTool()
-	await getDeviceList()
 	if (dialogMode.value === 'add') {
+		mapRef.value?.zoomToAdminBoundary?.()
 		// default no draw mode
 	} else if (dialogMode.value === 'edit') {
 		await loadDetail()
@@ -1159,13 +1351,20 @@
 	if (!readonly.value) {
 		renderDeviceRanges(selectedDeviceRows.value)
 	}
+	isDialogInitializing.value = false
+	queueDeviceListRefresh()
+	queuePoliceStationRefresh()
 }
 
-onMounted(() => {
-	getPoliceStationList()
-})
-
 onBeforeUnmount(() => {
+	if (deviceListTimer) {
+		clearTimeout(deviceListTimer)
+		deviceListTimer = null
+	}
+	if (policeStationListTimer) {
+		clearTimeout(policeStationListTimer)
+		policeStationListTimer = null
+	}
 	hideTypePanel()
 })
 

--
Gitblit v1.9.3