zjk
2026-07-29 429208957f30ab9eedc5533a81aaa284048d09f5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
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 | 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;
  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,
  routeDistance = null,
  handleOpenCancelPopup,
  queuePollingTimeSeconds
}: Props) {
  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;
    }
 
    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);
        }
      }
    };
 
    fetchQueueStatus();
    timer = setInterval(fetchQueueStatus, pollingIntervalMs);
 
    return () => {
      unmounted = true;
      if (timer) {
        clearInterval(timer);
      }
    };
  }, [tripStatus, orderId, 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]: '支付成功'
  // }
 
  // 说明文案映射(已注释)
  // 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' }
  // }
 
  // 站点信息(图二):与 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 distanceText = routeDistance ? `${routeDistance.value}${routeDistance.unit === 'm' ? 'M' : 'KM'}` : '';
  const approachingTimeText = `${normalizedDurationDisplay.value}MIN`;
  const inServiceTimeText = `${normalizedDurationDisplay.value}MIN`;
 
  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}位
                  </>
                )}
              </View>
            </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>
      )}
    </>
  );
}