From c1b252b4890ae7df1720955cb7caba184d78798f Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Tue, 27 Jan 2026 11:32:32 +0800
Subject: [PATCH] feat:数据驾驶舱部分调整

---
 applications/drone-command/src/utils/cesium/adminBoundary.js                     |    3 
 applications/drone-command/src/styles/common/cockpit.scss                        |   28 ++-
 applications/drone-command/src/components/map-container/device-map-container.vue |  238 +++++++++++++++++++++++----------
 applications/drone-command/src/assets/images/dataCockpit/map/yx-map-layer.png    |    0 
 applications/drone-command/src/utils/cesium/publicCesium.js                      |   50 +++++-
 applications/drone-command/src/assets/images/dataCockpit/map/blue-map-layer.png  |    0 
 applications/drone-command/src/components/map-container/common-cesium-map.vue    |   84 +++++++++--
 applications/drone-command/src/assets/images/dataCockpit/map/dz-map-layer.png    |    0 
 8 files changed, 290 insertions(+), 113 deletions(-)

diff --git a/applications/drone-command/src/assets/images/dataCockpit/map/blue-map-layer.png b/applications/drone-command/src/assets/images/dataCockpit/map/blue-map-layer.png
new file mode 100644
index 0000000..5f3e0a4
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/map/blue-map-layer.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/dataCockpit/map/dz-map-layer.png b/applications/drone-command/src/assets/images/dataCockpit/map/dz-map-layer.png
new file mode 100644
index 0000000..c387ea8
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/map/dz-map-layer.png
Binary files differ
diff --git a/applications/drone-command/src/assets/images/dataCockpit/map/yx-map-layer.png b/applications/drone-command/src/assets/images/dataCockpit/map/yx-map-layer.png
new file mode 100644
index 0000000..ca383c3
--- /dev/null
+++ b/applications/drone-command/src/assets/images/dataCockpit/map/yx-map-layer.png
Binary files differ
diff --git a/applications/drone-command/src/components/map-container/common-cesium-map.vue b/applications/drone-command/src/components/map-container/common-cesium-map.vue
index 3345c78..e728ff2 100644
--- a/applications/drone-command/src/components/map-container/common-cesium-map.vue
+++ b/applications/drone-command/src/components/map-container/common-cesium-map.vue
@@ -3,7 +3,7 @@
 </template>
 
 <script setup>
-import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
+import { onBeforeUnmount, onMounted, watch } from 'vue'
 import { PublicCesium } from '@/utils/cesium/publicCesium'
 import { loadJaAdminBoundary, removeJaAdminBoundary } from '@/utils/cesium/adminBoundary'
 
@@ -60,8 +60,13 @@
 
 const emit = defineEmits(['ready', 'height-change', 'stage-change'])
 const mapId = props.domId || `common-cesium-map-${Math.random().toString(36).slice(2, 10)}`
-const viewInstance = ref(null)
-const viewer = ref(null)
+let viewInstance = null
+let viewer = null
+const ADMIN_BOUNDARY_NAMES = {
+	boundary: 'jaBoundarySource',
+	line: 'jaBoundaryLineSource',
+	label: 'jaBoundaryLabelSource',
+}
 let adminBoundarySources = null
 let initialized = false
 let removeCameraListener = null
