feat(robotaxi):
- 新增订单ID参数到路线查询API与请求钩子,支持关联当前订单进行路线查询,同步更新修改目的地钩子的订单关联逻辑
- 修复获取附近站点时未传递type参数的问题,指定type:1为地图查询场景
- 优化无可用站点的提示文案,区分超出服务范围和无可选站点两种情况
- 添加页面级车门解锁状态追踪,订单切换时重置状态避免跨订单状态串用
- 蓝牙车控解锁成功的处理逻辑,统一同步UI状态并暴露回调接口
11个文件已修改
117 ■■■■■ 已修改文件
src/robotaxi/api/types/begin-route.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-modify-destination-preview-route.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-request.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/Robotaxi-public-method.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/types/begin-route.ts
@@ -96,6 +96,8 @@
    startPointId: OrderId | '';
    /** 用户优惠券 ID。 */
    userCouponId?: OrderId | '';
    /** 当前订单 ID。 */
    orderId?: OrderId | '';
}
/** 路线与费用查询对象响应。 */
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -12,6 +12,7 @@
    tripStatus: TripStatus;
    orderId?: string;
    orderTrunkStatus?: boolean;
    doorUnlockedInCurrentPage?: boolean;
    handleOpenOverRoutePopup?: () => void;
    onDoorUnlockSuccess?: () => void;
};
@@ -20,6 +21,7 @@
    tripStatus,
    orderId,
    orderTrunkStatus,
    doorUnlockedInCurrentPage = false,
    handleOpenOverRoutePopup,
    onDoorUnlockSuccess
}: Props) {
@@ -34,6 +36,18 @@
        setTrunkStatus(orderTrunkStatus);
    }, [orderTrunkStatus]);
    // 订单切换后重置车锁展示态,避免上一单状态串到下一单
    useEffect(() => {
        setLockStatus(false);
    }, [orderId]);
    // 蓝牙或手动解锁成功后,同步车锁为打开态
    useEffect(() => {
        if (doorUnlockedInCurrentPage) {
            setLockStatus(true);
        }
    }, [doorUnlockedInCurrentPage]);
    // 组件卸载时清理轮询定时器
    useEffect(() => {
        return () => {
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -63,6 +63,8 @@
    };
    orderId?: string;
    orderTrunkStatus?: boolean;
    /** 当前页面内是否已经解锁过车门 */
    doorUnlockedInCurrentPage?: boolean;
    bluetoothStatusReady?: boolean;
    bluetoothCardDescription?: string;
    bluetoothCardActionText?: string;
@@ -102,6 +104,7 @@
    vehicleInfo,
    orderId,
    orderTrunkStatus,
    doorUnlockedInCurrentPage = false,
    bluetoothStatusReady = false,
    bluetoothCardDescription = '授权并打开蓝牙链接,享受无感出行',
    bluetoothCardActionText = '去开启',
@@ -199,6 +202,7 @@
                        tripStatus={tripStatus}
                        orderId={orderId}
                        orderTrunkStatus={orderTrunkStatus}
                        doorUnlockedInCurrentPage={doorUnlockedInCurrentPage}
                        handleOpenOverRoutePopup={handleOpenOverRoutePopup}
                        onDoorUnlockSuccess={onDoorUnlockSuccess}
                    />
@@ -227,6 +231,7 @@
                tripStatus={tripStatus === TripStatus.ArrivedDestination ? TripStatus.ArrivedDestination : -1}
                orderId={orderId}
                orderTrunkStatus={orderTrunkStatus}
                doorUnlockedInCurrentPage={doorUnlockedInCurrentPage}
                handleOpenOverRoutePopup={handleOpenOverRoutePopup}
                onDoorUnlockSuccess={onDoorUnlockSuccess}
            />
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts
@@ -7,6 +7,7 @@
type Deps = {
  orderId: string;
  tripStatus: TripStatus;
  onBluetoothDoorUnlockSuccess?: () => void;
};
type BluetoothTokenPayload = {
@@ -49,7 +50,7 @@
/**
 * 蓝牙车控编排:负责凭证获取、蓝牙连接和 RtAuth 鉴权。
 */
export default function useBluetoothCarControl({ orderId, tripStatus }: Deps) {
export default function useBluetoothCarControl({ orderId, tripStatus, onBluetoothDoorUnlockSuccess }: Deps) {
  const managerRef = useRef<any>(null); // 蓝牙管理器实例引用
  const lastStartKeyRef = useRef(''); // 最近一次启动鉴权的参数签名
  const startAuthTaskRef = useRef<Promise<void> | null>(null); // 当前进行中的鉴权任务,防止并发重复请求
@@ -75,6 +76,24 @@
    });
  }; // 统一展示蓝牙解锁结果提示
  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();
  };
  /**
   * 判断当前蓝牙异步回调是否仍属于有效轮次。
@@ -173,31 +192,11 @@
        if (result?.type === 'pollingUnlock') {
          const isAlreadyUnlocked = Number(result?.data?.result) === 2; // RtNotification 挂载在 data,result=2 表示车辆已解锁
          if (isAlreadyUnlocked) {
            if (unlockFinishedRef.current) {
              return;
            }
            unlockFinishedRef.current = true;
            setAuthError('');
            const storageKey = `ROBOTAXI_UNLOCK_TOAST_SHOWN_${orderId}`;
            if (!Taro.getStorageSync(storageKey)) {
              showUnlockToast(UNLOCK_SUCCESS_MESSAGE);
              Taro.setStorageSync(storageKey, true);
            }
            closeConnectionRef.current();
            handleBluetoothDoorUnlockSuccess();
            return;
          }
          if (result?.success) {
            if (unlockFinishedRef.current) {
              return;
            }
            unlockFinishedRef.current = true;
            setAuthError('');
            const storageKey = `ROBOTAXI_UNLOCK_TOAST_SHOWN_${orderId}`;
            if (!Taro.getStorageSync(storageKey)) {
              showUnlockToast(UNLOCK_SUCCESS_MESSAGE);
              Taro.setStorageSync(storageKey, true);
            }
            closeConnectionRef.current();
            handleBluetoothDoorUnlockSuccess();
          } else {
            setAuthError(RETRY_UNLOCK_MESSAGE);
            showUnlockToast(RETRY_UNLOCK_MESSAGE);
src/robotaxi/pages/begin-route/hooks/use-modify-destination-preview-route.ts
@@ -1,6 +1,7 @@
import Taro from '@tarojs/taro';
import { useCallback, useEffect, useRef, useState } from 'react';
import { TripStatus } from '../../../constants/trip-status';
import { getOrderId } from '../../../utils/order';
/** 已选优惠券缓存键。 */
const SELECTED_COUPON_ID_STORAGE_KEY = 'selectedCouponId';
@@ -8,11 +9,13 @@
type RequestRouteOptions = {
  startPointId?: string | number;
  force?: boolean;
  orderId?: string;
  syncOrderRouteMetrics?: boolean;
};
type Deps = {
  tripStatus: TripStatus;
  orderId?: string;
  isModifyDestinationReviewVisible: boolean;
  hasLocalDestinationOverride: boolean;
  effectiveEndSite: any;
@@ -34,6 +37,7 @@
  destLat: number;
  destLng: number;
  startPointId: string | number;
  orderId: string;
  requestKey: string;
};
@@ -82,13 +86,15 @@
  if (startPointId === '' || startPointId === null || startPointId === undefined) {
    return null;
  }
  const currentOrderId = getOrderId(deps.orderId);
  return {
    originLat,
    originLng,
    destLat,
    destLng,
    startPointId,
    requestKey: `${deps.tripStatus}|${Number(targetEndSite?.id || 0)}|${startPointId}|${originLng},${originLat}-${destLng},${destLat}`
    orderId: currentOrderId,
    requestKey: `${deps.tripStatus}|${currentOrderId}|${Number(targetEndSite?.id || 0)}|${startPointId}|${originLng},${originLat}-${destLng},${destLat}`
  };
};
@@ -131,6 +137,7 @@
      {
        startPointId: routeContext.startPointId,
        force: true,
        orderId: routeContext.orderId,
        syncOrderRouteMetrics: false
      },
    );
