hejianhao
2026-05-25 a0deef395f1df7feb2e33e2e476ec999efda269a
refactor(begin-route): 拆分websocket事件处理逻辑为独立模块

抽离原集中在use-web-socket-events.ts中的websocket业务处理代码为多个独立工具文件,统一提取订单校验逻辑减少重复代码,包含各类业务消息处理器,统一管理事件绑定与解绑,优化代码结构提升可维护性和可读性。
4个文件已添加
1个文件已修改
1000 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 508 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/web-socket-order-guard.ts 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -1,18 +1,18 @@
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';
import { getOrderId } from '../../../utils/order';
import { toTripStatus, TripStatus } from '../constants/trip-status';
import { PENDING_CANCEL_CONTEXT_TTL_MS, PendingCancelContext } from './use-order-actions.shared';
import { normalizeSocketRouteSnapshot, normalizeSpeedMps } from './runtime-sync';
import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from '../constants/modify-destination';
import { clearPassedRouteCache, writePassedRouteCache } from './passed-route-cache';
import { resolveVehiclePoint } from './trip-motion-utils';
import {
    createOpenTrunkSocketHandler,
    createOrderReassignSocketHandler,
    createOrderUpdatePriceSocketHandler,
    createUpdateEndAddressStatusSocketHandler
} from './web-socket-handlers/create-order-meta-socket-handlers';
import { createOrderRouteSocketHandler } from './web-socket-handlers/create-order-route-socket-handler';
import { createOrderStatusSocketHandler } from './web-socket-handlers/create-order-status-socket-handler';
import { type WebSocketDepsRef } from './web-socket-handlers/web-socket-order-guard';
import { TripStatus } from '../constants/trip-status';
import { type PendingCancelContext } from './use-order-actions.shared';
type UseWebSocketEventsDeps = {
export type UseWebSocketEventsDeps = {
    tripStatusRef: React.MutableRefObject<TripStatus>;
    pendingCancelContextRef: React.MutableRefObject<PendingCancelContext | null>;
    modifyDestinationPickupSuccessAtRef: React.MutableRefObject<number>;
@@ -62,322 +62,91 @@
    resetSmoothRouteMotion: () => void;
};
const resolveOrderId = (payload: any, fallbackOrderId: string) =>
    getOrderId(payload?.content?.orderId || payload?.orderId || fallbackOrderId);
type BusinessSocketEventName =
    | 'order_status'
    | 'order_route'
    | 'open_trunk'
    | 'order_update_price'
    | 'order_reassign'
    | 'update_end_address_status';
// 解析“上一单乘客下车点”坐标,兼容顶层字段、content 字段与历史大小写差异。
const parseBeforeUserEndSite = (payload: any) => {
    const longitude = Number(
        payload?.endLongitude
        ?? payload?.data?.endLongitude
        ?? payload?.content?.endLongitude
    );
    const latitude = Number(
        payload?.endLatitude
        ?? payload?.endlatitude
        ?? payload?.data?.endLatitude
        ?? payload?.data?.endlatitude
        ?? payload?.content?.endLatitude
        ?? payload?.content?.endlatitude
    );
    if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
        return null;
    }
    return { longitude, latitude };
};
type SocketPayloadHandler = (payload: any) => void;
const isSameOrder = (incomingOrderId: string, currentOrderId: string) => {
    // 当前页面已无活跃订单时,不消费缺失 orderId 的兜底消息,避免手动取消后又被旧 socket 状态污染回接驾态。
    if (!incomingOrderId && !currentOrderId) return false;
    if (!incomingOrderId || !currentOrderId) return false;
    return String(incomingOrderId) === String(currentOrderId);
};
const BUSINESS_SOCKET_EVENTS: BusinessSocketEventName[] = [
    'order_status',
    'order_route',
    'open_trunk',
    'order_update_price',
    'order_reassign',
    'update_end_address_status'
];
/**
 * 获取仍在有效期内的待确认取消上下文;过期则立即清空,避免脏上下文影响后续订单。
 * 创建默认业务事件绑定标记。
 */
const getValidPendingCancelContext = (deps: UseWebSocketEventsDeps) => {
    const pendingCancelContext = deps.pendingCancelContextRef.current;
    if (!pendingCancelContext) {
        return null;
    }
    if (Date.now() - pendingCancelContext.createdAt > PENDING_CANCEL_CONTEXT_TTL_MS) {
        deps.pendingCancelContextRef.current = null;
        return null;
    }
    return pendingCancelContext;
};
const createBusinessEventBoundMap = (): Record<BusinessSocketEventName, boolean> => ({
    order_status: false,
    order_route: false,
    open_trunk: false,
    order_update_price: false,
    order_reassign: false,
    update_end_address_status: false
});
/**
 * 判断当前 socket 取消消息是否可复用本地记录的取消前上下文。
 * 创建默认业务处理器映射。
 */
