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
|
};
|
}
|