@@ -69,7 +74,7 @@
 const initMap = async () => {
 	if (initialized) return
 	initialized = true
-	viewInstance.value = new PublicCesium({
+	viewInstance = new PublicCesium({
 		dom: mapId,
 		flatMode: props.flatMode,
 		terrain: props.terrain,
@@ -77,16 +82,16 @@
 		boundary: props.boundary,
 		contour: props.contour,
 	})
-	viewer.value = viewInstance.value.getViewer()
+	viewer = viewInstance.getViewer()
 	if (props.showAdminBoundary) {
-		adminBoundarySources = await loadJaAdminBoundary(viewer.value, {
+		adminBoundarySources = await loadJaAdminBoundary(viewer, {
 			zoomTo: props.zoomToBoundary,
 		})
 	}
 	if (props.enableStageEmit) {
 		let lastStage = ''
 		const onCameraChanged = () => {
-			const height = viewer.value?.camera?.positionCartographic?.height
+			const height = viewer?.camera?.positionCartographic?.height
 			if (height == null) return
 			emit('height-change', height)
 			let stage = 'mid'
@@ -97,10 +102,10 @@
 				emit('stage-change', stage)
 			}
 		}
-		viewer.value?.camera?.changed?.addEventListener(onCameraChanged)
-		removeCameraListener = () => viewer.value?.camera?.changed?.removeEventListener(onCameraChanged)
+		viewer?.camera?.changed?.addEventListener(onCameraChanged)
+		removeCameraListener = () => viewer?.camera?.changed?.removeEventListener(onCameraChanged)
 	}
-	emit('ready', { viewer: viewer.value, publicCesium: viewInstance.value })
+	emit('ready', { viewer, publicCesium: viewInstance })
 }
 
 watch(
@@ -116,15 +121,62 @@
 
 onBeforeUnmount(() => {
 	if (removeCameraListener) removeCameraListener()
-	removeJaAdminBoundary(viewer.value, adminBoundarySources)
-	viewInstance.value?.viewerDestroy?.()
+	removeJaAdminBoundary(viewer, adminBoundarySources)
+	adminBoundarySources = null
+	viewInstance?.viewerDestroy?.()
 })
 
-const getMap = () => ({ viewer: viewer.value, publicCesium: viewInstance.value })
-const getViewer = () => viewer.value
-const getPublicCesium = () => viewInstance.value
+const setAdminBoundaryVisible = async visible => {
+	if (!viewer) return
+	const removeByNames = () => {
+		Object.values(ADMIN_BOUNDARY_NAMES).forEach(name => {
+			const sources = viewer.dataSources.getByName(name) || []
+			sources.forEach(source => viewer.dataSources.remove(source))
+		})
+	}
+	const resolveSources = () => {
+		if (adminBoundarySources) return adminBoundarySources
+		const boundarySource = viewer.dataSources.getByName(ADMIN_BOUNDARY_NAMES.boundary)?.[0] ?? null
+		const lineSource = viewer.dataSources.getByName(ADMIN_BOUNDARY_NAMES.line)?.[0] ?? null
+		const labelSource = viewer.dataSources.getByName(ADMIN_BOUNDARY_NAMES.label)?.[0] ?? null
+		if (boundarySource || lineSource || labelSource) {
+			adminBoundarySources = { boundarySource, lineSource, labelSource }
+		}
+		return adminBoundarySources
+	}
+	const setVisible = (source, next) => {
+		if (!source) return
+		source.show = next
+		source.entities?.values?.forEach(entity => {
+			entity.show = next
+		})
+	}
+	const sources = resolveSources()
+	if (!visible) {
+		if (sources) {
+			removeJaAdminBoundary(viewer, sources)
+			adminBoundarySources = null
+		}
+		removeByNames()
+		return
+	}
+	if (sources) {
+		const { boundarySource, lineSource, labelSource } = sources
+		setVisible(boundarySource, true)
+		setVisible(lineSource, true)
+		setVisible(labelSource, true)
+		return
+	}
+	adminBoundarySources = await loadJaAdminBoundary(viewer, {
+		zoomTo: false,
+	})
+}
 
-defineExpose({ getMap, getViewer, getPublicCesium })
+const getMap = () => ({ viewer, publicCesium: viewInstance })
+const getViewer = () => viewer
+const getPublicCesium = () => viewInstance
+
+defineExpose({ getMap, getViewer, getPublicCesium, setAdminBoundaryVisible })
 </script>
 
 <style scoped lang="scss">
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..49bd52b 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="toggleDroneFavorite"
+			@signal="handleDroneSignal" @counter="handleDroneCounter" />
 	</div>
 </template>
 
@@ -64,6 +67,8 @@
 	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
@@ -92,6 +97,7 @@
 
 const mapRef = ref(null)
 let viewer = null
+let publicCesium = null
 let cockpitPrimitiveLayer = null
 const devicePickMap = new Map()
 const dronePickMap = new Map()
@@ -114,6 +120,7 @@
 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 +130,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 +165,9 @@
 			{ key: 'route', label: '飞行航路' },
 		],
 	},
-])
+ */
+
+const adminBoundaryVisible = ref(treeCheckedKeys.value.includes('admin'))
 
 const getDevicePosition = item => {
 	const longitudeRaw = item.longitude ?? item.lng ?? item.lon
@@ -314,7 +330,12 @@
 
 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 => {
@@ -350,33 +371,33 @@
 			height: 120
 		},
 		{
-			longitude:114.957308,
+			longitude: 114.957308,
 			latitude: 27.138452,
 			height: 120
 		},
 		{
-			longitude:114.952,
+			longitude: 114.952,
 			latitude: 27.136317,
 			height: 120
 		},
 		{
-			longitude:114.949293,
+			longitude: 114.949293,
 			latitude: 27.133864,
 			height: 120
 		},
 		{
-			longitude:114.944666,
+			longitude: 114.944666,
 			latitude: 27.130526,
 			height: 120
 		},
 		{
-			longitude:114.945909,
-			latitude:27.127845,
+			longitude: 114.945909,
+			latitude: 27.127845,
 			height: 120
 		},
 		{
-			longitude:114.962974,
-			latitude:27.136242,
+			longitude: 114.962974,
+			latitude: 27.136242,
 			height: 120
 		},
 	]