const isPendingCancelContextMatched = (payload: any, pendingCancelContext: PendingCancelContext | null) => {
    if (!pendingCancelContext) {
        return false;
    }
    const socketStatus = Number(payload?.status);
    if (socketStatus !== 0) {
        return false;
    }
    const incomingOrderId = String(payload?.content?.orderId || payload?.orderId || '');
    if (!incomingOrderId || !pendingCancelContext.orderId) {
        return true;
    }
    return incomingOrderId === pendingCancelContext.orderId;
};
const resetRouteRuntimeState = (deps: UseWebSocketEventsDeps) => {
    deps.setRoutePoints([]);
    deps.setPolyline([]);
    deps.setVehicleMoveAlongPoints([]);
    clearPassedRouteCache();
    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;
    deps.resetSmoothRouteMotion();
};
const syncTripStatus = (payload: any, deps: UseWebSocketEventsDeps) => {
    const incomingTripStatus = Number(payload?.tripStatus);
    if (!Number.isFinite(incomingTripStatus)) {
        return null;
    }
    const currentTripStatus = deps.tripStatusRef.current;
    const mappedTripStatus = toTripStatus(incomingTripStatus, currentTripStatus);
    const modifyDestinationPickupSuccessAt = Number(deps.modifyDestinationPickupSuccessAtRef.current || 0);
    const shouldGuardModifyDestinationPickupStatus = mappedTripStatus === TripStatus.InService
        && [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(currentTripStatus)
        && modifyDestinationPickupSuccessAt > 0
        && Date.now() - modifyDestinationPickupSuccessAt <= deps.modifyDestinationPickupStatusGuardMs;
    if (shouldGuardModifyDestinationPickupStatus) {
        deps.modifyDestinationPickupSuccessAtRef.current = 0;
        return currentTripStatus;
    }
    // 行程进行关键态不允许被回退,避免 4/6 被延迟状态覆盖导致路线链路重置
    if ([TripStatus.VehicleApproaching, TripStatus.InService].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: TripStatus,
    deps: UseWebSocketEventsDeps,
    pendingCancelContext: PendingCancelContext | null
) => {
    if (!payload?.showCancelPopup) return;
    const matchedPendingCancelContext = isPendingCancelContextMatched(payload, pendingCancelContext);
    if (matchedPendingCancelContext) {
        deps.setCancelType(pendingCancelContext?.cancelType || 4);
        deps.pendingCancelContextRef.current = null;
    } else if (changeStatusOld === TripStatus.VehicleApproaching || changeStatusOld === TripStatus.VehicleArrivedPickup) {
        deps.setOrderId('');
        deps.setVehicleInfo({});
        clearOrderTripStorage();
        Taro.removeStorageSync('pickupEndTime');
        Taro.removeStorageSync('waitingTimerStartAt');
        Taro.removeStorageSync('waitingTimerElapsed');
        deps.setCancelType(4);
    } else {
        deps.setCancelType(5);
    }
    deps.setTripStatus(TripStatus.CancelOnlyPickup);
    deps.setShowCancelPopup(true);
};
const handleStatusSideEffects = (mappedTripStatus: TripStatus | null, changeStatusOld: TripStatus, deps: UseWebSocketEventsDeps) => {
    if (mappedTripStatus === TripStatus.InService) {
        deps.setShowAnnouncementsPopup(false);
    }
    if (mappedTripStatus === TripStatus.VehicleArrivedPickup && changeStatusOld !== TripStatus.VehicleArrivedPickup) {
        resetRouteRuntimeState(deps);
    }
    if (mappedTripStatus === TripStatus.VehicleApproaching && changeStatusOld !== TripStatus.VehicleApproaching) {
        resetRouteRuntimeState(deps);
    }
};
const handleOrderStatusPayload = (
    payload: any,
    deps: UseWebSocketEventsDeps,
    lastFetchedVehicleOrderIdRef: any
) => {
    const changeStatusOld = deps.tripStatusRef.current;
    const pendingCancelContext = getValidPendingCancelContext(deps);
    const currentOrderId = getOrderId(deps.orderId) || pendingCancelContext?.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, pendingCancelContext);
    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') {
        if (deps.lastSocketDirectionAtRef) {
            deps.lastSocketDirectionAtRef.current = Date.now();
        }
        deps.setVehicleDirection(normalizedDirection);
    }
    const route = safeParse(payload?.content?.route, {});
    const distanceMeters = Number(route?.distance);
    const durationSecondsRaw = Number(route?.duration);
    const speedMps = normalizeSpeedMps(route?.speed);
    const distanceKm = normalizeDistanceKm(distanceMeters);
    if (distanceKm !== null) {
        deps.setRouteDistance(distanceKm);
    }
    const durationSeconds = normalizeDurationSeconds(durationSecondsRaw);
    if (durationSeconds !== null) {
        deps.setRouteDuration(durationSeconds);
    }
    deps.setOrderRouteMetrics?.({
        distanceMeters: Number.isFinite(distanceMeters) ? distanceMeters : null,
        durationSeconds: Number.isFinite(durationSecondsRaw) ? durationSecondsRaw : null,
        speedMps
    });
};
const syncVehiclePointOnly = (routeSnapshot: ReturnType<typeof normalizeSocketRouteSnapshot>, deps: UseWebSocketEventsDeps) => {
    const targetPoint = resolveVehiclePoint(
        routeSnapshot.points,
        routeSnapshot.vehicleMoveAlongPoints,
        routeSnapshot.normalVehiclePointMode
    );
    if (!targetPoint) {
        return;
    }
    deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
};
const handleOrderRoutePayload = (payload: any, deps: UseWebSocketEventsDeps) => {
    const beforeUserSite = parseBeforeUserEndSite(payload);
    deps.setBeforeUserEndSite(beforeUserSite);
    const currentTripStatus = deps.tripStatusRef.current;
    // socket 路线拦截只跟随真实的“修改目的地预览态”,不要被本地终点覆盖态误伤。
    const isModifyDestinationPreviewActive = Boolean(deps.isModifyDestinationReviewVisible);
    const shouldKeepModifyPreviewSummary = isModifyDestinationPreviewActive;
    if (!shouldKeepModifyPreviewSummary) {
        updateRouteSummary(payload, deps);
    }
    // 行程结束态仅更新概要信息,不再消费路线点,避免视口反复重算造成跳动
    if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(currentTripStatus)) {
        return;
    }
    const routeSnapshot = normalizeSocketRouteSnapshot(payload);
    const shouldOnlySyncModifyPreviewVehicle = isModifyDestinationPreviewActive && currentTripStatus === TripStatus.InService;
    const shouldFreezeModifyPreviewRoute = isModifyDestinationPreviewActive && currentTripStatus !== TripStatus.InService;
    if (isModifyDestinationPreviewActive) {
        deps.updatePreviousRoutePoints?.(routeSnapshot.points || []);
    }
    if (shouldFreezeModifyPreviewRoute) {
        return;
    }
    if (shouldOnlySyncModifyPreviewVehicle) {
        syncVehiclePointOnly(routeSnapshot, deps);
        return;
    }
    const currentOrderId = getOrderId(deps.orderId);
    if (routeSnapshot.vehicleMoveAlongPoints.length > 1) {
        writePassedRouteCache(currentOrderId, routeSnapshot.vehicleMoveAlongPoints);
    }
    deps.applySmoothRouteSnapshot(routeSnapshot);
    if (routeSnapshot.forceFullRedraw && typeof deps.onOrderUpdateLineRendered === 'function') {
        Taro.nextTick(() => {
            deps.onOrderUpdateLineRendered?.(routeSnapshot.points || []);
        });
    }
};
const createBusinessHandlerMap = (): Record<BusinessSocketEventName, SocketPayloadHandler | null> => ({
    order_status: null,
    order_route: null,
    open_trunk: null,
    order_update_price: null,
    order_reassign: null,
    update_end_address_status: null
});
export default function useWebSocketEvents(deps: UseWebSocketEventsDeps) {
    // 持有最新依赖,避免渲染过程反复解绑/重绑监听
    const depsRef = useRef(deps);
    const depsRef = useRef(deps) as WebSocketDepsRef;
    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 updateEndAddressStatusBound = useRef(false);
    const businessEventBoundRef = useRef(createBusinessEventBoundMap());
    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 updateEndAddressStatusHandlerRef = useRef<(data: any) => void>();
    const businessHandlerMapRef = useRef(createBusinessHandlerMap());
    const socketOpenHandlerRef = useRef<(() => void) | null>(null);
    const lastFetchedVehicleOrderIdRef = useRef<string | null>(null);
    // 仅初始化一次事件处理器与解绑逻辑
    /**
     * 绑定业务事件监听。
     */
    const bindSocketListeners = () => {
        if (!authSuccessBound.current && authSuccessHandlerRef.current) {
            wsManager.on('auth_success', authSuccessHandlerRef.current);
            authSuccessBound.current = true;
        }
        BUSINESS_SOCKET_EVENTS.forEach((eventName) => {
            const handler = businessHandlerMapRef.current[eventName];
            if (!businessEventBoundRef.current[eventName] && handler) {
                wsManager.on(eventName, handler);
                businessEventBoundRef.current[eventName] = true;
            }
        });
    };
    useEffect(() => {
        businessHandlerMapRef.current = {
            order_status: createOrderStatusSocketHandler(depsRef, lastFetchedVehicleOrderIdRef),
            order_route: createOrderRouteSocketHandler(depsRef),
            open_trunk: createOpenTrunkSocketHandler(depsRef),
            order_update_price: createOrderUpdatePriceSocketHandler(depsRef),
            order_reassign: createOrderReassignSocketHandler(depsRef, lastFetchedVehicleOrderIdRef),
            update_end_address_status: createUpdateEndAddressStatusSocketHandler(depsRef)
        };
        authSuccessHandlerRef.current = () => {
            if (!messageBound.current && messageHandlerRef.current) {
                wsManager.on('message', messageHandlerRef.current);
@@ -387,71 +156,9 @@
        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 = getOrderId(payload?.orderId || payload?.content?.orderId);
            const currentOrderId = getOrderId(currentDeps.orderId);
            if (!isSameOrder(incomingOrderId, currentOrderId)) {
                return;
            }
            currentDeps.setPriceUpdateVersion((v: number) => v + 1);
        };
        orderReassignHandlerRef.current = (payload) => {
            const currentDeps = depsRef.current;
            const incomingOrderId = getOrderId(payload?.orderId || payload?.content?.orderId);
            const currentOrderId = getOrderId(currentDeps.orderId);
            if (!isSameOrder(incomingOrderId, currentOrderId)) {
                return;
            }
            lastFetchedVehicleOrderIdRef.current = null;
            const requestedOrderId = String(currentOrderId || '');
            queryVehicleByOrderId({ orderId: currentOrderId })
                .then((res: any) => {
                    const latestOrderId = getOrderId(depsRef.current.orderId);
                    if (!requestedOrderId || latestOrderId !== requestedOrderId) {
                        return;
                    }
                    currentDeps.setVehicleInfo(res);
                })
                .catch(() => { });
        };
        updateEndAddressStatusHandlerRef.current = (payload) => {
            const currentDeps = depsRef.current;
            const incomingOrderId = getOrderId(payload?.content?.orderId);
            const currentOrderId = getOrderId(currentDeps.orderId);
            if (!isSameOrder(incomingOrderId, currentOrderId)) {
                return;
            }
            const nextStatus = Number(payload?.status);
            const normalizedStatus = Number.isFinite(nextStatus) ? nextStatus : null;
            currentDeps.setModifyDestinationSocketStatus(normalizedStatus);
            if (normalizedStatus === null) {
                Taro.removeStorageSync(MODIFY_DESTINATION_SOCKET_STATUS_KEY);
                return;
            }
            Taro.setStorageSync(MODIFY_DESTINATION_SOCKET_STATUS_KEY, {
                orderId: String(currentOrderId || incomingOrderId || ''),
                status: normalizedStatus
            });
        };
        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);
@@ -461,30 +168,13 @@
                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 (updateEndAddressStatusBound.current && updateEndAddressStatusHandlerRef.current) {
                wsManager.off('update_end_address_status', updateEndAddressStatusHandlerRef.current);
                updateEndAddressStatusBound.current = false;
            }
            BUSINESS_SOCKET_EVENTS.forEach((eventName) => {
                const handler = businessHandlerMapRef.current[eventName];
                if (businessEventBoundRef.current[eventName] && handler) {
                    wsManager.off(eventName, handler);
                    businessEventBoundRef.current[eventName] = false;
                }
            });
            if (socketOpenHandlerRef.current) {
                wsManager.off('open', socketOpenHandlerRef.current);
                socketOpenHandlerRef.current = null;
@@ -492,39 +182,9 @@
        };
    }, []);
    // 绑定业务事件监听
    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;
        }
        if (!updateEndAddressStatusBound.current && updateEndAddressStatusHandlerRef.current) {
            wsManager.on('update_end_address_status', updateEndAddressStatusHandlerRef.current);
            updateEndAddressStatusBound.current = true;
        }
    };
    // 确保连接可用并完成监听绑定
    /**
     * 确保连接可用并完成监听绑定。
     */
    const ensureSocketListeners = (host: string) => {
        if (wsManager.isConnectedStatus()) {
            bindSocketListeners();
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts
New file
@@ -0,0 +1,87 @@
import Taro from '@tarojs/taro';
import { queryVehicleByOrderId } from '../../../../api/begin-route';
import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from '../../constants/modify-destination';
import {
  executeWhenOrderMatched,
  resolvePayloadOrderIdFromContentOnly,
  resolvePayloadOrderIdFromTopLevelFirst,
  type WebSocketDepsRef
} from './web-socket-order-guard';
/**
 * 创建后备箱开启消息处理器。
 */
export const createOpenTrunkSocketHandler = (depsRef: WebSocketDepsRef) => (payload: any) => {
  const currentDeps = depsRef.current;
  const isOpen = Number(payload?.isOpen);
  if (isOpen === 1) {
    currentDeps.setFinishCountdownEnabled(true);
  }
};
/**
 * 创建价格更新消息处理器。
 */
export const createOrderUpdatePriceSocketHandler = (depsRef: WebSocketDepsRef) => (payload: any) => {
  executeWhenOrderMatched({
    payload,
    deps: depsRef.current,
    resolveIncomingOrderId: resolvePayloadOrderIdFromTopLevelFirst,
    onMatched: () => {
      depsRef.current.setPriceUpdateVersion((value: number) => value + 1);
    }
  });
};
/**
 * 创建订单改派消息处理器。
 */
export const createOrderReassignSocketHandler = (
  depsRef: WebSocketDepsRef,
  lastFetchedVehicleOrderIdRef: React.MutableRefObject<string | null>
) => (payload: any) => {
  executeWhenOrderMatched({
    payload,
    deps: depsRef.current,
    resolveIncomingOrderId: resolvePayloadOrderIdFromTopLevelFirst,
    onMatched: ({ currentOrderId }) => {
      const currentDeps = depsRef.current;
      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(() => { });
    }
  });
};
/**
 * 创建修改终点状态消息处理器。
 */
export const createUpdateEndAddressStatusSocketHandler = (depsRef: WebSocketDepsRef) => (payload: any) => {
  executeWhenOrderMatched({
    payload,
    deps: depsRef.current,
    resolveIncomingOrderId: resolvePayloadOrderIdFromContentOnly,
    onMatched: ({ incomingOrderId, currentOrderId }) => {
      const currentDeps = depsRef.current;
      const nextStatus = Number(payload?.status);
      const normalizedStatus = Number.isFinite(nextStatus) ? nextStatus : null;
      currentDeps.setModifyDestinationSocketStatus(normalizedStatus);
      if (normalizedStatus === null) {
        Taro.removeStorageSync(MODIFY_DESTINATION_SOCKET_STATUS_KEY);
        return;
      }
      Taro.setStorageSync(MODIFY_DESTINATION_SOCKET_STATUS_KEY, {
        orderId: String(currentOrderId || incomingOrderId || ''),
        status: normalizedStatus
      });
    }
  });
};
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
New file
@@ -0,0 +1,132 @@
import Taro from '@tarojs/taro';
import { safeParse } from '../../../../../utils/Robotaxi-public-method';
import { normalizeDistanceKm, normalizeDurationSeconds } from '../../../../utils/format';
import { getOrderId } from '../../../../utils/order';
import { TripStatus } from '../../constants/trip-status';
import { writePassedRouteCache } from '../passed-route-cache';
import { normalizeSocketRouteSnapshot, normalizeSpeedMps } from '../runtime-sync';
import { resolveVehiclePoint } from '../trip-motion-utils';
import { type WebSocketDepsRef } from './web-socket-order-guard';
/**
 * 解析“上一单乘客下车点”坐标。
 */
const parseBeforeUserEndSite = (payload: any) => {
  const longitude = Number(
    payload?.endLongitude
    ?? payload?.data?.endLongitude
    ?? payload?.content?.endLongitude
  );
  const latitude = Number(
    payload?.endLatitude
    ?? payload?.endlatitude
    ?? payload?.data?.endLatitude
    ?? payload?.data?.endlatitude
    ?? payload?.content?.endLatitude
    ?? payload?.content?.endlatitude
  );
  if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
    return null;
  }
  return { longitude, latitude };
};
/**
 * 统一解析方向值。
 */
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: WebSocketDepsRef['current']) => {
  const normalizedDirection = resolveDirectionValue(payload);
  if (typeof normalizedDirection === 'number') {
    if (deps.lastSocketDirectionAtRef) {
      deps.lastSocketDirectionAtRef.current = Date.now();
    }
    deps.setVehicleDirection(normalizedDirection);
  }
  const route = safeParse(payload?.content?.route, {});
  const distanceMeters = Number(route?.distance);
  const durationSecondsRaw = Number(route?.duration);
  const speedMps = normalizeSpeedMps(route?.speed);
  const distanceKm = normalizeDistanceKm(distanceMeters);
  if (distanceKm !== null) {
    deps.setRouteDistance(distanceKm);
  }
  const durationSeconds = normalizeDurationSeconds(durationSecondsRaw);
  if (durationSeconds !== null) {
    deps.setRouteDuration(durationSeconds);
  }
  deps.setOrderRouteMetrics?.({
    distanceMeters: Number.isFinite(distanceMeters) ? distanceMeters : null,
    durationSeconds: Number.isFinite(durationSecondsRaw) ? durationSecondsRaw : null,
    speedMps
  });
};
/**
 * 仅同步车辆点位,不重绘整条路线。
 */
