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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
import Taro from '@tarojs/taro';
import { useEffect, useRef } from 'react';
import { getWalkingRoute } from '../../../../utils/Robotaxi-public-method';
import { queryOrderRoute, queryOrderTrajectory, queryRouteAndFare, queryRoutePreferences } from '../../../api/begin-route';
import { ensureSelectedCityByLocation } from '../../../utils/city';
import { normalizeDistanceKm, normalizeDurationSeconds } from '../../../utils/format';
import { TripStatus } from '../../../constants/trip-status';
import { type RouteSnapshot } from './runtime-sync';
import {
  Deps,
  RequestRouteAndFareOptions,
  RouteDistanceValue,
  WALKING_ORIGIN_LOCATION_CACHE_DURATION_MS,
  WalkingOriginLocation,
} from './use-route-manager.shared';
 
/** API 特殊响应码:暂无可通行路线/获取导航信息失败 */
const API_CODE_NO_ROUTE = 100001;
 
/** 路线请求 Hook 入参。 */
type UseRouteRequestParams = {
  /** 路线管理依赖。 */
  deps: Deps;
  /** 当前路线选项集合。 */
  routeOptions: any[];
  /** 设置路线选项集合。 */
  setRouteOptions: React.Dispatch<React.SetStateAction<any[]>>;
  /** 当前选中的路线索引。 */
  selectedRouteIndex: number;
  /** 设置当前选中的路线索引。 */
  setSelectedRouteIndex: React.Dispatch<React.SetStateAction<number>>;
  /** 设置全量路线点集合。 */
  setAllRoutePoints: React.Dispatch<React.SetStateAction<any[][]>>;
  /** 当前步行路线点。 */
  walkingPoints: any[];
  /** 设置步行路线点。 */
  setWalkingPoints: React.Dispatch<React.SetStateAction<any[]>>;
  /** 设置步行时长。 */
  setWalkingTime: React.Dispatch<React.SetStateAction<number>>;
  /** 设置预估费用。 */
  setFareEstimate: React.Dispatch<React.SetStateAction<any>>;
  /** 设置路线错误标记。 */
  setRouteAndFareError: React.Dispatch<React.SetStateAction<boolean>>;
  /** 设置路线请求中标记。 */
  setRouteAndFareLoading: React.Dispatch<React.SetStateAction<boolean>>;
  /** 设置路线距离。 */
  setRouteDistance: React.Dispatch<React.SetStateAction<RouteDistanceValue>>;
  /** 设置路线时长。 */
  setRouteDuration: React.Dispatch<React.SetStateAction<number | null>>;
  /** 应用路线点。 */
  applyRoutePoints: (points: any[]) => boolean;
  /** 当前完整订单路线引用。 */
  currentFullRouteRef: React.MutableRefObject<any[]>;
  /** 最近一次擦除索引引用。 */
  lastErasedIndexRef: React.MutableRefObject<number>;
  /** 订单路线初始化引用。 */
  orderRouteInitializedRef: React.MutableRefObject<boolean>;
  /** 同步多路线视口。 */
  syncRouteViewport: (routePointGroups: any[][], nextWalkingPoints?: any[]) => void;
  /** 接口回显主路线后,同步初始化平滑擦线基线。 */
  syncSmoothRouteBaselineRef?: React.MutableRefObject<((snapshot: RouteSnapshot) => void) | null>;
  /** 最近一次由 socket 写入车辆朝向的时间。 */
  lastSocketDirectionAtRef?: React.MutableRefObject<number>;
};
 
/** 路线请求 Hook 返回值。 */
type UseRouteRequestResult = {
  /** 路线请求签名引用。 */
  routeRequestKeyRef: React.MutableRefObject<string>;
  /** 最近一次路线费用请求时间引用。 */
  lastRouteAndFareRequestAtRef: React.MutableRefObject<number>;
  /** 请求预估路线与费用。 */
  requestRouteAndFare: (
    originLat: number,
    originLng: number,
    destLat: number,
    destLng: number,
    startPointIdOrOptions?: string | number | boolean | RequestRouteAndFareOptions,
    forceArg?: boolean
  ) => Promise<void>;
  /** 请求订单实时路线。 */
  requestOrderRoute: (targetOrderId: string, status: TripStatus) => void;
  /** 更新步行路线。 */
  updateWalkingRouteForStart: (startSite: any) => Promise<void>;
};
 
