From 9fb4de853fc8aab2000f312ba2b8907e3dc9a61d Mon Sep 17 00:00:00 2001
From: 张含笑 <zhx18749296735@163.com>
Date: Thu, 29 Jan 2026 17:33:29 +0800
Subject: [PATCH] feat:调整

---
 applications/drone-command/src/components/map-container/device-map-container.vue |  561 ++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 406 insertions(+), 155 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 c2a8168..f7fb7e9 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
@@ -11,33 +11,36 @@
 					<img :src="layerControlIcon" alt="图层控制" />
 				</div>
 				<div v-if="showLayerPanel" class="layer-panel">
-					<div class="panel-title">图层管理</div>
+					<div class="panel-title">底图切换</div>
+					<div class="base-map-options">
+						<div class="base-map-card" :class="{ active: baseLayerKey === 'base-standard' }"
+							@click="handleBaseLayerSelect('base-standard')">
+							<img class="base-map-thumb standard" :src="dzIcon" alt="">
+							<div class="base-map-label">标准地图</div>
+						</div>
+						<div class="base-map-card" :class="{ active: baseLayerKey === 'base-satellite' }"
+							@click="handleBaseLayerSelect('base-satellite')">
+							<img class="base-map-thumb satellite" :src="yxIcon" alt="">
+							<div class="base-map-label">卫星地图</div>
+						</div>
+					</div>
 
+					<div class="panel-title">图层管理</div>
 					<div class="panel-content">
-						<el-tree class="command-tree" :data="layerTree" show-checkbox default-expand-all node-key="key"
-							:props="layerTreeProps" :default-checked-keys="defaultCheckedKeys" />
+						<el-tree ref="layerTreeRef" class="command-tree map-layer-tree" :data="layerTree" show-checkbox
+							default-expand-all node-key="key" :props="layerTreeProps"
+							:default-checked-keys="treeCheckedKeys" @check="handleLayerCheck" />
 					</div>
 				</div>
 			</div>
 		</div>
 
-		<DevicePopup
-			:visible="popupVisible && !isDronePopup"
-			:position="popupPosition"
-			:device="selectedDevice"
-			@close="closePopup"
-		/>
+		<DevicePopup :visible="popupVisible && !isDronePopup" :position="popupPosition" :device="selectedDevice"
+			@close="closePopup" />
 
-		<DronePopup
-			:visible="popupVisible && isDronePopup"
-			:position="popupPosition"
-			:drone="selectedDevice"
-			:favorite="Boolean(selectedDevice?.isFavorite)"
-			@close="closePopup"
-			@toggle-favorite="toggleDroneFavorite"
-			@signal="handleDroneSignal"
-			@counter="handleDroneCounter"
-		/>
+		<DronePopup :visible="popupVisible && isDronePopup" :position="popupPosition" :drone="selectedDevice"
+			:favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="handleDroneFavorite"
+			@signal="handleDroneSignal" @counter="handleDroneCounter" />
 	</div>
 </template>
 
@@ -64,12 +67,18 @@
 	createRadialGradientMaterial,
 	getTexturedVertexFormat,
 } from './device-map-materials'
+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 props = defineProps({
 	onlineDevices: {
+		type: Array,
+		default: () => [],
+	},
+	alarmDrones: {
 		type: Array,
 		default: () => [],
 	},
@@ -86,12 +95,13 @@
 		default: true,
 	},
 })
-const emit = defineEmits(['droneSignal', 'droneCounter'])
+const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite'])
 
 const DEFAULT_ZONE_PAGE_SIZE = 999
 
 const mapRef = ref(null)
 let viewer = null
+let publicCesium = null
 let cockpitPrimitiveLayer = null
 const devicePickMap = new Map()
 const dronePickMap = new Map()
@@ -109,11 +119,20 @@
 let droneTrackTickHandler = null
 let droneTrackStartTime = null
 let droneTrackRuntime = []
+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 detailVisible = ref(true)
 const clusterVisible = ref(false)
 const countyCenterMap = new Map()
 const showLayerPanel = ref(false)
 const layerWrapRef = ref(null)
+const layerTreeRef = ref(null)
 const selectedDevice = ref(null)
 const selectedTargetType = ref('device')
 let selectedDeviceBillboard = null
@@ -123,17 +142,24 @@
 	label: 'label',
 	children: 'children',
 }