const syncVehiclePointOnly = (
  routeSnapshot: ReturnType<typeof normalizeSocketRouteSnapshot>,
  deps: WebSocketDepsRef['current']
) => {
  const targetPoint = resolveVehiclePoint(
    routeSnapshot.points,
    routeSnapshot.vehicleMoveAlongPoints,
    routeSnapshot.normalVehiclePointMode
  );
  if (!targetPoint) {
    return;
  }
  deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
};
/**
 * 创建订单路线消息处理器。
 */
export const createOrderRouteSocketHandler = (depsRef: WebSocketDepsRef) => (payload: any) => {
  const currentDeps = depsRef.current;
  const beforeUserSite = parseBeforeUserEndSite(payload);
  currentDeps.setBeforeUserEndSite(beforeUserSite);
  const currentTripStatus = currentDeps.tripStatusRef.current;
  const isModifyDestinationPreviewActive = Boolean(currentDeps.isModifyDestinationReviewVisible);
  if (!isModifyDestinationPreviewActive) {
    updateRouteSummary(payload, currentDeps);
  }
  if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(currentTripStatus)) {
    return;
  }
  const routeSnapshot = normalizeSocketRouteSnapshot(payload);
  const shouldOnlySyncModifyPreviewVehicle = isModifyDestinationPreviewActive && currentTripStatus === TripStatus.InService;
  const shouldFreezeModifyPreviewRoute = isModifyDestinationPreviewActive && currentTripStatus !== TripStatus.InService;
  if (isModifyDestinationPreviewActive) {
    currentDeps.updatePreviousRoutePoints?.(routeSnapshot.points || []);
  }
  if (shouldFreezeModifyPreviewRoute) {
    return;
  }
  if (shouldOnlySyncModifyPreviewVehicle) {
    syncVehiclePointOnly(routeSnapshot, currentDeps);
    return;
  }
  const currentOrderId = getOrderId(currentDeps.orderId);
  if (routeSnapshot.vehicleMoveAlongPoints.length > 1) {
    writePassedRouteCache(currentOrderId, routeSnapshot.vehicleMoveAlongPoints);
  }
  currentDeps.applySmoothRouteSnapshot(routeSnapshot);
  if (routeSnapshot.forceFullRedraw && typeof currentDeps.onOrderUpdateLineRendered === 'function') {
    Taro.nextTick(() => {
      currentDeps.onOrderUpdateLineRendered?.(routeSnapshot.points || []);
    });
  }
};
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts
New file
@@ -0,0 +1,206 @@
import Taro from '@tarojs/taro';
import { queryVehicleByOrderId } from '../../../../api/begin-route';
import { clearOrderTripStorage } from '../../../../../utils/Robotaxi-public-method';
import { getOrderId } from '../../../../utils/order';
import { TripStatus, toTripStatus } from '../../constants/trip-status';
import { clearPassedRouteCache } from '../passed-route-cache';
import { PENDING_CANCEL_CONTEXT_TTL_MS, type PendingCancelContext } from '../use-order-actions.shared';
import type { UseWebSocketEventsDeps } from '../use-web-socket-events';
import { isSameOrder, resolveCurrentOrderId, type WebSocketDepsRef } from './web-socket-order-guard';
/**
 * 解析 payload 中用于同步订单的有效 orderId。
 */
