| src/robotaxi/pages/share-trip/hooks/use-map-markers.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/share-trip/hooks/use-vehicle-tracking.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -105,59 +105,16 @@ export default function useMapMarkers(rawDeps?: Partial<Deps>) { const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps; // 内部"逻辑 markers"——真相态,所有内部判断(签名、是否含车标等)以此为准 // 注意:已废弃早期的 "Rebuild Dance" (两阶段移除恢复以派生新 ID 的做法) // 原因是:如果在 translateMarker 动画执行期间,直接从声明式 markers 中删除该 ID, // 原生层可能会因为找不到对应节点而无法正确销毁,导致地图上出现车标的"虚影"。 // 现在的做法是:保持车标 ID 永远不变(保证 React Diff 映射稳定), // 遇到需要重置位置或旋转矩阵时,交由 use-vehicle-tracking 通过发送极短时间的 translateMarker 动画来强行覆盖原生状态。 const [markers, setMarkers] = useState<any[]>([]); // 对外"展示 markers"——返回给页面,通过 rebuild dance 制造短暂"无车标"窗口以重建原生 marker const [displayMarkers, setDisplayMarkers] = useState<any[]>([]); const markersRef = useRef<any[]>([]); const markersSignatureRef = useRef<string>(""); const carMarkerInitialized = useRef(false); const tripStatus6MarkerRefreshedRef = useRef(false); const rebuildCarMarkerTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); // 每次 rebuild 递增,用于派生新的 car marker id,避免原生层因 id 相同跳过销毁/重建导致旋转矩阵残留 const carMarkerEpochRef = useRef(0); /** * 派生新的 car marker id:base + ((epoch % 1000) + 1) * 1000 * - 与业务 base id(3/30)和其他 marker(pickup=1, dropoff=2)不冲突 * - epoch 取模 1000 防止无限增长 */ const deriveNewCarMarkerId = (baseId: number) => { return baseId + ((carMarkerEpochRef.current % 1000) + 1) * 1000; }; /** * 当逻辑 markers 变化时,对展示 markers 做"先移除车标 → 100ms 后用新 id 恢复"的两阶段提交。 * 关键:恢复时给车标分配一个全新 markerId(base + epoch 派生),强制原生层创建新 marker 实例, * 不会因 id 相同被原生层复用,translateMarker 残留的旋转矩阵随之清除。 */ useEffect(() => { if (rebuildCarMarkerTimerRef.current) { clearTimeout(rebuildCarMarkerTimerRef.current); rebuildCarMarkerTimerRef.current = null; } const hasCarMarker = markers.some((m: any) => m.id === deps.vehicleMarkerId); if (!hasCarMarker) { setDisplayMarkers(markers); return; } carMarkerEpochRef.current += 1; const newCarId = deriveNewCarMarkerId(deps.vehicleMarkerId); // 通知外部:CarMarker 即将以 newCarId 重建,暂停 translateMarker 并切换到新 id deps.onCarMarkerWillRebuild(newCarId); // 第一步:展示层先去掉车标(按业务 base id 过滤),原生层销毁旧 car marker setDisplayMarkers(markers.filter((m: any) => m.id !== deps.vehicleMarkerId)); // 第二步:100ms 后用 newCarId 重新挂载车标,原生层创建全新 marker 实例 rebuildCarMarkerTimerRef.current = setTimeout(() => { rebuildCarMarkerTimerRef.current = null; setDisplayMarkers(markers.map((m: any) => m.id === deps.vehicleMarkerId ? { ...m, id: newCarId } : m)); }, 100); return () => { if (rebuildCarMarkerTimerRef.current) { clearTimeout(rebuildCarMarkerTimerRef.current); rebuildCarMarkerTimerRef.current = null; } }; }, [markers, deps.vehicleMarkerId]); const shouldUsePureTranslateCarMarker = [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) @@ -371,15 +328,14 @@ }, [markers]); useEffect(() => { // 这里使用 displayMarkers + name 匹配(而非 id),因为 displayMarkers 中 car id 每次 rebuild 都会变成新 id const hasVehicleMarker = deps.statusRule.showCar && displayMarkers.some((item: any) => item.name === '车辆位置'); const hasVehicleMarker = deps.statusRule.showCar && markers.some((item: any) => item.id === deps.vehicleMarkerId); if (!hasVehicleMarker) { return; } Taro.nextTick(() => { deps.onVehicleMarkerReady(); }); }, [displayMarkers, deps.statusRule.showCar, deps.onVehicleMarkerReady]); }, [markers, deps.statusRule.showCar, deps.onVehicleMarkerReady, deps.vehicleMarkerId]); useEffect(() => { const pickupSite = @@ -457,8 +413,8 @@ ]); return { // 对外返回经过 rebuild dance 的展示态 markers;内部逻辑(applyMarkerState 等)仍读取 markers(逻辑态) markers: displayMarkers, // 对外返回逻辑 markers,不再使用 rebuild dance markers, setMarkers, markersRef, markersSignatureRef, src/robotaxi/pages/share-trip/hooks/use-vehicle-tracking.ts
@@ -128,48 +128,50 @@ } }; // 兜底定时器,防止回调丢失导致动画队列卡死 fallbackTimer = setTimeout(() => { finishTranslate(); }, TRANSLATE_DURATION_MS + 50); // 兜底定时器,防止回调丢失导致动画队列卡死 fallbackTimer = setTimeout(() => { finishTranslate(); }, task.duration + 50); Taro.nextTick(() => { const mapCtx = Taro.createMapContext('map'); mapCtx.translateMarker({ markerId: task.markerId, destination: { latitude: task.latitude, longitude: task.longitude, }, autoRotate: false, moveWithRotate: true, rotate: task.rotate, duration: Math.max(task.duration, 100), animationEnd: () => { if (translateSessionId !== translateSessionIdRef.current) { return; } lastAppliedTranslateTaskRef.current = { latitude: task.latitude, longitude: task.longitude, rotate: task.rotate }; finishTranslate(); }, fail: () => { if (translateSessionId !== translateSessionIdRef.current) { return; } lastAppliedTranslateTaskRef.current = { latitude: task.latitude, longitude: task.longitude, rotate: task.rotate }; finishTranslate(); } }); }); }; Taro.nextTick(() => { const mapCtx = Taro.createMapContext("map"); mapCtx.translateMarker({ markerId: task.markerId, destination: { latitude: task.latitude, longitude: task.longitude, }, autoRotate: false, moveWithRotate: true, rotate: task.rotate, // duration 为 0 时,微信地图在 iOS 存在 Bug(回调永远不触发或引发死锁)。 // 兜底为 1ms,既能实现视觉上的"瞬移"(解决重置旋转矩阵的需求),又能保证动画队列正常结束。 duration: Math.max(task.duration, 1), animationEnd: () => { if (translateSessionId !== translateSessionIdRef.current) { return; } lastAppliedTranslateTaskRef.current = { latitude: task.latitude, longitude: task.longitude, rotate: task.rotate, }; finishTranslate(); }, fail: () => { if (translateSessionId !== translateSessionIdRef.current) { return; } lastAppliedTranslateTaskRef.current = { latitude: task.latitude, longitude: task.longitude, rotate: task.rotate, }; finishTranslate(); }, }); }); }; // 更新车辆位置并维护最近位置引用 const updateVehicleLocationThrottled = ( @@ -234,26 +236,27 @@ }, []); /** * 暂停 translateMarker 并切换到新的 markerId。 * 在 CarMarker 重建期间调用:原生层即将销毁旧 marker、用 newMarkerId 创建新 marker, * 这里同步把 vehicleMarkerIdRef 切到新 id,pending 任务的 markerId 也一并更新, * 确保 ready 恢复后 translateMarker 作用于新 marker。 * * 关键:保留 pendingTranslateRef.current(只改 markerId)。 * - 若 suspend 前已有 pending(最近一次推送写入但尚未 flush),ready 恢复后会直接消费它。 * - 若 suspend 后再有新推送,translateCar useEffect 会用最新值覆盖 pending(markerId 用 ref 取最新)。 * 重置 translateMarker 并准备新状态。 * 背景:为了解决旧版方案(修改 marker ID)导致的车标虚影问题,我们现已将 marker ID 固定。 * 当需要强行纠正位置或清除原生层残留的旋转矩阵(rotate)时: * 我们不再销毁/重建 marker,而是将 pending 动画的 duration 设为极小值(0,实际运行为 1ms), * 让原生引擎立即"瞬移"到目标状态,从而强制覆盖掉内部的旧状态。 */ const suspendVehicleMarker = useCallback((newMarkerId: number) => { const suspendVehicleMarker = useCallback((_newMarkerId: number) => { isTranslatingRef.current = false; // 清除上一次的记录,让下次 translate 时认为发生了位移和角度变化,必须派发指令 lastAppliedTranslateTaskRef.current = null; vehicleMarkerReadyRef.current = false; clearVehicleMarkerReadyTimer(); translateSessionIdRef.current += 1; vehicleMarkerIdRef.current = newMarkerId; // 注意:不更新 vehicleMarkerIdRef.current,因为不再派生新 ID // 保留 pending,并将 duration 设为 0 以强制原生层立即归位(重置旋转矩阵和位置) if (pendingTranslateRef.current) { pendingTranslateRef.current = { ...pendingTranslateRef.current, markerId: newMarkerId duration: ROTATE_ONLY_DURATION_MS }; } }, []);