/** 解析“上一单乘客下车点”坐标。 */
const parseBeforeUserEndSite = (endLongitudeValue: any, endLatitudeValue: any) => {
  const longitude = Number(endLongitudeValue);
  const latitude = Number(endLatitudeValue);
  if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
    return null;
  }
  return { longitude, latitude };
};
 
/** 统一解析方向值。 */
const resolveDirectionValue = (payload: any) => {
  const rawDirection = payload?.direction;
  if (rawDirection == null) return null;
  const direction = Number(rawDirection);
  if (!Number.isFinite(direction)) {
    return null;
  }
  return ((direction % 360) + 360) % 360;
};
 
/** 统一解析订单是否已开门。 */
const resolveDoorOpenedValue = (payload: any) => {
  const rawValue = payload?.isOpenDoor ?? payload?.content?.isOpenDoor;
  if (rawValue === true || rawValue === 1 || rawValue === '1' || rawValue === 'true') {
    return true;
  }
  return false;
};
 
/** 归一化路线选项,补齐校验字段,并保留响应最外层原价到独立字段。 */
const normalizeRouteOption = (
  item: any,
  snapshotId: any,
  generatedAtMillis: any,
  responseOriginalFare?: any,
  responseFreeText?: any
) => {
  const normalizedTopLevelOriginalFare = Number(responseOriginalFare);
  return {
    ...(item || {}),
    // 顶层免费文案(与顶层 operateFree 同级)兜底透传到每条路线,供免费态展示;路线自带 freeText 时优先使用
    freeText: item?.freeText ?? responseFreeText,
    fareEstimate: {
      ...(item?.fareEstimate || {}),
      ...(Number.isFinite(normalizedTopLevelOriginalFare)
        ? { topLevelOriginalFare: normalizedTopLevelOriginalFare }
        : {})
    },
    snapshotId: item?.snapshotId ?? snapshotId,
    generatedAtMillis: item?.generatedAtMillis ?? generatedAtMillis
  };
};
 
const WALKING_SPEED_METERS_PER_MINUTE = 75;
 
/** 当 socket 已提供较新的朝向时,接口方向只作为兜底,不再覆盖实时值。 */
const SOCKET_DIRECTION_PRIORITY_WINDOW_MS = 4000;
 
/** 将订单路线接口响应组装成平滑擦线基线快照。 */
const buildOrderRouteSnapshot = (
  response: any,
  points: Array<{ longitude: number; latitude: number }>,
  direction: number | null
): RouteSnapshot => ({
  points,
  distance: normalizeDistanceKm(Number(response?.distance)),
  duration: normalizeDurationSeconds(Number(response?.duration)),
  direction,
  timestamp: Date.now(),
  vehicleMoveAlongPoints: [],
  forceFullRedraw: true,
  eraseVehiclePointMode: 'first_then_served_last',
  normalVehiclePointMode: 'served_last_then_first'
});
 
/**
 * 提取路线稳定匹配键,优先使用规划策略值。
 */
const getRouteStableKey = (item: any) => {
  const routeRule = item?.primaryRoutePlanRule;
  if (routeRule !== undefined && routeRule !== null && String(routeRule) !== '') {
    return `rule:${String(routeRule)}`;
  }
  const routeId = item?.route?.routeID;
  if (routeId !== undefined && routeId !== null && String(routeId) !== '') {
    return `route:${String(routeId)}`;
  }
  return '';
};
 
/**
 * 归一化路线偏好列表。
 */
const normalizeRoutePreferenceOptions = (response: any) => {
  const list = Array.isArray(response)
    ? response
    : (Array.isArray(response?.list) ? response.list : (Array.isArray(response?.data) ? response.data : []));
  return list.map((item: any) => {
    const label = item?.name;
    const value = item?.amapStrategy;
    const code = item?.code;
    if (!label || value === undefined || value === null) {
      return null;
    }
    return {
      label: String(label),
      value: String(value),
      code: String(code),
      selected: item?.selected === true
    };
  }).filter(Boolean);
};
 
/**
 * 管理路线请求、订单轨迹请求与步行路线请求。
 */
