Merge branch 'feature_v2.6.0-cancel-btn-0810' into 'feature_2.6.0'
在车辆驶入禁行区/手动接管期间,即便已在行程中,也展示取消按钮
See merge request robotaxi1/xp-robotaxi-miniprogram!266
| | |
| | | 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 状态值集合 |
| | | |
| | |
| | | import { createContext, useContext } from 'react'; |
| | | import { TripStatus } from '../../../constants/trip-status'; |
| | | import { TripStatus, CancelType } from '../../../constants/trip-status'; |
| | | |
| | | /** BeginRoute 视图上下文。 */ |
| | | export type BeginRouteViewContextValue = { |
| | |
| | | /** 是否展示取消弹窗。 */ |
| | | showCancelPopup: boolean; |
| | | /** 取消类型。 */ |
| | | cancelType: number; |
| | | cancelType: CancelType; |
| | | /** 取消弹窗等待分钟数。 */ |
| | | cancelPopupWaitMinutes: number | null; |
| | | /** 起点。 */ |
| | |
| | | orderTrunkStatus?: boolean; |
| | | /** 当前页面内是否已经解锁过车门 */ |
| | | doorUnlockedInCurrentPage?: boolean; |
| | | /** 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮 */ |
| | | forceShowCancelTrip?: boolean; |
| | | /** 手动开门成功后同步页面级开门状态 */ |
| | | onDoorUnlockSuccess?: () => void; |
| | | bluetoothStatusReady?: boolean; |
| | |
| | | hasPreviousOrder = false, |
| | | orderTrunkStatus, |
| | | doorUnlockedInCurrentPage = false, |
| | | forceShowCancelTrip = false, |
| | | onDoorUnlockSuccess, |
| | | bluetoothStatusReady = false, |
| | | bluetoothCardDescription = '授权并打开蓝牙链接,享受无感出行', |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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); |
| | |
| | | |
| | | // 不同取消阶段对应的文案配置 |
| | | 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', |
| | | }) |
| | |
| | | <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'> |
| | |
| | | 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, |
| | |
| | | /** 下车点坐标。 */ |
| | | endSite: SitePoint; |
| | | /** 设置取消弹窗类型(用于本地手动触发各类取消弹窗文案)。 */ |
| | | setCancelType?: (type: number) => void; |
| | | setCancelType?: (type: CancelType) => void; |
| | | /** 设置取消弹窗展示状态。 */ |
| | | setShowCancelPopup?: (show: boolean) => void; |
| | | /** 设置修改目的地失败弹窗展示状态(用于本地手动触发)。 */ |
| | |
| | | setReplaying(false); |
| | | }; |
| | | |
| | | /** 手动触发「附近没有空余车辆,已为您取消订单」弹窗(cancelType 5)。 */ |
| | | /** 手动触发「附近没有空余车辆,已为您取消订单」弹窗(CancelType.NoAvailableVehicle)。 */ |
| | | const handleTriggerNoVehicleCancelPopup = () => { |
| | | props.setCancelType?.(5); |
| | | props.setCancelType?.(CancelType.NoAvailableVehicle); |
| | | props.setShowCancelPopup?.(true); |
| | | }; |
| | | |
| | |
| | | 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; |
| | |
| | | /** 取消前页面真实行程状态。 */ |
| | | tripStatus: TripStatus; |
| | | /** 期望展示的取消弹窗类型。 */ |
| | | cancelType: number; |
| | | cancelType: CancelType; |
| | | /** 取消来源。 */ |
| | | source: PendingCancelSource; |
| | | /** 上下文创建时间戳。 */ |
| | |
| | | /** 下单提交中。 */ |
| | | callSubmitting: boolean; |
| | | /** 取消类型。 */ |
| | | cancelType: number; |
| | | cancelType: CancelType; |
| | | /** 待确认取消上下文。 */ |
| | | pendingCancelContextRef: React.MutableRefObject<PendingCancelContext | null>; |
| | | /** 当前车辆信息。 */ |
| | |
| | | /** 设置下单提交状态。 */ |
| | | setCallSubmitting: (value: boolean) => void; |
| | | /** 设置取消类型。 */ |
| | | setCancelType: (value: number) => void; |
| | | setCancelType: (value: CancelType) => void; |
| | | /** 设置取消弹窗。 */ |
| | | setShowCancelPopup: (value: boolean) => void; |
| | | /** 设置取消弹窗等待分钟数。 */ |
| | |
| | | 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, |
| | |
| | | /** 重启发单规划。 */ |
| | | restartPlanningRoute: () => void; |
| | | /** 订单超时再来一单。 */ |
| | | handleOpenCancelPopupWithType: (type: number) => void; |
| | | handleOpenCancelPopupWithType: (type: CancelType) => void; |
| | | /** 根据状态打开取消弹窗。 */ |
| | | handleOpenCancelPopupByTripStatus: (status: TripStatus, displayInfo?: any) => void; |
| | | /** 上车倒计时超时后发起自动取消。 */ |
| | |
| | | /** |
| | | * 订单超时再来一单。 |
| | | */ |
| | | const handleOpenCancelPopupWithType = (type: number) => { |
| | | const handleOpenCancelPopupWithType = (type: CancelType) => { |
| | | clearActiveOrderRuntime(); |
| | | deps.setCancelType(type); |
| | | restartPlanningRoute(); |
| | |
| | | deps.pendingCancelContextRef.current = { |
| | | orderId: targetOrderId, |
| | | tripStatus: deps.tripStatus, |
| | | cancelType: 4, |
| | | cancelType: CancelType.OrderCanceled, |
| | | source: 'pickup_timeout', |
| | | createdAt: Date.now() |
| | | }; |
| | | handleOpenCancelPopupWithType(4); |
| | | handleOpenCancelPopupWithType(CancelType.OrderCanceled); |
| | | }; |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | 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); |
| | | }; |
| | |
| | | * 确认取消。 |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | 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 |
| | | ); |
| | | |
| | |
| | | showToast({ title: '行程已取消', icon: 'success' }); |
| | | clearActiveOrderRuntime(); |
| | | deps.setTripStatus(TripStatus.CancelOnlyPickup); |
| | | deps.setCancelType(4); |
| | | deps.setCancelType(CancelType.OrderCanceled); |
| | | } catch (error) { |
| | | showToast({ title: '取消订单失败,请重试', icon: 'none' }); |
| | | } |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | import { useState } from 'react'; |
| | | import { CancelType } from '../../../constants/trip-status'; |
| | | |
| | | export default function usePopupManager() { |
| | | const [showCustomerService, setShowCustomerService] = useState(false); |
| | |
| | | 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(''); |
| | |
| | | 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 */ |
| | |
| | | /** 设置路线时长 */ |
| | | setRouteDuration: (value: any) => void; |
| | | /** 设置取消类型 */ |
| | | setCancelType: (value: number) => void; |
| | | setCancelType: (value: CancelType) => void; |
| | | /** 设置取消弹窗 */ |
| | | setShowCancelPopup: (value: boolean) => void; |
| | | /** 显示乘客须知 */ |
| | |
| | | trunkStatus: boolean; |
| | | /** 当前页面内是否已经解锁过车门 */ |
| | | doorUnlockedInCurrentPage: boolean; |
| | | /** 后端推送 ORDER_ROUTE.content.oddOut === 1 时,强制展示取消行程按钮 */ |
| | | forceShowCancelTrip?: boolean; |
| | | /** 手动开门成功后同步页面级开门状态 */ |
| | | onDoorUnlockSuccess: () => void; |
| | | /** 路线选项集合 */ |
| | |
| | | onToggleDetailFade={deps.handleToggleDetailFade} |
| | | orderTrunkStatus={deps.trunkStatus} |
| | | doorUnlockedInCurrentPage={deps.doorUnlockedInCurrentPage} |
| | | forceShowCancelTrip={deps.forceShowCancelTrip} |
| | | onDoorUnlockSuccess={deps.onDoorUnlockSuccess} |
| | | bluetoothStatusReady={deps.bluetoothStatusReady} |
| | | bluetoothCardDescription={deps.bluetoothCardDescription} |
| | |
| | | import { useEffect, useRef } from 'react'; |
| | | import { CancelType } from '../../../constants/trip-status'; |
| | | import wsManager from '../../../../utils/websocket'; |
| | | import { |
| | | createOpenTrunkSocketHandler, |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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, |
| | |
| | | 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 || ''); |
| | |
| | | } |
| | | lastFetchedVehicleOrderIdRef.current = null; |
| | | rollbackOrderToCallingCar(currentDeps); |
| | | currentDeps.setCancelType(6); |
| | | currentDeps.setCancelType(CancelType.Redispatching); |
| | | currentDeps.setShowCancelPopup(true); |
| | | } |
| | | }); |
| | |
| | | } |
| | | 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( |
| | |
| | | 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'; |
| | |
| | | } |
| | | 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(''); |
| | |
| | | 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); |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | setPriceUpdateVersion, |
| | | setFinishCountdownEnabled, |
| | | setDoorUnlockedInCurrentPage, |
| | | setForceShowCancelTrip, |
| | | setShowAnnouncementsPopup, |
| | | setRoutePoints, |
| | | setPolyline, |
| | |
| | | } |
| | | setDoorUnlockedInCurrentPage(readDoorUnlockedStateForOrder(orderId)); |
| | | }, [orderId, readDoorUnlockedStateForOrder]); |
| | | |
| | | /** |
| | | * 订单切换后重置强制展示取消行程按钮的标记,避免串单。 |
| | | */ |
| | | useEffect(() => { |
| | | setForceShowCancelTrip(false); |
| | | }, [orderId]); |
| | | |
| | | useEffect(() => { |
| | | if (!doorUnlockedInCurrentPage || !orderId) { |
| | |
| | | handleShareTrip, |
| | | trunkStatus, |
| | | doorUnlockedInCurrentPage, |
| | | forceShowCancelTrip, |
| | | onDoorUnlockSuccess: handleDoorUnlockSuccess, |
| | | routeOptions, |
| | | selectedRouteIndex, |