liaof
2026-08-14 3c33a057324187cbede3093d15a73f2e2d139d81
Merge branch 'feature_v2.6.0-cancel-btn-0810' into 'feature_2.6.0'

在车辆驶入禁行区/手动接管期间,即便已在行程中,也展示取消按钮

See merge request robotaxi1/xp-robotaxi-miniprogram!266
16个文件已修改
167 ■■■■■ 已修改文件
src/robotaxi/constants/trip-status.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/begin-route-view-context.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/cancel-popup.tsx 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-status.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-popup-manager.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/constants/trip-status.ts
@@ -52,6 +52,26 @@
  Completed = 10, 
}
/**
 * 取消订单弹窗类型枚举:与历史 number 值保持 1:1 对齐,避免行为变化。
 */
export enum CancelType {
  /** 呼叫车中/发布中取消:预计 N 分钟内有车应答 */
  CallingCar = 1,
  /** 接驾中取消:车辆还有 N 分钟/时长到达上车点 */
  VehicleApproaching = 2,
  /** 车辆已到达上车点后取消 */
  VehicleArrivedPickup = 3,
  /** 订单已取消(终态提示,可再来一单) */
  OrderCanceled = 4,
  /** 附近没有空余车辆,已自动取消订单 */
  NoAvailableVehicle = 5,
  /** 车辆无法继续服务,正在为您重新派车中 */
  Redispatching = 6,
  /** 行程中取消:预计 N 分钟到达目的地 */
  InService = 7,
}
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 状态值集合
src/robotaxi/pages/begin-route/components/begin-route-view-context.tsx
@@ -1,5 +1,5 @@
import { createContext, useContext } from 'react';
import { TripStatus } from '../../../constants/trip-status';
import { TripStatus, CancelType } from '../../../constants/trip-status';
/** BeginRoute 视图上下文。 */
export type BeginRouteViewContextValue = {
@@ -88,7 +88,7 @@
  /** 是否展示取消弹窗。 */
  showCancelPopup: boolean;
  /** 取消类型。 */
  cancelType: number;
  cancelType: CancelType;
  /** 取消弹窗等待分钟数。 */
  cancelPopupWaitMinutes: number | null;
  /** 起点。 */
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -88,6 +88,8 @@
    orderTrunkStatus?: boolean;
    /** 当前页面内是否已经解锁过车门 */
    doorUnlockedInCurrentPage?: boolean;
    /** 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮 */
    forceShowCancelTrip?: boolean;
    /** 手动开门成功后同步页面级开门状态 */
    onDoorUnlockSuccess?: () => void;
    bluetoothStatusReady?: boolean;
@@ -143,6 +145,7 @@
    hasPreviousOrder = false,
    orderTrunkStatus,
    doorUnlockedInCurrentPage = false,
    forceShowCancelTrip = false,
    onDoorUnlockSuccess,
    bluetoothStatusReady = false,
    bluetoothCardDescription = '授权并打开蓝牙链接,享受无感出行',
@@ -168,8 +171,9 @@
    const isArriveSoon = tripStatus === TripStatus.VehicleApproaching && (typeof routeDurationSeconds === 'number' && routeDurationSeconds <= 60);
    /** 上车倒计时即将结束(<60秒) */
    const isPickupCountdownSoon = tripStatus === TripStatus.VehicleArrivedPickup && (typeof pickupCountdownSeconds === 'number' && pickupCountdownSeconds < 60);
    /** 开门后,接驾态不再展示取消行程。 */
    const showCancelTrip = [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) && !doorUnlockedInCurrentPage;
    /** 开门后,接驾态不再展示取消行程;但 ORDER_ROUTE 推送 oddOut === 1 时强制展示。 */
    const showCancelTrip = forceShowCancelTrip
        || ([TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus) && !doorUnlockedInCurrentPage);
    /** 是否为“到达终点/待支付”状态(图一、图二) */
    const isTripFinished = tripStatus === TripStatus.TripFinished;
src/robotaxi/pages/begin-route/components/cancel-popup.tsx
@@ -6,13 +6,13 @@
import {
    clearOrderTripStorage
} from '../../../../utils/Robotaxi-public-method'
import { TripStatus } from '../../../constants/trip-status';
import { TripStatus, CancelType } from '../../../constants/trip-status';
import useBottomPopupAnimation from './hooks/use-bottom-popup-animation';
type Props = {
    show?: boolean;
    cancelType?: number;
    cancelType?: CancelType;
    startSite?: any;
    endSite?: any;
    waitMinutes?: number;
@@ -27,7 +27,7 @@
const CANCEL_ANIMATION_WEBP = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/cancel-img20260731.png?x-oss-process=image/format,webp';
const CANCEL_ANIMATION_PNG = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/cancel-img20260731.png';
export default function CancelPopup({ show = false, cancelType = 1, startSite, endSite, waitMinutes, tripStatus, routeDurationSeconds, discountAmount, onClose, onConfirm }: Props) {
export default function CancelPopup({ show = false, cancelType = CancelType.CallingCar, startSite, endSite, waitMinutes, tripStatus, routeDurationSeconds, discountAmount, onClose, onConfirm }: Props) {
    const { visible, animationData } = useBottomPopupAnimation(show);
    // 占位动画当前使用的图片地址,默认 webp,加载失败时回退到 png
    const [animationSrc, setAnimationSrc] = useState(CANCEL_ANIMATION_WEBP);
@@ -51,42 +51,46 @@
    // 不同取消阶段对应的文案配置
    const STATUS_MAP = {
        1: {
        [CancelType.CallingCar]: {
            title: `${waitMinutesValue}分钟内预计有车,确定取消吗?`,
            tip: discountAmountValue > 0 ? `本单限时优惠${discountAmountValue}元,重新叫车可能等待更久` : '',
        },
        2: {
        [CancelType.VehicleApproaching]: {
            title: tripStatus === TripStatus.VehicleApproaching
                ? `车辆还有${approachingDurationDisplay.value}${approachingDurationDisplay.unit}到达,确定取消吗`
                : `车辆还有${waitMinutesValue}分钟到达,确定取消吗`,
            tip: discountAmountValue > 0 ? `本单限时优惠${discountAmountValue}元,重新叫车可能等待更久` : '',
        },
        3: {
        [CancelType.VehicleArrivedPickup]: {
            title: '车辆已到达,确定取消吗?',
            tip: '车辆到达情况下取消可能会被限制发单',
        },
        4: {
        [CancelType.OrderCanceled]: {
            title: '订单已取消',
            tip: '已为您取消订单,期待您下次体验',
        },
        5: {
        [CancelType.NoAvailableVehicle]: {
            title: '附近没有空余车辆,已为您取消订单',
            tip: '当前运力紧张,建议您稍后重新发起订单',
        },
        6: {
        [CancelType.Redispatching]: {
            title: '正在为您重新派车中...',
            tip: '当前车辆无法继续服务',
        },
        [CancelType.InService]: {
            title: `预计${approachingDurationDisplay.value}${approachingDurationDisplay.unit}到达目的地,确认取消吗?`,
            tip: '',
        },
    }
    // 关闭弹窗时按类型执行对应流程
    const closePopup = () => {
        if (cancelType == 5) {
        if (cancelType == CancelType.NoAvailableVehicle) {
            onConfirm?.();
            onClose?.();
            return
        }
        if (cancelType == 4) {
        if (cancelType == CancelType.OrderCanceled) {
            Taro.reLaunch({
                url: '/pages/robotaxi/home/index',
            })
@@ -109,11 +113,11 @@
                <View className='cancel-tip-box'>
                    <View className='cancel-tip-box-title'>{STATUS_MAP[cancelType]?.title}</View>
                    {
                        STATUS_MAP[cancelType]?.tip && <View className={`cancel-tip-box-desc ${cancelType === 4 && 'color1'}`}>{STATUS_MAP[cancelType].tip}</View>
                        STATUS_MAP[cancelType]?.tip && <View className={`cancel-tip-box-desc ${cancelType === CancelType.OrderCanceled && 'color1'}`}>{STATUS_MAP[cancelType].tip}</View>
                    }
                </View>
                {
                    [4, 5].includes(cancelType) ?
                    [CancelType.OrderCanceled, CancelType.NoAvailableVehicle].includes(cancelType) ?
                        <View>
                            <View className='cancel-site-box'>
                                <View className='cancel-site-box-icon'>
src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { View, Text } from '@tarojs/components';
import { TripStatus } from '../../../constants/trip-status';
import { TripStatus, CancelType } from '../../../constants/trip-status';
import OrderFlowSimulator, {
  buildDefaultRoutes,
  ensureSimulationOrderId,
@@ -29,7 +29,7 @@
  /** 下车点坐标。 */
  endSite: SitePoint;
  /** 设置取消弹窗类型(用于本地手动触发各类取消弹窗文案)。 */
  setCancelType?: (type: number) => void;
  setCancelType?: (type: CancelType) => void;
  /** 设置取消弹窗展示状态。 */
  setShowCancelPopup?: (show: boolean) => void;
  /** 设置修改目的地失败弹窗展示状态(用于本地手动触发)。 */
@@ -303,9 +303,9 @@
    setReplaying(false);
  };
  /** 手动触发「附近没有空余车辆,已为您取消订单」弹窗(cancelType 5)。 */
  /** 手动触发「附近没有空余车辆,已为您取消订单」弹窗(CancelType.NoAvailableVehicle)。 */
  const handleTriggerNoVehicleCancelPopup = () => {
    props.setCancelType?.(5);
    props.setCancelType?.(CancelType.NoAvailableVehicle);
    props.setShowCancelPopup?.(true);
  };
src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts
@@ -1,6 +1,6 @@
import { SignedOrderPaymentValue } from '../../../api/types/begin-route';
import type { OrderDistanceConfig } from './use-order-distance-config';
import { TripStatus } from '../constants/trip-status';
import { TripStatus, CancelType } from '../constants/trip-status';
/** 下单防抖时长,单位毫秒。 */
export const ORDER_SUBMIT_DEBOUNCE_MS = 500;
@@ -29,7 +29,7 @@
  /** 取消前页面真实行程状态。 */
  tripStatus: TripStatus;
  /** 期望展示的取消弹窗类型。 */
  cancelType: number;
  cancelType: CancelType;
  /** 取消来源。 */
  source: PendingCancelSource;
  /** 上下文创建时间戳。 */
@@ -68,7 +68,7 @@
  /** 下单提交中。 */
  callSubmitting: boolean;
  /** 取消类型。 */
  cancelType: number;
  cancelType: CancelType;
  /** 待确认取消上下文。 */
  pendingCancelContextRef: React.MutableRefObject<PendingCancelContext | null>;
  /** 当前车辆信息。 */
@@ -89,7 +89,7 @@
  /** 设置下单提交状态。 */
  setCallSubmitting: (value: boolean) => void;
  /** 设置取消类型。 */
  setCancelType: (value: number) => void;
  setCancelType: (value: CancelType) => void;
  /** 设置取消弹窗。 */
  setShowCancelPopup: (value: boolean) => void;
  /** 设置取消弹窗等待分钟数。 */
src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts
@@ -3,7 +3,7 @@
import { clearOrderTripStorage, reportEvent } from '../../../../utils/Robotaxi-public-method';
import { cancelOrder } from '../../../api/begin-route';
import { getOrderId } from '../../../utils/order';
import { TripStatus } from '../../../constants/trip-status';
import { TripStatus, CancelType } from '../../../constants/trip-status';
import {
  WAITING_FOR_DISPATCH_CANCEL_BUTTON_EVENT_ID,
  WAITING_FOR_PICKUP_CANCEL_BUTTON_EVENT_ID,
@@ -30,7 +30,7 @@
  /** 重启发单规划。 */
  restartPlanningRoute: () => void;
  /** 订单超时再来一单。 */
  handleOpenCancelPopupWithType: (type: number) => void;
  handleOpenCancelPopupWithType: (type: CancelType) => void;
  /** 根据状态打开取消弹窗。 */
  handleOpenCancelPopupByTripStatus: (status: TripStatus, displayInfo?: any) => void;
  /** 上车倒计时超时后发起自动取消。 */
@@ -133,7 +133,7 @@
  /**
   * 订单超时再来一单。
   */
  const handleOpenCancelPopupWithType = (type: number) => {
  const handleOpenCancelPopupWithType = (type: CancelType) => {
    clearActiveOrderRuntime();
    deps.setCancelType(type);
    restartPlanningRoute();
@@ -147,11 +147,11 @@
    deps.pendingCancelContextRef.current = {
      orderId: targetOrderId,
      tripStatus: deps.tripStatus,
      cancelType: 4,
      cancelType: CancelType.OrderCanceled,
      source: 'pickup_timeout',
      createdAt: Date.now()
    };
    handleOpenCancelPopupWithType(4);
    handleOpenCancelPopupWithType(CancelType.OrderCanceled);
  };
  /**
@@ -167,22 +167,25 @@
    }
    if ([TripStatus.Publishing, TripStatus.CallingCar].includes(status)) {
      deps.setCancelType(1);
      deps.setCancelType(CancelType.CallingCar);
      deps.setShowCancelPopup(true);
      return;
    }
    if (displayInfo?.durationDisplay && ![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(status)) {
      deps.setCancelType(2);
      deps.setCancelType(CancelType.VehicleApproaching);
      deps.setShowCancelPopup(true);
      return;
    }
    if (status === TripStatus.VehicleApproaching) {
      deps.setCancelType(2);
      deps.setCancelType(CancelType.VehicleApproaching);
    }
    if (status === TripStatus.VehicleArrivedPickup) {
      deps.setCancelType(3);
      deps.setCancelType(CancelType.VehicleArrivedPickup);
    }
    if (status === TripStatus.InService) {
      deps.setCancelType(CancelType.InService);
    }
    deps.setShowCancelPopup(true);
  };
@@ -191,13 +194,13 @@
   * 确认取消。
   */
  const handleConfirmCancel = async () => {
    if (deps.cancelType === 4) {
    if (deps.cancelType === CancelType.OrderCanceled) {
      restartPlanningRoute();
      return;
    }
    if (deps.cancelType === 5) {
      handleOpenCancelPopupWithType(5);
    if (deps.cancelType === CancelType.NoAvailableVehicle) {
      handleOpenCancelPopupWithType(CancelType.NoAvailableVehicle);
      return;
    }
@@ -208,15 +211,15 @@
    }
    const shouldReportWaitingForDispatchCancel = (
      deps.cancelType === 1
      deps.cancelType === CancelType.CallingCar
      && [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus)
    );
    const shouldReportWaitingForPickupCancel = (
      deps.cancelType === 2
      deps.cancelType === CancelType.VehicleApproaching
      && deps.tripStatus === TripStatus.VehicleApproaching
    );
    const shouldReportPickupArrivedCancelTrip = (
      deps.cancelType === 3
      deps.cancelType === CancelType.VehicleArrivedPickup
      && deps.tripStatus === TripStatus.VehicleArrivedPickup
    );
@@ -234,7 +237,7 @@
      showToast({ title: '行程已取消', icon: 'success' });
      clearActiveOrderRuntime();
      deps.setTripStatus(TripStatus.CancelOnlyPickup);
      deps.setCancelType(4);
      deps.setCancelType(CancelType.OrderCanceled);
    } catch (error) {
      showToast({ title: '取消订单失败,请重试', icon: 'none' });
    }
src/robotaxi/pages/begin-route/hooks/use-order-status.ts
@@ -1,5 +1,6 @@
import Taro from '@tarojs/taro';
import { useState, useRef, useEffect } from 'react';
import { CancelType } from '../../../constants/trip-status';
export default function useOrderStatus() {
    const [tripStatus, setTripStatus] = useState<number>(Number(Taro.getStorageSync('tripStatus')) || 1);
@@ -9,7 +10,7 @@
    const [vehicleInfo, setVehicleInfo] = useState<any>({});
    const [trunkStatus, setTrunkStatus] = useState(false);
    const [finishCountdownEnabled, setFinishCountdownEnabled] = useState(false);
    const [cancelType, setCancelType] = useState(1);
    const [cancelType, setCancelType] = useState<CancelType>(CancelType.CallingCar);
    const tripStatusRef = useRef(tripStatus);
    const lastTripStatusRef = useRef(tripStatus);
src/robotaxi/pages/begin-route/hooks/use-popup-manager.ts
@@ -1,4 +1,5 @@
import { useState } from 'react';
import { CancelType } from '../../../constants/trip-status';
export default function usePopupManager() {
    const [showCustomerService, setShowCustomerService] = useState(false);
@@ -7,7 +8,7 @@
    const [showAnnouncementsPopup, setShowAnnouncementsPopup] = useState(false);
    const [showOverRoutePopup, setShowOverRoutePopup] = useState(false);
    const [showPassengerNotice, setShowPassengerNotice] = useState(false);
    const [cancelType, setCancelType] = useState(1);
    const [cancelType, setCancelType] = useState<CancelType>(CancelType.CallingCar);
    const [showEvaluatePopup, setShowEvaluatePopup] = useState(false);
    const [showTipPopup, setShowTipPopup] = useState(false);
    const [tipType, setTipType] = useState('');
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
@@ -2,7 +2,7 @@
import { useEffect } from 'react';
import { getOrderDetail } from '../../../api/order';
import { parseLocationCoordinateString } from '../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../../../constants/trip-status';
import { TripStatus, CancelType } from '../../../constants/trip-status';
type Deps = {
  /** 等待计时开始 key */
@@ -70,7 +70,7 @@
  /** 设置路线时长 */
  setRouteDuration: (value: any) => void;
  /** 设置取消类型 */
  setCancelType: (value: number) => void;
  setCancelType: (value: CancelType) => void;
  /** 设置取消弹窗 */
  setShowCancelPopup: (value: boolean) => void;
  /** 显示乘客须知 */
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -140,6 +140,8 @@
  trunkStatus: boolean;
  /** 当前页面内是否已经解锁过车门 */
  doorUnlockedInCurrentPage: boolean;
  /** 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮 */
  forceShowCancelTrip?: boolean;
  /** 手动开门成功后同步页面级开门状态 */
  onDoorUnlockSuccess: () => void;
  /** 路线选项集合 */
@@ -1056,6 +1058,7 @@
          onToggleDetailFade={deps.handleToggleDetailFade}
          orderTrunkStatus={deps.trunkStatus}
          doorUnlockedInCurrentPage={deps.doorUnlockedInCurrentPage}
          forceShowCancelTrip={deps.forceShowCancelTrip}
          onDoorUnlockSuccess={deps.onDoorUnlockSuccess}
          bluetoothStatusReady={deps.bluetoothStatusReady}
          bluetoothCardDescription={deps.bluetoothCardDescription}
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -1,4 +1,5 @@
import { useEffect, useRef } from 'react';
import { CancelType } from '../../../constants/trip-status';
import wsManager from '../../../../utils/websocket';
import {
    createOpenTrunkSocketHandler,
@@ -47,6 +48,8 @@
    setPriceUpdateVersion: (updater: (v: number) => number) => void;
    setFinishCountdownEnabled: (value: boolean) => void;
    setDoorUnlockedInCurrentPage: React.Dispatch<React.SetStateAction<boolean>>;
    /** 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮(无视原有行程状态/车门解锁判断)。 */
    setForceShowCancelTrip?: (value: boolean) => void;
    setShowAnnouncementsPopup: (value: boolean) => void;
    setRoutePoints: (points: any[]) => void;
    setPolyline: (lines: any[]) => void;
@@ -59,9 +62,9 @@
    currentFullRouteRef: any;
    lastErasedIndexRef: any;
    orderRouteInitializedRef: any;
    setCancelType: (value: number) => void;
    setCancelType: (value: CancelType) => void;
    setShowCancelPopup: (value: boolean) => void;
    cancelTypeRef: React.MutableRefObject<number>;
    cancelTypeRef: React.MutableRefObject<CancelType>;
    showCancelPopupRef: React.MutableRefObject<boolean>;
    checkAndShowPassengerNotice: () => void;
    handlePickupException?: (pickupException: PickupExceptionInfo | null) => void;
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-meta-socket-handlers.ts
@@ -1,7 +1,7 @@
import Taro from '@tarojs/taro';
import { queryVehicleByOrderId } from '../../../../api/begin-route';
import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from '../../../../constants/modify-destination';
import { TripStatus } from '../../../../constants/trip-status';
import { TripStatus, CancelType } from '../../../../constants/trip-status';
import {
  executeWhenOrderMatched,
  resolvePayloadOrderIdFromContentOnly,
@@ -110,8 +110,8 @@
    onMatched: ({ currentOrderId }) => {
      const currentDeps = depsRef.current;
      lastFetchedVehicleOrderIdRef.current = null;
      // 改派成功,仅在正展示"正在为您重新派车中"弹窗(cancelType=6)时关闭,避免误关其他取消弹窗
      if (currentDeps.showCancelPopupRef.current && currentDeps.cancelTypeRef.current === 6) {
      // 改派成功,仅在正展示"正在为您重新派车中"弹窗(CancelType.Redispatching)时关闭,避免误关其他取消弹窗
      if (currentDeps.showCancelPopupRef.current && currentDeps.cancelTypeRef.current === CancelType.Redispatching) {
        currentDeps.setShowCancelPopup(false);
      }
      const requestedOrderId = String(currentOrderId || '');
@@ -146,7 +146,7 @@
      }
      lastFetchedVehicleOrderIdRef.current = null;
      rollbackOrderToCallingCar(currentDeps);
      currentDeps.setCancelType(6);
      currentDeps.setCancelType(CancelType.Redispatching);
      currentDeps.setShowCancelPopup(true);
    }
  });
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
@@ -134,6 +134,10 @@
  }
  const beforeUserSite = parseBeforeUserEndSite(payload);
  currentDeps.setBeforeUserEndSite(beforeUserSite);
  // 后端推送 oddOut === 1 时,无视行程状态/车门解锁判断,强制展示取消行程按钮;
  // oddOut 恢复为非 1 时同步写回 false,避免同一订单内状态无法自动复位(该状态仅在订单切换时被重置)。
  const oddOut = Number(payload?.content?.oddOut);
  currentDeps.setForceShowCancelTrip?.(oddOut === 1);
  const currentTripStatus = currentDeps.tripStatusRef.current;
  const isModifyDestinationPreviewActive = Boolean(currentDeps.isModifyDestinationReviewVisible);
  const isPickupRouteChangePreviewActive = Boolean(
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts
@@ -3,7 +3,7 @@
import { queryVehicleByOrderId } from '../../../../api/begin-route';
import { clearOrderTripStorage, reportEvent } from '../../../../../utils/Robotaxi-public-method';
import { getOrderId } from '../../../../utils/order';
import { SocketOrderStatus, TripStatus, toTripStatus } from '../../../../constants/trip-status';
import { SocketOrderStatus, TripStatus, toTripStatus, CancelType } from '../../../../constants/trip-status';
import { ARRIVED_DROPOFF_PAGE_VIEW_EVENT_ID, PICKUP_ARRIVED_PAGE_VIEW_EVENT_ID, TRIP_IN_PROGRESS_PAGE_VIEW_EVENT_ID, TRIP_IN_PROGRESS_COMPLETE_TRIP_BUTTON_EVENT_ID } from '../../../../constants/report-events';
import { createTripInProgressCompleteTripReportPayload } from '../../../../constants/confirm-ride-report';
import { clearPassedRouteCache } from '../passed-route-cache';
@@ -194,7 +194,7 @@
  }
  const matchedPendingCancelContext = isPendingCancelContextMatched(payload, pendingCancelContext);
  if (matchedPendingCancelContext) {
    deps.setCancelType(pendingCancelContext?.cancelType || 4);
    deps.setCancelType(pendingCancelContext?.cancelType || CancelType.OrderCanceled);
    deps.pendingCancelContextRef.current = null;
  } else if (changeStatusOld === TripStatus.VehicleApproaching || changeStatusOld === TripStatus.VehicleArrivedPickup) {
    deps.setOrderId('');
@@ -203,9 +203,9 @@
    Taro.removeStorageSync('pickupEndTime');
    Taro.removeStorageSync('waitingTimerStartAt');
    Taro.removeStorageSync('waitingTimerElapsed');
    deps.setCancelType(4);
    deps.setCancelType(CancelType.OrderCanceled);
  } else {
    deps.setCancelType(5);
    deps.setCancelType(CancelType.NoAvailableVehicle);
  }
  deps.setTripStatus(TripStatus.CancelOnlyPickup);
  deps.setShowCancelPopup(true);
@@ -403,10 +403,10 @@
  reportTripInProgressEventIfNeeded(effectiveTripStatus, changeStatusOld, effectiveOrderId, currentDeps);
  reportTripCompleteEventIfNeeded(effectiveTripStatus, changeStatusOld, effectiveOrderId, currentDeps);
  handleStatusSideEffects(effectiveTripStatus, changeStatusOld, currentDeps);
  // 改派场景兜底:若当前展示的是"正在为您重新派车中"弹窗(cancelType=6),
  // 改派场景兜底:若当前展示的是"正在为您重新派车中"弹窗(CancelType.Redispatching),
  // 一旦订单状态推进回接驾中/已到达上车点,说明改派成功,自动关闭弹窗。
  // 用同步 ref 镜像判断,避免依赖经 useEffect 异步回流的 cancelType 造成漏关。
  const isReassignPopupShowing = currentDeps.showCancelPopupRef.current && currentDeps.cancelTypeRef.current === 6;
  const isReassignPopupShowing = currentDeps.showCancelPopupRef.current && currentDeps.cancelTypeRef.current === CancelType.Redispatching;
  const isAdvancingIntoPickup = (
    effectiveTripStatus === TripStatus.VehicleApproaching
    || effectiveTripStatus === TripStatus.VehicleArrivedPickup
src/robotaxi/pages/begin-route/index.tsx
@@ -265,6 +265,7 @@
  const [callSubmitting, setCallSubmitting] = useState(false); // 呼叫车辆提交中状态
  const [orderId, setOrderId] = useState<string>(''); // 订单ID
  const [doorUnlockedInCurrentPage, setDoorUnlockedInCurrentPage] = useState(false); // 当前页面内是否已解锁过车门
  const [forceShowCancelTrip, setForceShowCancelTrip] = useState(false); // 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮
  const [finishCountdownEnabled, setFinishCountdownEnabled] = useState(false); // 完成倒计时是否启用
  const customCalloutIconUrl = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-tooltip-right.png'; // 自定义气泡图标URL
  const waitingCalloutBgUrl = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/tooltip-bg-20260715.png'; // 等待气泡背景图URL
@@ -1495,6 +1496,7 @@
    setPriceUpdateVersion,
    setFinishCountdownEnabled,
    setDoorUnlockedInCurrentPage,
    setForceShowCancelTrip,
    setShowAnnouncementsPopup,
    setRoutePoints,
    setPolyline,
@@ -1578,6 +1580,13 @@
    }
    setDoorUnlockedInCurrentPage(readDoorUnlockedStateForOrder(orderId));
  }, [orderId, readDoorUnlockedStateForOrder]);
  /**
   * 订单切换后重置强制展示取消行程按钮的标记,避免串单。
   */
  useEffect(() => {
    setForceShowCancelTrip(false);
  }, [orderId]);
  useEffect(() => {
    if (!doorUnlockedInCurrentPage || !orderId) {
@@ -2413,6 +2422,7 @@
    handleShareTrip,
    trunkStatus,
    doorUnlockedInCurrentPage,
    forceShowCancelTrip,
    onDoorUnlockSuccess: handleDoorUnlockSuccess,
    routeOptions,
    selectedRouteIndex,