From 89380e6260a75d1d3b94de687ebcc2f50d50659d Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Tue, 03 Feb 2026 15:44:33 +0800
Subject: [PATCH] feat:环境变量配置调整

---
 applications/drone-command/src/components/map-container/device-map-container.vue |  712 ++++++++++++++++++++++++++++++++++------------------------
 1 files changed, 414 insertions(+), 298 deletions(-)

diff --git a/applications/drone-command/src/components/map-container/device-map-container.vue b/applications/drone-command/src/components/map-container/device-map-container.vue
index d877826..7819129 100644
--- a/applications/drone-command/src/components/map-container/device-map-container.vue
+++ b/applications/drone-command/src/components/map-container/device-map-container.vue
@@ -47,36 +47,40 @@
 <script setup>
 import * as Cesium from 'cesium'
 import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
-import { geomAnalysis } from '@ztzf/utils'
 import { buildEllipsePositions } from '@/utils/cesium/shapeTools'
 import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
-import { fwDefenseZonePageApi } from '@/views/areaManage/defenseZone/defenseZoneApi'
 import { fwAreaDivideListApi } from '@/views/areaManage/partition/partitionApi'
-import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi'
-import { cockpitAggregationApi } from '@/api/dataCockpit'
+
+import { fwDefenseSceneManageListApi } from '@/views/areaManage/sceneManage/sceneManageApi'
+
+import { newCockpitAggregationApi } from '@/api/dataCockpit'
 import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
 import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png'
+import offlineEquipmentIcon from '@/assets/images/dataCockpit/map/offline-equipment.png'
 import droneIcon from '@/assets/images/dataCockpit/map/drone.png'
 import aggregationIcon from '@/assets/images/dataCockpit/map/aggregation.png'
 import commandPostIcon from '@/assets/images/dataCockpit/map/command-post.png'
 import jaGeojsonRaw from '@/assets/geojson/ja.geojson?raw'
 import DevicePopup from './components/DevicePopup.vue'
 import DronePopup from './components/DronePopup.vue'
+import dayjs from 'dayjs'
 import {
-	RING_STYLES,
-	addDeviceRings,
 	createDroneTrackMaterial,
 	createRadialGradientMaterial,
 	getTexturedVertexFormat,
 } from './device-map-materials'
+import { getPointPositionsHeight } from '@/utils/cesium/mapUtil'
+import { createDeviceRangePrimitiveWithHeight } from '@/utils/cesium/deviceRange'
 import dzIcon from '@/assets/images/dataCockpit/map/dz-map-layer.png'
 import yxIcon from '@/assets/images/dataCockpit/map/yx-map-layer.png'
 
 const CLUSTER_HEIGHT = 100000
 const DETAIL_HEIGHT = 10000
+const POLYGON_HEIGHT_M = 0.2
+const DRONE_TRACK_DURATION_S = 30 * 60
 
 const props = defineProps({
-	onlineDevices: {
+	allDevices: {
 		type: Array,
 		default: () => [],
 	},
@@ -99,7 +103,6 @@
 })
 const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite'])
 
-const DEFAULT_ZONE_PAGE_SIZE = 999
 
 const mapRef = ref(null)
 let viewer = null
@@ -110,25 +113,28 @@
 let deviceBillboardCollection = null
 let deviceRingOutlinePrimitives = []
 let deviceRingFillPrimitives = []
-let defenseZonePrimitive = null
-let defenseZoneOutlinePrimitive = null
-let partitionPrimitive = null
-let partitionOutlinePrimitive = null
+let partitionFillPrimitives = []
+let partitionOutlinePrimitives = []
 let aggregationSource = null
 let commandPostBillboardCollection = null
 let droneTrackBillboardCollection = null
 let droneTrackPolylineCollection = null
 let droneTrackTickHandler = null
+let droneTrackRafId = null
 let droneTrackStartTime = null
+let droneTrackAnimStartAt = 0
+let droneTrackLastTickAt = 0
 let droneTrackRuntime = []
+let droneTrackSource = null
+let mapReadyHandled = false
 let favoritePulseRafId = null
 let favoritePulseStartAt = 0
 let favoritePulseElapsed = 0
 const favoritePulseEntities = []
 const pulseBaseColor = Cesium.Color.fromCssColorString('#FF3B30')
-const PULSE_MIN_RADIUS_M = 60
-const PULSE_MAX_RADIUS_M = 180
-const PULSE_DURATION_S = 1.6
+const PULSE_MIN_RADIUS_M = 2
+const PULSE_MAX_RADIUS_M = 30
+const PULSE_DURATION_S = 1.4
 const detailVisible = ref(true)
 const clusterVisible = ref(false)
 const countyCenterMap = new Map()
@@ -211,27 +217,105 @@
 	return canvas
 }
 
