hejianhao
2026-05-14 2cba7d907c96d3e93a7fafb29e0d6b564c5b5eda
feat: 添加修改目的地的websocket状态处理与页面控制

新增WebSocketManager对UPDATE_END_ADDRESS_STATUS事件的监听通知,新增本地存储与状态管理来持久化后端推送的修改目的地状态,更新行程开始页面的显示逻辑以使用该状态控制修改目的地按钮的可见性,同时移除过时的修改次数与低速限制逻辑
4个文件已修改
117 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/constants/modify-destination.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/constants/modify-destination.ts
@@ -3,3 +3,4 @@
export const MODIFY_DESTINATION_OVERRIDE_KEY = 'modifyDestinationOverride';
export const MODIFY_DESTINATION_ROUTE_POINTS_KEY = 'modifyDestinationRoutePoints';
export const MODIFY_DESTINATION_SKIP_AC_QUERY_ON_RETURN_KEY = 'modifyDestinationSkipAcQueryOnReturn';
export const MODIFY_DESTINATION_SOCKET_STATUS_KEY = 'modifyDestinationSocketStatus';
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -6,6 +6,7 @@
import { normalizeDistanceKm, normalizeDurationSeconds } from '../../../../robotaxi/utils/format';
import { toTripStatus, TripStatus } from '../constants/trip-status';
import { normalizeSocketRouteSnapshot } from './runtime-sync';
import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from '../constants/modify-destination';
type UseWebSocketEventsDeps = {
    tripStatusRef: React.MutableRefObject<TripStatus>;
@@ -35,6 +36,7 @@
    setVehicleMoveAlongPoints: (points: any[]) => void;
    setRouteDistance: (value: any) => void;
    setRouteDuration: (value: number) => void;
    setModifyDestinationSocketStatus: (value: number | null) => void;
    setOrderRouteMetrics?: (metrics: {
        distanceMeters: number | null;
        durationSeconds: number | null;
@@ -249,6 +251,7 @@
    const openTrunkBound = useRef(false);
    const orderUpdatePriceBound = useRef(false);
    const orderReassignBound = useRef(false);
    const updateEndAddressStatusBound = useRef(false);
    const authSuccessHandlerRef = useRef<(data: any) => void>();
    const messageHandlerRef = useRef<(data: any) => void>();
@@ -257,6 +260,7 @@
    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 socketOpenHandlerRef = useRef<(() => void) | null>(null);
    const lastFetchedVehicleOrderIdRef = useRef<string | null>(null);
@@ -306,6 +310,25 @@
                })
                .catch(() => { });
        };
        updateEndAddressStatusHandlerRef.current = (payload) => {
            const currentDeps = depsRef.current;
            const incomingOrderId = payload?.content?.orderId || Taro.getStorageSync('orderId');
            const currentOrderId = currentDeps.orderId || Taro.getStorageSync('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);
        };
@@ -346,6 +369,10 @@
                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;
            }
            if (socketOpenHandlerRef.current) {
                wsManager.off('open', socketOpenHandlerRef.current);
                socketOpenHandlerRef.current = null;
@@ -379,6 +406,10 @@
            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;
        }
    };
    // 确保连接可用并完成监听绑定
src/robotaxi/pages/begin-route/index.tsx
@@ -43,6 +43,7 @@
// 蓝牙车控连接与鉴权编排
import useBluetoothCarControl from './hooks/use-bluetooth-car-control';
import { toTripStatus, TripStatus } from './constants/trip-status';
import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from './constants/modify-destination';
import { getDistance } from '../../../utils/Robotaxi-public-method';
import backIcon from '../../../robotaxi/assets/back-icon.png'
@@ -146,9 +147,7 @@
    durationSeconds: null,
    speedKmh: null
  });
  const [modifyDestinationUsedCount, setModifyDestinationUsedCount] = useState(0); // 本次行程已成功修改目的地次数
  const [lowSpeedDurationMs, setLowSpeedDurationMs] = useState(0); // 连续低速持续时长(毫秒)
  const lowSpeedStartAtRef = useRef<number | null>(null); // 连续低速开始时间戳
  const [modifyDestinationSocketStatus, setModifyDestinationSocketStatus] = useState<number | null>(null); // 后端推送的修改目的地展示状态
  const tripStatusRef = useRef(tripStatus); // 行程状态引用
  const lastTripStatusRef = useRef(tripStatus); // 上一次行程状态,用于检测状态变化