@@ -472,7 +493,7 @@
 	droneTrackBillboardCollection.show = detailVisible.value
 	droneTrackPolylineCollection.show = detailVisible.value
 	droneTrackRuntime = []
-	const baseCenters = [['默认区域', { longitude:114.958541, latitude: 27.121917 }]]
+	const baseCenters = [['默认区域', { longitude: 114.958541, latitude: 27.121917 }]]
 	const segmentDuration = 6
 	const baseTrackColor = Cesium.Color.fromCssColorString('red')
 	baseCenters.forEach(([centerName, center], trackIndex) => {
@@ -629,34 +650,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 +753,7 @@
 		viewer.dataSources.add(aggregationSource)
 	}
 	clearAggregationEntities()
-	aggregationSource.show = clusterVisible.value
+	updateAggregationVisibility()
 	const countMap = new Map()
 		; (list || []).forEach(item => {
 			if (!item?.type) return
@@ -851,17 +872,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()
 }
 
@@ -906,6 +927,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 +957,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)
@@ -960,6 +1006,7 @@
 	}
 	cockpitPrimitiveLayer = null
 	viewer = null
+	publicCesium = null
 })
 </script>
 
@@ -1041,6 +1088,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>
-
diff --git a/applications/drone-command/src/styles/common/cockpit.scss b/applications/drone-command/src/styles/common/cockpit.scss
index 61579d7..7891aa5 100644
--- a/applications/drone-command/src/styles/common/cockpit.scss
+++ b/applications/drone-command/src/styles/common/cockpit.scss
@@ -76,25 +76,12 @@
         color: #c3c3dd;
     }
 
-    .el-tree-node__expand-icon {
-        order: 3;
-        margin-left: auto;
-    }
-
-    .el-tree-node__expand-icon.is-leaf {
-        visibility: hidden;
-    }
-
     .el-checkbox {
         order: 1;
     }
 
     .el-tree-node__label {
         order: 2;
-    }
-
-    .el-tree-node__expand-icon:not(.is-leaf)~.el-checkbox {
-        display: none;
     }
 
     .el-tree-node__content:hover,
@@ -122,6 +109,21 @@
     }
 }
 
+.map-layer-tree {
+    .el-tree-node__expand-icon:not(.is-leaf)~.el-checkbox {
+        display: none !important;
+    }
+
+    .el-tree-node__expand-icon {
+        order: 3;
+        margin-left: auto;
+    }
+
+    .el-tree-node__expand-icon.is-leaf {
+        visibility: hidden;
+    }
+}
+
 .command-cascader {
     height: 40px;
 
diff --git a/applications/drone-command/src/utils/cesium/adminBoundary.js b/applications/drone-command/src/utils/cesium/adminBoundary.js
index 381dc7d..25caf0d 100644
--- a/applications/drone-command/src/utils/cesium/adminBoundary.js
+++ b/applications/drone-command/src/utils/cesium/adminBoundary.js
@@ -24,6 +24,7 @@
 		fill: Cesium.Color.fromCssColorString(config.fillColor).withAlpha(config.fillAlpha),
 		clampToGround: true,
 	})
