zjk
2026-07-29 429208957f30ab9eedc5533a81aaa284048d09f5
src/robotaxi/pages/begin-route/components/calling-car-modules/trip-status-header.tsx
@@ -1,151 +1,222 @@
import { View, Image } from "@tarojs/components";
import { useEffect, useState } from "react";
import { formatCountdown } from '../../../../../utils/Robotaxi-public-method';
import Taro from '@tarojs/taro';
import { View, Image, Text } from '@tarojs/components';
import { useEffect, useState } from 'react';
import { queryOrderQueueStatus } from '../../../../api/begin-route';
import { TripStatus } from '../../../../constants/trip-status';
type QueueStatus = {
    currentPosition: number;
    estimatedWaitTime: number;
    status: number;
    totalCount: number;
  currentPosition: number;
  estimatedWaitTime: number | null;
  status: number;
  totalCount: number;
} | null;
type Props = {
    orderId?: string;
    tripStatus: TripStatus;
    waitMinutes: number | null;
    durationDisplay: { value: string | number; unit: string };
    isArriveSoon: boolean;
    isPickupCountdownSoon: boolean;
    pickupCountdownSeconds: number | null;
    doorUnlockedInCurrentPage: boolean;
    finishCountdownEnabled: boolean;
    finishCountdownSeconds: number | null;
    limitMinutes: number | null;
    handleOpenCancelPopup?: (displayInfo: { durationDisplay: { value: string | number; unit: string }; waitMinutes: number | null }) => void;
    queuePollingTimeSeconds: number;
  orderId?: string;
  tripStatus: TripStatus;
  waitMinutes: number | null;
  durationDisplay: { value: string | number; unit: string };
  isArriveSoon: boolean;
  isPickupCountdownSoon: boolean;
  pickupCountdownSeconds: number | null;
  doorUnlockedInCurrentPage: boolean;
  finishCountdownEnabled: boolean;
  finishCountdownSeconds: number | null;
  limitMinutes: number | null;
  routeDistance?: { value: number; unit: 'km' | 'm' } | null;
  handleOpenCancelPopup?: (displayInfo: { durationDisplay: { value: string | number; unit: string }; waitMinutes: number | null }) => void;
  queuePollingTimeSeconds: number;
};
export default function TripStatusHeader({
    orderId,
    tripStatus,
    waitMinutes,
    durationDisplay,
    isArriveSoon,
    isPickupCountdownSoon,
    pickupCountdownSeconds,
    doorUnlockedInCurrentPage,
    finishCountdownEnabled,
    finishCountdownSeconds,
    limitMinutes,
    handleOpenCancelPopup,
    queuePollingTimeSeconds
  orderId,
  tripStatus,
  waitMinutes,
  durationDisplay,
  isArriveSoon,
  isPickupCountdownSoon,
  pickupCountdownSeconds,
  doorUnlockedInCurrentPage,
  finishCountdownEnabled,
  finishCountdownSeconds,
  limitMinutes,
  routeDistance = null,
  handleOpenCancelPopup,
  queuePollingTimeSeconds
}: Props) {
    const [queueStatus, setQueueStatus] = useState<QueueStatus>(null);
    const pollingIntervalMs = Math.max(1000, Number(queuePollingTimeSeconds || 10) * 1000); // 排队位次轮询间隔
    const inServiceDurationDisplay = durationDisplay.unit === '秒'
        ? { value: 1, unit: '分钟' as const }
        : durationDisplay; // 行程中剩余时间小于 1 分钟时按 1 分钟展示
  const [queueStatus, setQueueStatus] = useState<QueueStatus>(null);
  const pollingIntervalMs = Math.max(1000, Number(queuePollingTimeSeconds || 10) * 1000); // 排队位次轮询间隔
  const normalizedDurationDisplay = durationDisplay.unit === '秒'
    ? { value: 1, unit: '分钟' as const }
    : durationDisplay; // 剩余时间小于 1 分钟时按 1 分钟展示,不展示秒
  const inServiceDurationDisplay = normalizedDurationDisplay; // 行程中沿用统一的归一化时长
  // 排队接口返回实时预计等待分钟数;无有效值时回退下单时的初始预估。
  const displayedWaitMinutes = queueStatus?.estimatedWaitTime ?? waitMinutes;
    useEffect(() => {
        if (tripStatus !== TripStatus.CallingCar || !orderId) {
            setQueueStatus(null);
            return;
  useEffect(() => {
    if (tripStatus !== TripStatus.CallingCar || !orderId) {
      setQueueStatus(null);
      return;
    }
    let timer: ReturnType<typeof setInterval> | null = null;
    let unmounted = false;
    // 排队位次是动态变化的,状态为 3 时定时刷新
    const fetchQueueStatus = async () => {
      try {
        const res = await queryOrderQueueStatus({ orderId });
        if (unmounted) {
          return;
        }
        const queueData = res?.data ?? res;
        const rawEstimatedWaitTime = queueData?.estimatedWaitTime;
        const estimatedWaitTime = rawEstimatedWaitTime === null || rawEstimatedWaitTime === undefined || rawEstimatedWaitTime === ''
          ? null
          : Number(rawEstimatedWaitTime);
        if (
          Number(queueData?.currentPosition) > 0 &&
          Number(queueData?.totalCount) > 0
        ) {
          setQueueStatus({
            currentPosition: Number(queueData.currentPosition),
            estimatedWaitTime: typeof estimatedWaitTime === 'number' && Number.isFinite(estimatedWaitTime) && estimatedWaitTime >= 0 ? estimatedWaitTime : null,
            status: Number(queueData.status) || 0,
            totalCount: Number(queueData.totalCount)
          });
          return;
        }
        setQueueStatus(null);
      } catch (_) {
        if (!unmounted) {
          setQueueStatus(null);
        }
      }
    };
        let timer: ReturnType<typeof setInterval> | null = null;
        let unmounted = false;
    fetchQueueStatus();
    timer = setInterval(fetchQueueStatus, pollingIntervalMs);
        // 排队位次是动态变化的,状态为 3 时定时刷新
        const fetchQueueStatus = async () => {
            try {
                const res = await queryOrderQueueStatus({ orderId });
                if (unmounted) {
                    return;
                }
                const queueData = res?.data ?? res;
                if (
                    Number(queueData?.currentPosition) > 0 &&
                    Number(queueData?.totalCount) > 0
                ) {
                    setQueueStatus({
                        currentPosition: Number(queueData.currentPosition),
                        estimatedWaitTime: Number(queueData.estimatedWaitTime) || 0,
                        status: Number(queueData.status) || 0,
                        totalCount: Number(queueData.totalCount)
                    });
                    return;
                }
                setQueueStatus(null);
            } catch (_) {
                if (!unmounted) {
                    setQueueStatus(null);
                }
            }
        };
    return () => {
      unmounted = true;
      if (timer) {
        clearInterval(timer);
      }
    };
  }, [tripStatus, orderId, pollingIntervalMs]);
        fetchQueueStatus();
        timer = setInterval(fetchQueueStatus, pollingIntervalMs);
  // 标题行文案映射(已改为按状态直接渲染 DOM,保留注释供参考)
  // const titleMap = {
  //   [TripStatus.CallingCar]: <>预计<View className='time'>{waitMinutes}</View>分钟内有车应答</>,
  //   [TripStatus.VehicleApproaching]: isArriveSoon
  //     ? <>车辆即将到达,请准备上车</>
  //     : <>车辆还有<View className='time'>{durationDisplay.value}</View>{durationDisplay.unit}到达</>,
  //   [TripStatus.VehicleArrivedPickup]: doorUnlockedInCurrentPage
  //     ? <>车辆已到达,请尽快上车</>
  //     : <>{isPickupCountdownSoon ? '车辆已到达,请尽快上车' : '车辆已到达上车点'}<View className='time'>{pickupCountdownSeconds}</View></>,
  //   [TripStatus.InService]: <>预计<View className='time'>{inServiceDurationDisplay.value}</View>{inServiceDurationDisplay.unit}抵达目的地</>,
  //   [TripStatus.ArrivedDestination]: (finishCountdownEnabled || finishCountdownSeconds !== null)
  //     ? <>已到达目的地<View className='time'>{finishCountdownSeconds}</View></>
  //     : <>已到达目的地</>,
  //   [TripStatus.TripFinished]: '行程已结束',
  //   [TripStatus.PaymentSuccess]: '支付成功'
  // }
        return () => {
            unmounted = true;
            if (timer) {
                clearInterval(timer);
            }
        };
    }, [tripStatus, orderId, pollingIntervalMs]);
  // 说明文案映射(已注释)
  // const descMap = {
  //   [TripStatus.CallingCar]: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' },
  //   [TripStatus.VehicleApproaching]: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: 'calling-car-content-warning' } : null,
  //   [TripStatus.VehicleArrivedPickup]: doorUnlockedInCurrentPage
  //     ? { text: '车门已解锁,请尽快上车', className: 'calling-car-content-desc' }
  //     : { text: isPickupCountdownSoon ? `车辆到达后最多等待${limitMinutes}分钟,超时自动取消` : '此处限停,请在倒计时结束前到达上车点', 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' }
  // }
    // 标题行文案映射
    const titleMap = {
        [TripStatus.CallingCar]: <>预计<View className="time">{waitMinutes}</View>分钟内有车应答</>,
        [TripStatus.VehicleApproaching]: isArriveSoon
            ? <>车辆即将到达,请准备上车</>
            : <>车辆还有<View className="time">{durationDisplay.value}</View>{durationDisplay.unit}到达</>,
        [TripStatus.VehicleArrivedPickup]: doorUnlockedInCurrentPage
            ? <>车辆已到达,请尽快上车</>
            : <>{isPickupCountdownSoon ? '车辆已到达,请尽快上车' : '车辆已到达上车点'}<View className="time" style={isPickupCountdownSoon ? { color: '#f58900' } : undefined}>{pickupCountdownSeconds !== null ? formatCountdown(pickupCountdownSeconds) : '--:--'}</View></>,
        [TripStatus.InService]: <>预计<View className="time">{inServiceDurationDisplay.value}</View>{inServiceDurationDisplay.unit}抵达目的地</>,
        [TripStatus.ArrivedDestination]: (finishCountdownEnabled || finishCountdownSeconds !== null)
            ? <>已到达目的地<View className="time">{finishCountdownSeconds !== null ? formatCountdown(finishCountdownSeconds) : '--:--'}</View></>
            : <>已到达目的地</>,
        [TripStatus.TripFinished]: '行程已结束',
        [TripStatus.PaymentSuccess]: '支付成功'
    }
  // 站点信息(图二):与 cancel-popup 一致,优先取缓存的起终点
  const cachedStartSite = Taro.getStorageSync('startSite') || {};
  const cachedEndSite = Taro.getStorageSync('endSite') || {};
  const startSiteName = cachedStartSite?.name || cachedStartSite?.address || '-';
  const endSiteName = cachedEndSite?.name || cachedEndSite?.address || '-';
    // 说明文案映射
    const descMap = {
        [TripStatus.CallingCar]: { text: '正全力为您叫车中,请耐心等待...', className: 'calling-car-content-desc' },
        [TripStatus.VehicleApproaching]: limitMinutes ? { text: `此处限停${limitMinutes}分钟,请尽快前往上车点`, className: 'calling-car-content-warning' } : null,
        [TripStatus.VehicleArrivedPickup]: doorUnlockedInCurrentPage
            ? { text: '车门已解锁,请尽快上车', className: 'calling-car-content-desc' }
            : { text: isPickupCountdownSoon ? `车辆到达后最多等待${limitMinutes}分钟,超时自动取消` : '此处限停,请在倒计时结束前到达上车点', 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' }
    }
  // 状态栏右侧的距离/时间文案
  const distanceText = routeDistance ? `${routeDistance.value}${routeDistance.unit === 'm' ? 'M' : 'KM'}` : '';
  const approachingTimeText = `${normalizedDurationDisplay.value}MIN`;
  const inServiceTimeText = `${normalizedDurationDisplay.value}MIN`;
    return (
        <View className='calling-car-content'>
            <View>
                {titleMap[tripStatus] && (
                    <View className='calling-car-content-title'>{titleMap[tripStatus]}</View>
  return (
    <>
      {/* 呼叫车中:DOM 结构与样式保持不变(图一 + 站点卡片) */}
      {tripStatus === TripStatus.CallingCar && (
        <>
          <View className='calling-car-content'>
            <View className='calling-car-content-main'>
              <View className='calling-car-content-title calling-car-content-title--strong'>
                预计{displayedWaitMinutes ?? '-'}分钟内有车应答
              </View>
              <View className='calling-car-content-desc'>
                正全力为您叫车中...
                {queueStatus && (
                  <>
                    <Text className='calling-car-content-desc-divider'>|</Text>
                    第{queueStatus.currentPosition}位/共{queueStatus.totalCount}位
                  </>
                )}
                {descMap[tripStatus] && (
                    <View style={{ paddingBottom: tripStatus === TripStatus.CallingCar && !queueStatus ? '20px' : '0px' }} className={descMap[tripStatus]?.className}>{descMap[tripStatus]?.text}</View>
                )}
                {tripStatus === TripStatus.CallingCar && queueStatus && (
                    <View className='calling-car-content-desc'>
                        第{queueStatus.currentPosition}位/共{queueStatus.totalCount}位
                    </View>
                )}
              </View>
            </View>
            {
                [TripStatus.CallingCar].includes(tripStatus) &&
                <View className="cancel-btn">
                    <Image onClick={() => handleOpenCancelPopup?.({ durationDisplay, waitMinutes })} src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/cancel-btn.png' className="cancel" />
                </View>
            }
            <View
              className='calling-car-cancel-btn'
              onClick={() => handleOpenCancelPopup?.({ durationDisplay, waitMinutes: displayedWaitMinutes })}
            >
              取消行程
            </View>
          </View>
          <View className='calling-car-site-box'>
            <View className='calling-car-site-box-icon'>
              <Image className='calling-car-site-check'src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-golden-20260702.png' ></Image>
              <Image className='dotted-line' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png' />
              <Image className='calling-car-site-check' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-black-20260702.png'></Image>
            </View>
            <View className='calling-car-site-box-content'>
              <View>{startSiteName}</View>
              <View className='calling-car-site-box-divider'></View>
              <View>{endSiteName}</View>
            </View>
          </View>
        </>
      )}
      {/* 车辆即将到达:左右布局状态栏(图一) */}
      {tripStatus === TripStatus.VehicleApproaching && (
        <View className='calling-car-status-box'>
          <Image className='calling-car-status-box-come-icon' mode='widthFix' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/come-icon-20260728.webp'></Image>
          <View className='calling-car-status-box-left'>{isArriveSoon ? '即将抵达' : '正在赶来'}</View>
          <View className='calling-car-status-box-right'>
            {distanceText && <View className='distance'>{distanceText}</View>}
            <View className='time'>{approachingTimeText}</View>
          </View>
        </View>
    );
      )}
      {/* 车辆到达上车点:直接展示图片 */}
      {tripStatus === TripStatus.VehicleArrivedPickup && (
        <View className='calling-car-status-box calling-car-arrived-img'>
          <View className='calling-car-status-box-left'>车辆已到达上车点,倒计时结束将自动取消订单</View>
        </View>
      )}
      {/* 行程中:左右布局状态栏 */}
      {tripStatus === TripStatus.InService && (
        <View className='calling-car-status-box'>
          <Image className='calling-car-status-box-come-icon'  mode='widthFix' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/come-icon-20260728.webp'></Image>
          <View className='calling-car-status-box-left'>正在前往目的地</View>
          <View className='calling-car-status-box-right'>
            {distanceText && <View className='distance'>{distanceText}</View>}
            <View className='time'>{inServiceTimeText}</View>
          </View>
        </View>
      )}
    </>
  );
}