src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx
@@ -33,6 +33,14 @@ const [isEvaluated, setIsEvaluated] = useState(false); // 是否已完成评价 const skipEvaluateResetRef = useRef(false); // 跳过关闭重置的一次性标记 // 订单切换时重置评价态:去掉 CallingCar 的 key 后本组件不再随状态重挂载, // 需显式按 orderId 复位,避免上一单「已评价」状态串到下一单。 useEffect(() => { setActiveIndex(0); setIsEvaluated(false); skipEvaluateResetRef.current = false; }, [orderId]); // 外部提交成功后同步评价状态 useEffect(() => { if (typeof evaluateSubmitVersion === 'number' && evaluateSubmitVersion > 0) { src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -200,11 +200,29 @@ const isTripFinished = tripStatus === TripStatus.TripFinished; const isCallingCar = tripStatus === TripStatus.CallingCar; /** * 「直达待评价」入场快照:与 useDispatchTransition 里的 shouldPlayEnter 同理, * 父层的 playEvaluateEnter 在下一次渲染即翻 false,直接绑定会把动画类中途卸掉、动效被扼杀, * 故在挂载时快照一次,组件生命周期内不变(本组件由 key={tripStatus} 保证每次状态切换都重挂载)。 * 「直达待评价」入场:与 useDispatchTransition 里的 shouldPlayEnter 同理, * 父层的 playEvaluateEnter 在下一次渲染即翻 false,直接绑定会把动画类中途卸掉、动效被扼杀。 * 过去依赖 key={tripStatus} 重挂载做挂载快照;去 key 后组件不再重挂载, * 改为在渲染期同步捕捉 playEvaluateEnter 上升沿置类(避免先渲染一帧静止态再跳到位移起点的闪跳), * 播放一次后按动画时长(0.7s)清除类名——最终态与静止态一致无跳变,且使下一次 6→9 能重新触发。 */ const [shouldPlayEvaluateEnter] = useState(playEvaluateEnter); const [shouldPlayEvaluateEnter, setShouldPlayEvaluateEnter] = useState(false); const prevPlayEvaluateRef = useRef(false); const evaluateEnterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); if (playEvaluateEnter && !prevPlayEvaluateRef.current) { prevPlayEvaluateRef.current = true; if (!shouldPlayEvaluateEnter) setShouldPlayEvaluateEnter(true); } else if (!playEvaluateEnter && prevPlayEvaluateRef.current) { prevPlayEvaluateRef.current = false; } useEffect(() => { if (!shouldPlayEvaluateEnter) return; if (evaluateEnterTimerRef.current) clearTimeout(evaluateEnterTimerRef.current); evaluateEnterTimerRef.current = setTimeout(() => setShouldPlayEvaluateEnter(false), 750); return () => { if (evaluateEnterTimerRef.current) clearTimeout(evaluateEnterTimerRef.current); }; }, [shouldPlayEvaluateEnter]); /** * 车控(空调 / 音乐 / 香氛):卡片与弹窗分开挂载。 * 卡片放进横向滑动的 ScrollView;弹窗是 fixed 全屏浮层,必须挂在 ScrollView 之外, src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -154,16 +154,10 @@ opacity: 0.4; } to { transform: translateY(0); // 透明度单独提前收口:0.4 → 1 用 0.2s 走完(0.7s 总时长的 28.57% 处), // 之后 transform 继续独立完成剩余位移,不再受 opacity 影响。 28.57% { opacity: 1; } } // 待评价态新增内容(费用明细卡 / 评价卡)分层浮现:轻微上移淡入,手法对齐 finished-distance-fade。 @keyframes calling-car-evaluate-content-fade { from { transform: translateY(16px); opacity: 0; } to { @@ -175,13 +169,6 @@ // 位移行程约 380px(大于派单入场的 170px),时长相应放到 0.7s,保持速度感与派单入场一致。 animation: calling-car-evaluate-rise 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) both; will-change: transform; // 分层:整卡升起过半后,新增的费用明细卡与评价卡再依次浮现,进一步掩盖高度差。 .fare-box-card { animation: calling-car-evaluate-content-fade 600ms ease-out 420ms both; } .car-card-evaluate { animation: calling-car-evaluate-content-fade 600ms ease-out 560ms both; } } // 派单成功转场期间:蓝牙卡 / 车控卡 / 底部操作栏隐藏但保留占位(opacity),逻辑另在各组件延迟执行; // 动效完成后移除 .dispatch-defer-content,随 opacity 过渡淡入。用 opacity 而非 visibility,保证淡入过渡生效。 src/robotaxi/pages/begin-route/hooks/use-dispatch-transition.ts
@@ -17,10 +17,21 @@ export function useDispatchTransition(playDispatchTransition: boolean) { const [dispatchPhase, setDispatchPhase] = useState<DispatchPhase>('idle'); const [transitionDone, setTransitionDone] = useState(false); // shouldPlayEnter 是挂载快照,在组件生命周期内不变——不能用"两帧 + transition": // 起始态在 weapp 可能未被提交导致过渡不触发,且 playDispatchTransition 在父层下一次渲染即翻 false, // 直接绑定会把 --enter 类中途卸掉、动画被扼杀。 const [shouldPlayEnter] = useState(playDispatchTransition); // shouldPlayEnter 控制转场期间的内容延迟、背景图门槛与整卡升起 keyframe。 // 过去依赖 CallingCar 的 key={tripStatus} 重挂载做「挂载快照」,去 key 后组件不再重挂载, // 改为在渲染期同步捕捉 playDispatchTransition 的上升沿(false→true)置真—— // 同步置真而非放到 effect,是为避免整卡升起前先渲染一帧静止态导致的位移闪跳; // 又不能直接绑定 playDispatchTransition(其在父层下一次渲染即翻 false,会把 keyframe 类中途卸掉扼杀动画), // 故用独立状态 latch,转场完成后回落 false,使同一实例内下一单再次进入派单成功时能重新触发。 const [shouldPlayEnter, setShouldPlayEnter] = useState(false); const prevPlayRef = useRef(false); // 渲染期同步捕捉上升沿并置类(React 官方「prop 变化时调整 state」模式,重渲染早于绘制,无中间帧) if (playDispatchTransition && !prevPlayRef.current) { prevPlayRef.current = true; if (!shouldPlayEnter) setShouldPlayEnter(true); } else if (!playDispatchTransition && prevPlayRef.current) { prevPlayRef.current = false; } const enterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const settleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); @@ -48,6 +59,9 @@ settleTimerRef.current = setTimeout(() => { setDispatchPhase('idle'); setTransitionDone(true); // 回落转场门槛:--enter keyframe 已在前 0.6s 播完(此时约 3.9s),清除类名不影响观感, // 且为同一实例内下一次上升沿重新触发 keyframe 留出空间(false→true 才会重播)。 setShouldPlayEnter(false); }, 600); }; @@ -88,15 +102,31 @@ scheduleShrinkCheck(currentTime); }; // shouldPlayEnter 置真(上升沿)后启动转场状态机;复位所有一次性标记,保证同一实例内可重复播放。 useEffect(() => { if (playDispatchTransition) { if (!shouldPlayEnter) return; if (enterTimerRef.current) clearTimeout(enterTimerRef.current); if (settleTimerRef.current) clearTimeout(settleTimerRef.current); if (fallbackTimerRef.current) clearTimeout(fallbackTimerRef.current); if (shrinkTimerRef.current) clearTimeout(shrinkTimerRef.current); settleTriggeredRef.current = false; shrinkTriggeredRef.current = false; lastTimeSyncRef.current = { currentTime: 0, wallClock: 0 }; videoDurationRef.current = 0; setTransitionDone(false); setDispatchPhase('idle'); enterTimerRef.current = setTimeout(() => { // 视频已在 32ms 内报错/结束(极端)时勿覆盖 settle 阶段 if (settleTriggeredRef.current) return; setDispatchPhase('video'); }, 32); fallbackTimerRef.current = setTimeout(() => enterSettle(), DISPATCH_FALLBACK_MS); } }, [shouldPlayEnter]); // 卸载时清理所有定时器 useEffect(() => { return () => { if (enterTimerRef.current) clearTimeout(enterTimerRef.current); if (settleTimerRef.current) clearTimeout(settleTimerRef.current); src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -1042,7 +1042,6 @@ ) { return ( <CallingCar key={deps.tripStatus} playDispatchTransition={playDispatchTransition} playEvaluateEnter={playEvaluateEnter} onDispatchTransitionSettled={deps.onDispatchTransitionSettled}