| src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/utils/subscribe.ts | ●●●●● 补丁 | 查看 | 原始文档 | 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) {