lucl
2026-06-29 a85cf5f480b7ee289e310f0ed5d4d1a2bbd917c7
Merge branch 'lucl/auto-scale-v6' into feature_v2.0
10个文件已修改
260 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-vehicle-tracking.ts 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/index.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -75,6 +75,7 @@
  arrivedMarkerReady: boolean;
  arrivedTooltipReady: boolean;
  onVehicleMarkerReady: () => void;
  onCarMarkerWillRebuild: (newMarkerId: number) => void;
};
const defaultDeps: Deps = {
@@ -91,17 +92,66 @@
  vehicleMarkerId: 3,
  arrivedMarkerReady: false,
  arrivedTooltipReady: false,
  onVehicleMarkerReady: () => {}
  onVehicleMarkerReady: () => { },
  onCarMarkerWillRebuild: (_newMarkerId: number) => { }
};
export default function useMapMarkers(rawDeps?: Partial<Deps>) {
  const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps;
  // 地图 marker 集合
  // 内部"逻辑 markers"——真相态,所有内部判断(签名、是否含车标等)以此为准
  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, 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) {
      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]);
  // 行驶中/接驾中/到达上车点 车辆 marker 统一交由 translateMarker 托管位置与角度
  const shouldUsePureTranslateCarMarker =
@@ -249,6 +299,7 @@
    const verticalExtraOffset = hasBeforeUserOrderTip ? 22 : 0;
    const anchorY = 80 + verticalExtraOffset;
    const anchorX = 0;
    return {
      id: deps.vehicleMarkerId,
@@ -309,14 +360,8 @@
          return prevMarkers;
        }
        const prevCar = prevMarkers[targetCarIndex] || {};
        const shouldClearCarRotate = Object.prototype.hasOwnProperty.call(
          prevCar,
          "rotate"
        );
        if (
          prevMarkersSignature === nextMarkersSignature &&
          !shouldClearCarRotate
        ) {
        // const shouldClearCarRotate = Object.prototype.hasOwnProperty.call(prevCar, 'rotate');
        if (prevMarkersSignature === nextMarkersSignature) {
          return prevMarkers;
        }
        markersSignatureRef.current = nextMarkersSignature;
@@ -329,11 +374,7 @@
        const latestVehicleLongitude = Number(deps.vehicleLocation?.longitude);
        // 车辆在签名中以 'translate' 占位,签名差异只可能来自非车辆 marker(如终点从新目的地回退到原目的地)。
        // 因此仅当非车辆 marker 结构未变、车辆结构未变且无需清理朝向时才保留旧 marker,否则必须应用最新 marker,避免终点/上车点残留。
        if (
          !shouldRefreshCarMarkerStructure &&
          !shouldClearCarRotate &&
          prevMarkersSignature === nextMarkersSignature
        ) {
        if (!shouldRefreshCarMarkerStructure) {
          return prevMarkers;
        }
        mergedMarkers[nextCarIndex] = {
@@ -377,21 +418,15 @@
  }, [markers]);
  useEffect(() => {
    const hasVehicleMarker =
      deps.statusRule.showCar &&
      markers.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();
    });
  }, [
    markers,
    deps.statusRule.showCar,
    deps.vehicleMarkerId,
    deps.onVehicleMarkerReady
  ]);
  }, [displayMarkers, deps.statusRule.showCar, deps.onVehicleMarkerReady]);
  useEffect(() => {
    const pickupSite =
@@ -493,7 +528,8 @@
  ]);
  return {
    markers,
    // 对外返回经过 rebuild dance 的展示态 markers;内部逻辑(applyMarkerState 等)仍读取 markers(逻辑态)
    markers: displayMarkers,
    setMarkers,
    markersRef,
    markersSignatureRef,
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts
@@ -21,7 +21,7 @@
/** 地图视口顶部附加安全区内边距。 */
export const ROUTE_VIEWPORT_TOP_EXTRA_PADDING = 50;
/** 地图视口底部附加安全区内边距,因为地图的高度是 87vh,到时底部 padding 偏高,要减去一点 */
export const ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING = -30;
export const ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING = 0;
/** 地图视口左右内边距。 */
export const ROUTE_VIEWPORT_SIDE_PADDING = 80;
export const ROUTE_VIEWPORT_RIGHT_SIDE_PADDING  = 80;
src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts
@@ -40,7 +40,7 @@
 * 间隔太短会让多次 fit 的 race 窗口叠加。这里取 1500ms 让每次 fit 有充裕
 * 的同步时间,相当于把抖动从"持续震"压成"偶尔轻微闪一下"。
 */
const MIN_FIT_INTERVAL_MS = 1500;
const MIN_FIT_INTERVAL_MS = 5000;
const buildRouteSignature = (points: { latitude: number; longitude: number }[]) => {
  if (!Array.isArray(points) || points.length === 0) return '';
@@ -88,7 +88,6 @@
  const mapCtx = Taro.createMapContext('map');
  const bottomPadding = computeBottomPadding(points, 'route', tripStatus, true) + ROUTE_VIEWPORT_BOTTOM_EXTRA_PADDING ;
  const topPadding = Math.max(ROUTE_VIEWPORT_MIN_TOP_PADDING, resolveSafeTopInset() + ROUTE_VIEWPORT_TOP_EXTRA_PADDING);
  console.log('[lucl] fitRouteIntoView ---> includePoints', JSON.stringify({ points, padding: [topPadding, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING, bottomPadding, ROUTE_VIEWPORT_SIDE_PADDING] }));
  mapCtx.includePoints({
    points,
    padding: [topPadding, ROUTE_VIEWPORT_RIGHT_SIDE_PADDING, bottomPadding, ROUTE_VIEWPORT_SIDE_PADDING]
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts
@@ -4,7 +4,7 @@
const TRANSLATE_DURATION_MS = 100;
const ROTATE_ONLY_DURATION_MS = 0;
const VEHICLE_LOCATION_EPSILON = 0.000001;
const FIRST_VEHICLE_MARKER_READY_DELAY_MS = 120;
const FIRST_VEHICLE_MARKER_READY_DELAY_MS = 300;
/**
 * 计算两个角度之间的最小夹角,范围 0~180。
@@ -133,10 +133,12 @@
    // 兜底定时器,防止回调丢失导致动画队列卡死
    fallbackTimer = setTimeout(() => {
      finishTranslate();
    }, TRANSLATE_DURATION_MS + 50);
    }, 1500);
    Taro.nextTick(() => {
      const mapCtx = Taro.createMapContext('map');
      const duration =Math.min(Math.max(task.duration, 100), 1000);
      mapCtx.translateMarker({
        markerId: task.markerId,
        destination: {
@@ -145,9 +147,10 @@
        },
        autoRotate: false,
        moveWithRotate: false,
        rotate: task.rotate,
        duration: Math.max(task.duration, 100),
        rotate: Math.round(task.rotate),
        duration,
        animationEnd: () => {
          clearTimeout(fallbackTimer);
          if (translateSessionId !== translateSessionIdRef.current) {
            return;
          }
@@ -157,8 +160,10 @@
            rotate: task.rotate
          };
          finishTranslate();
        },
        fail: () => {
          clearTimeout(fallbackTimer);
          if (translateSessionId !== translateSessionIdRef.current) {
            return;
          }
@@ -167,7 +172,7 @@
            longitude: task.longitude,
            rotate: task.rotate
          };
          finishTranslate();
           finishTranslate();
        }
      });
    });
@@ -206,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();
@@ -233,6 +240,30 @@
    clearVehicleMarkerReadyTimer();
  }, []);
  /**
   * 暂停 translateMarker 并切换到新的 markerId。
   * 在 CarMarker 重建期间调用:原生层即将销毁旧 marker、用 newMarkerId 创建新 marker,
   * 这里同步把 vehicleMarkerIdRef 切到新 id,pending 任务的 markerId 也一并更新,
   * 确保 ready 恢复后 translateMarker 作用于新 marker。
   *
   * 关键:保留 pendingTranslateRef.current(只改 markerId),
   * 若 suspend 前已有 pending(最近一次推送写入但尚未 flush),ready 恢复后会直接消费它。
   */
  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 {
    vehicleLocation,
    setVehicleLocation,
@@ -247,6 +278,7 @@
    lastVehiclePointRef,
    lastVehicleUpdateAtRef,
    updateVehicleLocationThrottled,
    notifyVehicleMarkerReady
    notifyVehicleMarkerReady,
    suspendVehicleMarker
  };
}
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
@@ -55,15 +55,15 @@
      deps.lastSocketDirectionAtRef.current = Date.now();
    }
    deps.setVehicleDirection(normalizedDirection);
    if (deps.tripStatusRef.current === TripStatus.InService) {
      console.log('[开始行程][送驾方向][Socket]', {
        订单ID: getOrderId(deps.orderId),
        原始方向: rawDirection,
        归一化方向: normalizedDirection,
        车辆点位数量: Array.isArray(payload?.content?.vehicleMoveAlongPoints) ? payload.content.vehicleMoveAlongPoints.length : undefined,
        Socket原始数据: payload,
      });
    }
    // if (deps.tripStatusRef.current === TripStatus.InService) {
    //   console.log('[开始行程][送驾方向][Socket]', {
    //     订单ID: getOrderId(deps.orderId),
    //     原始方向: rawDirection,
    //     归一化方向: normalizedDirection,
    //     车辆点位数量: Array.isArray(payload?.content?.vehicleMoveAlongPoints) ? payload.content.vehicleMoveAlongPoints.length : undefined,
    //     Socket原始数据: payload,
    //   });
    // }
  }
  const route = safeParse(payload?.content?.route, {});
  const distanceMeters = Number(route?.distance);
src/robotaxi/pages/begin-route/index.tsx
@@ -10,7 +10,7 @@
import CreditPayPopup from './components/credit-pay-popup';
import CouponSelectPopup from '../../components/coupon-select-popup';
// 调试面板:模拟完整行程流转(仅 dev 构建可见)
import DebugOrderSimulatorPanel from './components/debug-order-simulator-panel';
// import DebugOrderSimulatorPanel from './components/debug-order-simulator-panel';
// 弹窗状态管理
import usePopupManager from './hooks/use-popup-manager';
// WebSocket 事件编排
@@ -1063,7 +1063,8 @@
    beforeUserEndSite: isPickupRouteChangeMapMode ? null : beforeUserEndSite,
    arrivedMarkerReady,
    arrivedTooltipReady,
    onVehicleMarkerReady: vehicleState.notifyVehicleMarkerReady
    onVehicleMarkerReady: vehicleState.notifyVehicleMarkerReady,
    onCarMarkerWillRebuild: vehicleState.suspendVehicleMarker
  }); // 地图标记状态容器
  const {
    markers,
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -54,6 +54,7 @@
    vehicleMarkerId: number;
    arrivedMarkerReady: boolean;
    onVehicleMarkerReady: () => void;
    onCarMarkerWillRebuild: (newMarkerId: number) => void;
};
const defaultDeps: Deps = {
@@ -66,16 +67,67 @@
    vehicleDirection: null,
    vehicleMarkerId: 3,
    arrivedMarkerReady: false,
    onVehicleMarkerReady: () => { }
    onVehicleMarkerReady: () => { },
    onCarMarkerWillRebuild: (_newMarkerId: number) => { }
};
export default function useMapMarkers(rawDeps?: Partial<Deps>) {
    const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps;
    // 内部"逻辑 markers"——真相态,所有内部判断(签名、是否含车标等)以此为准
    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)
        && deps.statusRule.showCar;
