From 4ee55a85df6b5ef0e14832a02df4c76753717c50 Mon Sep 17 00:00:00 2001
From: 张含笑 <zhx18749296735@163.com>
Date: Wed, 04 Feb 2026 11:38:20 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue |  125 ++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 122 insertions(+), 3 deletions(-)

diff --git a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
index f11d973..ae57019 100644
--- a/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/sceneConfig/FormDiaLog.vue
@@ -60,7 +60,11 @@
 								row-key="id"
 							>
 								<el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" />
-								<el-table-column prop="deviceType" show-overflow-tooltip label="设备类型" />
+								<el-table-column prop="deviceType" show-overflow-tooltip label="设备类型">
+									<template v-slot="{ row }">
+										{{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+									</template>
+								</el-table-column>
 							</el-table>
 						</div>
 					</div>
@@ -153,7 +157,11 @@
 								row-key="id"
 							>
 								<el-table-column prop="deviceName" show-overflow-tooltip label="设备名称" />
-								<el-table-column prop="deviceType" show-overflow-tooltip label="设备类型" />
+								<el-table-column prop="deviceType" show-overflow-tooltip label="设备类型">
+									<template v-slot="{ row }">
+										{{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+									</template>
+								</el-table-column>
 							</el-table>
 						</div>
 					</div>
@@ -195,9 +203,15 @@
 	buildEllipsePositions,
 } from '@/utils/cesium/shapeTools'
 import { cartesian3Convert } from '@/utils/cesium/mapUtil'
+import {
+	createDeviceRangePrimitive,
+	getDeviceLngLat,
+	normalizeDeviceRange,
+} from '@/utils/cesium/deviceRange'
 import { saveOperationLog } from '@ztzf/apis'
 import { useRoute } from 'vue-router'
 import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
+import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png'
 
 const initForm = () => ({
 	sceneName: '', // 配置名称
@@ -219,6 +233,7 @@
 const searchName = ref('')
 const areaList = ref([]) // 关联区域列表
 const selectedAreaRows = ref([]) //选中列表
+let lastSelectedAreaIds = []
 const deviceList = ref([]) // 设备列表
 const deviceLoading = ref(false)
 const dictObj = inject('dictObj')
@@ -226,6 +241,9 @@
 const route = useRoute()
 let viewer
 let areaDisplaySource
+const deviceRangePrimitiveMap = new Map()
+const deviceIconEntityMap = new Map()
+let deviceRangeRenderToken = 0
 
 const rules = {
 	sceneName: fieldRules(true, 50),
@@ -279,12 +297,16 @@
 async function loadDeviceListByAreaIds(areaIdsKey) {
 	if (!areaIdsKey) {
 		deviceList.value = []
+		clearDeviceRangePrimitives()
+		clearDeviceIconEntities()
 		return
 	}
 	deviceLoading.value = true
 	try {
 		const res = await fwDeviceListApi({ areaIds: areaIdsKey })
 		deviceList.value = res?.data?.data ?? []
+		await nextTick()
+		renderDeviceRanges(deviceList.value)
 	} finally {
 		deviceLoading.value = false
 	}
@@ -292,11 +314,17 @@
 
 // 关联区域变更
 async function handleAreaSelectionChange(rows) {
+	const prevIds = new Set(lastSelectedAreaIds)
 	const selectedRows = await ensureAreaExtList(rows)
 	selectedAreaRows.value = selectedRows
 	const ids = selectedRows.map(item => item.id)
+	lastSelectedAreaIds = [...ids]
 	formData.value.areaDivideIds = ids.join(',')
 	formData.value.areaCount = ids.length
+	if (ids.some(id => !prevIds.has(id))) {
+		await nextTick()
+		fitViewToSelectedAreas()
+	}
 }
 
 // 渲染面
@@ -312,6 +340,22 @@
 function clearAreaDisplay() {
 	if (!areaDisplaySource) return
 	areaDisplaySource.entities.removeAll()
+}
+
+function clearDeviceRangePrimitives() {
+	if (!viewer || !deviceRangePrimitiveMap.size) return
+	for (const primitive of deviceRangePrimitiveMap.values()) {
+		viewer.scene.primitives.remove(primitive)
+	}
+	deviceRangePrimitiveMap.clear()
+}
+
+function clearDeviceIconEntities() {
+	if (!viewer || !deviceIconEntityMap.size) return
+	for (const entity of deviceIconEntityMap.values()) {
+		viewer.entities.remove(entity)
+	}
+	deviceIconEntityMap.clear()
 }
 
 function normalizeShapePoint(point) {
@@ -559,6 +603,70 @@
 	})
 }
 
+function addDeviceIconEntity(device, position) {
+	if (!viewer || !position) return
+	const id = String(device?.id ?? '')
+	if (!id || deviceIconEntityMap.has(id)) return
+	const entity = viewer.entities.add({
+		position: Cesium.Cartesian3.fromDegrees(position.lng, position.lat, position.height || 0),
+		billboard: {
+			image: equipmentIcon,
+			width: 28,
+			height: 38,
+			verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+			heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+		},
+	})
+	deviceIconEntityMap.set(id, entity)
+}
+
+async function renderDeviceRanges(rows) {
+	if (!visible.value) return
+	if (!viewer) initMap()
+	if (!viewer) return
+	deviceRangeRenderToken += 1
+	const renderToken = deviceRangeRenderToken
+	const selectedRows = Array.isArray(rows) ? rows : []
+	const nextIds = new Set(selectedRows.map(row => String(row?.id)))
+	for (const [id, primitive] of deviceRangePrimitiveMap.entries()) {
+		if (!nextIds.has(id)) {
+			viewer.scene.primitives.remove(primitive)
+			deviceRangePrimitiveMap.delete(id)
+		}
+	}
+	for (const [id, entity] of deviceIconEntityMap.entries()) {
+		if (!nextIds.has(id)) {
+			viewer.entities.remove(entity)
+			deviceIconEntityMap.delete(id)
+		}
+	}
+	const pendingRows = selectedRows.filter(row => {
+		const id = String(row?.id)
+		return id && !deviceRangePrimitiveMap.has(id)
+	})
+	if (!pendingRows.length) return
+	const primitives = await Promise.all(
+		pendingRows.map(async row => {
+			const position = getDeviceLngLat(row)
+			if (!position) return { row, primitive: null, position: null }
+			const rangeMeters = normalizeDeviceRange(row.effectiveRangeKm)
+			const primitive = await createDeviceRangePrimitive(viewer, position, rangeMeters)
+			return { row, primitive, position }
+		})
+	)
+	if (renderToken !== deviceRangeRenderToken) return
+	primitives.forEach(({ row, primitive, position }) => {
+		if (position) {
+			addDeviceIconEntity(row, position)
+		}
+		if (!primitive) return
+		const id = String(row?.id)
+		if (!nextIds.has(id)) return
+		deviceRangePrimitiveMap.set(id, primitive)
+		viewer.scene.primitives.add(primitive)
+	})
+}
+
 watch(() => selectedAreaRows.value, renderSelectedAreas)
 const deviceAreaIdsKey = computed(() =>
 	selectedAreaRows.value.map(item => item?.id).filter(Boolean).join(',')
@@ -570,9 +678,19 @@
 	}
 )
 watch(
+	() => deviceList.value,
+	val => {
+		renderDeviceRanges(val)
+	}
+)
+watch(
 	() => visible.value,
 	val => {
-		if (!val) clearAreaDisplay()
+		if (!val) {
+			clearAreaDisplay()
+			clearDeviceRangePrimitives()
+			clearDeviceIconEntities()
+		}
 	}
 )
 
@@ -619,6 +737,7 @@
 	selectedAreaRows.value = ensuredRows
 	if (!ensuredRows.length) return
 	formData.value.areaCount = ensuredRows.length
+	lastSelectedAreaIds = ensuredRows.map(row => row.id)
 }
 
 // 初始化地图实例

--
Gitblit v1.9.3