hejianhao
2026-05-09 3e8a64304d33d5a2ff8debe74e19641f981dadc5
Revert "Merge branch 'feature_fix_socket' into feature_v1.0"

This reverts commit 9ef64f2625339257111de96ed31e938e1ef8c348, reversing
changes made to 505f5fa28a36cd5b798a5c8196252580ef88ef66.
4个文件已删除
11个文件已修改
2842 ■■■■■ 已修改文件
src/pages/robotaxi/home/index.tsx 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/runtime-sync.ts 433 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/trip-motion-utils.ts 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 289 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-smooth-route-motion.ts 334 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-socket-fallback-polling.ts 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 755 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.less 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx
@@ -72,11 +72,7 @@
import { subscribeOperationCouponGroup } from '../../../utils/subscribe';
import { GeofenceType } from '../../../robotaxi/constants/geofence';
enum HomePageTripStatus {
  None = 0
}
type HomeTripStatus = TripStatus | HomePageTripStatus;
type HomeTripStatus = TripStatus | 0;
export default function Home() {
  const renderTailPreserveText = (text: string, tailCount = 3) => {
@@ -144,7 +140,7 @@
  const [isMaintain, setIsMaintain] = useState(false); // 是否维护中
  const [maintainBounce, setMaintainBounce] = useState(false);
  const [hotfixEndText, setHotfixEndText] = useState('');
  const [tripStatus, setTripStatus] = useState<HomeTripStatus>(HomePageTripStatus.None); // 行程状态(None 表示无进行中行程)
  const [tripStatus, setTripStatus] = useState<HomeTripStatus>(0); // 行程状态(0 表示无进行中行程)
  const [bannerList, setBannerList] = useState<any[]>([]); // 轮播图列表
  const [weatherData, setWeatherData] = useState<any>(null); // 天气数据
  const [orderData, setOrderData] = useState<any>({}); // 订单数据
@@ -155,10 +151,7 @@
  const [stationGeofenceList, setStationGeofenceList] = useState<any[]>([]); // 场站围栏原始数据
  const operatingGeofenceListRef = useRef<any[]>([]); // 运营区围栏最新缓存
  const stationGeofenceListRef = useRef<any[]>([]); // 场站围栏最新缓存
  const shouldShowOrderStatusCard = useMemo(
    () => tripStatus === TripStatus.CallingCar || Boolean(orderData?.id),
    [orderData?.id, tripStatus]
  ); // 订单状态卡片展示时机,避免真实数据未返回时先展示默认图
  const shouldShowOrderStatusCard = useMemo(() => tripStatus === 3 || Boolean(orderData?.id), [orderData?.id, tripStatus]); // 订单状态卡片展示时机
  const {
    boxHeight,
    originOpacity,
@@ -296,11 +289,7 @@
    const orderStatusHandler = (payload) => {
      const mappedTripStatus = payload?.tripStatus; // ws 推送的行程状态
      if (typeof mappedTripStatus === 'number') {
        setTripStatus(
          mappedTripStatus === HomePageTripStatus.None
            ? HomePageTripStatus.None
            : toTripStatus(mappedTripStatus)
        );
        setTripStatus(mappedTripStatus === 0 ? 0 : toTripStatus(mappedTripStatus));
      }
    };
    let openHandler: (() => void) | null = null;
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -35,39 +35,27 @@
        };
    }, []);
    // 获取解锁请求所需定位参数,优先高精定位,失败后降级普通定位
    // 获取解锁请求所需定位参数,失败时回退到缓存上车点
    const getUnlockLocationPayload = async () => {
        Taro.showLoading({ title: '正在获取位置...', mask: true });
        try {
            const location = await Taro.getLocation({
                type: 'gcj02',
                isHighAccuracy: true,
                highAccuracyExpireTime: 4000
            });
            Taro.showLoading({ title: '正在获取位置...', mask: true });
            const location = await Taro.getLocation({ type: 'gcj02', isHighAccuracy: true });
            Taro.hideLoading();
            const longitude = Number(location?.longitude);
            const latitude = Number(location?.latitude);
            if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
                Taro.hideLoading();
                return { longitude, latitude };
            }
        } catch (error) {
            console.error('高精度定位获取失败:', error);
        } catch (_) {
            Taro.hideLoading();
        }
        try {
            const location = await Taro.getLocation({ type: 'gcj02' });
            const longitude = Number(location?.longitude);
            const latitude = Number(location?.latitude);
            if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
                Taro.hideLoading();
                return { longitude, latitude };
            }
        } catch (error) {
            console.error('普通定位获取失败:', error);
        const startSite = Taro.getStorageSync('startSite') || {};
        const longitude = Number(startSite?.longitude);
        const latitude = Number(startSite?.latitude);
        if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
            return { longitude, latitude };
        }
        Taro.hideLoading();
        return null;
        return {};
    };
    // 解锁车门/后备箱,后备箱场景会轮询确认结果
@@ -76,10 +64,6 @@
        if (!targetOrderId) return;
        
        const locationPayload = await getUnlockLocationPayload();
        if (!locationPayload) {
            Taro.showToast({ title: '获取定位失败,请打开定位开关', icon: 'none' });
            return;
        }
        const isCloseAction = doorType === 'trunk' ? !!trunkStatus : false;
        const actionLoadingText = doorType === 'trunk'
            ? (isCloseAction ? '正在关闭后备箱' : '正在开启后备箱')