@@ -262,14 +314,15 @@
    }, [markers]);
    useEffect(() => {
        const hasVehicleMarker = deps.statusRule.showCar && markers.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();
        });
    }, [markers, deps.statusRule.showCar, deps.vehicleMarkerId, deps.onVehicleMarkerReady]);
    }, [displayMarkers, deps.statusRule.showCar, deps.onVehicleMarkerReady]);
    useEffect(() => {
        const pickupSite =
@@ -347,7 +400,8 @@
    ]);
    return {
        markers,
        // 对外返回经过 rebuild dance 的展示态 markers;内部逻辑(applyMarkerState 等)仍读取 markers(逻辑态)
        markers: displayMarkers,
        setMarkers,
        markersRef,
        markersSignatureRef,
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();
@@ -231,6 +233,31 @@
        clearVehicleMarkerReadyTimer();
    }, []);
    /**
     * 暂停 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 取最新)。
     */
    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 {
        vehicleLocation,
        setVehicleLocation,
@@ -245,6 +272,7 @@
        lastVehiclePointRef,
        lastVehicleUpdateAtRef,
        updateVehicleLocationThrottled,
        notifyVehicleMarkerReady
        notifyVehicleMarkerReady,
        suspendVehicleMarker
    };
}
src/robotaxi/pages/share-trip/index.tsx
@@ -114,7 +114,8 @@
    setVehicleMoveAlongPoints,
    setVehicleDirection,
    updateVehicleLocationThrottled,
    notifyVehicleMarkerReady
    notifyVehicleMarkerReady,
    suspendVehicleMarker
  } = useVehicleTracking({
    statusRule,
    vehicleMarkerId,
@@ -133,6 +134,7 @@
    vehicleMarkerId,
    arrivedMarkerReady,
    onVehicleMarkerReady: notifyVehicleMarkerReady,
    onCarMarkerWillRebuild: suspendVehicleMarker,
  }); // 地图 marker 状态容器
  const {
src/utils/websocket.ts
@@ -357,7 +357,7 @@
      return;
    }
    console.log('收到 WebSocket 消息:', rawData);
    // console.log('收到 WebSocket 消息:', rawData);
    const data = this.normalizeIncomingMessage(rawData);
    if (this.isPongMessage(data)) {
      this.handlePong(connectionId);