| src/robotaxi/pages/begin-route/hooks/use-map-markers.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| 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/begin-route/hooks/use-map-markers.ts
@@ -60,7 +60,7 @@ arrivedMarkerReady: boolean; arrivedTooltipReady: boolean; onVehicleMarkerReady: () => void; onCarMarkerWillRebuild: () => void; onCarMarkerWillRebuild: (newMarkerId: number) => void; }; const defaultDeps: Deps = { @@ -78,7 +78,7 @@ arrivedMarkerReady: false, arrivedTooltipReady: false, onVehicleMarkerReady: () => { }, onCarMarkerWillRebuild: () => { } onCarMarkerWillRebuild: (_newMarkerId: number) => { } }; export default function useMapMarkers(rawDeps?: Partial<Deps>) { @@ -92,11 +92,22 @@ 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); /** * 当逻辑 markers 变化时,对展示 markers 做"先移除车标 → 50ms 后恢复完整列表"的两阶段提交。 * 该 effect 与 markers 解耦,不会被外部 useEffect 频繁触发打断(即便 vehicleLocation/vehicleDirection 更新, * 只要 logical markers 引用未变化,rebuild dance 就不会重启)。 * 派生新的 car marker id:base + ((epoch % 1000) + 1) * 1000 * - 与业务 base id(3/30)和其他 marker(pickup=1, dropoff=2, beforeUser=31)不冲突 * - 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) { @@ -108,14 +119,16 @@ setDisplayMarkers(markers); return; } // 通知外部:CarMarker 即将重建,请暂停 translateMarker deps.onCarMarkerWillRebuild(); // 第一步:展示层先去掉车标,原生层销毁旧 car marker 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)); // 第二步:下一帧恢复完整列表,原生层重建 car marker(清除 translateMarker 遗留的旋转矩阵) // 第二步:100ms 后用 newCarId 重新挂载车标,原生层创建全新 marker 实例 rebuildCarMarkerTimerRef.current = setTimeout(() => { rebuildCarMarkerTimerRef.current = null; setDisplayMarkers(markers); setDisplayMarkers(markers.map((m: any) => m.id === deps.vehicleMarkerId ? { ...m, id: newCarId } : m)); }, 100); return () => { if (rebuildCarMarkerTimerRef.current) { @@ -314,15 +327,15 @@ }, [markers]); useEffect(() => { // 这里使用 displayMarkers 而非 markers(逻辑态),以保证 rebuild 完成(车标已真实渲染到原生地图)后才通知 ready const hasVehicleMarker = deps.statusRule.showCar && displayMarkers.some((item: any) => item.id === deps.vehicleMarkerId); // 这里使用 displayMarkers + name 匹配(而非 id),因为 displayMarkers 中 car id 每次 rebuild 都会变成新 id const hasVehicleMarker = deps.statusRule.showCar && displayMarkers.some((item: any) => item.name === '车辆位置'); if (!hasVehicleMarker) { return; } Taro.nextTick(() => { deps.onVehicleMarkerReady(); }); }, [displayMarkers, deps.statusRule.showCar, deps.vehicleMarkerId, deps.onVehicleMarkerReady]); }, [displayMarkers, deps.statusRule.showCar, deps.onVehicleMarkerReady]); useEffect(() => { const pickupSite = deps.startSite?.latitude && deps.startSite?.longitude ? deps.startSite : Taro.getStorageSync('startSite'); src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts
@@ -211,7 +211,9 @@ latitude: vehicleLocation.latitude, longitude: vehicleLocation.longitude, rotate: targetRotate, markerId: vehicleMarkerId, // 用 ref 而非 props:rebuild 期间 suspendVehicleMarker(newId) 已经把 ref 切到新 id, // 必须读最新 ref 才能让 pending 的 markerId 与原生层新 marker 一致 markerId: vehicleMarkerIdRef.current, duration: hasLocationDelta ? TRANSLATE_DURATION_MS : ROTATE_ONLY_DURATION_MS }; flushTranslateTask(); @@ -239,21 +241,27 @@ }, []); /** * 暂停 translateMarker:清除 ready 状态、lastApplied 任务,递增 session 让在飞回调失效。 * 用于 CarMarker 重建期间阻止 translateMarker 作用于即将被销毁的旧 marker。 * 暂停 translateMarker 并切换到新的 markerId。 * 在 CarMarker 重建期间调用:原生层即将销毁旧 marker、用 newMarkerId 创建新 marker, * 这里同步把 vehicleMarkerIdRef 切到新 id,pending 任务的 markerId 也一并更新, * 确保 ready 恢复后 translateMarker 作用于新 marker。 * * 关键:保留 pendingTranslateRef.current。 * - 若 suspend 前已有 pending(最近一次 vehicleLocation/vehicleDirection 推送写入但尚未 flush), * ready 恢复后会直接消费它,无需等下一次 socket 推送。 * - 若 suspend 后再有新推送,translateCar useEffect 会用最新值覆盖 pending。 * 后续调用 notifyVehicleMarkerReady 会重新走 ready 延时并 flush 平移任务。 * 关键:保留 pendingTranslateRef.current(只改 markerId), * 若 suspend 前已有 pending(最近一次推送写入但尚未 flush),ready 恢复后会直接消费它。 */ const suspendVehicleMarker = useCallback(() => { const suspendVehicleMarker = useCallback((newMarkerId: number) => { isTranslatingRef.current = false; lastAppliedTranslateTaskRef.current = null; vehicleMarkerReadyRef.current = false; clearVehicleMarkerReadyTimer(); translateSessionIdRef.current += 1; vehicleMarkerIdRef.current = newMarkerId; if (pendingTranslateRef.current) { pendingTranslateRef.current = { ...pendingTranslateRef.current, markerId: newMarkerId }; } }, []); return { src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -54,7 +54,7 @@ vehicleMarkerId: number; arrivedMarkerReady: boolean; onVehicleMarkerReady: () => void; onCarMarkerWillRebuild: () => void; onCarMarkerWillRebuild: (newMarkerId: number) => void; }; const defaultDeps: Deps = { @@ -68,7 +68,7 @@ vehicleMarkerId: 3, arrivedMarkerReady: false, onVehicleMarkerReady: () => { }, onCarMarkerWillRebuild: () => { } onCarMarkerWillRebuild: (_newMarkerId: number) => { } }; export default function useMapMarkers(rawDeps?: Partial<Deps>) { @@ -82,10 +82,22 @@ 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); /** * 当逻辑 markers 变化时,对展示 markers 做"先移除车标 → 50ms 后恢复完整列表"的两阶段提交。 * 该 effect 与外部 markers useEffect 解耦,避免被频繁的 vehicleLocation/vehicleDirection 更新打断。 * 派生新的 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) { @@ -97,14 +109,16 @@ setDisplayMarkers(markers); return; } // 通知外部:CarMarker 即将重建,请暂停 translateMarker deps.onCarMarkerWillRebuild(); // 第一步:展示层先去掉车标,原生层销毁旧 car marker 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)); // 第二步:下一帧恢复完整列表,原生层重建 car marker(清除 translateMarker 遗留的旋转矩阵) // 第二步:100ms 后用 newCarId 重新挂载车标,原生层创建全新 marker 实例 rebuildCarMarkerTimerRef.current = setTimeout(() => { rebuildCarMarkerTimerRef.current = null; setDisplayMarkers(markers); setDisplayMarkers(markers.map((m: any) => m.id === deps.vehicleMarkerId ? { ...m, id: newCarId } : m)); }, 100); return () => { if (rebuildCarMarkerTimerRef.current) { @@ -300,15 +314,15 @@ }, [markers]); useEffect(() => { // 这里使用 displayMarkers 而非 markers(逻辑态),以保证 rebuild 完成(车标已真实渲染到原生地图)后才通知 ready const hasVehicleMarker = deps.statusRule.showCar && displayMarkers.some((item: any) => item.id === deps.vehicleMarkerId); // 这里使用 displayMarkers + name 匹配(而非 id),因为 displayMarkers 中 car id 每次 rebuild 都会变成新 id const hasVehicleMarker = deps.statusRule.showCar && displayMarkers.some((item: any) => item.name === '车辆位置'); if (!hasVehicleMarker) { return; } Taro.nextTick(() => { deps.onVehicleMarkerReady(); }); }, [displayMarkers, deps.statusRule.showCar, deps.vehicleMarkerId, deps.onVehicleMarkerReady]); }, [displayMarkers, deps.statusRule.showCar, deps.onVehicleMarkerReady]); useEffect(() => { const pickupSite = src/robotaxi/pages/share-trip/hooks/use-vehicle-tracking.ts
@@ -204,7 +204,9 @@ latitude: vehicleLocation.latitude, longitude: vehicleLocation.longitude, rotate: targetRotate, markerId: vehicleMarkerId, // 用 ref 而非 props:rebuild 期间 suspendVehicleMarker(newId) 已经把 ref 切到新 id, // 必须读最新 ref 才能让 pending 的 markerId 与原生层新 marker 一致 markerId: vehicleMarkerIdRef.current, duration: hasLocationDelta ? TRANSLATE_DURATION_MS : ROTATE_ONLY_DURATION_MS }; flushTranslateTask(); @@ -232,20 +234,28 @@ }, []); /** * 暂停 translateMarker:清除 ready 状态、lastApplied 任务,递增 session 让在飞回调失效。 * 用于 CarMarker 重建期间阻止 translateMarker 作用于即将被销毁的旧 marker。 * 暂停 translateMarker 并切换到新的 markerId。 * 在 CarMarker 重建期间调用:原生层即将销毁旧 marker、用 newMarkerId 创建新 marker, * 这里同步把 vehicleMarkerIdRef 切到新 id,pending 任务的 markerId 也一并更新, * 确保 ready 恢复后 translateMarker 作用于新 marker。 * * 关键:保留 pendingTranslateRef.current。 * - 若 suspend 前已有 pending(最近一次推送写入但尚未 flush),ready 恢复后会直接消费它,无需等下一次 socket 推送。 * - 若 suspend 后再有新推送,translateCar useEffect 会用最新值覆盖 pending。 * 后续调用 notifyVehicleMarkerReady 会重新走 ready 延时并 flush 平移任务。 * 关键:保留 pendingTranslateRef.current(只改 markerId)。 * - 若 suspend 前已有 pending(最近一次推送写入但尚未 flush),ready 恢复后会直接消费它。 * - 若 suspend 后再有新推送,translateCar useEffect 会用最新值覆盖 pending(markerId 用 ref 取最新)。 */ const suspendVehicleMarker = useCallback(() => { const suspendVehicleMarker = useCallback((newMarkerId: number) => { isTranslatingRef.current = false; lastAppliedTranslateTaskRef.current = null; vehicleMarkerReadyRef.current = false; clearVehicleMarkerReadyTimer(); translateSessionIdRef.current += 1; vehicleMarkerIdRef.current = newMarkerId; if (pendingTranslateRef.current) { pendingTranslateRef.current = { ...pendingTranslateRef.current, markerId: newMarkerId }; } }, []); return {