feat(robotaxi/begin-route):增加USER_OPEN_DOOR WebSocket事件处理器,代表当前订单已拉开过车门。去掉之前的前端蓝牙回调、手动点击解锁逻辑
14个文件已修改
216 ■■■■■ 已修改文件
src/robotaxi/api/types/begin-route.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-action-bar.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-request.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/types/begin-route.ts
@@ -459,6 +459,8 @@
    tripStatus?: NumericLike;
    /** 订单状态。 */
    orderStatus?: NumericLike;
    /** 当前订单是否已开过车门。 */
    isOpenDoor?: boolean | NumericLike;
    /** content 扩展数据。 */
    content?: OrderRouteResponseContent;
}
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -18,7 +18,6 @@
    orderTrunkStatus?: boolean;
    doorUnlockedInCurrentPage?: boolean;
    handleOpenOverRoutePopup?: () => void;
    onDoorUnlockSuccess?: () => void;
};
export default function DoorTrunkControl({
@@ -26,8 +25,7 @@
    orderId,
    orderTrunkStatus,
    doorUnlockedInCurrentPage = false,
    handleOpenOverRoutePopup,
    onDoorUnlockSuccess
    handleOpenOverRoutePopup
}: Props) {
    const [trunkStatus, setTrunkStatus] = useState(orderTrunkStatus); // 后备箱开关状态
    const [lockStatus, setLockStatus] = useState(false); // 车门开关状态
@@ -220,9 +218,7 @@
            if (doorType === 'door') {
                // Taro.hideLoading();
                stopLoading(doorType);
                Taro.showToast({ title: '右侧车门已解锁', icon: 'none' });
                setLockStatus(true);
                onDoorUnlockSuccess?.();
                Taro.showToast({ title: '开门指令已发送', icon: 'none' });
                onSuccess?.();
            }
        }).catch(() => {
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-action-bar.tsx
@@ -8,6 +8,7 @@
    handleModifyDestination?: () => void;
    handleShareTrip?: () => void;
    showModifyDestination?: boolean;
    showCancelTrip?: boolean;
};
export default function TripActionBar({
@@ -15,7 +16,8 @@
    handleCancelTrip,
    handleModifyDestination,
    handleShareTrip,
    showModifyDestination = true
    showModifyDestination = true,
    showCancelTrip = false
}: Props) {
    const canShowTripAction = [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus);
    // 按行程状态控制按钮集合,避免在 JSX 中堆叠过多条件判断
@@ -24,7 +26,7 @@
            key: "cancel",
            label: "取消行程",
            icon: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/cancel-trip.png',
            visible: tripStatus === TripStatus.VehicleApproaching,
            visible: showCancelTrip,
            onClick: handleCancelTrip
        },
        {
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
@@ -19,6 +19,7 @@
    isArriveSoon: boolean;
    isPickupCountdownSoon: boolean;
    pickupCountdownSeconds: number | null;
    doorUnlockedInCurrentPage: boolean;
    finishCountdownEnabled: boolean;
    finishCountdownSeconds: number | null;
    limitMinutes: number | null;
@@ -34,6 +35,7 @@
    isArriveSoon,
    isPickupCountdownSoon,
    pickupCountdownSeconds,
    doorUnlockedInCurrentPage,
    finishCountdownEnabled,
    finishCountdownSeconds,
    limitMinutes,
@@ -100,7 +102,9 @@
        [TripStatus.VehicleApproaching]: isArriveSoon
            ? <>车辆即将到达,请准备上车</>
            : <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>,
        [TripStatus.VehicleArrivedPickup]: <>{isPickupCountdownSoon ? '车辆已到达,请尽快上车' : '车辆已到达上车点'}<View className="time" style={isPickupCountdownSoon ? { color: '#f58900' } : undefined}>{pickupCountdownSeconds !== null ? formatCountdown(pickupCountdownSeconds) : '--:--'}</View></>,
        [TripStatus.VehicleArrivedPickup]: doorUnlockedInCurrentPage
            ? <>车辆已到达,请尽快上车</>
            : <>{isPickupCountdownSoon ? '车辆已到达,请尽快上车' : '车辆已到达上车点'}<View className="time" style={isPickupCountdownSoon ? { color: '#f58900' } : undefined}>{pickupCountdownSeconds !== null ? formatCountdown(pickupCountdownSeconds) : '--:--'}</View></>,
        [TripStatus.InService]: <>预计<View className="time">{inServiceDurationDisplay.value}</View>{inServiceDurationDisplay.unit}抵达目的地</>,
        [TripStatus.ArrivedDestination]: (finishCountdownEnabled || finishCountdownSeconds !== null)
            ? <>已到达目的地<View className="time">{finishCountdownSeconds !== null ? formatCountdown(finishCountdownSeconds) : '--:--'}</View></>
@@ -113,7 +117,9 @@
    const descMap = {
        [TripStatus.CallingCar]: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' },
        [TripStatus.VehicleApproaching]: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: 'calling-car-content-warning' } : null,
        [TripStatus.VehicleArrivedPickup]: { text: isPickupCountdownSoon ? `车辆到达后最多等待${limitMinutes}分钟,超时自动取消` : '此处限停,请在倒计时结束前到达上车点', className: 'calling-car-content-desc' },
        [TripStatus.VehicleArrivedPickup]: doorUnlockedInCurrentPage
            ? { text: '车门已解锁,请尽快上车', className: 'calling-car-content-desc' }
            : { text: isPickupCountdownSoon ? `车辆到达后最多等待${limitMinutes}分钟,超时自动取消` : '此处限停,请在倒计时结束前到达上车点', className: 'calling-car-content-desc' },
        [TripStatus.InService]: { text: '行程已开始,请全程系好安全带', className: 'calling-car-content-desc' },
        [TripStatus.ArrivedDestination]: { text: '倒计时结束时车辆将自动驶离,请带好物品安全下车', className: 'calling-car-content-desc' },
        [TripStatus.TripFinished]: { text: '订单待支付,请核对后完成支付', className: 'calling-car-content-desc' }
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
@@ -10,6 +10,7 @@
type Props = {
    tripStatus: TripStatus;
    orderId?: string;
    doorUnlockedInCurrentPage?: boolean;
    estimatedWaitMinutes?: number;
    finishCountdownEnabled: boolean;
    handleCancelTimeout?: () => void;
@@ -25,6 +26,7 @@
export default function useTripCountdown({
    tripStatus,
    orderId,
    doorUnlockedInCurrentPage = false,
    estimatedWaitMinutes,
    finishCountdownEnabled,
    handleCancelTimeout,
@@ -41,6 +43,7 @@
    const finishTimerRef = useRef<any>(null);
    const countdownFinishedRef = useRef(false);
    const pickupTripStatusRef = useRef(tripStatus);
    const pickupDoorUnlockedRef = useRef(doorUnlockedInCurrentPage);
    const pickupEndTimeRef = useRef<number | null>(null);
    const finishEndTimeRef = useRef<number | null>(null);
    const lastFareRequestKeyRef = useRef<string | null>(null); // 费用明细请求去重键(订单+优惠券)
@@ -138,15 +141,32 @@
    }, [tripStatus]);
    useEffect(() => {
        pickupDoorUnlockedRef.current = doorUnlockedInCurrentPage;
    }, [doorUnlockedInCurrentPage]);
    const stopPickupCountdown = useCallback((clearStoredEndTime = false) => {
        clearPickupTimer();
        pickupEndTimeRef.current = null;
        if (clearStoredEndTime) {
            Taro.removeStorageSync(PICKUP_END_TIME_KEY);
        }
        setPickupCountdownSeconds(null);
    }, [clearPickupTimer]);
    useEffect(() => {
        if (tripStatus !== TripStatus.VehicleArrivedPickup) {
            clearPickupTimer();
            stopPickupCountdown(false);
            return;
        }
        if (doorUnlockedInCurrentPage) {
            stopPickupCountdown(true);
            return;
        }
        const runPickupTick = () => {
            // 防止状态已切换后,旧定时回调仍触发取消逻辑
            if (pickupTripStatusRef.current !== TripStatus.VehicleArrivedPickup) {
                clearPickupTimer();
            if (pickupTripStatusRef.current !== TripStatus.VehicleArrivedPickup || pickupDoorUnlockedRef.current) {
                stopPickupCountdown(pickupDoorUnlockedRef.current);
                return;
            }
            const remaining = getRemainingSeconds(pickupEndTimeRef.current);
@@ -200,7 +220,7 @@
        return () => {
            clearPickupTimer();
        };
    }, [tripStatus, orderId, handleCancelTimeout, clearPickupTimer]);
    }, [tripStatus, orderId, doorUnlockedInCurrentPage, handleCancelTimeout, clearPickupTimer, stopPickupCountdown]);
    useEffect(() => {
        if (tripStatus !== TripStatus.ArrivedDestination) {
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -84,7 +84,6 @@
    handleCloseSuccessAuthPopup?: () => void;
    handleAuthorizeBluetooth?: () => void;
    authSuccess?: boolean;
    onDoorUnlockSuccess?: () => void;
    pageDidShowVersion?: number;
    onOpenCouponSelect?: () => void;
    queuePollingTimeSeconds?: number;
@@ -131,7 +130,6 @@
    handleCloseSuccessAuthPopup,
    handleAuthorizeBluetooth,
    authSuccess = false,
    onDoorUnlockSuccess,
    pageDidShowVersion = 0,
    onOpenCouponSelect,
    queuePollingTimeSeconds = 10
@@ -142,6 +140,8 @@
    const isArriveSoon = tripStatus === TripStatus.VehicleApproaching && (typeof routeDurationSeconds === 'number' && routeDurationSeconds <= 60);
    /** 上车倒计时即将结束(<60秒) */
    const isPickupCountdownSoon = tripStatus === TripStatus.VehicleArrivedPickup && (typeof pickupCountdownSeconds === 'number' && pickupCountdownSeconds < 60);
    /** 开门后,接驾态不再展示取消行程。 */
    const showCancelTrip = [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) && !doorUnlockedInCurrentPage;
    // 按状态组合渲染行程卡片模块
    return (
@@ -156,6 +156,7 @@
                isArriveSoon={isArriveSoon}
                isPickupCountdownSoon={isPickupCountdownSoon}
                pickupCountdownSeconds={pickupCountdownSeconds}
                doorUnlockedInCurrentPage={doorUnlockedInCurrentPage}
                finishCountdownEnabled={finishCountdownEnabled}
                finishCountdownSeconds={finishCountdownSeconds}
                limitMinutes={limitMinutes}
@@ -205,7 +206,6 @@
                        orderTrunkStatus={orderTrunkStatus}
                        doorUnlockedInCurrentPage={doorUnlockedInCurrentPage}
                        handleOpenOverRoutePopup={handleOpenOverRoutePopup}
                        onDoorUnlockSuccess={onDoorUnlockSuccess}
                    />
                    <FareDetailCard
@@ -224,6 +224,7 @@
                <TripActionBar
                    tripStatus={tripStatus}
                    handleCancelTrip={handleCancelTrip}
                    showCancelTrip={showCancelTrip}
                    handleModifyDestination={handleModifyDestination}
                    showModifyDestination={showModifyDestination}
                    handleShareTrip={handleShareTrip}
@@ -236,7 +237,6 @@
                orderTrunkStatus={orderTrunkStatus}
                doorUnlockedInCurrentPage={doorUnlockedInCurrentPage}
                handleOpenOverRoutePopup={handleOpenOverRoutePopup}
                onDoorUnlockSuccess={onDoorUnlockSuccess}
            />
            <EvaluationCard
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts
@@ -7,7 +7,6 @@
type Deps = {
  orderId: string;
  tripStatus: TripStatus;
  onBluetoothDoorUnlockSuccess?: () => void;
};
type BluetoothTokenPayload = {
@@ -17,7 +16,6 @@
type ConnectStatus = 0 | 1 | 2 | 3;
const RETRY_UNLOCK_MESSAGE = '请点击解锁按钮解锁车门'; // 鉴权失败/解锁失败统一提示文案
const UNLOCK_SUCCESS_MESSAGE = '车门已解锁,请上车'; // 解锁成功提示文案
const BLUETOOTH_AUTH_ACTIVE_TRIP_STATUSES = [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup] as const; // 仅这两个状态允许处理蓝牙鉴权回调
/**
@@ -50,7 +48,7 @@
/**
 * 蓝牙车控编排:负责凭证获取、蓝牙连接和 RtAuth 鉴权。
 */
export default function useBluetoothCarControl({ orderId, tripStatus, onBluetoothDoorUnlockSuccess }: Deps) {
export default function useBluetoothCarControl({ orderId, tripStatus }: Deps) {
  const managerRef = useRef<any>(null); // 蓝牙管理器实例引用
  const lastStartKeyRef = useRef(''); // 最近一次启动鉴权的参数签名
  const startAuthTaskRef = useRef<Promise<void> | null>(null); // 当前进行中的鉴权任务,防止并发重复请求
@@ -59,7 +57,6 @@
  const deviceModelRef = useRef(resolveDeviceModel()); // 当前设备型号缓存
  const latestTripStatusRef = useRef(tripStatus); // 最新行程状态引用,供异步蓝牙回调读取
  const unlockFinishedRef = useRef(false); // 当前轮次是否已出现解锁成功
  const manualDoorUnlockRef = useRef(false); // 当前轮次是否已手动解锁,命中后停止后续蓝牙鉴权
  const acceptBluetoothCallbacksRef = useRef(false); // 当前轮次是否继续接收蓝牙回调,离开 4/5 或收口后立即关闭
  const [connectStatus, setConnectStatus] = useState<ConnectStatus>(0); // 蓝牙连接状态:0断开/1连接中/2已连接/3已鉴权
  const [authSuccess, setAuthSuccess] = useState(false); // RtAuth 鉴权是否成功
@@ -77,24 +74,6 @@
    });
  }; // 统一展示蓝牙解锁结果提示
  latestTripStatusRef.current = tripStatus;
  /**
   * 收口蓝牙真实解锁成功后的状态、提示与外部 UI 联动。
   */
  const handleBluetoothDoorUnlockSuccess = () => {
    if (unlockFinishedRef.current) {
      return;
    }
    unlockFinishedRef.current = true;
    setAuthError('');
    onBluetoothDoorUnlockSuccess?.();
    const storageKey = `ROBOTAXI_UNLOCK_TOAST_SHOWN_${orderId}`;
    if (!Taro.getStorageSync(storageKey)) {
      showUnlockToast(UNLOCK_SUCCESS_MESSAGE);
      Taro.setStorageSync(storageKey, true);
    }
    closeConnectionRef.current();
  };
  /**
   * 判断当前蓝牙异步回调是否仍属于有效轮次。
@@ -192,14 +171,14 @@
        console.log('[蓝牙] 解锁通知:', result);
        if (result?.type === 'pollingUnlock') {
          const isAlreadyUnlocked = Number(result?.data?.result) === 2; // RtNotification 挂载在 data,result=2 表示车辆已解锁
          if (isAlreadyUnlocked) {
            handleBluetoothDoorUnlockSuccess();
            return;
          }
          if (result?.success) {
            handleBluetoothDoorUnlockSuccess();
          if (isAlreadyUnlocked || result?.success) {
            unlockFinishedRef.current = true;
            setAuthError('');
            closeConnectionRef.current();
            setIsAuthorizing(false);
          } else {
            setAuthError(RETRY_UNLOCK_MESSAGE);
            showUnlockToast(RETRY_UNLOCK_MESSAGE);
            showUnlockToast(RETRY_UNLOCK_MESSAGE);
          }
          return;
@@ -230,10 +209,6 @@
    });
    if (!BLUETOOTH_AUTH_ACTIVE_TRIP_STATUSES.includes(tripStatus)) return;
    if (!orderId) return;
    if (manualDoorUnlockRef.current) {
      console.log('[蓝牙] 当前轮次已手动解锁,跳过后续鉴权请求');
      return;
    }
    if (startAuthTaskRef.current) return startAuthTaskRef.current;
    const startKey = `${orderId}`;
@@ -248,12 +223,6 @@
    const runTask = async () => {
      try {
        const tokenRes = await getBluetoothUnlockToken(orderId);
        if (manualDoorUnlockRef.current) {
          console.log('[蓝牙] 用户已手动解锁,忽略鉴权凭证返回结果');
          isAuthorizingRef.current = false;
          setIsAuthorizing(false);
          return;
        }
        const tokenPayload: BluetoothTokenPayload = typeof tokenRes === 'object' && tokenRes !== null ? tokenRes as BluetoothTokenPayload : {};
        const certificate = typeof tokenPayload.token === 'string' ? tokenPayload.token : String(tokenPayload.token || '');
        const mainMacAddress = normalizeMac(tokenPayload.macAddress);
@@ -279,21 +248,9 @@
          certificate,
          deviceModel: deviceModelRef.current
        });
        if (manualDoorUnlockRef.current) {
          console.log('[蓝牙] 用户已手动解锁,停止打开蓝牙适配器');
          isAuthorizingRef.current = false;
          setIsAuthorizing(false);
          return;
        }
        console.log('[蓝牙] 准备打开蓝牙适配器');
        manager.openBluetoothAdapter();
      } catch (error: any) {
        if (manualDoorUnlockRef.current) {
          console.log('[蓝牙] 手动解锁后忽略鉴权异常:', error);
          isAuthorizingRef.current = false;
          setIsAuthorizing(false);
          return;
        }
        console.log('[蓝牙] 鉴权流程异常:', error);
        isAuthorizingRef.current = false;
        setAuthError(error?.msg || '获取蓝牙鉴权凭证失败');
@@ -314,10 +271,6 @@
   */
  const retryAuth = useCallback(() => {
    console.log('[蓝牙] 重试鉴权');
    if (manualDoorUnlockRef.current) {
      console.log('[蓝牙] 当前轮次已手动解锁,忽略重试鉴权');
      return;
    }
    if (!managerRef.current) {
      startAuth();
      return;
@@ -329,22 +282,6 @@
    setUnlockTokenFailed(false);
    managerRef.current.retryConnection();
  }, [startAuth]);
  /**
   * 用户手动解锁成功后,停止当前轮次剩余的蓝牙鉴权流程。
   */
  const markManualDoorUnlock = useCallback(() => {
    console.log('[蓝牙] 用户已手动解锁,停止后续蓝牙鉴权');
    manualDoorUnlockRef.current = true;
    unlockFinishedRef.current = true;
    if (orderId) {
      Taro.setStorageSync(`ROBOTAXI_UNLOCK_TOAST_SHOWN_${orderId}`, true);
    }
    acceptBluetoothCallbacksRef.current = false;
    setAuthError('');
    setUnlockTokenFailed(false);
    closeConnectionRef.current();
  }, [orderId]);
  useEffect(() => {
    if (!BLUETOOTH_AUTH_ACTIVE_TRIP_STATUSES.includes(tripStatus)) {
@@ -358,16 +295,10 @@
      authSuccessRef.current = false;
      isAuthorizingRef.current = false;
      unlockFinishedRef.current = false;
      manualDoorUnlockRef.current = false; // 重置手动解锁状态,供下一次使用
      setAuthError('');
      setUnlockTokenFailed(false);
      // 行程进入 6(InService 行驶中)或以上,或者是回退/取消等情况时,清除该订单已弹出过成功解锁的标识缓存
      if (orderId) {
        Taro.removeStorageSync(`ROBOTAXI_UNLOCK_TOAST_SHOWN_${orderId}`);
      }
    }
  }, [tripStatus, orderId]);
  }, [tripStatus]);
  return {
    connectStatus,
@@ -376,7 +307,6 @@
    isAuthorizing,
    unlockTokenFailed,
    startAuth,
    markManualDoorUnlock,
    retryAuth,
    closeConnection
  };
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts
@@ -108,6 +108,8 @@
    /** 时长,单位秒。 */
    durationSeconds: number | null;
  }) => void;
  /** 根据订单路线接口回显同步当前订单开门状态。 */
  syncDoorUnlockedStateFromOrderRoute?: (isOpenDoor: boolean) => void;
  /** 设置当前用户定位。 */
  setCurrentUserLocation?: (location: CurrentUserLocation) => void;
};
src/robotaxi/pages/begin-route/hooks/use-route-request.ts
@@ -101,6 +101,15 @@
  return ((direction % 360) + 360) % 360;
};
/** 统一解析订单是否已开门。 */
const resolveDoorOpenedValue = (payload: any) => {
  const rawValue = payload?.isOpenDoor ?? payload?.content?.isOpenDoor;
  if (rawValue === true || rawValue === 1 || rawValue === '1' || rawValue === 'true') {
    return true;
  }
  return false;
};
/** 归一化路线选项,补齐校验字段。 */
const normalizeRouteOption = (item: any, snapshotId: any, generatedAtMillis: any) => ({
  ...(item || {}),
@@ -514,6 +523,7 @@
    requestHandler({ orderId: targetOrderId, routeType })
      .then((response: any) => {
        deps.syncDoorUnlockedStateFromOrderRoute?.(resolveDoorOpenedValue(response));
        const beforeUserSite = parseBeforeUserEndSite(
          response?.endLongitude,
          response?.endLatitude ?? response?.endlatitude
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -166,8 +166,6 @@
  /** 去授权蓝牙 */
  handleAuthorizeBluetooth?: () => void;
  bluetoothAuthSuccess?: boolean;
  /** 手动解锁车门成功 */
  onDoorUnlockSuccess?: () => void;
  /** 页面 didShow 版本号 */
  pageDidShowVersion: number;
  /** 排队位次轮询频率(秒) */
@@ -224,6 +222,7 @@
  } = useTripCountdown({
    tripStatus: deps.tripStatus,
    orderId: deps.orderId,
    doorUnlockedInCurrentPage: deps.doorUnlockedInCurrentPage,
    estimatedWaitMinutes: deps.fareEstimate?.waitingTime,
    finishCountdownEnabled: deps.finishCountdownEnabled,
    handleCancelTimeout: deps.handlePickupTimeoutAutoCancel,
@@ -515,7 +514,6 @@
          handleCloseSuccessAuthPopup={deps.handleCloseSuccessAuthPopup}
          handleAuthorizeBluetooth={deps.handleAuthorizeBluetooth}
          authSuccess={deps.bluetoothAuthSuccess}
          onDoorUnlockSuccess={deps.onDoorUnlockSuccess}
          pageDidShowVersion={deps.pageDidShowVersion}
          queuePollingTimeSeconds={deps.queuePollingTimeSeconds}
        />
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -4,6 +4,7 @@
    createOpenTrunkSocketHandler,
    createOrderReassignSocketHandler,
    createOrderUpdatePriceSocketHandler,
  createUserOpenDoorSocketHandler,
    createUpdateEndAddressStatusSocketHandler
} from './web-socket-handlers/create-order-meta-socket-handlers';
import { createOrderRouteSocketHandler } from './web-socket-handlers/create-order-route-socket-handler';
@@ -39,6 +40,7 @@
    } | null;
    setPriceUpdateVersion: (updater: (v: number) => number) => void;
    setFinishCountdownEnabled: (value: boolean) => void;
    setDoorUnlockedInCurrentPage: React.Dispatch<React.SetStateAction<boolean>>;
    setShowAnnouncementsPopup: (value: boolean) => void;
    setRoutePoints: (points: any[]) => void;
    setPolyline: (lines: any[]) => void;
@@ -82,6 +84,7 @@
    | 'order_status'
    | 'order_route'
    | 'open_trunk'
    | 'user_open_door'
    | 'order_update_price'
    | 'order_reassign'
    | 'update_end_address_status';
@@ -92,6 +95,7 @@
    'order_status',
    'order_route',
    'open_trunk',
    'user_open_door',
    'order_update_price',
    'order_reassign',
    'update_end_address_status'
@@ -104,6 +108,7 @@
    order_status: false,
    order_route: false,
    open_trunk: false,
    user_open_door: false,
    order_update_price: false,
    order_reassign: false,
    update_end_address_status: false
@@ -116,6 +121,7 @@
    order_status: null,
    order_route: null,
    open_trunk: null,
    user_open_door: null,
    order_update_price: null,
    order_reassign: null,
    update_end_address_status: null
@@ -159,6 +165,7 @@
            order_status: createOrderStatusSocketHandler(depsRef, lastFetchedVehicleOrderIdRef),
            order_route: createOrderRouteSocketHandler(depsRef),
            open_trunk: createOpenTrunkSocketHandler(depsRef),
            user_open_door: createUserOpenDoorSocketHandler(depsRef),
            order_update_price: createOrderUpdatePriceSocketHandler(depsRef),
            order_reassign: createOrderReassignSocketHandler(depsRef, lastFetchedVehicleOrderIdRef),
            update_end_address_status: createUpdateEndAddressStatusSocketHandler(depsRef)
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts
@@ -20,6 +20,20 @@
};
/**
 * 创建用户开门成功消息处理器。
 */
export const createUserOpenDoorSocketHandler = (depsRef: WebSocketDepsRef) => (payload: any) => {
  executeWhenOrderMatched({
    payload,
    deps: depsRef.current,
    resolveIncomingOrderId: resolvePayloadOrderIdFromTopLevelFirst,
    onMatched: () => {
      depsRef.current.setDoorUnlockedInCurrentPage(true);
    }
  });
};
/**
 * 创建价格更新消息处理器。
 */
export const createOrderUpdatePriceSocketHandler = (depsRef: WebSocketDepsRef) => (payload: any) => {
src/robotaxi/pages/begin-route/index.tsx
@@ -561,6 +561,12 @@
    vehicleMoveAlongPoints,
    setVehicleDirection,
    setOrderRouteMetrics,
    syncDoorUnlockedStateFromOrderRoute: (isOpenDoor) => {
      if (!isOpenDoor) {
        return;
      }
      setDoorUnlockedInCurrentPage(true);
    },
    setCurrentUserLocation
  }, {
    syncSmoothRouteBaselineRef,
@@ -1178,6 +1184,7 @@
    vehicleLocation,
    setPriceUpdateVersion,
    setFinishCountdownEnabled,
    setDoorUnlockedInCurrentPage,
    setShowAnnouncementsPopup,
    setRoutePoints,
    setPolyline,
@@ -1229,13 +1236,6 @@
  useEffect(() => {
    setDoorUnlockedInCurrentPage(false);
  }, [orderId]);
  /**
   * 蓝牙真实解锁成功后,同步当前页面的车锁打开态。
   */
  const handleBluetoothDoorUnlockSuccess = useCallback(() => {
    setDoorUnlockedInCurrentPage(true);
  }, []);
  const { queryPaymentResult: queryOrderPaymentResult } = useOrderPaymentResultQuery({
    orderId,
@@ -1322,20 +1322,11 @@
  const {
    authSuccess: bluetoothAuthSuccess,
    startAuth: startBluetoothAuth,
    markManualDoorUnlock,
    unlockTokenFailed
  } = useBluetoothCarControl({
    orderId,
    tripStatus,
    onBluetoothDoorUnlockSuccess: handleBluetoothDoorUnlockSuccess
    tripStatus
  });
  /**
   * 手动点击开门成功后,同步车锁打开态并停止后续蓝牙自动解锁流程。
   */
  const handleManualDoorUnlockSuccess = useCallback(() => {
    setDoorUnlockedInCurrentPage(true);
    markManualDoorUnlock();
  }, [markManualDoorUnlock]);
  const {
    bluetoothStatusReady,
    bluetoothCardDescription,
@@ -1377,6 +1368,7 @@
  useDidShow(() => {
    setPageDidShowVersion((v) => v + 1);
    setDoorUnlockedInCurrentPage(false);
    handleDidShow(router.params?.skipPageFlag, router.params?.fromModifyDestination);
    updateMapViewportPadding();
  });
@@ -1817,7 +1809,6 @@
    handleCloseSuccessAuthPopup,
    handleAuthorizeBluetooth,
    bluetoothAuthSuccess,
    onDoorUnlockSuccess: handleManualDoorUnlockSuccess,
    pageDidShowVersion,
    queuePollingTimeSeconds
  }); // 视图渲染委托
src/utils/websocket.ts
@@ -850,6 +850,10 @@
      const isOpen = content?.isOpen ?? content?.open;
      this.notifyListeners('open_trunk', { isOpen, data, content });
    }
    if (data?.type === 'USER_OPEN_DOOR') {
      const orderId = content?.orderId ?? data?.orderId;
      this.notifyListeners('user_open_door', { orderId, data, content });
    }
    if (data?.type === 'UPDATE_END_ADDRESS_STATUS') {
      const status = Number(content?.status);
      this.notifyListeners('update_end_address_status', { status, data, content });