const resolveEffectiveOrderId = (payload: any, fallbackOrderId: string) =>
  getOrderId(payload?.content?.orderId || payload?.orderId || fallbackOrderId);
/**
 * 获取仍在有效期内的待确认取消上下文。
 */
const getValidPendingCancelContext = (deps: UseWebSocketEventsDeps) => {
  const pendingCancelContext = deps.pendingCancelContextRef.current;
  if (!pendingCancelContext) {
    return null;
  }
  if (Date.now() - pendingCancelContext.createdAt > PENDING_CANCEL_CONTEXT_TTL_MS) {
    deps.pendingCancelContextRef.current = null;
    return null;
  }
  return pendingCancelContext;
};
/**
 * 判断取消消息是否可复用本地记录的取消上下文。
 */
const isPendingCancelContextMatched = (payload: any, pendingCancelContext: PendingCancelContext | null) => {
  if (!pendingCancelContext) {
    return false;
  }
  const socketStatus = Number(payload?.status);
  if (socketStatus !== 0) {
    return false;
  }
  const incomingOrderId = String(payload?.content?.orderId || payload?.orderId || '');
  if (!incomingOrderId || !pendingCancelContext.orderId) {
    return true;
  }
  return incomingOrderId === pendingCancelContext.orderId;
};
/**
 * 重置路线相关运行时状态。
 */