-const defaultCheckedKeys = ['global', 'city-base']
+const baseLayerKeys = ['base-standard', 'base-satellite']
+const defaultCheckedKeys = ['ja-terrain', 'admin', 'city-base']
+const baseLayerKey = ref('base-satellite')
+const treeCheckedKeys = ref([...defaultCheckedKeys])
 const layerTree = ref([
 	{
 		key: 'base',
 		label: '地理信息图层',
 		children: [
-			{ key: 'global', label: '全球地形' },
+			{ key: 'ja-terrain', label: '吉安地形' },
 			{ key: 'admin', label: '行政区划' },
 		],
 	},
-	{
+
+])
+
+/**
+ * {
 		key: 'city',
 		label: '城市CIM图层',
 		children: [
@@ -151,7 +177,60 @@
 			{ key: 'route', label: '飞行航路' },
 		],
 	},
-])
+ */
+
+const adminBoundaryVisible = ref(treeCheckedKeys.value.includes('admin'))
+
+const isFavorited = item => {
+	const value = item?.favorited ?? item?.isFavorite
+	return value === 1 || value === '1' || value === true
+}
+
+let pulseCanvas = null
+const getPulseCanvas = () => {
+	if (pulseCanvas) return pulseCanvas
+	const size = 256
+	const canvas = document.createElement('canvas')
+	canvas.width = size
+	canvas.height = size
+	const ctx = canvas.getContext('2d')
+	const center = size / 2
+	const radius = center - 2
+	const gradient = ctx.createRadialGradient(center, center, radius * 0.1, center, center, radius)
+	gradient.addColorStop(0, 'rgba(255,59,48,0.55)')
+	gradient.addColorStop(0.35, 'rgba(255,59,48,0.35)')
+	gradient.addColorStop(0.7, 'rgba(255,59,48,0.15)')
+	gradient.addColorStop(1, 'rgba(255,59,48,0)')
+	ctx.fillStyle = gradient
+	ctx.beginPath()
+	ctx.arc(center, center, radius, 0, Math.PI * 2)
+	ctx.fill()
+	pulseCanvas = canvas
+	return canvas
+}
+
+const createFavoritePulseEntity = (billboard) => {
+	if (!viewer || !billboard) return
+	const material = new Cesium.ImageMaterialProperty({
+		image: getPulseCanvas(),
+		transparent: true,
+	})
+	const pulse = {
+		material,
+		offset: Math.random() * PULSE_DURATION_S,
+		entity: null,
+	}
+	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),
+			material,
+			heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+		},
+	})
+	favoritePulseEntities.push(pulse)
+}
 
 const getDevicePosition = item => {
 	const longitudeRaw = item.longitude ?? item.lng ?? item.lon
@@ -291,6 +370,12 @@
 	}
 }
 
+const getPulseRadius = pulse => {
+	const phase = ((favoritePulseElapsed + pulse.offset) % PULSE_DURATION_S) / PULSE_DURATION_S
+	const wave = phase < 0.5 ? phase * 2 : (1 - phase) * 2
+	return PULSE_MIN_RADIUS_M + (PULSE_MAX_RADIUS_M - PULSE_MIN_RADIUS_M) * wave
+}
+
 const setDetailVisibility = visible => {
 	detailVisible.value = visible
 	if (defenseZonePrimitive) defenseZonePrimitive.show = visible
@@ -314,12 +399,22 @@
 
 const setClusterVisibility = visible => {
 	clusterVisible.value = visible
-	if (aggregationSource) aggregationSource.show = visible
+	updateAggregationVisibility()
+}
+
+const updateAggregationVisibility = () => {
+	if (!aggregationSource) return
+	aggregationSource.show = clusterVisible.value && adminBoundaryVisible.value
 }
 
 const setDroneVisibility = visible => {
 	if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible
 	if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible
+	if (favoritePulseEntities.length) {
+		favoritePulseEntities.forEach(pulse => {
+			if (pulse?.entity) pulse.entity.show = visible
+		})
+	}
 	if (!visible && selectedTargetType.value === 'drone') {
 		closePopup()
 	}
@@ -342,50 +437,93 @@
 	})
 }
 
