diff --git a/packages/screen/src/views/3DSituationalAwarenessRefactor/components/LeftPanel/ForceDispatch.vue b/packages/screen/src/views/3DSituationalAwarenessRefactor/components/LeftPanel/ForceDispatch.vue
index 7c981da..4b14f8d 100644
--- a/packages/screen/src/views/3DSituationalAwarenessRefactor/components/LeftPanel/ForceDispatch.vue
+++ b/packages/screen/src/views/3DSituationalAwarenessRefactor/components/LeftPanel/ForceDispatch.vue
@@ -26,7 +26,7 @@
应急基地与预置点
- {{ dispatchSuggestion.stations }}
+ {{ dispatchSuggestion.bases }}
个
diff --git a/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useDisasterData.js b/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useDisasterData.js
index b1bdf01..7c6c8cb 100644
--- a/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useDisasterData.js
+++ b/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useDisasterData.js
@@ -98,16 +98,19 @@ export function useDisasterData() {
const stationsCount = forcePreset.value.stations?.length || 0
return {
- // 应急物资建议数:基于装备数量
- supplies: forcePreset.value.equipment,
+ // 应急物资建议数
+ supplies: forcePreset.value.materials,
- // 应急人员建议数:取总人员的一部分(约5-10%)作为调度建议
- personnel: Math.min(Math.ceil(forcePreset.value.personnel * 0.06), forcePreset.value.personnel),
+ // 应急人员建议数
+ personnel: forcePreset.value.personnel,
// 养护站建议数:使用实际可用养护站数量
stations: stationsCount,
- // 挖掘机建议数:每2个养护站配置1台挖掘机
+ // 应急基地
+ bases: forcePreset.value.bases,
+
+ // 挖掘机建议数
excavators: Math.max(1, Math.ceil(stationsCount / 2)),
// 阻断信息:固定建议
@@ -140,36 +143,167 @@ export function useDisasterData() {
}
}
+ /**
+ * 将储备中心/预置点接口返回的数据转换为通用 stations 结构
+ * @param {Array} reserveData - /yhYjll/list 接口数据
+ * @param {Object} disasterCenter - 灾害中心点坐标 { longitude, latitude }
+ * @returns {Array} 标准化的 stations 数组
+ */
+ const transformReserveDataToStations = (reserveData = [], disasterCenter = {}) => {
+ if (!Array.isArray(reserveData)) {
+ console.warn('[useDisasterData] transformReserveDataToStations: 输入数据不是数组')
+ return []
+ }
+
+ if (reserveData.length === 0) {
+ console.warn('[useDisasterData] transformReserveDataToStations: 输入数据为空数组')
+ return []
+ }
+
+ console.log(`[useDisasterData] 开始转换 ${reserveData.length} 条储备中心/预置点数据`)
+
+ return reserveData.map((item, index) => {
+ // 1. 提取并验证类型
+ const typeCode = String(item?.gl1Lx ?? '').trim()
+ if (!typeCode) {
+ console.warn(`[useDisasterData] 数据项 ${index} 缺少 gl1Lx 字段:`, item)
+ }
+ const isReserveCenter = typeCode === '2'
+ const type = isReserveCenter ? 'reserveCenter' : 'presetPoint'
+
+ // 2. 提取经纬度
+ const longitude = typeof item?.gl1Lng === 'number' ? item.gl1Lng : null
+ const latitude = typeof item?.gl1Lat === 'number' ? item.gl1Lat : null
+
+ // 3. 计算距离(如果接口未提供)
+ let distance = null
+ if (
+ longitude !== null &&
+ latitude !== null &&
+ typeof disasterCenter?.longitude === 'number' &&
+ typeof disasterCenter?.latitude === 'number'
+ ) {
+ distance = Number(
+ calculateDistance(
+ { longitude, latitude },
+ { longitude: disasterCenter.longitude, latitude: disasterCenter.latitude }
+ ).toFixed(2)
+ )
+ }
+
+ // 4. 构造标准格式
+ const station = {
+ id: item?.gl1Id || `reserve-${index}`,
+ name: item?.gl1Yjllmc?.trim() || (isReserveCenter ? '储备中心' : '预置点'),
+ distance,
+ type,
+ longitude,
+ latitude,
+ district: item?.gl1Qxmc || '-',
+ personnelCount: Number(item?.gl1Rysl) || 0,
+ area: item?.gl1Zdmj || '-'
+ }
+
+ console.log(`[useDisasterData] 转换成功: ${station.name} (${type}, ${distance}km)`)
+ return station
+ })
+ }
+
+ /**
+ * 计算两个经纬度点之间的距离(公里)
+ * 使用 Haversine 公式
+ * @param {Object} pointA - { longitude, latitude }
+ * @param {Object} pointB - { longitude, latitude }
+ * @returns {number} 距离(km)
+ */
+ const EARTH_RADIUS_KM = 6371
+ const calculateDistance = (pointA, pointB) => {
+ if (
+ typeof pointA?.longitude !== 'number' ||
+ typeof pointA?.latitude !== 'number' ||
+ typeof pointB?.longitude !== 'number' ||
+ typeof pointB?.latitude !== 'number'
+ ) {
+ console.warn('[useDisasterData] calculateDistance: 坐标参数无效')
+ return 0
+ }
+
+ const lat1 = toRadians(pointA.latitude)
+ const lat2 = toRadians(pointB.latitude)
+ const deltaLat = toRadians(pointB.latitude - pointA.latitude)
+ const deltaLon = toRadians(pointB.longitude - pointA.longitude)
+
+ const a =
+ Math.sin(deltaLat / 2) ** 2 +
+ Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) ** 2
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
+
+ return EARTH_RADIUS_KM * c
+ }
+
+ /**
+ * 角度转弧度
+ * @param {number} degrees - 角度值
+ * @returns {number} 弧度值
+ */
+ const toRadians = (degrees) => (degrees * Math.PI) / 180
+
/**
* 更新力量预置数据
* @param {Object} emergencyResourcesData - 接口返回的应急资源数据
* @param {number} emergencyResourcesData.equipmentCount - 应急装备数量
* @param {number} emergencyResourcesData.personnelCount - 应急人员数量
+ * @param {number} emergencyResourcesData.materialCount - 应急物资数量
+ * @param {number} emergencyResourcesData.baseCount - 应急基地数量
* @param {Array} emergencyResourcesData.stations - 养护站列表
* @param {string} emergencyResourcesData.stations[].stationId - 养护站ID
* @param {string} emergencyResourcesData.stations[].stationName - 养护站名称
* @param {number} emergencyResourcesData.stations[].distance - 距离(km)
+ * @param {Object} options - 可选配置
+ * @param {boolean} options.onlyStatistics - 是否仅更新统计数据(不更新stations)
*/
- const updateForcePreset = (emergencyResourcesData) => {
+ const updateForcePreset = (emergencyResourcesData, options = {}) => {
if (!emergencyResourcesData) {
console.warn('[useDisasterData] 应急资源数据为空,跳过更新')
return
}
- const { equipmentCount, personnelCount, stations } = emergencyResourcesData
+ const { onlyStatistics = false } = options
+ const {
+ equipmentCount,
+ personnelCount,
+ materialCount,
+ baseCount,
+ stations
+ } = emergencyResourcesData
- // 更新装备和人员数量
+ // 更新装备数量
if (typeof equipmentCount === 'number') {
forcePreset.value.equipment = equipmentCount
}
+
+ // 更新人员数量
if (typeof personnelCount === 'number') {
forcePreset.value.personnel = personnelCount
}
- // 更新养护站列表
- if (Array.isArray(stations)) {
- // 根据养护站列表更新应急基地数
- forcePreset.value.bases = stations.length
+ // 更新物资数量 (新增)
+ if (typeof materialCount === 'number') {
+ forcePreset.value.materials = materialCount
+ }
+
+ // 更新基地数量 (新增)
+ if (typeof baseCount === 'number') {
+ forcePreset.value.bases = baseCount
+ }
+
+ // 仅当不是"仅统计"模式时才更新养护站列表
+ if (!onlyStatistics && Array.isArray(stations)) {
+ // 如果baseCount未提供,则从stations长度推导
+ if (typeof baseCount !== 'number') {
+ forcePreset.value.bases = stations.length
+ }
+
forcePreset.value.stations = stations.map((station) => ({
id: station.stationId,
name: station.stationName?.trim() || '未命名养护站',
@@ -191,6 +325,7 @@ export function useDisasterData() {
dispatchSuggestion,
totalResources,
updateForcePreset,
- updateSearchRadius
+ updateSearchRadius,
+ transformReserveDataToStations
}
}
diff --git a/packages/screen/src/views/3DSituationalAwarenessRefactor/index.vue b/packages/screen/src/views/3DSituationalAwarenessRefactor/index.vue
index 6a14710..476b96b 100644
--- a/packages/screen/src/views/3DSituationalAwarenessRefactor/index.vue
+++ b/packages/screen/src/views/3DSituationalAwarenessRefactor/index.vue
@@ -327,6 +327,8 @@ const {
showMarkers,
hideMarkers,
markerEntities,
+ reserveCenterEntities,
+ emergencyResourceEntities,
} = useMapMarkers()
// 3D Tiles 加载
@@ -682,19 +684,22 @@ const handleMapToolChange = async ({ tool, active }) => {
const handleDistanceChange = async (newDistance) => {
console.log(`[index.vue] 距离范围改变为: ${newDistance}km`)
- // 更新搜索半径
+ // 1. 更新搜索半径
disasterData.updateSearchRadius(newDistance)
- // 更新范围圈
+ // 2. 更新范围圈
if (mapStore.viewer) {
createOrUpdateRangeCircle(mapStore.viewer, newDistance)
}
- // 重新加载应急资源数据并更新地图标记
- await loadEmergencyResources(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
+ // 3. 重新加载应急资源数据并更新地图标记
+ // await loadEmergencyResources(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
- // 重新加载储备中心和预置点数据并更新地图标记
+ // 4. 重新加载储备中心和预置点数据并更新地图标记
await loadReserveCentersAndPresets(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
+
+ // 5. 重新加载应急统计数据 (新增)
+ await loadEmergencyBaseAndPreset(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
}
/**
@@ -704,53 +709,154 @@ const showAllMarkersAndRange = () => {
const viewer = mapStore.viewer
if (!viewer) return
- // 1. 显示模拟点位
+ console.log('[index.vue] 开始显示快速响应标记...')
+
+ // 1. 清除/隐藏其他所有标记
+ // 1.1 隐藏模拟点位(soldier/device)和路径起点标记
viewer.entities.values.forEach((entity) => {
if (entity.properties) {
const props = entity.properties
- if (props.type?.getValue() === 'soldier' ||
- props.type?.getValue() === 'device' ||
- props.isPathStartMarker?.getValue()) {
- entity.show = true
- }
- }
- })
-
- // 2. 显示接口标记
- showMarkers()
-
- // 3. 显示范围圈
- showRangeCircle()
-
- console.log('[index.vue] 已显示所有标记点和范围圈')
-}
-
-/**
- * 隐藏所有标记点和范围圈
- */
-const hideAllMarkersAndRange = () => {
- const viewer = mapStore.viewer
- if (!viewer) return
-
- // 1. 隐藏模拟点位
- viewer.entities.values.forEach((entity) => {
- if (entity.properties) {
- const props = entity.properties
- if (props.type?.getValue() === 'soldier' ||
- props.type?.getValue() === 'device' ||
+ const type = props.type?.getValue()
+ if (type === 'soldier' ||
+ type === 'device' ||
props.isPathStartMarker?.getValue()) {
entity.show = false
}
}
})
- // 2. 隐藏接口标记
+ // 1.2 隐藏应急资源标记(养护站等)
hideMarkers()
+ console.log('[index.vue] 已清除其他标记')
+
+ // 2. 显示储备中心/预置点标记
+ reserveCenterEntities.value.forEach((entity) => {
+ if (entity) {
+ entity.show = true
+ }
+ })
+ console.log(`[index.vue] 显示 ${reserveCenterEntities.value.length} 个储备中心/预置点标记`)
+
+ // 3. 在灾害中心点附近添加人员和装备点位
+ const offset = 0.0002 // 约22米偏移,更明显
+
+ // 检查是否已存在中心点人员和装备标记,避免重复添加
+ let existingCenterPersonnel = viewer.entities.values.find(
+ e => e.properties && e.properties.type && e.properties.type.getValue() === 'centerPersonnel'
+ )
+ let existingCenterEquipment = viewer.entities.values.find(
+ e => e.properties && e.properties.type && e.properties.type.getValue() === 'centerEquipment'
+ )
+
+ // 添加人员点位(如果不存在)
+ if (!existingCenterPersonnel) {
+ const personnelEntity = viewer.entities.add({
+ position: Cesium.Cartesian3.fromDegrees(DISASTER_CENTER.lon + offset, DISASTER_CENTER.lat, 0),
+ billboard: {
+ image: soldierIcon,
+ width: 36,
+ height: 40,
+ verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY
+ },
+ properties: new Cesium.PropertyBag({
+ type: 'centerPersonnel',
+ name: '应急人员'
+ }),
+ show: true
+ })
+ console.log('[index.vue] 已添加中心点人员标记:', personnelEntity.id)
+ } else {
+ existingCenterPersonnel.show = true
+ console.log('[index.vue] 显示已存在的中心点人员标记')
+ }
+
+ // 添加装备点位(如果不存在)
+ if (!existingCenterEquipment) {
+ const equipmentEntity = viewer.entities.add({
+ position: Cesium.Cartesian3.fromDegrees(DISASTER_CENTER.lon - offset, DISASTER_CENTER.lat, 0),
+ billboard: {
+ image: deviceIcon,
+ width: 36,
+ height: 40,
+ verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY
+ },
+ properties: new Cesium.PropertyBag({
+ type: 'centerEquipment',
+ name: '应急装备'
+ }),
+ show: true
+ })
+ console.log('[index.vue] 已添加中心点装备标记:', equipmentEntity.id)
+ } else {
+ existingCenterEquipment.show = true
+ console.log('[index.vue] 显示已存在的中心点装备标记')
+ }
+
+ // 强制渲染场景
+ viewer.scene.requestRender()
+
+ // 4. 显示范围圈
+ showRangeCircle()
+
+ console.log('[index.vue] 快速响应标记显示完成')
+}
+
+/**
+ * 隐藏所有标记点和范围圈
+ * 恢复到初始状态(隐藏快速响应相关的标记)
+ */
+const hideAllMarkersAndRange = () => {
+ const viewer = mapStore.viewer
+ if (!viewer) return
+
+ console.log('[index.vue] 开始隐藏快速响应标记...')
+
+ // 1. 隐藏储备中心/预置点标记
+ reserveCenterEntities.value.forEach((entity) => {
+ if (entity) {
+ entity.show = false
+ }
+ })
+ console.log(`[index.vue] 隐藏 ${reserveCenterEntities.value.length} 个储备中心/预置点标记`)
+
+ // 2. 隐藏中心点人员和装备标记
+ viewer.entities.values.forEach((entity) => {
+ if (entity.properties?.type) {
+ const type = entity.properties.type.getValue()
+ if (type === 'centerPersonnel' || type === 'centerEquipment') {
+ entity.show = false
+ }
+ }
+ })
+ console.log('[index.vue] 隐藏中心点人员和装备标记')
+
// 3. 隐藏范围圈
hideRangeCircle()
- console.log('[index.vue] 已隐藏所有标记点和范围圈')
+ // 4. 恢复显示模拟点位(如果需要的话,这里暂时不恢复,保持隐藏状态)
+ // 如果需要恢复显示,取消下面的注释
+ // viewer.entities.values.forEach((entity) => {
+ // if (entity.properties) {
+ // const props = entity.properties
+ // const type = props.type?.getValue()
+ // if (type === 'soldier' ||
+ // type === 'device' ||
+ // props.isPathStartMarker?.getValue()) {
+ // entity.show = true
+ // }
+ // }
+ // })
+
+ // 5. 恢复显示应急资源标记(如果需要的话,这里暂时不恢复)
+ // 如果需要恢复显示,取消下面的注释
+ // showMarkers()
+
+ console.log('[index.vue] 快速响应标记隐藏完成')
}
/**
@@ -857,6 +963,19 @@ const loadReserveCentersAndPresets = async (longitude, latitude) => {
if (response?.data && Array.isArray(response.data)) {
console.log('[index.vue] 储备中心和预置点数据加载成功:', response.data)
+ // 1. 转换数据为标准 stations 格式并更新到 forcePreset
+ const transformedStations = disasterData.transformReserveDataToStations(
+ response.data,
+ { longitude, latitude }
+ )
+
+ if (transformedStations.length > 0) {
+ // 更新 forcePreset.stations (用于 station-list 显示)
+ disasterData.forcePreset.value.stations = transformedStations
+ console.log('[index.vue] 已更新 forcePreset.stations:', transformedStations)
+ }
+
+ // 2. 添加地图标记
if (mapStore.viewer) {
console.log('[index.vue] 添加储备中心和预置点地图标记...')
clearReserveCenterMarkers(mapStore.viewer)
@@ -880,9 +999,10 @@ const loadReserveCentersAndPresets = async (longitude, latitude) => {
}
/**
- * 统计应急基地与预置点、应急装备、应急物资、应急人员的数量 /snow-ops-platform/yhYjll/statistics
+ * 加载应急基地与预置点、应急装备、应急物资、应急人员的统计数量
+ * /snow-ops-platform/yhYjll/statistics
*/
-const loadEmergencyBaseAndPreset = async (longitude, latitude, maxDistance) => {
+const loadEmergencyBaseAndPreset = async (longitude, latitude) => {
try {
const response = await request({
url: `/snow-ops-platform/yhYjll/statistics`,
@@ -893,16 +1013,24 @@ const loadEmergencyBaseAndPreset = async (longitude, latitude, maxDistance) => {
maxDistance: disasterData.forcePreset.value.searchRadius,
},
})
+
if (response?.data) {
- console.log('[index.vue] 应急基地与预置点、应急装备、应急物资、应急人员的数量加载成功:', response.data)
+ // 保存统计数据到状态 (仅更新统计,不更新stations)
+ disasterData.updateForcePreset(response.data, { onlyStatistics: true })
+ console.log('[index.vue] 应急统计数据加载成功:', response.data)
} else {
- console.warn('[index.vue] 应急基地与预置点、应急装备、应急物资、应急人员的数量接口返回数据为空')
+ console.warn('[index.vue] 应急统计数据接口返回数据为空')
}
+
return response
} catch (error) {
- console.error('[index.vue] 加载应急基地与预置点、应急装备、应急物资、应急人员的数量失败:', error)
+ console.error('[index.vue] 加载应急统计数据失败:', error)
+ ElMessage.warning({
+ message: '应急统计数据加载失败',
+ duration: 3000,
+ })
+ return null
}
- return null
}
/**
@@ -934,6 +1062,13 @@ const loadEmergencyEquipmentAndMaterial = async (longitude, latitude) => {
return null
}
}
+loadEmergencyEquipmentAndMaterial(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
+ .then((response) => {
+ console.log('[index.vue] 应急装备和应急物资列表加载成功:', response.data)
+ })
+ .catch((error) => {
+ console.error('[index.vue] 查询应急装备和应急物资列表失败:', error)
+ })
// ====================
// 场景初始化函数
@@ -1111,7 +1246,7 @@ const initializeScene = async () => {
// 11. 加载应急资源数据(在地形就绪后)
console.log('[index.vue] 加载应急资源数据...')
- await loadEmergencyResources(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
+ // await loadEmergencyResources(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
// 默认隐藏接口标记(等待快速匹配激活)
hideMarkers()
@@ -1121,6 +1256,10 @@ const initializeScene = async () => {
console.log('[index.vue] 加载储备中心和预置点数据...')
await loadReserveCentersAndPresets(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
+ // 13. 加载应急统计数据
+ console.log('[index.vue] 加载应急统计数据...')
+ await loadEmergencyBaseAndPreset(DISASTER_CENTER.lon, DISASTER_CENTER.lat)
+
console.log('[index.vue] 场景初始化完成')
}