+class PulseSphereMaterialProperty {
+	constructor(color, speed = 1.2, bands = 3.0, softness = 0.18) {
+		this._definitionChanged = new Cesium.Event()
+		this.color = color
+		this.speed = speed
+		this.bands = bands
+		this.softness = softness
+	}
+	get isConstant() {
+		return true
+	}
+	get definitionChanged() {
+		return this._definitionChanged
+	}
+	getType() {
+		return 'PulseSphereMaterial'
+	}
+	getValue(_time, result = {}) {
+		result.color = this.color
+		result.time = (performance.now() / 1000) * this.speed
+		result.bands = this.bands
+		result.softness = this.softness
+		return result
+	}
+	equals(other) {
+		return (
+			other instanceof PulseSphereMaterialProperty &&
+			Cesium.Color.equals(this.color, other.color) &&
+			this.speed === other.speed &&
+			this.bands === other.bands &&
+			this.softness === other.softness
+		)
+	}
+}
+
+let pulseSphereMaterial = null
+const getPulseSphereMaterial = () => {
+	if (pulseSphereMaterial) return pulseSphereMaterial
+	Cesium.Material._materialCache.addMaterial('PulseSphereMaterial', {
+		fabric: {
+			type: 'PulseSphereMaterial',
+			uniforms: {
+				color: pulseBaseColor.withAlpha(0.45),
+				time: 0,
+				bands: 3.0,
+				softness: 0.18,
+			},
+			source: `
+				czm_material czm_getMaterial(czm_materialInput materialInput)
+				{
+					czm_material material = czm_getDefaultMaterial(materialInput);
+					vec3 normalEC = normalize(materialInput.normalEC);
+					vec3 viewEC = normalize(-materialInput.positionToEyeEC);
+					float ndv = max(dot(normalEC, viewEC), 0.0);
+					float fresnel = pow(1.0 - ndv, 1.6);
+					float wave = sin((ndv * bands + time) * 6.2831853);
+					float ring = smoothstep(-softness, softness, wave);
+					float alpha = (ring * 0.6 + fresnel * 0.4) * color.a;
+					material.diffuse = color.rgb;
+					material.alpha = alpha;
+					return material;
+				}
+			`,
+		},
+		translucent: () => true,
+	})
+	pulseSphereMaterial = new PulseSphereMaterialProperty(pulseBaseColor.withAlpha(0.45), 1.1, 3.2, 0.22)
+	return pulseSphereMaterial
+}
+
 const createFavoritePulseEntity = (billboard) => {
 	if (!viewer || !billboard) return
-	const material = new Cesium.ImageMaterialProperty({
-		image: getPulseCanvas(),
-		transparent: true,
-	})
+	const material = getPulseSphereMaterial()
 	const pulse = {
 		material,
 		offset: Math.random() * PULSE_DURATION_S,
 		entity: null,
 	}
+	const getPulseRadius3D = () => {
+		const radius = getPulseRadius(pulse)
+		return new Cesium.Cartesian3(radius, radius, radius)
+	}
 	pulse.entity = viewer.entities.add({
-		position: new Cesium.CallbackProperty(() => billboard.position, false),
-		ellipse: {
-			semiMajorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false),
-			semiMinorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false),
+		position: new Cesium.CallbackProperty(() => getBillboardPosition(billboard), false),
+		ellipsoid: {
+			radii: new Cesium.CallbackProperty(() => getPulseRadius3D(), false),
 			material,
-			heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
 		},
 	})
 	favoritePulseEntities.push(pulse)
+}
+
+const getBillboardPosition = billboard => {
+	if (!viewer || !billboard) return null
+	const position = billboard.position
+	if (position?.getValue) {
+		return position.getValue(viewer.clock.currentTime)
+	}
+	return position
 }
 
 const getDevicePosition = item => {
@@ -251,6 +335,7 @@
 	if (!Number.isFinite(range) || range <= 0) return null
 	return range
 }
+
 
 const ensureCockpitPrimitiveLayer = () => {
 	if (!viewer) return
@@ -282,10 +367,12 @@
 	if (!viewer) return
 	ensureCockpitPrimitiveLayer()
 	cockpitPrimitiveLayer.removeAll(false)
-	if (defenseZonePrimitive) cockpitPrimitiveLayer.add(defenseZonePrimitive)
-	if (defenseZoneOutlinePrimitive) cockpitPrimitiveLayer.add(defenseZoneOutlinePrimitive)
-	if (partitionPrimitive) cockpitPrimitiveLayer.add(partitionPrimitive)
-	if (partitionOutlinePrimitive) cockpitPrimitiveLayer.add(partitionOutlinePrimitive)
+	if (partitionFillPrimitives.length) {
+		partitionFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
+	}
+	if (partitionOutlinePrimitives.length) {
+		partitionOutlinePrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
+	}
 	if (deviceRingFillPrimitives.length) {
 		deviceRingFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
 	}
@@ -325,27 +412,16 @@
 	}
 }
 
