refactor(robotaxi/begin-route): 重构行程视口适配逻辑,移除冗余车速代码,优化蓝牙卡片显示
1个文件已删除
11个文件已修改
451 ■■■■■ 已修改文件
src/robotaxi/api/types/begin-route.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/runtime-sync.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/trip-motion-utils.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-active-trip-scale.ts 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-request.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-vehicle-speed-scale.ts 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/types/begin-route.ts
@@ -417,8 +417,6 @@
    duration?: NumericLike;
    /** 方向角。 */
    direction?: NumericLike;
    /** 行驶速度。 */
    speed?: NumericLike;
}
/** 订单轨迹响应 content。 */
@@ -447,8 +445,6 @@
    distance?: NumericLike;
    /** 路线时长。 */
    duration?: NumericLike;
    /** 行驶速度。 */
    speed?: NumericLike;
    /** 方向角。 */
    direction?: NumericLike;
    /** 行程状态。 */
src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx
@@ -7,10 +7,14 @@
};
export default function BluetoothOperateCard({ description, actionText = '', onClick }: Props) {
    const iconSrc = actionText === '去开启'
        ? "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/close-bluetooth.png"
        : "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/bluetooth-icon.png";
    return (
        <View className="bluetooth-operate-card" onClick={onClick}>
            <View className="bluetooth-operate-card-left">
                <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/bluetooth-icon.png" />
                <Image src={iconSrc} />
                <View>{description}</View>
            </View>
            {!!actionText && (
src/robotaxi/pages/begin-route/hooks/runtime-sync.ts
@@ -26,8 +26,6 @@
  duration: number | null;
  /** 车辆朝向(0-359) */
  direction: number | null;
  /** 当前速度(m/s)。 */
  speedMps: number | null;
  /** 消息时间戳(用于乱序保护) */
  timestamp: number | null;
  /** 已行驶轨迹点(servedPoints) */
@@ -38,6 +36,8 @@
  eraseVehiclePointMode: VehiclePointMode;
  /** 非擦线态车辆点策略 */
  normalVehiclePointMode: VehiclePointMode;
  /** 当前车速 (m/s) */
  speed: number | null;
};
/**
@@ -67,34 +67,22 @@
}
/**
 * 将后端下发的速度统一规整为 m/s。
 */
export function normalizeSpeedMps(rawSpeed: any) {
  const speedMetersPerSecond = Number(rawSpeed);
  if (!Number.isFinite(speedMetersPerSecond)) {
    return null;
  }
  return Math.max(0, speedMetersPerSecond);
}
/**
 * 归一化 socket 路线消息,统一路线点、概要和车辆移动点结构。
 */
export function normalizeSocketRouteSnapshot(payload: any): RouteSnapshot {
  const route = safeParse(payload?.content?.route, {});
  const timestamp = Number(payload?.data?.timestamp);
  const speedMps = normalizeSpeedMps(route?.speed);
  return {
    points: normalizeRoutePoints(payload?.points),
    distance: normalizeDistanceKm(Number(route?.distance)),
    duration: normalizeDurationSeconds(Number(route?.duration)),
    direction: normalizeDirection(payload?.content?.direction),
    speedMps,
    timestamp: Number.isFinite(timestamp) ? timestamp : null,
    vehicleMoveAlongPoints: normalizeRoutePoints(payload?.servedPoints),
    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/trip-motion-utils.ts
@@ -269,3 +269,29 @@
  }
  return Math.max(searchStartDistance, bestProjectedDistance);
}
/**
 * 根据最大距离截取路线片段(用于车速分级自适应缩放视口)。
 * 从起点开始,累加线段距离,直到超过 maxDistanceMeters,截取这部分点返回。
 */
export function sliceRoutePointsByDistance(points: RoutePoint[], maxDistanceMeters: number): RoutePoint[] {
  if (!Array.isArray(points) || points.length <= 1) {
    return points;
  }
  const result: RoutePoint[] = [points[0]];
  let accumulatedDistance = 0;
  for (let index = 1; index < points.length; index += 1) {
    const prev = points[index - 1];
    const curr = points[index];
    const segmentDistance = getDistanceMeters(prev, curr);
    if (accumulatedDistance + segmentDistance > maxDistanceMeters) {
      const ratio = (maxDistanceMeters - accumulatedDistance) / segmentDistance;
      result.push(interpolatePoint(prev, curr, ratio));
      break;
    } else {
      accumulatedDistance += segmentDistance;
      result.push(curr);
    }
  }
  return result;
}
src/robotaxi/pages/begin-route/hooks/use-active-trip-scale.ts
@@ -1,7 +1,7 @@
import Taro from '@tarojs/taro';
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useEffect, useRef } from 'react';
import { TripStatus } from '../constants/trip-status';
import useVehicleSpeedScale from './use-vehicle-speed-scale';
import { sliceRoutePointsByDistance } from './trip-motion-utils';
type RoutePoint = {
  latitude: number;
@@ -11,51 +11,91 @@
type Deps = {
  /** 当前行程状态。 */
  tripStatus: TripStatus;
  /** 当前速度,单位 m/s。 */
  speedMps: number | null;
  /** 当前地图比例尺。 */
  mapScale: number;
  /** 设置地图比例尺。 */
  setMapScale: (value: number) => void;
  /** 动态缩放生效后同步车辆视口中心。 */
  syncActiveTripViewportToVehicle: () => void;
  /** 车辆速度 (m/s) */
  vehicleSpeed: number | null;
  /** 当前路线点集 */
  routePoints: RoutePoint[];
  /** 获取速度缩放暂停截止时间 */
  getDynamicScaleHoldUntil: () => number;
  /** 计算 includePoints 所需底部留白。 */
  computeBottomPadding: (points: any[], viewportType: string) => number;
  /** 读取顶部安全区高度。 */
  resolveSafeTopInset: () => number;
  /** 暂停动态缩放。 */
  holdDynamicScale: (durationMs?: number) => void;
  /** 读取动态缩放暂停截止时间。 */
  getDynamicScaleHoldUntil: () => number;
};
const ACTIVE_TRIP_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService]; // 启用速度缩放的行程状态
const USER_GESTURE_CAUSED_BY = new Set(['gesture', 'drag', 'scale']); // 需要暂停速度缩放的用户手势来源
const USER_GESTURE_HOLD_MS = 3000; // 用户手势后暂停速度缩放时长
const ORDER_UPDATE_LINE_HOLD_MS = 2500; // ORDER_UPDATE_LINE 全路线入屏后暂停速度缩放时长
const ACTIVE_TRIP_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService]; // 启用视口编排的行程状态
const USER_GESTURE_CAUSED_BY = new Set(['gesture', 'drag', 'scale']); // 需要暂停动态缩放的用户手势来源
const USER_GESTURE_HOLD_MS = 3000; // 用户手势后暂停动态缩放时长
const ORDER_UPDATE_LINE_HOLD_MS = 2500; // ORDER_UPDATE_LINE 全路线入屏后暂停动态缩放时长
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 = 8000; // 动态缩放巡航周期
/**
 * 接驾中/行程中动态缩放入口:速度缩放单独交给专用 Hook,这里仅保留视口编排职责。
 * 接驾中/行程中视口编排入口:仅保留视口编排职责。
 */
export default function useActiveTripScale(deps: Deps) {
  const isActiveStatus = useMemo(() => {
    return ACTIVE_TRIP_STATUSES.includes(deps.tripStatus);
  }, [deps.tripStatus]);
  useVehicleSpeedScale({
    tripStatus: deps.tripStatus,
    speedMps: deps.speedMps,
    mapScale: deps.mapScale,
    setMapScale: deps.setMapScale,
    syncActiveTripViewportToVehicle: deps.syncActiveTripViewportToVehicle,
    getDynamicScaleHoldUntil: deps.getDynamicScaleHoldUntil
  });
  const depsRef = useRef(deps);
  useEffect(() => {
    depsRef.current = deps;
  }, [deps]);
  /**
   * 处理地图手势,手动缩放/拖拽后短暂暂停速度接管。
   * 基于车速区间进行路线截取,推导视口缩放。
   */
  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;
    const points = currentDeps.routePoints;
    if (!Array.isArray(points) || points.length < 2) {
      return;
    }
    let targetDistance = 5000; // 高速段 5公里
    if (speed < 8.3) { // 约 30km/h
      targetDistance = 300; // 低速段 300米
    } else if (speed < 16.6) { // 约 60km/h
      targetDistance = 1500; // 中速段 1.5公里
    }
    const slicedPoints = sliceRoutePointsByDistance(points, targetDistance);
    if (slicedPoints.length < 2) {
      return;
    }
    const mapCtx = Taro.createMapContext('map');
    const bottomPadding = currentDeps.computeBottomPadding(slicedPoints, 'route');
    const topPadding = Math.max(ORDER_UPDATE_LINE_TOP_PADDING, currentDeps.resolveSafeTopInset() + ORDER_UPDATE_LINE_TOP_EXTRA_PADDING);
    mapCtx.includePoints({
      points: slicedPoints,
      padding: [topPadding, ORDER_UPDATE_LINE_SIDE_PADDING, bottomPadding, ORDER_UPDATE_LINE_SIDE_PADDING]
    });
  }, []);
  useEffect(() => {
    if (!isActiveStatus) return;
    const timer = setInterval(() => {
      tryApplySpeedBasedScale();
    }, DYNAMIC_SCALE_INTERVAL_MS);
    return () => clearInterval(timer);
  }, [isActiveStatus, tryApplySpeedBasedScale]);
  /**
   * 处理地图手势,手动缩放/拖拽后短暂暂停视口同步。
   */
  const handleRegionChange = useCallback((event: any) => {
    const causedBy = String(event?.causedBy || event?.detail?.causedBy || '').toLowerCase();
src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts
@@ -55,8 +55,6 @@
    distanceMeters: number | null;
    /** 预估行程时长,单位秒。 */
    durationSeconds: number | null;
    /** 预估速度,单位米/秒。 */
    speedMps: number | null;
  };
  /** 路线备选项。 */
  routeOptions?: any[];
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts
@@ -105,8 +105,6 @@
    distanceMeters: number | null;
    /** 时长,单位秒。 */
    durationSeconds: number | null;
    /** 速度,单位 m/s。 */
    speedMps: number | null;
  }) => void;
  /** 设置当前用户定位。 */
  setCurrentUserLocation?: (location: CurrentUserLocation) => void;
