From affb54c33083cb09cd0ef9da137b446cafdfd0fb Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Tue, 27 Jan 2026 16:47:08 +0800
Subject: [PATCH] feat:用户管理、部门管理调整
---
applications/drone-command/src/components/map-container/device-map-container.vue | 1280 +++++++++++++++++++++++++++++++++++++++++++++------------
1 files changed, 1,005 insertions(+), 275 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 b54ab6d..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
@@ -1,38 +1,46 @@
-<template>
- <CommonCesiumMap
- ref="mapRef"
- class="command-cesium map-container"
- :dom-id="props.containerId"
- :active="true"
- :flat-mode="false"
- :terrain="false"
- :layer-mode="4"
- :contour="false"
- :boundary="false"
- :show-admin-boundary="true"
- :zoom-to-boundary="true"
- @ready="handleMapReady"
- />
- <div v-if="props.showLayerControl" class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
- <div class="layer-control-wrap" ref="layerWrapRef">
- <div class="layer-control" @click="toggleLayerPanel">
- <img :src="layerControlIcon" alt="图层控制" />
- </div>
- <div v-if="showLayerPanel" class="layer-panel">
- <div class="panel-title">图层管理</div>
+<template>
+ <div class="map-shell">
+ <CommonCesiumMap ref="mapRef" class="command-cesium map-container" :dom-id="props.containerId" :active="true"
+ :flat-mode="false" :terrain="true" :layer-mode="4" :contour="false" :boundary="false"
+ :show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true"
+ :cluster-height="CLUSTER_HEIGHT" :detail-height="DETAIL_HEIGHT" @ready="handleMapReady"
+ @stage-change="handleStageChange" />
+ <div v-if="props.showLayerControl" class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
+ <div class="layer-control-wrap" ref="layerWrapRef">
+ <div class="layer-control" @click="toggleLayerPanel">
+ <img :src="layerControlIcon" alt="图层控制" />
+ </div>
+ <div v-if="showLayerPanel" class="layer-panel">
+ <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-content">
- <el-tree
- :data="layerTree"
- show-checkbox
- default-expand-all
- node-key="key"
- :props="layerTreeProps"
- :default-checked-keys="defaultCheckedKeys"
- />
+ <div class="panel-title">图层管理</div>
+ <div class="panel-content">
+ <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" />
+
+ <DronePopup :visible="popupVisible && isDronePopup" :position="popupPosition" :drone="selectedDevice"
+ :favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="handleDroneFavorite"
+ @signal="handleDroneSignal" @counter="handleDroneCounter" />
</div>
</template>
@@ -42,11 +50,35 @@
import { geomAnalysis } from '@ztzf/utils'
import { fwDefenseZonePageApi } from '@/views/areaManage/defenseZone/defenseZoneApi'
import { fwAreaDividePageApi } from '@/views/areaManage/partition/partitionApi'
+import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi'
+import { cockpitAggregationApi } from '@/api/dataCockpit'
import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
import equipmentIcon from '@/assets/images/dataCockpit/map/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 {
+ RING_STYLES,
+ addDeviceRings,
+ createDroneTrackMaterial,
+ 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: () => [],
},
@@ -63,31 +95,71 @@
default: true,
},
})
+const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite'])
const DEFAULT_ZONE_PAGE_SIZE = 999
const mapRef = ref(null)
let viewer = null
-const deviceEntityIds = new Set()
-let defenseZoneSource = null
-let partitionSource = null
+let publicCesium = null
+let cockpitPrimitiveLayer = null
+const devicePickMap = new Map()
+const dronePickMap = new Map()
+let deviceBillboardCollection = null
+let deviceRingOutlinePrimitives = []
+let deviceRingFillPrimitives = []
+let defenseZonePrimitive = null
+let defenseZoneOutlinePrimitive = null
+let partitionPrimitive = null
+let partitionOutlinePrimitive = null
+let aggregationSource = null
+let commandPostBillboardCollection = null
+let droneTrackBillboardCollection = null
+let droneTrackPolylineCollection = null
+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
+let deviceClickHandler = null
+let popupRenderHandler = null
const layerTreeProps = {
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: [
@@ -105,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
@@ -117,151 +242,541 @@
return { longitude, latitude }
}
+const getDeviceRange = item => {
+ if (!item) return null
+ const rawRange = item.effectiveRangeKm ?? item.range ?? item.coverRadiusM
+ const range = Number(rawRange)
+ if (!Number.isFinite(range) || range <= 0) return null
+ return range
+}
+
+const ensureCockpitPrimitiveLayer = () => {
+ if (!viewer) return
+ if (!cockpitPrimitiveLayer) {
+ cockpitPrimitiveLayer = new Cesium.PrimitiveCollection({ destroyPrimitives: false })
+ viewer.scene.primitives.add(cockpitPrimitiveLayer)
+ }
+}
+
+const addCockpitPrimitive = primitive => {
+ if (!primitive) return
+ if (cockpitPrimitiveLayer) {
+ cockpitPrimitiveLayer.add(primitive)
+ return
+ }
+ viewer?.scene?.primitives?.add(primitive)
+}
+
+const removeCockpitPrimitive = (primitive, destroy = true) => {
+ if (!primitive) return
+ if (cockpitPrimitiveLayer) {
+ cockpitPrimitiveLayer.remove(primitive, destroy)
+ return
+ }
+ viewer?.scene?.primitives?.remove(primitive)
+}
+
+const reorderCockpitPrimitives = () => {
+ 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 (deviceRingFillPrimitives.length) {
+ deviceRingFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
+ }
+ if (deviceRingOutlinePrimitives.length) {
+ deviceRingOutlinePrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
+ }
+ if (droneTrackPolylineCollection) cockpitPrimitiveLayer.add(droneTrackPolylineCollection)
+ if (droneTrackBillboardCollection) cockpitPrimitiveLayer.add(droneTrackBillboardCollection)
+ if (deviceBillboardCollection) cockpitPrimitiveLayer.add(deviceBillboardCollection)
+ if (commandPostBillboardCollection) cockpitPrimitiveLayer.add(commandPostBillboardCollection)
+}
+
const clearDeviceEntities = () => {
if (!viewer) return
- deviceEntityIds.forEach(id => {
- viewer.entities.removeById(id)
- })
- deviceEntityIds.clear()
+ if (deviceBillboardCollection) {
+ deviceBillboardCollection.removeAll()
+ removeCockpitPrimitive(deviceBillboardCollection)
+ deviceBillboardCollection = null
+ }
+ if (deviceRingFillPrimitives.length) {
+ deviceRingFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+ deviceRingFillPrimitives = []
+ }
+ if (deviceRingOutlinePrimitives.length) {
+ deviceRingOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+ deviceRingOutlinePrimitives = []
+ }
+ devicePickMap.clear()
+}
+
+const ensureDeviceCollections = () => {
+ if (!viewer) return
+ ensureCockpitPrimitiveLayer()
+ if (!deviceBillboardCollection) {
+ deviceBillboardCollection = new Cesium.BillboardCollection()
+ addCockpitPrimitive(deviceBillboardCollection)
+ }
}
const clearDefenseZoneEntities = () => {
- if (!defenseZoneSource) return
- defenseZoneSource.entities.removeAll()
+ if (!viewer) return
+ if (defenseZonePrimitive) {
+ removeCockpitPrimitive(defenseZonePrimitive)
+ defenseZonePrimitive = null
+ }
+ if (defenseZoneOutlinePrimitive) {
+ removeCockpitPrimitive(defenseZoneOutlinePrimitive)
+ defenseZoneOutlinePrimitive = null
+ }
}
const clearPartitionEntities = () => {
- if (!partitionSource) return
- partitionSource.entities.removeAll()
-}
-
-
-const RING_STYLES = [
- { inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] }
-]
-
-const MATERIAL_TYPE = 'RadialGradientMaterial'
-let materialRegistered = false
-
-const registerRadialGradientMaterial = () => {
- if (materialRegistered || !Cesium?.Material) return
- materialRegistered = true
- Cesium.Material._materialCache.addMaterial(MATERIAL_TYPE, {
- fabric: {
- type: MATERIAL_TYPE,
- uniforms: {
- color1: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
- color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0),
- },
- source: `
- czm_material czm_getMaterial(czm_materialInput materialInput) {
- czm_material material = czm_getDefaultMaterial(materialInput);
- vec2 st = materialInput.st - vec2(0.5);
- float t = clamp(length(st) * 2.0, 0.0, 1.0);
- vec4 color = mix(color1, color2, t);
- material.diffuse = color.rgb;
- material.alpha = color.a;
- return material;
- }
- `,
- },
- translucent: () => true,
- })
-}
-
-const buildCirclePositions = (center, radiusMeters, steps = 64) => {
- const positions = []
- const latRad = Cesium.Math.toRadians(center.latitude)
- const metersToLat = 1 / 111320
- const metersToLon = 1 / (111320 * Math.cos(latRad))
- for (let i = 0; i <= steps; i += 1) {
- const angle = Cesium.Math.toRadians((i / steps) * 360)
- const dx = radiusMeters * Math.cos(angle)
- const dy = radiusMeters * Math.sin(angle)
- const lon = center.longitude + dx * metersToLon
- const lat = center.latitude + dy * metersToLat
- positions.push(Cesium.Cartesian3.fromDegrees(lon, lat))
+ if (!viewer) return
+ if (partitionPrimitive) {
+ removeCockpitPrimitive(partitionPrimitive)
+ partitionPrimitive = null
}
- return positions
+ if (partitionOutlinePrimitive) {
+ removeCockpitPrimitive(partitionOutlinePrimitive)
+ partitionOutlinePrimitive = null
+ }
}
-const addDeviceRings = (center, baseId) => {
- RING_STYLES.forEach((ring, index) => {
- const outerPositions = buildCirclePositions(center, ring.outer)
- const holes = ring.inner
- ? [new Cesium.PolygonHierarchy(buildCirclePositions(center, ring.inner))]
- : []
- const entityId = `${baseId}-ring-${index}`
- deviceEntityIds.add(entityId)
- registerRadialGradientMaterial()
- const [startColor, endColor] = ring.gradient
- const color1 = Cesium.Color.fromCssColorString(startColor).withAlpha(0.34)
- const color2 = Cesium.Color.fromCssColorString(endColor).withAlpha(0.34)
- const material = new RadialGradientMaterialProperty(color1, color2)
- viewer.entities.add({
- id: entityId,
- polygon: {
- hierarchy: new Cesium.PolygonHierarchy(outerPositions, holes),
- material,
- },
+const clearAggregationEntities = () => {
+ if (!aggregationSource) return
+ aggregationSource.entities.removeAll()
+}
+
+const clearCommandPostEntities = () => {
+ if (!viewer) return
+ if (commandPostBillboardCollection) {
+ commandPostBillboardCollection.removeAll()
+ removeCockpitPrimitive(commandPostBillboardCollection)
+ commandPostBillboardCollection = null
+ }
+}
+
+const ensureCommandPostCollection = () => {
+ if (!viewer) return
+ ensureCockpitPrimitiveLayer()
+ if (!commandPostBillboardCollection) {
+ commandPostBillboardCollection = new Cesium.BillboardCollection()
+ addCockpitPrimitive(commandPostBillboardCollection)
+ }
+}
+
+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
+ if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = visible
+ if (partitionPrimitive) partitionPrimitive.show = visible
+ if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = visible
+ if (commandPostBillboardCollection) commandPostBillboardCollection.show = visible
+ if (deviceBillboardCollection) deviceBillboardCollection.show = visible
+ if (deviceRingFillPrimitives.length) {
+ deviceRingFillPrimitives.forEach(primitive => {
+ primitive.show = visible
})
+ }
+ if (deviceRingOutlinePrimitives.length) {
+ deviceRingOutlinePrimitives.forEach(primitive => {
+ primitive.show = visible
+ })
+ }
+ if (!visible) closePopup()
+}
+
+const setClusterVisibility = visible => {
+ clusterVisible.value = 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()
+ }
+}
+const getStageByHeight = height => {
+ if (height == null) return 'detail'
+ if (height >= CLUSTER_HEIGHT) return 'cluster'
+ if (height <= DETAIL_HEIGHT) return 'detail'
+ return 'mid'
+}
+
+const ensureCountyCenterMap = () => {
+ if (countyCenterMap.size) return
+ const geojson = JSON.parse(jaGeojsonRaw)
+ geojson.features?.forEach(feature => {
+ const name = feature?.properties?.name
+ const center = feature?.properties?.centroid || feature?.properties?.center
+ if (!name || !Array.isArray(center) || center.length < 2) return
+ countyCenterMap.set(name, { longitude: center[0], latitude: center[1] })
})
}
-class RadialGradientMaterialProperty {
- constructor(color1, color2) {
- this._definitionChanged = new Cesium.Event()
- this.color1 = color1
- this.color2 = color2
- }
+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
+ },
+ ],
- get isConstant() {
- return true
- }
+ [
+ {
+ 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
+ },
+ ]
+ ]
- get definitionChanged() {
- return this._definitionChanged
- }
+ return basePoints[center.trackIndex]
+}
- getType() {
- return MATERIAL_TYPE
- }
- getValue(time, result) {
- const target = result || {}
- target.color1 = this.color1
- target.color2 = this.color2
- return target
+const clearDroneTrackEntities = () => {
+ if (!viewer) return
+ stopDroneTrackAnimation()
+ stopFavoritePulseAnimation()
+ if (droneTrackBillboardCollection) {
+ if (!droneTrackBillboardCollection.isDestroyed?.()) {
+ removeCockpitPrimitive(droneTrackBillboardCollection)
+ }
+ droneTrackBillboardCollection = null
}
+ if (droneTrackPolylineCollection) {
+ if (!droneTrackPolylineCollection.isDestroyed?.()) {
+ removeCockpitPrimitive(droneTrackPolylineCollection)
+ }
+ 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()
+ }
+}
- equals(other) {
- return (
- other instanceof RadialGradientMaterialProperty &&
- Cesium.Color.equals(this.color1, other.color1) &&
- Cesium.Color.equals(this.color2, other.color2)
- )
+
+const ensureDroneTrackCollections = () => {
+ if (!viewer) return
+ ensureCockpitPrimitiveLayer()
+ if (droneTrackBillboardCollection?.isDestroyed?.()) {
+ droneTrackBillboardCollection = null
}
+ if (droneTrackPolylineCollection?.isDestroyed?.()) {
+ droneTrackPolylineCollection = null
+ }
+ if (!droneTrackBillboardCollection) {
+ droneTrackBillboardCollection = new Cesium.BillboardCollection()
+ addCockpitPrimitive(droneTrackBillboardCollection)
+ }
+ if (!droneTrackPolylineCollection) {
+ droneTrackPolylineCollection = new Cesium.PolylineCollection()
+ addCockpitPrimitive(droneTrackPolylineCollection)
+ }
+}
+
+const startDroneTrackAnimation = () => {
+ if (!viewer) return
+ stopDroneTrackAnimation()
+ droneTrackStartTime = Cesium.JulianDate.now()
+ droneTrackTickHandler = clock => {
+ if (!viewer || viewer.isDestroyed?.() || !viewer.scene) {
+ stopDroneTrackAnimation()
+ 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()
+ )
+ track.billboard.position = pos
+ })
+ }
+ viewer.clock.onTick.addEventListener(droneTrackTickHandler)
+ viewer.clock.shouldAnimate = true
+}
+
+const stopDroneTrackAnimation = () => {
+ if (!viewer || !droneTrackTickHandler) return
+ viewer.clock.onTick.removeEventListener(droneTrackTickHandler)
+ droneTrackTickHandler = null
+}
+
+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 segmentDuration = 6
+ const baseTrackColor = Cesium.Color.fromCssColorString('red')
+ ; (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: {
+ ...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)
+ }
+ droneTrackRuntime.push({
+ positions,
+ billboard,
+ segmentDuration,
+ duration: (points.length - 1) * segmentDuration,
+ })
+ })
+ viewer.clock.multiplier = 1
+ startDroneTrackAnimation()
+ startFavoritePulseAnimation()
+ reorderCockpitPrimitives()
}
const renderDeviceEntities = devices => {
if (!viewer) return
- clearDeviceEntities()
+ ensureCockpitPrimitiveLayer()
+ ensureDeviceCollections()
+ deviceBillboardCollection.removeAll()
+ if (deviceRingFillPrimitives.length) {
+ deviceRingFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+ deviceRingFillPrimitives = []
+ }
+ if (deviceRingOutlinePrimitives.length) {
+ deviceRingOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+ deviceRingOutlinePrimitives = []
+ }
+ devicePickMap.clear()
+ deviceBillboardCollection.show = detailVisible.value
+ const ringFillInstancesByStyle = RING_STYLES.map(() => [])
+ const ringOutlineInstancesByStyle = RING_STYLES.map(() => [])
devices.forEach((item, index) => {
const position = getDevicePosition(item)
if (!position) return
- const entityId = `online-device-${item.id ?? index}`
- deviceEntityIds.add(entityId)
- addDeviceRings(position, entityId)
- viewer.entities.add({
- id: entityId,
+ const entityId = `online-device-${item.id ?? index}-${index}`
+ const rangeMeters = getDeviceRange(item)
+ addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters)
+ const billboard = deviceBillboardCollection.add({
position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
- billboard: {
- image: equipmentIcon,
- width: 40.34,
- height: 40.34,
- },
+ image: equipmentIcon,
+ width: 40,
+ height: 56,
+ verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
})
+ billboard.id = entityId
+ devicePickMap.set(entityId, { data: item, billboard })
})
+ 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,
+ }
+ )
+ const primitive = new Cesium.GroundPrimitive({
+ geometryInstances: instances,
+ appearance: new Cesium.MaterialAppearance({
+ material,
+ translucent: true,
+ }),
+ })
+ 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 => {
@@ -276,68 +791,84 @@
return list.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude))
}
-const renderZonePolygons = ({ zones, source, idPrefix, lineColor, fillGradient }) => {
- if (!viewer || !source) return
- registerRadialGradientMaterial()
- const borderColor = Cesium.Color.fromCssColorString(lineColor)
- const fillColorStart = Cesium.Color.fromCssColorString(fillGradient[0]).withAlpha(0.34)
- const fillColorEnd = Cesium.Color.fromCssColorString(fillGradient[1]).withAlpha(0.34)
- const fillMaterial = new RadialGradientMaterialProperty(fillColorStart, fillColorEnd)
- zones.forEach((zone, index) => {
- if (!zone?.geom) return
- const positions = getDefenseZonePositions(zone.geom)
- if (!positions.length) return
- const entityId = `${idPrefix}-${zone.id ?? index}`
- const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions
- source.entities.add({
- id: entityId,
- polygon: {
- hierarchy: new Cesium.PolygonHierarchy(positions),
- material: fillMaterial,
- outline: true,
- outlineColor: borderColor,
- heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
- },
- polyline: {
- positions: linePositions,
- clampToGround: true,
- width: 2,
- material: borderColor,
- },
+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 renderDefenseZones = zones => {
if (!viewer) return
- if (!defenseZoneSource) {
- defenseZoneSource = new Cesium.CustomDataSource('defenseZoneSource')
- viewer.dataSources.add(defenseZoneSource)
- }
clearDefenseZoneEntities()
- renderZonePolygons({
- zones,
- source: defenseZoneSource,
- idPrefix: 'defense-zone',
- lineColor: '#19D266',
- fillGradient: ['#2AEDBF', '#012B11'],
- })
+ 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 => {
if (!viewer) return
- if (!partitionSource) {
- partitionSource = new Cesium.CustomDataSource('partitionSource')
- viewer.dataSources.add(partitionSource)
- }
clearPartitionEntities()
- renderZonePolygons({
- zones,
- source: partitionSource,
- idPrefix: 'partition-zone',
- lineColor: '#FFCD2A',
- fillGradient: ['#FFC609', '#583300'],
- })
+ const result = buildZonePrimitives(zones, '#FFD772', '#FFD772', '#A86B00')
+ partitionPrimitive = result.primitive
+ partitionOutlinePrimitive = result.outlinePrimitive
+ if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
+ if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = detailVisible.value
+ reorderCockpitPrimitives()
}
const loadDefenseZones = async () => {
@@ -360,11 +891,185 @@
}
}
+const renderAggregation = list => {
+ if (!viewer) return
+ ensureCountyCenterMap()
+ if (!aggregationSource) {
+ aggregationSource = new Cesium.CustomDataSource('aggregationSource')
+ viewer.dataSources.add(aggregationSource)
+ }
+ clearAggregationEntities()
+ updateAggregationVisibility()
+ const countMap = new Map()
+ ; (list || []).forEach(item => {
+ if (!item?.type) return
+ countMap.set(item.type, Number(item.count ?? 0))
+ })
+ Array.from(countyCenterMap.entries()).forEach(([name, center], index) => {
+ const position = Cesium.Cartesian3.fromDegrees(center.longitude, center.latitude, 0)
+ const count = countMap.get(name) ?? 0
+ aggregationSource.entities.add({
+ id: `aggregation-${name}-${index}`,
+ position,
+ billboard: {
+ image: aggregationIcon,
+ width: 66.73,
+ height: 43,
+ verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+ // disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ label: {
+ text: `${count}`,
+ fillColor: Cesium.Color.WHITE,
+ style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+ verticalOrigin: Cesium.VerticalOrigin.CENTER,
+ horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
+ font: '11pt Source Han Sans CN',
+ eyeOffset: new Cesium.Cartesian3(0, 0, -20), // 让label "浮" 在广告牌前面
+
+ pixelOffset: new Cesium.Cartesian2(0, -35),
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ })
+ })
+}
+
+const getPickedTarget = picks => {
+ for (const pick of picks) {
+ const pickId = pick?.id
+ const resolvedId = typeof pickId === 'string' ? pickId : pickId?.id
+ if (resolvedId && devicePickMap.has(resolvedId)) {
+ return { type: 'device', ...devicePickMap.get(resolvedId) }
+ }
+ if (resolvedId && dronePickMap.has(resolvedId)) {
+ return { type: 'drone', ...dronePickMap.get(resolvedId) }
+ }
+ }
+ return null
+}
+
+const updatePopupPosition = () => {
+ if (!viewer || !selectedDeviceBillboard) return
+ const cartesian = selectedDeviceBillboard.position
+ if (!cartesian) return
+ const screenPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian)
+ if (!screenPosition) return
+ popupPosition.value = { x: screenPosition.x, y: screenPosition.y }
+}
+
+const startPopupRender = () => {
+ if (!viewer || popupRenderHandler) return
+ popupRenderHandler = () => updatePopupPosition()
+ viewer.scene.postRender.addEventListener(popupRenderHandler)
+ updatePopupPosition()
+}
+
+const stopPopupRender = () => {
+ if (!viewer || !popupRenderHandler) return
+ viewer.scene.postRender.removeEventListener(popupRenderHandler)
+ popupRenderHandler = null
+}
+
+const handleDeviceClick = movement => {
+ if (!viewer) return
+ const picks = viewer.scene.drillPick(movement.position) || []
+ const pickedTarget = getPickedTarget(picks)
+ if (!pickedTarget) {
+ closePopup()
+ return
+ }
+ selectedTargetType.value = pickedTarget.type
+ selectedDevice.value = pickedTarget.data
+ selectedDeviceBillboard = pickedTarget.billboard
+ startPopupRender()
+}
+
+const initDeviceClickHandler = () => {
+ if (deviceClickHandler || !viewer) return
+ deviceClickHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
+ deviceClickHandler.setInputAction(handleDeviceClick, Cesium.ScreenSpaceEventType.LEFT_CLICK)
+}
+
+const destroyDeviceClickHandler = () => {
+ deviceClickHandler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ deviceClickHandler?.destroy()
+ deviceClickHandler = null
+}
+
+function closePopup () {
+ selectedDevice.value = null
+ selectedDeviceBillboard = null
+ selectedTargetType.value = 'device'
+ stopPopupRender()
+}
+
+const popupPosition = ref({ x: 0, y: 0 })
+const popupVisible = computed(() => Boolean(selectedDevice.value))
+const isDronePopup = computed(() => selectedTargetType.value === 'drone')
+const handleDroneFavorite = () => {
+ if (!selectedDevice.value) return
+ emit('droneFavorite', selectedDevice.value)
+}
+const handleDroneSignal = () => emit('droneSignal', selectedDevice.value)
+const handleDroneCounter = () => emit('droneCounter', selectedDevice.value)
+
+const renderCommandPosts = list => {
+ if (!viewer) return
+ 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,
+ })
+ })
+ reorderCockpitPrimitives()
+}
+
+const loadAggregation = async () => {
+ try {
+ const res = await cockpitAggregationApi({
+ effectiveRangeKmIsNotNull: 1
+ })
+ renderAggregation(res?.data?.data ?? [])
+ } catch (error) {
+ renderAggregation([])
+ }
+}
+
+const loadCommandPosts = async () => {
+ try {
+ const res = await fwDefenseSceneListApi()
+ renderCommandPosts(res?.data?.data ?? [])
+ } catch (error) {
+ renderCommandPosts([])
+ }
+}
+
watch(
() => props.onlineDevices,
devices => {
renderDeviceEntities(devices || [])
+ },
+ { deep: true }
+)
+
+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 }
)
@@ -381,11 +1086,55 @@
showLayerPanel.value = !showLayerPanel.value
}
-const handleMapReady = ({ viewer: mapViewer }) => {
+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)
+ setDetailVisibility(!showCluster)
+ setDroneVisibility(!showCluster)
+}
+
+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)
renderDeviceEntities(props.onlineDevices)
loadDefenseZones()
loadPartitions()
+ loadAggregation()
+ loadCommandPosts()
+ renderSimulatedDroneTrack(props.alarmDrones)
+ initDeviceClickHandler()
+}
+
+const handleStageChange = stage => {
+ updateStageDisplay(stage)
}
const handleClickOutside = event => {
@@ -406,11 +1155,27 @@
clearDeviceEntities()
clearDefenseZoneEntities()
clearPartitionEntities()
+ clearAggregationEntities()
+ clearCommandPostEntities()
+ clearDroneTrackEntities()
+ closePopup()
+ destroyDeviceClickHandler()
+ if (cockpitPrimitiveLayer && viewer?.scene?.primitives) {
+ viewer.scene.primitives.remove(cockpitPrimitiveLayer)
+ }
+ cockpitPrimitiveLayer = null
viewer = null
+ publicCesium = null
})
</script>
<style lang="scss" scoped>
+.map-shell {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
.map-container {
position: absolute;
top: 0;
@@ -483,85 +1248,50 @@
overflow: auto;
}
- ::v-deep(.el-tree) {
- background: transparent;
- color: #c3c3dd;
+ .base-map-title {
+ margin-bottom: 8px;
font-size: 12px;
+ color: #ffffff;
+ font-weight: 600;
+ }
- .el-tree-node {
- line-height: 30px !important;
+ .base-map-options {
+ padding: 16px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 8px;
+ }
- .el-tree-node__content {
- padding-left: 0 !important;
- display: flex;
- align-items: center;
- height: 40px !important;
- line-height: 40px !important;
- border-bottom: 1px solid rgba(70, 70, 100, 0.5);
- box-sizing: border-box;
+ .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;
}
- .el-tree-node__children {
- .el-tree-node__content {
- border: none;
- }
- }
-
- &:focus {
- .el-tree-node__content {
- background: transparent !important;
- }
+ .base-map-label {
+ color: #2ea8ff;
}
}
}
-}
-
-.layer-panel :deep(.el-tree-node__label) {
- color: #c3c3dd;
-}
-
-.layer-panel :deep(.el-tree-node__expand-icon) {
- order: 3;
- margin-left: auto;
-}
-
-.layer-panel :deep(.el-tree-node__expand-icon.is-leaf) {
- visibility: hidden;
-}
-
-.layer-panel :deep(.el-checkbox) {
- order: 1;
-}
-
-.layer-panel :deep(.el-tree-node__label) {
- order: 2;
-}
-
-.layer-panel :deep(.el-tree-node__expand-icon:not(.is-leaf) ~ .el-checkbox) {
- display: none;
-}
-
-.layer-panel :deep(.el-tree-node__content:hover),
-.layer-panel :deep(.el-tree-node__content:focus) {
- background: transparent !important;
-}
-
-.layer-panel :deep(.el-tree-node.is-current > .el-tree-node__content) {
- background: transparent !important;
- color: #ffffff;
-}
-
-.layer-panel :deep(.el-tree-node.is-current > .el-tree-node__content .el-tree-node__label) {
- color: #ffffff;
-}
-
-.layer-panel :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
- background-color: #023aff;
- border-color: #023aff;
-}
-
-.layer-panel :deep(.el-checkbox__inner) {
- background-color: transparent;
- border-color: #a1a3d4;
}
</style>
--
Gitblit v1.9.3