@@ -159,14 +158,20 @@
    Taro.setStorageSync('tripStatus', tripStatus);
  }, [tripStatus]);
  useEffect(() => {
    setModifyDestinationUsedCount(0);
    setOrderRouteMetrics({
      distanceMeters: null,
      durationSeconds: null,
      speedKmh: null
    });
    lowSpeedStartAtRef.current = null;
    setLowSpeedDurationMs(0);
    const cachedSocketStatus = Taro.getStorageSync(MODIFY_DESTINATION_SOCKET_STATUS_KEY);
    const cachedOrderId = String(cachedSocketStatus?.orderId || '');
    const nextOrderId = String(orderId || '');
    if (cachedOrderId && nextOrderId && cachedOrderId === nextOrderId) {
      const cachedStatus = Number(cachedSocketStatus?.status);
      setModifyDestinationSocketStatus(Number.isFinite(cachedStatus) ? cachedStatus : null);
      return;
    }
    setModifyDestinationSocketStatus(null);
  }, [orderId]);
  // 行程状态显示规则配置:showPickup-显示上车点,showDropoff-显示下车点,showCar-显示车辆,showRoute-显示路线,showWalking-显示步行路线,viewport-视口类型,translateCar-车辆是否平移
  const STATUS_RULES: Record<TripStatus, StatusRule> = {
@@ -229,7 +234,6 @@
    setEndSite,
    toSelectSite,
    onModifySuccess: () => {
      setModifyDestinationUsedCount((prev) => prev + 1);
      // 成功修改后触发路线重新获取
      // 下一次渲染时 isModifyDestinationReviewVisible 变为 false
      // 我们需要强制让选路状态退出,同时获取后端的新行程路线
@@ -463,36 +467,7 @@
    : DEFAULT_REQUOTE_EXPIRE_SECONDS; // 重新报价有效期(秒)
  const modifyDistanceLimitMeters = Number(distanceConfig?.xczSyDistance); // 修改目的地剩余里程限制(米)
  const modifyDurationLimitMinutes = Number(distanceConfig?.xczSyTime); // 修改目的地剩余时间限制(分钟)
  const modifyCountLimit = Number(distanceConfig?.xczCaCount); // 单次行程允许修改目的地次数上限
  const lowSpeedLimitKmh = Number(distanceConfig?.xczHmSpeedPerHour); // 低速豁免阈值(km/h)
  const lowSpeedLimitMinutes = Number(distanceConfig?.xczHmDuration); // 低速豁免持续时间(分钟)
  const isTripStatusAllowModifyEntry = [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus);
  useEffect(() => {
    if (!isTripStatusAllowModifyEntry) {
      lowSpeedStartAtRef.current = null;
      setLowSpeedDurationMs(0);
      return;
    }
    if (!Number.isFinite(lowSpeedLimitKmh) || lowSpeedLimitKmh <= 0) {
      lowSpeedStartAtRef.current = null;
      setLowSpeedDurationMs(0);
      return;
    }
    const currentSpeed = Number(orderRouteMetrics?.speedKmh);
    if (!Number.isFinite(currentSpeed) || currentSpeed >= lowSpeedLimitKmh) {
      lowSpeedStartAtRef.current = null;
      setLowSpeedDurationMs(0);
      return;
    }
    const now = Date.now();
    if (!lowSpeedStartAtRef.current) {
      lowSpeedStartAtRef.current = now;
      setLowSpeedDurationMs(0);
      return;
    }
    setLowSpeedDurationMs(now - lowSpeedStartAtRef.current);
  }, [isTripStatusAllowModifyEntry, orderRouteMetrics?.speedKmh, lowSpeedLimitKmh]);
  const showModifyDestination = useMemo(() => {
    if (!isTripStatusAllowModifyEntry) {
@@ -501,44 +476,29 @@
    if (tripStatus !== TripStatus.InService) {
      return true;
    }
    if (modifyDestinationSocketStatus === 0) {
      return false;
    }
    if (modifyDestinationSocketStatus === 1) {
      return true;
    }
    const hasDistanceLimit = Number.isFinite(modifyDistanceLimitMeters) && modifyDistanceLimitMeters > 0;
    const hasDurationLimit = Number.isFinite(modifyDurationLimitMinutes) && modifyDurationLimitMinutes > 0;
    const hasCountLimit = Number.isFinite(modifyCountLimit) && modifyCountLimit > 0;
    const hasLowSpeedLimit = Number.isFinite(lowSpeedLimitKmh) && lowSpeedLimitKmh > 0;
    const hasLowSpeedDurationLimit = Number.isFinite(lowSpeedLimitMinutes) && lowSpeedLimitMinutes > 0;
    const currentDistance = Number(orderRouteMetrics?.distanceMeters);
    const currentDurationSeconds = Number(orderRouteMetrics?.durationSeconds);
    const currentSpeed = Number(orderRouteMetrics?.speedKmh);
    const hitDistanceLimit = hasDistanceLimit && Number.isFinite(currentDistance) && currentDistance < modifyDistanceLimitMeters;
    const hitDurationLimit = hasDurationLimit && Number.isFinite(currentDurationSeconds) && (currentDurationSeconds / 60) < modifyDurationLimitMinutes;
    const hitCountLimit = hasCountLimit && modifyDestinationUsedCount >= modifyCountLimit;
    const hitAnyLimit = hitDistanceLimit || hitDurationLimit || hitCountLimit;
    const hitLowSpeedExemption = hasLowSpeedLimit
      && hasLowSpeedDurationLimit
      && Number.isFinite(currentSpeed)
      && currentSpeed < lowSpeedLimitKmh
      && lowSpeedDurationMs >= lowSpeedLimitMinutes * 60 * 1000;
    if (hitLowSpeedExemption) {
      return true;
    }
    return !hitAnyLimit;
    return !(hitDistanceLimit || hitDurationLimit);
  }, [
    isTripStatusAllowModifyEntry,
    tripStatus,
    modifyDestinationSocketStatus,
    modifyDistanceLimitMeters,
    modifyDurationLimitMinutes,
    modifyCountLimit,
    lowSpeedLimitKmh,
    lowSpeedLimitMinutes,
    orderRouteMetrics?.distanceMeters,
    orderRouteMetrics?.durationSeconds,
    orderRouteMetrics?.speedKmh,
    modifyDestinationUsedCount,
    lowSpeedDurationMs
    orderRouteMetrics?.durationSeconds
  ]);
  const {
    handleDidShow
@@ -676,6 +636,7 @@
    setVehicleMoveAlongPoints,
    setRouteDistance,
    setRouteDuration,
    setModifyDestinationSocketStatus,
    setOrderRouteMetrics,
    setBeforeUserEndSite,
    erasePassedRouteSegments: routeErasePassedRouteSegments,
src/utils/websocket.ts
@@ -846,6 +846,10 @@
      const isOpen = content?.isOpen ?? content?.open;
      this.notifyListeners('open_trunk', { isOpen, data, content });
    }
    if (data?.type === 'UPDATE_END_ADDRESS_STATUS') {
      const status = Number(content?.status);
      this.notifyListeners('update_end_address_status', { status, data, content });
    }
  }
  /**