卢春龙
2026-06-30 b1e8db827c7f048339e7d4eee110a4abf6bbbdae
Merge branch 'lucl/fix-0630' into 'feature_v2.0'

fix: 修复修改目的地确认页方向角不正确问题

See merge request robotaxi1/xp-robotaxi-miniprogram!137
4个文件已修改
60 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/hooks/use-smooth-route-motion.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-smooth-route-motion.ts
@@ -36,6 +36,8 @@
  setVehicleMoveAlongPoints: (points: RoutePoint[]) => void;
  /** 节流更新车辆位置 */
  updateVehicleLocationThrottled: (lat: number, lng: number, force?: boolean) => void;
  /** 读取用户手势是否进行中(拖拽/缩放手势 begin→end 之间为 true)。 */
  getUserGestureActive?: () => boolean;
};
type MotionRuntimeState = {
@@ -85,7 +87,7 @@
    lastSocketTimestamp: 0,
    routeReady: false,
  });
  const renderTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const renderTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const lastRenderAtRef = useRef(0);
  const prevTripStatusRef = useRef(deps.tripStatus);
  const nativeRouteEraseReadyRef = useRef(false);
@@ -134,7 +136,7 @@
   */
  const stopRenderLoop = () => {
    if (renderTimerRef.current) {
      clearInterval(renderTimerRef.current);
      clearTimeout(renderTimerRef.current);
      renderTimerRef.current = null;
    }
    lastRenderAtRef.current = 0;
@@ -281,15 +283,28 @@
  /**
   * 启动本地补帧循环,在 socket 两帧之间平滑推动车辆和擦线进度。
   * 用递归 setTimeout 自调度而非 setInterval:同一时刻只保留一个待执行定时器,
   * 渲染层繁忙时不会像 setInterval 那样在桥接层堆积回调。下一帧在帧首调度,早退分支也不会中断循环。
   */
  const startRenderLoop = () => {
    if (renderTimerRef.current) {
      return;
    }
    renderTimerRef.current = setInterval(() => {
    const tick = () => {
      // 帧首先排下一帧(早退分支不影响循环延续),由 stopRenderLoop 的 clearTimeout 终止。
      renderTimerRef.current = setTimeout(tick, TripMotionConfig.RENDER_INTERVAL_MS);
      const runtime = runtimeRef.current;
      const routeIndex = runtime.routeIndex;
      if (!runtime.routeReady || !routeIndex) {
        return;
      }
      // 用户手势(拖拽/缩放)进行中暂停补帧:本帧会通过 updateVehicleLocationThrottled setState 触发
      // 页面重渲染,进而把受控 <Map> 的相机重新写回原生地图,与用户手势争抢导致「拖动被弹回」。
      // 整帧跳过(车标/擦线进度一并冻结,保持一致),并刷新 lastRenderAt 避免手势结束后步长突跳;
      // 手势期间 socket 仍可推进 targetDistanceMeters,手势结束后由后续帧平滑追帧。
      if (deps.getUserGestureActive?.()) {
        lastRenderAtRef.current = Date.now();
        return;
      }
      const now = Date.now();
@@ -328,7 +343,8 @@
      if (shouldEraseRoute) {
        eraseRouteToDistance(routeIndex, prevDisplayDistance);
      }
    }, TripMotionConfig.RENDER_INTERVAL_MS);
    };
    renderTimerRef.current = setTimeout(tick, TripMotionConfig.RENDER_INTERVAL_MS);
  };
  /**
src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts
@@ -31,7 +31,7 @@
const TOP_PADDING_MIN = 32;
const TOP_EXTRA_PADDING = 50;
const SHORT_ROUTE_FALLBACK_POINT_THRESHOLD = 3;
const USER_MAP_INTERACTION_HOLD_MS = 3000;
const USER_MAP_INTERACTION_HOLD_MS = 8000;
/**
 * 两次 includePoints 之间的最小间隔(毫秒)。
 *
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
@@ -113,6 +113,10 @@
    return;
  }
  deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
  // 修改目的地确认页期间仍需同步车辆朝向,否则 rebuild marker 后 translateMarker 的 rotate 为 0
  if (typeof routeSnapshot.direction === 'number') {
    deps.setVehicleDirection(routeSnapshot.direction);
  }
};
/**
src/robotaxi/pages/begin-route/index.tsx
@@ -255,6 +255,9 @@
  const markViewportFitSyncing = useCallback(() => {
    globalFitSyncRef.current = true;
  }, []);
  // 用户手势进行中标记:从 regionchange 'begin'(用户拖拽/缩放手势) 到 'end' 之间为 true。
  // 供平滑运动在手势期间暂停车辆位置回灌,避免高频 setState 触发重渲染把受控相机弹回。
  const userGestureActiveRef = useRef(false);
  // 经纬度 setter:更新 state 触发渲染的同时同步记录最新值,保证 ref 始终为最新(兼容函数式更新)
  const setLatitude = useCallback((value: number | null | ((prev: number | null) => number | null)) => {
    setLatitudeState((prev) => {
@@ -1315,7 +1318,8 @@
    setRouteDuration,
    setVehicleDirection,
    setVehicleMoveAlongPoints,
    updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled
    updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled,
    getUserGestureActive: () => userGestureActiveRef.current
  });
  useEffect(() => {
@@ -1743,15 +1747,31 @@
    // 用户手势导致的缩放/拖拽结束后,把原生地图的实际 scale 同步回 React 状态,
    // 避免受控 <Map scale={mapScale}> 与原生实际缩放漂移,影响后续视口编排判断。
    const eventType = String(e?.type || e?.detail?.type || '').toLowerCase();
    if (eventType === 'end' && (causedBy === 'scale' || causedBy === 'drag' || causedBy === 'gesture')) {
        notifyUserMapGesture();
    // 维护用户手势进行中标记:手势 begin 置位、end 复位,供平滑运动在手势期间暂停车辆位置回灌。
    const isUserGesture = causedBy === 'scale' || causedBy === 'drag' || causedBy === 'gesture';
    if (eventType === 'begin') {
      if (isUserGesture) {
        userGestureActiveRef.current = true;
      }
    } else if (eventType === 'end' && isUserGesture) {
      // 仅用户手势(gesture/drag/scale)的 end 才复位;手势期间穿插的程序化 update 事件
      // (causedBy='update') 的 end 不应清除标记,否则会在用户仍在操作时提前恢复补帧导致弹回。
      userGestureActiveRef.current = false;
    }
    if(eventType === 'begin' && isUserGesture) {
      notifyUserMapGesture();
    }
    if (eventType === 'end' && isUserGesture) {
        const actualScale = Number(e?.detail?.scale);
        if (!Number.isFinite(actualScale)) {
          return;
        }
        const rounded = Math.round(actualScale);
        if (rounded !== mapScale) {
        if (rounded !== mapScaleRef.current) {
          recordMapScale(rounded);
        }
@@ -1775,7 +1795,7 @@
      const fittedScale = Number(detail?.scale);
      if (Number.isFinite(fittedScale)) {
        const rounded = Math.floor(fittedScale);
        if (rounded !== mapScale) {
        if (rounded !== mapScaleRef.current) {
          recordMapScale(rounded);
        }
      }