hejianhao
2026-04-21 ee97974a4b66097843908d381a8fe4c107885d46
feat: 引入行程状态枚举以替代魔术数字

- 新增 TripStatus 和 SocketOrderStatus 枚举,统一管理行程状态
- 替换多处硬编码的状态数字为枚举引用,提升代码可读性和维护性
- 提供类型安全的转换函数 toTripStatus 和 toSocketOrderStatus
- 更新相关组件、钩子和工具函数以使用新枚举类型
3个文件已添加
33个文件已修改
2270 ■■■■■ 已修改文件
src/pages/robotaxi/home/components/order-status-card/index.tsx 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-drag-gesture.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-home-bootstrap.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/begin-route-view.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-action-bar.tsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/cancel-popup.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/constants/trip-status.ts 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-actions.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-page-actions.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/components/share-trip-view.tsx 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-viewport.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-route-manager.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-share-trip-runtime.ts 436 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/index.tsx 976 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/order-status-card/index.tsx
@@ -1,29 +1,32 @@
import { View, Image } from '@tarojs/components';
import { safeParse } from '../../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../../../../../robotaxi/pages/begin-route/constants/trip-status';
type HomeTripStatus = TripStatus | 0;
type Props = {
  /** 行程状态 */
  tripStatus: number;
  tripStatus: HomeTripStatus;
  /** 订单信息 */
  orderData: any;
  /** 查看详情回调 */
  onViewDetail: () => void;
  /** 状态文案映射 */
  orderStatusMap: Record<number, string>;
  orderStatusMap: Partial<Record<TripStatus, string>>;
};
/**
 * 订单状态卡片。
 */