-const clearDefenseZoneEntities = () => {
-	if (!viewer) return
-	if (defenseZonePrimitive) {
-		removeCockpitPrimitive(defenseZonePrimitive)
-		defenseZonePrimitive = null
-	}
-	if (defenseZoneOutlinePrimitive) {
-		removeCockpitPrimitive(defenseZoneOutlinePrimitive)
-		defenseZoneOutlinePrimitive = null
-	}
-}
 
 const clearPartitionEntities = () => {
 	if (!viewer) return
-	if (partitionPrimitive) {
-		removeCockpitPrimitive(partitionPrimitive)
-		partitionPrimitive = null
+	if (partitionFillPrimitives.length) {
+		partitionFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+		partitionFillPrimitives = []
 	}
-	if (partitionOutlinePrimitive) {
-		removeCockpitPrimitive(partitionOutlinePrimitive)
-		partitionOutlinePrimitive = null
+	if (partitionOutlinePrimitives.length) {
+		partitionOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+		partitionOutlinePrimitives = []
 	}
 }
 
@@ -380,10 +456,16 @@
 
 const setDetailVisibility = visible => {
 	detailVisible.value = visible
-	if (defenseZonePrimitive) defenseZonePrimitive.show = visible
-	if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = visible
-	if (partitionPrimitive) partitionPrimitive.show = visible
-	if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = visible
+	if (partitionFillPrimitives.length) {
+		partitionFillPrimitives.forEach(primitive => {
+			primitive.show = visible
+		})
+	}
+	if (partitionOutlinePrimitives.length) {
+		partitionOutlinePrimitives.forEach(primitive => {
+			primitive.show = visible
+		})
+	}
 	if (commandPostBillboardCollection) commandPostBillboardCollection.show = visible
 	if (deviceBillboardCollection) deviceBillboardCollection.show = visible
 	if (deviceRingFillPrimitives.length) {
@@ -412,6 +494,7 @@
 const setDroneVisibility = visible => {
 	if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible
 	if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible
+	if (droneTrackSource) droneTrackSource.show = visible
 	if (favoritePulseEntities.length) {
 		favoritePulseEntities.forEach(pulse => {
 			if (pulse?.entity) pulse.entity.show = visible
@@ -538,6 +621,10 @@
 		}
 		droneTrackPolylineCollection = null
 	}
+	if (droneTrackSource) {
+		viewer.dataSources.remove(droneTrackSource)
+		droneTrackSource = null
+	}
 	droneTrackRuntime = []
 	if (favoritePulseEntities.length) {
 		favoritePulseEntities.forEach(pulse => {
@@ -571,44 +658,96 @@
 	}
 }
 
+const ensureDroneTrackSource = () => {
+	if (!viewer) return
+	if (!droneTrackSource) {
+		droneTrackSource = new Cesium.CustomDataSource('droneTrackSource')
+		viewer.dataSources.add(droneTrackSource)
+	}
+}
+
+const setupDroneTrackClock = () => {
+	if (!viewer) return
+	const startTime = Cesium.JulianDate.now()
+	const stopTime = Cesium.JulianDate.addSeconds(startTime, DRONE_TRACK_DURATION_S, new Cesium.JulianDate())
+	viewer.clock.startTime = startTime
+	viewer.clock.stopTime = stopTime
+	viewer.clock.currentTime = startTime
+	viewer.clock.multiplier = 1
+	viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER
+	viewer.clock.clockRange = Cesium.ClockRange.CLAMPED
+}
+
+const updateDroneTrackPositions = elapsed => {
+	if (!viewer || !viewer.scene || !droneTrackRuntime.length) return
+	droneTrackRuntime.forEach(track => {
+		if (!track?.billboard || track.billboard.isDestroyed?.()) return
+		if (!track?.polyline) return
+		if (!track.positions || track.positions.length < 2) return
+		const duration = track.duration
+		if (duration <= 0) return
+		const t = Math.min(Math.max(elapsed, 0), duration)
+		const seg = Math.min(track.positions.length - 2, Math.floor(t / track.segmentDuration))
+		const ratio = (t - seg * track.segmentDuration) / track.segmentDuration
+		const isEntityPosition = typeof track.billboard?.position?.getValue === 'function'
+		const pos = isEntityPosition
+			? getBillboardPosition(track.billboard)
+			: Cesium.Cartesian3.lerp(
+					track.positions[seg],
+					track.positions[seg + 1],
+					ratio,
+					new Cesium.Cartesian3()
+			  )
+		if (!pos) return
+		if (!isEntityPosition) {
+			track.billboard.position = pos
+		}
+		const pathCount = Math.min(track.positions.length - 1, Math.floor(t / track.segmentDuration))
+		const pathPositions = track.positions.slice(0, pathCount + 1)
+		track.polyline.positions = [...pathPositions, pos]
+	})
+}
+
 const startDroneTrackAnimation = () => {
 	if (!viewer) return
 	stopDroneTrackAnimation()
-	droneTrackStartTime = Cesium.JulianDate.now()
-	droneTrackTickHandler = clock => {
-		if (!viewer || viewer.isDestroyed?.() || !viewer.scene) {
-			stopDroneTrackAnimation()
+	droneTrackStartTime = viewer.clock.startTime
+	viewer.clock.shouldAnimate = true
+	droneTrackAnimStartAt = performance.now()
+	droneTrackLastTickAt = droneTrackAnimStartAt
+	const renderTick = now => {
+		if (!viewer || viewer.isDestroyed?.()) {
+			droneTrackRafId = null
 			return
 		}
-		if (!droneTrackRuntime.length) return
-		const current = clock.currentTime
-		const elapsed = Cesium.JulianDate.secondsDifference(current, droneTrackStartTime)
-		droneTrackRuntime.forEach(track => {
-			if (!track?.billboard || track.billboard.isDestroyed?.()) return
-			if (!track.billboard._scene) return
-			if (!track.positions || track.positions.length < 2) return
-			const duration = track.duration
-			if (duration <= 0) return
-			const t = ((elapsed % duration) + duration) % duration
-			const seg = Math.min(track.positions.length - 2, Math.floor(t / track.segmentDuration))
-			const ratio = (t - seg * track.segmentDuration) / track.segmentDuration
-			const pos = Cesium.Cartesian3.lerp(
-				track.positions[seg],
-				track.positions[seg + 1],
-				ratio,
-				new Cesium.Cartesian3()
+		const deltaSeconds = Math.max(0, (now - droneTrackLastTickAt) / 1000)
+		droneTrackLastTickAt = now
+		if (viewer.clock?.currentTime && viewer.clock?.stopTime) {
+			const nextTime = Cesium.JulianDate.addSeconds(
+				viewer.clock.currentTime,
+				deltaSeconds * (viewer.clock.multiplier || 1),
+				new Cesium.JulianDate()
 			)
-			track.billboard.position = pos
-		})
+			if (Cesium.JulianDate.greaterThan(nextTime, viewer.clock.stopTime)) {
+				viewer.clock.currentTime = viewer.clock.stopTime
+			} else {
+				viewer.clock.currentTime = nextTime
+			}
+		}
+		const elapsed = Cesium.JulianDate.secondsDifference(viewer.clock.currentTime, droneTrackStartTime)
+		updateDroneTrackPositions(elapsed)
+		viewer.scene?.requestRender?.()
+		droneTrackRafId = requestAnimationFrame(renderTick)
 	}
-	viewer.clock.onTick.addEventListener(droneTrackTickHandler)
-	viewer.clock.shouldAnimate = true
+	droneTrackRafId = requestAnimationFrame(renderTick)
 }
 
 const stopDroneTrackAnimation = () => {
-	if (!viewer || !droneTrackTickHandler) return
-	viewer.clock.onTick.removeEventListener(droneTrackTickHandler)
 	droneTrackTickHandler = null
+	if (droneTrackRafId) {
+		cancelAnimationFrame(droneTrackRafId)
+		droneTrackRafId = null
+	}
 }
 
 const startFavoritePulseAnimation = () => {
@@ -638,10 +777,11 @@
 	clearDroneTrackEntities()
 	if (!list?.length) return
 	ensureDroneTrackCollections()
+	ensureDroneTrackSource()
+	setupDroneTrackClock()
 	droneTrackBillboardCollection.show = detailVisible.value
 	droneTrackPolylineCollection.show = detailVisible.value
 	droneTrackRuntime = []
-	const segmentDuration = 6
 	const baseTrackColor = Cesium.Color.fromCssColorString('red')
 		; (list || []).forEach((item, trackIndex) => {
 			const position = getDevicePosition(item)
@@ -651,6 +791,7 @@
 			const positions = points.map(point =>
 				Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
 			)
+			const segmentDuration = DRONE_TRACK_DURATION_S / Math.max(positions.length - 1, 1)
 			let trackMaterial = createDroneTrackMaterial({
 				color: baseTrackColor,
 				speed: 4.5,
@@ -661,49 +802,73 @@
 			if (!trackMaterial) {
 				trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
 			}
-			droneTrackPolylineCollection.add({
-				positions,
+			const polyline = droneTrackPolylineCollection.add({
+				positions: [positions[0]],
 				width: 3,
 				material: trackMaterial,
 			})
 			const droneId = `drone-alarm-${item?.alarmRecordId ?? item?.id ?? trackIndex}`
-			const billboard = droneTrackBillboardCollection.add({
-				position: positions[positions.length - 1],
-				image: droneIcon,
-				width: 36,
-				height: 36,
-				verticalOrigin: Cesium.VerticalOrigin.CENTER,
-				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			const startTime = viewer.clock.startTime
+			const stopTime = viewer.clock.stopTime
+			const positionProperty = new Cesium.SampledPositionProperty()
+			positions.forEach((pos, index) => {
+				const sampleTime = Cesium.JulianDate.addSeconds(
+					startTime,
+					index * segmentDuration,
+					new Cesium.JulianDate()
+				)
+				positionProperty.addSample(sampleTime, pos)
 			})
-			billboard.id = droneId
+			positionProperty.setInterpolationOptions({
+				interpolationDegree: 1,
+				interpolationAlgorithm: Cesium.LinearApproximation,
+			})
+			const entity = droneTrackSource.entities.add({
+				id: droneId,
+				availability: new Cesium.TimeIntervalCollection([
+					new Cesium.TimeInterval({
+						start: startTime,
+						stop: stopTime,
+					}),
+				]),
+				position: positionProperty,
+				orientation: new Cesium.VelocityOrientationProperty(positionProperty),
+				billboard: {
+					image: droneIcon,
+					width: 36,
+					height: 36,
+					verticalOrigin: Cesium.VerticalOrigin.CENTER,
+					disableDepthTestDistance: Number.POSITIVE_INFINITY,
+				},
+			})
 			const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
-		dronePickMap.set(droneId, {
-			data: {
-				...item,
-				flightHeightM: item.flightHeightM ?? points[0].height,
-				flightSpeedMs: item.flightSpeedMs ?? speedMs,
-				longitude: item.longitude ?? points[0].longitude,
-				latitude: item.latitude ?? points[0].latitude,
-			},
-			billboard,
-		})
-		if (isFavorited(item)) {
-			createFavoritePulseEntity(billboard)
-		}
+			dronePickMap.set(droneId, {
+				data: {
+					...item,
+					flightHeightM: item.flightHeightM ?? points[0].height,
+					flightSpeedMs: item.flightSpeedMs ?? speedMs,
+					longitude: item.longitude ?? points[0].longitude,
+					latitude: item.latitude ?? points[0].latitude,
+				},
+				billboard: entity,
+			})
+			if (isFavorited(item)) {
+				createFavoritePulseEntity(entity)
+			}
 			droneTrackRuntime.push({
 				positions,
-				billboard,
+				polyline,
+				billboard: entity,
 				segmentDuration,
-				duration: (points.length - 1) * segmentDuration,
+				duration: DRONE_TRACK_DURATION_S,
 			})
 		})
-	viewer.clock.multiplier = 1
 	startDroneTrackAnimation()
 	startFavoritePulseAnimation()
 	reorderCockpitPrimitives()
 }
 
-const renderDeviceEntities = devices => {
+const renderDeviceEntities = async devices => {
 	if (!viewer) return
 	ensureCockpitPrimitiveLayer()
 	ensureDeviceCollections()
@@ -718,195 +883,154 @@
 	}
 	devicePickMap.clear()
 	deviceBillboardCollection.show = detailVisible.value
-	const ringFillInstancesByStyle = RING_STYLES.map(() => [])
-	const ringOutlineInstancesByStyle = RING_STYLES.map(() => [])
+	const deviceEntries = []
+	const devicePositions = []
 	devices.forEach((item, index) => {
+		const isOnline = String(item.status) === '0'
+
 		const position = getDevicePosition(item)
 		if (!position) return
 		const entityId = `online-device-${item.id ?? index}-${index}`
 		const rangeMeters = getDeviceRange(item)
-		addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters)
+		devicePositions.push({ lng: position.longitude, lat: position.latitude })
 		const billboard = deviceBillboardCollection.add({
 			position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
-			image: equipmentIcon,
+			image: isOnline ? equipmentIcon : offlineEquipmentIcon,
 			width: 40,
 			height: 56,
 			verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
 		})
 		billboard.id = entityId
 		devicePickMap.set(entityId, { data: item, billboard })
+		deviceEntries.push({ position, rangeMeters, billboard, isOnline })
 	})
 	if (selectedTargetType.value === 'device' && selectedDeviceBillboard && !devicePickMap.has(selectedDeviceBillboard.id)) {
 		closePopup()
 	}
-	ringFillInstancesByStyle.forEach((instances, index) => {
-		if (!instances.length) return
-		const ring = RING_STYLES[index]
-		const alpha = ring.alpha ?? 0.64
-		const innerRatio =
-			typeof ring.innerRatio === 'number'
-				? ring.innerRatio
-				: ring.inner && ring.outer
-					? Math.min(Math.max(ring.inner / ring.outer, 0), 0.9)
-					: 0
-		const material = createRadialGradientMaterial(
-			Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(alpha),
-			Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(alpha),
-			{
-				gamma: 1.7,
-				innerCutoff: innerRatio,
+	if (deviceEntries.length) {
+		const heights = await getPointPositionsHeight(devicePositions, viewer)
+		deviceEntries.forEach((entry, index) => {
+			const height = Number(heights?.[index]?.ASL)
+			const centerHeight = Number.isFinite(height) ? height : 0
+			if (entry.billboard) {
+				entry.billboard.position = Cesium.Cartesian3.fromDegrees(
+					entry.position.longitude,
+					entry.position.latitude,
+					centerHeight
+				)
 			}
-		)
-		const primitive = new Cesium.GroundPrimitive({
-			geometryInstances: instances,
-			appearance: new Cesium.MaterialAppearance({
-				material,
-				translucent: true,
-			}),
+			if (!entry.isOnline) return
+			if (!Number.isFinite(entry.rangeMeters) || entry.rangeMeters <= 0) return
+			const primitive = createDeviceRangePrimitiveWithHeight(entry.position, entry.rangeMeters, centerHeight)
+			if (!primitive) return
+			primitive.show = detailVisible.value
+			addCockpitPrimitive(primitive)
+			deviceRingFillPrimitives.push(primitive)
 		})
-		primitive.show = detailVisible.value
-		addCockpitPrimitive(primitive)
-		deviceRingFillPrimitives.push(primitive)
-	})
-	ringOutlineInstancesByStyle.forEach(instances => {
-		if (!instances.length) return
-		const primitive = new Cesium.GroundPolylinePrimitive({
-			geometryInstances: instances,
-			appearance: new Cesium.PolylineColorAppearance(),
-		})
-		primitive.show = detailVisible.value
-		addCockpitPrimitive(primitive)
-		deviceRingOutlinePrimitives.push(primitive)
-	})
+	}
 	reorderCockpitPrimitives()
 }
 
-const getDefenseZonePositions = geom => {
-	const points = geomAnalysis(geom)
-	if (points.length < 3) return []
-	const first = points[0]
-	const last = points[points.length - 1]
-	const list =
-		first && last && first.longitude === last.longitude && first.latitude === last.latitude
-			? points.slice(0, -1)
-			: points
-	return list.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude))
+const elevatePositions = async (positions, height = POLYGON_HEIGHT_M) => {
+	if (!Array.isArray(positions) || !positions.length) return []
+	if (!viewer) return positions
+	const points = positions
+		.map(pos => {
+			const carto = Cesium.Cartographic.fromCartesian(pos)
+			if (!carto) return null
+			return {
+				lng: Cesium.Math.toDegrees(carto.longitude),
+				lat: Cesium.Math.toDegrees(carto.latitude),
+			}
+		})
+		.filter(Boolean)
+	if (!points.length) return positions
+	const heights = await getPointPositionsHeight(points, viewer)
+	return heights.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude, (item.ASL ?? 0) + height))
 }
 
 const getAreaTypeStyle = areaType => {
 	return AREA_TYPE_STYLE_MAP?.[String(areaType)] || DEFAULT_AREA_STYLE
 }
 
-const buildZonePrimitives = (zones, lineColor, fillColor1, fillColor2) => {
-	if (!viewer) return { primitive: null, outlinePrimitive: null }
-	const polygonInstances = []
-	const lineInstances = []
-	const texturedVertexFormat = getTexturedVertexFormat()
-		; (zones || []).forEach((zone, index) => {
-			if (!zone?.geom) return
-			const positions = getDefenseZonePositions(zone.geom)
-			if (!positions.length) return
-			const polygon = new Cesium.PolygonGeometry({
-				polygonHierarchy: new Cesium.PolygonHierarchy(positions),
-				vertexFormat: texturedVertexFormat,
-			})
-			polygonInstances.push(
-				new Cesium.GeometryInstance({
-					geometry: polygon,
-				})
-			)
-			const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions
-			lineInstances.push(
-				new Cesium.GeometryInstance({
-					geometry: new Cesium.GroundPolylineGeometry({
-						positions: linePositions,
-						width: 2,
-					}),
-					attributes: {
-						color: Cesium.ColorGeometryInstanceAttribute.fromColor(
-							Cesium.Color.fromCssColorString(lineColor)
-						),
-					},
-				})
-			)
-		})
-	let primitive = null
-	if (polygonInstances.length) {
-		const baseColor1 = Cesium.Color.fromCssColorString(fillColor1)
-		const baseColor2 = Cesium.Color.fromCssColorString(fillColor2)
-		const material = createRadialGradientMaterial(baseColor1.withAlpha(0.64), baseColor2.withAlpha(0.64))
-		primitive = new Cesium.GroundPrimitive({
-			geometryInstances: polygonInstances,
-			appearance: new Cesium.MaterialAppearance({
-				material,
-				translucent: true,
-			}),
-		})
-		addCockpitPrimitive(primitive)
-	}
-	let outlinePrimitive = null
-	if (lineInstances.length) {
-		outlinePrimitive = new Cesium.GroundPolylinePrimitive({
-			geometryInstances: lineInstances,
-			appearance: new Cesium.PolylineColorAppearance(),
-		})
-		addCockpitPrimitive(outlinePrimitive)
-	}
-	return { primitive, outlinePrimitive }
-}
-
-const buildPartitionPrimitives = shapes => {
-	if (!viewer) return { primitive: null, outlinePrimitive: null }
-	const polygonInstances = []
-	const lineInstances = []
-	const vertexFormat = Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
+const buildPartitionPrimitives = async shapes => {
+	if (!viewer) return { primitives: [], outlinePrimitives: [] }
+	const vertexFormat = getTexturedVertexFormat()
+	const groups = new Map()
+	const tasks = []
 		; (shapes || []).forEach(shape => {
 			if (!shape?.positions || shape.positions.length < 3) return
+			const outlineColor = shape.outlineColor
+			const fillColor = shape.fillColor
+			const key = `${outlineColor.toCssColorString()}|${fillColor.toCssColorString()}`
+			if (!groups.has(key)) {
+				groups.set(key, { outlineColor, fillColor, polygonInstances: [], lineInstances: [] })
+			}
+			const group = groups.get(key)
 			const positions = shape.positions
-			const polygon = new Cesium.PolygonGeometry({
-				polygonHierarchy: new Cesium.PolygonHierarchy(positions),
-				vertexFormat,
+			tasks.push(
+				elevatePositions(positions).then(elevatedPositions => {
+					if (!elevatedPositions.length) return
+					const polygon = new Cesium.PolygonGeometry({
+						polygonHierarchy: new Cesium.PolygonHierarchy(elevatedPositions),
+						perPositionHeight: true,
+						vertexFormat,
+					})
+					group.polygonInstances.push(
+						new Cesium.GeometryInstance({
+							geometry: polygon,
+						})
+					)
+					const linePositions =
+						elevatedPositions.length > 1
+							? [...elevatedPositions, elevatedPositions[0]]
+							: elevatedPositions
+					group.lineInstances.push(
+						new Cesium.GeometryInstance({
+							geometry: new Cesium.PolylineGeometry({
+								positions: linePositions,
+								width: 2,
+							}),
+							attributes: {
+								color: Cesium.ColorGeometryInstanceAttribute.fromColor(outlineColor),
+							},
+						})
+					)
+				})
+			)
+		})
+
+	if (tasks.length) await Promise.all(tasks)
+
+	const primitives = []
+	const outlinePrimitives = []
+	groups.forEach(group => {
+		if (group.polygonInstances.length) {
+			const material = createRadialGradientMaterial(group.outlineColor, group.fillColor, {
+				gamma: 1.7,
+				innerCutoff: 0,
 			})
-			polygonInstances.push(
-				new Cesium.GeometryInstance({
-					geometry: polygon,
-					attributes: {
-						color: Cesium.ColorGeometryInstanceAttribute.fromColor(shape.fillColor),
-					},
-				})
-			)
-			const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions
-			lineInstances.push(
-				new Cesium.GeometryInstance({
-					geometry: new Cesium.GroundPolylineGeometry({
-						positions: linePositions,
-						width: 2,
-					}),
-					attributes: {
-						color: Cesium.ColorGeometryInstanceAttribute.fromColor(
-							shape.outlineColor
-						),
-					},
-				})
-			)
-		})
-	let primitive = null
-	if (polygonInstances.length) {
-		primitive = new Cesium.GroundPrimitive({
-			geometryInstances: polygonInstances,
-			appearance: new Cesium.PerInstanceColorAppearance({ translucent: true, flat: true }),
-		})
-		addCockpitPrimitive(primitive)
-	}
-	let outlinePrimitive = null
-	if (lineInstances.length) {
-		outlinePrimitive = new Cesium.GroundPolylinePrimitive({
-			geometryInstances: lineInstances,
-			appearance: new Cesium.PolylineColorAppearance(),
-		})
-		addCockpitPrimitive(outlinePrimitive)
-	}
-	return { primitive, outlinePrimitive }
+			const primitive = new Cesium.Primitive({
+				geometryInstances: group.polygonInstances,
+				appearance: new Cesium.MaterialAppearance({
+					material,
+					translucent: true,
+				}),
+			})
+			addCockpitPrimitive(primitive)
+			primitives.push(primitive)
+		}
+		if (group.lineInstances.length) {
+			const outlinePrimitive = new Cesium.Primitive({
+				geometryInstances: group.lineInstances,
+				appearance: new Cesium.PolylineColorAppearance(),
+			})
+			addCockpitPrimitive(outlinePrimitive)
+			outlinePrimitives.push(outlinePrimitive)
+		}
+	})
+
+	return { primitives, outlinePrimitives }
 }
 
 const parseGeomJson = geomJson => {
@@ -995,46 +1119,36 @@
 	return shapes
 }
 
-const renderDefenseZones = zones => {
-	if (!viewer) return
-	clearDefenseZoneEntities()
-	const result = buildZonePrimitives(zones, '#72F3FF', '#72F3FF', '#0A7C88')
-	defenseZonePrimitive = result.primitive
-	defenseZoneOutlinePrimitive = result.outlinePrimitive
-	if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value
-	if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = detailVisible.value
-	reorderCockpitPrimitives()
-}
-
-const renderPartitions = zones => {
+const renderPartitions = async zones => {
 	if (!viewer) return
 	clearPartitionEntities()
 	const shapes = resolvePartitionShapes(zones)
-	const result = buildPartitionPrimitives(shapes)
-	partitionPrimitive = result.primitive
-	partitionOutlinePrimitive = result.outlinePrimitive
-	if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
-	if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = detailVisible.value
-	reorderCockpitPrimitives()
-}
-
-const loadDefenseZones = async () => {
-	if (!viewer) return
-	try {
-		const res = await fwDefenseZonePageApi({ current: 1, size: DEFAULT_ZONE_PAGE_SIZE })
-		renderDefenseZones(res?.data?.data?.records ?? [])
-	} catch (error) {
-		renderDefenseZones([])
+	const result = await buildPartitionPrimitives(shapes)
+	partitionFillPrimitives = result.primitives
+	partitionOutlinePrimitives = result.outlinePrimitives
+	if (partitionFillPrimitives.length) {
+		partitionFillPrimitives.forEach(primitive => {
+			primitive.show = detailVisible.value
+		})
 	}
+	if (partitionOutlinePrimitives.length) {
+		partitionOutlinePrimitives.forEach(primitive => {
+			primitive.show = detailVisible.value
+		})
+	}
+	reorderCockpitPrimitives()
 }
 
 const loadPartitions = async () => {
 	if (!viewer) return
 	try {
-		const res = await fwAreaDivideListApi()
-		renderPartitions(res?.data?.data ?? [])
+		const res = await fwAreaDivideListApi({
+			isSetSceneManage: 1,
+			flyTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+		})
+		await renderPartitions(res?.data?.data ?? [])
 	} catch (error) {
-		renderPartitions([])
+		await renderPartitions([])
 	}
 }
 
@@ -1097,7 +1211,7 @@
 
 const updatePopupPosition = () => {
 	if (!viewer || !selectedDeviceBillboard) return
-	const cartesian = selectedDeviceBillboard.position
+	const cartesian = getBillboardPosition(selectedDeviceBillboard)
 	if (!cartesian) return
 	const screenPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian)
 	if (!screenPosition) return
@@ -1181,7 +1295,7 @@
 
 const loadAggregation = async () => {
 	try {
-		const res = await cockpitAggregationApi({
+		const res = await newCockpitAggregationApi({
 			effectiveRangeKmIsNotNull: 1
 		})
 		renderAggregation(res?.data?.data ?? [])
@@ -1192,7 +1306,9 @@
 
 const loadCommandPosts = async () => {
 	try {
-		const res = await fwDefenseSceneListApi()
+		const res = await fwDefenseSceneManageListApi({
+			time: dayjs().format('YYYY-MM-DD HH:mm:ss')
+		})
 		renderCommandPosts(res?.data?.data ?? [])
 	} catch (error) {
 		renderCommandPosts([])
@@ -1201,7 +1317,7 @@
 
 
 watch(
-	() => props.onlineDevices,
+	() => props.allDevices,
 	devices => {
 		renderDeviceEntities(devices || [])
 	},
@@ -1264,6 +1380,8 @@
 }
 
 const handleMapReady = ({ viewer: mapViewer, publicCesium: mapPublic }) => {
+	if (mapReadyHandled) return
+	mapReadyHandled = true
 	viewer = mapViewer
 	publicCesium = mapPublic
 	ensureCockpitPrimitiveLayer()
@@ -1271,8 +1389,7 @@
 	const height = viewer?.camera?.positionCartographic?.height
 	const stage = getStageByHeight(height)
 	updateStageDisplay(stage)
-	renderDeviceEntities(props.onlineDevices)
-	loadDefenseZones()
+	renderDeviceEntities(props.allDevices)
 	loadPartitions()
 	loadAggregation()
 	loadCommandPosts()
@@ -1300,7 +1417,6 @@
 onBeforeUnmount(() => {
 	document.removeEventListener('click', handleClickOutside)
 	clearDeviceEntities()
-	clearDefenseZoneEntities()
 	clearPartitionEntities()
 	clearAggregationEntities()
 	clearCommandPostEntities()

--
Gitblit v1.9.3