src/robotaxi/pages/begin-route/hooks/use-route-request.ts
@@ -121,7 +121,6 @@
  distance: normalizeDistanceKm(Number(response?.distance)),
  duration: normalizeDurationSeconds(Number(response?.duration)),
  direction,
  speedMps: normalizeSpeedMps(response?.speed),
  timestamp: Date.now(),
  vehicleMoveAlongPoints: [],
  forceFullRedraw: true,
@@ -480,8 +479,7 @@
      if (syncOrderRouteMetrics) {
        deps.setOrderRouteMetrics?.({
          distanceMeters: Number.isFinite(Number(route?.distance)) ? Number(route?.distance) : null,
          durationSeconds: Number.isFinite(Number(route?.duration)) ? Number(route?.duration) : null,
          speedMps: normalizeSpeedMps(route?.speed)
          durationSeconds: Number.isFinite(Number(route?.duration)) ? Number(route?.duration) : null
        });
      }
@@ -562,8 +560,7 @@
        deps.setOrderRouteMetrics?.({
          distanceMeters: Number.isFinite(Number(response?.distance)) ? Number(response?.distance) : null,
          durationSeconds: Number.isFinite(Number(response?.duration)) ? Number(response?.duration) : null,
          speedMps: normalizeSpeedMps(response?.speed)
          durationSeconds: Number.isFinite(Number(response?.duration)) ? Number(response?.duration) : null
        });
        const normalizedDirection = resolveDirectionValue(response);