export default function OrderStatusCard({ tripStatus, orderData, onViewDetail, orderStatusMap }: Props) {
  if (![3, 4, 5, 6, 7, 8].includes(tripStatus)) {
  if (![TripStatus.CallingCar, TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.ArrivedDestination, TripStatus.TripFinished].includes(tripStatus as TripStatus)) {
    return null;
  }
  return (
    <View className="order-status">
      {
        [3].includes(tripStatus) &&
        [TripStatus.CallingCar].includes(tripStatus as TripStatus) &&
        <View className="order-status-left">
          <View className="order-status-left-item">
            <Image className="order-status-left-item-icon" src={require('../../../../../robotaxi/assets/pointer.png')} />
@@ -34,7 +37,7 @@
        </View>
      }
      {
        [4, 5, 6, 7, 8].includes(tripStatus) &&
        [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.ArrivedDestination, TripStatus.TripFinished].includes(tripStatus as TripStatus) &&
        <View className="order-status-left">
          {
            orderData.vehiclePictures
@@ -43,7 +46,7 @@
          }
          <View className="order-status-left-detail">
            <View>{orderData.plateNumber}</View>
            <View>{orderStatusMap[tripStatus]}</View>
            <View>{orderStatusMap[tripStatus as TripStatus]}</View>
          </View>
        </View>
      }
src/pages/robotaxi/home/hooks/use-drag-gesture.ts
@@ -1,11 +1,14 @@
import Taro from '@tarojs/taro';
import { useEffect, useRef, useState } from 'react';
import { TripStatus } from '../../../../robotaxi/pages/begin-route/constants/trip-status';
type HomeTripStatus = TripStatus | 0;
type Deps = {
  /** 轮播列表 */
  bannerList: any[];
  /** 行程状态 */
  tripStatus: number;
  tripStatus: HomeTripStatus;
};
/**
src/pages/robotaxi/home/hooks/use-home-bootstrap.ts
@@ -1,6 +1,9 @@
import Taro from '@tarojs/taro';
import wsManager from '../../../../utils/websocket';
import { mapOrderStatusToTripStatus } from '../../../../utils/Robotaxi-public-method';
import { TripStatus, toTripStatus } from '../../../../robotaxi/pages/begin-route/constants/trip-status';
type HomeTripStatus = TripStatus | 0;
type Deps = {
  /** ws 服务地址 */
@@ -18,7 +21,7 @@
  /** 设置吸附状态 */
  setIsAdsorbed: (value: boolean) => void;
  /** 设置行程状态 */
  setTripStatus: (value: number) => void;
  setTripStatus: (value: HomeTripStatus) => void;
  /** 认证校验 */
  checkOtp: (silent?: boolean) => Promise<any>;
  /** 设置登录态 */
@@ -69,6 +72,11 @@
 * 首页启动流程编排。
 */
export default function useHomeBootstrap(deps: Deps) {
  const toHomeTripStatus = (value: unknown): HomeTripStatus => {
    const numericValue = Number(value); // 统一转数值后做状态归一
    return numericValue === 0 ? 0 : toTripStatus(numericValue);
  };
  /**
   * 同步登录、协议与活跃订单。
   */
@@ -95,7 +103,7 @@
      return;
    }
    deps.setOrderData(activeOrder || {});
    const mappedStatus = mapOrderStatusToTripStatus(activeOrder.status || 0);
    const mappedStatus = toHomeTripStatus(mapOrderStatusToTripStatus(activeOrder.status || 0));
    deps.setTripStatus(mappedStatus);
    Taro.setStorageSync('tripStatus', mappedStatus);
    Taro.setStorageSync('orderId', activeOrder.id || '');
@@ -209,7 +217,7 @@
      setCurrentAddress: deps.setCurrentAddress,
      setIsAdsorbed: deps.setIsAdsorbed
    });
    deps.setTripStatus(Number(Taro.getStorageSync('tripStatus')) || 0);
    deps.setTripStatus(toHomeTripStatus(Taro.getStorageSync('tripStatus')));
    syncAuthAndOrder();
    Taro.getSetting({
      success: (res) => {
src/pages/robotaxi/home/index.tsx
@@ -45,6 +45,7 @@
// 首页启动流程编排
import useHomeBootstrap from './hooks/use-home-bootstrap';
import { DEFAULT_LATITUDE, DEFAULT_LONGITUDE, FIRST_ENTER_KEY } from '../../../robotaxi/constants/map';
import { TripStatus, toTripStatus } from '../../../robotaxi/pages/begin-route/constants/trip-status';
import {
  getGeofence,
  getBannerList,
@@ -55,6 +56,8 @@
import { getServicePhone } from '../../../robotaxi/api/customer-service';
import { getOrderDistance } from '../../../robotaxi/api/select-map-site';
import { checkOtp } from '../../../utils/tools';
type HomeTripStatus = TripStatus | 0;
export default function Home() {
  const renderTailPreserveText = (text: string, tailCount = 3) => {
@@ -115,7 +118,7 @@
  const [isMaintain, setIsMaintain] = useState(false); // 是否维护中
  const [maintainBounce, setMaintainBounce] = useState(false);
  const [hotfixEndText, setHotfixEndText] = useState('');
  const [tripStatus, setTripStatus] = useState<number>(0); // 行程状态
  const [tripStatus, setTripStatus] = useState<HomeTripStatus>(0); // 行程状态(0 表示无进行中行程)
  const [bannerList, setBannerList] = useState<any[]>([]); // 轮播图列表
  const [weatherData, setWeatherData] = useState<any>(null); // 天气数据
  const [orderData, setOrderData] = useState<any>({}); // 订单数据
@@ -148,13 +151,13 @@
    }
  }, []);
  const ORDER_STATUS = {
    4: '车辆正在赶来',
    5: '车辆已到达上车点,请尽快前往',
    6: '正在前往目的地',
    7: '车辆已到达目的地,请尽快结束行程',
    8: '行程已结束,请尽快支付'
  }
  const ORDER_STATUS: Partial<Record<TripStatus, string>> = {
    [TripStatus.VehicleApproaching]: '车辆正在赶来',
    [TripStatus.VehicleArrivedPickup]: '车辆已到达上车点,请尽快前往',
    [TripStatus.InService]: '正在前往目的地',
    [TripStatus.ArrivedDestination]: '车辆已到达目的地,请尽快结束行程',
    [TripStatus.TripFinished]: '行程已结束,请尽快支付'
  }; // 订单状态文案映射
  useEffect(() => {
    let timer: any = null;
@@ -199,9 +202,9 @@
  // 监听订单状态变化
  useEffect(() => {
    const orderStatusHandler = (payload) => {
      const mappedTripStatus = payload?.tripStatus;
      const mappedTripStatus = payload?.tripStatus; // ws 推送的行程状态
      if (typeof mappedTripStatus === 'number') {
        setTripStatus(mappedTripStatus);
        setTripStatus(mappedTripStatus === 0 ? 0 : toTripStatus(mappedTripStatus));
      }
    };
    let openHandler: (() => void) | null = null;
src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -7,6 +7,7 @@
import EvaluatePopup from './evaluate-popup';
import TipPopup from '../../select-map-site/components/tip-popup';
import AuthBluetoothPopup from './auth-bluetooth-popup';
import { TripStatus } from '../constants/trip-status';
type Props = {
  mapLongitude: number;
@@ -48,7 +49,7 @@
  startSite: any;
  endSite: any;
  fareEstimate: any;
  tripStatus: number;
  tripStatus: TripStatus;
  routeDuration: number | null;
  handleCloseCancelPopup: () => void;
  handleConfirmCancel: () => void;
@@ -170,7 +171,7 @@
        <View className='container-inner'>
          {shouldShowOrigin && (
            <View className='origin-box' style={{ opacity: originBoxHidden ? 0 : originOpacity, transition: 'opacity 0.2s ease-out', pointerEvents: originBoxHidden ? 'none' : 'auto' }}>
              {[4, 5].includes(tripStatus) && (
              {[TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) && (
                <Image onClick={handleWalkNavigation} style={{ marginBottom: '16px' }} className='origin-icon' src={walkIcon} />
              )}
              <Image onClick={toOrigin} className='origin-icon' src={originIcon} />
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -2,9 +2,10 @@
import { View, Image } from "@tarojs/components";
import { useState, useRef, useEffect } from "react";
import { unlockVehicle, queryVehicleTrunkOpenStatus } from '../../../../api/begin-route';
import { TripStatus } from '../../constants/trip-status';
type Props = {
    tripStatus: number;
    tripStatus: TripStatus;
    orderId?: string;
    orderTrunkStatus?: boolean;
    handleOpenOverRoutePopup?: () => void;
@@ -168,7 +169,7 @@
        });
    };
    if (tripStatus === 5) {
    if (tripStatus === TripStatus.VehicleArrivedPickup) {
        return (
            <View className="car-card-arrive">
                <View className={`car-card-arrive-item ${trunkStatus && 'close-btn-item'}`} onClick={() => handleUnlock('trunk', () => setTrunkStatus(!trunkStatus))}>
@@ -183,7 +184,7 @@
        );
    }
    if (tripStatus === 7) {
    if (tripStatus === TripStatus.ArrivedDestination) {
        return (
            <View className="car-card-reach">
                <View className="car-card-reach-text">点击按钮开车门</View>
src/robotaxi/pages/begin-route/components/calling-car-modules/evaluation-card.tsx
@@ -8,9 +8,10 @@
import activeGood from '../../../../assets/active-good.png'
import activeBad from '../../../../assets/active-bad.png'
import activeNormal from '../../../../assets/active-normal.png'
import { TripStatus } from '../../constants/trip-status';
type Props = {
    tripStatus: number;
    tripStatus: TripStatus;
    orderId?: string;
    handleOpenEvaluatePopup?: () => void;
    evaluateSubmitVersion?: number;
@@ -75,7 +76,7 @@
        });
    };
    if (tripStatus !== 9) {
    if (tripStatus !== TripStatus.PaymentSuccess) {
        return null;
    }
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
@@ -2,9 +2,10 @@
import { View, Image, Text } from "@tarojs/components";
import { useState, useEffect } from "react";
import { formatMoney, getTypeLabel, formatTripDistance, formatNumber, formatTripDurationFromSeconds } from '../../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../../constants/trip-status';
type Props = {
    tripStatus: number;
    tripStatus: TripStatus;
    fareBreakdown: any;
    orderId?: string;
    resetDetailVersion?: number;
@@ -54,13 +55,13 @@
        }
    };
    if (![8, 9].includes(tripStatus)) {
    if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) {
        return null;
    }
    return (
        <View>
            <View className="car-card-end" style={{ paddingBottom: tripStatus === 8 ? '145px' : '44px' }}>
            <View className="car-card-end" style={{ paddingBottom: tripStatus === TripStatus.TripFinished ? '145px' : '44px' }}>
                <View className="car-card-line" style="margin:24px 0;"></View>
                <View className="car-card-end-particulars">
                    <View className="car-card-end-particulars-label">
@@ -181,7 +182,7 @@
                }
            </View>
            {
                tripStatus === 8 &&
                tripStatus === TripStatus.TripFinished &&
                <View className="pay-box">
                    {
                        fareBreakdown.operateFree != 1 ?
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
@@ -2,9 +2,10 @@
import { View, Image } from "@tarojs/components";
import { useState, useEffect, useRef } from "react";
import { queryTemperatureByOrderId, setTemperature } from '../../../../api/begin-route';
import { TripStatus } from '../../constants/trip-status';
type Props = {
    tripStatus: number;
    tripStatus: TripStatus;
    orderId?: string;
};
@@ -16,9 +17,9 @@
    const TEMP_MIN = 22;
    const TEMP_MAX = 28;
    // 状态 4 时拉取车辆当前温度
    // 车辆即将到达状态时拉取车辆当前温度
    useEffect(() => {
        if (tripStatus === 4) {
        if (tripStatus === TripStatus.VehicleApproaching) {
            const targetOrderId = orderId || Taro.getStorageSync('orderId');
            if (targetOrderId) {
                queryTemperatureByOrderId({ orderId: targetOrderId }).then((res: any) => {
@@ -99,7 +100,7 @@
        });
    };
    // if (tripStatus !== 4 || typeof tempStatus !== 'number') {
    // if (tripStatus !== TripStatus.VehicleApproaching || typeof tempStatus !== 'number') {
    //     return null;
    // }
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-action-bar.tsx
@@ -2,9 +2,10 @@
import changeDestinationIcon from "../../../../assets/change-destination.png";
import shareIcon from "../../../../assets/share-trip.png";
import cancelIcon from "../../../../assets/cancel-trip.png";
import { TripStatus } from '../../constants/trip-status';
type Props = {
    tripStatus: number;
    tripStatus: TripStatus;
    handleCancelTrip?: () => void;
    handleModifyDestination?: () => void;
    handleShareTrip?: () => void;
@@ -22,21 +23,21 @@
            key: "cancel",
            label: "取消行程",
            icon: cancelIcon,
            visible: tripStatus === 4,
            visible: tripStatus === TripStatus.VehicleApproaching,
            onClick: handleCancelTrip
        },
        {
            key: "destination",
            label: "修改目的地",
            icon: changeDestinationIcon,
            visible: [4, 5, 6].includes(tripStatus),
            visible: [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus),
            onClick: handleModifyDestination
        },
        {
            key: "share",
            label: "分享行程",
            icon: shareIcon,
            visible: [4, 5, 6].includes(tripStatus),
            visible: [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus),
            onClick: handleShareTrip
        }
    ].filter(item => item.visible);
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
@@ -2,6 +2,7 @@
import { useEffect, useState } from "react";
import { formatCountdown } from '../../../../../utils/Robotaxi-public-method';
import { queryOrderQueueStatus } from '../../../../api/begin-route';
import { TripStatus } from '../../constants/trip-status';
type QueueStatus = {
    currentPosition: number;
@@ -12,7 +13,7 @@
type Props = {
    orderId?: string;
    tripStatus: number;
    tripStatus: TripStatus;
    waitMinutes: number | null;
    durationDisplay: { value: string | number; unit: string };
    isArriveSoon: boolean;
@@ -40,7 +41,7 @@
    const [queueStatus, setQueueStatus] = useState<QueueStatus>(null);
    useEffect(() => {
        if (tripStatus !== 3 || !orderId) {
        if (tripStatus !== TripStatus.CallingCar || !orderId) {
            setQueueStatus(null);
            return;
        }
@@ -89,25 +90,25 @@
    // 标题行文案映射
    const titleMap = {
        3: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>,
        4: <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>,
        5: <>车辆已到达上车点<View className="time" style={isPickupCountdownSoon ? { color: '#E03434' } : undefined}>{pickupCountdownSeconds !== null ? formatCountdown(pickupCountdownSeconds) : '--:--'}</View></>,
        6: <>预计<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}抵达目的地</>,
        7: (finishCountdownEnabled || finishCountdownSeconds !== null)
        [TripStatus.CallingCar]: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>,
        [TripStatus.VehicleApproaching]: <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>,
        [TripStatus.VehicleArrivedPickup]: <>车辆已到达上车点<View className="time" style={isPickupCountdownSoon ? { color: '#E03434' } : undefined}>{pickupCountdownSeconds !== null ? formatCountdown(pickupCountdownSeconds) : '--:--'}</View></>,
        [TripStatus.InService]: <>预计<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}抵达目的地</>,
        [TripStatus.ArrivedDestination]: (finishCountdownEnabled || finishCountdownSeconds !== null)
            ? <>已到达目的地<View className="time">{finishCountdownSeconds !== null ? formatCountdown(finishCountdownSeconds) : '--:--'}</View></>
            : <>已到达目的地</>,
        8: '行程已结束',
        9: '支付成功'
        [TripStatus.TripFinished]: '行程已结束',
        [TripStatus.PaymentSuccess]: '支付成功'
    }
    // 说明文案映射
    const descMap = {
        3: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' },
        4: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: isArriveSoon ? 'calling-car-content-desc-red' : 'calling-car-content-warning' } : null,
        5: { text: '此处限停,请在倒计时结束前到达上车点', className: 'calling-car-content-desc' },
        6: { text: '行程已开始,请全程系好安全带', className: 'calling-car-content-desc' },
        7: { text: '倒计时结束时车辆将自动驶离,请带好物品安全下车', className: 'calling-car-content-desc' },
        8: { text: '订单待支付,请核对后完成支付', className: 'calling-car-content-desc' }
        [TripStatus.CallingCar]: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' },
        [TripStatus.VehicleApproaching]: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: isArriveSoon ? 'calling-car-content-desc-red' : 'calling-car-content-warning' } : null,
        [TripStatus.VehicleArrivedPickup]: { text: '此处限停,请在倒计时结束前到达上车点', className: 'calling-car-content-desc' },
        [TripStatus.InService]: { text: '行程已开始,请全程系好安全带', className: 'calling-car-content-desc' },
        [TripStatus.ArrivedDestination]: { text: '倒计时结束时车辆将自动驶离,请带好物品安全下车', className: 'calling-car-content-desc' },
        [TripStatus.TripFinished]: { text: '订单待支付,请核对后完成支付', className: 'calling-car-content-desc' }
    }
    return (
@@ -117,17 +118,17 @@
                    <View className='calling-car-content-title'>{titleMap[tripStatus]}</View>
                )}
                {descMap[tripStatus] && (
                    // <View style={{ paddingBottom: tripStatus === 3 ? '20px' : '0px' }} className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View>
                    // <View style={{ paddingBottom: tripStatus === TripStatus.CallingCar ? '20px' : '0px' }} className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View>
                    <View className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View>
                )}
                {tripStatus === 3 && queueStatus && (
                {tripStatus === TripStatus.CallingCar && queueStatus && (
                    <View className='calling-car-content-desc'>
                        第{queueStatus.currentPosition}位/共{queueStatus.totalCount}位
                    </View>
                )}
            </View>
            {
                [3].includes(tripStatus) &&
                [TripStatus.CallingCar].includes(tripStatus) &&
                <View className="cancel-btn">
                    <Image onClick={() => handleOpenCancelPopup?.(durationDisplay)} src={require('../../../../assets/cancel-btn.png')} className="cancel" />
                </View>
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
@@ -2,9 +2,10 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { getRemainingSeconds, parseFlexibleEndTimeMs } from '../../../../../utils/Robotaxi-public-method';
import { getWaitTime, queryOrderFinishTime, cancelOrder, queryFareBreakdown } from '../../../../api/begin-route';
import { TripStatus } from '../../constants/trip-status';
type Props = {
    tripStatus: number;
    tripStatus: TripStatus;
    orderId?: string;
    estimatedWaitMinutes?: number;
    finishCountdownEnabled: boolean;
@@ -73,14 +74,14 @@
    };
    useEffect(() => {
        if (typeof priceUpdateVersion === 'number' && priceUpdateVersion > 0 && [8, 9].includes(tripStatus)) {
        if (typeof priceUpdateVersion === 'number' && priceUpdateVersion > 0 && [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) {
            requestFareBreakdown(true);
        }
    }, [priceUpdateVersion, tripStatus, requestFareBreakdown]);
    useEffect(() => {
        let active = true;
        if (tripStatus !== 3) return () => { active = false; };
        if (tripStatus !== TripStatus.CallingCar) return () => { active = false; };
        if (typeof estimatedWaitMinutes === 'number' && !Number.isNaN(estimatedWaitMinutes)) {
            setWaitMinutes(estimatedWaitMinutes);
            return () => { active = false; };
@@ -100,7 +101,7 @@
    }, [tripStatus, estimatedWaitMinutes]);
    useEffect(() => {
        if (tripStatus !== 4) return;
        if (tripStatus !== TripStatus.VehicleApproaching) return;
        const startSite = Taro.getStorageSync('startSite');
        const time = Number(startSite?.time);
        if (!Number.isNaN(time)) {
@@ -113,14 +114,14 @@
    }, [tripStatus]);
    useEffect(() => {
        if (tripStatus !== 5) {
        if (tripStatus !== TripStatus.VehicleArrivedPickup) {
            clearPickupTimer();
            return;
        }
        const runPickupTick = () => {
            // 防止状态已切换后,旧定时回调仍触发取消逻辑
            if (pickupTripStatusRef.current !== 5) {
            if (pickupTripStatusRef.current !== TripStatus.VehicleArrivedPickup) {
                clearPickupTimer();
                return;
            }
@@ -178,7 +179,7 @@
    }, [tripStatus, orderId, handleCancelTimeout, clearPickupTimer]);
    useEffect(() => {
        if (tripStatus !== 7) {
        if (tripStatus !== TripStatus.ArrivedDestination) {
            clearFinishTimer();
            return;
        }
@@ -237,7 +238,7 @@
    }, [tripStatus, handleFinishTimeout, finishCountdownEnabled, clearFinishTimer]);
    useEffect(() => {
        if ([8, 9].includes(tripStatus)) {
        if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) {
            Taro.removeStorageSync(FINISH_END_TIME_KEY);
            requestFareBreakdown();
        }
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -1,6 +1,7 @@
import { View, Image } from "@tarojs/components";
import './css-folder/calling-car.less'
import { formatDurationDisplay } from '../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../constants/trip-status';
import useTripCountdown from './calling-car-modules/use-trip-countdown'; // 行程倒计时
import TripStatusHeader from './calling-car-modules/trip-status-header'; // 行程状态头
@@ -16,7 +17,7 @@
type Props = {
    /** 行程状态:3呼叫中、4即将到达、5已到达上车点、6行程中、7到达终点、8待支付、9已支付 */
    tripStatus?: number;
    tripStatus?: TripStatus;
    /** 打开取消弹窗,入参为预计到达展示文案 */
    handleOpenCancelPopup?: (durationDisplay: any) => void;
    /** 上车点超时后的回调 */
@@ -74,7 +75,7 @@
}
export default function CallingCar({
    tripStatus = 3,
    tripStatus = TripStatus.CallingCar,
    handleOpenCancelPopup,
    handleCancelTimeout,
    handleFinishTimeout,
@@ -126,9 +127,9 @@
    /** 预计到达时长展示(秒/分钟) */
    const durationDisplay = formatDurationDisplay(routeDurationSeconds);
    /** 车辆即将到达上车点(<=60秒) */
    const isArriveSoon = tripStatus === 4 && (typeof routeDurationSeconds === 'number' && routeDurationSeconds <= 60);
    const isArriveSoon = tripStatus === TripStatus.VehicleApproaching && (typeof routeDurationSeconds === 'number' && routeDurationSeconds <= 60);
    /** 上车倒计时即将结束(<60秒) */
    const isPickupCountdownSoon = tripStatus === 5 && (typeof pickupCountdownSeconds === 'number' && pickupCountdownSeconds < 60);
    const isPickupCountdownSoon = tripStatus === TripStatus.VehicleArrivedPickup && (typeof pickupCountdownSeconds === 'number' && pickupCountdownSeconds < 60);
    // 按状态组合渲染行程卡片模块
    return (
@@ -150,7 +151,7 @@
            />
            {
                [4, 5].includes(tripStatus) && bluetoothStatusReady &&
                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) && bluetoothStatusReady &&
                <BluetoothOperateCard
                    description={bluetoothCardDescription}
                    actionText={bluetoothCardActionText}
@@ -159,7 +160,7 @@
            }
            {
                [4, 5].includes(tripStatus) &&
                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) &&
                <AuthBluetoothPopup
                    show={showAuthBluetoothPopup}
                    onClose={handleCloseAuthBluetoothPopup}
@@ -168,7 +169,7 @@
            }
            {
                [4, 5].includes(tripStatus) &&
                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) &&
                <SuccessAuthPopup
                    show={showSuccessAuthPopup}
                    onClose={handleCloseSuccessAuthPopup || (() => { })}
@@ -176,7 +177,7 @@
            }
            {
                [4, 5, 6, 8, 9].includes(tripStatus) &&
                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus) &&
                <View className="car-card">
                    <VehicleInfoCard vehicleInfo={vehicleInfo} />
@@ -209,7 +210,7 @@
            }
            <DoorTrunkControl
                tripStatus={tripStatus === 7 ? 7 : -1}
                tripStatus={tripStatus === TripStatus.ArrivedDestination ? TripStatus.ArrivedDestination : -1}
                orderId={orderId}
                orderTrunkStatus={orderTrunkStatus}
                handleOpenOverRoutePopup={handleOpenOverRoutePopup}
src/robotaxi/pages/begin-route/components/cancel-popup.tsx
@@ -6,6 +6,7 @@
    clearOrderTripStorage,
    formatDurationDisplay
} from '../../../../utils/Robotaxi-public-method'
import { TripStatus } from '../constants/trip-status';
type Props = {
@@ -14,7 +15,7 @@
    startSite?: any;
    endSite?: any;
    waitMinutes?: number;
    tripStatus?: number;
    tripStatus?: TripStatus;
    routeDurationSeconds?: number | null;
    discountAmount?: number;
    onClose?: () => void;
@@ -39,7 +40,7 @@
            tip: discountAmountValue > 0 ? `本单限时优惠${discountAmountValue}元,重新叫车可能等待更久` : '',
        },
        2: {
            title: tripStatus === 4
            title: tripStatus === TripStatus.VehicleApproaching
                ? `车辆还有${durationDisplay.value}${durationDisplay.unit}到达,确定取消吗`
                : `车辆还有${waitMinutesValue}分钟到达,确定取消吗`,
            tip: discountAmountValue > 0 ? `本单限时优惠${discountAmountValue}元,重新叫车可能等待更久` : '',
src/robotaxi/pages/begin-route/constants/trip-status.ts
New file
@@ -0,0 +1,65 @@
/**
 * 页面行程状态枚举:与历史 number 值保持 1:1 对齐,避免行为变化。
 */
export enum TripStatus {
  PublishRoute = 1, // 发布行程
  Publishing = 2, // 发布中
  CallingCar = 3, // 呼叫车中
  VehicleApproaching = 4, // 车辆即将到达
  VehicleArrivedPickup = 5, // 车辆到达上车点
  InService = 6, // 车辆行驶中
  ArrivedDestination = 7, // 到达目的地
  TripFinished = 8, // 行程已结束
  PaymentSuccess = 9, // 支付成功
  CancelOnlyPickup = 10, // 取消订单时只展示上车点
}
/**
 * socket 订单状态枚举:对应后端推送 status 字段。
 */
export enum SocketOrderStatus {
  Canceled = 0, // 已取消
  PendingAccept = 1, // 待接单
  PendingDeparture = 2, // 待出发
  PickupOnTheWay = 3, // 接驾中
  WaitingPassenger = 4, // 待客上车
  PassengerOnboard = 5, // 乘客已上车
  InProgress = 6, // 服务中
  WaitingDropoff = 7, // 待客下车
  PendingPayment = 8, // 待付款
  PendingComment = 9, // 待评价
  Completed = 10, // 已完成
}
const TRIP_STATUS_SET = new Set<number>(Object.values(TripStatus).filter((value) => typeof value === 'number') as number[]); // 行程状态值集合
const SOCKET_STATUS_SET = new Set<number>(Object.values(SocketOrderStatus).filter((value) => typeof value === 'number') as number[]); // socket 状态值集合
export const DEFAULT_TRIP_STATUS = TripStatus.PublishRoute; // 默认行程状态
/**
 * 将未知值安全转为行程状态,异常值回落到 fallback。
 */
export function toTripStatus(value: unknown, fallback: TripStatus = DEFAULT_TRIP_STATUS): TripStatus {
  const numericValue = Number(value); // 统一转数值
  if (!Number.isFinite(numericValue)) {
    return fallback;
  }
  if (!TRIP_STATUS_SET.has(numericValue)) {
    return fallback;
  }
  return numericValue as TripStatus;
}
/**
 * 将未知值安全转为 socket 状态,异常值回落到 fallback。
 */
export function toSocketOrderStatus(value: unknown, fallback: SocketOrderStatus = SocketOrderStatus.PendingAccept): SocketOrderStatus {
  const numericValue = Number(value); // 统一转数值
  if (!Number.isFinite(numericValue)) {
    return fallback;
  }
  if (!SOCKET_STATUS_SET.has(numericValue)) {
    return fallback;
  }
  return numericValue as SocketOrderStatus;
}
src/robotaxi/pages/begin-route/hooks/use-android-overlays.ts
@@ -1,10 +1,11 @@
import Taro from '@tarojs/taro';
import { useEffect, useRef, useState } from 'react';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  shouldShowWaitingRadar: boolean;
  isAndroid: boolean;
  tripStatus: number;
  tripStatus: TripStatus;
  mapScale: number;
  startSite: any;
  endSite: any;
@@ -85,7 +86,7 @@
  // 更新车辆气泡位置
  const updateCarCalloutPosition = async () => {
    if (!deps.isAndroid || !deps.vehicleLocation || ![4, 6].includes(deps.tripStatus)) {
    if (!deps.isAndroid || !deps.vehicleLocation || ![TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus)) {
      setCarCalloutVisible(false);
      return;
    }
@@ -103,7 +104,7 @@
  // 更新到达气泡位置
  const updateArriveCalloutPosition = async () => {
    if (!deps.isAndroid || ![7].includes(deps.tripStatus)) {
    if (!deps.isAndroid || ![TripStatus.ArrivedDestination].includes(deps.tripStatus)) {
      setArriveCalloutVisible(false);
      return;
    }
@@ -153,7 +154,7 @@
  }, [deps.isAndroid, deps.tripStatus, deps.endSite?.latitude, deps.endSite?.longitude]);
  useEffect(() => {
    if (!deps.isAndroid || deps.tripStatus !== 7) return;
    if (!deps.isAndroid || deps.tripStatus !== TripStatus.ArrivedDestination) return;
    if (deps.mapScale < 20) return;
    const timer = setTimeout(() => {
      updateArriveCalloutPosition();
src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts
@@ -1,8 +1,9 @@
import Taro, { useDidShow } from '@tarojs/taro';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  tripStatus: number;
  tripStatus: TripStatus;
  bluetoothAuthSuccess: boolean;
  startBluetoothAuth: () => void;
};
@@ -168,7 +169,7 @@
  // 仅在 4/5 状态维护蓝牙 UI;其他状态强制收起弹窗
  useEffect(() => {
    if ([4, 5].includes(tripStatus)) {
    if ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) {
      setStatusReady(false);
      refreshBluetoothState();
      startBluetoothStateWatch();
@@ -185,7 +186,7 @@
  // 页面回前台后同步蓝牙状态,避免设置页返回后状态滞后
  useDidShow(() => {
    if ([4, 5].includes(tripStatus)) {
    if ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) {
      refreshBluetoothState();
    }
  });
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts
@@ -2,10 +2,11 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import RtAuthBluetoothManager from '../../../utils/car-control/RtAuthBluetoothManager';
import { getBluetoothUnlockToken } from '../../../api/car-control';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  orderId: string;
  tripStatus: number;
  tripStatus: TripStatus;
  vehicleInfo: any;
};
@@ -101,7 +102,7 @@
   */
  const startAuth = useCallback(async () => {
    console.log('[蓝牙] 开始鉴权入参:', { tripStatus, orderId, bluetoothMac, isAuthorizing, authSuccess });
    if (![4, 5].includes(tripStatus)) return;
    if (![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) return;
    if (!orderId || !bluetoothMac) return;
    const startKey = `${orderId}|${bluetoothMac}`;
@@ -150,7 +151,7 @@
  }, [startAuth]);
  useEffect(() => {
    if (![4, 5].includes(tripStatus)) {
    if (![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) {
      console.log('[蓝牙] 当前状态非4/5,断开连接:', tripStatus);
      closeConnection();
      lastStartKeyRef.current = '';
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -1,8 +1,9 @@
import Taro from '@tarojs/taro';
import { useEffect, useRef, useState } from 'react';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  tripStatus: number;
  tripStatus: TripStatus;
  statusRule: any;
  isAndroid: boolean;
  showTrip1StyleEndpoint: boolean;
@@ -16,7 +17,7 @@
};
const defaultDeps: Deps = {
  tripStatus: 1,
  tripStatus: TripStatus.PublishRoute,
  statusRule: { showPickup: true, showDropoff: true, showCar: false },
  isAndroid: false,
  showTrip1StyleEndpoint: false,
@@ -54,7 +55,7 @@
      ...(useAndroidNativeTrip1Callout
        ? {
          callout: {
            content: (deps.tripStatus === 1 || deps.showTrip1StyleEndpoint) ? `${pickupSite?.name || '上车点'} >` : (pickupSite?.name || '上车点'),
            content: (deps.tripStatus === TripStatus.PublishRoute || deps.showTrip1StyleEndpoint) ? `${pickupSite?.name || '上车点'} >` : (pickupSite?.name || '上车点'),
            color: '#000000',
            fontSize: 12,
            bgColor: '#FFFFFF',
@@ -65,7 +66,7 @@
          },
        }
        : {
          customCallout: { anchorY: [2, 3].includes(deps.tripStatus) ? 150 : -5, anchorX: 0, display: 'ALWAYS' },
          customCallout: { anchorY: [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus) ? 150 : -5, anchorX: 0, display: 'ALWAYS' },
        })
    };
  };
@@ -73,7 +74,7 @@
  // 构建下车点 marker
  const createDropoffMarker = (dropoffSite: any, useAndroidNativeTrip1Callout: boolean) => {
    if (!(dropoffSite?.latitude && dropoffSite?.longitude)) return null;
    const dropoffName = [7].includes(deps.tripStatus) ? '已到达' : (dropoffSite.name || '目的地');
    const dropoffName = [TripStatus.ArrivedDestination].includes(deps.tripStatus) ? '已到达' : (dropoffSite.name || '目的地');
    return {
      id: 2,
      latitude: dropoffSite.latitude,
@@ -84,10 +85,10 @@
      anchor: { x: 0.5, y: 1 },
      zIndex: 1,
      name: dropoffName,
      ...(useAndroidNativeTrip1Callout || deps.tripStatus == 6
      ...(useAndroidNativeTrip1Callout || deps.tripStatus === TripStatus.InService
        ? {
          callout: {
            content: (deps.tripStatus === 1 || deps.showTrip1StyleEndpoint) ? `${dropoffName} >` : dropoffName,
            content: (deps.tripStatus === TripStatus.PublishRoute || deps.showTrip1StyleEndpoint) ? `${dropoffName} >` : dropoffName,
            color: '#000000',
            fontSize: 12,
            bgColor: '#FFFFFF',
@@ -121,13 +122,13 @@
    };
  };
  // 应用 marker 状态并处理状态6刷新标记
  // 应用 marker 状态并处理“行驶中”阶段的刷新标记
  const applyMarkerState = (nextMarkers: any[], nextMarkersSignature: string, shouldSkipUpdate: boolean, carMarker: any) => {
    if (!shouldSkipUpdate && markersSignatureRef.current !== nextMarkersSignature) {
      markersSignatureRef.current = nextMarkersSignature;
      setMarkers(nextMarkers);
    }
    if (!deps.isAndroid && [4, 6].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;
@@ -140,10 +141,10 @@
  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 useAndroidNativeTrip1Callout = deps.isAndroid && ([1, 5, 8, 9, 10].includes(deps.tripStatus) || deps.showTrip1StyleEndpoint);
    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 shouldWaitArrivedDirection = deps.tripStatus === 7 && !deps.arrivedMarkerReady;
    const shouldWaitArrivedDirection = deps.tripStatus === TripStatus.ArrivedDestination && !deps.arrivedMarkerReady;
    const effectiveShowCar = deps.statusRule.showCar && !shouldWaitArrivedDirection;
    const carMarker = createCarMarker();
    const nextMarkers: any[] = [];
@@ -161,7 +162,7 @@
      ].join('|'))
      .join(';');
    const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2);
    const shouldHoldMarkerByTranslate = [4, 6].includes(deps.tripStatus) && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
    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.vehicleLocation, deps.vehicleDirection, deps.vehicleMarkerId, deps.isAndroid, deps.statusRule.showPickup, deps.statusRule.showCar, deps.statusRule.showDropoff]);
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
@@ -1,9 +1,10 @@
import Taro from '@tarojs/taro';
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
import { computeAdaptiveBottomPadding } from '../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../constants/trip-status';
type Deps = {
    tripStatus: number;
    tripStatus: TripStatus;
    statusRule: any;
    routePoints: any[];
    walkingPoints: any[];
@@ -17,7 +18,7 @@
};
const defaultDeps: Deps = {
    tripStatus: 1,
    tripStatus: TripStatus.PublishRoute,
    statusRule: { viewport: 'pickup' },
    routePoints: [],
    walkingPoints: [],
@@ -141,7 +142,7 @@
    // 按视口类型构建 includePoints 入参
    const buildViewportPayload = (viewportType: string, pickupSite: any, dropoffSite: any) => {
        const points: any[] = [];
        const keyParts: any[] = [viewportType === 'order_detail' ? 8 : deps.tripStatus, viewportType];
        const keyParts: any[] = [viewportType === 'order_detail' ? TripStatus.TripFinished : deps.tripStatus, viewportType];
        if (viewportType === 'route_walking') {
            if (deps.routePoints.length > 0) points.push(...deps.routePoints);
            if (deps.walkingPoints.length > 0) points.push(...deps.walkingPoints);
@@ -228,7 +229,7 @@
    const toOrigin = () => {
        const mapCtx = Taro.createMapContext('map');
        if (deps.tripStatus === 6 && deps.routePoints.length > 0) {
        if (deps.tripStatus === TripStatus.InService && deps.routePoints.length > 0) {
            const bottomPadding = computeBottomPadding(deps.routePoints, 'route');
            const topPadding = Math.max(24, resolveSafeTopInset() + 32);
            mapCtx.includePoints({
@@ -244,12 +245,12 @@
    const getLocationInfo = () => {
        Taro.getLocation({ type: 'gcj02' })
            .then((res) => {
                if (deps.tripStatus === 7) {
                if (deps.tripStatus === TripStatus.ArrivedDestination) {
                    return;
                }
                deps.setLongitude(res.longitude);
                deps.setLatitude(res.latitude);
                if (deps.tripStatus === 6 && deps.routePoints.length > 0) {
                if (deps.tripStatus === TripStatus.InService && deps.routePoints.length > 0) {
                    const bottomPadding = computeBottomPadding(deps.routePoints, 'route');
                    const topPadding = Math.max(24, resolveSafeTopInset() + 32);
                    Taro.createMapContext('map').includePoints({
@@ -268,7 +269,7 @@
    };
    useEffect(() => {
        if ([6, 7].includes(deps.tripStatus)) {
        if ([TripStatus.InService, TripStatus.ArrivedDestination].includes(deps.tripStatus)) {
            setShowLocationFlag(false);
            return;
        }
@@ -293,7 +294,7 @@
    }, [deps.tripStatus, deps.statusRule?.viewport, deps.endSite?.latitude, deps.endSite?.longitude, mapScale]);
    useEffect(() => {
        if (![8, 9].includes(deps.tripStatus)) {
        if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus)) {
            orderDetailViewportLockedRef.current = false;
            orderDetailRouteUpdatedAfterEnterRef.current = false;
            orderDetailEnterSignatureRef.current = '';
@@ -308,7 +309,7 @@
    }, [deps.tripStatus]);
    useEffect(() => {
        if (![8, 9].includes(deps.tripStatus)) return;
        if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus)) return;
        const { pickupSite, dropoffSite } = resolveViewportSites();
        const currentSignature = buildOrderDetailRouteSignature(pickupSite, dropoffSite);
        if (!orderDetailEnterSignatureRef.current) {
@@ -328,22 +329,22 @@
        const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite);
        if (points.length === 0) return;
        if (
            [8, 9].includes(deps.tripStatus) &&
            [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus) &&
            viewportType === 'order_detail' &&
            orderDetailViewportLockedRef.current &&
            orderDetailRouteUpdatedAfterEnterRef.current
        ) {
            return;
        }
        if (deps.tripStatus === 6 && viewportType === 'route' && tripStatus6ViewportLockedRef.current) return;
        if (deps.tripStatus === TripStatus.InService && viewportType === 'route' && tripStatus6ViewportLockedRef.current) return;
        const nextKey = keyParts.join('|');
        if (nextKey === viewportKeyRef.current) return;
        viewportKeyRef.current = nextKey;
        updateViewportByPayload(viewportType, points);
        if (deps.tripStatus === 6 && viewportType === 'route') {
        if (deps.tripStatus === TripStatus.InService && viewportType === 'route') {
            tripStatus6ViewportLockedRef.current = true;
        }
        if ([8, 9].includes(deps.tripStatus) && viewportType === 'order_detail') {
        if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus) && viewportType === 'order_detail') {
            const hasOrderDetailRouteAfterEnter = orderDetailRouteUpdatedAfterEnterRef.current && deps.routePoints.length > 0;
            orderDetailViewportLockedRef.current = hasOrderDetailRouteAfterEnter;
        }
src/robotaxi/pages/begin-route/hooks/use-modify-destination-flow.ts
@@ -2,6 +2,7 @@
import { useEffect, useMemo, useState } from 'react';
import { orderChange } from '../../../api/begin-route';
import { getOrderActive } from '../../../api/home';
import { TripStatus } from '../constants/trip-status';
import {
  MODIFY_DESTINATION_CANDIDATE_KEY,
  MODIFY_DESTINATION_OVERRIDE_KEY,
@@ -10,7 +11,7 @@
} from '../constants/modify-destination';
type Deps = {
  tripStatus: number;
  tripStatus: TripStatus;
  orderId: string;
  endSite: any;
  setEndSite: (site: any) => void;
@@ -51,13 +52,22 @@
    return isValidSite(cached) ? cached : null;
  });
  const MODIFIABLE_TRIP_STATUSES: TripStatus[] = [
    TripStatus.VehicleApproaching,
    TripStatus.VehicleArrivedPickup,
    TripStatus.InService,
    TripStatus.ArrivedDestination,
    TripStatus.TripFinished,
    TripStatus.PaymentSuccess
  ]; // 可修改目的地的行程状态
  useEffect(() => {
    if (!deps.orderId || ![4, 5, 6, 7, 8, 9].includes(deps.tripStatus)) {
    if (!deps.orderId || !MODIFIABLE_TRIP_STATUSES.includes(deps.tripStatus)) {
      setPreviewEndSite(null);
      setPreviousEndSite(null);
      setPreviousFareEstimate(null);
      setPreviousRoutePoints([]);
      if (![4, 5, 6, 7, 8, 9].includes(deps.tripStatus)) {
      if (!MODIFIABLE_TRIP_STATUSES.includes(deps.tripStatus)) {
        clearModifyDestinationSession();
        Taro.removeStorageSync(MODIFY_DESTINATION_OVERRIDE_KEY);
        setOverrideEndSite(null);
src/robotaxi/pages/begin-route/hooks/use-order-actions.ts
@@ -3,10 +3,11 @@
import { saveOrder, getOrderPayment, cancelOrder, endOrder } from '../../../api/begin-route';
import { checkOtp } from '../../../../utils/tools';
import { jumpTo } from '../../../../utils/jump';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  /** 行程状态 */
  tripStatus: number;
  tripStatus: TripStatus;
  /** 订单 ID */
  orderId: string;
  /** 结束点 */
@@ -26,7 +27,7 @@
  /** 取消类型 */
  cancelType: number;
  /** 状态设置 */
  setTripStatus: (value: number) => void;
  setTripStatus: (value: TripStatus) => void;
  /** 订单设置 */
  setOrderId: (value: string) => void;
  /** 下单提交设置 */
@@ -175,7 +176,7 @@
        return;
      }
      deps.setOrderId(nextOrderId);
      deps.setTripStatus(2);
      deps.setTripStatus(TripStatus.Publishing);
      deps.bindSocketListeners();
    } catch (error) {
      stopCallSubmitting();
@@ -198,7 +199,7 @@
   */
  const restartPlanningRoute = () => {
    deps.setShowCancelPopup(false);
    deps.setTripStatus(1);
    deps.setTripStatus(TripStatus.PublishRoute);
    deps.routeUpdateWalkingRouteForStart(deps.startSite);
    const s = Taro.getStorageSync('startSite');
    const e = Taro.getStorageSync('endSite');
@@ -221,7 +222,7 @@
        await cancelOrder(targetOrderId);
        Taro.showToast({ title: '行程已取消', icon: 'success' });
        clearActiveOrderRuntime();
        deps.setTripStatus(10);
        deps.setTripStatus(TripStatus.CancelOnlyPickup);
        deps.setCancelType(4);
        return;
      } catch (error) {
@@ -244,15 +245,15 @@
  /**
   * 根据状态打开取消弹窗。
   */
  const handleOpenCancelPopupByTripStatus = (status: number, durationDisplay?: any) => {
  const handleOpenCancelPopupByTripStatus = (status: TripStatus, durationDisplay?: any) => {
    if (durationDisplay && ![3, 5].includes(status)) {
      deps.setCancelType(2);
      deps.setShowCancelPopup(true);
      return;
    }
    if ([2, 3].includes(status)) deps.setCancelType(1);
    if (status == 4) deps.setCancelType(2);
    if (status == 5) deps.setCancelType(3);
    if ([TripStatus.Publishing, TripStatus.CallingCar].includes(status)) deps.setCancelType(1);
    if (status === TripStatus.VehicleApproaching) deps.setCancelType(2);
    if (status === TripStatus.VehicleArrivedPickup) deps.setCancelType(3);
    deps.setShowCancelPopup(true);
  };
@@ -261,7 +262,7 @@
   */
  const handleConfirmOverRoute = () => {
    deps.setShowOverRoutePopup(false);
    deps.setTripStatus(8);
    deps.setTripStatus(TripStatus.TripFinished);
  };
  /**
@@ -274,7 +275,7 @@
      await endOrder(targetOrderId);
    } catch (error) {
    }
    deps.setTripStatus(8);
    deps.setTripStatus(TripStatus.TripFinished);
  };
  /**
@@ -289,8 +290,8 @@
    try {
      await getOrderPayment({ orderId: targetOrderId });
      Taro.showToast({ title: '支付成功', icon: 'none' });
      deps.setTripStatus(9);
      Taro.setStorageSync('tripStatus', 9);
      deps.setTripStatus(TripStatus.PaymentSuccess);
      Taro.setStorageSync('tripStatus', TripStatus.PaymentSuccess);
    } catch (error) {
      Taro.showToast({ title: '支付失败,请重试', icon: 'none' });
      return;
src/robotaxi/pages/begin-route/hooks/use-page-actions.ts
@@ -1,8 +1,9 @@
import Taro from '@tarojs/taro';
import { clearOrderTripStorage } from '../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  tripStatus: number;
  tripStatus: TripStatus;
  startSite: any;
  getCheckedEnter: () => boolean;
  setCheckedEnter: (checked: boolean) => void;
@@ -52,7 +53,7 @@
   * 返回首页并清理必要缓存。
   */
  const toBack = () => {
    if (deps.tripStatus == 9 || deps.tripStatus == 1) {
    if (deps.tripStatus === TripStatus.PaymentSuccess || deps.tripStatus === TripStatus.PublishRoute) {
      clearOrderTripStorage();
      Taro.removeStorageSync('endSite');
    }
@@ -78,7 +79,7 @@
      deps.setCheckedEnter(false);
      return;
    }
    if (deps.tripStatus === 1) {
    if (deps.tripStatus === TripStatus.PublishRoute) {
      const markerId = e.detail.markerId;
      toSelectSite(markerId === 1 ? 'start' : 'end');
    }
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
@@ -3,13 +3,14 @@
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';
type Deps = {
  wsHost: string;
  ensureSocketListeners: (wsHost: string) => void;
  setOrderId: (value: string) => void;
  setVehicleInfo: (value: any) => void;
  setTripStatus: (value: number) => void;
  setTripStatus: (value: TripStatus) => void;
  setStartSite: (value: any) => void;
  setEndSite: (value: any) => void;
  setTrunkStatus: (value: boolean) => void;
@@ -17,9 +18,9 @@
  setLongitude: (value: number | null) => void;
  getLocationInfo: () => void;
  routeRequestRouteAndFare: (originLat: number, originLng: number, destLat: number, destLng: number, force?: boolean) => void;
  routeRequestOrderRoute: (orderId: string, status: number) => void;
  routeRequestOrderRoute: (orderId: string, status: TripStatus) => void;
  routeUpdateWalkingRouteForStart: (startSite: any) => void;
  getCurrentTripStatus: () => number;
  getCurrentTripStatus: () => TripStatus;
  getCurrentLongitude: () => number | null;
  getCurrentLatitude: () => number | null;
};
@@ -70,7 +71,7 @@
          electronicAreaId: res.xpElectronicFenceAreaId || '',
          time: res.endParkingTimeLimit || 0,
        };
        const nextStatus = mapOrderStatusToTripStatus(res.status || 1);
        const nextStatus = toTripStatus(mapOrderStatusToTripStatus(res.status || 1));
        Taro.setStorageSync('tripStatus', nextStatus);
        deps.setTripStatus(nextStatus);
        Taro.setStorageSync('startSite', nextStartSite);
@@ -84,9 +85,9 @@
  /**
   * 同步地图定位并按状态初始化路线。
   */
  const syncLocationAndInitRoute = (s: any, e: any, cachedTripStatus: number) => {
    // 状态 7 直接以下车点为中心,避免 getLocationInfo 抢占视口中心
    if (cachedTripStatus === 7) {
  const syncLocationAndInitRoute = (s: any, e: any, cachedTripStatus: TripStatus) => {
    // 到达目的地阶段直接以下车点为中心,避免 getLocationInfo 抢占视口中心
    if (cachedTripStatus === TripStatus.ArrivedDestination) {
      const dropoffLat = Number(e?.latitude);
      const dropoffLng = Number(e?.longitude);
      if (Number.isFinite(dropoffLat) && Number.isFinite(dropoffLng)) {
@@ -101,10 +102,10 @@
    }
    const originLng = s?.longitude || deps.getCurrentLongitude();
    const originLat = s?.latitude || deps.getCurrentLatitude();
    if (cachedTripStatus === 1 && originLng && originLat && e?.longitude && e?.latitude) {
    if (cachedTripStatus === TripStatus.PublishRoute && originLng && originLat && e?.longitude && e?.latitude) {
      deps.routeRequestRouteAndFare(originLat, originLng, e.latitude, e.longitude, true);
    }
    if (deps.getCurrentTripStatus() === 1 && s?.latitude && s?.longitude) {
    if (deps.getCurrentTripStatus() === TripStatus.PublishRoute && s?.latitude && s?.longitude) {
      deps.routeUpdateWalkingRouteForStart(s);
    }
  };
@@ -127,11 +128,11 @@
    const e = Taro.getStorageSync('endSite');
    const cachedOrderId = Taro.getStorageSync('orderId');
    const cachedTripStatusStorage = Taro.getStorageSync('tripStatus');
    const cachedTripStatus = Number(cachedTripStatusStorage) || deps.getCurrentTripStatus();
    const cachedTripStatus = toTripStatus(cachedTripStatusStorage, deps.getCurrentTripStatus());
    deps.setTripStatus(cachedTripStatus);
    syncCachedOrderVehicle(cachedOrderId);
    syncSitesFromActiveOrder(cachedTripStatusStorage, s, e);
    if (cachedOrderId && [4, 5, 6, 7, 8, 9].includes(cachedTripStatus)) {
    if (cachedOrderId && [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.ArrivedDestination, TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(cachedTripStatus)) {
      deps.routeRequestOrderRoute(cachedOrderId, cachedTripStatus);
    }
    syncLocationAndInitRoute(s, e, cachedTripStatus);
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
@@ -4,11 +4,12 @@
import { queryOrderRoute, queryOrderTrajectory, queryRouteAndFare } from '../../../api/begin-route';
import { normalizeDistanceKm, normalizeDurationSeconds } from '../../../utils/format';
import { ensureSelectedCityByLocation } from '../../../utils/city';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  isAndroid: boolean;
  tripStatus: number;
  tripStatusRef: React.MutableRefObject<number>;
  tripStatus: TripStatus;
  tripStatusRef: React.MutableRefObject<TripStatus>;
  statusRule: any;
  shouldShowRoute: boolean;
  allowSelectableRoutes: boolean;
@@ -24,8 +25,8 @@
const defaultDeps: Deps = {
  isAndroid: false,
  tripStatus: 1,
  tripStatusRef: { current: 1 },
  tripStatus: TripStatus.PublishRoute,
  tripStatusRef: { current: TripStatus.PublishRoute },
  statusRule: { showWalking: false },
  shouldShowRoute: false,
  allowSelectableRoutes: false,
@@ -221,7 +222,7 @@
        newEndLng = routeData.destination.longitude ?? newEndLng;
    }
    if (deps.tripStatusRef.current === 1 && Number.isFinite(newStartLat) && Number.isFinite(newStartLng) && typeof deps.setStartSite === 'function') {
    if (deps.tripStatusRef.current === TripStatus.PublishRoute && Number.isFinite(newStartLat) && Number.isFinite(newStartLng) && typeof deps.setStartSite === 'function') {
        deps.setStartSite({
           ...deps.startSite,
           latitude: newStartLat,
@@ -230,7 +231,7 @@
        });
    }
    if (deps.tripStatusRef.current === 1 && Number.isFinite(newEndLat) && Number.isFinite(newEndLng) && typeof deps.setEndSite === 'function') {
    if (deps.tripStatusRef.current === TripStatus.PublishRoute && Number.isFinite(newEndLat) && Number.isFinite(newEndLng) && typeof deps.setEndSite === 'function') {
        deps.setEndSite({
           ...deps.endSite,
           latitude: newEndLat,
@@ -252,7 +253,7 @@
      applyRoutePoints(points);
      // 如果允许选择路线(当前业务下未发单及修改目的地确认态时 allowSelectableRoutes 为 true)
      if (deps.allowSelectableRoutes) {
        // 在 tripStatus === 1 或 修改目的地确认态 时,我们需要包含所有路线的点,以便自适应视野
        // 在发布行程阶段或修改目的地确认态时,需要包含所有路线点以便自适应视野
        const allRoutesPoints: any[] = [];
        allRoutePoints.forEach(pts => {
           if (Array.isArray(pts)) {
@@ -446,9 +447,9 @@
  };
  // 请求订单实时路线/轨迹
  const requestOrderRoute = (targetOrderId: string, status: number) => {
    const routeType = status <= 5 ? 1 : 2;
    const requestRoute = [8, 9].includes(status) ? queryOrderTrajectory : queryOrderRoute;
  const requestOrderRoute = (targetOrderId: string, status: TripStatus) => {
    const routeType = status <= TripStatus.VehicleArrivedPickup ? 1 : 2;
    const requestRoute = [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(status) ? queryOrderTrajectory : queryOrderRoute;
    requestRoute({ orderId: targetOrderId, routeType })
      .then((res: any) => {
        const rawPoints = Array.isArray(res?.polyline) ? res.polyline : [];
@@ -459,22 +460,22 @@
          }))
          .filter((item: any) => Number.isFinite(item.longitude) && Number.isFinite(item.latitude));
        if (points.length > 0) {
          if (status === 5) {
          if (status === TripStatus.VehicleArrivedPickup) {
            const last = points[points.length - 1];
            deps.updateVehicleLocationThrottled(last.latitude, last.longitude, true);
          } else {
            applyRoutePoints(points);
            const targetPoint = status === 7
            const targetPoint = status === TripStatus.ArrivedDestination
              ? points[points.length - 1]
              : points[0];
            deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
            if ([4, 6].includes(status)) {
            if ([TripStatus.VehicleApproaching, TripStatus.InService].includes(status)) {
              currentFullRouteRef.current = [...points];
              lastErasedIndexRef.current = 0;
              orderRouteInitializedRef.current = true;
            }
          }
        } else if (status === 7) {
        } else if (status === TripStatus.ArrivedDestination) {
          const cachedEndSite = Taro.getStorageSync('endSite') || {};
          const fallbackLat = Number(cachedEndSite?.latitude);
          const fallbackLng = Number(cachedEndSite?.longitude);
@@ -528,7 +529,7 @@
  // 获取当前用于渲染的路线点
  const getDisplayRoutePoints = () => {
    const isEraseMode = [4, 6].includes(deps.tripStatus);
    const isEraseMode = [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus);
    return isEraseMode ? routePoints : reducePoints(routePoints, deps.isAndroid ? 120 : 200);
  };
@@ -626,7 +627,7 @@
      lastPolylineUpdateAtRef.current = Date.now();
      return;
    }
    if (![8, 9].includes(deps.tripStatus)) {
    if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus)) {
      setPolyline([]);
    }
    lastPolylineUpdateAtRef.current = 0;
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
@@ -2,6 +2,7 @@
import { useEffect } from 'react';
import { preloadImageToTemp } from '../../../../utils/Robotaxi-public-method';
import { getOrderDetail } from '../../../api/order';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  /** 等待计时开始 key */
@@ -11,13 +12,13 @@
  /** 重新报价过期秒数 */
  requoteExpireSeconds: number;
  /** 行程状态 */
  tripStatus: number;
  tripStatus: TripStatus;
  /** 行程状态引用 */
  tripStatusRef: React.MutableRefObject<number>;
  tripStatusRef: React.MutableRefObject<TripStatus>;
  /** 上一行程状态引用 */
  lastTripStatusRef: React.MutableRefObject<number>;
  lastTripStatusRef: React.MutableRefObject<TripStatus>;
  /** 行程同步引用 */
  tripStatusForMapSyncRef: React.MutableRefObject<number>;
  tripStatusForMapSyncRef: React.MutableRefObject<TripStatus>;
  /** 重新报价触发引用 */
  requoteTriggeredRef: React.MutableRefObject<boolean>;
  /** 路线是否初始化 */
@@ -55,7 +56,7 @@
  /** 设置 polyline */
  setPolyline: (value: any[]) => void;
  /** 行程状态设置 */
  setTripStatus: (value: number) => void;
  setTripStatus: (value: TripStatus) => void;
  /** 订单状态重置版本 */
  setResetDetailVersion: (updater: (v: number) => number) => void;
  /** 是否提交中 */
@@ -203,14 +204,14 @@
  /**
   * 刷新完成态轨迹。
   */
  const refreshOrderDetailRoute = (prevTripStatus: number) => {
    const shouldSkipOrderDetailRefresh = prevTripStatus === 8 && deps.tripStatus === 9;
  const refreshOrderDetailRoute = (prevTripStatus: TripStatus) => {
    const shouldSkipOrderDetailRefresh = prevTripStatus === TripStatus.TripFinished && deps.tripStatus === TripStatus.PaymentSuccess;
    if (shouldSkipOrderDetailRefresh) return;
    deps.orderDetailRouteRequestIdRef.current += 1;
    const currentRequestId = deps.orderDetailRouteRequestIdRef.current;
    getOrderDetail(Taro.getStorageSync('orderId')).then((res: any) => {
      if (currentRequestId !== deps.orderDetailRouteRequestIdRef.current) return;
      if (![8, 9].includes(deps.tripStatusRef.current)) return;
      if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatusRef.current)) return;
      const mapCtx = Taro.createMapContext('map');
      if (!res.trajectories) {
        mapCtx.moveToLocation({ latitude: 39.916527, longitude: 116.397128 });
@@ -229,10 +230,10 @@
  };
  useEffect(() => {
    const wasHold = [4, 6].includes(deps.lastTripStatusRef.current);
    const isHold = [4, 6].includes(deps.tripStatus);
    if (deps.tripStatus != 5) Taro.removeStorageSync('pickupEndTime');
    if (deps.tripStatus != 7) Taro.removeStorageSync('finishEndTime');
    const wasHold = [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.lastTripStatusRef.current);
    const isHold = [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus);
    if (deps.tripStatus !== TripStatus.VehicleArrivedPickup) Taro.removeStorageSync('pickupEndTime');
    if (deps.tripStatus !== TripStatus.ArrivedDestination) Taro.removeStorageSync('finishEndTime');
    if (!wasHold && isHold) {
      deps.orderRouteInitializedRef.current = false;
      deps.tripStatus6ViewportLockedRef.current = false;
@@ -251,8 +252,8 @@
  }, [deps.vehicleInfo?.id]);
  useEffect(() => {
    if (deps.tripStatus === 1) checkAndShowPassengerNotice();
    if (![2, 3].includes(deps.tripStatus)) return;
    if (deps.tripStatus === TripStatus.PublishRoute) checkAndShowPassengerNotice();
    if (![TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus)) return;
    let cancelled = false;
    const load = async (url: string, setter: (value: string) => void) => {
      const local = await preloadImageToTemp(url);
@@ -281,15 +282,15 @@
    const prevTripStatus = deps.tripStatusForMapSyncRef.current;
    let timer: any = null;
    let requoteTimer: any = null;
    if (deps.tripStatus == 1) {
    if (deps.tripStatus === TripStatus.PublishRoute) {
      deps.setCallSubmitting(false);
      requoteTimer = startRequoteTimer();
    } else if (deps.tripStatus == 2 || deps.tripStatus == 3) {
    } else if (deps.tripStatus === TripStatus.Publishing || deps.tripStatus === TripStatus.CallingCar) {
      deps.requoteTriggeredRef.current = false;
      deps.setWalkingPoints([]);
      deps.setWalkingTime(0);
      timer = startWaitingTimer();
    } else if ([4, 5].includes(deps.tripStatus)) {
    } else if ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(deps.tripStatus)) {
      deps.requoteTriggeredRef.current = false;
      const cachedStartSite = Taro.getStorageSync('startSite') || {};
      if (cachedStartSite?.latitude && cachedStartSite?.longitude) {
@@ -300,7 +301,7 @@
      }
      deps.setElapsedSeconds(0);
      clearWaitingTimerStorage();
    } else if (deps.tripStatus == 7) {
    } else if (deps.tripStatus === TripStatus.ArrivedDestination) {
      deps.requoteTriggeredRef.current = false;
      deps.setWalkingPoints([]);
      deps.setWalkingTime(0);
@@ -311,7 +312,7 @@
      }
      deps.setElapsedSeconds(0);
      clearWaitingTimerStorage();
    } else if ([8, 9].includes(deps.tripStatus)) {
    } else if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus)) {
      deps.requoteTriggeredRef.current = false;
      deps.setWalkingPoints([]);
      deps.setWalkingTime(0);
@@ -335,7 +336,7 @@
  useDidHide(() => {
    deps.setResetDetailVersion((v: number) => v + 1);
    const currentTripStatus = deps.tripStatusRef.current;
    if (currentTripStatus === 2 || currentTripStatus === 3) {
    if (currentTripStatus === TripStatus.Publishing || currentTripStatus === TripStatus.CallingCar) {
      const startAt = Number(Taro.getStorageSync(deps.waitingTimerStartKey));
      if (Number.isFinite(startAt) && startAt > 0) {
        const currentElapsed = Math.max(0, Math.floor((Date.now() - startAt) / 1000));
@@ -352,10 +353,10 @@
  useEffect(() => {
    const originLng = deps.startSite?.longitude || deps.longitude;
    const originLat = deps.startSite?.latitude || deps.latitude;
    if (deps.tripStatus === 1 && originLng && originLat && deps.endSite?.longitude && deps.endSite?.latitude) {
    if (deps.tripStatus === TripStatus.PublishRoute && originLng && originLat && deps.endSite?.longitude && deps.endSite?.latitude) {
      deps.routeRequestRouteAndFare(originLat, originLng, deps.endSite.latitude, deps.endSite.longitude);
    }
    if (deps.tripStatus === 1 && deps.startSite?.latitude && deps.startSite?.longitude) {
    if (deps.tripStatus === TripStatus.PublishRoute && deps.startSite?.latitude && deps.startSite?.longitude) {
      deps.routeUpdateWalkingRouteForStart(deps.startSite);
    }
  }, [deps.startSite?.longitude, deps.startSite?.latitude, deps.endSite?.longitude, deps.endSite?.latitude, deps.longitude, deps.latitude]);
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -5,12 +5,13 @@
import CallingCar from '../components/calling-car';
import ModifyDestinationReview from '../components/modify-destination-review';
import { formatDurationDisplay } from '../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../constants/trip-status';
type Deps = {
  /** 是否安卓 */
  isAndroid: boolean;
  /** 行程状态 */
  tripStatus: number;
  tripStatus: TripStatus;
  /** 地图缩放等级 */
  mapScale: number;
  /** 地图 markers */
@@ -56,11 +57,11 @@
  /** 发单禁用 */
  isCallDisabled: boolean;
  /** 打开取消弹窗 */
  handleOpenCancelPopupByTripStatus: (status: number, durationDisplay?: any) => void;
  handleOpenCancelPopupByTripStatus: (status: TripStatus, durationDisplay?: any) => void;
  /** 取消超时 */
  handleOpenCancelPopupWithType: (type: number) => void;
  /** 行程状态设置 */
  setTripStatus: (value: number) => void;
  setTripStatus: (value: TripStatus) => void;
  /** 完成倒计时启用 */
  finishCountdownEnabled: boolean;
  /** 订单 ID */
@@ -176,14 +177,14 @@
        ) : null
      ) : (
        <>
          {(item.name !== '车辆位置' && !(item.id === 2 && [7].includes(deps.tripStatus))) && (
          {(item.name !== '车辆位置' && !(item.id === 2 && [TripStatus.ArrivedDestination].includes(deps.tripStatus))) && (
            <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 == 1 || deps.showTrip1StyleEndpoint) && <CoverImage className='custom-callout-icon' src={deps.customCalloutIconSrc} />}
              {(deps.tripStatus === TripStatus.PublishRoute || deps.showTrip1StyleEndpoint) && <CoverImage className='custom-callout-icon' src={deps.customCalloutIconSrc} />}
            </CoverView>
          )}
          {(item.name === '车辆位置' && [4, 6].includes(deps.tripStatus) && !deps.isAndroid) && (
          {(item.name === '车辆位置' && [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) && !deps.isAndroid) && (
            <CoverView className='custom-callout-content' style={{ padding: '6px 10px', minWidth: '40px', justifyContent: 'center', zIndex: 2 }}>
              <CoverView className='car-time'>
                <CoverView className='car-time-num'>{deps.routeDistance?.value ?? 0}</CoverView>
@@ -193,7 +194,7 @@
              </CoverView>
            </CoverView>
          )}
          {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid && deps.arrivedTooltipReady) && (
          {(item.id === 2 && [TripStatus.ArrivedDestination].includes(deps.tripStatus) && !deps.isAndroid && deps.arrivedTooltipReady) && (
            <CoverView className='custom-callout-content'>
              已到达
            </CoverView>
@@ -209,9 +210,9 @@
  const renderMarkerCallout = (item: any, index: number) => {
    const calloutKey = `callout-${item.id}-${item.name || ''}`;
    if (item.isRouteSummaryBubble) return renderDefaultCallout(item, calloutKey);
    const isWaiting = [2, 3].includes(deps.tripStatus) && index === 0;
    const isWaiting = [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus) && index === 0;
    if (isWaiting) return renderWaitingCallout(item, calloutKey);
    if (item.name === '车辆位置' && deps.isAndroid && [4, 6].includes(deps.tripStatus)) return null;
    if (item.name === '车辆位置' && deps.isAndroid && [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus)) return null;
    return renderDefaultCallout(item, calloutKey);
  };
@@ -220,7 +221,7 @@
   */
  const renderMapOverlays = () => (
    <>
      {!(deps.isAndroid && [1, 5, 8, 9, 10].includes(deps.tripStatus)) && deps.markers.map(renderMarkerCallout)}
      {!(deps.isAndroid && [TripStatus.PublishRoute, TripStatus.VehicleArrivedPickup, TripStatus.TripFinished, TripStatus.PaymentSuccess, TripStatus.CancelOnlyPickup].includes(deps.tripStatus)) && deps.markers.map(renderMarkerCallout)}
      {deps.shouldShowWaitingRadar && deps.waitingRadarVisible && (
        <Image className="waiting-radar-img" src={deps.waitingRadarSrc} webp={true} style={deps.waitingRadarStyle} />
      )}
@@ -250,7 +251,7 @@
   * 渲染底部行程卡片。
   */
  const renderTripCard = () => {
    if (deps.tripStatus == 1 && deps.startSite && deps.endSite) {
    if (deps.tripStatus === TripStatus.PublishRoute && deps.startSite && deps.endSite) {
      return (
        <ReleaseRoute
          startSite={deps.startSite}
@@ -284,18 +285,18 @@
        />
      );
    }
    if (deps.tripStatus == 2) {
    if (deps.tripStatus === TripStatus.Publishing) {
      return (
        <ReleaseBeBeing
          handleOpenCancelPopup={() => deps.handleOpenCancelPopupByTripStatus(2)}
          handleOpenCancelPopup={() => deps.handleOpenCancelPopupByTripStatus(TripStatus.Publishing)}
          onFinish={() => {
            deps.setTripStatus(3);
            Taro.setStorageSync('tripStatus', 3);
            deps.setTripStatus(TripStatus.CallingCar);
            Taro.setStorageSync('tripStatus', TripStatus.CallingCar);
          }}
        />
      );
    }
    if (deps.tripStatus >= 3 && deps.tripStatus !== 10) {
    if (deps.tripStatus >= TripStatus.CallingCar && deps.tripStatus !== TripStatus.CancelOnlyPickup) {
      return (
        <CallingCar
          key={deps.tripStatus}
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -4,12 +4,13 @@
import { queryVehicleByOrderId } from '../../../api/begin-route';
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: any;
    tripStatusRef: React.MutableRefObject<TripStatus>;
    orderId: string;
    setOrderId: (orderId: string) => void;
    setTripStatus: (status: number) => void;
    setTripStatus: (status: TripStatus) => void;
    setVehicleInfo: (vehicle: any) => void;
    vehicleInfo: any;
    setPriceUpdateVersion: (updater: (v: number) => number) => void;
@@ -62,13 +63,14 @@
};
const syncTripStatus = (payload: any, deps: UseWebSocketEventsDeps) => {
    const mappedTripStatus = payload?.tripStatus;
    if (typeof mappedTripStatus !== 'number') {
    const incomingTripStatus = Number(payload?.tripStatus);
    if (!Number.isFinite(incomingTripStatus)) {
        return null;
    }
    const currentTripStatus = Number(deps.tripStatusRef.current);
    const currentTripStatus = deps.tripStatusRef.current;
    const mappedTripStatus = toTripStatus(incomingTripStatus, currentTripStatus);
    // 行程进行关键态不允许被回退,避免 4/6 被延迟状态覆盖导致路线链路重置
    if ([4, 6].includes(currentTripStatus) && mappedTripStatus < currentTripStatus) {
    if ([TripStatus.VehicleApproaching, TripStatus.InService].includes(currentTripStatus) && mappedTripStatus < currentTripStatus) {
        return currentTripStatus;
    }
    if (currentTripStatus === mappedTripStatus) {
@@ -105,9 +107,9 @@
        .catch(() => { });
};
const handleCancelPopupPush = (payload: any, changeStatusOld: number, deps: UseWebSocketEventsDeps) => {
const handleCancelPopupPush = (payload: any, changeStatusOld: TripStatus, deps: UseWebSocketEventsDeps) => {
    if (!payload?.showCancelPopup) return;
    if (changeStatusOld === 4 || changeStatusOld === 5) {
    if (changeStatusOld === TripStatus.VehicleApproaching || changeStatusOld === TripStatus.VehicleArrivedPickup) {
        deps.setOrderId('');
        clearOrderTripStorage();
        Taro.removeStorageSync('pickupEndTime');
@@ -117,18 +119,18 @@
    } else {
        deps.setCancelType(5);
    }
    deps.setTripStatus(10);
    deps.setTripStatus(TripStatus.CancelOnlyPickup);
    deps.setShowCancelPopup(true);
};
const handleStatusSideEffects = (mappedTripStatus: number | null, changeStatusOld: number, deps: UseWebSocketEventsDeps) => {
    if (mappedTripStatus === 6) {
const handleStatusSideEffects = (mappedTripStatus: TripStatus | null, changeStatusOld: TripStatus, deps: UseWebSocketEventsDeps) => {
    if (mappedTripStatus === TripStatus.InService) {
        deps.setShowAnnouncementsPopup(false);
    }
    if (mappedTripStatus === 5 && changeStatusOld !== 5) {
    if (mappedTripStatus === TripStatus.VehicleArrivedPickup && changeStatusOld !== TripStatus.VehicleArrivedPickup) {
        resetRouteRuntimeState(deps);
    }
    if (mappedTripStatus === 4 && changeStatusOld !== 4) {
    if (mappedTripStatus === TripStatus.VehicleApproaching && changeStatusOld !== TripStatus.VehicleApproaching) {
        resetRouteRuntimeState(deps);
    }
};
@@ -199,8 +201,8 @@
    if (!targetPoint) {
        return;
    }
    const currentTripStatus = Number(deps.tripStatusRef.current);
    if (currentTripStatus === 7) {
    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);
@@ -233,7 +235,7 @@
    Taro.nextTick(() => {
        const updated = deps.applyRoutePoints(points);
        if (!updated) return;
        if (![4, 5, 6, 7].includes(deps.tripStatusRef.current)) return;
        if (![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.ArrivedDestination].includes(deps.tripStatusRef.current)) return;
        syncVehiclePointWithRoute(points, deps, servedPoints);
    });
};
@@ -264,20 +266,20 @@
    const points = payload?.points || [];
    const servedPoints = Array.isArray(payload?.servedPoints) ? payload.servedPoints : [];
    const forceFullRedraw = payload?.forceFullRedraw === true;
    const currentTripStatus = Number(deps.tripStatusRef.current);
    const currentTripStatus = deps.tripStatusRef.current;
    updateRouteSummary(payload, deps);
    // 行程结束态仅更新概要信息,不再消费路线点,避免视口反复重算造成跳动
    if ([8, 9].includes(currentTripStatus)) {
    if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(currentTripStatus)) {
        return;
    }
    if ([4, 6].includes(currentTripStatus)) {
    if ([TripStatus.VehicleApproaching, TripStatus.InService].includes(currentTripStatus)) {
        deps.setVehicleMoveAlongPoints(servedPoints);
    }
    if (forceFullRedraw) {
        handleForceFullRouteRedraw(points, deps, servedPoints);
        return;
    }
    const isEraseStatus = ![7, 8, 9].includes(currentTripStatus);
    const isEraseStatus = ![TripStatus.ArrivedDestination, TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(currentTripStatus);
    if (isEraseStatus) {
        handleEraseRouteStatus(points, servedPoints, deps);
        return;
src/robotaxi/pages/begin-route/index.tsx
@@ -36,6 +36,7 @@
import useBluetoothAccess from './hooks/use-bluetooth-access';
// 蓝牙车控连接与鉴权编排
import useBluetoothCarControl from './hooks/use-bluetooth-car-control';
import { toTripStatus, TripStatus } from './constants/trip-status';
import backIcon from '../../../robotaxi/assets/back-icon.png'
import personalCenter from '../../../robotaxi/assets/personal-center.png'
@@ -48,6 +49,15 @@
import { _ROBOTAXI_WS_HOST } from '../../../constants/api';
const ROUTE_SUMMARY_MARKER_ID = 999001;
type StatusRule = {
  showPickup: boolean;
  showDropoff: boolean;
  showCar: boolean;
  showRoute: boolean;
  showWalking: boolean;
  viewport: 'route_walking' | 'pickup' | 'route' | 'car_user' | 'order_detail';
  translateCar: boolean;
};
export default function BeginRoute() {
  const WAITING_TIMER_START_KEY = 'waitingTimerStartAt'; // 等待计时开始时间
@@ -88,7 +98,7 @@
  const [latitude, setLatitude] = useState<number | null>(null); // 纬度
  const [startSite, setStartSite] = useState<any>(null); // 上车点
  const [endSite, setEndSite] = useState<any>({}); // 下车点
  const [tripStatus, setTripStatus] = useState<number>(Number(Taro.getStorageSync('tripStatus')) || 1); // 行程状态 1:发布行程 2:发布中 3:呼叫车中 4:车辆即将到达 5:车辆到达上车点 6:车辆行驶中 7:到达目的地 8:行程已结束 9:支付成功 10:取消订单时页面只展示一个上车点
  const [tripStatus, setTripStatus] = useState<TripStatus>(toTripStatus(Taro.getStorageSync('tripStatus'))); // 当前行程状态
  const [elapsedSeconds, setElapsedSeconds] = useState(0); // 等待计时秒数
  const requoteTriggeredRef = useRef(false); // 重新报价触发标记,防止重复触发
  const [callSubmitting, setCallSubmitting] = useState(false); // 呼叫车辆提交中状态
@@ -115,17 +125,17 @@
    Taro.setStorageSync('tripStatus', tripStatus);
  }, [tripStatus]);
  // 行程状态显示规则配置:showPickup-显示上车点,showDropoff-显示下车点,showCar-显示车辆,showRoute-显示路线,showWalking-显示步行路线,viewport-视口类型,translateCar-车辆是否平移
  const STATUS_RULES: Record<number, any> = {
    1: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: true, viewport: 'route_walking', translateCar: false },
    2: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    3: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    4: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: true, viewport: 'route', translateCar: true },
    5: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: true, viewport: 'car_user', translateCar: true },
    6: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
    7: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
    8: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
    9: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
    10: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
  const STATUS_RULES: Record<TripStatus, StatusRule> = {
    [TripStatus.PublishRoute]: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: true, viewport: 'route_walking', translateCar: false },
    [TripStatus.Publishing]: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    [TripStatus.CallingCar]: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    [TripStatus.VehicleApproaching]: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: true, viewport: 'route', translateCar: true },
    [TripStatus.VehicleArrivedPickup]: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: true, viewport: 'car_user', translateCar: true },
    [TripStatus.InService]: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
    [TripStatus.ArrivedDestination]: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
    [TripStatus.TripFinished]: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
    [TripStatus.PaymentSuccess]: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
    [TripStatus.CancelOnlyPickup]: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
  };
  const orderDetailRouteRequestIdRef = useRef(0); // 订单详情路线请求ID
  const isCheckedEnter = useRef(false); // 是否已检查页面进入状态
@@ -137,8 +147,8 @@
      return false;
    }
  }, []);
  const shouldShowWaitingRadar = isAndroid && [2, 3].includes(tripStatus); // 是否显示等待雷达动画(Android且状态2/3)
  const vehicleMarkerId = !isAndroid && tripStatus === 6 ? 30 : 3; // 车辆标记ID,iOS状态6使用不同ID
  const shouldShowWaitingRadar = isAndroid && [TripStatus.Publishing, TripStatus.CallingCar].includes(tripStatus); // 是否显示等待雷达动画(Android 且处于发布中/呼叫车中)
  const vehicleMarkerId = !isAndroid && tripStatus === TripStatus.InService ? 30 : 3; // 车辆标记 ID,iOS 行驶中阶段使用不同 ID
  const {
    openService,
    toCoupon,
@@ -184,7 +194,7 @@
    }
  });
  const statusRule = useMemo(() => {
    const baseRule = STATUS_RULES[tripStatus] || STATUS_RULES[1];
    const baseRule = STATUS_RULES[tripStatus] || STATUS_RULES[TripStatus.PublishRoute];
    if (isModifyDestinationReviewVisible || hasLocalDestinationOverride) {
      return {
        ...baseRule,
@@ -199,7 +209,7 @@
    return baseRule;
  }, [tripStatus, isModifyDestinationReviewVisible, hasLocalDestinationOverride]);
  const shouldShowRoute = statusRule.showRoute; // 当前状态是否显示路线
  const allowSelectableRoutes = tripStatus === 1 || isModifyDestinationReviewVisible || hasLocalDestinationOverride;
  const allowSelectableRoutes = tripStatus === TripStatus.PublishRoute || isModifyDestinationReviewVisible || hasLocalDestinationOverride;
  const showTrip1StyleEndpoint = isModifyDestinationReviewVisible || hasLocalDestinationOverride;
  const vehicleState = useVehicleTracking({
    statusRule,
@@ -330,8 +340,8 @@
    toOrigin,
    getLocationInfo
  } = viewportState; // 视口状态解构
  const arrivedMarkerReady = tripStatus !== 7 || typeof vehicleDirection === 'number';
  const arrivedTooltipReady = tripStatus !== 7 || (arrivedMarkerReady && mapScale >= 20);
  const arrivedMarkerReady = tripStatus !== TripStatus.ArrivedDestination || typeof vehicleDirection === 'number';
  const arrivedTooltipReady = tripStatus !== TripStatus.ArrivedDestination || (arrivedMarkerReady && mapScale >= 20);
  const markerState = useMapMarkers({
    tripStatus,
    statusRule,
@@ -562,7 +572,7 @@
    const destLat = Number(targetEndSite?.latitude);
    const destLng = Number(targetEndSite?.longitude);
    if (![4, 5, 6].includes(tripStatus) || !Number.isFinite(destLat) || !Number.isFinite(destLng)) {
    if (![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus) || !Number.isFinite(destLat) || !Number.isFinite(destLng)) {
      modifyPreviewRouteKeyRef.current = '';
      return;
    }
@@ -571,11 +581,11 @@
    let originLat = Number(startSite?.latitude ?? cachedStartSite?.latitude);
    let originLng = Number(startSite?.longitude ?? cachedStartSite?.longitude);
    if ([4, 5].includes(tripStatus)) {
    if ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) {
      if (!Number.isFinite(originLat) || !Number.isFinite(originLng)) {
        return;
      }
    } else if (tripStatus === 6 && Number.isFinite(Number(vehicleLocation?.latitude)) && Number.isFinite(Number(vehicleLocation?.longitude))) {
    } else if (tripStatus === TripStatus.InService && Number.isFinite(Number(vehicleLocation?.latitude)) && Number.isFinite(Number(vehicleLocation?.longitude))) {
      originLat = Number(vehicleLocation?.latitude);
      originLng = Number(vehicleLocation?.longitude);
    } else {
@@ -730,7 +740,7 @@
  const mapLatitude = latitude ?? 39.916527;
  const canToggleRouteSelect = allowSelectableRoutes && routeOptions.length > 1;
  const shouldShowOrigin = ![7, 8, 9].includes(tripStatus);
  const shouldShowOrigin = ![TripStatus.ArrivedDestination, TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus);
  const handleServiceEntry = () => {
    isCheckedEnter.current = true;
    openService();
@@ -744,7 +754,7 @@
    toOrder();
  };
  const handleWalkNavigation = () => {
    if (![4, 5].includes(tripStatus) || !startSite?.latitude || !startSite?.longitude) {
    if (![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) || !startSite?.latitude || !startSite?.longitude) {
      return;
    }
src/robotaxi/pages/share-trip/components/share-trip-view.tsx
New file
@@ -0,0 +1,197 @@
import { View, Image, Map, CoverView, CoverImage } from '@tarojs/components';
import { formatDurationDisplay, safeParse } from '../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../../begin-route/constants/trip-status';
import homeIcon from '../../../assets/back-home.png';
import shareContactIcon from '../../../assets/share-contact.png';
import contactServiceIcon from '../../../assets/contact-service.png';
import shareLine from '../../../assets/share-line.png';
type Props = {
  isExpired: boolean;
  showReminder: boolean;
  setShowReminder: (value: boolean) => void;
  tripData: any;
  mapLongitude: number;
  mapLatitude: number;
  mapScale: number;
  markers: any[];
  polyline: any[];
  routeDuration: number | null;
  renderMapOverlays: () => React.ReactNode;
  goHome: () => void;
  handleContactService: () => void;
};
/**
 * 分享页视图组件:仅负责渲染,不承载业务副作用。
 */
export default function ShareTripView({
  isExpired,
  showReminder,
  setShowReminder,
  tripData,
  mapLongitude,
  mapLatitude,
  mapScale,
  markers,
  polyline,
  routeDuration,
  renderMapOverlays,
  goHome,
  handleContactService,
}: Props) {
  const durationDisplay = formatDurationDisplay(routeDuration); // 预计耗时展示文案
  if (isExpired) {
    return (
      <View className='share-trip-expired'>
        <View className='expired-content'>
          <Image className='expired-img' src={shareContactIcon} />
          <View className='expired-text'>行程结束已超过24小时</View>
          <View className='expired-sub-text'>查看行程信息,请联系客服</View>
          <View className='expired-btn' onClick={handleContactService}>
            联系客服
          </View>
        </View>
      </View>
    );
  }
  return (
    <View className='home-container'>
      <Map
        id='map'
        longitude={mapLongitude}
        latitude={mapLatitude}
        scale={mapScale}
        markers={markers}
        polyline={polyline}
        style={{ width: '100%', height: '87vh' }}
      >
        {renderMapOverlays()}
        <View onClick={goHome} className='select-city'>
          <Image className='select-city-icon' src={homeIcon} />
        </View>
        <View className='container'>
          <CoverView
            className={`share-contact ${showReminder ? 'has-tip' : ''}`}
            onClick={handleContactService}
          >
            <CoverImage className='share-contact-icon' src={contactServiceIcon} />
          </CoverView>
          {showReminder && (
            <View className='tip-box'>
              <View className='tip-box-content'>
                <View className='tip-box-text'>开启提醒后,亲友行程动态秒通知</View>
                <View className='tip-box-btn' onClick={() => setShowReminder(false)}>
                  去开启
                </View>
              </View>
            </View>
          )}
          <View className='container-inner'>
            <View catchMove onClick={(event) => event.stopPropagation()}>
              <View className='calling-car'>
                <Image
                  className='calling-car-bg'
                  src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg.png'
                />
                <View className='calling-car-head'>
                  {tripData?.tripStatus === TripStatus.VehicleApproaching && (
                    <View className='calling-car-head-title'>
                      车辆还有
                      <View className='calling-car-head-title-eta'>{durationDisplay.value}</View>
                      {durationDisplay.unit}到达上车点
                    </View>
                  )}
                  {tripData?.tripStatus === TripStatus.VehicleArrivedPickup && (
                    <View className='calling-car-head-title'>车辆已到达上车点</View>
                  )}
                  {tripData?.tripStatus === TripStatus.InService && (
                    <View className='calling-car-head-title'>
                      预计
                      <View className='calling-car-head-title-eta'>{durationDisplay.value}</View>
                      {durationDisplay.unit}抵达目的地
                    </View>
                  )}
                  {tripData?.tripStatus > TripStatus.ArrivedDestination && (
                    <View className='calling-car-head-title'>已到达目的地</View>
                  )}
                </View>
                <View className='car-card'>
                  <View className='car-card-detail'>
                    <View>
                      <View className='car-card-detail-num'>{tripData?.plateNumber || '--'}</View>
                      <View className='car-card-detail-color'>
                        {tripData?.vehicleColor || '--'}
                        <View className='car-card-detail-color-line'></View>
                        车身编码: {tripData?.vehicleCode || '--'}
                      </View>
                    </View>
                    {tripData?.vehiclePictures && (
                      <Image
                        className='car-card-detail-img'
                        src={
                          safeParse(tripData?.vehiclePictures, {})?.url
                          || 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/vehicle-default.png'
                        }
                      />
                    )}
                  </View>
                  <View className='car-card-line'></View>
                  <View className='car-card-temp'>
                    <View className='car-card-temp-list'>
                      <Image className='car-card-temp-img' src={shareLine} />
                      <View className='car-card-temp-row'>
                        <View className='car-card-temp-row-left'>
                          <View className='dot start'></View>
                          <View
                            className={`car-card-temp-title ${
                              tripData?.tripStatus === TripStatus.InService
                              || tripData?.tripStatus === TripStatus.ArrivedDestination
                                ? 'disabled-text'
                                : ''
                            }`}
                          >
                            起点:{tripData?.startPointName}
                          </View>
                        </View>
                        {(tripData?.tripStatus === TripStatus.InService
                          || tripData?.tripStatus === TripStatus.ArrivedDestination)
                        && tripData?.boardingTime && (
                          <View className='car-card-temp-time disabled-text'>
                            {tripData.boardingTime} 上车
                          </View>
                        )}
                      </View>
                      <View className='car-card-temp-row' style={{ marginTop: '12px' }}>
                        <View className='car-card-temp-row-left'>
                          <View className='dot end'></View>
                          <View
                            className={`car-card-temp-title ${
                              tripData?.tripStatus === TripStatus.ArrivedDestination
                                ? 'disabled-text'
                                : ''
                            }`}
                          >
                            终点:{tripData?.endPointName}
                          </View>
                        </View>
                        {tripData?.tripStatus === TripStatus.ArrivedDestination
                          && tripData?.getoffTime && (
                          <View className='car-card-temp-time disabled-text'>
                            {tripData.getoffTime} 到达
                          </View>
                        )}
                      </View>
                    </View>
                  </View>
                </View>
              </View>
            </View>
          </View>
        </View>
      </Map>
    </View>
  );
}
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -1,8 +1,9 @@
import Taro from "@tarojs/taro";
import { useEffect, useRef, useState } from "react";
import { TripStatus } from "../../begin-route/constants/trip-status";
type Deps = {
    tripStatus: number;
    tripStatus: TripStatus;
    statusRule: any;
    isAndroid: boolean;
    startSite: any;
@@ -13,7 +14,7 @@
};
const defaultDeps: Deps = {
    tripStatus: 1,
    tripStatus: TripStatus.PublishRoute,
    statusRule: { showPickup: true, showDropoff: true, showCar: false },
    isAndroid: false,
    startSite: null,
@@ -50,7 +51,7 @@
                ? {
                      callout: {
                          content:
                              deps.tripStatus === 1
                              deps.tripStatus === TripStatus.PublishRoute
                                  ? `${pickupSite?.name || "上车点"} >`
                                  : pickupSite?.name || "上车点",
                          color: "#000000",
@@ -64,7 +65,7 @@
                  }
                : {
                      customCallout: {
                          anchorY: [2, 3].includes(deps.tripStatus) ? 150 : -5,
                          anchorY: [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus) ? 150 : -5,
                          anchorX: 0,
                          display: "ALWAYS"
                      }
@@ -78,7 +79,7 @@
    ) => {
        if (!(dropoffSite?.latitude && dropoffSite?.longitude)) return null;
        const dropoffName =
            [7].includes(deps.tripStatus) ? "已到达" : dropoffSite.name || "目的地";
            [TripStatus.ArrivedDestination].includes(deps.tripStatus) ? "已到达" : dropoffSite.name || "目的地";
        return {
            id: 2,
            latitude: dropoffSite.latitude,
@@ -90,11 +91,11 @@
            anchor: { x: 0.5, y: 1 },
            zIndex: 1,
            name: dropoffName,
            ...(useAndroidNativeTrip1Callout || deps.tripStatus == 6
            ...(useAndroidNativeTrip1Callout || deps.tripStatus === TripStatus.InService
                ? {
                      callout: {
                          content:
                              deps.tripStatus === 1
                              deps.tripStatus === TripStatus.PublishRoute
                                  ? `${dropoffName} >`
                                  : dropoffName,
                          color: "#000000",
@@ -148,7 +149,7 @@
        }
        if (
            !deps.isAndroid &&
            [4, 6].includes(deps.tripStatus) &&
            [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) &&
            deps.statusRule.showCar &&
            !!carMarker &&
            !tripStatus6MarkerRefreshedRef.current
@@ -172,7 +173,7 @@
                ? deps.endSite
                : Taro.getStorageSync("endSite");
        const useAndroidNativeTrip1Callout =
            deps.isAndroid && [1, 5, 8, 9, 10].includes(deps.tripStatus);
            deps.isAndroid && [TripStatus.PublishRoute, TripStatus.VehicleArrivedPickup, TripStatus.TripFinished, TripStatus.PaymentSuccess, TripStatus.CancelOnlyPickup].includes(deps.tripStatus);
        const pickupMarker = createPickupMarker(
            pickupSite,
            useAndroidNativeTrip1Callout
@@ -210,12 +211,12 @@
                currentCarMarker.rotate !== carMarker.rotate
        );
        const shouldHoldMarkerByTranslate =
            [4, 6].includes(deps.tripStatus) &&
            [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) &&
            carMarkerInitialized.current &&
            deps.statusRule.showCar &&
            (!deps.statusRule.showDropoff || hasDropoffMarker);
        const shouldHoldMarkerWhenArrived =
            deps.tripStatus === 7 &&
            deps.tripStatus === TripStatus.ArrivedDestination &&
            carMarkerInitialized.current &&
            deps.statusRule.showCar &&
            (!deps.statusRule.showDropoff || hasDropoffMarker);
src/robotaxi/pages/share-trip/hooks/use-map-viewport.ts
@@ -1,9 +1,10 @@
import Taro from "@tarojs/taro";
import { useCallback, useEffect, useRef, useState } from "react";
import { computeAdaptiveBottomPadding } from "../../../../utils/Robotaxi-public-method";
import { TripStatus } from "../../begin-route/constants/trip-status";
type Deps = {
    tripStatus: number;
    tripStatus: TripStatus;
    statusRule: any;
    routePoints: any[];
    walkingPoints: any[];
@@ -15,7 +16,7 @@
};
const defaultDeps: Deps = {
    tripStatus: 1,
    tripStatus: TripStatus.PublishRoute,
    statusRule: { viewport: "pickup" },
    routePoints: [],
    walkingPoints: [],
@@ -107,7 +108,7 @@
    ) => {
        const points: any[] = [];
        const keyParts: any[] = [
            viewportType === "order_detail" ? 8 : deps.tripStatus,
            viewportType === "order_detail" ? TripStatus.TripFinished : deps.tripStatus,
            viewportType
        ];
@@ -250,7 +251,7 @@
        );
        if (points.length === 0) return;
        if (
            deps.tripStatus === 6 &&
            deps.tripStatus === TripStatus.InService &&
            viewportType === "route" &&
            tripStatus6ViewportLockedRef.current
        ) {
@@ -260,7 +261,7 @@
        if (nextKey === viewportKeyRef.current) return;
        viewportKeyRef.current = nextKey;
        updateViewportByPayload(viewportType, points);
        if (deps.tripStatus === 6 && viewportType === "route") {
        if (deps.tripStatus === TripStatus.InService && viewportType === "route") {
            tripStatus6ViewportLockedRef.current = true;
        }
    }, [
src/robotaxi/pages/share-trip/hooks/use-route-manager.ts
@@ -1,15 +1,16 @@
import { useEffect, useRef, useState } from "react";
import { TripStatus } from "../../begin-route/constants/trip-status";
type Deps = {
    isAndroid: boolean;
    tripStatus: number;
    tripStatus: TripStatus;
    statusRule: any;
    shouldShowRoute: boolean;
};
const defaultDeps: Deps = {
    isAndroid: false,
    tripStatus: 1,
    tripStatus: TripStatus.PublishRoute,
    statusRule: { showWalking: false },
    shouldShowRoute: false
};
@@ -50,7 +51,7 @@
    };
    const getDisplayRoutePoints = () => {
        const isTranslateMode = [4, 6].includes(deps.tripStatus);
        const isTranslateMode = [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus);
        return isTranslateMode
            ? routePoints
            : reducePoints(routePoints, deps.isAndroid ? 120 : 200);
@@ -116,7 +117,7 @@
            return;
        }
        if (![8, 9].includes(deps.tripStatus)) {
        if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus)) {
            setPolyline([]);
        }
    }, [
src/robotaxi/pages/share-trip/hooks/use-share-trip-runtime.ts
New file
@@ -0,0 +1,436 @@
import Taro from '@tarojs/taro';
import { useEffect, useRef } from 'react';
import { mapOrderStatusToTripStatus } from '../../../../utils/Robotaxi-public-method';
import { shareQueryOrderInfo, shareQueryOrderRoute } from '../../../api/begin-route';
import { normalizeDistanceKm, normalizeDurationSeconds } from '../../../utils/format';
import { TripStatus } from '../../begin-route/constants/trip-status';
type LocationPoint = { latitude: number; longitude: number };
type RouteDistance = { value: number; unit: 'km' | 'm' } | null;
type Deps = {
  orderId: string;
  tripStatus: TripStatus;
  startSite: any;
  endSite: any;
  setTripStatus: (value: TripStatus) => void;
  setTripData: (value: any) => void;
  setStartSite: (value: any) => void;
  setEndSite: (value: any) => void;
  setIsExpired: (value: boolean) => void;
  setShowReminder: (value: boolean) => void;
  setLongitude: (value: number) => void;
  setLatitude: (value: number) => void;
  setRouteDistance: (value: RouteDistance) => void;
  setRouteDuration: (value: number | null) => void;
  applyRoutePoints: (value: any[]) => void;
  updateVehicleLocationThrottled: (lat: number, lng: number, force?: boolean) => void;
  setVehicleDirection: (value: number) => void;
};
/**
 * 分享页运行时编排:负责订单拉取、状态同步、轮询与地图数据联动。
 */
export default function useShareTripRuntime(deps: Deps) {
  const tripStatusRef = useRef<TripStatus>(deps.tripStatus); // 行程状态引用
  const startSiteRef = useRef<any>(deps.startSite); // 上车点引用
  const endSiteRef = useRef<any>(deps.endSite); // 下车点引用
  const infoStatusRef = useRef<TripStatus | null>(null); // 最新详情状态引用
  const errorToastShownRef = useRef(false); // 错误提示节流标记
  const applyRoutePointsRef = useRef(deps.applyRoutePoints); // 路线落图函数引用
  const updateVehicleLocationRef = useRef(deps.updateVehicleLocationThrottled); // 车辆位置更新函数引用
  const setVehicleDirectionRef = useRef(deps.setVehicleDirection); // 车辆朝向更新函数引用
  /**
   * 计算两点方向角(0-360)。
   */
  const getDirectionByPoints = (from: LocationPoint, to?: LocationPoint) => {
    if (!from || !to) return 0;
    const deltaLng = to.longitude - from.longitude;
    const deltaLat = to.latitude - from.latitude;
    const angle = Math.atan2(deltaLng, deltaLat) * (180 / Math.PI);
    return ((angle % 360) + 360) % 360;
  };
  /**
   * 朝向值归一化到 0-360,非法值返回 null。
   */
  const normalizeDirection = (value: any) => {
    const direction = Number(value);
    if (!Number.isFinite(direction)) return null;
    return ((direction % 360) + 360) % 360;
  };
  /**
   * 安全转换数值,非法值返回 null。
   */
  const toFiniteNumber = (value: any) => {
    const num = Number(value);
    return Number.isFinite(num) ? num : null;
  };
  /**
   * 归一化坐标点结构,兼容字符串和对象格式。
   */
  const normalizePoint = (point: any) => {
    if (!point) return null;
    if (typeof point === 'string') {
      const [longitudeValue, latitudeValue] = point.split(',');
      const longitudeNum = toFiniteNumber(longitudeValue);
      const latitudeNum = toFiniteNumber(latitudeValue);
      if (longitudeNum === null || latitudeNum === null) return null;
      return { longitude: longitudeNum, latitude: latitudeNum };
    }
    const longitudeNum = toFiniteNumber(point.longitude ?? point.lon ?? point.lng);
    const latitudeNum = toFiniteNumber(point.latitude ?? point.lat);
    if (longitudeNum === null || latitudeNum === null) return null;
    return { longitude: longitudeNum, latitude: latitudeNum };
  };
  /**
   * 获取对象中首个有效字段值。
   */
  const getFieldValue = (source: any, keys: string[]) => {
    for (const key of keys) {
      const value = source?.[key];
      if (value !== undefined && value !== null && value !== '') {
        return value;
      }
    }
    return null;
  };
  /**
   * 构建上/下车点结构。
   */
  const buildSite = (source: any, type: 'start' | 'end') => {
    const isStart = type === 'start';
    const latitudeValue = getFieldValue(
      source,
      isStart
        ? ['startLat', 'startLatitude', 'upLat', 'originLat', 'originLatitude']
        : ['endLat', 'endLatitude', 'downLat', 'destinationLat', 'destinationLatitude'],
    );
    const longitudeValue = getFieldValue(
      source,
      isStart
        ? ['startLng', 'startLon', 'startLongitude', 'upLng', 'upLon', 'originLng', 'originLon', 'originLongitude']
        : ['endLng', 'endLon', 'endLongitude', 'downLng', 'downLon', 'destinationLng', 'destinationLon', 'destinationLongitude'],
    );
    const latitudeNum = toFiniteNumber(latitudeValue);
    const longitudeNum = toFiniteNumber(longitudeValue);
    if (latitudeNum === null || longitudeNum === null) return null;
    return {
      latitude: latitudeNum,
      longitude: longitudeNum,
      name: getFieldValue(
        source,
        isStart ? ['startName', 'startSiteName', 'upName', 'originName'] : ['endName', 'endSiteName', 'downName', 'destinationName'],
      ) || (isStart ? '上车点' : '终点'),
      address: getFieldValue(
        source,
        isStart ? ['startAddress', 'startSiteAddress', 'upAddress', 'originAddress'] : ['endAddress', 'endSiteAddress', 'downAddress', 'destinationAddress'],
      ) || '',
    };
  };
  /**
   * 格式化时间展示文案。
   */
  const formatTimeText = (value: any) => {
    if (!value) return null;
    const text = String(value);
    if (/^\d{2}:\d{2}/.test(text)) return text.slice(0, 5);
    const date = new Date(text);
    if (Number.isNaN(date.getTime())) return text;
    const hours = `${date.getHours()}`.padStart(2, '0');
    const minutes = `${date.getMinutes()}`.padStart(2, '0');
    return `${hours}:${minutes}`;
  };
  /**
   * 解析时间为毫秒时间戳。
   */
  const parseTimeToTimestamp = (value: any) => {
    if (value === undefined || value === null || value === '') return null;
    if (typeof value === 'number') {
      if (!Number.isFinite(value) || value <= 0) return null;
      return value < 1e12 ? value * 1000 : value;
    }
    const normalizedValue = String(value).trim();
    if (!normalizedValue) return null;
    if (/^\d+$/.test(normalizedValue)) {
      const numericValue = Number(normalizedValue);
      if (!Number.isFinite(numericValue) || numericValue <= 0) return null;
      return numericValue < 1e12 ? numericValue * 1000 : numericValue;
    }
    const parsedTime = new Date(normalizedValue.replace(/-/g, '/')).getTime();
    return Number.isFinite(parsedTime) && parsedTime > 0 ? parsedTime : null;
  };
  /**
   * 根据详情与状态判断分享页是否过期。
   */
  const resolveExpiredState = (info: any, mappedStatus: TripStatus) => {
    if (info?.expired === true || info?.isExpired === true) return true;
    if (mappedStatus < TripStatus.ArrivedDestination) return false;
    const getoffTimestamp = parseTimeToTimestamp(info?.getoffTime);
    if (typeof getoffTimestamp !== 'number') return false;
    return Date.now() - getoffTimestamp > 24 * 60 * 60 * 1000;
  };
  /**
   * 获取点集合中心点。
   */
  const getCenterPoint = (points: Array<{ longitude: number; latitude: number }>) => {
    if (!points.length) return null;
    const init = {
      minLng: points[0].longitude,
      maxLng: points[0].longitude,
      minLat: points[0].latitude,
      maxLat: points[0].latitude,
    };
    const bounds = points.reduce(
      (acc, point) => ({
        minLng: Math.min(acc.minLng, point.longitude),
        maxLng: Math.max(acc.maxLng, point.longitude),
        minLat: Math.min(acc.minLat, point.latitude),
        maxLat: Math.max(acc.maxLat, point.latitude),
      }),
      init,
    );
    return {
      longitude: (bounds.minLng + bounds.maxLng) / 2,
      latitude: (bounds.minLat + bounds.maxLat) / 2,
    };
  };
  /**
   * 提取路线点。
   */
  const extractRoutePoints = (routeResponse: any) => {
    const candidates = [
      routeResponse?.points,
      routeResponse?.route?.polyline,
      routeResponse?.polyline,
      routeResponse?.content?.points,
    ];
    for (const candidate of candidates) {
      if (Array.isArray(candidate) && candidate.length > 0) {
        return candidate.map(normalizePoint).filter(Boolean) as Array<{ longitude: number; latitude: number }>;
      }
    }
    return [];
  };
  /**
   * 提取已服务轨迹点。
   */
  const extractServedPoints = (routeResponse: any) => {
    if (!Array.isArray(routeResponse?.servedPoints)) return [];
    return routeResponse.servedPoints.map(normalizePoint).filter(Boolean) as Array<{ longitude: number; latitude: number }>;
  };
  /**
   * 解析车辆应展示位置。
   */
  const resolveVehiclePoint = (points: Array<{ longitude: number; latitude: number }>, servedPoints: Array<{ longitude: number; latitude: number }> = []) => {
    const servedLastPoint = Array.isArray(servedPoints) && servedPoints.length > 0 ? servedPoints[servedPoints.length - 1] : null;
    if (servedLastPoint) return servedLastPoint;
    return points[0] || null;
  };
  /**
   * 将分享路线数据同步到地图与车辆状态。
   */
  const syncShareRoute = (routeResponse: any, mappedStatus: TripStatus, nextStartSite: any, nextEndSite: any) => {
    const extractedRoutePoints = extractRoutePoints(routeResponse);
    const servedPoints = extractServedPoints(routeResponse);
    const routeMeta = routeResponse?.content?.route || routeResponse?.route || routeResponse || {};
    const distance = normalizeDistanceKm(Number(routeMeta?.distance));
    const duration = normalizeDurationSeconds(Number(routeMeta?.duration));
    deps.setRouteDistance(distance);
    deps.setRouteDuration(duration);
    applyRoutePointsRef.current(extractedRoutePoints);
    const direction = normalizeDirection(
      routeResponse?.direction ??
      routeResponse?.content?.direction ??
      routeMeta?.direction ??
      routeResponse?.vehicleDirection,
    );
    const vehiclePoint = resolveVehiclePoint(extractedRoutePoints, servedPoints);
    if (vehiclePoint && [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(mappedStatus)) {
      updateVehicleLocationRef.current(vehiclePoint.latitude, vehiclePoint.longitude, true);
      if (typeof direction === 'number') {
        setVehicleDirectionRef.current(direction);
      } else {
        setVehicleDirectionRef.current(getDirectionByPoints(vehiclePoint, extractedRoutePoints[1]));
      }
    }
    const center = getCenterPoint([
      ...extractedRoutePoints,
      ...(nextStartSite ? [nextStartSite] : []),
      ...(nextEndSite ? [nextEndSite] : []),
    ]);
    if (center) {
      deps.setLongitude(center.longitude);
      deps.setLatitude(center.latitude);
    }
  };
  useEffect(() => {
    tripStatusRef.current = deps.tripStatus;
  }, [deps.tripStatus]);
  useEffect(() => {
    startSiteRef.current = deps.startSite;
  }, [deps.startSite]);
  useEffect(() => {
    endSiteRef.current = deps.endSite;
  }, [deps.endSite]);
  useEffect(() => {
    applyRoutePointsRef.current = deps.applyRoutePoints;
    updateVehicleLocationRef.current = deps.updateVehicleLocationThrottled;
    setVehicleDirectionRef.current = deps.setVehicleDirection;
  }, [deps.applyRoutePoints, deps.updateVehicleLocationThrottled, deps.setVehicleDirection]);
  useEffect(() => {
    if (!deps.orderId) {
      Taro.showToast({ title: '分享订单不存在', icon: 'none' });
      return;
    }
    let cancelled = false;
    let timer: ReturnType<typeof setInterval> | null = null;
    const showFetchError = (error: unknown) => {
      console.error('获取分享行程失败', error);
      if (!errorToastShownRef.current) {
        errorToastShownRef.current = true;
        Taro.showToast({ title: '获取分享行程失败', icon: 'none' });
      }
    };
    const stopPolling = () => {
      if (timer) {
        clearInterval(timer);
        timer = null;
      }
    };
    const applyShareInfo = (info: any) => {
      const mappedStatus = mapOrderStatusToTripStatus(info?.status) as TripStatus;
      const nextStartSite = buildSite(info, 'start');
      const nextEndSite = buildSite(info, 'end');
      const etaMinutes = Math.max(
        0,
        Number(info?.etaMinutes ?? info?.estimatedWaitTime ?? info?.arrivalMinutes ?? info?.routeDurationMinutes ?? 0) || 0,
      );
      const nextTripData = {
        ...info,
        tripStatus: mappedStatus,
        etaMinutes,
        plateNumber: info?.plateNumber || info?.vehiclePlateNumber || '',
        vehicleColor: info?.vehicleColor || '',
        vehicleCode: info?.vehicleCode || '',
        vehiclePictures: info?.vehiclePictures || info?.vehicleImage || '',
        startPointName: info?.startPointName || nextStartSite?.name || '上车点',
        endPointName: info?.endPointName || nextEndSite?.name || '终点',
        boardingTime: formatTimeText(info?.boardingTime || info?.pickupTime),
        getoffTime: formatTimeText(info?.getoffTime || info?.finishTime || info?.orderFinishTime),
        customerServicePhone: info?.customerServicePhone || info?.servicePhone || '400-000-0000',
      };
      infoStatusRef.current = mappedStatus;
      tripStatusRef.current = mappedStatus;
      startSiteRef.current = nextStartSite;
      endSiteRef.current = nextEndSite || {};
      deps.setTripStatus(mappedStatus);
      deps.setTripData(nextTripData);
      deps.setStartSite(nextStartSite);
      deps.setEndSite(nextEndSite || {});
      deps.setIsExpired(resolveExpiredState(info, mappedStatus));
      deps.setShowReminder(Boolean(info?.showReminder));
      return {
        mappedStatus,
        nextStartSite,
        nextEndSite,
      };
    };
    const resolveRouteOrderStatus = (routeResponse: any): TripStatus | null => {
      const statusValue =
        getFieldValue(routeResponse?.content, ['tripStatus', 'orderStatus', 'status']) ??
        getFieldValue(routeResponse, ['tripStatus', 'orderStatus']);
      const parsedStatus = Number(statusValue);
      return Number.isFinite(parsedStatus)
        ? (mapOrderStatusToTripStatus(parsedStatus) as TripStatus)
        : null;
    };
    const fetchInitialShareInfo = async () => {
      const info = await shareQueryOrderInfo({ orderId: deps.orderId });
      if (cancelled) return null;
      return applyShareInfo(info || {});
    };
    const fetchShareTripData = async () => {
      try {
        if (tripStatusRef.current > TripStatus.ArrivedDestination) {
          stopPolling();
          return;
        }
        const routeResponse = await shareQueryOrderRoute(deps.orderId);
        if (cancelled) return;
        const routeStatus = resolveRouteOrderStatus(routeResponse);
        const shouldFetchInfo = routeStatus !== null && routeStatus !== infoStatusRef.current;
        let mappedStatus = tripStatusRef.current;
        let nextStartSite = startSiteRef.current;
        let nextEndSite = endSiteRef.current;
        if (shouldFetchInfo) {
          const info = await shareQueryOrderInfo({ orderId: deps.orderId });
          if (cancelled) return;
          const infoState = applyShareInfo(info || {});
          mappedStatus = infoState.mappedStatus;
          nextStartSite = infoState.nextStartSite;
          nextEndSite = infoState.nextEndSite;
        }
        if (mappedStatus > TripStatus.ArrivedDestination) {
          stopPolling();
          return;
        }
        syncShareRoute(routeResponse, mappedStatus, nextStartSite, nextEndSite);
        errorToastShownRef.current = false;
      } catch (error) {
        showFetchError(error);
      }
    };
    fetchInitialShareInfo()
      .then((infoState) => {
        if (!infoState || cancelled) return;
        if (infoState.mappedStatus > TripStatus.ArrivedDestination) return;
        fetchShareTripData();
        timer = setInterval(fetchShareTripData, 10000);
      })
      .catch((error) => {
        showFetchError(error);
      });
    return () => {
      cancelled = true;
      stopPolling();
    };
  }, [deps.orderId]);
}
src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx
@@ -1,9 +1,10 @@
import { CoverImage, CoverView, Image, View } from "@tarojs/components";
import { formatDurationDisplay } from "../../../../utils/Robotaxi-public-method";
import { TripStatus } from "../../begin-route/constants/trip-status";
type Deps = {
    isAndroid: boolean;
    tripStatus: number;
    tripStatus: TripStatus;
    mapScale: number;
    markers: any[];
    shouldShowWaitingRadar?: boolean;
@@ -58,7 +59,7 @@
        >
            <>
                {item.name !== "车辆位置" &&
                    !(item.id === 2 && [7].includes(deps.tripStatus)) && (
                    !(item.id === 2 && [TripStatus.ArrivedDestination].includes(deps.tripStatus)) && (
                        <CoverView
                            className={`custom-callout-content ${
                                deps.isAndroid && "txt-center "
@@ -75,7 +76,7 @@
                                {item.name}
                            </CoverView>
                            {deps.isAndroid && <CoverView style={{ width: "2px" }}></CoverView>}
                            {deps.tripStatus == 1 && (
                            {deps.tripStatus === TripStatus.PublishRoute && (
                                <CoverImage
                                    className="custom-callout-icon"
                                    src={deps.customCalloutIconSrc}
@@ -84,7 +85,7 @@
                        </CoverView>
                    )}
                {item.name === "车辆位置" &&
                    [4, 6].includes(deps.tripStatus) &&
                    [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) &&
                    !deps.isAndroid && (
                        <CoverView
                            className="custom-callout-content"
@@ -113,7 +114,7 @@
                        </CoverView>
                    )}
                {item.id === 2 &&
                    [7].includes(deps.tripStatus) &&
                    [TripStatus.ArrivedDestination].includes(deps.tripStatus) &&
                    !deps.isAndroid &&
                    deps.mapScale >= 20 && (
                        <CoverView className="custom-callout-content">
@@ -126,9 +127,9 @@
    const renderMarkerCallout = (item: any, index: number) => {
        const calloutKey = `callout-${item.id}-${item.name || ""}`;
        const isWaiting = [2, 3].includes(deps.tripStatus) && index === 0;
        const isWaiting = [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus) && index === 0;
        if (isWaiting) return renderWaitingCallout(item, calloutKey);
        if (item.name === "车辆位置" && deps.isAndroid && [4, 6].includes(deps.tripStatus)) {
        if (item.name === "车辆位置" && deps.isAndroid && [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus)) {
            return null;
        }
        return renderDefaultCallout(item, calloutKey);
@@ -136,7 +137,7 @@
    const renderMapOverlays = () => (
        <>
            {!(deps.isAndroid && [1, 5, 8, 9, 10].includes(deps.tripStatus)) &&
            {!(deps.isAndroid && [TripStatus.PublishRoute, TripStatus.VehicleArrivedPickup, TripStatus.TripFinished, TripStatus.PaymentSuccess, TripStatus.CancelOnlyPickup].includes(deps.tripStatus)) &&
                deps.markers.map(renderMarkerCallout)}
            {deps.shouldShowWaitingRadar && deps.waitingRadarVisible && (
                <Image
src/robotaxi/pages/share-trip/index.tsx
@@ -1,172 +1,75 @@
import Taro, { useRouter } from "@tarojs/taro";
import { View, Image, Map, CoverView, CoverImage } from "@tarojs/components";
import { useState, useEffect, useMemo, useRef } from "react";
import {
  formatDurationDisplay,
  mapOrderStatusToTripStatus,
  safeParse
} from "../../../utils/Robotaxi-public-method";
import "./index.less";
import Taro, { useRouter } from '@tarojs/taro';
import { useEffect, useMemo, useState } from 'react';
import './index.less';
import useMapMarkers from './hooks/use-map-markers';
import useVehicleTracking from './hooks/use-vehicle-tracking';
import useRouteManager from './hooks/use-route-manager';
import useMapViewport from './hooks/use-map-viewport';
import useViewDelegates from './hooks/use-view-delegates';
import useShareTripRuntime from './hooks/use-share-trip-runtime';
import ShareTripView from './components/share-trip-view';
import { TripStatus } from '../begin-route/constants/trip-status';
import homeIcon from "../../assets/back-home.png"; // 占位,建议换成首页小房子图标
import shareContactIcon from "../../assets/share-contact.png"; // 客服占位图
import contactServiceIcon from "../../assets/contact-service.png"; // 客服占位图
import shareLine from "../../assets/share-line.png"; // 分享线占位图
// 分享页本地 hooks,避免依赖 begin-route 页面实现
import useMapMarkers from "./hooks/use-map-markers";
import useVehicleTracking from "./hooks/use-vehicle-tracking";
import useRouteManager from "./hooks/use-route-manager";
import useMapViewport from "./hooks/use-map-viewport";
import useViewDelegates from "./hooks/use-view-delegates";
import {
  shareQueryOrderInfo,
  shareQueryOrderRoute
} from "../../api/begin-route";
import {
  normalizeDistanceKm,
  normalizeDurationSeconds
} from "../../utils/format";
type StatusRule = {
  showPickup: boolean;
  showDropoff: boolean;
  showCar: boolean;
  showRoute: boolean;
  showWalking: boolean;
  viewport: string;
  translateCar: boolean;
};
export default function ShareTrip() {
  const router = useRouter();
  const { orderId = "" } = router.params || {};
  const [tripData, setTripData] = useState<any>(null);
  const [longitude, setLongitude] = useState(113.32452);
  const [latitude, setLatitude] = useState(23.10229);
  const { orderId = '' } = router.params || {}; // 分享订单 ID
  const [tripData, setTripData] = useState<any>(null); // 行程详情数据
  const [longitude, setLongitude] = useState(113.32452); // 地图中心经度
  const [latitude, setLatitude] = useState(23.10229); // 地图中心纬度
  const [startSite, setStartSite] = useState<any>(null); // 上车点
  const [endSite, setEndSite] = useState<any>({}); // 下车点
  const [isExpired, setIsExpired] = useState(false); // 是否已过期(大于24小时)
  const [showReminder, setShowReminder] = useState(false);
  const [tripStatus, setTripStatus] = useState<number>(1);
  const tripStatusRef = useRef(tripStatus);
  const startSiteRef = useRef<any>(null);
  const endSiteRef = useRef<any>({});
  const infoStatusRef = useRef<number | null>(null);
  const errorToastShownRef = useRef(false);
  // 行程状态显示规则配置:showPickup-显示上车点,showDropoff-显示下车点,showCar-显示车辆,showRoute-显示路线,showWalking-显示步行路线,viewport-视口类型,translateCar-车辆是否平移
  const STATUS_RULES: Record<number, any> = {
    1: {
      showPickup: true,
      showDropoff: true,
      showCar: false,
      showRoute: true,
      showWalking: true,
      viewport: "route_walking",
      translateCar: false
    },
    2: {
      showPickup: true,
      showDropoff: false,
      showCar: false,
      showRoute: false,
      showWalking: false,
      viewport: "pickup",
      translateCar: false
    },
    3: {
      showPickup: true,
      showDropoff: false,
      showCar: false,
      showRoute: false,
      showWalking: false,
      viewport: "pickup",
      translateCar: false
    },
    4: {
      showPickup: true,
      showDropoff: false,
      showCar: true,
      showRoute: true,
      showWalking: false,
      viewport: "route",
      translateCar: true
    },
    5: {
      showPickup: true,
      showDropoff: false,
      showCar: true,
      showRoute: false,
      showWalking: false,
      viewport: "car_user",
      translateCar: true
    },
    6: {
      showPickup: false,
      showDropoff: true,
      showCar: true,
      showRoute: true,
      showWalking: false,
      viewport: "route",
      translateCar: true
    },
    7: {
      showPickup: false,
      showDropoff: true,
      showCar: false,
      showRoute: false,
      showWalking: false,
      viewport: "dropoff",
      translateCar: false
    },
    8: {
      showPickup: true,
      showDropoff: true,
      showCar: false,
      showRoute: true,
      showWalking: false,
      viewport: "order_detail",
      translateCar: false
    },
    9: {
      showPickup: true,
      showDropoff: true,
      showCar: false,
      showRoute: true,
      showWalking: false,
      viewport: "order_detail",
      translateCar: false
    },
    10: {
      showPickup: true,
      showDropoff: false,
      showCar: false,
      showRoute: false,
      showWalking: false,
      viewport: "pickup",
      translateCar: false
    }
  };
  const [isExpired, setIsExpired] = useState(false); // 是否超 24h 过期
  const [showReminder, setShowReminder] = useState(false); // 是否展示提醒
  const [tripStatus, setTripStatus] = useState<TripStatus>(TripStatus.PublishRoute); // 当前行程状态
  const STATUS_RULES: Record<TripStatus, StatusRule> = {
    [TripStatus.PublishRoute]: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: true, viewport: 'route_walking', translateCar: false },
    [TripStatus.Publishing]: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    [TripStatus.CallingCar]: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    [TripStatus.VehicleApproaching]: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
    [TripStatus.VehicleArrivedPickup]: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
    [TripStatus.InService]: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
    [TripStatus.ArrivedDestination]: { showPickup: false, showDropoff: true, showCar: false, showRoute: false, showWalking: false, viewport: 'dropoff', translateCar: false },
    [TripStatus.TripFinished]: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
    [TripStatus.PaymentSuccess]: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
    [TripStatus.CancelOnlyPickup]: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
  }; // 行程状态显示规则
  const statusRule = useMemo(
    () => STATUS_RULES[tripStatus] || STATUS_RULES[1],
    [tripStatus]
  ); // 当前行程状态对应的显示规则
  const shouldShowRoute = statusRule.showRoute;
    () => STATUS_RULES[tripStatus] || STATUS_RULES[TripStatus.PublishRoute],
    [tripStatus],
  ); // 当前状态渲染规则
  const shouldShowRoute = statusRule.showRoute; // 是否展示主路线
  const isAndroid = useMemo(() => {
    try {
      const info = Taro.getSystemInfoSync();
      return (
        info?.platform === "android" || /android/i.test(info?.system || "")
      );
      return info?.platform === 'android' || /android/i.test(info?.system || '');
    } catch (error) {
      return false;
    }
  }, []);
  const mapLongitude = longitude ?? 116.397128;
  const mapLatitude = latitude ?? 39.916527;
  const vehicleMarkerId = !isAndroid && tripStatus === 6 ? 30 : 3;
  const vehicleState = useVehicleTracking({
    statusRule,
    vehicleMarkerId
  }); // 车辆追踪状态容器
  const {
    vehicleLocation,
    vehicleDirection,
    setVehicleDirection,
    updateVehicleLocationThrottled
  } = vehicleState; // 车辆状态解构
  }, []); // 平台标记
  const markerState = useMapMarkers({
  const mapLongitude = longitude ?? 116.397128; // 地图兜底经度
  const mapLatitude = latitude ?? 39.916527; // 地图兜底纬度
  const vehicleMarkerId = !isAndroid && tripStatus === TripStatus.InService ? 30 : 3; // 车辆 marker ID
  const { vehicleLocation, vehicleDirection, setVehicleDirection, updateVehicleLocationThrottled } = useVehicleTracking({
    statusRule,
    vehicleMarkerId,
  }); // 车辆追踪状态容器
  const { markers } = useMapMarkers({
    tripStatus,
    statusRule,
    isAndroid,
@@ -174,16 +77,9 @@
    endSite,
    vehicleLocation,
    vehicleDirection,
    vehicleMarkerId
  }); // 地图标记状态容器
  const { markers } = markerState; // 标记状态解构
    vehicleMarkerId,
  }); // 地图 marker 状态容器
  const routeState = useRouteManager({
    isAndroid,
    tripStatus,
    statusRule,
    shouldShowRoute
  });
  const {
    polyline,
    applyRoutePoints,
@@ -192,343 +88,15 @@
    routeDistance,
    routeDuration,
    setRouteDistance,
    setRouteDuration
  } = routeState;
  const applyRoutePointsRef = useRef(applyRoutePoints);
  const updateVehicleLocationRef = useRef(updateVehicleLocationThrottled);
  const setVehicleDirectionRef = useRef(setVehicleDirection);
    setRouteDuration,
  } = useRouteManager({
    isAndroid,
    tripStatus,
    statusRule,
    shouldShowRoute,
  }); // 路线状态容器
  const getDirectionByPoints = (
    from: { latitude: number; longitude: number },
    to?: { latitude: number; longitude: number }
  ) => {
    if (!from || !to) return 0;
    const deltaLng = to.longitude - from.longitude;
    const deltaLat = to.latitude - from.latitude;
    const angle = Math.atan2(deltaLng, deltaLat) * (180 / Math.PI);
    return ((angle % 360) + 360) % 360;
  };
  const normalizeDirection = (value: any) => {
    const direction = Number(value);
    if (!Number.isFinite(direction)) {
      return null;
    }
    return ((direction % 360) + 360) % 360;
  };
  const toFiniteNumber = (value: any) => {
    const num = Number(value);
    return Number.isFinite(num) ? num : null;
  };
  const normalizePoint = (point: any) => {
    if (!point) {
      return null;
    }
    if (typeof point === "string") {
      const [longitudeValue, latitudeValue] = point.split(",");
      const longitudeNum = toFiniteNumber(longitudeValue);
      const latitudeNum = toFiniteNumber(latitudeValue);
      if (longitudeNum === null || latitudeNum === null) {
        return null;
      }
      return { longitude: longitudeNum, latitude: latitudeNum };
    }
    const longitudeNum = toFiniteNumber(
      point.longitude ?? point.lon ?? point.lng
    );
    const latitudeNum = toFiniteNumber(point.latitude ?? point.lat);
    if (longitudeNum === null || latitudeNum === null) {
      return null;
    }
    return { longitude: longitudeNum, latitude: latitudeNum };
  };
  const getFieldValue = (source: any, keys: string[]) => {
    for (const key of keys) {
      const value = source?.[key];
      if (value !== undefined && value !== null && value !== "") {
        return value;
      }
    }
    return null;
  };
  const buildSite = (source: any, type: "start" | "end") => {
    const isStart = type === "start";
    const latitudeValue = getFieldValue(
      source,
      isStart
        ? ["startLat", "startLatitude", "upLat", "originLat", "originLatitude"]
        : [
            "endLat",
            "endLatitude",
            "downLat",
            "destinationLat",
            "destinationLatitude"
          ]
    );
    const longitudeValue = getFieldValue(
      source,
      isStart
        ? [
            "startLng",
            "startLon",
            "startLongitude",
            "upLng",
            "upLon",
            "originLng",
            "originLon",
            "originLongitude"
          ]
        : [
            "endLng",
            "endLon",
            "endLongitude",
            "downLng",
            "downLon",
            "destinationLng",
            "destinationLon",
            "destinationLongitude"
          ]
    );
    const latitudeNum = toFiniteNumber(latitudeValue);
    const longitudeNum = toFiniteNumber(longitudeValue);
    if (latitudeNum === null || longitudeNum === null) {
      return null;
    }
    return {
      latitude: latitudeNum,
      longitude: longitudeNum,
      name:
        getFieldValue(
          source,
          isStart
            ? ["startName", "startSiteName", "upName", "originName"]
            : ["endName", "endSiteName", "downName", "destinationName"]
        ) || (isStart ? "上车点" : "终点"),
      address:
        getFieldValue(
          source,
          isStart
            ? ["startAddress", "startSiteAddress", "upAddress", "originAddress"]
            : [
                "endAddress",
                "endSiteAddress",
                "downAddress",
                "destinationAddress"
              ]
        ) || ""
    };
  };
  const formatTimeText = (value: any) => {
    if (!value) {
      return null;
    }
    const text = String(value);
    if (/^\d{2}:\d{2}/.test(text)) {
      return text.slice(0, 5);
    }
    const date = new Date(text);
    if (Number.isNaN(date.getTime())) {
      return text;
    }
    const hours = `${date.getHours()}`.padStart(2, "0");
    const minutes = `${date.getMinutes()}`.padStart(2, "0");
    return `${hours}:${minutes}`;
  };
  const parseTimeToTimestamp = (value: any) => {
    if (value === undefined || value === null || value === "") {
      return null;
    }
    if (typeof value === "number") {
      if (!Number.isFinite(value) || value <= 0) {
        return null;
      }
      // 兼容秒级时间戳,统一转换成毫秒。
      return value < 1e12 ? value * 1000 : value;
    }
    const normalizedValue = String(value).trim();
    if (!normalizedValue) {
      return null;
    }
    if (/^\d+$/.test(normalizedValue)) {
      const numericValue = Number(normalizedValue);
      if (!Number.isFinite(numericValue) || numericValue <= 0) {
        return null;
      }
      return numericValue < 1e12 ? numericValue * 1000 : numericValue;
    }
    // 兼容 "2026-04-17 10:00:00" 这类在部分环境下解析不稳定的时间格式。
    const parsedTime = new Date(normalizedValue.replace(/-/g, "/")).getTime();
    return Number.isFinite(parsedTime) && parsedTime > 0 ? parsedTime : null;
  };
  const resolveExpiredState = (info: any, mappedStatus: number) => {
    if (info?.expired === true || info?.isExpired === true) {
      return true;
    }
    if (mappedStatus < 7) {
      return false;
    }
    const getoffTimestamp = parseTimeToTimestamp(info?.getoffTime);
    if (typeof getoffTimestamp !== "number") {
      return false;
    }
    // 仅以下车时间作为分享页过期依据。
    return Date.now() - getoffTimestamp > 24 * 60 * 60 * 1000;
  };
  const getCenterPoint = (
    points: Array<{ longitude: number; latitude: number }>
  ) => {
    if (!points.length) {
      return null;
    }
    const init = {
      minLng: points[0].longitude,
      maxLng: points[0].longitude,
      minLat: points[0].latitude,
      maxLat: points[0].latitude
    };
    const bounds = points.reduce(
      (acc, point) => ({
        minLng: Math.min(acc.minLng, point.longitude),
        maxLng: Math.max(acc.maxLng, point.longitude),
        minLat: Math.min(acc.minLat, point.latitude),
        maxLat: Math.max(acc.maxLat, point.latitude)
      }),
      init
    );
    return {
      longitude: (bounds.minLng + bounds.maxLng) / 2,
      latitude: (bounds.minLat + bounds.maxLat) / 2
    };
  };
  const extractRoutePoints = (routeResponse: any) => {
    const candidates = [
      routeResponse?.points,
      routeResponse?.route?.polyline,
      routeResponse?.polyline,
      routeResponse?.content?.points
    ];
    for (const candidate of candidates) {
      if (Array.isArray(candidate) && candidate.length > 0) {
        return candidate.map(normalizePoint).filter(Boolean) as Array<{
          longitude: number;
          latitude: number;
        }>;
      }
    }
    return [];
  };
  const extractServedPoints = (routeResponse: any) => {
    if (!Array.isArray(routeResponse?.servedPoints)) {
      return [];
    }
    return routeResponse.servedPoints
      .map(normalizePoint)
      .filter(Boolean) as Array<{ longitude: number; latitude: number }>;
  };
  const resolveVehiclePoint = (
    points: Array<{ longitude: number; latitude: number }>,
    servedPoints: Array<{ longitude: number; latitude: number }> = []
  ) => {
    const servedLastPoint =
      Array.isArray(servedPoints) && servedPoints.length > 0
        ? servedPoints[servedPoints.length - 1]
        : null;
    if (servedLastPoint) {
      return servedLastPoint;
    }
    return points[0] || null;
  };
  const syncShareRoute = (
    routeResponse: any,
    mappedStatus: number,
    nextStartSite: any,
    nextEndSite: any
  ) => {
    const extractedRoutePoints = extractRoutePoints(routeResponse);
    const servedPoints = extractServedPoints(routeResponse);
    const routeMeta =
      routeResponse?.content?.route ||
      routeResponse?.route ||
      routeResponse ||
      {};
    const distance = normalizeDistanceKm(Number(routeMeta?.distance));
    const duration = normalizeDurationSeconds(Number(routeMeta?.duration));
    setRouteDistance(distance);
    setRouteDuration(duration);
    applyRoutePointsRef.current(extractedRoutePoints);
    const direction = normalizeDirection(
      routeResponse?.direction ??
        routeResponse?.content?.direction ??
        routeMeta?.direction ??
        routeResponse?.vehicleDirection
    );
    const vehiclePoint = resolveVehiclePoint(
      extractedRoutePoints,
      servedPoints
    );
    if (vehiclePoint && [4, 5, 6].includes(mappedStatus)) {
      updateVehicleLocationRef.current(
        vehiclePoint.latitude,
        vehiclePoint.longitude,
        true
      );
      if (typeof direction === "number") {
        setVehicleDirectionRef.current(direction);
      } else {
        setVehicleDirectionRef.current(
          getDirectionByPoints(vehiclePoint, extractedRoutePoints[1])
        );
      }
    }
    const center = getCenterPoint([
      ...extractedRoutePoints,
      ...(nextStartSite ? [nextStartSite] : []),
      ...(nextEndSite ? [nextEndSite] : [])
    ]);
    if (center) {
      setLongitude(center.longitude);
      setLatitude(center.latitude);
    }
  };
  useEffect(() => {
    tripStatusRef.current = tripStatus;
  }, [tripStatus]);
  useEffect(() => {
    startSiteRef.current = startSite;
  }, [startSite]);
  useEffect(() => {
    endSiteRef.current = endSite;
  }, [endSite]);
  const viewportState = useMapViewport({
  const { mapScale, updateMapViewportPadding } = useMapViewport({
    tripStatus,
    statusRule,
    routePoints,
@@ -537,198 +105,40 @@
    endSite,
    vehicleLocation,
    longitude,
    latitude
  });
  const { mapScale, updateMapViewportPadding } = viewportState;
    latitude,
  }); // 视口状态容器
  useEffect(() => {
    applyRoutePointsRef.current = applyRoutePoints;
    updateVehicleLocationRef.current = updateVehicleLocationThrottled;
    setVehicleDirectionRef.current = setVehicleDirection;
  }, [applyRoutePoints, updateVehicleLocationThrottled, setVehicleDirection]);
  useShareTripRuntime({
    orderId,
    tripStatus,
    startSite,
    endSite,
    setTripStatus,
    setTripData,
    setStartSite,
    setEndSite,
    setIsExpired,
    setShowReminder,
    setLongitude,
    setLatitude,
    setRouteDistance,
    setRouteDuration,
    applyRoutePoints,
    updateVehicleLocationThrottled,
    setVehicleDirection,
  }); // 分享页业务流程编排
  useEffect(() => {
    updateMapViewportPadding();
  }, [tripStatus, updateMapViewportPadding]);
  }, [tripStatus, updateMapViewportPadding]); // 状态变化后刷新地图留白
  useEffect(() => {
    const timer = setTimeout(() => {
      updateMapViewportPadding();
    }, 50);
    return () => clearTimeout(timer);
  }, [
    tripStatus,
    showReminder,
    tripData?.startPointName,
    tripData?.endPointName,
    tripData?.plateNumber,
    updateMapViewportPadding
  ]);
  }, [tripStatus, showReminder, tripData?.startPointName, tripData?.endPointName, tripData?.plateNumber, updateMapViewportPadding]); // UI 数据变化后二次修正留白
  useEffect(() => {
    if (!orderId) {
      Taro.showToast({ title: "分享订单不存在", icon: "none" });
      return;
    }
    let cancelled = false;
    let timer: ReturnType<typeof setInterval> | null = null;
    const showFetchError = (error: unknown) => {
      console.error("获取分享行程失败", error);
      if (!errorToastShownRef.current) {
        errorToastShownRef.current = true;
        Taro.showToast({ title: "获取分享行程失败", icon: "none" });
      }
    };
    const stopPolling = () => {
      if (timer) {
        clearInterval(timer);
        timer = null;
      }
    };
    const applyShareInfo = (info: any) => {
      const mappedStatus = mapOrderStatusToTripStatus(info?.status);
      const nextStartSite = buildSite(info, "start");
      const nextEndSite = buildSite(info, "end");
      const etaMinutes = Math.max(
        0,
        Number(
          info?.etaMinutes ??
            info?.estimatedWaitTime ??
            info?.arrivalMinutes ??
            info?.routeDurationMinutes ??
            0
        ) || 0
      );
      const nextTripData = {
        ...info,
        tripStatus: mappedStatus,
        etaMinutes,
        plateNumber: info?.plateNumber || info?.vehiclePlateNumber || "",
        vehicleColor: info?.vehicleColor || "",
        vehicleCode: info?.vehicleCode || "",
        vehiclePictures: info?.vehiclePictures || info?.vehicleImage || "",
        startPointName: info?.startPointName || nextStartSite?.name || "上车点",
        endPointName: info?.endPointName || nextEndSite?.name || "终点",
        boardingTime: formatTimeText(info?.boardingTime || info?.pickupTime),
        getoffTime: formatTimeText(
          info?.getoffTime || info?.finishTime || info?.orderFinishTime
        ),
        customerServicePhone:
          info?.customerServicePhone || info?.servicePhone || "400-000-0000"
      };
      infoStatusRef.current = mappedStatus;
      tripStatusRef.current = mappedStatus;
      startSiteRef.current = nextStartSite;
      endSiteRef.current = nextEndSite || {};
      setTripStatus(mappedStatus);
      setTripData(nextTripData);
      setStartSite(nextStartSite);
      setEndSite(nextEndSite || {});
      setIsExpired(resolveExpiredState(info, mappedStatus));
      setShowReminder(Boolean(info?.showReminder));
      return {
        mappedStatus,
        nextStartSite,
        nextEndSite
      };
    };
    const resolveRouteOrderStatus = (routeResponse: any) => {
      const statusValue =
        getFieldValue(routeResponse?.content, [
          "tripStatus",
          "orderStatus",
          "status"
        ]) ?? getFieldValue(routeResponse, ["tripStatus", "orderStatus"]);
      const parsedStatus = Number(statusValue);
      return Number.isFinite(parsedStatus)
        ? mapOrderStatusToTripStatus(parsedStatus)
        : null;
    };
    const fetchInitialShareInfo = async () => {
      const info = await shareQueryOrderInfo({ orderId });
      if (cancelled) {
        return null;
      }
      return applyShareInfo(info || {});
    };
    const fetchShareTripData = async () => {
      try {
        if (tripStatusRef.current > 7) {
          stopPolling();
          return;
        }
        const routeResponse = await shareQueryOrderRoute(orderId);
        if (cancelled) {
          return;
        }
        const routeStatus = resolveRouteOrderStatus(routeResponse);
        const shouldFetchInfo =
          routeStatus !== null && routeStatus !== infoStatusRef.current;
        let mappedStatus = tripStatusRef.current;
        let nextStartSite = startSiteRef.current;
        let nextEndSite = endSiteRef.current;
        if (shouldFetchInfo) {
          const info = await shareQueryOrderInfo({ orderId });
          if (cancelled) {
            return;
          }
          const infoState = applyShareInfo(info || {});
          mappedStatus = infoState.mappedStatus;
          nextStartSite = infoState.nextStartSite;
          nextEndSite = infoState.nextEndSite;
        }
        if (mappedStatus > 7) {
          stopPolling();
          return;
        }
        syncShareRoute(routeResponse, mappedStatus, nextStartSite, nextEndSite);
        errorToastShownRef.current = false;
      } catch (error) {
        showFetchError(error);
      }
    };
    fetchInitialShareInfo()
      .then(infoState => {
        if (!infoState || cancelled) {
          return;
        }
        if (infoState.mappedStatus > 7) {
          return;
        }
        fetchShareTripData();
        timer = setInterval(fetchShareTripData, 10000);
      })
      .catch(error => {
        showFetchError(error);
      });
    return () => {
      cancelled = true;
      stopPolling();
    };
  }, [orderId, setRouteDistance, setRouteDuration]);
  const goHome = () => {
    Taro.reLaunch({ url: "/pages/robotaxi/home/index" });
  };
  const durationDisplay = formatDurationDisplay(routeDuration);
  const { renderMapOverlays } = useViewDelegates({
    isAndroid,
    tripStatus,
@@ -737,198 +147,44 @@
    shouldShowWaitingRadar: false,
    waitingRadarVisible: false,
    waitingRadarStyle: {},
    waitingRadarSrc: "",
    waitingRadarSrc: '',
    carCalloutVisible: false,
    carCalloutStyle: {},
    arriveCalloutVisible: false,
    arriveCalloutStyle: {},
    routeDistance,
    routeDuration,
    customCalloutIconSrc:
      "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-tooltip-right.png"
  });
    customCalloutIconSrc: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-tooltip-right.png',
  }); // 地图浮层视图委托
  const goHome = () => {
    Taro.reLaunch({ url: '/pages/robotaxi/home/index' });
  }; // 返回首页
  const handleContactService = () => {
    const phoneNumber = Taro.getStorageSync("customerServicePhone");
    const phoneNumber = Taro.getStorageSync('customerServicePhone');
    if (phoneNumber) {
      Taro.makePhoneCall({ phoneNumber });
    } else {
      Taro.showToast({ title: "暂无客服电话", icon: "none" });
      Taro.showToast({ title: '暂无客服电话', icon: 'none' });
    }
  };
  if (isExpired) {
    return (
      <View className="share-trip-expired">
        <View className="expired-content">
          <Image className="expired-img" src={shareContactIcon} />
          <View className="expired-text">行程结束已超过24小时</View>
          <View className="expired-sub-text">查看行程信息,请联系客服</View>
          <View className="expired-btn" onClick={handleContactService}>
            联系客服
          </View>
        </View>
      </View>
    );
  }
  }; // 联系客服动作
  return (
    <View className="home-container">
      <Map
        id="map"
        longitude={mapLongitude}
        latitude={mapLatitude}
        scale={mapScale}
        markers={markers}
        polyline={polyline}
        style={{ width: "100%", height: "87vh" }}
      >
        {renderMapOverlays()}
        {/* 顶部返回/首页按钮 */}
        <View onClick={goHome} className="select-city">
          <Image className="select-city-icon" src={homeIcon} />
        </View>
        <View className="container">
          {/* 联系客服悬浮按钮 */}
          <CoverView
            className={`share-contact ${showReminder ? "has-tip" : ""}`}
            onClick={handleContactService}
          >
            <CoverImage
              className="share-contact-icon"
              src={contactServiceIcon}
            />
          </CoverView>
          {/* 提醒 */}
          {showReminder && (
            <View className="tip-box">
              <View className="tip-box-content">
                <View className="tip-box-text">
                  开启提醒后,亲友行程动态秒通知
                </View>
                <View
                  className="tip-box-btn"
                  onClick={() => setShowReminder(false)}
                >
                  去开启
                </View>
              </View>
            </View>
          )}
          <View className="container-inner">
            <View catchMove onClick={e => e.stopPropagation()}>
              <View className="calling-car">
                <Image
                  className="calling-car-bg"
                  src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg.png"
                />
                <View className="calling-car-head">
                  {tripData?.tripStatus === 4 && (
                    <View className="calling-car-head-title">
                      车辆还有
                      <View className="calling-car-head-title-eta">
                        {durationDisplay.value}
                      </View>
                      {durationDisplay.unit}到达上车点
                    </View>
                  )}
                  {tripData?.tripStatus === 5 && (
                    <View className="calling-car-head-title">
                      车辆已到达上车点
                    </View>
                  )}
                  {tripData?.tripStatus === 6 && (
                    <View className="calling-car-head-title">
                      预计
                      <View className="calling-car-head-title-eta">
                        {durationDisplay.value}
                      </View>
                      {durationDisplay.unit}抵达目的地
                    </View>
                  )}
                  {tripData?.tripStatus > 7 && (
                    <View className="calling-car-head-title">已到达目的地</View>
                  )}
                </View>
                <View className="car-card">
                  <View className="car-card-detail">
                    <View>
                      <View className="car-card-detail-num">
                        {tripData?.plateNumber || "--"}
                      </View>
                      <View className="car-card-detail-color">
                        {tripData?.vehicleColor || "--"}
                        <View className="car-card-detail-color-line"></View>
                        车身编码: {tripData?.vehicleCode || "--"}
                      </View>
                    </View>
                    {tripData?.vehiclePictures && (
                      <Image
                        className="car-card-detail-img"
                        src={
                          safeParse(tripData?.vehiclePictures, {})?.url ||
                          "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/vehicle-default.png"
                        }
                      />
                    )}
                  </View>
                  <View className="car-card-line"></View>
                  <View className="car-card-temp">
                    <View className="car-card-temp-list">
                      <Image className="car-card-temp-img" src={shareLine} />
                      {/* 起点 */}
                      <View className="car-card-temp-row">
                        <View className="car-card-temp-row-left">
                          <View className="dot start"></View>
                          <View
                            className={`car-card-temp-title ${
                              tripData?.tripStatus === 6 ||
                              tripData?.tripStatus === 7
                                ? "disabled-text"
                                : ""
                            }`}
                          >
                            起点:{tripData?.startPointName}
                          </View>
                        </View>
                        {(tripData?.tripStatus === 6 ||
                          tripData?.tripStatus === 7) &&
                          tripData?.boardingTime && (
                            <View className="car-card-temp-time disabled-text">
                              {tripData.boardingTime} 上车
                            </View>
                          )}
                      </View>
                      {/* 终点 */}
                      <View
                        className="car-card-temp-row"
                        style={{ marginTop: "12px" }}
                      >
                        <View className="car-card-temp-row-left">
                          <View className="dot end"></View>
                          <View
                            className={`car-card-temp-title ${
                              tripData?.tripStatus === 7 ? "disabled-text" : ""
                            }`}
                          >
                            终点:{tripData?.endPointName}
                          </View>
                        </View>
                        {tripData?.tripStatus === 7 && tripData?.getoffTime && (
                          <View className="car-card-temp-time disabled-text">
                            {tripData.getoffTime} 到达
                          </View>
                        )}
                      </View>
                    </View>
                  </View>
                </View>
              </View>
            </View>
          </View>
        </View>
      </Map>
    </View>
    <ShareTripView
      isExpired={isExpired}
      showReminder={showReminder}
      setShowReminder={setShowReminder}
      tripData={tripData}
      mapLongitude={mapLongitude}
      mapLatitude={mapLatitude}
      mapScale={mapScale}
      markers={markers}
      polyline={polyline}
      routeDuration={routeDuration}
      renderMapOverlays={renderMapOverlays}
      goHome={goHome}
      handleContactService={handleContactService}
    />
  );
}