@@ -196,6 +203,7 @@
      {
        startPointId: routeContext.startPointId,
        force: true,
        orderId: routeContext.orderId,
        syncOrderRouteMetrics: false
      },
    );
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts
@@ -43,6 +43,8 @@
  couponId?: string;
  /** 起点站点 ID。 */
  startPointId?: string | number;
  /** 当前订单 ID。 */
  orderId?: string;
  /** 是否同步原订单剩余里程/时长指标。 */
  syncOrderRouteMetrics?: boolean;
};
src/robotaxi/pages/begin-route/hooks/use-route-request.ts
@@ -353,6 +353,7 @@
    const refreshMode = resolvedOptions.refreshMode || 'full';
    const startPointId = resolvedOptions.startPointId ?? '';
    const syncOrderRouteMetrics = resolvedOptions.syncOrderRouteMetrics !== false;
    const targetOrderId = String(resolvedOptions.orderId || '').trim();
    const now = Date.now();
    if (now - lastRouteAndFareRequestAtRef.current < 1000) {
@@ -386,6 +387,9 @@
      if (couponId) {
        requestParams.userCouponId = couponId;
      }
      if (targetOrderId) {
        requestParams.orderId = targetOrderId;
      }
      const response = await queryRouteAndFare(requestParams);
      setRouteAndFareError(false);
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -113,6 +113,8 @@
  onToggleRouteSelect: (expanded: boolean) => void;
  /** 后备箱状态 */
  trunkStatus: boolean;
  /** 当前页面内是否已经解锁过车门 */
  doorUnlockedInCurrentPage: boolean;
  /** 路线选项集合 */
  routeOptions: any[];
  /** 选中路线索引 */
@@ -451,6 +453,7 @@
          routeDurationSeconds={deps.routeDuration}
          onToggleDetailFade={deps.handleToggleDetailFade}
          orderTrunkStatus={deps.trunkStatus}
          doorUnlockedInCurrentPage={deps.doorUnlockedInCurrentPage}
          bluetoothStatusReady={deps.bluetoothStatusReady}
          bluetoothCardDescription={deps.bluetoothCardDescription}
          bluetoothCardActionText={deps.bluetoothCardActionText}
src/robotaxi/pages/begin-route/index.tsx
@@ -188,6 +188,7 @@
  const requoteTriggeredRef = useRef(false); // 重新报价触发标记,防止重复触发
  const [callSubmitting, setCallSubmitting] = useState(false); // 呼叫车辆提交中状态
  const [orderId, setOrderId] = useState<string>(''); // 订单ID
  const [doorUnlockedInCurrentPage, setDoorUnlockedInCurrentPage] = useState(false); // 当前页面内是否已解锁过车门
  const [finishCountdownEnabled, setFinishCountdownEnabled] = useState(false); // 完成倒计时是否启用
  const customCalloutIconUrl = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-tooltip-right.png'; // 自定义气泡图标URL
  const waitingCalloutBgUrl = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-tooltip-bg.png'; // 等待气泡背景图URL
@@ -954,6 +955,20 @@
    Taro.setStorageSync('tripStatus', TripStatus.PaymentSuccess);
  }, []);
  /**
   * 订单切换后重置当前页面内的车门打开态,避免串单。
   */
  useEffect(() => {
    setDoorUnlockedInCurrentPage(false);
  }, [orderId]);
  /**
   * 蓝牙真实解锁成功后,同步当前页面的车锁打开态。
   */
  const handleBluetoothDoorUnlockSuccess = useCallback(() => {
    setDoorUnlockedInCurrentPage(true);
  }, []);
  const { queryPaymentResult: queryOrderPaymentResult } = useOrderPaymentResultQuery({
    orderId,
    onPaySuccess: handlePaymentSuccess
@@ -1042,12 +1057,14 @@
    markManualDoorUnlock
  } = useBluetoothCarControl({
    orderId,
    tripStatus
    tripStatus,
    onBluetoothDoorUnlockSuccess: handleBluetoothDoorUnlockSuccess
  });
  /**
   * 手动点击开门成功后,停止后续蓝牙自动解锁流程。
   * 手动点击开门成功后,同步车锁打开态并停止后续蓝牙自动解锁流程。
   */
  const handleManualDoorUnlockSuccess = useCallback(() => {
    setDoorUnlockedInCurrentPage(true);
    markManualDoorUnlock();
  }, [markManualDoorUnlock]);
  const {
@@ -1067,6 +1084,7 @@
  });
  const { handleConfirmRequoteIfNeeded } = useModifyDestinationPreviewRoute({
    tripStatus,
    orderId,
    isModifyDestinationReviewVisible,
    hasLocalDestinationOverride,
    effectiveEndSite,
@@ -1457,6 +1475,7 @@
    showModifyDestination,
    handleShareTrip,
    trunkStatus,
    doorUnlockedInCurrentPage,
    routeOptions,
    selectedRouteIndex,
    handleSelectRoute,
src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx
@@ -165,9 +165,10 @@
                            <Image onClick={deps.handleCollect} className='collect-icon' src={deps.selectedSite.isCollect === 1 ? deps.activeStar : deps.noStar} />
                          )}
                        </View>
                      ) : (
                      ) : deps.outOfServiceRange ? (
                        <View className="no-service">超出服务范围,请在运营区内选择</View>
                      )}
                      ) : <View className="no-service">附近无可选站点</View>
                      }
                    </View>
                  </ScrollView>
                </View>
src/utils/Robotaxi-public-method.ts
@@ -763,10 +763,10 @@
    const isInside = fenceRes.status === 'inside';
    const electronicAreaId = fenceRes?.electronicAreaId || '';
    // 2. 只有在运营区域内才获取附近站点
    // 2. 只有在运营区域内才获取附近站点  type 传1 地图查询附近站点  不传 查搜索页附近站点
    let sites: any = { points: [] };
    if (isInside) {
      sites = await getNearestSite({ latitude: lat, longitude: lng, electronicAreaId, pointOnOff }).catch(e => {
      sites = await getNearestSite({ latitude: lat, longitude: lng, electronicAreaId, pointOnOff, type:1 }).catch(e => {
        return { points: [] };
      });
      // 兼容处理