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;
|
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);
|
if (currentTripStatus > mappedTripStatus && currentTripStatus !== 10) {
|
return null;
|
}
|
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 vehicleInfoEmpty =
|
!deps.vehicleInfo ||
|
(!deps.vehicleInfo.plateNumber && !deps.vehicleInfo.vehicleCode && !deps.vehicleInfo.vehicleColor && !deps.vehicleInfo.vehiclePictures);
|
const shouldFetchVehicle =
|
lastFetchedVehicleOrderIdRef.current !== effectiveOrderId &&
|
(vehicleInfoEmpty || deps.orderId !== effectiveOrderId);
|
|
if (deps.orderId !== effectiveOrderId) {
|
deps.setOrderId(effectiveOrderId);
|
}
|
if (!shouldFetchVehicle) return;
|
lastFetchedVehicleOrderIdRef.current = effectiveOrderId;
|
queryVehicleByOrderId({ orderId: effectiveOrderId })
|
.then((res: any) => {
|
deps.setVehicleInfo(res);
|
})
|
.catch(() => { });
|
};
|
|
const handleCancelPopupPush = (payload: any, changeStatusOld: number, deps: UseWebSocketEventsDeps) => {
|
if (!payload?.showCancelPopup) return;
|
if (changeStatusOld === 4 || changeStatusOld === 5) {
|
deps.setOrderId('');
|
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 updateRouteSummary = (payload: any, deps: UseWebSocketEventsDeps) => {
|
const direction = Number(payload?.direction);
|
if (Number.isFinite(direction)) {
|
deps.setVehicleDirection(((direction % 360) + 360) % 360);
|
}
|
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 syncVehiclePointWithRoute = (points: any[], deps: UseWebSocketEventsDeps) => {
|
const firstPoint = points?.[0];
|
if (!firstPoint || typeof firstPoint.latitude !== 'number' || typeof firstPoint.longitude !== 'number') {
|
return;
|
}
|
deps.updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
|
};
|
|
const handleEraseRouteStatus = (points: any[], servedPoints: any[], deps: UseWebSocketEventsDeps) => {
|
const firstPoint = points?.[0];
|
if (deps.orderRouteInitializedRef.current && deps.currentFullRouteRef.current.length > 0) {
|
deps.erasePassedRouteSegments(points, servedPoints);
|
} else {
|
deps.orderRouteInitializedRef.current = true;
|
deps.currentFullRouteRef.current = [...points];
|
deps.lastErasedIndexRef.current = 0;
|
Taro.nextTick(() => {
|
deps.applyRoutePoints(points);
|
});
|
}
|
if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
|
setTimeout(() => {
|
deps.updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
|
}, 50);
|
}
|
};
|
|
const handleNormalRouteStatus = (points: any[], deps: UseWebSocketEventsDeps) => {
|
Taro.nextTick(() => {
|
const updated = deps.applyRoutePoints(points);
|
if (!updated) return;
|
if (![4, 5, 6].includes(deps.tripStatusRef.current)) return;
|
syncVehiclePointWithRoute(points, deps);
|
});
|
};
|
|
const handleForceFullRouteRedraw = (points: any[], deps: UseWebSocketEventsDeps) => {
|
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 = true;
|
deps.currentFullRouteRef.current = [...points];
|
deps.lastErasedIndexRef.current = 0;
|
deps.applyRoutePoints(points);
|
syncVehiclePointWithRoute(points, deps);
|
});
|
};
|
|
const handleOrderRoutePayload = (payload: any, deps: UseWebSocketEventsDeps) => {
|
if (deps.tripStatusRef.current === 5) return;
|
const points = payload?.points || [];
|
const servedPoints = Array.isArray(payload?.servedPoints) ? payload.servedPoints : [];
|
const forceFullRedraw = payload?.forceFullRedraw === true;
|
updateRouteSummary(payload, deps);
|
if ([4, 6].includes(deps.tripStatusRef.current)) {
|
deps.setVehicleMoveAlongPoints(servedPoints);
|
}
|
if (forceFullRedraw) {
|
handleForceFullRouteRedraw(points, deps);
|
return;
|
}
|
const isEraseStatus = [4, 6].includes(deps.tripStatusRef.current);
|
if (isEraseStatus) {
|
handleEraseRouteStatus(points, servedPoints, deps);
|
return;
|
}
|
handleNormalRouteStatus(points, deps);
|
};
|
|
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;
|
queryVehicleByOrderId({ orderId: currentOrderId })
|
.then((res: any) => {
|
currentDeps.setVehicleInfo(res);
|
})
|
.catch(() => { });
|
};
|
orderStatusHandlerRef.current = (payload) => {
|
handleOrderStatusPayload(payload, depsRef.current, lastFetchedVehicleOrderIdRef);
|
};
|
orderRouteHandlerRef.current = (payload) => {
|
handleOrderRoutePayload(payload, depsRef.current);
|
};
|
|
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
|
};
|
}
|