-const buildSimulatedTrackPoints = () => {
-	return [
-		{
-			longitude: 114.963191,
-			latitude: 27.136716,
-			height: 120
-		},
-		{
-			longitude:114.957308,
-			latitude: 27.138452,
-			height: 120
-		},
-		{
-			longitude:114.952,
-			latitude: 27.136317,
-			height: 120
-		},
-		{
-			longitude:114.949293,
-			latitude: 27.133864,
-			height: 120
-		},
-		{
-			longitude:114.944666,
-			latitude: 27.130526,
-			height: 120
-		},
-		{
-			longitude:114.945909,
-			latitude:27.127845,
-			height: 120
-		},
-		{
-			longitude:114.962974,
-			latitude:27.136242,
-			height: 120
-		},
+const buildSimulatedTrackPoints = center => {
+	const basePoints = [
+		[
+			{
+				longitude: 114.963191,
+				latitude: 27.136716,
+				height: 120
+			},
+			{
+				longitude: 114.957308,
+				latitude: 27.138452,
+				height: 120
+			},
+			{
+				longitude: 114.952,
+				latitude: 27.136317,
+				height: 120
+			},
+			{
+				longitude: 114.949293,
+				latitude: 27.133864,
+				height: 120
+			},
+			{
+				longitude: 114.944666,
+				latitude: 27.130526,
+				height: 120
+			},
+			{
+				longitude: 114.945909,
+				latitude: 27.127845,
+				height: 120
+			},
+			{
+				longitude: 114.962974,
+				latitude: 27.136242,
+				height: 120
+			},
+		],
+
+		[
+			{
+				longitude: 114.931063,
+				latitude: 27.095052,
+				height: 120
+			},
+			{
+				longitude: 114.928653,
+				latitude: 27.096307,
+				height: 120
+			},
+			{
+				longitude: 114.925958,
+				latitude: 27.096963,
+				height: 120
+			},
+			{
+				longitude: 114.925836, 
+				latitude: 27.096102,
+				height: 120
+			},
+			{
+				longitude: 114.928384, 
+				latitude: 27.095866,
+				height: 120
+			},
+			{
+				longitude: 114.929874, 
+				latitude: 27.096095,
+				height: 120
+			},
+			{
+				longitude: 114.931048, 
+				latitude: 27.097666,
+				height: 120
+			},
+		]
 	]
+
+	return basePoints[center.trackIndex]
 }
 
 
 const clearDroneTrackEntities = () => {
 	if (!viewer) return
 	stopDroneTrackAnimation()
+	stopFavoritePulseAnimation()
 	if (droneTrackBillboardCollection) {
 		if (!droneTrackBillboardCollection.isDestroyed?.()) {
 			removeCockpitPrimitive(droneTrackBillboardCollection)
@@ -399,6 +537,12 @@
 		droneTrackPolylineCollection = null
 	}
 	droneTrackRuntime = []
+	if (favoritePulseEntities.length) {
+		favoritePulseEntities.forEach(pulse => {
+			if (pulse?.entity) viewer?.entities?.remove(pulse.entity)
+		})
+		favoritePulseEntities.length = 0
+	}
 	dronePickMap.clear()
 	if (selectedTargetType.value === 'drone') {
 		closePopup()
@@ -465,72 +609,95 @@
 	droneTrackTickHandler = null
 }
 
-const renderSimulatedDroneTrack = () => {
+const startFavoritePulseAnimation = () => {
+	if (!viewer || favoritePulseRafId) return
+	favoritePulseStartAt = performance.now()
+	const tick = now => {
+		if (!viewer || viewer.isDestroyed?.() || !favoritePulseEntities.length) {
+			favoritePulseRafId = null
+			return
+		}
+		favoritePulseElapsed = (now - favoritePulseStartAt) / 1000
+		viewer.scene.requestRender()
+		favoritePulseRafId = requestAnimationFrame(tick)
+	}
+	favoritePulseRafId = requestAnimationFrame(tick)
+}
+
+const stopFavoritePulseAnimation = () => {
+	if (favoritePulseRafId) {
+		cancelAnimationFrame(favoritePulseRafId)
+		favoritePulseRafId = null
+	}
+}
+
+const renderSimulatedDroneTrack = (list) => {
 	if (!viewer) return
 	clearDroneTrackEntities()
+	if (!list?.length) return
 	ensureDroneTrackCollections()
 	droneTrackBillboardCollection.show = detailVisible.value
 	droneTrackPolylineCollection.show = detailVisible.value
 	droneTrackRuntime = []
-	const baseCenters = [['默认区域', { longitude:114.958541, latitude: 27.121917 }]]
 	const segmentDuration = 6
 	const baseTrackColor = Cesium.Color.fromCssColorString('red')
-	baseCenters.forEach(([centerName, center], trackIndex) => {
-		const points = buildSimulatedTrackPoints(center)
-		if (points.length < 2) return
-		const positions = points.map(point =>
-			Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
-		)
-		let trackMaterial = createDroneTrackMaterial({
-			color: baseTrackColor,
-			speed: 4.5,
-			headWidth: 0.2,
-			glowPower: 1.8,
-			backgroundAlpha: 0.34,
-		})
-		if (!trackMaterial) {
-			trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
-		}
-		droneTrackPolylineCollection.add({
-			positions,
-			width: 3,
-			material: trackMaterial,
-		})
-		const droneId = `drone-track-${trackIndex}`
-		const billboard = droneTrackBillboardCollection.add({
-			position: positions[positions.length - 1],
-			image: droneIcon,
-			width: 36,
-			height: 36,
-			verticalOrigin: Cesium.VerticalOrigin.CENTER,
-			disableDepthTestDistance: Number.POSITIVE_INFINITY,
-		})
-		billboard.id = droneId
-		const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
+		; (list || []).forEach((item, trackIndex) => {
+			const position = getDevicePosition(item)
+			if (!position) return
+			const points = buildSimulatedTrackPoints({ ...position, height: item.flightHeightM, trackIndex })
+			if (points.length < 2) return
+			const positions = points.map(point =>
+				Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
+			)
+			let trackMaterial = createDroneTrackMaterial({
+				color: baseTrackColor,
+				speed: 4.5,
+				headWidth: 0.2,
+				glowPower: 1.8,
+				backgroundAlpha: 0.34,
+			})
+			if (!trackMaterial) {
+				trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
+			}
+			droneTrackPolylineCollection.add({
+				positions,
+				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,
+			})
+			billboard.id = droneId
+			const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
 		dronePickMap.set(droneId, {
 			data: {
-				droneName: `无人机-${trackIndex + 1}`,
-				droneSerialNo: `UAV-${trackIndex + 1}`,
-				flightHeightM: points[0].height,
-				flightSpeedMs: speedMs,
-				longitude: points[0].longitude,
-				latitude: points[0].latitude,
-				flightStatus: 1,
-				deviceName: centerName,
-				dataSource: centerName,
-				counterDeviceName: centerName,
+				...item,
+				flightHeightM: item.flightHeightM ?? points[0].height,
+				flightSpeedMs: item.flightSpeedMs ?? speedMs,
+				longitude: item.longitude ?? points[0].longitude,
+				latitude: item.latitude ?? points[0].latitude,
 			},
 			billboard,
 		})
-		droneTrackRuntime.push({
-			positions,
-			billboard,
-			segmentDuration,
-			duration: (points.length - 1) * segmentDuration,
+		if (isFavorited(item)) {
+			createFavoritePulseEntity(billboard)
+		}
+			droneTrackRuntime.push({
+				positions,
+				billboard,
+				segmentDuration,
+				duration: (points.length - 1) * segmentDuration,
+			})
 		})
-	})
 	viewer.clock.multiplier = 1
 	startDroneTrackAnimation()
+	startFavoritePulseAnimation()
 	reorderCockpitPrimitives()
 }
 
@@ -629,34 +796,34 @@
 	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,
+		; (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)
+						),
+					},
+				})
+			)
 		})