src/robotaxi/pages/begin-route/hooks/use-vehicle-speed-scale.ts
File was deleted
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -26,7 +26,6 @@
    orderRouteMetrics: {
        distanceMeters: number | null;
        durationSeconds: number | null;
        speedMps: number | null;
    };
    setOrderId: (orderId: string) => void;
    setTripStatus: (status: TripStatus) => void;
@@ -58,11 +57,11 @@
    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;
        durationSeconds: number | null;
        speedMps: number | null;
    }) => void;
    setBeforeUserEndSite: (site: { longitude: number; latitude: number } | null) => void;
    isModifyDestinationReviewVisible?: boolean;
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
@@ -57,7 +57,6 @@
  const route = safeParse(payload?.content?.route, {});
  const distanceMeters = Number(route?.distance);
  const durationSecondsRaw = Number(route?.duration);
  const speedMps = normalizeSpeedMps(route?.speed);
  const distanceKm = normalizeDistanceKm(distanceMeters);
  if (distanceKm !== null) {
    deps.setRouteDistance(distanceKm);
@@ -68,8 +67,7 @@
  }
  deps.setOrderRouteMetrics?.({
    distanceMeters: Number.isFinite(distanceMeters) ? distanceMeters : null,
    durationSeconds: Number.isFinite(durationSecondsRaw) ? durationSecondsRaw : null,
    speedMps
    durationSeconds: Number.isFinite(durationSecondsRaw) ? durationSecondsRaw : null
  });
};
@@ -107,6 +105,9 @@
    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