const resetRouteRuntimeState = (deps: UseWebSocketEventsDeps) => {
  deps.setRoutePoints([]);
  deps.setPolyline([]);
  deps.setVehicleMoveAlongPoints([]);
  clearPassedRouteCache();
  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;
  deps.resetSmoothRouteMotion();
};
/**
 * 同步订单状态。
 */
const syncTripStatus = (payload: any, deps: UseWebSocketEventsDeps) => {
  const incomingTripStatus = Number(payload?.tripStatus);
  if (!Number.isFinite(incomingTripStatus)) {
    return null;
  }
  const currentTripStatus = deps.tripStatusRef.current;
  const mappedTripStatus = toTripStatus(incomingTripStatus, currentTripStatus);
  const modifyDestinationPickupSuccessAt = Number(deps.modifyDestinationPickupSuccessAtRef.current || 0);
  const shouldGuardModifyDestinationPickupStatus = mappedTripStatus === TripStatus.InService
    && [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(currentTripStatus)
    && modifyDestinationPickupSuccessAt > 0
    && Date.now() - modifyDestinationPickupSuccessAt <= deps.modifyDestinationPickupStatusGuardMs;
  if (shouldGuardModifyDestinationPickupStatus) {
    deps.modifyDestinationPickupSuccessAtRef.current = 0;
    return currentTripStatus;
  }
  if ([TripStatus.VehicleApproaching, TripStatus.InService].includes(currentTripStatus) && mappedTripStatus < currentTripStatus) {
    return currentTripStatus;
  }
  if (currentTripStatus === mappedTripStatus) {
    return mappedTripStatus;
  }
  deps.setTripStatus(mappedTripStatus);
  deps.tripStatusRef.current = mappedTripStatus;
  Taro.setStorageSync('tripStatus', mappedTripStatus);
  return mappedTripStatus;
};
/**
 * 同步车辆与当前订单 ID。
 */
const syncVehicleInfo = (
  effectiveOrderId: string,
  deps: UseWebSocketEventsDeps,
  lastFetchedVehicleOrderIdRef: React.MutableRefObject<string | null>
) => {
  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: TripStatus,
  deps: UseWebSocketEventsDeps,
  pendingCancelContext: PendingCancelContext | null
) => {
  if (!payload?.showCancelPopup) {
    return;
  }
  const matchedPendingCancelContext = isPendingCancelContextMatched(payload, pendingCancelContext);
  if (matchedPendingCancelContext) {
    deps.setCancelType(pendingCancelContext?.cancelType || 4);
    deps.pendingCancelContextRef.current = null;
  } else if (changeStatusOld === TripStatus.VehicleApproaching || changeStatusOld === TripStatus.VehicleArrivedPickup) {
    deps.setOrderId('');
    deps.setVehicleInfo({});
    clearOrderTripStorage();
    Taro.removeStorageSync('pickupEndTime');
    Taro.removeStorageSync('waitingTimerStartAt');
    Taro.removeStorageSync('waitingTimerElapsed');
    deps.setCancelType(4);
  } else {
    deps.setCancelType(5);
  }
  deps.setTripStatus(TripStatus.CancelOnlyPickup);
  deps.setShowCancelPopup(true);
};
/**
 * 处理状态切换后的路线与公告副作用。
 */
const handleStatusSideEffects = (
  mappedTripStatus: TripStatus | null,
  changeStatusOld: TripStatus,
  deps: UseWebSocketEventsDeps
) => {
  if (mappedTripStatus === TripStatus.InService) {
    deps.setShowAnnouncementsPopup(false);
  }
  if (mappedTripStatus === TripStatus.VehicleArrivedPickup && changeStatusOld !== TripStatus.VehicleArrivedPickup) {
    resetRouteRuntimeState(deps);
  }
  if (mappedTripStatus === TripStatus.VehicleApproaching && changeStatusOld !== TripStatus.VehicleApproaching) {
    resetRouteRuntimeState(deps);
  }
};
/**
 * 创建订单状态消息处理器。
 */
export const createOrderStatusSocketHandler = (
  depsRef: WebSocketDepsRef,
  lastFetchedVehicleOrderIdRef: React.MutableRefObject<string | null>
) => (payload: any) => {
  const currentDeps = depsRef.current;
  const changeStatusOld = currentDeps.tripStatusRef.current;
  const pendingCancelContext = getValidPendingCancelContext(currentDeps);
  const currentOrderId = resolveCurrentOrderId(currentDeps) || pendingCancelContext?.orderId || '';
  const incomingOrderId = payload?.content?.orderId || payload?.orderId;
  if (!isSameOrder(incomingOrderId, currentOrderId)) {
    return;
  }
  const mappedTripStatus = syncTripStatus(payload, currentDeps);
  const effectiveOrderId = resolveEffectiveOrderId(payload, currentOrderId);
  syncVehicleInfo(effectiveOrderId, currentDeps, lastFetchedVehicleOrderIdRef);
  handleStatusSideEffects(mappedTripStatus, changeStatusOld, currentDeps);
  handleCancelPopupPush(payload, changeStatusOld, currentDeps, pendingCancelContext);
  if (payload?.showPassengerNotice) {
    currentDeps.checkAndShowPassengerNotice();
  }
};
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/web-socket-order-guard.ts
New file
@@ -0,0 +1,67 @@
import { getOrderId } from '../../../../utils/order';
import type { UseWebSocketEventsDeps } from '../use-web-socket-events';
/** 最新依赖引用。 */
export type WebSocketDepsRef = React.MutableRefObject<UseWebSocketEventsDeps>;
/**
 * 解析当前页面对应的订单 ID。
 */
export const resolveCurrentOrderId = (deps: UseWebSocketEventsDeps) => getOrderId(deps.orderId);
/**
 * 解析 payload 中优先 content 字段的订单 ID。
 */
export const resolvePayloadOrderIdFromContentFirst = (payload: any) =>
  getOrderId(payload?.content?.orderId || payload?.orderId);
/**
 * 解析 payload 中优先顶层字段的订单 ID。
 */
export const resolvePayloadOrderIdFromTopLevelFirst = (payload: any) =>
  getOrderId(payload?.orderId || payload?.content?.orderId);
/**
 * 仅解析 payload content 字段中的订单 ID。
 */
export const resolvePayloadOrderIdFromContentOnly = (payload: any) =>
  getOrderId(payload?.content?.orderId);
/**
 * 判断当前消息与页面订单是否一致。
 */
export const isSameOrder = (incomingOrderId?: string, currentOrderId?: string) => {
  if (!incomingOrderId && !currentOrderId) return false;
  if (!incomingOrderId || !currentOrderId) return false;
  return String(incomingOrderId) === String(currentOrderId);
};
type ExecuteWhenOrderMatchedOptions = {
  payload: any;
  deps: UseWebSocketEventsDeps;
  resolveIncomingOrderId: (payload: any) => string;
  onMatched: (params: {
    incomingOrderId: string;
    currentOrderId: string;
  }) => void;
};
/**
 * 仅在 payload 与当前页面订单匹配时执行处理逻辑。
 */
export const executeWhenOrderMatched = ({
  payload,
  deps,
  resolveIncomingOrderId,
  onMatched
}: ExecuteWhenOrderMatchedOptions) => {
  const incomingOrderId = resolveIncomingOrderId(payload);
  const currentOrderId = resolveCurrentOrderId(deps);
  if (!isSameOrder(incomingOrderId, currentOrderId)) {
    return;
  }
  onMatched({
    incomingOrderId,
    currentOrderId
  });
};