-		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)
@@ -732,7 +899,7 @@
 		viewer.dataSources.add(aggregationSource)
 	}
 	clearAggregationEntities()
-	aggregationSource.show = clusterVisible.value
+	updateAggregationVisibility()
 	const countMap = new Map()
 		; (list || []).forEach(item => {
 			if (!item?.type) return
@@ -839,9 +1006,9 @@
 const popupPosition = ref({ x: 0, y: 0 })
 const popupVisible = computed(() => Boolean(selectedDevice.value))
 const isDronePopup = computed(() => selectedTargetType.value === 'drone')
-const toggleDroneFavorite = () => {
+const handleDroneFavorite = () => {
 	if (!selectedDevice.value) return
-	selectedDevice.value.isFavorite = !selectedDevice.value.isFavorite
+	emit('droneFavorite', selectedDevice.value)
 }
 const handleDroneSignal = () => emit('droneSignal', selectedDevice.value)
 const handleDroneCounter = () => emit('droneCounter', selectedDevice.value)
@@ -851,17 +1018,17 @@
 	ensureCommandPostCollection()
 	commandPostBillboardCollection.removeAll()
 	commandPostBillboardCollection.show = detailVisible.value
-	; (list || []).forEach((item, index) => {
-		const position = getDevicePosition(item)
-		if (!position) return
-		commandPostBillboardCollection.add({
-			position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
-			image: commandPostIcon,
-			width: 40,
-			height: 56,
-			verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+		; (list || []).forEach((item, index) => {
+			const position = getDevicePosition(item)
+			if (!position) return
+			commandPostBillboardCollection.add({
+				position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
+				image: commandPostIcon,
+				width: 40,
+				height: 56,
+				verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+			})
 		})
-	})
 	reorderCockpitPrimitives()
 }
 
@@ -895,6 +1062,19 @@
 )
 
 watch(
+	() => props.alarmDrones,
+	list => {
+		renderSimulatedDroneTrack(list || [])
+		if (selectedTargetType.value === 'drone' && selectedDevice.value) {
+			const selectedId = selectedDevice.value.alarmRecordId ?? selectedDevice.value.id
+			const match = (list || []).find(item => (item?.alarmRecordId ?? item?.id) === selectedId)
+			if (match) selectedDevice.value = { ...selectedDevice.value, ...match }
+		}
+	},
+	{ deep: true }
+)
+
+watch(
 	() => props.leftCollapsed,
 	isCollapsed => {
 		if (isCollapsed) showLayerPanel.value = false
@@ -906,6 +1086,29 @@
 	showLayerPanel.value = !showLayerPanel.value
 }
 
+const applyLayerVisibility = checkedKeys => {
+	publicCesium?.switchLayers?.(baseLayerKey.value === 'base-standard' ? 0 : 4)
+	const showTerrain = checkedKeys.includes('ja-terrain')
+	publicCesium?.setTerrainVisible?.(showTerrain)
+	const showAdmin = checkedKeys.includes('admin')
+	adminBoundaryVisible.value = showAdmin
+	mapRef.value?.setAdminBoundaryVisible?.(showAdmin)
+	updateAggregationVisibility()
+}
+
+const handleLayerCheck = (_data, state) => {
+	const checkedKeys = state?.checkedKeys ?? layerTreeRef.value?.getCheckedKeys?.() ?? []
+	treeCheckedKeys.value = checkedKeys
+	applyLayerVisibility(checkedKeys)
+}
+
+const handleBaseLayerSelect = key => {
+	if (!baseLayerKeys.includes(key)) return
+	if (baseLayerKey.value === key) return
+	baseLayerKey.value = key
+	applyLayerVisibility(treeCheckedKeys.value)
+}
+
 const updateStageDisplay = stage => {
 	const showCluster = stage === 'cluster'
 	setClusterVisibility(showCluster)
@@ -913,9 +1116,11 @@
 	setDroneVisibility(!showCluster)
 }
 
-const handleMapReady = ({ viewer: mapViewer }) => {
+const handleMapReady = ({ viewer: mapViewer, publicCesium: mapPublic }) => {
 	viewer = mapViewer
+	publicCesium = mapPublic
 	ensureCockpitPrimitiveLayer()
+	applyLayerVisibility(treeCheckedKeys.value)
 	const height = viewer?.camera?.positionCartographic?.height
 	const stage = getStageByHeight(height)
 	updateStageDisplay(stage)
@@ -924,7 +1129,7 @@
 	loadPartitions()
 	loadAggregation()
 	loadCommandPosts()
-	renderSimulatedDroneTrack()
+	renderSimulatedDroneTrack(props.alarmDrones)
 	initDeviceClickHandler()
 }
 
@@ -960,6 +1165,7 @@
 	}
 	cockpitPrimitiveLayer = null
 	viewer = null
+	publicCesium = null
 })
 </script>
 
@@ -1041,6 +1247,51 @@
 		flex: 1;
 		overflow: auto;
 	}
+
+	.base-map-title {
+		margin-bottom: 8px;
+		font-size: 12px;
+		color: #ffffff;
+		font-weight: 600;
+	}
+
+	.base-map-options {
+		padding: 16px;
+		display: grid;
+		grid-template-columns: repeat(2, minmax(0, 1fr));
+		gap: 8px;
+	}
+
+	.base-map-card {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		gap: 6px;
+		border-radius: 6px;
+		color: #d8e6ff;
+		font-size: 12px;
+		cursor: pointer;
+		transition: all 0.2s ease;
+
+		.base-map-thumb {
+			width: 46px;
+			height: 46px;
+			border-radius: 4px;
+			background-size: cover;
+			background-position: center;
+			box-sizing: border-box;
+		}
+
+		&.active {
+			.base-map-thumb {
+				border: 2px solid #2ea8ff;
+			}
+
+			.base-map-label {
+				color: #2ea8ff;
+			}
+		}
+	}
 }
 </style>
-

--
Gitblit v1.9.3