| src/pages/robotaxi/home/hooks/use-home-bootstrap.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/robotaxi/home/index.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/api/begin-route.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/hooks/use-order-actions.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/robotaxi/pages/begin-route/hooks/use-route-manager.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/pages/robotaxi/home/hooks/use-home-bootstrap.ts
@@ -83,7 +83,7 @@ const syncAuthAndOrder = async () => { const otp = await deps.checkOtp(false); deps.setIsLogin(!otp); if (!otp) return; if (!otp) return false; wsManager.connect(deps.wsHost); const agreement = await deps.getAgreement(); if (agreement === false) { @@ -100,7 +100,7 @@ Taro.removeStorageSync('endSite'); deps.setTripStatus(0); deps.setOrderData({}); return; return true; } deps.setOrderData(activeOrder || {}); const mappedStatus = toHomeTripStatus(mapOrderStatusToTripStatus(activeOrder.status || 0)); @@ -127,6 +127,7 @@ time: activeOrder.endParkingTimeLimit || 0, }); } return true; }; /** @@ -218,7 +219,7 @@ setIsAdsorbed: deps.setIsAdsorbed }); deps.setTripStatus(toHomeTripStatus(Taro.getStorageSync('tripStatus'))); syncAuthAndOrder(); const authSyncTask = syncAuthAndOrder(); Taro.getSetting({ success: (res) => { const auth = res.authSetting['scope.userLocation']; @@ -226,6 +227,7 @@ processLocationSetting(auth, startSite); } }); return authSyncTask; }; return { src/pages/robotaxi/home/index.tsx
@@ -405,11 +405,21 @@ }, [selectedCity]); useDidShow(() => { bootstrap.onDidShow(); const authSyncTask = bootstrap.onDidShow(); refreshQuickCapsule(); firstEnter() .then((res: any) => { setShowMarketing(resolveMarketingVisible(res)); authSyncTask .then((hasLogin) => { if (!hasLogin) { setShowMarketing(false); return; } return firstEnter() .then((res: any) => { setShowMarketing(resolveMarketingVisible(res)); }) .catch(() => { setShowMarketing(false); }); }) .catch(() => { setShowMarketing(false); @@ -577,7 +587,7 @@ <Image className="select-city-icon" src={selectCityIcon} /> </View> {weatherData && ( <View className="weather-info" onClick={() => setShowMarketing(true)}> <View className="weather-info"> <Image className="weather-icon" src={getWeatherIconByDesc(weatherData.weather)} /> <Text>{weatherData.temperature}°C</Text> </View> src/robotaxi/api/begin-route.ts
@@ -4,7 +4,7 @@ // 导航-获取路线和费用 export const queryRouteAndFare = async (payload) => { return request({ url: _ROBOTAXI_HOST + '/itinerary/base/v1/mini-program/navigation/route-and-fare', url: _ROBOTAXI_HOST + '/itinerary/base/v1/mini-program/navigation/multi-route-and-fare', method: 'POST', payload }, true); @@ -29,10 +29,11 @@ } // 导航-下单前校验路况是否过期 export const routeStaleCheck = async () => { export const routeStaleCheck = async (payload?) => { return request({ url: _ROBOTAXI_HOST + '/itinerary/base/v1/mini-program/navigation/route-stale-check', method: 'POST' method: 'POST', payload }, true); } src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
@@ -120,8 +120,7 @@ <View className='calling-car-content-title'>{titleMap[tripStatus]}</View> )} {descMap[tripStatus] && ( // <View style={{ paddingBottom: tripStatus === TripStatus.CallingCar ? '20px' : '0px' }} className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View> <View className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View> <View style={{ paddingBottom: tripStatus === TripStatus.CallingCar && !queueStatus ? '20px' : '0px' }} className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View> )} {tripStatus === TripStatus.CallingCar && queueStatus && ( <View className='calling-car-content-desc'> src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts
@@ -1,4 +1,4 @@ import Taro, { useDidShow } from '@tarojs/taro'; import Taro from '@tarojs/taro'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { TripStatus } from '../constants/trip-status'; @@ -35,6 +35,11 @@ const adapterStateHandlerRef = useRef<((res: any) => void) | null>(null); // 蓝牙适配器状态监听函数引用 const openSettingPollingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null); // 去开启后的短轮询定时器 const openSettingPollingCountRef = useRef(0); // 去开启后的短轮询计数 const startBluetoothAuthRef = useRef(startBluetoothAuth); // 蓝牙鉴权动作引用,避免函数引用变化导致状态 effect 反复触发 useEffect(() => { startBluetoothAuthRef.current = startBluetoothAuth; }, [startBluetoothAuth]); /** * 停止“去开启”后的短轮询。 @@ -118,9 +123,9 @@ setStatusReady(true); stopOpenSettingPolling(); if (nextStatus === 'authorized') { startBluetoothAuth(); startBluetoothAuthRef.current?.(); } }, [startBluetoothAuth, stopOpenSettingPolling, openBluetoothAdapterSafe]); }, [stopOpenSettingPolling, openBluetoothAdapterSafe]); /** * 开启“去开启”后的短轮询兜底,处理未触发页面 show 的场景。 @@ -183,13 +188,6 @@ setShowAuthBluetoothPopup(false); setShowSuccessAuthPopup(false); }, [tripStatus, refreshBluetoothState, startBluetoothStateWatch, stopBluetoothStateWatch, stopOpenSettingPolling]); // 页面回前台后同步蓝牙状态,避免设置页返回后状态滞后 useDidShow(() => { if ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) { refreshBluetoothState(); } }); // 跳转系统蓝牙设置,并在返回后触发一次状态刷新 const handleOpenSystemBluetoothSetting = useCallback(async () => { src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts
@@ -67,6 +67,7 @@ export default function useBluetoothCarControl({ orderId, tripStatus }: Deps) { const managerRef = useRef<any>(null); // 蓝牙管理器实例引用 const lastStartKeyRef = useRef(''); // 最近一次启动鉴权的参数签名 const startAuthTaskRef = useRef<Promise<void> | null>(null); // 当前进行中的鉴权任务,防止并发重复请求 const candidateMacsRef = useRef<string[]>([]); // 蓝牙地址候选列表(主 + 从) const currentMacIndexRef = useRef(0); // 当前尝试的 MAC 索引 const certificateRef = useRef(''); // 当前会话证书缓存 @@ -75,6 +76,8 @@ const [authSuccess, setAuthSuccess] = useState(false); // RtAuth 鉴权是否成功 const [isAuthorizing, setIsAuthorizing] = useState(false); // 鉴权流程进行中 const [authError, setAuthError] = useState<string>(''); // 鉴权流程错误信息 const authSuccessRef = useRef(false); // 鉴权成功状态引用,避免并发期间闭包值滞后 const isAuthorizingRef = useRef(false); // 鉴权中状态引用,避免并发期间闭包值滞后 const showUnlockToast = useCallback((title: string) => { Taro.showToast({ title, @@ -117,6 +120,8 @@ console.log('[蓝牙] 连接状态变更:', status); setConnectStatus(status); if (status === 3) { authSuccessRef.current = true; isAuthorizingRef.current = false; setAuthSuccess(true); setAuthError(''); setIsAuthorizing(false); @@ -127,20 +132,24 @@ if (isRetryableConnectError(error) && tryFallbackMacConnection()) { return; } isAuthorizingRef.current = false; setAuthError(error?.msg || '蓝牙连接失败'); setIsAuthorizing(false); }, onRtAuthResult: (result: any) => { console.log('[蓝牙] 鉴权结果:', result); if (result?.success) { authSuccessRef.current = true; setAuthSuccess(true); setAuthError(''); setConnectStatus(3); } else { authSuccessRef.current = false; setAuthSuccess(false); setAuthError(RETRY_UNLOCK_MESSAGE); showUnlockToast(RETRY_UNLOCK_MESSAGE); } isAuthorizingRef.current = false; setIsAuthorizing(false); }, onRtNotification: (result: any) => { @@ -172,6 +181,9 @@ if (managerRef.current) { managerRef.current.closeConnection(); } authSuccessRef.current = false; isAuthorizingRef.current = false; startAuthTaskRef.current = null; setConnectStatus(0); setAuthSuccess(false); setIsAuthorizing(false); @@ -181,50 +193,66 @@ * 触发蓝牙连接与 RtAuth 鉴权。 */ const startAuth = useCallback(async () => { console.log('[蓝牙] 开始鉴权入参:', { tripStatus, orderId, isAuthorizing, authSuccess }); console.log('[蓝牙] 开始鉴权入参:', { tripStatus, orderId, isAuthorizing: isAuthorizingRef.current, authSuccess: authSuccessRef.current }); if (![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) return; if (!orderId) return; if (startAuthTaskRef.current) return startAuthTaskRef.current; const startKey = `${orderId}`; if (isAuthorizing || (authSuccess && lastStartKeyRef.current === startKey)) return; if (isAuthorizingRef.current || (authSuccessRef.current && lastStartKeyRef.current === startKey)) return; lastStartKeyRef.current = startKey; isAuthorizingRef.current = true; setIsAuthorizing(true); setAuthError(''); try { const tokenRes = await getBluetoothUnlockToken(orderId); const tokenPayload: BluetoothTokenPayload = typeof tokenRes === 'object' && tokenRes !== null ? tokenRes as BluetoothTokenPayload : {}; const certificate = typeof tokenPayload.token === 'string' ? tokenPayload.token : String(tokenPayload.token || ''); const candidateMacs = buildMacCandidates(tokenPayload.macAddress, tokenPayload.slaveMacList); console.log('[蓝牙] 获取凭证与地址成功:', { tokenLength: certificate?.length || 0, macCount: candidateMacs.length }); if (!certificate) { setAuthError('蓝牙鉴权凭证为空'); const runTask = async () => { try { const tokenRes = await getBluetoothUnlockToken(orderId); const tokenPayload: BluetoothTokenPayload = typeof tokenRes === 'object' && tokenRes !== null ? tokenRes as BluetoothTokenPayload : {}; const certificate = typeof tokenPayload.token === 'string' ? tokenPayload.token : String(tokenPayload.token || ''); const candidateMacs = buildMacCandidates(tokenPayload.macAddress, tokenPayload.slaveMacList); console.log('[蓝牙] 获取凭证与地址成功:', { tokenLength: certificate?.length || 0, macCount: candidateMacs.length }); if (!certificate) { isAuthorizingRef.current = false; setAuthError('蓝牙鉴权凭证为空'); setIsAuthorizing(false); return; } if (candidateMacs.length === 0) { isAuthorizingRef.current = false; setAuthError('蓝牙地址为空'); setIsAuthorizing(false); return; } certificateRef.current = certificate; candidateMacsRef.current = candidateMacs; currentMacIndexRef.current = 0; const manager = ensureManager(); console.log('[蓝牙] 更新车辆配置(主地址):', { macAddress: candidateMacs[0], deviceModel: deviceModelRef.current }); manager.updateCarConfig({ macAddress: candidateMacs[0], certificate, deviceModel: deviceModelRef.current }); console.log('[蓝牙] 准备打开蓝牙适配器'); manager.openBluetoothAdapter(); } catch (error: any) { console.log('[蓝牙] 鉴权流程异常:', error); isAuthorizingRef.current = false; setAuthError(error?.msg || '获取蓝牙鉴权凭证失败'); setIsAuthorizing(false); return; } finally { startAuthTaskRef.current = null; } if (candidateMacs.length === 0) { setAuthError('蓝牙地址为空'); setIsAuthorizing(false); return; } certificateRef.current = certificate; candidateMacsRef.current = candidateMacs; currentMacIndexRef.current = 0; const manager = ensureManager(); console.log('[蓝牙] 更新车辆配置(主地址):', { macAddress: candidateMacs[0], deviceModel: deviceModelRef.current }); manager.updateCarConfig({ macAddress: candidateMacs[0], certificate, deviceModel: deviceModelRef.current }); console.log('[蓝牙] 准备打开蓝牙适配器'); manager.openBluetoothAdapter(); } catch (error: any) { console.log('[蓝牙] 鉴权流程异常:', error); setAuthError(error?.msg || '获取蓝牙鉴权凭证失败'); setIsAuthorizing(false); } }, [tripStatus, orderId, isAuthorizing, authSuccess, ensureManager]); }; const task = runTask(); startAuthTaskRef.current = task; return task; }, [tripStatus, orderId, ensureManager]); /** * 重试蓝牙鉴权流程。 @@ -235,6 +263,7 @@ startAuth(); return; } isAuthorizingRef.current = true; setIsAuthorizing(true); setAuthError(''); managerRef.current.retryConnection(); @@ -248,6 +277,9 @@ candidateMacsRef.current = []; currentMacIndexRef.current = 0; certificateRef.current = ''; startAuthTaskRef.current = null; authSuccessRef.current = false; isAuthorizingRef.current = false; setAuthError(''); } }, [tripStatus, closeConnection]); src/robotaxi/pages/begin-route/hooks/use-order-actions.ts
@@ -167,6 +167,16 @@ }; /** * 获取当前选中的路线。 */ const getSelectedRoute = () => { if (!Array.isArray(deps.routeOptions) || typeof deps.selectedRouteIndex !== 'number') { return null; } return deps.routeOptions[deps.selectedRouteIndex] || null; }; /** * 读取下单起点。 */ const getStartSiteForCall = () => { @@ -182,9 +192,31 @@ /** * 下单前校验路线是否过期,命中过期时刷新路线并终止本次下单。 */ const checkRouteStaleBeforeOrder = async (startSite: any) => { const checkRouteStaleBeforeOrder = async (startSite: any, selectedRoute: any) => { const snapshotId = selectedRoute?.snapshotId; // 路线快照 ID const pathFingerprint = selectedRoute?.pathFingerprint ?? selectedRoute?.route?.pathFingerprint; // 路线指纹 const clientSnapshotMillis = Number(selectedRoute?.generatedAtMillis); // 客户端路线生成时间戳 const clientFinalFare = Number(selectedRoute?.fareEstimate?.finalFare ?? deps.fareEstimate?.finalFare); // 客户端路线预估价 const staleCheckPayload = { snapshotId, pathFingerprint, clientSnapshotMillis, clientFinalFare }; // 路况过期校验请求参数 if (!snapshotId || !pathFingerprint || !Number.isFinite(clientSnapshotMillis) || !Number.isFinite(clientFinalFare)) { Taro.showToast({ title: '路线信息已更新,请稍后重试', icon: 'none' }); deps.routeRequestRouteAndFare( Number(startSite?.latitude), Number(startSite?.longitude), Number(deps.endSite?.latitude), Number(deps.endSite?.longitude), true ); stopCallSubmitting(); return false; } try { const staleCheckRes: any = await routeStaleCheck(); const staleCheckRes: any = await routeStaleCheck(staleCheckPayload); const needRefreshNotice = staleCheckRes?.needRefreshNotice === true; // 服务端是否要求刷新路线 if (!needRefreshNotice) { return true; @@ -231,9 +263,15 @@ if (!routeReady) return; const s = getStartSiteForCall(); if (!s) return; const selectedRoute = getSelectedRoute(); if (!selectedRoute) { Taro.showToast({ title: '路线信息缺失,请重试', icon: 'none' }); stopCallSubmitting(); return; } const openCityId = await deps.checkDistanceAndCityReady(s); if (!openCityId) return; const routeStalePassed = await checkRouteStaleBeforeOrder(s); const routeStalePassed = await checkRouteStaleBeforeOrder(s, selectedRoute); if (!routeStalePassed) return; try { const nextOrderId = await submitOrder(s, openCityId); src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
@@ -396,25 +396,41 @@ setRouteAndFareError(false); const responseRoutes = Array.isArray(res?.routes) ? res.routes : []; // 新版接口路线集合 const responseSnapshotId = res?.snapshotId; // 新版接口快照 ID const responseGeneratedAtMillis = res?.generatedAtMillis; // 新版接口报价生成时间戳 const normalizeRouteOption = (item: any) => ({ ...(item || {}), snapshotId: item?.snapshotId ?? responseSnapshotId, generatedAtMillis: item?.generatedAtMillis ?? responseGeneratedAtMillis }); // 统一补齐路线校验所需的快照信息 let routeData = res; let options: any[] = []; // Handle both array and single object response for backward compatibility // 兼容新版对象结构(res.routes)与历史结构 let allPointsArr: any[][] = []; if (Array.isArray(res) && res.length > 0) { options = res; setRouteOptions(res); routeData = res[0]; if (responseRoutes.length > 0) { options = responseRoutes.map(normalizeRouteOption); setRouteOptions(options); routeData = options[0]; setSelectedRouteIndex(0); allPointsArr = options.map(item => Array.isArray(item?.route?.polyline) ? item.route.polyline : []); setAllRoutePoints(allPointsArr); } else if (Array.isArray(res) && res.length > 0) { options = res.map(normalizeRouteOption); setRouteOptions(options); routeData = options[0]; setSelectedRouteIndex(0); // 解析所有路线的点集 allPointsArr = res.map(item => Array.isArray(item?.route?.polyline) ? item.route.polyline : []); allPointsArr = options.map(item => Array.isArray(item?.route?.polyline) ? item.route.polyline : []); setAllRoutePoints(allPointsArr); } else if (res) { options = [res]; setRouteOptions([res]); routeData = res; options = [normalizeRouteOption(res)]; setRouteOptions(options); routeData = options[0]; setSelectedRouteIndex(0); const pts = Array.isArray(res?.route?.polyline) ? res.route.polyline : []; const pts = Array.isArray(routeData?.route?.polyline) ? routeData.route.polyline : []; allPointsArr = [pts]; setAllRoutePoints(allPointsArr); } else {