ai-coding
2026-06-13 f681c0d670f01e7b0f249bde4598aa1d7cebb8c9
Merge branch 'feature_v1.0' of http://gitlab.xiaopeng.local:18080/robotaxi1/xp-robotaxi-miniprogram into feature_v1.0
2个文件已修改
35 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/subscribe.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts
@@ -1,6 +1,7 @@
import Taro from '@tarojs/taro';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { TripStatus } from '../../../constants/trip-status';
import { waitForOrderSuccessSubscribePopup } from '../../../../utils/subscribe';
type Deps = {
  tripStatus: TripStatus;
@@ -26,6 +27,7 @@
const BLUETOOTH_ACCESS_LOG_PREFIX = '[蓝牙权限]';
const BLUETOOTH_AUTH_SYNC_DELAY_MS = 300; // 授权完成后首轮刷新延迟,给小程序权限态一点同步时间
const BLUETOOTH_AUTH_PROTECTION_WINDOW_MS = 5000; // 授权成功后的保护窗口,避免瞬时 permission_deny 覆盖 UI
const SUBSCRIBE_POPUP_WAIT_TIMEOUT_MS = 8000; // 进入接驾态时等待订阅弹窗结束的最长兜底时间,超时后仍放行蓝牙初始化
// 蓝牙图标资源:点亮(已授权)与熄灭(未开/未授权)两态
const BLUETOOTH_ICON_ON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/bluetooth-icon.png';
const BLUETOOTH_ICON_OFF = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/close-bluetooth.png';
@@ -352,9 +354,22 @@
        tripStatus
      });
      setStatusReady(false);
      refreshBluetoothState();
      // 方案 B:先等待“下单成功”订阅弹窗结束,再初始化蓝牙
      // (refreshBluetoothState 内部 openBluetoothAdapter 会触发系统蓝牙授权弹窗),
      // 保证订阅弹窗与蓝牙授权弹窗按先后顺序出现,避免叠加;
      // Promise.race 设兜底超时,订阅异常未 complete 时不阻塞蓝牙初始化。
      let cancelled = false;
      const subscribeTimeout = new Promise<void>((resolve) => {
        setTimeout(resolve, SUBSCRIBE_POPUP_WAIT_TIMEOUT_MS);
      });
      Promise.race([waitForOrderSuccessSubscribePopup(), subscribeTimeout]).then(() => {
        if (cancelled) return;
        logBluetoothAccess('订阅弹窗已结束,开始初始化蓝牙');
        refreshBluetoothState();
      });
      startBluetoothStateWatchRef.current();
      return () => {
        cancelled = true;
        stopBluetoothStateWatchRef.current();
      };
    }
src/utils/subscribe.ts
@@ -23,6 +23,8 @@
];
const ORDER_SUCCESS_CACHE_EXPIRE_MS = 10 * 60 * 1000; // 下单成功订阅缓存有效期(毫秒)
let isOrderSuccessSubscribeRequesting = false; // 下单成功订阅请求中标记,避免重复触发
let orderSuccessSubscribePromise: Promise<void> | null = null; // 下单成功订阅弹窗进行中的 Promise,结束后 resolve
let orderSuccessSubscribeResolve: (() => void) | null = null; // 上述 Promise 的 resolve 引用
let pendingOrderSuccessResult: Record<string, string> | null = null; // 点击阶段缓存的订阅返回结果
let pendingOrderSuccessAcceptedTemplateIds: string[] = []; // 点击阶段缓存的已同意模板
let pendingOrderSuccessCapturedAt = 0; // 点击阶段缓存写入时间戳
@@ -144,6 +146,9 @@
export function requestOrderSuccessSubscribeOnTap() {
  if (isOrderSuccessSubscribeRequesting) return;
  isOrderSuccessSubscribeRequesting = true;
  orderSuccessSubscribePromise = new Promise<void>((resolve) => {
    orderSuccessSubscribeResolve = resolve;
  });
  Taro.requestSubscribeMessage({
    tmplIds: ORDER_SUCCESS_SUBSCRIBE_CONFIG.tmplIds,
    success: (result) => {
@@ -157,11 +162,24 @@
    },
    complete: () => {
      isOrderSuccessSubscribeRequesting = false;
      // 订阅弹窗已结束(用户已处理或未弹出),放行等待中的后续原生弹窗
      orderSuccessSubscribeResolve?.();
      orderSuccessSubscribeResolve = null;
      orderSuccessSubscribePromise = null;
    }
  });
}
/**
 * 等待“下单成功”订阅弹窗结束。
 * 若当前没有进行中的订阅弹窗,立即 resolve;否则在订阅弹窗 complete 后 resolve。
 * 用于让蓝牙系统授权等其它原生弹窗排在订阅弹窗之后,避免多个原生弹窗叠加。
 */
export function waitForOrderSuccessSubscribePopup(): Promise<void> {
  return orderSuccessSubscribePromise || Promise.resolve();
}
/**
 * 消费点击阶段缓存结果,并在拿到订单 ID 后补齐频控与后端上报。
 */
async function consumeOrderSuccessSubscribeCache(orderId: string) {