par-zengjunkun
2026-07-27 1d1e3c5313a26bc4b3cd2e5e2fec1a98f7832f02
Fix 0715 share order
2个文件已修改
169 ■■■■■ 已修改文件
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-vehicle-tracking.ts 107 ●●●●● 补丁 | 查看 | 原始文档 | 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
            };
        }
    }, []);