import { useEffect, useRef } from 'react';
|
import Taro from '@tarojs/taro';
|
import wsManager from '../../../../utils/websocket';
|
import { queryVehicleByOrderId } from '../../../api/begin-route';
|
import { clearOrderTripStorage, safeParse } from '../../../../utils/Robotaxi-public-method';
|
import { normalizeDistanceKm, normalizeDurationSeconds } from '../../../../robotaxi/utils/format';
|
|
type UseWebSocketEventsDeps = {
|
tripStatusRef: any;
|
orderId: string;
|
setOrderId: (orderId: string) => void;
|
setTripStatus: (status: number) => void;
|
setVehicleInfo: (vehicle: any) => void;
|
vehicleInfo: any;
|
setPriceUpdateVersion: (updater: (v: number) => number) => void;
|
setFinishCountdownEnabled: (value: boolean) => void;
|
setShowAnnouncementsPopup: (value: boolean) => void;
|
setRoutePoints: (points: any[]) => void;
|
setPolyline: (lines: any[]) => void;
|
polylineKeyRef: any;
|
lastPolylineUpdateAtRef: any;
|
lastRoutePointRef: any;
|
lastRouteUpdateAtRef: any;
|
lastVehiclePointRef: any;
|
lastVehicleUpdateAtRef: any;
|
currentFullRouteRef: any;
|
lastErasedIndexRef: any;
|
orderRouteInitializedRef: any;
|
setCancelType: (value: number) => void;
|
setShowCancelPopup: (value: boolean) => void;
|
checkAndShowPassengerNotice: () => void;
|
setVehicleDirection: (value: number) => void;
|
setVehicleMoveAlongPoints: (points: any[]) => void;
|
setRouteDistance: (value: any) => void;
|
setRouteDuration: (value: number) => void;
|
erasePassedRouteSegments: (points: any[], servedPoints?: any[]) => void;
|
applyRoutePoints: (points: any[]) => boolean;
|
updateVehicleLocationThrottled: (lat: number, lng: number, force?: boolean) => void;
|
};
|
|
const resolveOrderId = (payload: any, fallbackOrderId: string) =>
|
payload?.content?.orderId || payload?.orderId || fallbackOrderId || Taro.getStorageSync('orderId');
|
|
const isSameOrder = (incomingOrderId: string, currentOrderId: string) => {
|
if (!incomingOrderId && !currentOrderId) return true;
|
if (!incomingOrderId || !currentOrderId) return false;
|
return String(incomingOrderId) === String(currentOrderId);
|
};
|
|
const resetRouteRuntimeState = (deps: UseWebSocketEventsDeps) => {
|
deps.setRoutePoints([]);
|
deps.setPolyline([]);
|
deps.setVehicleMoveAlongPoints([]);
|
deps.polylineKeyRef.current = '';
|
deps.lastPolylineUpdateAtRef.current = 0;
|
deps.lastRoutePointRef.current = null;
|
deps.lastRouteUpdateAtRef.current = 0;
|
deps.lastVehiclePointRef.current = null;
|
deps.lastVehicleUpdateAtRef.current = 0;
|
deps.currentFullRouteRef.current = [];
|
deps.lastErasedIndexRef.current = 0;
|
deps.orderRouteInitializedRef.current = false;
|
};
|
|
const syncTripStatus = (payload: any, deps: UseWebSocketEventsDeps) => {
|
const mappedTripStatus = payload?.tripStatus;
|
if (typeof mappedTripStatus !== 'number') {
|
return null;
|
}
|
const currentTripStatus = Number(deps.tripStatusRef.current);
|
// 行程进行关键态不允许被回退,避免 4/6 被延迟状态覆盖导致路线链路重置
|
if ([4, 6].includes(currentTripStatus) && mappedTripStatus < currentTripStatus) {
|
return currentTripStatus;
|
}
|
if (currentTripStatus === mappedTripStatus) {
|
return mappedTripStatus;
|
}
|
deps.setTripStatus(mappedTripStatus);
|
deps.tripStatusRef.current = mappedTripStatus;
|
Taro.setStorageSync('tripStatus', mappedTripStatus);
|
return mappedTripStatus;
|
};
|
|
const syncVehicleInfo = (
|
effectiveOrderId: string,
|
deps: UseWebSocketEventsDeps,
|
lastFetchedVehicleOrderIdRef: any
|
) => {
|
if (!effectiveOrderId) return;
|
const shouldFetchVehicle = lastFetchedVehicleOrderIdRef.current !== effectiveOrderId;
|
|
if (deps.orderId !== effectiveOrderId) {
|
deps.setOrderId(effectiveOrderId);
|
Taro.setStorageSync('orderId', effectiveOrderId);
|
}
|
if (!shouldFetchVehicle) return;
|
lastFetchedVehicleOrderIdRef.current = effectiveOrderId;
|
const requestedOrderId = String(effectiveOrderId);
|
queryVehicleByOrderId({ orderId: effectiveOrderId })
|
.then((res: any) => {
|
const latestOrderId = String(Taro.getStorageSync('orderId') || '');
|
if (latestOrderId !== requestedOrderId) {
|
return;
|
}
|
deps.setVehicleInfo(res);
|
})
|
.catch(() => { });
|
};
|
|
const handleCancelPopupPush = (payload: any, changeStatusOld: number, deps: UseWebSocketEventsDeps) => {
|
if (!payload?.showCancelPopup) return;
|
if (changeStatusOld === 4 || changeStatusOld === 5) {
|
deps.setOrderId('');
|
deps.setVehicleInfo({});
|
clearOrderTripStorage();
|
Taro.removeStorageSync('pickupEndTime');
|
Taro.removeStorageSync('waitingTimerStartAt');
|
Taro.removeStorageSync('waitingTimerElapsed');
|
deps.setCancelType(4);
|
} else {
|
deps.setCancelType(5);
|
}
|
deps.setTripStatus(10);
|
deps.setShowCancelPopup(true);
|
};
|
|
const handleStatusSideEffects = (mappedTripStatus: number | null, changeStatusOld: number, deps: UseWebSocketEventsDeps) => {
|
if (mappedTripStatus === 6) {
|
deps.setShowAnnouncementsPopup(false);
|
}
|
if (mappedTripStatus === 5 && changeStatusOld !== 5) {
|
resetRouteRuntimeState(deps);
|
}
|
if (mappedTripStatus === 4 && changeStatusOld !== 4) {
|
resetRouteRuntimeState(deps);
|
}
|
};
|
|
const handleOrderStatusPayload = (
|
payload: any,
|
deps: UseWebSocketEventsDeps,
|
lastFetchedVehicleOrderIdRef: any
|
) => {
|
const changeStatusOld = deps.tripStatusRef.current;
|
const currentOrderId = deps.orderId || Taro.getStorageSync('orderId');
|
const incomingOrderId = payload?.content?.orderId || payload?.orderId;
|
if (!isSameOrder(incomingOrderId, currentOrderId)) {
|
return;
|
}
|
|
const mappedTripStatus = syncTripStatus(payload, deps);
|
const effectiveOrderId = resolveOrderId(payload, currentOrderId);
|
syncVehicleInfo(effectiveOrderId, deps, lastFetchedVehicleOrderIdRef);
|
handleStatusSideEffects(mappedTripStatus, changeStatusOld, deps);
|
handleCancelPopupPush(payload, changeStatusOld, deps);
|
|
if (payload?.showPassengerNotice) {
|
deps.checkAndShowPassengerNotice();
|
}
|
};
|
|
// 统一解析方向值
|
const resolveDirectionValue = (payload: any) => {
|
const rawDirection = payload?.content?.direction;
|
const direction = Number(rawDirection);
|
if (!Number.isFinite(direction)) {
|
return null;
|
}
|
return ((direction % 360) + 360) % 360;
|
};
|
|
const updateRouteSummary = (payload: any, deps: UseWebSocketEventsDeps) => {
|
const normalizedDirection = resolveDirectionValue(payload);
|
if (typeof normalizedDirection === 'number') {
|
deps.setVehicleDirection(normalizedDirection);
|
}
|
const route = safeParse(payload?.content?.route, {});
|
const distanceKm = normalizeDistanceKm(Number(route?.distance));
|
if (distanceKm !== null) {
|
deps.setRouteDistance(distanceKm);
|
}
|
const durationSeconds = normalizeDurationSeconds(Number(route?.duration));
|
if (durationSeconds !== null) {
|
deps.setRouteDuration(durationSeconds);
|
}
|
};
|
|
const resolveVehiclePoint = (points: any[], servedPoints: any[] = []) => {
|
const servedLastPoint = Array.isArray(servedPoints) && servedPoints.length > 0
|
? servedPoints[servedPoints.length - 1]
|
: null;
|
if (servedLastPoint && typeof servedLastPoint.latitude === 'number' && typeof servedLastPoint.longitude === 'number') {
|
return servedLastPoint;
|
}
|
const firstPoint = points?.[0];
|
if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
|
return firstPoint;
|
}
|
return null;
|
};
|
|
const resolveVehiclePointForErase = (points: any[], servedPoints: any[] = []) => {
|
const firstPoint = points?.[0];
|
if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
|
return firstPoint;
|
}
|
return resolveVehiclePoint(points, servedPoints);
|
};
|
|
const syncVehiclePointWithRoute = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => {
|
const targetPoint = resolveVehiclePoint(points, servedPoints);
|
if (!targetPoint) {
|
return;
|
}
|
const currentTripStatus = Number(deps.tripStatusRef.current);
|
if (currentTripStatus === 7) {
|
const lastPoint = deps.lastVehiclePointRef.current;
|
const latDiff = Math.abs(Number(lastPoint?.latitude || 0) - targetPoint.latitude);
|
const lngDiff = Math.abs(Number(lastPoint?.longitude || 0) - targetPoint.longitude);
|
const almostSamePoint = latDiff < 0.000003 && lngDiff < 0.000003;
|
if (almostSamePoint) {
|
return;
|
}
|
}
|
deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
|
};
|
|
const handleEraseRouteStatus = (points: any[], servedPoints: any[], deps: UseWebSocketEventsDeps) => {
|
const targetPoint = resolveVehiclePointForErase(points, servedPoints);
|
if (targetPoint) {
|
deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
|
}
|
if (deps.orderRouteInitializedRef.current && deps.currentFullRouteRef.current.length > 0) {
|
deps.erasePassedRouteSegments(points);
|
} else {
|
deps.orderRouteInitializedRef.current = true;
|
deps.currentFullRouteRef.current = [...points];
|
deps.lastErasedIndexRef.current = 0;
|
Taro.nextTick(() => {
|
deps.applyRoutePoints(points);
|
});
|
}
|
};
|
|
const handleNormalRouteStatus = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => {
|
Taro.nextTick(() => {
|
const updated = deps.applyRoutePoints(points);
|
if (!updated) return;
|
if (![4, 5, 6, 7].includes(deps.tripStatusRef.current)) return;
|
syncVehiclePointWithRoute(points, deps, servedPoints);
|
});
|
};
|
|
const handleForceFullRouteRedraw = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => {
|
deps.setRoutePoints([]);
|
deps.setPolyline([]);
|
deps.orderRouteInitializedRef.current = false;
|
deps.currentFullRouteRef.current = [];
|
deps.lastErasedIndexRef.current = 0;
|
deps.polylineKeyRef.current = '';
|
deps.lastPolylineUpdateAtRef.current = 0;
|
deps.lastRoutePointRef.current = null;
|
deps.lastRouteUpdateAtRef.current = 0;
|
|
Taro.nextTick(() => {
|
deps.orderRouteInitializedRef.current = points.length > 0;
|
deps.currentFullRouteRef.current = [...points];
|
deps.lastErasedIndexRef.current = 0;
|
deps.applyRoutePoints(points);
|
if (points.length > 0) {
|
syncVehiclePointWithRoute(points, deps, servedPoints);
|
}
|
});
|
};
|
|
const handleOrderRoutePayload = (payload: any, deps: UseWebSocketEventsDeps) => {
|
const points = payload?.points || [];
|
const servedPoints = Array.isArray(payload?.servedPoints) ? payload.servedPoints : [];
|
const forceFullRedraw = payload?.forceFullRedraw === true;
|
const currentTripStatus = Number(deps.tripStatusRef.current);
|
updateRouteSummary(payload, deps);
|
// 行程结束态仅更新概要信息,不再消费路线点,避免视口反复重算造成跳动
|
if ([8, 9].includes(currentTripStatus)) {
|
return;
|
}
|
if ([4, 6].includes(currentTripStatus)) {
|
deps.setVehicleMoveAlongPoints(servedPoints);
|
}
|
if (forceFullRedraw) {
|
handleForceFullRouteRedraw(points, deps, servedPoints);
|
return;
|
}
|
const isEraseStatus = ![7, 8, 9].includes(currentTripStatus);
|
if (isEraseStatus) {
|
handleEraseRouteStatus(points, servedPoints, deps);
|
return;
|
}
|
handleNormalRouteStatus(points, deps, servedPoints);
|
};
|
|
export default function useWebSocketEvents(deps: UseWebSocketEventsDeps) {
|
// 持有最新依赖,避免渲染过程反复解绑/重绑监听
|
const depsRef = useRef(deps);
|
useEffect(() => {
|
depsRef.current = deps;
|
}, [deps]);
|
|
// 各事件是否已绑定标记
|
const authSuccessBound = useRef(false);
|
const messageBound = useRef(false);
|
const orderStatusBound = useRef(false);
|
const orderRouteBound = useRef(false);
|
const openTrunkBound = useRef(false);
|
const orderUpdatePriceBound = useRef(false);
|
const orderReassignBound = useRef(false);
|
|
const authSuccessHandlerRef = useRef<(data: any) => void>();
|
const messageHandlerRef = useRef<(data: any) => void>();
|
const orderStatusHandlerRef = useRef<(data: any) => void>();
|
const orderRouteHandlerRef = useRef<(data: any) => void>();
|
const openTrunkHandlerRef = useRef<(data: any) => void>();
|
const orderUpdatePriceHandlerRef = useRef<(data: any) => void>();
|
const orderReassignHandlerRef = useRef<(data: any) => void>();
|
const socketOpenHandlerRef = useRef<(() => void) | null>(null);
|
const lastFetchedVehicleOrderIdRef = useRef<string | null>(null);
|
|
// 仅初始化一次事件处理器与解绑逻辑
|
useEffect(() => {
|
authSuccessHandlerRef.current = () => {
|
if (!messageBound.current && messageHandlerRef.current) {
|
wsManager.on('message', messageHandlerRef.current);
|
messageBound.current = true;
|
}
|
};
|
messageHandlerRef.current = (data) => {
|
console.log('ws message:', data);
|
};
|
openTrunkHandlerRef.current = (payload) => {
|
const currentDeps = depsRef.current;
|
const isOpen = Number(payload?.isOpen);
|
if (isOpen === 1) {
|
currentDeps.setFinishCountdownEnabled(true);
|
}
|
};
|
orderUpdatePriceHandlerRef.current = (payload) => {
|
const currentDeps = depsRef.current;
|
const incomingOrderId = payload?.orderId || payload?.content?.orderId || Taro.getStorageSync('orderId');
|
const currentOrderId = currentDeps.orderId || Taro.getStorageSync('orderId');
|
if (!isSameOrder(incomingOrderId, currentOrderId)) {
|
return;
|
}
|
currentDeps.setPriceUpdateVersion((v: number) => v + 1);
|
};
|
orderReassignHandlerRef.current = (payload) => {
|
const currentDeps = depsRef.current;
|
const incomingOrderId = payload?.orderId || payload?.content?.orderId || Taro.getStorageSync('orderId');
|
const currentOrderId = currentDeps.orderId || Taro.getStorageSync('orderId');
|
if (!isSameOrder(incomingOrderId, currentOrderId)) {
|
return;
|
}
|
lastFetchedVehicleOrderIdRef.current = null;
|
const requestedOrderId = String(currentOrderId || '');
|
queryVehicleByOrderId({ orderId: currentOrderId })
|
.then((res: any) => {
|
const latestOrderId = String((depsRef.current.orderId || Taro.getStorageSync('orderId') || ''));
|
if (!requestedOrderId || latestOrderId !== requestedOrderId) {
|
return;
|
}
|
currentDeps.setVehicleInfo(res);
|
})
|
.catch(() => { });
|
};
|
orderStatusHandlerRef.current = (payload) => {
|
handleOrderStatusPayload(payload, depsRef.current, lastFetchedVehicleOrderIdRef);
|
};
|
orderRouteHandlerRef.current = (payload) => {
|
handleOrderRoutePayload(payload, depsRef.current);
|
};
|
// 处理重进页面时 useDidShow 先于 handler 初始化的场景,确保已连接状态下也能完成监听绑定
|
if (wsManager.isConnectedStatus()) {
|
bindSocketListeners();
|
}
|
|
return () => {
|
if (authSuccessBound.current && authSuccessHandlerRef.current) {
|
wsManager.off('auth_success', authSuccessHandlerRef.current);
|
authSuccessBound.current = false;
|
}
|
if (messageBound.current && messageHandlerRef.current) {
|
wsManager.off('message', messageHandlerRef.current);
|
messageBound.current = false;
|
}
|
if (orderStatusBound.current && orderStatusHandlerRef.current) {
|
wsManager.off('order_status', orderStatusHandlerRef.current);
|
orderStatusBound.current = false;
|
}
|
if (orderRouteBound.current && orderRouteHandlerRef.current) {
|
wsManager.off('order_route', orderRouteHandlerRef.current);
|
orderRouteBound.current = false;
|
}
|
if (openTrunkBound.current && openTrunkHandlerRef.current) {
|
wsManager.off('open_trunk', openTrunkHandlerRef.current);
|
openTrunkBound.current = false;
|
}
|
if (orderUpdatePriceBound.current && orderUpdatePriceHandlerRef.current) {
|
wsManager.off('order_update_price', orderUpdatePriceHandlerRef.current);
|
orderUpdatePriceBound.current = false;
|
}
|
if (orderReassignBound.current && orderReassignHandlerRef.current) {
|
wsManager.off('order_reassign', orderReassignHandlerRef.current);
|
orderReassignBound.current = false;
|
}
|
if (socketOpenHandlerRef.current) {
|
wsManager.off('open', socketOpenHandlerRef.current);
|
socketOpenHandlerRef.current = null;
|
}
|
};
|
}, []);
|
|
// 绑定业务事件监听
|
const bindSocketListeners = () => {
|
if (!authSuccessBound.current && authSuccessHandlerRef.current) {
|
wsManager.on('auth_success', authSuccessHandlerRef.current);
|
authSuccessBound.current = true;
|
}
|
if (!orderStatusBound.current && orderStatusHandlerRef.current) {
|
wsManager.on('order_status', orderStatusHandlerRef.current);
|
orderStatusBound.current = true;
|
}
|
if (!orderRouteBound.current && orderRouteHandlerRef.current) {
|
wsManager.on('order_route', orderRouteHandlerRef.current);
|
orderRouteBound.current = true;
|
}
|
if (!openTrunkBound.current && openTrunkHandlerRef.current) {
|
wsManager.on('open_trunk', openTrunkHandlerRef.current);
|
openTrunkBound.current = true;
|
}
|
if (!orderUpdatePriceBound.current && orderUpdatePriceHandlerRef.current) {
|
wsManager.on('order_update_price', orderUpdatePriceHandlerRef.current);
|
orderUpdatePriceBound.current = true;
|
}
|
if (!orderReassignBound.current && orderReassignHandlerRef.current) {
|
wsManager.on('order_reassign', orderReassignHandlerRef.current);
|
orderReassignBound.current = true;
|
}
|
};
|
|
// 确保连接可用并完成监听绑定
|
const ensureSocketListeners = (host: string) => {
|
if (wsManager.isConnectedStatus()) {
|
bindSocketListeners();
|
return;
|
}
|
if (socketOpenHandlerRef.current) {
|
wsManager.connect(host);
|
return;
|
}
|
const handleOpen = () => {
|
bindSocketListeners();
|
};
|
socketOpenHandlerRef.current = handleOpen;
|
wsManager.on('open', handleOpen);
|
wsManager.connect(host);
|
};
|
|
return {
|
ensureSocketListeners,
|
bindSocketListeners
|
};
|
}
|