+	boundarySource.name = 'jaBoundarySource'
 	viewer.dataSources.add(boundarySource)
 
 	const lineSource = new Cesium.CustomDataSource('jaBoundaryLineSource')
@@ -42,6 +43,7 @@
 			},
 		})
 	})
+	lineSource.name = 'jaBoundaryLineSource'
 	viewer.dataSources.add(lineSource)
 
 	const labelSource = new Cesium.CustomDataSource('jaBoundaryLabelSource')
@@ -67,6 +69,7 @@
 			},
 		})
 	})
+	labelSource.name = 'jaBoundaryLabelSource'
 	viewer.dataSources.add(labelSource)
 
 	if (config.zoomTo) {
diff --git a/applications/drone-command/src/utils/cesium/publicCesium.js b/applications/drone-command/src/utils/cesium/publicCesium.js
index 94c734e..234d0e5 100644
--- a/applications/drone-command/src/utils/cesium/publicCesium.js
+++ b/applications/drone-command/src/utils/cesium/publicCesium.js
@@ -114,6 +114,9 @@
 		this.handlerRightEvents = { handler: null }
 		this.handlerMouseMoveEvents = { handler: null }
 		this.viewer = null
+		this.terrainProvider = null
+		this.terrainLoading = null
+		this.terrainEnabled = false
 		this.globalBaseMapLayers = []
 		this.boundary = null
 		this.init(options)
@@ -177,16 +180,7 @@
 
 		this.viewer?.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) // 禁用双击
 
-		if (terrain) {
-			// 使用公司地形
-			Cesium.CesiumTerrainProvider.fromUrl(`${import.meta.env.VITE_APP_TERRAIN_URL}ja_terrain`, {
-				requestVertexNormals: true, // 启用地形法线增强立体感
-				requestWaterMask: true, // 启用水体遮罩效果
-			}).then(terrainProvider => {
-				this.viewer.terrainProvider = terrainProvider
-				terrainLoadCallback?.()
-			})
-		}
+		this.setTerrainVisible(terrain, terrainLoadCallback)
 
 		this.viewer.scene.screenSpaceCameraController.maximumZoomDistance = 4500000
 		this.switchLayers(layerMode)
@@ -203,6 +197,40 @@
 
 	getViewer () {
 		return this.viewer
+	}
+
+	async ensureTerrainProvider () {
+		if (this.terrainProvider) return this.terrainProvider
+		if (this.terrainLoading) return this.terrainLoading
+		this.terrainLoading = Cesium.CesiumTerrainProvider.fromUrl(
+			`${import.meta.env.VITE_APP_TERRAIN_URL}ja_terrain`,
+			{
+				requestVertexNormals: true, // 启用地形法线增强立体感
+				requestWaterMask: true, // 启用水体遮罩效果
+			}
+		).then(provider => {
+			this.terrainProvider = provider
+			return provider
+		})
+		return this.terrainLoading
+	}
+
+	async setTerrainVisible (visible, terrainLoadCallback) {
+		if (!this.viewer) return
+		if (!visible) {
+			this.viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider()
+			this.terrainEnabled = false
+			return
+		}
+		try {
+			const provider = await this.ensureTerrainProvider()
+			if (!this.viewer) return
+			this.viewer.terrainProvider = provider
+			this.terrainEnabled = true
+			terrainLoadCallback?.()
+		} catch (error) {
+			this.terrainEnabled = false
+		}
 	}
 
 	// 销毁viewer
@@ -876,4 +904,4 @@
 			this.viewer?.imageryLayers.remove(imageryLayers)
 		}
 	}
-}
\ No newline at end of file
+}

--
Gitblit v1.9.3