export default function useRouteRequest(params: UseRouteRequestParams): UseRouteRequestResult {
  const {
    deps,
    routeOptions,
    setRouteOptions,
    selectedRouteIndex,
    setSelectedRouteIndex,
    setAllRoutePoints,
    walkingPoints,
    setWalkingPoints,
    setWalkingTime,
    setFareEstimate,
    setRouteAndFareError,
    setRouteAndFareLoading,
    setRouteDistance,
    setRouteDuration,
    applyRoutePoints,
    currentFullRouteRef,
    lastErasedIndexRef,
    orderRouteInitializedRef,
    syncRouteViewport,
    syncSmoothRouteBaselineRef,
    lastSocketDirectionAtRef
  } = params;
 
  /** 路线请求签名引用。 */
  const routeRequestKeyRef = useRef<string>('');
  /** 最近一次路线费用请求时间引用。 */
  const lastRouteAndFareRequestAtRef = useRef<number>(0);
  /** 步行起点定位缓存引用。 */
  const walkingOriginLocationRef = useRef<WalkingOriginLocation | null>(null);
  /** 最新路线选项引用,避免异步回包使用旧闭包状态。 */
  const latestRouteOptionsRef = useRef<any[]>(routeOptions);
  /** 最新选中路线索引引用,避免异步回包回退到旧索引。 */
  const latestSelectedRouteIndexRef = useRef<number>(selectedRouteIndex);
  /** 路线偏好缓存引用,避免重复请求。 */
  const routePreferenceCacheRef = useRef<Record<string, any[]>>({});
 
  useEffect(() => {
    latestRouteOptionsRef.current = routeOptions;
    latestSelectedRouteIndexRef.current = selectedRouteIndex;
  }, [routeOptions, selectedRouteIndex]);
 
  /**
   * 确保当前路线请求具备城市上下文。
   */
  const ensureSelectedCityForRoute = async (longitude: number, latitude: number) => (
    ensureSelectedCityByLocation(longitude, latitude)
  );
 
  /**
   * 获取步行路线起点定位,优先命中短时缓存。
   */
  const getWalkingOriginLocation = async () => {
    const cachedLocation = walkingOriginLocationRef.current;
    const now = Date.now();
    if (
      cachedLocation
      && now - cachedLocation.cachedAt <= WALKING_ORIGIN_LOCATION_CACHE_DURATION_MS
    ) {
      deps.setCurrentUserLocation?.({
        latitude: cachedLocation.latitude,
        longitude: cachedLocation.longitude
      });
      return cachedLocation;
    }
 
    const location = await Taro.getLocation({
      type: 'gcj02',
      isHighAccuracy: true,
      highAccuracyExpireTime: WALKING_ORIGIN_LOCATION_CACHE_DURATION_MS,
    } as any);
    const latitude = Number(location?.latitude);
    const longitude = Number(location?.longitude);
    if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
      return null;
    }
 
    const nextLocation: WalkingOriginLocation = { latitude, longitude, cachedAt: now };
    walkingOriginLocationRef.current = nextLocation;
    deps.setCurrentUserLocation?.({ latitude, longitude });
    return nextLocation;
  };
 
  /**
   * 根据最新已选路线,在新路线集合中解析应保留的索引。
   */
  const resolveSelectedRouteIndex = (options: any[]) => {
    if (!Array.isArray(options) || options.length === 0) {
      return -1;
    }
    const latestRouteOptions = latestRouteOptionsRef.current;
    const latestSelectedRouteIndex = latestSelectedRouteIndexRef.current;
    const currentSelectedRoute = latestRouteOptions[latestSelectedRouteIndex];
    const currentStableKey = getRouteStableKey(currentSelectedRoute);
    if (currentStableKey) {
      const matchedIndex = options.findIndex((item) => getRouteStableKey(item) === currentStableKey);
      if (matchedIndex !== -1) {
        return matchedIndex;
      }
    }
    const currentRouteId = currentSelectedRoute?.route?.routeID;
    if (currentRouteId !== undefined && currentRouteId !== null && String(currentRouteId) !== '') {
      const matchedIndex = options.findIndex((item) => String(item?.route?.routeID ?? '') === String(currentRouteId));
      if (matchedIndex !== -1) {
        return matchedIndex;
      }
    }
    return 0;
  };
 
  /**
   * 读取当前城市的路线偏好,优先命中内存缓存。
   */
  const getRoutePreferenceOptions = async () => {
    const openCityId = Number(Taro.getStorageSync('selectedCity')?.id || 0);
    const cacheKey = String(openCityId);
    if (routePreferenceCacheRef.current[cacheKey]) {
      return routePreferenceCacheRef.current[cacheKey];
    }
    try {
      const response = await queryRoutePreferences({ openCityId });
      const normalizedOptions = normalizeRoutePreferenceOptions(response);
      routePreferenceCacheRef.current[cacheKey] = normalizedOptions;
      return normalizedOptions;
    } catch (error) {
      routePreferenceCacheRef.current[cacheKey] = [];
      return [];
    }
  };
 
  /**
   * 首次请求路线时,按偏好策略确定默认选中项。
   */
  const resolveInitialSelectedRouteIndex = async (options: any[]) => {
    if (!Array.isArray(options) || options.length === 0) {
      return -1;
    }
    const preferenceOptions = await getRoutePreferenceOptions();
    const selectedPreferenceOption = preferenceOptions.find((item: any) => item?.selected === true);
    if (!selectedPreferenceOption) {
      return 0;
    }
    const matchedIndex = options.findIndex((item: any) => (
      String(item?.primaryRoutePlanRule ?? '') === String(selectedPreferenceOption.value)
    ));
    return matchedIndex >= 0 ? matchedIndex : 0;
  };
 
  /**
   * 请求预估路线与费用。
   */
  const requestRouteAndFare = async (
    originLat: number,
    originLng: number,
    destLat: number,
    destLng: number,
    startPointIdOrOptions: string | number | boolean | RequestRouteAndFareOptions = '',
    forceArg = false
  ) => {
    const resolvedOptions: RequestRouteAndFareOptions = {
      force: false,
      refreshMode: 'full',
      couponId: '',
      startPointId: '',
      syncOrderRouteMetrics: true,
    };
    if (typeof startPointIdOrOptions === 'boolean') {
      resolvedOptions.force = startPointIdOrOptions;
    } else if (
      typeof startPointIdOrOptions === 'object'
      && startPointIdOrOptions !== null
      && !Array.isArray(startPointIdOrOptions)
    ) {
      Object.assign(resolvedOptions, startPointIdOrOptions);
    } else {
      resolvedOptions.startPointId = startPointIdOrOptions ?? '';
      resolvedOptions.force = !!forceArg;
    }
 
    const force = !!resolvedOptions.force;
    const refreshMode = resolvedOptions.refreshMode || 'full';
    const startPointId = resolvedOptions.startPointId ?? '';
    const syncOrderRouteMetrics = resolvedOptions.syncOrderRouteMetrics !== false;
    const targetOrderId = String(resolvedOptions.orderId || '').trim();
 
    const now = Date.now();
    if (now - lastRouteAndFareRequestAtRef.current < 1000) {
      return;
    }
    lastRouteAndFareRequestAtRef.current = now;
 
    const requestKey = `${originLng},${originLat}-${destLng},${destLat}-${startPointId ?? ''}`;
    if (!force && requestKey === routeRequestKeyRef.current) {
      return;
    }
    routeRequestKeyRef.current = requestKey;
    setRouteAndFareLoading(true);
 
    try {
      const selectedCity = await ensureSelectedCityForRoute(originLng, originLat);
      const openCityId = selectedCity ? selectedCity.id : 0;
      const couponId = resolvedOptions.couponId || Taro.getStorageSync('selectedCouponId');
      const requestParams: any = {
        destination: {
          latitude: destLat,
          longitude: destLng
        },
        openCityId,
        origin: {
          latitude: originLat,
          longitude: originLng
        },
        startPointId,
      };
      if (couponId) {
        requestParams.userCouponId = couponId;
      }
      if (targetOrderId) {
        requestParams.orderId = targetOrderId;
      }
 
      const rawResponse = await queryRouteAndFare(requestParams);
 
      if (rawResponse && typeof rawResponse === 'object' && (rawResponse as any).code === API_CODE_NO_ROUTE) {
        deps.handleOpenNoRoutePopup?.();
        setRouteAndFareError(true);
        setRouteAndFareLoading(false);
        return;
      }
 
      const response = rawResponse;
      setRouteAndFareError(false);
 
      const responseRoutes = Array.isArray(response?.routes) ? response.routes : [];
      const responseSnapshotId = response?.snapshotId;
      const responseGeneratedAtMillis = response?.generatedAtMillis;
      const responseOriginalFare = response?.originalFare;
      const responseFreeText = response?.freeText;
 
      let options: any[] = [];
      if (responseRoutes.length > 0) {
        options = responseRoutes.map((item: any) => normalizeRouteOption(
          item,
          responseSnapshotId,
          responseGeneratedAtMillis,
          responseOriginalFare,
          responseFreeText
        ));
      } else if (Array.isArray(response) && response.length > 0) {
        options = response.map((item: any) => normalizeRouteOption(
          item,
          responseSnapshotId,
          responseGeneratedAtMillis,
          responseOriginalFare,
          responseFreeText
        ));
      } else if (response) {
        options = [normalizeRouteOption(response, responseSnapshotId, responseGeneratedAtMillis, responseOriginalFare, responseFreeText)];
      }
 
      let routeData = options[0] || null;
      let allRoutePointGroups: any[][] = [];
      if (refreshMode === 'price_only') {
        const resolvedSelectedRouteIndex = resolveSelectedRouteIndex(options);
        routeData = options[resolvedSelectedRouteIndex] || options[0] || null;
        if (options.length > 0) {
          setRouteOptions((previousRouteOptions) => {
            if (!Array.isArray(previousRouteOptions) || previousRouteOptions.length === 0) {
              return options;
            }
            const optionMap = new Map(
              options.map((item: any) => [getRouteStableKey(item), item])
            );
            return previousRouteOptions.map((oldItem: any, index: number) => {
              const matchedOption = optionMap.get(getRouteStableKey(oldItem)) || options[index];
              if (!matchedOption) {
                return oldItem;
              }
              return {
                ...oldItem,
                ...matchedOption,
                route: {
                  ...(oldItem?.route || {}),
                  ...(matchedOption?.route || {}),
                  polyline: oldItem?.route?.polyline || matchedOption?.route?.polyline || []
                }
              };
            });
          });
        }
      } else {
        if (options.length > 0) {
          const hasCurrentSelectedRoute = Array.isArray(latestRouteOptionsRef.current) && latestRouteOptionsRef.current.length > 0;
          const resolvedSelectedRouteIndex = hasCurrentSelectedRoute
            ? resolveSelectedRouteIndex(options)
            : await resolveInitialSelectedRouteIndex(options);
          setRouteOptions(options);
          setSelectedRouteIndex(resolvedSelectedRouteIndex);
          allRoutePointGroups = options.map((item) => (
            Array.isArray(item?.route?.polyline) ? item.route.polyline : []
          ));
          setAllRoutePoints(allRoutePointGroups);
          routeData = options[resolvedSelectedRouteIndex] || options[0] || null;
        } else {
          setRouteOptions([]);
          setAllRoutePoints([]);
        }
      }
 
      if (routeData?.fareEstimate) {
        setFareEstimate(routeData.fareEstimate);
      }
      if (routeData?.xpPricingCard) {
        Taro.setStorageSync('xpPricingCard', routeData.xpPricingCard);
      }
 
      const route = routeData?.route;
      const normalizedDistance = normalizeDistanceKm(Number(route?.distance));
      if (normalizedDistance !== null) {
        setRouteDistance(normalizedDistance);
      }
      const normalizedDuration = normalizeDurationSeconds(Number(route?.duration));
      if (normalizedDuration !== null) {
        setRouteDuration(normalizedDuration);
      }
      if (syncOrderRouteMetrics) {
        deps.setOrderRouteMetrics?.({
          distanceMeters: Number.isFinite(Number(route?.distance)) ? Number(route?.distance) : null,
          durationSeconds: Number.isFinite(Number(route?.duration)) ? Number(route?.duration) : null
        });
      }
 
      if (refreshMode === 'full') {
        const points = Array.isArray(route?.polyline) ? route.polyline : [];
        if (points.length > 0) {
          applyRoutePoints(points);
          syncRouteViewport(allRoutePointGroups, walkingPoints);
        } else {
          applyRoutePoints([]);
        }
      }
    } catch (error) {
      setRouteAndFareError(true);
    } finally {
      setRouteAndFareLoading(false);
    }
  };
 
  /**
   * 请求订单实时路线或完成态轨迹。
   */
  const requestOrderRoute = (targetOrderId: string, status: TripStatus) => {
    const routeType = status <= TripStatus.VehicleArrivedPickup ? 1 : 2;
    const requestHandler = [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(status)
      ? queryOrderTrajectory
      : queryOrderRoute;
 
    requestHandler({ orderId: targetOrderId, routeType })
      .then((response: any) => {
        deps.syncDoorUnlockedStateFromOrderRoute?.(resolveDoorOpenedValue(response));
        const beforeUserSite = parseBeforeUserEndSite(
          response?.endLongitude,
          response?.endLatitude ?? response?.endlatitude
        );
        deps.setBeforeUserEndSite?.(beforeUserSite);
        const hasPreviousOrderValue = Boolean(response?.hasPreviousOrder);
        deps.setHasPreviousOrder?.(hasPreviousOrderValue);
        Taro.setStorageSync('hasPreviousOrder', { orderId: targetOrderId, value: hasPreviousOrderValue });
 
        const rawPoints = Array.isArray(response?.polyline) ? response.polyline : [];
        const points = rawPoints
          .map((item: any) => ({
            longitude: Number(item?.longitude),
            latitude: Number(item?.latitude),
          }))
          .filter((item: any) => Number.isFinite(item.longitude) && Number.isFinite(item.latitude));
 
        if (points.length > 0) {
          if (status === TripStatus.VehicleArrivedPickup) {
            const lastPoint = points[points.length - 1];
            deps.updateVehicleLocationThrottled(lastPoint.latitude, lastPoint.longitude, true);
          } else {
            applyRoutePoints(points);
            const targetPoint = status === TripStatus.ArrivedDestination
              ? points[points.length - 1]
              : points[0];
            deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
            if ([TripStatus.VehicleApproaching, TripStatus.InService].includes(status)) {
              currentFullRouteRef.current = [...points];
              lastErasedIndexRef.current = 0;
              orderRouteInitializedRef.current = true;
            }
          }
        } else if (status === TripStatus.ArrivedDestination) {
          const cachedEndSite = Taro.getStorageSync('endSite') || {};
          const fallbackLatitude = Number(cachedEndSite?.latitude);
          const fallbackLongitude = Number(cachedEndSite?.longitude);
          if (Number.isFinite(fallbackLatitude) && Number.isFinite(fallbackLongitude)) {
            deps.updateVehicleLocationThrottled(fallbackLatitude, fallbackLongitude, true);
          }
        }
 
        const normalizedDistance = normalizeDistanceKm(Number(response?.distance));
        if (normalizedDistance !== null) {
          setRouteDistance(normalizedDistance);
        }
        const normalizedDuration = normalizeDurationSeconds(Number(response?.duration));
        if (normalizedDuration !== null) {
          setRouteDuration(normalizedDuration);
        }
 
        deps.setOrderRouteMetrics?.({
          distanceMeters: Number.isFinite(Number(response?.distance)) ? Number(response?.distance) : null,
          durationSeconds: Number.isFinite(Number(response?.duration)) ? Number(response?.duration) : null
        });
 
        const normalizedDirection = resolveDirectionValue(response);
        const hasFreshSocketDirection = Date.now() - Number(lastSocketDirectionAtRef?.current || 0) < SOCKET_DIRECTION_PRIORITY_WINDOW_MS;
        const shouldUseSocketDirectionOnly = [TripStatus.VehicleApproaching, TripStatus.InService].includes(status);
        const fallbackDirection = (hasFreshSocketDirection || shouldUseSocketDirectionOnly) ? null : normalizedDirection;
        if (typeof fallbackDirection === 'number') {
          deps.setVehicleDirection(fallbackDirection);
        }
        if ([TripStatus.VehicleApproaching, TripStatus.InService].includes(status) && points.length > 0) {
          const routeSnapshot = buildOrderRouteSnapshot(response, points, fallbackDirection);
          syncSmoothRouteBaselineRef?.current?.(routeSnapshot);
        }
      })
      .catch(() => { });
  };
 
  /**
   * 更新“当前位置 -> 上车点”的步行路线。
   */
  const updateWalkingRouteForStart = async (startSite: any) => {
    try {
      const walkingOriginLocation = await getWalkingOriginLocation();
      const originLatitude = walkingOriginLocation?.latitude;
      const originLongitude = walkingOriginLocation?.longitude;
      const targetLatitude = startSite?.latitude;
      const targetLongitude = startSite?.longitude;
      if (!originLatitude || !originLongitude || !targetLatitude || !targetLongitude) {
        setWalkingPoints([]);
        setWalkingTime(0);
        return;
      }
      const origin = `${originLongitude},${originLatitude}`;
      const destination = `${targetLongitude},${targetLatitude}`;
      const { points, duration } = await getWalkingRoute(origin, destination);
      if (Array.isArray(points) && points.length > 0) {
        setWalkingPoints(points);
        setWalkingTime(Math.ceil(Number(duration) / 60));
        syncRouteViewport([], points);
        return;
      }
 
      setWalkingPoints([]);
      setWalkingTime(0);
    } catch (error) {
      setWalkingPoints([]);
      setWalkingTime(0);
    }
  };
 
  return {
    routeRequestKeyRef,
    lastRouteAndFareRequestAtRef,
    requestRouteAndFare,
    requestOrderRoute,
    updateWalkingRouteForStart
  };
}