src/robotaxi/pages/begin-route/hooks/runtime-sync.ts
File was deleted
src/robotaxi/pages/begin-route/hooks/trip-motion-utils.ts
File was deleted
src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
@@ -15,7 +15,6 @@
};
export default function useAndroidOverlays(deps: Deps) {
  const CAR_CALLOUT_TOP_OFFSET = -10; // 车辆气泡顶部基准偏移,保证与车 icon 保持约 10px 间距
  // 等待雷达样式与显示状态
  const [waitingRadarStyle, setWaitingRadarStyle] = useState<any>({});
  const [waitingRadarVisible, setWaitingRadarVisible] = useState(true);
@@ -92,13 +91,13 @@
      return;
    }
    const rect = await ensureMapRect();
    const pos = await computeScreenPosition(deps.vehicleLocation, rect, CAR_CALLOUT_TOP_OFFSET);
    const pos = await computeScreenPosition(deps.vehicleLocation, rect, 60);
    if (!pos) return;
    setCarCalloutStyle({
      position: 'fixed',
      left: pos.left,
      top: pos.top,
      transform: 'translate(-50%, calc(-100% - 10px))'
      transform: 'translate(-50%, -120%)'
    });
    setCarCalloutVisible(true);
  };
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -3,45 +3,9 @@
import { TripStatus } from '../constants/trip-status';
import changeSiteUpIcon from '../../../assets/change-site-up.png';
export enum MarkerType {
  PICKUP_ICON = 'pickup_icon',
  PICKUP_TEXT = 'pickup_text',
  DROPOFF_ICON = 'dropoff_icon',
  DROPOFF_TEXT = 'dropoff_text',
  BEFORE_USER_DROPOFF = 'before_user_dropoff',
  CAR = 'car'
}
type MarkerItem = {
  id: number;
  latitude: number;
  longitude: number;
  iconPath: string;
  width: number;
  height: number;
  anchor: { x: number; y: number };
  zIndex: number;
  name: string;
  markerType: MarkerType;
  alpha?: number;
  rotate?: number;
  customCallout?: any;
  callout?: any;
  isBeforeUserSite?: boolean;
  hideNameCallout?: boolean;
  isRouteSummaryBubble?: boolean;
};
type StatusRule = {
  showPickup: boolean;
  showDropoff: boolean;
  showCar: boolean;
  markerOrder?: MarkerType[];
};
type Deps = {
  tripStatus: TripStatus;
  statusRule: StatusRule;
  statusRule: any;
  isAndroid: boolean;
  showTrip1StyleEndpoint: boolean;
  isModifyDestinationReviewVisible: boolean;
@@ -71,36 +35,21 @@
  arrivedTooltipReady: false
};
const PICKUP_ICON_MARKER_ID = 11; // 上车点图标 marker ID
const DROPOFF_ICON_MARKER_ID = 12; // 下车点图标 marker ID
const BEFORE_USER_DROPOFF_MARKER_ID = 31; // 上一单终点 marker ID
const SITE_TEXT_MARKER_SIZE = 1; // 站点文字锚点 marker 尺寸
const SITE_TEXT_CALLOUT_ANCHOR = -5; // 站点文字默认锚点偏移
const CAR_CALLOUT_ANCHOR = -10; // 车辆里程气泡锚点偏移,保证与车 icon 保持约 10px 间距
const DEFAULT_MARKER_ORDER: MarkerType[] = [
  MarkerType.PICKUP_ICON,
  MarkerType.CAR,
  MarkerType.DROPOFF_ICON,
  MarkerType.BEFORE_USER_DROPOFF,
  MarkerType.PICKUP_TEXT,
  MarkerType.DROPOFF_TEXT
]; // 默认图层顺序
export default function useMapMarkers(rawDeps?: Partial<Deps>) {
  const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps;
  const [markers, setMarkers] = useState<MarkerItem[]>([]);
  const markersRef = useRef<MarkerItem[]>([]);
  // 地图 marker 集合
  const [markers, setMarkers] = useState<any[]>([]);
  const markersRef = useRef<any[]>([]);
  const markersSignatureRef = useRef<string>('');
  const carMarkerInitialized = useRef(false);
  const tripStatus6MarkerRefreshedRef = useRef(false);
  const createPickupIconMarker = (pickupSite: any): MarkerItem | null => {
  // 构建上车点 marker
  const createPickupMarker = (pickupSite: any, useAndroidNativeTrip1Callout: boolean) => {
    if (!(pickupSite?.latitude && pickupSite?.longitude)) return null;
    const shouldUseChangePickupIcon =
      deps.isModifyDestinationReviewVisible &&
      deps.tripStatus >= TripStatus.VehicleArrivedPickup;
    const shouldUseChangePickupIcon = deps.isModifyDestinationReviewVisible && deps.tripStatus >= TripStatus.VehicleArrivedPickup; // 修改目的地确认态且进入行程阶段时使用换点上车图标
    return {
      id: PICKUP_ICON_MARKER_ID,
      id: 1,
      latitude: pickupSite.latitude,
      longitude: pickupSite.longitude,
      iconPath: shouldUseChangePickupIcon
@@ -111,40 +60,32 @@
      anchor: { x: 0.5, y: 1 },
      zIndex: 1,
      name: pickupSite?.name || '上车点',
      markerType: MarkerType.PICKUP_ICON
      hideNameCallout: deps.isModifyDestinationReviewVisible, // 修改目的地确认态隐藏上车点名称气泡
      ...(useAndroidNativeTrip1Callout
        ? {
          callout: {
            content: (deps.tripStatus === TripStatus.PublishRoute || deps.showTrip1StyleEndpoint) ? `${pickupSite?.name || '上车点'} >` : (pickupSite?.name || '上车点'),
            color: '#000000',
            fontSize: 12,
            bgColor: '#FFFFFF',
            borderRadius: 8,
            padding: 8,
            display: 'ALWAYS',
            textAlign: 'center',
          },
        }
        : {
          customCallout: { anchorY: [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus) ? 150 : -5, anchorX: 0, display: 'ALWAYS' },
        })
    };
  };
  const createPickupTextMarker = (pickupSite: any): MarkerItem | null => {
    if (!(pickupSite?.latitude && pickupSite?.longitude)) return null;
    return {
      id: 1,
      latitude: pickupSite.latitude,
      longitude: pickupSite.longitude,
      iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-up.png',
      width: SITE_TEXT_MARKER_SIZE,
      height: SITE_TEXT_MARKER_SIZE,
      alpha: 0,
      anchor: { x: 0.5, y: 1 },
      zIndex: 3,
      name: pickupSite?.name || '上车点',
      markerType: MarkerType.PICKUP_TEXT,
      customCallout: {
        anchorY: SITE_TEXT_CALLOUT_ANCHOR,
        anchorX: 0,
        display: 'ALWAYS'
      }
    };
  };
  const createDropoffIconMarker = (dropoffSite: any): MarkerItem | null => {
  // 构建下车点 marker
  const createDropoffMarker = (dropoffSite: any, useAndroidNativeTrip1Callout: boolean) => {
    if (!(dropoffSite?.latitude && dropoffSite?.longitude)) return null;
    const dropoffName =
      deps.tripStatus === TripStatus.ArrivedDestination
        ? '已到达'
        : (dropoffSite.name || '目的地');
    const dropoffName = [TripStatus.ArrivedDestination].includes(deps.tripStatus) ? '已到达' : (dropoffSite.name || '目的地');
    return {
      id: DROPOFF_ICON_MARKER_ID,
      id: 2,
      latitude: dropoffSite.latitude,
      longitude: dropoffSite.longitude,
      iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-down.png',
@@ -153,42 +94,32 @@
      anchor: { x: 0.5, y: 1 },
      zIndex: 1,
      name: dropoffName,
      markerType: MarkerType.DROPOFF_ICON
      ...(useAndroidNativeTrip1Callout || deps.tripStatus === TripStatus.InService
        ? {
          callout: {
            content: (deps.tripStatus === TripStatus.PublishRoute || deps.showTrip1StyleEndpoint) ? `${dropoffName} >` : dropoffName,
            color: '#000000',
            fontSize: 12,
            bgColor: '#FFFFFF',
            borderRadius: 8,
            padding: 8,
            display: 'ALWAYS',
            textAlign: 'center',
          },
        }
        : {
          customCallout: { anchorY: -5, anchorX: 0, display: 'ALWAYS' },
        })
    };
  };
  const createDropoffTextMarker = (dropoffSite: any): MarkerItem | null => {
    if (!(dropoffSite?.latitude && dropoffSite?.longitude)) return null;
    const dropoffName =
      deps.tripStatus === TripStatus.ArrivedDestination
        ? '已到达'
        : (dropoffSite.name || '目的地');
    return {
      id: 2,
      latitude: dropoffSite.latitude,
      longitude: dropoffSite.longitude,
      iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-down.png',
      width: SITE_TEXT_MARKER_SIZE,
      height: SITE_TEXT_MARKER_SIZE,
      alpha: 0,
      anchor: { x: 0.5, y: 1 },
      zIndex: 3,
      name: dropoffName,
      markerType: MarkerType.DROPOFF_TEXT,
      customCallout: {
        anchorY: SITE_TEXT_CALLOUT_ANCHOR,
        anchorX: 0,
        display: 'ALWAYS'
      }
    };
  };
  const createBeforeUserDropoffMarker = (): MarkerItem | null => {
  // 构建上一单终点 marker
  const createBeforeUserDropoffMarker = () => {
    const latitude = Number(deps.beforeUserEndSite?.latitude);
    const longitude = Number(deps.beforeUserEndSite?.longitude);
    if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null;
    return {
      id: BEFORE_USER_DROPOFF_MARKER_ID,
      id: 31,
      latitude,
      longitude,
      iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/before-user-site.png',
@@ -197,17 +128,13 @@
      anchor: { x: 0.5, y: 1 },
      zIndex: 1,
      name: '上一单终点',
      markerType: MarkerType.BEFORE_USER_DROPOFF,
      isBeforeUserSite: true,
      customCallout: {
        anchorY: SITE_TEXT_CALLOUT_ANCHOR,
        anchorX: 0,
        display: 'ALWAYS'
      }
      customCallout: { anchorY: -5, anchorX: 0, display: 'ALWAYS' }
    };
  };
  const createCarMarker = (): MarkerItem | null => {
  // 构建车辆 marker
  const createCarMarker = () => {
    if (!deps.vehicleLocation) return null;
    return {
      id: deps.vehicleMarkerId,
@@ -220,22 +147,16 @@
      zIndex: 2,
      rotate: typeof deps.vehicleDirection === 'number' ? deps.vehicleDirection : 0,
      name: '车辆位置',
      markerType: MarkerType.CAR,
      customCallout: { anchorY: CAR_CALLOUT_ANCHOR, anchorX: 0, display: 'ALWAYS' }
      customCallout: { anchorY: 70, anchorX: 0, display: 'ALWAYS' }
    };
  };
  const applyMarkerState = (
    nextMarkers: MarkerItem[],
    nextMarkersSignature: string,
    shouldSkipUpdate: boolean,
    carMarker: MarkerItem | null
  ) => {
  // 应用 marker 状态并处理“行驶中”阶段的刷新标记
  const applyMarkerState = (nextMarkers: any[], nextMarkersSignature: string, shouldSkipUpdate: boolean, carMarker: any) => {
    if (shouldSkipUpdate) {
      setMarkers((prevMarkers: MarkerItem[]) => {
        const targetCarIndex = prevMarkers.findIndex(
          (item: MarkerItem) => item.id === deps.vehicleMarkerId
        );
      // 跳过整包 markers 更新时,仍兜底同步车辆 marker 位置与角度,避免朝向卡旧值
      setMarkers((prevMarkers: any[]) => {
        const targetCarIndex = prevMarkers.findIndex((item: any) => item.id === deps.vehicleMarkerId);
        if (targetCarIndex === -1) {
          if (markersSignatureRef.current !== nextMarkersSignature) {
            markersSignatureRef.current = nextMarkersSignature;
@@ -244,8 +165,7 @@
          return prevMarkers;
        }
        const prevCar = prevMarkers[targetCarIndex] || {};
        const nextCarRotate =
          typeof carMarker?.rotate === 'number' ? carMarker.rotate : prevCar.rotate;
        const nextCarRotate = typeof carMarker?.rotate === 'number' ? carMarker.rotate : prevCar.rotate;
        const carLocationChanged =
          Number(prevCar.latitude) !== Number(carMarker?.latitude) ||
          Number(prevCar.longitude) !== Number(carMarker?.longitude);
@@ -267,13 +187,7 @@
      markersSignatureRef.current = nextMarkersSignature;
      setMarkers(nextMarkers);
    }
    if (
      !deps.isAndroid &&
      [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) &&
      deps.statusRule.showCar &&
      !!carMarker &&
      !tripStatus6MarkerRefreshedRef.current
    ) {
    if (!deps.isAndroid && [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) && deps.statusRule.showCar && !!carMarker && !tripStatus6MarkerRefreshedRef.current) {
      tripStatus6MarkerRefreshedRef.current = true;
    }
    carMarkerInitialized.current = deps.statusRule.showCar && !!carMarker;
@@ -284,86 +198,35 @@
  }, [markers]);
  useEffect(() => {
    const pickupSite =
      deps.startSite?.latitude && deps.startSite?.longitude
        ? deps.startSite
        : Taro.getStorageSync('startSite');
    const dropoffSite =
      deps.endSite?.latitude && deps.endSite?.longitude
        ? deps.endSite
        : Taro.getStorageSync('endSite');
    const pickupIconMarker = createPickupIconMarker(pickupSite);
    const pickupTextMarker = createPickupTextMarker(pickupSite);
    const dropoffIconMarker = createDropoffIconMarker(dropoffSite);
    const dropoffTextMarker = createDropoffTextMarker(dropoffSite);
    const pickupSite = deps.startSite?.latitude && deps.startSite?.longitude ? deps.startSite : Taro.getStorageSync('startSite');
    const dropoffSite = deps.endSite?.latitude && deps.endSite?.longitude ? deps.endSite : Taro.getStorageSync('endSite');
    const useAndroidNativeTrip1Callout = deps.isAndroid && ([TripStatus.PublishRoute, TripStatus.VehicleArrivedPickup, TripStatus.TripFinished, TripStatus.PaymentSuccess, TripStatus.CancelOnlyPickup].includes(deps.tripStatus) || deps.showTrip1StyleEndpoint);
    const pickupMarker = createPickupMarker(pickupSite, useAndroidNativeTrip1Callout);
    const dropoffMarker = createDropoffMarker(dropoffSite, useAndroidNativeTrip1Callout);
    const beforeUserDropoffMarker = createBeforeUserDropoffMarker();
    const shouldWaitArrivedDirection =
      deps.tripStatus === TripStatus.ArrivedDestination && !deps.arrivedMarkerReady;
    const shouldWaitArrivedDirection = deps.tripStatus === TripStatus.ArrivedDestination && !deps.arrivedMarkerReady;
    const effectiveShowCar = deps.statusRule.showCar && !shouldWaitArrivedDirection;
    const carMarker = createCarMarker();
    const markerMap: Partial<Record<MarkerType, MarkerItem>> = {
      [MarkerType.PICKUP_ICON]: deps.statusRule.showPickup ? pickupIconMarker ?? undefined : undefined,
      [MarkerType.PICKUP_TEXT]: deps.statusRule.showPickup ? pickupTextMarker ?? undefined : undefined,
      [MarkerType.DROPOFF_ICON]: deps.statusRule.showDropoff ? dropoffIconMarker ?? undefined : undefined,
      [MarkerType.DROPOFF_TEXT]: deps.statusRule.showDropoff ? dropoffTextMarker ?? undefined : undefined,
      [MarkerType.CAR]: effectiveShowCar ? carMarker ?? undefined : undefined,
      [MarkerType.BEFORE_USER_DROPOFF]: beforeUserDropoffMarker ?? undefined
    };
    const incomingMarkerOrder = Array.isArray(deps.statusRule.markerOrder)
      ? deps.statusRule.markerOrder
      : [];
    const mergedMarkerOrder = [
      ...incomingMarkerOrder,
      ...DEFAULT_MARKER_ORDER.filter((item) => !incomingMarkerOrder.includes(item))
    ];
    const nextMarkers: MarkerItem[] = mergedMarkerOrder
      .map((type) => markerMap[type])
      .filter((item): item is MarkerItem => Boolean(item));
    const nextMarkers: any[] = [];
    if (deps.statusRule.showPickup && pickupMarker) nextMarkers.push(pickupMarker);
    if (effectiveShowCar && carMarker) nextMarkers.push(carMarker);
    if (deps.statusRule.showDropoff && dropoffMarker) nextMarkers.push(dropoffMarker);
    if (beforeUserDropoffMarker) nextMarkers.push(beforeUserDropoffMarker);
    const nextMarkersSignature = nextMarkers
      .map((m: MarkerItem) => [
      .map((m: any) => [
        m.id,
        m.markerType,
        Number(m.latitude).toFixed(6),
        Number(m.longitude).toFixed(6),
        m.name || '',
        typeof m.rotate === 'number' ? m.rotate : '',
        m?.customCallout?.anchorY ?? ''
        m?.customCallout?.anchorY ?? '',
      ].join('|'))
      .join(';');
    const hasDropoffMarker = markersRef.current.some(
      (m: MarkerItem) =>
        m.markerType === MarkerType.DROPOFF_ICON ||
        m.markerType === MarkerType.DROPOFF_TEXT
    );
    const shouldHoldMarkerByTranslate =
      [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) &&
      carMarkerInitialized.current &&
      deps.statusRule.showCar &&
      (!deps.statusRule.showDropoff || hasDropoffMarker);
    const shouldSkipUpdate =
      shouldHoldMarkerByTranslate &&
      (deps.isAndroid || tripStatus6MarkerRefreshedRef.current);
    const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2);
    const shouldHoldMarkerByTranslate = [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
    const shouldSkipUpdate = shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current);
    applyMarkerState(nextMarkers, nextMarkersSignature, shouldSkipUpdate, carMarker);
  }, [
    deps.tripStatus,
    deps.startSite?.name,
    deps.startSite?.latitude,
    deps.startSite?.longitude,
    deps.endSite?.name,
    deps.endSite?.latitude,
    deps.endSite?.longitude,
    deps.beforeUserEndSite?.latitude,
    deps.beforeUserEndSite?.longitude,
    deps.vehicleLocation,
    deps.vehicleDirection,
    deps.vehicleMarkerId,
    deps.isAndroid,
    deps.statusRule.showPickup,
    deps.statusRule.showCar,
    deps.statusRule.showDropoff,
    deps.statusRule.markerOrder,
    deps.arrivedMarkerReady
  ]);
  }, [deps.tripStatus, deps.startSite?.name, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.name, deps.endSite?.latitude, deps.endSite?.longitude, deps.beforeUserEndSite?.latitude, deps.beforeUserEndSite?.longitude, deps.vehicleLocation, deps.vehicleDirection, deps.vehicleMarkerId, deps.isAndroid, deps.statusRule.showPickup, deps.statusRule.showCar, deps.statusRule.showDropoff]);
  return {
    markers,
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
@@ -1,12 +1,10 @@
import Taro from '@tarojs/taro';
import wsManager from '../../../../utils/websocket';
import { mapOrderStatusToTripStatus } from '../../../../utils/Robotaxi-public-method';
import { getOrderActive, } from '../../../api/home';
import { queryVehicleByOrderId } from '../../../api/begin-route';
import { toTripStatus, TripStatus } from '../constants/trip-status';
import { MODIFY_DESTINATION_SESSION_KEY } from '../constants/modify-destination';
import {
  normalizeActiveOrderSnapshot
} from './runtime-sync';
type Deps = {
  wsHost: string;
@@ -64,27 +62,30 @@
    getOrderActive()
      .then((res: any) => {
        if (!res) return;
        const currentOrderId = String(Taro.getStorageSync('orderId') || '');
        const orderSnapshot = normalizeActiveOrderSnapshot(res, currentOrderId);
        if (!orderSnapshot) return;
        const nextStatus = toTripStatus(orderSnapshot.tripStatus, deps.getCurrentTripStatus());
        deps.setTrunkStatus(![0, 2].includes(res.openTrunk));
        const nextStartSite = {
          id: res.startPointId || '',
          name: res.startPointName || '',
          latitude: res.startLat || 0,
          longitude: res.startLon || 0,
          electronicAreaId: res.xpElectronicFenceAreaId || '',
          time: res.startParkingTimeLimit || 0,
        };
        const nextEndSite = {
          id: res.endPointId || '',
          name: res.endPointName || '',
          latitude: res.endLat || 0,
          longitude: res.endLon || 0,
          electronicAreaId: res.xpElectronicFenceAreaId || '',
          time: res.endParkingTimeLimit || 0,
        };
        const nextStatus = toTripStatus(mapOrderStatusToTripStatus(res.status || 1));
        Taro.setStorageSync('tripStatus', nextStatus);
        deps.setTripStatus(nextStatus);
        if (orderSnapshot.orderId) {
          deps.setOrderId(orderSnapshot.orderId);
          Taro.setStorageSync('orderId', orderSnapshot.orderId);
        }
        if (typeof orderSnapshot.trunkStatus === 'boolean') {
          deps.setTrunkStatus(orderSnapshot.trunkStatus);
        }
        if (orderSnapshot.startSite) {
          Taro.setStorageSync('startSite', orderSnapshot.startSite);
          deps.setStartSite(orderSnapshot.startSite);
        }
        if (orderSnapshot.endSite) {
          Taro.setStorageSync('endSite', orderSnapshot.endSite);
          deps.setEndSite(orderSnapshot.endSite);
        }
        Taro.setStorageSync('startSite', nextStartSite);
        Taro.setStorageSync('endSite', nextEndSite);
        deps.setStartSite(nextStartSite);
        deps.setEndSite(nextEndSite);
      })
      .catch(() => { });
  };
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
@@ -156,12 +156,6 @@
    orderRouteInitializedRef.current = false;
  };
  // 失效路线渲染签名,确保下一次路线落地时强制刷新 polyline
  const resetRoutePolylineCache = () => {
    polylineKeyRef.current = '';
    lastPolylineUpdateAtRef.current = 0;
  };
  // 擦除已行驶路段(仅基于前后两次路线点差分,不依赖后端 served)
  const erasePassedRouteSegments = (newPoints: any[], _servedPoints: any[] = []) => {
    const oldPoints = currentFullRouteRef.current;
@@ -778,7 +772,6 @@
    applyRoutePoints,
    reducePoints,
    resetRouteRuntimeState,
    resetRoutePolylineCache,
    erasePassedRouteSegments,
    requestRouteAndFare,
    requestOrderRoute,
src/robotaxi/pages/begin-route/hooks/use-smooth-route-motion.ts
File was deleted
src/robotaxi/pages/begin-route/hooks/use-socket-fallback-polling.ts
File was deleted
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts
@@ -6,10 +6,6 @@
  vehicleMarkerId: number;
};
enum VehicleTranslateConfig {
  DURATION_MS = 100,
}
export default function useVehicleTracking({
  statusRule,
  vehicleMarkerId
@@ -26,6 +22,7 @@
  const [trunkStatus, setTrunkStatus] = useState(false);
  const lastVehiclePointRef = useRef<{ latitude: number; longitude: number } | null>(null);
  const lastVehicleUpdateAtRef = useRef<number>(0);
  const TRANSLATE_DURATION_MS = 100;
  const isTranslatingRef = useRef(false); // 当前是否有进行中的平移动画
  const pendingTranslateRef = useRef<{
    latitude: number;
@@ -51,7 +48,7 @@
      autoRotate: false,
      moveWithRotate: true,
      rotate: task.rotate,
      duration: VehicleTranslateConfig.DURATION_MS,
      duration: TRANSLATE_DURATION_MS,
      complete: () => {
        isTranslatingRef.current = false;
        if (pendingTranslateRef.current) {
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -5,92 +5,160 @@
import CallingCar from '../components/calling-car';
import ModifyDestinationReview from '../components/modify-destination-review';
import { TripStatus } from '../constants/trip-status';
import { MarkerType } from './use-map-markers';
import { formatRemainingDurationDisplay } from '../../../utils/format';
import summaryIcon from '../../../assets/summary-icon.png';
type Deps = {
  /** 是否安卓 */
  isAndroid: boolean;
  /** 行程状态 */
  tripStatus: TripStatus;
  /** 地图缩放等级 */
  mapScale: number;
  /** 地图 markers */
  markers: any[];
  /** 是否显示等待雷达 */
  shouldShowWaitingRadar: boolean;
  /** 等待雷达可见 */
  waitingRadarVisible: boolean;
  /** 等待雷达样式 */
  waitingRadarStyle: any;
  /** 等待雷达资源 */
  waitingRadarSrc: string;
  customCalloutIconSrc: string;
  /** 车辆气泡可见 */
  carCalloutVisible: boolean;
  /** 车辆气泡样式 */
  carCalloutStyle: any;
  /** 到达气泡可见 */
  arriveCalloutVisible: boolean;
  /** 到达气泡样式 */
  arriveCalloutStyle: any;
  /** 到达态气泡渲染就绪(角度与缩放均就绪) */
  arrivedTooltipReady: boolean;
  /** 路线距离 */
  routeDistance: { value: number; unit: 'km' | 'm' } | null;
  /** 路线时长 */
  routeDuration: number | null;
  /** 是否存在上一单终点 */
  hasBeforeUserEndSite: boolean;
  /** 自定义气泡图标 */
  customCalloutIconSrc: string;
  /** 是否使用下单前端点气泡样式 */
  showTrip1StyleEndpoint: boolean;
  /** 上车点 */
  startSite: any;
  /** 下车点 */
  endSite: any;
  /** 发单 */
  callImmediately: () => void;
  /** 费用信息 */
  fareEstimate: any;
  /** 打开优惠券选择弹窗 */
  handleOpenCouponSelect: () => void;
  /** 打开乘客须知 */
  handleOpenPassengerNotice: () => void;
  /** 步行时长 */
  walkingTime: number;
  /** 发单禁用 */
  isCallDisabled: boolean;
  /** 打开取消弹窗 */
  handleOpenCancelPopupByTripStatus: (status: TripStatus, durationDisplay?: any) => void;
  /** 取消超时 */
  handleOpenCancelPopupWithType: (type: number) => void;
  /** 行程状态设置 */
  setTripStatus: (value: TripStatus) => void;
  /** 完成倒计时启用 */
  finishCountdownEnabled: boolean;
  /** 订单 ID */
  orderId: string;
  /** 车辆信息 */
  vehicleInfo: any;
  /** 完成倒计时超时 */
  handleFinishCountdownTimeout: () => void;
  /** 重置版本 */
  resetDetailVersion: number;
  /** 价格版本 */
  priceUpdateVersion: number;
  /** 评价提交版本 */
  evaluateSubmitVersion: number;
  /** 评价关闭版本 */
  evaluateCloseVersion: number;
  /** 打开结束弹窗 */
  handleOpenOverRoutePopup: () => void;
  /** 支付 */
  handlePay: () => void;
  /** 打开评价 */
  handleOpenEvaluatePopup: () => void;
  /** 详情展开回调 */
  handleToggleDetailFade: (expanded: boolean, hasFare: boolean) => void;
  /** 修改目的地 */
  handleModifyDestination: () => void;
  /** 分享行程 */
  handleShareTrip: () => void;
  /** 路线选择展开状态 */
  isRouteSelectExpanded: boolean;
  /** 路线选择状态切换 */
  onToggleRouteSelect: (expanded: boolean) => void;
  /** 后备箱状态 */
  trunkStatus: boolean;
  /** 路线选项集合 */
  routeOptions: any[];
  /** 选中路线索引 */
  selectedRouteIndex: number;
  /** 切换路线回调 */
  handleSelectRoute: (index: number) => void;
  /** 地图点击 */
  handleMapTap?: (e: any) => void;
  /** 修改目的地确认态 */
  isModifyDestinationReviewVisible?: boolean;
  /** 修改目的地确认请求中 */
  isConfirmingModifyDestination?: boolean;
  /** 修改前目的地 */
  previousEndSite?: any;
  /** 修改前费用 */
  previousFareEstimate?: any;
  /** 修改后目的地 */
  previewEndSite?: any;
  /** 确认修改目的地 */
  handleConfirmModifyDestination?: (selectedRoute?: any) => void;
  /** 蓝牙卡片描述文案 */
  bluetoothStatusReady: boolean;
  /** 蓝牙卡片描述文案 */
  bluetoothCardDescription: string;
  /** 蓝牙卡片右侧动作文案 */
  bluetoothCardActionText: string;
  /** 点击蓝牙卡片 */
  handleBluetoothCardClick: () => void;
  /** 蓝牙授权弹窗是否展示 */
  showAuthBluetoothPopup: boolean;
  /** 蓝牙授权成功弹窗是否展示 */
  showSuccessAuthPopup: boolean;
  /** 关闭蓝牙授权弹窗 */
  handleCloseAuthBluetoothPopup: () => void;
  /** 关闭蓝牙授权成功弹窗 */
  handleCloseSuccessAuthPopup: () => void;
  /** 去授权蓝牙 */
  handleAuthorizeBluetooth: () => void;
  /** 页面 didShow 版本号 */
  pageDidShowVersion: number;
  /** 排队位次轮询频率(秒) */
  queuePollingTimeSeconds: number;
};
/**
 * 页面展示委托:地图覆盖物与行程卡片渲染。
 */
export default function useViewDelegates(deps: Deps) {
  const durationDisplay = formatRemainingDurationDisplay(deps.routeDuration);
  /** 时长文案 */
    const durationDisplay = formatRemainingDurationDisplay(deps.routeDuration);
  const routeSummaryText = deps.routeDistance && deps.routeDuration !== null
    ? `${durationDisplay.value}${durationDisplay.unit} ${deps.routeDistance.value}${deps.routeDistance.unit === 'm' ? '米' : '公里'}`
    : '';
  const shouldShowSiteTextBelow = [
    TripStatus.VehicleApproaching,
    TripStatus.VehicleArrivedPickup,
    TripStatus.InService
  ].includes(deps.tripStatus);
  const shouldShowSiteArrow = deps.tripStatus === TripStatus.PublishRoute || deps.showTrip1StyleEndpoint;
  const shouldHideAndroidMarkerCallout = deps.isAndroid && [TripStatus.PublishRoute, TripStatus.VehicleArrivedPickup, TripStatus.TripFinished, TripStatus.PaymentSuccess, TripStatus.CancelOnlyPickup].includes(deps.tripStatus); // Android 指定状态下隐藏常规 marker 气泡
  /**
   * 渲染等待态气泡。
   */
  const renderWaitingCallout = (item: any, calloutKey: string) => (
    <CoverView slot='callout' marker-id={item.id} key={calloutKey} className='waiting-callout'>
      <CoverView className='waiting-callout-content'>
@@ -103,100 +171,73 @@
    </CoverView>
  );
  const renderRouteSummaryCallout = (item: any, calloutKey: string) => (
  /**
   * 渲染默认气泡。
   */
  const renderDefaultCallout = (item: any, calloutKey: string) => (
    <CoverView slot='callout' marker-id={item.id} key={calloutKey} className='custom-callout'>
      {routeSummaryText ? (
        <CoverView className='route-summary-callout'>
          <CoverImage className='route-summary-callout-bg' src={summaryIcon} />
          <CoverView className='route-summary-callout-text'>{routeSummaryText}</CoverView>
        </CoverView>
      ) : null}
    </CoverView>
  );
  const renderSiteTextCallout = (item: any, calloutKey: string) => {
    if (
      item.markerType === MarkerType.DROPOFF_TEXT
      && deps.tripStatus === TripStatus.ArrivedDestination
      && !deps.arrivedTooltipReady
    ) {
      return null;
    }
    return (
      <CoverView
        slot='callout'
        marker-id={item.id}
        key={calloutKey}
        className={`custom-callout site-text-callout ${shouldShowSiteTextBelow ? 'site-text-callout-bottom' : 'site-text-callout-top'}`}
      >
        {shouldShowSiteTextBelow && <CoverView className='site-text-bubble-arrow site-text-bubble-arrow-top' />}
        <CoverView className='site-text-bubble'>
          <CoverView className='site-text-bubble-content'>
            <CoverView className='site-text-label'>{item.name}</CoverView>
            {shouldShowSiteArrow && <CoverImage className='site-text-action-icon' src={deps.customCalloutIconSrc} />}
      {item.isRouteSummaryBubble ? (
        routeSummaryText ? (
          <CoverView className='route-summary-callout'>
            <CoverImage className='route-summary-callout-bg' src={summaryIcon} />
            <CoverView className='route-summary-callout-text'>
              {routeSummaryText}
            </CoverView>
          </CoverView>
        </View>
        {!shouldShowSiteTextBelow && <CoverView className='site-text-bubble-arrow site-text-bubble-arrow-bottom' />}
      </CoverView>
    );
  };
  const renderBeforeUserCallout = (item: any, calloutKey: string) => (
    <CoverView slot='callout' marker-id={item.id} key={calloutKey} className='custom-callout'>
      <CoverView className='before-user-site-callout'>
        <CoverView className='before-user-site-txt'>上一站终点</CoverView>
      </CoverView>
    </CoverView>
  );
  const renderCarCallout = (item: any, calloutKey: string) => (
    <CoverView slot='callout' marker-id={item.id} key={calloutKey} className='custom-callout'>
      <CoverView className='custom-callout-content car-callout-with-before' style={{ padding: '6px 10px', minWidth: '40px', justifyContent: 'center', zIndex: 2 }}>
        {deps.hasBeforeUserEndSite && (
          <CoverView className='car-time car-before-order-tip'>即将完成上一单</CoverView>
        )}
        <CoverView className='car-time'>
          <CoverView className='car-time-num'>{deps.routeDistance?.value ?? 0}</CoverView>
          <CoverView>{deps.routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView>
          <CoverView style={{ marginLeft: '2.5px' }} className='car-time-num'>{durationDisplay.value}</CoverView>
          <CoverView>{durationDisplay.unit}</CoverView>
        ) : null
      ) : item.isBeforeUserSite ? (
        <CoverView className='before-user-site-callout'>
          <CoverView className='before-user-site-txt'>上一站终点</CoverView>
        </CoverView>
      </CoverView>
      ) : (
        <>
          {(item.name !== '车辆位置' && !(item.id === 2 && [TripStatus.ArrivedDestination].includes(deps.tripStatus)) && !item.hideNameCallout) && (
            <CoverView className={`custom-callout-content ${deps.isAndroid && 'txt-center '}`} style={{ zIndex: 1 }}>
              <CoverView style={{ lineHeight: '22px', display: 'flex', alignItems: 'center' }}>{item.name}</CoverView>
              {deps.isAndroid && <CoverView style={{ width: '2px' }}></CoverView>}
              {(deps.tripStatus === TripStatus.PublishRoute || deps.showTrip1StyleEndpoint) && <CoverImage className='custom-callout-icon' src={deps.customCalloutIconSrc} />}
            </CoverView>
          )}
          {(item.name === '车辆位置' && [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) && !deps.isAndroid) && (
            <CoverView className='custom-callout-content car-callout-with-before' style={{ padding: '6px 10px', minWidth: '40px', justifyContent: 'center', zIndex: 2 }}>
              {deps.hasBeforeUserEndSite && (
                <CoverView className='car-time car-before-order-tip'>即将完成上一单</CoverView>
              )}
              <CoverView className='car-time'>
                <CoverView className='car-time-num'>{deps.routeDistance?.value ?? 0}</CoverView>
                <CoverView>{deps.routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView>
                <CoverView style={{ marginLeft: '2.5px' }} className='car-time-num'>{durationDisplay.value}</CoverView>
                <CoverView>{durationDisplay.unit}</CoverView>
              </CoverView>
            </CoverView>
          )}
          {(item.id === 2 && [TripStatus.ArrivedDestination].includes(deps.tripStatus) && !deps.isAndroid && deps.arrivedTooltipReady) && (
            <CoverView className='custom-callout-content'>
              已到达
            </CoverView>
          )}
        </>
      )}
    </CoverView>
  );
  const renderMarkerCallout = (item: any) => {
  /**
   * 渲染单个 marker 气泡。
   */
  const renderMarkerCallout = (item: any, index: number) => {
    const calloutKey = `callout-${item.id}-${item.name || ''}`;
    if (item.isRouteSummaryBubble) {
      return renderRouteSummaryCallout(item, calloutKey);
    }
    if (item.markerType === MarkerType.BEFORE_USER_DROPOFF || item.isBeforeUserSite) {
      return renderBeforeUserCallout(item, calloutKey);
    }
    const isSiteIconMarker = [MarkerType.PICKUP_ICON, MarkerType.DROPOFF_ICON].includes(item.markerType);
    const isSiteTextMarker = [MarkerType.PICKUP_TEXT, MarkerType.DROPOFF_TEXT].includes(item.markerType);
    const isWaitingPickupText =
      [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus)
      && item.markerType === MarkerType.PICKUP_TEXT;
    if (isSiteIconMarker) return null;
    if (isWaitingPickupText) return renderWaitingCallout(item, calloutKey);
    if (
      item.markerType === MarkerType.CAR
      && deps.isAndroid
      && [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus)
    ) {
      return null;
    }
    if (isSiteTextMarker) return renderSiteTextCallout(item, calloutKey);
    if (
      item.markerType === MarkerType.CAR
      && [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus)
    ) {
      return renderCarCallout(item, calloutKey);
    }
    return null;
    // 路线摘要气泡需跨平台强制显示,避免被 Android 普通气泡隐藏规则误伤
    if (item.isRouteSummaryBubble) return renderDefaultCallout(item, calloutKey);
    if (shouldHideAndroidMarkerCallout && !item.isBeforeUserSite) return null;
    const isWaiting = [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus) && index === 0;
    if (isWaiting) return renderWaitingCallout(item, calloutKey);
    if (item.name === '车辆位置' && deps.isAndroid && [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus)) return null;
    return renderDefaultCallout(item, calloutKey);
  };
  /**
   * 渲染地图覆盖层。
   */
  const renderMapOverlays = () => (
    <>
      {deps.markers.map(renderMarkerCallout)}
@@ -218,9 +259,19 @@
          </View>
        </View>
      )}
      {deps.isAndroid && deps.arriveCalloutVisible && deps.arrivedTooltipReady && (
        <View className="car-callout-android" style={deps.arriveCalloutStyle}>
          <View className="custom-callout-content">
            已到达
          </View>
        </View>
      )}
    </>
  );
  /**
   * 渲染底部行程卡片。
   */
  const renderTripCard = () => {
    if (deps.tripStatus === TripStatus.PublishRoute && deps.startSite && deps.endSite) {
      return (
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -1,345 +1,492 @@
import { useEffect, useRef } from 'react';
import type { MutableRefObject } from 'react';
import Taro from '@tarojs/taro';
import wsManager from '../../../../utils/websocket';
import { queryVehicleByOrderId } from '../../../api/begin-route';
import { clearOrderTripStorage } from '../../../../utils/Robotaxi-public-method';
import { TripStatus, toTripStatus } from '../constants/trip-status';
import {
  applyOrderSnapshot,
  normalizeSocketOrderStatusSnapshot,
  normalizeSocketRouteSnapshot
} from './runtime-sync';
import { clearOrderTripStorage, safeParse } from '../../../../utils/Robotaxi-public-method';
import { normalizeDistanceKm, normalizeDurationSeconds } from '../../../../robotaxi/utils/format';
import { toTripStatus, TripStatus } from '../constants/trip-status';
type UseWebSocketEventsDeps = {
  tripStatusRef: MutableRefObject<TripStatus>;
  orderId: string;
  setOrderId: (orderId: string) => void;
  setTripStatus: (status: TripStatus) => void;
  setVehicleInfo: (vehicle: any) => void;
  setPriceUpdateVersion: (updater: (v: number) => number) => void;
  setFinishCountdownEnabled: (value: boolean) => void;
  setShowAnnouncementsPopup: (value: boolean) => void;
  setRoutePoints?: (points: any[]) => void;
  setPolyline?: (lines: any[]) => void;
  setCancelType: (value: number) => void;
  setShowCancelPopup: (value: boolean) => void;
  checkAndShowPassengerNotice: () => void;
  setVehicleMoveAlongPoints?: (points: any[]) => void;
  setBeforeUserEndSite?: (site: { longitude: number; latitude: number } | null) => void;
  setStartSite?: (value: any) => void;
  setEndSite?: (value: any) => void;
  setTrunkStatus?: (value: boolean) => void;
  applySmoothRouteSnapshot?: (payload: any) => void;
  resetSmoothRouteMotion?: () => void;
  polylineKeyRef?: MutableRefObject<string>;
  lastPolylineUpdateAtRef?: MutableRefObject<number>;
  lastRoutePointRef?: MutableRefObject<any>;
  lastRouteUpdateAtRef?: MutableRefObject<number>;
  lastVehiclePointRef?: MutableRefObject<any>;
  lastVehicleUpdateAtRef?: MutableRefObject<number>;
  currentFullRouteRef?: MutableRefObject<any[]>;
  lastErasedIndexRef?: MutableRefObject<number>;
  orderRouteInitializedRef?: MutableRefObject<boolean>;
    tripStatusRef: React.MutableRefObject<TripStatus>;
    orderId: string;
    setOrderId: (orderId: string) => void;
    setTripStatus: (status: TripStatus) => 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;
    setBeforeUserEndSite: (site: { longitude: number; latitude: number } | null) => void;
    erasePassedRouteSegments: (points: any[], servedPoints?: any[]) => void;
    applyRoutePoints: (points: any[]) => boolean;
    updateVehicleLocationThrottled: (lat: number, lng: number, force?: boolean) => void;
    onOrderUpdateLineRendered?: (points: any[]) => void;
};
const resolveOrderId = (payload: any, fallbackOrderId: string) =>
    payload?.content?.orderId || payload?.orderId || fallbackOrderId || Taro.getStorageSync('orderId');
// 解析“上一单乘客下车点”坐标,任一坐标无效则返回空,供 marker 显隐控制。
const parseBeforeUserEndSite = (payload: any) => {
    const longitude = Number(payload?.content?.endLongitude);
    const latitude = Number(payload?.content?.endlatitude);
    if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
        return null;
    }
    return { longitude, latitude };
};
const isSameOrder = (incomingOrderId: string, currentOrderId: string) => {
  if (!incomingOrderId && !currentOrderId) return true;
  if (!incomingOrderId || !currentOrderId) return false;
  return String(incomingOrderId) === String(currentOrderId);
};
const parseBeforeUserEndSite = (payload: any) => {
  const longitude = Number(payload?.content?.endLongitude);
  const latitude = Number(payload?.content?.endlatitude);
  if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
    return null;
  }
  return { longitude, latitude };
    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.resetSmoothRouteMotion?.();
  if (deps.polylineKeyRef) deps.polylineKeyRef.current = '';
  if (deps.lastPolylineUpdateAtRef) deps.lastPolylineUpdateAtRef.current = 0;
  if (deps.lastRoutePointRef) deps.lastRoutePointRef.current = null;
  if (deps.lastRouteUpdateAtRef) deps.lastRouteUpdateAtRef.current = 0;
  if (deps.lastVehiclePointRef) deps.lastVehiclePointRef.current = null;
  if (deps.lastVehicleUpdateAtRef) deps.lastVehicleUpdateAtRef.current = 0;
  if (deps.currentFullRouteRef) deps.currentFullRouteRef.current = [];
  if (deps.lastErasedIndexRef) deps.lastErasedIndexRef.current = 0;
  if (deps.orderRouteInitializedRef) deps.orderRouteInitializedRef.current = false;
    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 incomingTripStatus = Number(payload?.tripStatus);
    if (!Number.isFinite(incomingTripStatus)) {
        return null;
    }
    const currentTripStatus = deps.tripStatusRef.current;
    const mappedTripStatus = toTripStatus(incomingTripStatus, 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: MutableRefObject<string | null>
    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(() => { });
    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 = (
  showCancelPopup: boolean,
  changeStatusOld: TripStatus,
  deps: UseWebSocketEventsDeps
) => {
  if (!showCancelPopup) return;
  if ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(changeStatusOld)) {
    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 handleCancelPopupPush = (payload: any, changeStatusOld: TripStatus, deps: UseWebSocketEventsDeps) => {
    if (!payload?.showCancelPopup) return;
    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 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: MutableRefObject<string | null>
    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;
  }
  deps.setBeforeUserEndSite?.(parseBeforeUserEndSite(payload));
    const changeStatusOld = deps.tripStatusRef.current;
    const currentOrderId = deps.orderId || Taro.getStorageSync('orderId');
    const incomingOrderId = payload?.content?.orderId || payload?.orderId;
    if (!isSameOrder(incomingOrderId, currentOrderId)) {
        return;
    }
    deps.setBeforeUserEndSite(parseBeforeUserEndSite(payload));
  const orderSnapshot = normalizeSocketOrderStatusSnapshot(payload, currentOrderId);
  const { nextOrderId, nextTripStatus } = applyOrderSnapshot(orderSnapshot, {
    orderId: String(deps.orderId || ''),
    tripStatusRef: deps.tripStatusRef as MutableRefObject<number>,
    setOrderId: deps.setOrderId,
    setTripStatus: (value: number) => deps.setTripStatus(toTripStatus(value, deps.tripStatusRef.current)),
    setStartSite: deps.setStartSite || (() => { }),
    setEndSite: deps.setEndSite || (() => { }),
    setTrunkStatus: deps.setTrunkStatus || (() => { })
  }, {
    protectCriticalTripStatus: true
  });
  const resolvedTripStatus = toTripStatus(nextTripStatus, changeStatusOld);
  syncVehicleInfo(nextOrderId, deps, lastFetchedVehicleOrderIdRef);
  handleStatusSideEffects(resolvedTripStatus, changeStatusOld, deps);
  handleCancelPopupPush(orderSnapshot.showCancelPopup === true, changeStatusOld, deps);
  if (orderSnapshot.showPassengerNotice) {
    deps.checkAndShowPassengerNotice();
  }
    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 = deps.tripStatusRef.current;
    if (currentTripStatus === TripStatus.ArrivedDestination) {
        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 (![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.ArrivedDestination].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);
        }
        if (typeof deps.onOrderUpdateLineRendered === 'function') {
            // 等路线点与折线提交渲染后再触发视口重算,避免 includePoints 早于渲染导致跳动。
            Taro.nextTick(() => {
                deps.onOrderUpdateLineRendered?.(points);
            });
        }
    });
};
const handleOrderRoutePayload = (payload: any, deps: UseWebSocketEventsDeps) => {
  deps.setBeforeUserEndSite?.(parseBeforeUserEndSite(payload));
  const routeSnapshot = normalizeSocketRouteSnapshot(payload);
  deps.applySmoothRouteSnapshot?.(routeSnapshot);
    deps.setBeforeUserEndSite(parseBeforeUserEndSite(payload));
    const points = payload?.points || [];
    const servedPoints = Array.isArray(payload?.servedPoints) ? payload.servedPoints : [];
    const forceFullRedraw = payload?.forceFullRedraw === true;
    const currentTripStatus = deps.tripStatusRef.current;
    updateRouteSummary(payload, deps);
    // 行程结束态仅更新概要信息,不再消费路线点,避免视口反复重算造成跳动
    if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(currentTripStatus)) {
        return;
    }
    if ([TripStatus.VehicleApproaching, TripStatus.InService].includes(currentTripStatus)) {
        deps.setVehicleMoveAlongPoints(servedPoints);
    }
    if (forceFullRedraw) {
        handleForceFullRouteRedraw(points, deps, servedPoints);
        return;
    }
    const isEraseStatus = ![TripStatus.ArrivedDestination, TripStatus.TripFinished, TripStatus.PaymentSuccess].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 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 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);
    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);
  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;
    }
  };
    // 仅初始化一次事件处理器与解绑逻辑
    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();
        }
  useEffect(() => {
    authSuccessHandlerRef.current = () => {
      if (!messageBound.current && messageHandlerRef.current) {
        wsManager.on('message', messageHandlerRef.current);
        messageBound.current = true;
      }
        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;
        }
    };
    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) {
    // 确保连接可用并完成监听绑定
    const ensureSocketListeners = (host: string) => {
        if (wsManager.isConnectedStatus()) {
            bindSocketListeners();
            return;
          }
          currentDeps.setVehicleInfo(res);
        })
        .catch(() => { });
        }
        if (socketOpenHandlerRef.current) {
            wsManager.connect(host);
            return;
        }
        const handleOpen = () => {
            bindSocketListeners();
        };
        socketOpenHandlerRef.current = handleOpen;
        wsManager.on('open', handleOpen);
        wsManager.connect(host);
    };
    orderStatusHandlerRef.current = (payload) => {
      handleOrderStatusPayload(payload, depsRef.current, lastFetchedVehicleOrderIdRef);
    };
    orderRouteHandlerRef.current = (payload) => {
      handleOrderRoutePayload(payload, depsRef.current);
    };
    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 ensureSocketListeners = (host: string) => {
    if (wsManager.isConnectedStatus()) {
      bindSocketListeners();
      return;
    }
    if (socketOpenHandlerRef.current) {
      wsManager.connect(host);
      return;
    }
    const handleOpen = () => {
      bindSocketListeners();
    return {
        ensureSocketListeners,
        bindSocketListeners
    };
    socketOpenHandlerRef.current = handleOpen;
    wsManager.on('open', handleOpen);
    wsManager.connect(host);
  };
  return {
    ensureSocketListeners,
    bindSocketListeners
  };
}
src/robotaxi/pages/begin-route/index.less
@@ -96,75 +96,6 @@
    flex-direction: column;
}
.site-text-callout {
    transform: translateY(48px);
}
.site-text-callout {
    position: relative;
}
.site-text-callout-top {
    transform: translateY(-48px);
}
.site-text-callout-bottom {
    transform: translateY(48px);
}
.site-text-bubble {
    display: flex;
    align-items: center;
    justify-content: center;
    max-width: 8em;
    border-radius: 16px;
    background: rgba(255, 255, 255, 0.96);
    box-shadow: 0px 4px 16px 0px rgba(45, 48, 64, 0.2);
    padding: 8px 20px;
}
.site-text-bubble-content {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}
.site-text-bubble-arrow {
    align-self: center;
    width: 0;
    height: 0;
    border-left: 10px solid transparent;
    border-right: 10px solid transparent;
}
.site-text-bubble-arrow-top {
    margin-bottom: -2px;
    border-bottom: 12px solid rgba(255, 255, 255, 0.96);
}
.site-text-bubble-arrow-bottom {
    margin-top: -2px;
    border-top: 12px solid rgba(255, 255, 255, 0.96);
}
.site-text-label {
    max-width: 8em;
    font-size: 24px;
    font-weight: 500;
    color: rgba(0, 0, 0, 0.9);
    line-height: 34px;
    text-align: center;
    white-space: normal;
    word-break: break-all;
}
.site-text-action-icon {
    width: 35px;
    height: 35px;
    flex-shrink: 0;
}
.pd-6-10 {
    padding: 8px 20px !important;
}
@@ -340,12 +271,14 @@
.waiting-callout-txt {
    z-index: 200;
    border-radius: 16px;
    color: rgba(0, 0, 0, 0.9);
    font-size: 24px;
    font-weight: 500;
    line-height: 36px;
    font-weight: 400;
    background: rgba(255, 255, 255, 1);
    padding: 8px 20px;
    line-height: 48px;
    display: flex;
    text-shadow: 0px 2px 8px rgba(255, 255, 255, 0.9);
}
.pr-6 {
src/robotaxi/pages/begin-route/index.tsx
@@ -15,8 +15,6 @@
import useMapMarkers from './hooks/use-map-markers';
// 车辆追踪状态管理
import useVehicleTracking from './hooks/use-vehicle-tracking';
// 行程平滑运动编排
import useSmoothRouteMotion from './hooks/use-smooth-route-motion';
// Android 覆盖物状态管理
import useAndroidOverlays from './hooks/use-android-overlays';
// 地图视口状态管理
@@ -32,8 +30,6 @@
import useTripRuntime from './hooks/use-trip-runtime';
// 订单动作编排
import useOrderActions from './hooks/use-order-actions';
// socket 掉线兜底轮询编排
import useSocketFallbackPolling from './hooks/use-socket-fallback-polling';
// 视图委托渲染编排
import useViewDelegates from './hooks/use-view-delegates';
// 修改目的地流程编排
@@ -52,7 +48,6 @@
import originIcon from '../../../robotaxi/assets/origin-icon.png'
import packUpIcon from '../../../robotaxi/assets/pack-up.png'
import { _ROBOTAXI_WS_HOST } from '../../../constants/api';
import { queryOrderRoute, queryOrderTrajectory } from '../../api/begin-route';
const ROUTE_SUMMARY_MARKER_ID = 999001;
type StatusRule = {
@@ -74,13 +69,6 @@
  const WAITING_TIMER_ELAPSED_KEY = 'waitingTimerElapsed'; // 等待计时已过秒数
  const DEFAULT_QUEUE_POLLING_TIME = 10; // 排队位次默认轮询秒数
  const DEFAULT_REQUOTE_EXPIRE_SECONDS = 10 * 60; // 重新报价默认有效期秒数
  const requestOrderRouteApi = (targetOrderId: string, status: number) => { // 掉线兜底专用路线查询,直接返回接口结果供统一落地层消费
    const routeType = status <= TripStatus.VehicleArrivedPickup ? 1 : 2;
    const requestRoute = [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(status as TripStatus)
      ? queryOrderTrajectory
      : queryOrderRoute;
    return requestRoute({ orderId: targetOrderId, routeType });
  };
  const {
    showCustomerService,
@@ -305,26 +293,11 @@
    currentFullRouteRef,
    lastErasedIndexRef,
    applyRoutePoints: routeApplyRoutePoints,
    resetRoutePolylineCache,
    erasePassedRouteSegments: routeErasePassedRouteSegments,
    requestRouteAndFare: routeRequestRouteAndFare,
    requestOrderRoute: routeRequestOrderRoute,
    updateWalkingRouteForStart: routeUpdateWalkingRouteForStart
  } = routeState; // 路线状态解构
  const {
    applyRouteSnapshot: applySmoothRouteSnapshot,
    resetSmoothRouteMotion
  } = useSmoothRouteMotion({
    tripStatus,
    tripStatusRef,
    applyRoutePoints: routeApplyRoutePoints,
    resetRoutePolylineCache,
    setRouteDistance,
    setRouteDuration,
    setVehicleDirection,
    setVehicleMoveAlongPoints,
    updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled
  }); // 平滑路线运动编排
  const displayPolyline = useMemo(() => {
    if (!(isModifyDestinationReviewVisible || hasLocalDestinationOverride) || !Array.isArray(previousRoutePoints) || previousRoutePoints.length === 0) {
      return polyline;
@@ -551,31 +524,33 @@
    setOrderId,
    setTripStatus,
    setVehicleInfo,
    vehicleInfo,
    setPriceUpdateVersion,
    setFinishCountdownEnabled,
    setShowAnnouncementsPopup,
    setRoutePoints,
    setPolyline,
    polylineKeyRef,
    lastPolylineUpdateAtRef,
    lastRoutePointRef,
    lastRouteUpdateAtRef,
    lastVehiclePointRef,
    lastVehicleUpdateAtRef,
    currentFullRouteRef,
    lastErasedIndexRef,
    orderRouteInitializedRef,
    setCancelType,
    setShowCancelPopup,
    checkAndShowPassengerNotice: tripRuntime.checkAndShowPassengerNotice,
    setVehicleDirection,
    setVehicleMoveAlongPoints,
    setRouteDistance,
    setRouteDuration,
    setBeforeUserEndSite,
    applySmoothRouteSnapshot,
    resetSmoothRouteMotion
  });
  useSocketFallbackPolling({
    tripStatus,
    tripStatusRef,
    orderId,
    setTripStatus,
    setOrderId,
    setVehicleInfo,
    setStartSite,
    setEndSite,
    setTrunkStatus,
    routeRequestOrderRouteApi: requestOrderRouteApi,
    applySmoothRouteSnapshot
    erasePassedRouteSegments: routeErasePassedRouteSegments,
    applyRoutePoints: routeApplyRoutePoints,
    updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled,
    onOrderUpdateLineRendered: autoRouteViewport.handleOrderUpdateLineRendered
  });
  const orderActions = useOrderActions({