diff --git a/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useEmergencyDispatch.js b/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useEmergencyDispatch.js index 152b511..8a264a1 100644 --- a/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useEmergencyDispatch.js +++ b/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useEmergencyDispatch.js @@ -42,7 +42,7 @@ export function useEmergencyDispatch({ // 初始化路由相关 composables const { calculateRoutes, isCalculating } = useAmapRouting() const { selectByType } = useEmergencyRouteSelection() - const { drawMultipleRoutes, clearRoutes, getRoutesBounds } = useRouteVisualization() + const { drawMultipleRoutes, clearRoutes, clearRoute, getRoutesBounds } = useRouteVisualization() // Loading 状态 const showLoading = ref(false) @@ -164,8 +164,13 @@ export function useEmergencyDispatch({ hideEmergencyPointMarkers(viewer) startMultipleAnimationsWithRoutes(viewer, routes, { - speedMultiplier: 10, - disablePulse: true // 禁用脉冲效果 + speedMultiplier: 40, + disablePulse: true, // 禁用脉冲效果 + // 新增:实体到达时清除对应路线的回调 + onEntityComplete: (routeId) => { + console.log(`[useEmergencyDispatch] 实体到达目标,清除路线: ${routeId}`) + clearRoute(viewer, routeId) + } }) // 7. 相机飞向全景位置 diff --git a/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useEntityAnimation.js b/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useEntityAnimation.js index 235c6b7..f91ed0d 100644 --- a/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useEntityAnimation.js +++ b/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useEntityAnimation.js @@ -176,17 +176,17 @@ export function useEntityAnimation() { ) }, // 添加发光轨迹线 - path: { - resolution: 1, - material: new Cesium.PolylineGlowMaterialProperty({ - glowPower: 0.4, // 发光强度 - taperPower: 0.5, // 渐变效果 - color: Cesium.Color.CYAN // 青色轨迹 - }), - width: 8, - leadTime: 0, // 前方不显示轨迹 - trailTime: config.duration // 后方显示完整轨迹 - }, + // path: { + // resolution: 1, + // material: new Cesium.PolylineGlowMaterialProperty({ + // glowPower: 0.4, // 发光强度 + // taperPower: 0.5, // 渐变效果 + // color: Cesium.Color.CYAN // 青色轨迹 + // }), + // width: 8, + // leadTime: 0, // 前方不显示轨迹 + // trailTime: config.duration // 后方显示完整轨迹 + // }, properties: { type: 'animatedSoldier', name: config.personnelName, @@ -342,17 +342,17 @@ export function useEntityAnimation() { disableDepthTestDistance: Number.POSITIVE_INFINITY, scaleByDistance: new Cesium.NearFarScalar(1000, 1.5, 50000, 0.8) }, - path: { - resolution: 1, - material: new Cesium.PolylineGlowMaterialProperty({ - glowPower: 0.4, - taperPower: 0.5, - color: trailColor - }), - width: 8, - leadTime: 0, - trailTime: config.duration - }, + // path: { + // resolution: 1, + // material: new Cesium.PolylineGlowMaterialProperty({ + // glowPower: 0.4, + // taperPower: 0.5, + // color: trailColor + // }), + // width: 8, + // leadTime: 0, + // trailTime: config.duration + // }, properties: { type: config.type === 'device' ? 'animatedDevice' : 'animatedSoldier', name: config.name, @@ -483,6 +483,11 @@ export function useEntityAnimation() { // 创建位置属性 const positionProperty = new Cesium.SampledPositionProperty() + + // 关键修复:设置外推类型,确保在采样范围外保持最后位置 + positionProperty.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD + positionProperty.backwardExtrapolationType = Cesium.ExtrapolationType.HOLD + const timeInterval = animationDuration / (positions.length - 1) positions.forEach((position, index) => { @@ -525,9 +530,6 @@ export function useEntityAnimation() { // 创建动画实体 return viewer.entities.add({ - availability: new Cesium.TimeIntervalCollection([ - new Cesium.TimeInterval({ start: startTime, stop: stopTime }) - ]), position: positionProperty, orientation: new Cesium.VelocityOrientationProperty(positionProperty), billboard: { @@ -540,22 +542,23 @@ export function useEntityAnimation() { disableDepthTestDistance: Number.POSITIVE_INFINITY, scaleByDistance: new Cesium.NearFarScalar(1000, 1.5, 50000, 0.8) }, - path: { - resolution: 1, - material: new Cesium.PolylineGlowMaterialProperty({ - glowPower: 0.4, - taperPower: 0.5, - color: trailColor - }), - width: 8, - leadTime: 0, - trailTime: animationDuration - }, + // path: { + // resolution: 1, + // material: new Cesium.PolylineGlowMaterialProperty({ + // glowPower: 0.4, + // taperPower: 0.5, + // color: trailColor + // }), + // width: 8, + // leadTime: 0, + // trailTime: animationDuration + // }, properties: { type: route.type === 'equipment' ? 'animatedDevice' : 'animatedSoldier', name: route.name, routeId: route.id, isAnimating: true, + isCompleted: false, // 新增:标记动画是否完成 scaleState: scaleState, // 新增:保存状态引用 animationDuration: animationDuration, // 新增:保存动画时长 animationStartTime: startTime.clone() // 新增:保存开始时间 @@ -576,10 +579,13 @@ export function useEntityAnimation() { return [] } - console.log(`[useEntityAnimation] 启动 ${routes.length} 条路线的动画 (disablePulse: ${options.disablePulse || false})`) + // 解构回调参数 + const { speedMultiplier: baseSpeedMultiplier, disablePulse, onEntityComplete } = options + + console.log(`[useEntityAnimation] 启动 ${routes.length} 条路线的动画 (disablePulse: ${disablePulse || false})`) // 计算最大动画时长 - const speedMultiplier = options.speedMultiplier || 10 + const speedMultiplier = baseSpeedMultiplier || 10 const durations = routes.map(r => Math.min(Math.max(r.duration / speedMultiplier, 30), 120) ) @@ -597,7 +603,7 @@ export function useEntityAnimation() { viewer.clock.startTime = startTime.clone() viewer.clock.stopTime = stopTime.clone() viewer.clock.currentTime = startTime.clone() - viewer.clock.clockRange = Cesium.ClockRange.CLAMPED // 动画到达终点后停止,不循环 + viewer.clock.clockRange = Cesium.ClockRange.UNBOUNDED // 允许时间继续前进,支持不同时长动画独立完成 viewer.clock.multiplier = 1 viewer.clock.shouldAnimate = true @@ -627,7 +633,15 @@ export function useEntityAnimation() { // 监听时钟,检测动画完成 const clockListener = viewer.clock.onTick.addEventListener((clock) => { - checkAnimationsCompletion(viewer, clock.currentTime) + checkAnimationsCompletion(viewer, clock.currentTime, onEntityComplete) + + // 注释掉自动停止时钟的逻辑,让时钟继续运行 + // 原因:时钟停止会导致 Cesium 停止更新实体位置,导致前面完成的实体消失 + // if (areAllAnimationsCompleted()) { + // console.log('[useEntityAnimation] 所有动画已完成,停止时钟') + // viewer.clock.shouldAnimate = false + // clockListener() // 移除监听器 + // } }) // 监听动画结束 @@ -644,31 +658,53 @@ export function useEntityAnimation() { } /** - * 检查动画是否完成,如果完成则停止脉冲 + * 检查动画是否完成,如果完成则停止脉冲并清除路线 * @param {Cesium.Viewer} viewer * @param {Cesium.JulianDate} currentTime + * @param {Function} onEntityComplete - 完成回调 (routeId) => void */ - const checkAnimationsCompletion = (viewer, currentTime) => { + const checkAnimationsCompletion = (viewer, currentTime, onEntityComplete) => { animatedEntities.value.forEach(entity => { if (!entity || !entity.properties) return const scaleState = entity.properties.scaleState?.getValue() const startTime = entity.properties.animationStartTime?.getValue() const duration = entity.properties.animationDuration?.getValue() + const isCompleted = entity.properties.isCompleted?.getValue() + const routeId = entity.properties.routeId?.getValue() - if (!scaleState || !startTime || !duration) return + if (!scaleState || !startTime || !duration || !routeId) return - // 如果已经停止脉冲,跳过 - if (!scaleState.isPulsing) return + // 如果已经完成,跳过(避免重复触发) + if (isCompleted) return // 计算已过时间 const elapsed = Cesium.JulianDate.secondsDifference(currentTime, startTime) // 如果动画已完成(留0.5秒容差) if (elapsed >= duration - 0.5) { - console.log(`[useEntityAnimation] 实体 ${entity.properties.name.getValue()} 动画完成,停止脉冲`) + const entityName = entity.properties.name?.getValue() || '未知实体' + console.log(`[useEntityAnimation] ========================================`) + console.log(`[useEntityAnimation] 实体 "${entityName}" 动画完成`) + console.log(`[useEntityAnimation] - routeId: ${routeId}`) + console.log(`[useEntityAnimation] - elapsed: ${elapsed.toFixed(2)}秒`) + console.log(`[useEntityAnimation] - duration: ${duration}秒`) + console.log(`[useEntityAnimation] ========================================`) + + // 1. 停止脉冲效果 scaleState.isPulsing = false - scaleState.fixedScale = 1.0 // 设置为正常大小 + scaleState.fixedScale = 1.0 + + // 2. 标记为已完成(使用 ConstantProperty) + entity.properties.isCompleted = new Cesium.ConstantProperty(true) + + // 3. 触发回调清除对应路线 + if (onEntityComplete && typeof onEntityComplete === 'function') { + console.log(`[useEntityAnimation] 触发 onEntityComplete 回调,routeId: ${routeId}`) + onEntityComplete(routeId) + } else { + console.warn(`[useEntityAnimation] onEntityComplete 回调不存在或不是函数`) + } } }) } @@ -693,6 +729,19 @@ export function useEntityAnimation() { console.log('[useEntityAnimation] 已停止所有脉冲效果') } + /** + * 检查所有动画是否都已完成 + * @returns {boolean} + */ + const areAllAnimationsCompleted = () => { + if (animatedEntities.value.length === 0) return false + + return animatedEntities.value.every(entity => { + const isCompleted = entity.properties?.isCompleted?.getValue() + return isCompleted === true + }) + } + return { animatedEntity, animatedEntities, diff --git a/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useRouteVisualization.js b/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useRouteVisualization.js index 7b50c4c..6979d91 100644 --- a/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useRouteVisualization.js +++ b/packages/screen/src/views/3DSituationalAwarenessRefactor/composables/useRouteVisualization.js @@ -65,7 +65,7 @@ export function useRouteVisualization() { polyline: { positions: positions, width: styleOptions.width || style.width, - material: styleOptions.material || style.material(viewer), + material: styleOptions.material || (typeof style.material === 'function' ? style.material(viewer) : style.material), clampToGround: true, classificationType: Cesium.ClassificationType.TERRAIN }, @@ -82,7 +82,7 @@ export function useRouteVisualization() { routeEntities.value.push(entity) - console.log(`[useRouteVisualization] 绘制路线: ${routeData.name} (${type})`) + console.log(`[useRouteVisualization] 绘制路线: ${routeData.name} (${type}), routeId: ${routeData.id}`) return entity } @@ -224,27 +224,52 @@ export function useRouteVisualization() { * @param {string} routeId - 路线 ID */ const clearRoute = (viewer, routeId) => { - if (!viewer || !routeId) return + if (!viewer || !routeId) { + console.warn(`[useRouteVisualization] clearRoute 参数无效: viewer=${!!viewer}, routeId=${routeId}`) + return + } + + console.log(`[useRouteVisualization] 开始清除路线: ${routeId}`) + console.log(`[useRouteVisualization] 当前路线实体数量: ${routeEntities.value.length}`) + console.log(`[useRouteVisualization] 当前标签实体数量: ${labelEntities.value.length}`) + + let removedRoutes = 0 + let removedLabels = 0 // 清除路线实体 routeEntities.value = routeEntities.value.filter(entity => { - if (entity && entity.properties && entity.properties.routeId === routeId) { - viewer.entities.remove(entity) - return false + if (entity && entity.properties) { + // 关键修复:使用 getValue() 获取 Cesium Property 的实际值 + const entityRouteId = entity.properties.routeId?.getValue ? entity.properties.routeId.getValue() : entity.properties.routeId + console.log(`[useRouteVisualization] 检查路线实体 routeId: "${entityRouteId}", 目标: "${routeId}", 匹配: ${entityRouteId === routeId}`) + + if (entityRouteId === routeId) { + console.log(`[useRouteVisualization] ✅ 匹配成功,移除路线实体: ${routeId}`) + viewer.entities.remove(entity) + removedRoutes++ + return false + } } return true }) // 清除标签实体 labelEntities.value = labelEntities.value.filter(entity => { - if (entity && entity.properties && entity.properties.routeId === routeId) { - viewer.entities.remove(entity) - return false + if (entity && entity.properties) { + // 关键修复:使用 getValue() 获取 Cesium Property 的实际值 + const entityRouteId = entity.properties.routeId?.getValue ? entity.properties.routeId.getValue() : entity.properties.routeId + + if (entityRouteId === routeId) { + console.log(`[useRouteVisualization] 移除标签实体: ${routeId}`) + viewer.entities.remove(entity) + removedLabels++ + return false + } } return true }) - console.log(`[useRouteVisualization] 已清除路线: ${routeId}`) + console.log(`[useRouteVisualization] 已清除路线: ${routeId}, 移除了 ${removedRoutes} 条路线和 ${removedLabels} 个标签`) } /** diff --git a/packages/screen/src/views/3DSituationalAwarenessRefactor/constants/routeStyles.js b/packages/screen/src/views/3DSituationalAwarenessRefactor/constants/routeStyles.js index 1a2a33b..450e495 100644 --- a/packages/screen/src/views/3DSituationalAwarenessRefactor/constants/routeStyles.js +++ b/packages/screen/src/views/3DSituationalAwarenessRefactor/constants/routeStyles.js @@ -11,25 +11,18 @@ import * as Cesium from 'cesium' export const ROUTE_STYLES = { // 人员路线样式 personnel: { - color: Cesium.Color.CYAN, + color: Cesium.Color.RED, width: 5, - material: (viewer) => new Cesium.PolylineGlowMaterialProperty({ - glowPower: 0.3, - color: Cesium.Color.CYAN - }), - labelColor: Cesium.Color.CYAN + material: Cesium.Color.RED, + labelColor: Cesium.Color.RED }, // 装备路线样式 equipment: { - color: Cesium.Color.ORANGE, + color: Cesium.Color.RED, width: 6, - material: (viewer) => new Cesium.PolylineDashMaterialProperty({ - color: Cesium.Color.ORANGE, - dashLength: 20.0, - dashPattern: 255 - }), - labelColor: Cesium.Color.ORANGE + material: Cesium.Color.RED, + labelColor: Cesium.Color.RED }, // 降级直线样式(API 失败时使用) @@ -65,7 +58,7 @@ export const LABEL_STYLE = { */ export const ROUTE_ANIMATION_CONFIG = { // 动画速度倍数(相对于真实时间) - speedMultiplier: 10, + speedMultiplier: 40, // 最小动画时长(秒) minDuration: 30,