@@ -99,7 +99,6 @@
type OrderRouteMetrics = {
  distanceMeters: number | null;
  durationSeconds: number | null;
  speedMps: number | null;
};
/**
@@ -203,10 +202,10 @@
  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,
    speedMps: null
    durationSeconds: null
  });
  const [currentUserLocation, setCurrentUserLocation] = useState<CurrentUserLocation>(null); // 当前用户真实定位,避免被地图中心点覆盖
  const [modifyDestinationSocketStatus, setModifyDestinationSocketStatus] = useState<number | null>(null); // 后端推送的修改目的地展示状态
@@ -252,8 +251,7 @@
  useEffect(() => {
    setOrderRouteMetrics({
      distanceMeters: null,
      durationSeconds: null,
      speedMps: null
      durationSeconds: null
    });
    const cachedSocketStatus = Taro.getStorageSync(MODIFY_DESTINATION_SOCKET_STATUS_KEY);
    const cachedOrderId = String(cachedSocketStatus?.orderId || '');
@@ -624,15 +622,13 @@
  } = viewportState; // 视口状态解构
  const activeTripScale = useActiveTripScale({
    tripStatus,
    speedMps: orderRouteMetrics?.speedMps,
    mapScale,
    setMapScale: viewportState.setMapScale,
    syncActiveTripViewportToVehicle,
    vehicleSpeed,
    routePoints,
    getDynamicScaleHoldUntil,
    computeBottomPadding,
    resolveSafeTopInset,
    holdDynamicScale,
    getDynamicScaleHoldUntil
  }); // 行程中速度缩放与 ORDER_UPDATE_LINE 视口编排
    holdDynamicScale
  }); // 行程中 ORDER_UPDATE_LINE 视口编排
  const arrivedMarkerReady = tripStatus !== TripStatus.ArrivedDestination || typeof vehicleDirection === 'number';
  const arrivedTooltipReady = tripStatus !== TripStatus.ArrivedDestination || (arrivedMarkerReady && mapScale >= 20);
  const markerState = useMapMarkers({
@@ -929,6 +925,7 @@
    setVehicleMoveAlongPoints,
    setRouteDistance,
    setRouteDuration,
    setVehicleSpeed,
    setModifyDestinationSocketStatus,
    setOrderRouteMetrics,
    setBeforeUserEndSite,