hejianhao
2026-06-03 39e0eda46990f1ad46fe3a6d1760e39598564c10
feat(robotaxi): 优化地图显示与动画逻辑,移除车速缩放功能

移除全部车速同步相关代码,包括websocket车速接收、车速状态管理及基于车速的动态地图缩放
新增车辆marker气泡跟随行驶方向动态调整锚点的功能,避免气泡遮挡前方路线
优化返程地图视口,将落客站点加入显示范围以完整展示行程
新增车辆位置动画兜底定时器,修复回调丢失导致的动画队列卡死问题
修复平滑路线移动时的路线擦除逻辑错误
优化区域地图变化触发条件及手动选址的显示判断逻辑
补充站点marker的显式name字段赋值
13个文件已修改
164 ■■■■■ 已修改文件
src/pages/robotaxi/home/hooks/use-location-flow.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-region-change.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/runtime-sync.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-active-trip-scale.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-smooth-route-motion.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/Robotaxi-public-method.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-location-flow.ts
@@ -9,7 +9,8 @@
import {
  isHomeStartSiteDisplayStateUsable,
  isManualSelectedHomeStartSiteDisplayState,
  readHomeStartSiteDisplayState
  readHomeStartSiteDisplayState,
  shouldRetainManualSelectedHomeStartSiteOnNextHome
} from './use-home-start-site-display';
type Deps = {
@@ -73,7 +74,7 @@
   */
  const resolveLockedManualStartSite = (cityCodeArg?: string) => {
    const preferredState = readHomeStartSiteDisplayState();
    if (!isManualSelectedHomeStartSiteDisplayState(preferredState)) {
    if (!shouldRetainManualSelectedHomeStartSiteOnNextHome(preferredState)) {
      return null;
    }
    if (!isHomeStartSiteDisplayStateUsable(preferredState, cityCodeArg)) {
src/pages/robotaxi/home/hooks/use-region-change.ts
@@ -113,7 +113,7 @@
          return;
        }
        
        if (causedBy === 'gesture' || causedBy === 'drag' || causedBy === 'scale') {
        if (causedBy === 'gesture' || causedBy === 'drag' || causedBy === 'scale' || wasReturningToOrigin) {
          // 只有发生显著位移时才同步经纬度,避免微小抖动引发的二次重绘
          if (Math.abs(mapState.latitude - lat) > 0.0001 || Math.abs(mapState.longitude - lng) > 0.0001) {
            mapState.setLatitude(lat);
src/robotaxi/pages/begin-route/hooks/runtime-sync.ts
@@ -36,8 +36,6 @@
  eraseVehiclePointMode: VehiclePointMode;
  /** 非擦线态车辆点策略 */
  normalVehiclePointMode: VehiclePointMode;
  /** 当前车速 (m/s) */
  speed: number | null;
};
/**
@@ -82,7 +80,6 @@
    forceFullRedraw: payload?.forceFullRedraw === true,
    eraseVehiclePointMode: 'first_then_served_last',
    normalVehiclePointMode: 'served_last_then_first',
    speed: Number.isFinite(Number(route?.speed)) ? Number(route?.speed) : null,
  };
}
src/robotaxi/pages/begin-route/hooks/use-active-trip-scale.ts
@@ -1,5 +1,5 @@
import Taro from '@tarojs/taro';
import { useCallback, useMemo, useEffect, useRef } from 'react';
import { useCallback, useMemo } from 'react';
import { TripStatus } from '../constants/trip-status';
type RoutePoint = {
@@ -10,16 +10,6 @@
type Deps = {
  /** 当前行程状态。 */
  tripStatus: TripStatus;
  /** 车辆速度 (m/s) */
  vehicleSpeed: number | null;
  /** 当前路线点集 */
  routePoints: RoutePoint[];
  /** 当前地图缩放比例 */
  mapScale: number;
  /** 设置地图缩放比例 */
  setMapScale: (scale: number) => void;
  /** 获取速度缩放暂停截止时间 */
  getDynamicScaleHoldUntil: () => number;
  /** 计算 includePoints 所需底部留白。 */
  computeBottomPadding: (points: any[], viewportType: string) => number;
  /** 读取顶部安全区高度。 */
@@ -35,7 +25,6 @@
const ORDER_UPDATE_LINE_SIDE_PADDING = 100; // ORDER_UPDATE_LINE 路线入屏左右留白
const ORDER_UPDATE_LINE_TOP_PADDING = 24; // ORDER_UPDATE_LINE 路线入屏最小顶部留白
const ORDER_UPDATE_LINE_TOP_EXTRA_PADDING = 32; // ORDER_UPDATE_LINE 顶部安全区附加留白
const DYNAMIC_SCALE_INTERVAL_MS = 3000; // 动态缩放巡航周期 (缩短到3秒,对车速变化更灵敏)
/**
 * 接驾中/行程中视口编排入口:仅保留视口编排职责。
@@ -44,45 +33,6 @@
  const isActiveStatus = useMemo(() => {
    return ACTIVE_TRIP_STATUSES.includes(deps.tripStatus);
  }, [deps.tripStatus]);
  const depsRef = useRef(deps);
  useEffect(() => {
    depsRef.current = deps;
  }, [deps]);
  /**
   * 基于车速区间直接推导视口缩放比例 (直接驱动受控 mapScale,避免与原生 includePoints 冲突)。
   */
  const tryApplySpeedBasedScale = useCallback(() => {
    const currentDeps = depsRef.current;
    if (currentDeps.tripStatus !== TripStatus.InService) {
      return; // 仅在行程中开启速度级缩放
    }
    const now = Date.now();
    if (currentDeps.getDynamicScaleHoldUntil() > now) {
      return; // 处于手势或强行全览冷却期
    }
    const speed = currentDeps.vehicleSpeed || 0;
    let targetScale = 14; // 高速段:视野更宏观
    if (speed < 8.3) { // 约 30km/h
      targetScale = 18; // 低速段:细节更清晰
    } else if (speed < 16.6) { // 约 60km/h
      targetScale = 16; // 中速段:均衡视野
    }
    if (currentDeps.mapScale !== targetScale) {
      currentDeps.setMapScale(targetScale);
    }
  }, []);
  useEffect(() => {
    if (!isActiveStatus) return;
    const timer = setInterval(() => {
      tryApplySpeedBasedScale();
    }, DYNAMIC_SCALE_INTERVAL_MS);
    return () => clearInterval(timer);
  }, [isActiveStatus, tryApplySpeedBasedScale]);
  /**
   * 处理地图手势,手动缩放/拖拽后短暂暂停视口同步。
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -169,6 +169,32 @@
  // 构建车辆 marker
  const createCarMarker = () => {
    if (!deps.vehicleLocation) return null;
    // 动态计算气泡位置,始终将气泡放在车辆行驶方向的后方,避免遮挡前方路线
    // 车辆图标高 40,宽 20。气泡宽约 140,高约 40。
    // anchorY/anchorX 的基准是基于 marker 自身的坐标点 (0, 0)
    let anchorY = 60; // 默认向北行驶,气泡放在正下方,避开车身高度 (40) 和部分留白
    let anchorX = 0;
    if (typeof deps.vehicleDirection === 'number') {
      const normalizedDirection = ((deps.vehicleDirection % 360) + 360) % 360;
      if (normalizedDirection >= 45 && normalizedDirection < 135) {
        // 向东行驶,后方为西(左侧)
        // 气泡向左偏移一半的气泡宽度 (约 70) 加上车身宽度一半 (10) 和留白
        anchorY = 20; // 垂直居中于车身
        anchorX = -85;
      } else if (normalizedDirection >= 135 && normalizedDirection < 225) {
        // 向南行驶,后方为北(上方)
        // 气泡向上偏移气泡高度 (40) 加上车身高度的一半 (20)
        anchorY = -40;
        anchorX = 0;
      } else if (normalizedDirection >= 225 && normalizedDirection < 315) {
        // 向西行驶,后方为东(右侧)
        // 气泡向右偏移一半的气泡宽度 (约 70) 加上车身宽度一半 (10) 和留白
        anchorY = 20; // 垂直居中于车身
        anchorX = 85;
      }
    }
    return {
      id: deps.vehicleMarkerId,
      latitude: deps.vehicleLocation.latitude,
@@ -180,7 +206,7 @@
      zIndex: 2,
      rotate: typeof deps.vehicleDirection === 'number' ? deps.vehicleDirection : 0,
      name: '车辆位置',
      customCallout: { anchorY: 70, anchorX: 0, display: 'ALWAYS' }
      customCallout: { anchorY, anchorX, display: 'ALWAYS' }
    };
  };
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
@@ -449,11 +449,17 @@
    const toOrigin = () => {
        const mapCtx = Taro.createMapContext('map');
        if (deps.tripStatus === TripStatus.InService && deps.routePoints.length > 0) {
            const bottomPadding = computeBottomPadding(deps.routePoints, 'route');
            const { dropoffSite } = resolveViewportSites();
            const dropoffPoint = buildSiteViewportPoint(dropoffSite);
            const pointsToInclude = [...deps.routePoints];
            if (dropoffPoint) pointsToInclude.push(dropoffPoint);
            const bottomPadding = computeBottomPadding(pointsToInclude, 'route');
            const topPadding = Math.max(24, resolveSafeTopInset() + 32);
            holdDynamicScale();
            mapCtx.includePoints({
                points: deps.routePoints,
                points: pointsToInclude,
                padding: [topPadding, 100, bottomPadding, 100]
            });
            tripStatus6ViewportLockedRef.current = true;
src/robotaxi/pages/begin-route/hooks/use-smooth-route-motion.ts
@@ -219,6 +219,9 @@
      const remainingGap = runtime.targetDistanceMeters - runtime.displayDistanceMeters;
      lastRenderAtRef.current = now;
      if (remainingGap <= 0.5) {
        if (runtime.displayDistanceMeters - runtime.erasedDistanceMeters > 0) {
          eraseRouteToDistance(routeIndex, runtime.displayDistanceMeters);
        }
        return;
      }
      const stepBySpeed = TripMotionConfig.FALLBACK_SPEED_MPS * (deltaMs / 1000);
@@ -233,15 +236,18 @@
          Math.max(stepBySpeed, stepByGap)
        )
      );
      const prevDisplayDistance = runtime.displayDistanceMeters;
      runtime.displayDistanceMeters = nextDisplayDistance;
      const currentVehiclePoint = getPointByDistance(routeIndex, nextDisplayDistance);
      deps.updateVehicleLocationThrottled(currentVehiclePoint.latitude, currentVehiclePoint.longitude, true);
      const shouldEraseRoute = (
        nextDisplayDistance - runtime.erasedDistanceMeters >= TripMotionConfig.ERASE_STEP_METERS
        || nextDisplayDistance >= routeIndex.totalDistance
        prevDisplayDistance - runtime.erasedDistanceMeters >= TripMotionConfig.ERASE_STEP_METERS
        || prevDisplayDistance >= routeIndex.totalDistance
      );
      if (shouldEraseRoute) {
        eraseRouteToDistance(routeIndex, nextDisplayDistance);
        eraseRouteToDistance(routeIndex, prevDisplayDistance);
      }
    }, TripMotionConfig.RENDER_INTERVAL_MS);
  };
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts
@@ -41,14 +41,22 @@
    pendingTranslateRef.current = null;
    isTranslatingRef.current = true;
    let finished = false;
    let fallbackTimer: any = null;
    const finishTranslate = () => {
      if (finished) return;
      finished = true;
      if (fallbackTimer) clearTimeout(fallbackTimer);
      isTranslatingRef.current = false;
      if (pendingTranslateRef.current) {
        flushTranslateTask();
      }
    };
    // 兜底定时器,防止回调丢失导致动画队列卡死
    fallbackTimer = setTimeout(() => {
      finishTranslate();
    }, TRANSLATE_DURATION_MS + 50);
    Taro.nextTick(() => {
      const mapCtx = Taro.createMapContext('map');
      mapCtx.translateMarker({
@@ -61,10 +69,10 @@
        moveWithRotate: false,
        rotate: task.rotate,
        duration: TRANSLATE_DURATION_MS,
        fail: () => {
        animationEnd: () => {
          finishTranslate();
        },
        complete: () => {
        fail: () => {
          finishTranslate();
        }
      });
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -57,7 +57,6 @@
    setVehicleMoveAlongPoints: (points: any[]) => void;
    setRouteDistance: (value: any) => void;
    setRouteDuration: (value: number) => void;
    setVehicleSpeed?: (speed: number) => void;
    setModifyDestinationSocketStatus: (value: number | null) => void;
    setOrderRouteMetrics?: (metrics: {
        distanceMeters: number | null;
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
@@ -105,9 +105,6 @@
    return;
  }
  const routeSnapshot = normalizeSocketRouteSnapshot(payload);
  if (typeof routeSnapshot.speed === 'number') {
    currentDeps.setVehicleSpeed?.(routeSnapshot.speed);
  }
  const shouldOnlySyncModifyPreviewVehicle = isModifyDestinationPreviewActive && currentTripStatus === TripStatus.InService;
  const shouldFreezeModifyPreviewRoute = isModifyDestinationPreviewActive && currentTripStatus !== TripStatus.InService;
  if (isModifyDestinationPreviewActive) {
src/robotaxi/pages/begin-route/index.tsx
@@ -202,7 +202,6 @@
  const [showCouponSelectPopup, setShowCouponSelectPopup] = useState(false); // 优惠券选择弹窗展示状态
  const [showModifyDestinationFailPopup, setShowModifyDestinationFailPopup] = useState(false); // 修改目的地失败弹窗
  const [modifyDestinationFailMessage, setModifyDestinationFailMessage] = useState(''); // 修改目的地失败文案(默认空,避免闪文案)
  const [vehicleSpeed, setVehicleSpeed] = useState<number | null>(null); // 车辆当前速度 (m/s)
  const [orderRouteMetrics, setOrderRouteMetrics] = useState<OrderRouteMetrics>({
    distanceMeters: null,
    durationSeconds: null
@@ -622,11 +621,6 @@
  } = viewportState; // 视口状态解构
  const activeTripScale = useActiveTripScale({
    tripStatus,
    vehicleSpeed,
    routePoints,
    mapScale,
    setMapScale: viewportState.setMapScale,
    getDynamicScaleHoldUntil,
    computeBottomPadding,
    resolveSafeTopInset,
    holdDynamicScale
@@ -927,7 +921,6 @@
    setVehicleMoveAlongPoints,
    setRouteDistance,
    setRouteDuration,
    setVehicleSpeed,
    setModifyDestinationSocketStatus,
    setOrderRouteMetrics,
    setBeforeUserEndSite,
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -119,6 +119,32 @@
    const createCarMarker = () => {
        if (!deps.vehicleLocation) return null;
        // 动态计算气泡位置,始终将气泡放在车辆行驶方向的后方,避免遮挡前方路线
        // 车辆图标高 40,宽 20。气泡宽约 140,高约 40。
        // anchorY/anchorX 的基准是基于 marker 自身的坐标点 (0, 0)
        let anchorY = 60; // 默认向北行驶,气泡放在正下方,避开车身高度 (40) 和部分留白
        let anchorX = 0;
        if (typeof deps.vehicleDirection === 'number') {
            const normalizedDirection = ((deps.vehicleDirection % 360) + 360) % 360;
            if (normalizedDirection >= 45 && normalizedDirection < 135) {
                // 向东行驶,气泡放在左侧
                // 气泡向左偏移一半的气泡宽度 (约 70) 加上车身宽度一半 (10) 和留白
                anchorY = 20; // 垂直居中于车身
                anchorX = -85;
            } else if (normalizedDirection >= 135 && normalizedDirection < 225) {
                // 向南行驶,气泡放在上方
                // 气泡向上偏移气泡高度 (40) 加上车身高度的一半 (20)
                anchorY = -40;
                anchorX = 0;
            } else if (normalizedDirection >= 225 && normalizedDirection < 315) {
                // 向西行驶,气泡放在右侧
                // 气泡向右偏移一半的气泡宽度 (约 70) 加上车身宽度一半 (10) 和留白
                anchorY = 20; // 垂直居中于车身
                anchorX = 85;
            }
        }
        return {
            id: deps.vehicleMarkerId,
            latitude: deps.vehicleLocation.latitude,
@@ -133,7 +159,7 @@
                    ? deps.vehicleDirection
                    : 0,
            name: "车辆位置",
            customCallout: { anchorY: 70, anchorX: 0, display: "ALWAYS" }
            customCallout: { anchorY, anchorX, display: "ALWAYS" }
        };
    };
src/utils/Robotaxi-public-method.ts
@@ -513,6 +513,7 @@
export const formatSiteToMarker = (sites: any[]) => {
  return sites.map((site, index) => ({
    ...site,
    name: site.name,
    // id: index + 1,
    iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-poi.png',
    width: 15,