import Taro, { useDidShow, useDidHide } from "@tarojs/taro"
|
import { useEffect, useState, useRef, useMemo } from 'react';
|
import { View, Image, CoverView, CoverImage } from '@tarojs/components';
|
import './index.less';
|
|
import ReleaseRoute from './components/release-route';
|
import ReleaseBeBeing from './components/release-be-being';
|
import CallingCar from './components/calling-car';
|
import BeginRouteView from './components/begin-route-view';
|
import usePopupManager from './hooks/use-popup-manager';
|
import useWebSocketEvents from './hooks/use-web-socket-events';
|
|
import backIcon from '../../../robotaxi/assets/back-icon.png'
|
import contactService from '../../../robotaxi/assets/contact-service.png'
|
import coupon from '../../../robotaxi/assets/coupon.png'
|
import suggest from '../../../robotaxi/assets/suggest.png'
|
import originIcon from '../../../robotaxi/assets/origin-icon.png'
|
import wsManager from '../../../utils/websocket';
|
import {
|
getWalkingRoute,
|
formatDateTime,
|
clearOrderTripStorage,
|
preloadImageToTemp,
|
debounce,
|
computeAdaptiveBottomPadding,
|
getAddressByLocation,
|
convertAdcodeToCityCode,
|
getCityInfoWithId,
|
getDistance,
|
getProjectedPoint,
|
mapOrderStatusToTripStatus,
|
reportEvent,
|
formatDurationDisplay
|
} from '../../../utils/Robotaxi-public-method';
|
import {
|
saveOrder,
|
queryRouteAndFare,
|
queryVehicleByOrderId,
|
getOrderPayment,
|
cancelOrder,
|
queryOrderRoute,
|
queryOrderTrajectory,
|
endOrder,
|
} from '../../../robotaxi/api/begin-route';
|
import { getOrderActive } from '../../../robotaxi/api/home';
|
import { getOrderDetail } from '../../../robotaxi/api/order';
|
import { getOrderDistance } from '../../api/select-map-site';
|
import { checkOtp } from '../../../utils/tools';
|
import { jumpTo } from '../../../utils/jump';
|
import { _ROBOTAXI_WS_HOST } from '../../../constants/api';
|
|
export default function SelectMapSite() {
|
const WAITING_TIMER_START_KEY = 'waitingTimerStartAt'; // 等待计时开始时间
|
const WAITING_TIMER_ELAPSED_KEY = 'waitingTimerElapsed'; // 等待计时已过秒数
|
const REQUOTE_EXPIRE_SECONDS = 10 * 60; // 10分钟
|
|
const {
|
showCustomerService,
|
setShowCustomerService,
|
handleCloseCustomerService,
|
showCancelPopup,
|
setShowCancelPopup,
|
handleCloseCancelPopup,
|
cancelType,
|
setCancelType,
|
showAnnouncementsPopup,
|
setShowAnnouncementsPopup,
|
handleCloseAnnouncementsPopup,
|
handleOpenAnnouncementsPopup,
|
showOverRoutePopup,
|
setShowOverRoutePopup,
|
handleCloseOverRoutePopup,
|
handleOpenOverRoutePopup,
|
showPassengerNotice,
|
setShowPassengerNotice,
|
handleClosePassengerNotice,
|
handleOpenPassengerNotice,
|
showEvaluatePopup,
|
setShowEvaluatePopup,
|
handleOpenEvaluatePopup,
|
showTipPopup,
|
setShowTipPopup,
|
handleCloseTipPopup,
|
tipType,
|
setTipType
|
} = usePopupManager();
|
const [longitude, setLongitude] = useState<number | null>(null); // 经度
|
const [latitude, setLatitude] = useState<number | null>(null); // 纬度
|
const [markers, setMarkers] = useState<any[]>([]); // 标记点列表
|
const [polyline, setPolyline] = useState<any[]>([]); // 路线
|
const [startSite, setStartSite] = useState<any>(null); // 上车点
|
const [endSite, setEndSite] = useState<any>({}); // 下车点
|
const [tripStatus, setTripStatus] = useState<number>(Number(Taro.getStorageSync('tripStatus')) || 1); // 行程状态 1:发布行程 2:发布中 3:呼叫车中 4:车辆即将到达 5:车辆到达上车点 6:车辆行驶中 7:到达目的地 8:行程已结束 9:支付成功 10:取消订单时页面只展示一个上车点
|
const [elapsedSeconds, setElapsedSeconds] = useState(0); // 等待计时秒数
|
const requoteTriggeredRef = useRef(false); // 重新报价触发标记,防止重复触发
|
const [routePoints, setRoutePoints] = useState<any[]>([]); // 路线点集合
|
const [walkingPoints, setWalkingPoints] = useState<any[]>([]); // 步行路径点
|
const [walkingTime, setWalkingTime] = useState<number>(0); // 步行到上车点的预估时间(秒)
|
const carMarkerInitialized = useRef(false); // 车辆标记是否已初始化
|
const [fareEstimate, setFareEstimate] = useState<any>({}); // 预估费用信息
|
const [routeAndFareError, setRouteAndFareError] = useState(false); // 路线费用查询是否出错
|
const [callSubmitting, setCallSubmitting] = useState(false); // 呼叫车辆提交中状态
|
const [vehicleLocation, setVehicleLocation] = useState<{ latitude: number, longitude: number } | null>(null); // 车辆当前位置坐标
|
const [vehicleDirection, setVehicleDirection] = useState<number | null>(null); // 车辆航向角(0-360度)
|
const [routeDistance, setRouteDistance] = useState<{ value: number; unit: 'km' | 'm' } | null>(null); // 路线距离
|
const [routeDuration, setRouteDuration] = useState<number | null>(null); // 路线时间
|
const [vehicleInfo, setVehicleInfo] = useState<any>({}); // 车辆信息
|
const [orderId, setOrderId] = useState<string>(''); // 订单ID
|
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/maker-tooltip-bg.png'; // 等待气泡背景图URL
|
const waitingRadarUrl = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/waiting-radar.webp'; // 等待雷达动画图URL
|
const [customCalloutIconSrc, setCustomCalloutIconSrc] = useState(customCalloutIconUrl); // 自定义气泡图标源(本地缓存路径)
|
const [waitingCalloutBgSrc, setWaitingCalloutBgSrc] = useState(waitingCalloutBgUrl); // 等待气泡背景图源
|
const [waitingRadarSrc, setWaitingRadarSrc] = useState(waitingRadarUrl); // 等待雷达动画图源
|
const [waitingRadarStyle, setWaitingRadarStyle] = useState<any>({}); // 等待雷达动画样式
|
const [waitingRadarVisible, setWaitingRadarVisible] = useState(true); // 等待雷达动画是否可见
|
const [resetDetailVersion, setResetDetailVersion] = useState(0); // 重置详情版本号,用于触发组件更新
|
const [priceUpdateVersion, setPriceUpdateVersion] = useState(0); // 价格更新版本号
|
const [evaluateSubmitVersion, setEvaluateSubmitVersion] = useState(0); // 评价提交版本号
|
const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0); // 评价关闭版本号
|
const [originOpacity, setOriginOpacity] = useState(1); // 起点标记透明度
|
const [originBoxHidden, setOriginBoxHidden] = useState(false); // 起点信息框是否隐藏
|
const [mapBottomPadding, setMapBottomPadding] = useState(280); // 地图底部内边距(像素)
|
const [carCalloutStyle, setCarCalloutStyle] = useState<any>({}); // 车辆气泡标注样式
|
const [carCalloutVisible, setCarCalloutVisible] = useState(false); // 车辆气泡标注是否可见
|
const [arriveCalloutStyle, setArriveCalloutStyle] = useState<any>({}); // 到达气泡标注样式
|
const [arriveCalloutVisible, setArriveCalloutVisible] = useState(false); // 到达气泡标注是否可见
|
const [distanceConfig, setDistanceConfig] = useState<any>({}); // 距离显示配置
|
const [mapScale, setMapScale] = useState(14); // 地图缩放级别
|
const [showLocationFlag, setShowLocationFlag] = useState(true); // 是否显示定位按钮
|
const [trunkStatus, setTrunkStatus] = useState(false); // 后备箱开启状态
|
|
const markersRef = useRef<any[]>([]); // 标记点列表引用,用于实时访问最新值
|
const tripStatusRef = useRef(tripStatus); // 行程状态引用
|
const lastTripStatusRef = useRef(tripStatus); // 上一次行程状态,用于检测状态变化
|
const tripStatusForMapSyncRef = useRef(tripStatus); // 用于地图同步的行程状态引用
|
const routeRequestKeyRef = useRef<string>(''); // 路线请求键
|
const orderRouteInitializedRef = useRef(false); // 订单路线是否已初始化
|
// 行程状态显示规则配置:showPickup-显示上车点,showDropoff-显示下车点,showCar-显示车辆,showRoute-显示路线,showWalking-显示步行路线,viewport-视口类型,translateCar-车辆是否平移
|
const STATUS_RULES: Record<number, any> = {
|
1: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: true, viewport: 'route_walking', translateCar: false },
|
2: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
|
3: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
|
4: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
|
5: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
|
6: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
|
7: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'dropoff', translateCar: false },
|
8: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
|
9: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
|
10: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
|
};
|
const statusRule = useMemo(() => STATUS_RULES[tripStatus] || STATUS_RULES[1], [tripStatus]); // 当前行程状态对应的显示规则
|
const shouldShowRoute = statusRule.showRoute; // 当前状态是否显示路线
|
const mapRectRef = useRef<any>(null); // 地图边界矩形区域引用
|
const viewportKeyRef = useRef<string>(''); // 视口唯一键,用于标识视口状态
|
const tripStatus6ViewportLockedRef = useRef(false); // 行程状态6视口是否锁定
|
const tripStatus6MarkerRefreshedRef = useRef(false); // 行程状态6标记是否已刷新
|
const polylineKeyRef = useRef<string>(''); // 路线唯一键
|
const orderDetailRouteRequestIdRef = useRef(0); // 订单详情路线请求ID
|
const lastRoutePointRef = useRef<{ latitude: number; longitude: number } | null>(null); // 最后一次路线点位置
|
const lastRouteUpdateAtRef = useRef<number>(0); // 最后路线更新时间戳
|
const lastVehiclePointRef = useRef<{ latitude: number; longitude: number } | null>(null); // 最后一次车辆位置
|
const lastVehicleUpdateAtRef = useRef<number>(0); // 最后车辆位置更新时间戳
|
const lastPolylineUpdateAtRef = useRef<number>(0); // 最后路线绘制更新时间戳
|
const lastRouteAndFareRequestAtRef = useRef<number>(0); // 最后路线费用请求时间戳
|
const markersSignatureRef = useRef<string>(''); // 标记点签名,用于检测变化
|
const currentFullRouteRef = useRef<any[]>([]); // 当前完整路线点集合
|
const lastErasedIndexRef = useRef<number>(0); // 最后擦除的路线点索引
|
const isAndroid = useMemo(() => {
|
try {
|
const info = Taro.getSystemInfoSync();
|
console.log('机型:', info);
|
return info?.platform === 'android' || /android/i.test(info?.system || '');
|
} catch (error) {
|
return false;
|
}
|
}, []);
|
const shouldShowWaitingRadar = isAndroid && [2, 3].includes(tripStatus); // 是否显示等待雷达动画(Android且状态2/3)
|
const vehicleMarkerId = !isAndroid && tripStatus === 6 ? 30 : 3; // 车辆标记ID,iOS状态6使用不同ID
|
const isCheckedEnter = useRef(false); // 是否已检查页面进入状态
|
const router = Taro.useRouter(); // 路由对象
|
|
useEffect(() => {
|
const wasHold = [4, 6].includes(lastTripStatusRef.current);
|
const isHold = [4, 6].includes(tripStatus);
|
if ([6, 7].includes(tripStatus)) {
|
setShowLocationFlag(false);
|
} else {
|
setShowLocationFlag(true);
|
}
|
if (tripStatus != 4) {
|
Taro.removeStorageSync('pickupEndTime')
|
}
|
if (tripStatus != 7) {
|
Taro.removeStorageSync('finishEndTime')
|
}
|
if (!wasHold && isHold) {
|
orderRouteInitializedRef.current = false;
|
tripStatus6ViewportLockedRef.current = false;
|
tripStatus6MarkerRefreshedRef.current = false;
|
currentFullRouteRef.current = [];
|
}
|
if (!isHold) {
|
tripStatus6ViewportLockedRef.current = false;
|
tripStatus6MarkerRefreshedRef.current = false;
|
}
|
lastTripStatusRef.current = tripStatus;
|
}, [tripStatus]);
|
|
useEffect(() => {
|
markersRef.current = markers;
|
}, [markers]);
|
|
useEffect(() => {
|
setFinishCountdownEnabled(false);
|
}, [orderId]);
|
|
const checkAndShowPassengerNotice = () => {
|
const checkedTimeRaw = Taro.getStorageSync('passengerNoticeCheckedTime');
|
if (!checkedTimeRaw) {
|
setShowPassengerNotice(true);
|
return;
|
}
|
const checkedTime = Number(checkedTimeRaw);
|
const THREE_MONTH_MS = 3 * 30 * 24 * 60 * 60 * 1000;
|
if (Number.isFinite(checkedTime) && Date.now() - checkedTime > THREE_MONTH_MS) {
|
setShowPassengerNotice(true);
|
Taro.removeStorageSync('passengerNoticeCheckedTime');
|
}
|
};
|
|
// 加载等待弹窗图片
|
useEffect(() => {
|
if (tripStatus === 1) {
|
checkAndShowPassengerNotice();
|
}
|
if (![2, 3].includes(tripStatus)) return;
|
let cancelled = false;
|
const load = async (url: string, setter: (value: string) => void) => {
|
const local = await preloadImageToTemp(url);
|
if (!cancelled) setter(local);
|
}
|
load(waitingCalloutBgUrl, setWaitingCalloutBgSrc);
|
load(waitingRadarUrl, setWaitingRadarSrc);
|
return () => {
|
cancelled = true;
|
};
|
}, [tripStatus]);
|
|
// 页面初始化预下载自定义气泡图标
|
useEffect(() => {
|
let cancelled = false;
|
const run = async () => {
|
const local = await preloadImageToTemp(customCalloutIconUrl);
|
if (!cancelled) setCustomCalloutIconSrc(local);
|
};
|
run();
|
return () => { cancelled = true; };
|
}, []);
|
// 更新等待雷达位置
|
const updateWaitingRadarPosition = () => {
|
if (!shouldShowWaitingRadar) {
|
return;
|
}
|
const s = Taro.getStorageSync('startSite');
|
if (!s?.latitude || !s?.longitude) {
|
return;
|
}
|
const mapCtx = Taro.createMapContext('map');
|
const resolveRect = (rect: any) => {
|
if (!rect) {
|
return;
|
}
|
mapCtx.getRegion({
|
success: (res) => {
|
const sw = res?.southwest;
|
const ne = res?.northeast;
|
const latRange = ne?.latitude - sw?.latitude;
|
const lngRange = ne?.longitude - sw?.longitude;
|
if (!latRange || !lngRange) {
|
return;
|
}
|
const x = ((s.longitude - sw.longitude) / lngRange) * rect.width;
|
const y = ((ne.latitude - s.latitude) / latRange) * rect.height;
|
setWaitingRadarStyle({
|
position: 'fixed',
|
left: `${rect.left + x}px`,
|
top: `${rect.top + y}px`,
|
transform: 'translate(-50%, -55%)'
|
});
|
}
|
});
|
};
|
if (mapRectRef.current) {
|
resolveRect(mapRectRef.current);
|
return;
|
}
|
Taro.createSelectorQuery()
|
.select('#map')
|
.boundingClientRect((rect) => {
|
if (rect) {
|
mapRectRef.current = rect;
|
}
|
resolveRect(rect);
|
})
|
.exec();
|
};
|
|
const updateCarCalloutPosition = () => {
|
if (!isAndroid || !vehicleLocation || ![4, 6].includes(tripStatus)) {
|
setCarCalloutVisible(false);
|
return;
|
}
|
const mapCtx = Taro.createMapContext('map');
|
const resolveRect = (rect: any) => {
|
if (!rect) {
|
return;
|
}
|
mapCtx.getRegion({
|
success: (res) => {
|
const sw = res?.southwest;
|
const ne = res?.northeast;
|
const latRange = ne?.latitude - sw?.latitude;
|
const lngRange = ne?.longitude - sw?.longitude;
|
if (!latRange || !lngRange) {
|
return;
|
}
|
const x = ((vehicleLocation.longitude - sw.longitude) / lngRange) * rect.width;
|
const y = ((ne.latitude - vehicleLocation.latitude) / latRange) * rect.height;
|
setCarCalloutStyle({
|
position: 'fixed',
|
left: `${rect.left + x}px`,
|
top: `${rect.top + y + 60}px`,
|
transform: 'translate(-50%, -120%)'
|
});
|
setCarCalloutVisible(true);
|
}
|
});
|
};
|
if (mapRectRef.current) {
|
resolveRect(mapRectRef.current);
|
return;
|
}
|
Taro.createSelectorQuery()
|
.select('#map')
|
.boundingClientRect((rect) => {
|
if (rect) {
|
mapRectRef.current = rect;
|
}
|
resolveRect(rect);
|
})
|
.exec();
|
};
|
const updateArriveCalloutPosition = () => {
|
if (!isAndroid) {
|
return;
|
}
|
if (![7].includes(tripStatus)) {
|
setArriveCalloutVisible(false);
|
return;
|
}
|
const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
|
if (!dropoffSite?.latitude || !dropoffSite?.longitude) {
|
setArriveCalloutVisible(false);
|
return;
|
}
|
const mapCtx = Taro.createMapContext('map');
|
const resolveRect = (rect: any) => {
|
if (!rect) {
|
return;
|
}
|
mapCtx.getRegion({
|
success: (res) => {
|
const sw = res?.southwest;
|
const ne = res?.northeast;
|
const latRange = ne?.latitude - sw?.latitude;
|
const lngRange = ne?.longitude - sw?.longitude;
|
if (!latRange || !lngRange) {
|
return;
|
}
|
const x = ((dropoffSite.longitude - sw.longitude) / lngRange) * rect.width;
|
const y = ((ne.latitude - dropoffSite.latitude) / latRange) * rect.height;
|
setArriveCalloutStyle({
|
position: 'fixed',
|
left: `${rect.left + x}px`,
|
top: `${rect.top + y - 40}px`,
|
transform: 'translate(-50%, -120%)'
|
});
|
setArriveCalloutVisible(true);
|
}
|
});
|
};
|
if (mapRectRef.current) {
|
resolveRect(mapRectRef.current);
|
return;
|
}
|
Taro.createSelectorQuery()
|
.select('#map')
|
.boundingClientRect((rect) => {
|
if (rect) {
|
mapRectRef.current = rect;
|
}
|
resolveRect(rect);
|
})
|
.exec();
|
};
|
|
// 更新等待雷达位置
|
useEffect(() => {
|
updateWaitingRadarPosition();
|
}, [shouldShowWaitingRadar, startSite?.latitude, startSite?.longitude, longitude, latitude]);
|
|
useEffect(() => {
|
updateCarCalloutPosition();
|
}, [isAndroid, vehicleLocation?.latitude, vehicleLocation?.longitude, tripStatus]);
|
useEffect(() => {
|
updateArriveCalloutPosition();
|
}, [isAndroid, tripStatus, endSite?.latitude, endSite?.longitude]);
|
|
const resolveSafeBottomInset = () => {
|
try {
|
const info = Taro.getSystemInfoSync();
|
const screenHeight = info?.screenHeight;
|
const safeBottom = info?.safeArea?.bottom;
|
if (Number.isFinite(screenHeight) && Number.isFinite(safeBottom)) {
|
return Math.max(0, screenHeight - safeBottom);
|
}
|
} catch (error) {
|
return 0;
|
}
|
return 0;
|
};
|
|
const resolveSafeTopInset = () => {
|
try {
|
const info = Taro.getSystemInfoSync();
|
const safeTop = info?.safeArea?.top;
|
if (Number.isFinite(safeTop)) {
|
return Math.max(0, safeTop);
|
}
|
} catch (error) {
|
return 0;
|
}
|
return 0;
|
};
|
|
// 更新地图视口内边距
|
const updateMapViewportPadding = () => {
|
Taro.nextTick(() => {
|
const query = Taro.createSelectorQuery();
|
query.select('.container').boundingClientRect();
|
query.select('.origin-box').boundingClientRect();
|
query.select('.map-entry').boundingClientRect();
|
query.exec((res) => {
|
const rect = res?.[0];
|
const originRect = res?.[1];
|
const mapEntryRect = res?.[2];
|
const safeInset = resolveSafeBottomInset();
|
const measured = rect?.height ? rect.height + safeInset + 16 : 0;
|
const nextPadding = Math.max(280, Math.round(measured));
|
setMapBottomPadding(nextPadding);
|
if (originRect && mapEntryRect) {
|
const originLeft = originRect.left ?? 0;
|
const originTop = originRect.top ?? 0;
|
const originRight = originLeft + (originRect.width ?? 0);
|
const originBottom = originTop + (originRect.height ?? 0);
|
const entryLeft = mapEntryRect.left ?? 0;
|
const entryTop = mapEntryRect.top ?? 0;
|
const entryRight = entryLeft + (mapEntryRect.width ?? 0);
|
const entryBottom = entryTop + (mapEntryRect.height ?? 0);
|
const overlap = !(originRight < entryLeft || originLeft > entryRight || originBottom < entryTop || originTop > entryBottom);
|
setOriginBoxHidden(overlap);
|
} else {
|
setOriginBoxHidden(false);
|
}
|
});
|
});
|
};
|
|
const computeBottomPadding = (points: any[], viewportType: string) => {
|
const basePadding = viewportType === 'order_detail' ? Math.max(200, mapBottomPadding) : mapBottomPadding;
|
return computeAdaptiveBottomPadding(points, basePadding);
|
};
|
|
// 应用路线点
|
const applyRoutePoints = (points: any[]) => {
|
if (Array.isArray(points) && points.length > 0) {
|
const firstPoint = points[0];
|
const newLat = Number(firstPoint?.latitude);
|
const newLng = Number(firstPoint?.longitude);
|
const now = Date.now();
|
|
if (Number.isFinite(newLat) && Number.isFinite(newLng)) {
|
lastRoutePointRef.current = { latitude: newLat, longitude: newLng };
|
lastRouteUpdateAtRef.current = now;
|
} else {
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
}
|
setRoutePoints(points);
|
return true;
|
} else {
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
setRoutePoints([]);
|
return true;
|
}
|
};
|
|
// 使用 eraseLines 擦除已行驶路段(状态4/6专用)
|
const erasePassedRouteSegments = (newPoints: any[]) => {
|
const oldPoints = currentFullRouteRef.current;
|
if (!oldPoints?.length || !newPoints?.length) return;
|
|
const newFirst = newPoints[0];
|
let bestMatchIndex = -1;
|
let bestDistance = Infinity;
|
const startIndex = lastErasedIndexRef.current;
|
|
for (let i = startIndex; i < oldPoints.length; i++) {
|
const dist = getDistance(
|
oldPoints[i].latitude, oldPoints[i].longitude,
|
newFirst.latitude, newFirst.longitude
|
);
|
if (dist < bestDistance) {
|
bestDistance = dist;
|
bestMatchIndex = i;
|
}
|
// 已找到足够近的匹配点且距离开始增大,提前退出
|
if (dist > bestDistance * 3 && bestDistance < 100) break;
|
}
|
|
if (bestMatchIndex >= startIndex && bestDistance < 500) {
|
const mapCtx = Taro.createMapContext('map');
|
|
const eraseIndex = Math.max(0, bestMatchIndex - 1);
|
|
mapCtx.eraseLines({
|
lines: [{
|
id: 2001,
|
index: eraseIndex,
|
point: {
|
latitude: newFirst.latitude,
|
longitude: newFirst.longitude,
|
},
|
clear: true
|
}],
|
success: (res) => {
|
console.log('擦除路线成功', res);
|
},
|
fail: (err) => {
|
console.error('擦除路线失败', err);
|
}
|
});
|
|
lastErasedIndexRef.current = bestMatchIndex;
|
} else {
|
// 路线发生较大变化,回退为全量重绘
|
currentFullRouteRef.current = [...newPoints];
|
lastErasedIndexRef.current = 0;
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
setRoutePoints([...newPoints]);
|
}
|
};
|
|
// 更新车辆位置(移除节流,实时更新)
|
const updateVehicleLocationThrottled = (lat: number, lng: number, force = false) => {
|
const now = Date.now();
|
if (Number.isFinite(lat) && Number.isFinite(lng)) {
|
lastVehiclePointRef.current = { latitude: lat, longitude: lng };
|
lastVehicleUpdateAtRef.current = now;
|
setVehicleLocation({ latitude: lat, longitude: lng });
|
}
|
};
|
|
const reducePoints = (points: any[], maxCount: number) => {
|
if (!Array.isArray(points) || points.length <= maxCount) {
|
return points;
|
}
|
const step = Math.ceil(points.length / maxCount);
|
const reduced = [];
|
for (let i = 0; i < points.length; i += step) {
|
reduced.push(points[i]);
|
}
|
const last = points[points.length - 1];
|
if (reduced[reduced.length - 1] !== last) {
|
reduced.push(last);
|
}
|
return reduced;
|
};
|
|
// 重置路线渲染运行时状态,避免状态切换时展示残留轨迹
|
const resetRouteRuntimeState = () => {
|
setRoutePoints([]);
|
setPolyline([]);
|
polylineKeyRef.current = '';
|
lastPolylineUpdateAtRef.current = 0;
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
lastVehiclePointRef.current = null;
|
lastVehicleUpdateAtRef.current = 0;
|
currentFullRouteRef.current = [];
|
lastErasedIndexRef.current = 0;
|
orderRouteInitializedRef.current = false;
|
};
|
|
// 归一化距离(公里或米)
|
const normalizeDistanceKm = (value: any) => {
|
const num = Number(value);
|
if (Number.isNaN(num)) {
|
return null;
|
}
|
if (num >= 1000) {
|
return { value: Number((num / 1000).toFixed(1)), unit: 'km' as const };
|
}
|
return { value: Math.max(0, Math.round(num)), unit: 'm' as const };
|
};
|
|
// 归一化时间(秒)
|
const normalizeDurationSeconds = (value: any) => {
|
const num = Number(value);
|
if (Number.isNaN(num)) {
|
return null;
|
}
|
return Math.round(num);
|
};
|
|
const ensureSelectedCityForRoute = async (lng: number, lat: number) => {
|
const cachedCity = Taro.getStorageSync('selectedCity');
|
if (cachedCity?.id && Number(cachedCity.id) !== 0) {
|
return cachedCity;
|
}
|
try {
|
const data = await getAddressByLocation(lng, lat);
|
const addressComponent = data?.regeocodeData?.addressComponent;
|
if (addressComponent) {
|
const { city, province, adcode } = addressComponent;
|
const cityName = (city && typeof city === 'string' && city.length > 0) ? city : province;
|
const cityCode = convertAdcodeToCityCode(adcode, cityName, province);
|
const cityInfo = await getCityInfoWithId(cityCode, cityName);
|
Taro.setStorageSync('selectedCity', cityInfo);
|
return cityInfo;
|
}
|
} catch (error) {
|
}
|
return cachedCity;
|
};
|
|
// 请求路线和车费
|
const requestRouteAndFare = async (originLat: number, originLng: number, destLat: number, destLng: number, force = false) => {
|
const now = Date.now();
|
if (now - lastRouteAndFareRequestAtRef.current < 1000) {
|
return;
|
}
|
lastRouteAndFareRequestAtRef.current = now;
|
const requestKey = `${originLng},${originLat}-${destLng},${destLat}`;
|
if (!force && requestKey === routeRequestKeyRef.current) {
|
return;
|
}
|
routeRequestKeyRef.current = requestKey;
|
const selectedCity = await ensureSelectedCityForRoute(originLng, originLat);
|
const openCityId = selectedCity ? selectedCity.id : 0;
|
const cachedCouponId = Taro.getStorageSync('selectedCouponId');
|
const params: any = {
|
destination: {
|
latitude: destLat,
|
longitude: destLng
|
},
|
openCityId: openCityId,
|
origin: {
|
latitude: originLat,
|
longitude: originLng
|
},
|
};
|
if (cachedCouponId) {
|
params.userCouponId = cachedCouponId;
|
}
|
queryRouteAndFare(params).then((res: any) => {
|
setRouteAndFareError(false);
|
if (res?.fareEstimate) {
|
setFareEstimate(res.fareEstimate);
|
}
|
if (res?.xpPricingCard) {
|
Taro.setStorageSync('xpPricingCard', res.xpPricingCard);
|
}
|
// 解析预估阶段返回的路线并展示
|
const route = res?.route;
|
const distance = Number(route?.distance);
|
const duration = Number(route?.duration);
|
const points = (Array.isArray(route?.polyline) ? route?.polyline : []);
|
const distanceKm = normalizeDistanceKm(distance);
|
if (distanceKm !== null) {
|
setRouteDistance(distanceKm);
|
}
|
const durationSeconds = normalizeDurationSeconds(duration);
|
if (durationSeconds !== null) {
|
setRouteDuration(durationSeconds);
|
}
|
if (Array.isArray(points) && points.length > 0) {
|
applyRoutePoints(points);
|
if (tripStatusRef.current === 1) {
|
const mapCtx = Taro.createMapContext('map');
|
const allPoints = [...points, ...walkingPoints];
|
if (allPoints.length > 0) {
|
const topPadding = Math.max(24, resolveSafeTopInset() + 32);
|
const bottomPadding = computeBottomPadding(allPoints, 'route_walking');
|
mapCtx.includePoints({
|
points: allPoints,
|
padding: [topPadding, 100, bottomPadding, 100]
|
});
|
}
|
}
|
} else {
|
applyRoutePoints([]);
|
}
|
|
}).catch(() => {
|
setRouteAndFareError(true);
|
});
|
};
|
|
const requestOrderRoute = (targetOrderId: string, status: number) => {
|
const routeType = status <= 5 ? 1 : 2;
|
const isTrajectoryStatus = [7, 8, 9].includes(status);
|
const requestRoute = isTrajectoryStatus ? queryOrderTrajectory : queryOrderRoute;
|
requestRoute({ orderId: targetOrderId, routeType })
|
.then((res: any) => {
|
const data = res;
|
const rawPoints = Array.isArray(data?.polyline) ? data.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 === 5) {
|
const last = points[points.length - 1];
|
updateVehicleLocationThrottled(last.latitude, last.longitude, true);
|
} else {
|
applyRoutePoints(points);
|
const first = points[0];
|
updateVehicleLocationThrottled(first.latitude, first.longitude, true);
|
// 状态4/6:存储完整路线用于后续 eraseLines 对比
|
if ([4, 6].includes(status)) {
|
currentFullRouteRef.current = [...points];
|
lastErasedIndexRef.current = 0;
|
orderRouteInitializedRef.current = true;
|
}
|
}
|
}
|
const distance = Number(data?.distance);
|
const distanceKm = normalizeDistanceKm(distance);
|
if (distanceKm !== null) {
|
setRouteDistance(distanceKm);
|
}
|
const duration = Number(data?.duration);
|
const durationSeconds = normalizeDurationSeconds(duration);
|
if (durationSeconds !== null) {
|
setRouteDuration(durationSeconds);
|
}
|
const dir = Number(data?.direction);
|
if (Number.isFinite(dir)) {
|
setVehicleDirection(dir);
|
}
|
})
|
.catch(() => { });
|
};
|
|
// 计算“当前位置→上车点”的步行路径
|
const updateWalkingRouteForStart = async () => {
|
try {
|
const loc = await Taro.getLocation({ type: 'gcj02' });
|
const originLat = loc.latitude;
|
const originLng = loc.longitude;
|
const targetLat = startSite?.latitude;
|
const targetLng = startSite?.longitude;
|
if (!originLat || !originLng || !targetLat || !targetLng) {
|
setWalkingPoints([]);
|
return;
|
}
|
const origin = `${originLng},${originLat}`;
|
const destination = `${targetLng},${targetLat}`;
|
const { points: wPoints, duration } = await getWalkingRoute(origin, destination);
|
if (Array.isArray(wPoints) && wPoints.length > 0) {
|
setWalkingPoints(wPoints);
|
setWalkingTime(Math.ceil(duration / 60));
|
} else {
|
setWalkingPoints([]);
|
setWalkingTime(0);
|
}
|
} catch (e) {
|
setWalkingPoints([]);
|
setWalkingTime(0);
|
}
|
};
|
|
const { bindSocketListeners, ensureSocketListeners } = useWebSocketEvents({
|
tripStatusRef,
|
orderId,
|
setOrderId,
|
setTripStatus,
|
setVehicleInfo,
|
vehicleInfo,
|
setPriceUpdateVersion,
|
setFinishCountdownEnabled,
|
setShowAnnouncementsPopup,
|
setRoutePoints,
|
setPolyline,
|
polylineKeyRef,
|
lastPolylineUpdateAtRef,
|
lastRoutePointRef,
|
lastRouteUpdateAtRef,
|
lastVehiclePointRef,
|
lastVehicleUpdateAtRef,
|
currentFullRouteRef,
|
lastErasedIndexRef,
|
orderRouteInitializedRef,
|
setCancelType,
|
setShowCancelPopup,
|
checkAndShowPassengerNotice,
|
setVehicleDirection,
|
setRouteDistance,
|
setRouteDuration,
|
erasePassedRouteSegments,
|
applyRoutePoints,
|
updateVehicleLocationThrottled
|
});
|
|
const clearWaitingTimerStorage = () => {
|
Taro.removeStorageSync(WAITING_TIMER_START_KEY);
|
Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
|
};
|
|
const startRequoteTimer = () => {
|
requoteTriggeredRef.current = false;
|
const startAt = Date.now();
|
const updateRequoteElapsed = () => {
|
const nextElapsed = Math.max(0, Math.floor((Date.now() - startAt) / 1000));
|
if (nextElapsed < REQUOTE_EXPIRE_SECONDS || requoteTriggeredRef.current) return;
|
requoteTriggeredRef.current = true;
|
const s = Taro.getStorageSync('startSite') || {};
|
const e = Taro.getStorageSync('endSite') || {};
|
if (s?.latitude && s?.longitude && e?.latitude && e?.longitude) {
|
requestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude, true);
|
}
|
};
|
updateRequoteElapsed();
|
return setInterval(updateRequoteElapsed, 1000);
|
};
|
|
const startWaitingTimer = () => {
|
const s = Taro.getStorageSync('startSite');
|
const hasPlannedRoute = Array.isArray(routePoints) && routePoints.length > 0;
|
if (!hasPlannedRoute && s && s.latitude && s.longitude) {
|
setLatitude(s.latitude);
|
setLongitude(s.longitude);
|
}
|
const cachedStartAt = Number(Taro.getStorageSync(WAITING_TIMER_START_KEY));
|
const cachedElapsed = Number(Taro.getStorageSync(WAITING_TIMER_ELAPSED_KEY));
|
let startAt = cachedStartAt;
|
if (!Number.isFinite(startAt) || startAt <= 0) {
|
startAt = Number.isFinite(cachedElapsed) && cachedElapsed >= 0
|
? Date.now() - cachedElapsed * 1000
|
: Date.now();
|
Taro.setStorageSync(WAITING_TIMER_START_KEY, startAt);
|
}
|
const updateElapsed = () => {
|
const nextElapsed = Math.max(0, Math.floor((Date.now() - startAt) / 1000));
|
setElapsedSeconds(nextElapsed);
|
};
|
updateElapsed();
|
return setInterval(updateElapsed, 1000);
|
};
|
|
const refreshOrderDetailRoute = (prevTripStatus: number) => {
|
const shouldSkipOrderDetailRefresh = prevTripStatus === 8 && tripStatus === 9;
|
if (shouldSkipOrderDetailRefresh) return;
|
orderDetailRouteRequestIdRef.current += 1;
|
const currentRequestId = orderDetailRouteRequestIdRef.current;
|
getOrderDetail(Taro.getStorageSync('orderId')).then((res: any) => {
|
if (currentRequestId !== orderDetailRouteRequestIdRef.current) return;
|
if (![8, 9].includes(tripStatusRef.current)) return;
|
const mapCtx = Taro.createMapContext('map');
|
if (!res.trajectories) {
|
mapCtx.moveToLocation({ latitude: 39.916527, longitude: 116.397128 });
|
return;
|
}
|
const points = res.trajectories
|
.map((item: any) => {
|
const [lonStr, latStr] = String(item.location || '').split(',');
|
const lon = Number(lonStr);
|
const lat = Number(latStr);
|
return { latitude: lat, longitude: lon };
|
})
|
.filter(p => Number.isFinite(p.latitude) && Number.isFinite(p.longitude));
|
applyRoutePoints(points);
|
});
|
};
|
|
const handleTripStatusOne = () => {
|
setCallSubmitting(false);
|
return startRequoteTimer();
|
};
|
|
const handleTripStatusWait = () => {
|
requoteTriggeredRef.current = false;
|
setWalkingPoints([]);
|
setWalkingTime(0);
|
return startWaitingTimer();
|
};
|
|
const handleTripStatusArrived = () => {
|
requoteTriggeredRef.current = false;
|
setWalkingPoints([]);
|
setWalkingTime(0);
|
const e = Taro.getStorageSync('endSite');
|
if (e && e.latitude && e.longitude) {
|
setLatitude(e.latitude);
|
setLongitude(e.longitude);
|
}
|
setElapsedSeconds(0);
|
clearWaitingTimerStorage();
|
};
|
|
const handleTripStatusOrderDone = (prevTripStatus: number) => {
|
requoteTriggeredRef.current = false;
|
setWalkingPoints([]);
|
setWalkingTime(0);
|
refreshOrderDetailRoute(prevTripStatus);
|
setElapsedSeconds(0);
|
clearWaitingTimerStorage();
|
};
|
|
const handleTripStatusFallback = () => {
|
requoteTriggeredRef.current = false;
|
setWalkingPoints([]);
|
setWalkingTime(0);
|
setElapsedSeconds(0);
|
clearWaitingTimerStorage();
|
};
|
|
|
// 监听行程状态,处理等待计时
|
useEffect(() => {
|
Taro.setStorageSync('tripStatus', tripStatus);
|
tripStatusRef.current = tripStatus;
|
const prevTripStatus = tripStatusForMapSyncRef.current;
|
let timer: any = null;
|
let requoteTimer: any = null;
|
if (tripStatus == 1) {
|
requoteTimer = handleTripStatusOne();
|
} else if (tripStatus == 2 || tripStatus == 3) {
|
timer = handleTripStatusWait();
|
} else if (tripStatus == 7) {
|
handleTripStatusArrived();
|
} else if ([8, 9].includes(tripStatus)) {
|
handleTripStatusOrderDone(prevTripStatus);
|
} else {
|
handleTripStatusFallback();
|
}
|
tripStatusForMapSyncRef.current = tripStatus;
|
return () => {
|
if (timer) clearInterval(timer);
|
if (requoteTimer) clearInterval(requoteTimer);
|
};
|
}, [tripStatus]);
|
|
// 页面隐藏时保存等待时间并解绑事件
|
useDidHide(() => {
|
setResetDetailVersion(v => v + 1);
|
const currentTripStatus = tripStatusRef.current;
|
if (currentTripStatus === 2 || currentTripStatus === 3) {
|
const startAt = Number(Taro.getStorageSync(WAITING_TIMER_START_KEY));
|
if (Number.isFinite(startAt) && startAt > 0) {
|
const currentElapsed = Math.max(0, Math.floor((Date.now() - startAt) / 1000));
|
Taro.setStorageSync(WAITING_TIMER_ELAPSED_KEY, currentElapsed);
|
} else {
|
Taro.setStorageSync(WAITING_TIMER_ELAPSED_KEY, elapsedSeconds);
|
}
|
} else {
|
Taro.removeStorageSync(WAITING_TIMER_START_KEY);
|
Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
|
}
|
});
|
|
const createPickupMarker = (pickupSite: any, useAndroidNativeTrip1Callout: boolean) => {
|
if (!(pickupSite?.latitude && pickupSite?.longitude)) return null;
|
return {
|
id: 1,
|
latitude: pickupSite.latitude,
|
longitude: pickupSite.longitude,
|
iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-up.png',
|
width: 22,
|
height: 40,
|
anchor: { x: 0.5, y: 1 },
|
zIndex: 1,
|
name: pickupSite?.name || '上车点',
|
...(useAndroidNativeTrip1Callout
|
? {
|
callout: {
|
content: tripStatus === 1 ? `${pickupSite?.name || '上车点'} >` : (pickupSite?.name || '上车点'),
|
color: '#000000',
|
fontSize: 12,
|
bgColor: '#FFFFFF',
|
borderRadius: 8,
|
padding: 8,
|
display: 'ALWAYS',
|
textAlign: 'center',
|
},
|
}
|
: {
|
customCallout: { anchorY: [2, 3].includes(tripStatus) ? 150 : -5, anchorX: 0, display: 'ALWAYS' },
|
})
|
};
|
};
|
|
const createDropoffMarker = (dropoffSite: any, useAndroidNativeTrip1Callout: boolean) => {
|
if (!(dropoffSite?.latitude && dropoffSite?.longitude)) return null;
|
const dropoffName = [7].includes(tripStatus) ? '已到达' : (dropoffSite.name || '目的地');
|
return {
|
id: 2,
|
latitude: dropoffSite.latitude,
|
longitude: dropoffSite.longitude,
|
iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/maker-down.png',
|
width: 22,
|
height: 40,
|
anchor: { x: 0.5, y: 1 },
|
zIndex: 1,
|
name: dropoffName,
|
...(useAndroidNativeTrip1Callout || tripStatus == 6
|
? {
|
callout: {
|
content: tripStatus === 1 ? `${dropoffName} >` : dropoffName,
|
color: '#000000',
|
fontSize: 12,
|
bgColor: '#FFFFFF',
|
borderRadius: 8,
|
padding: 8,
|
display: 'ALWAYS',
|
textAlign: 'center',
|
},
|
}
|
: {
|
customCallout: { anchorY: -5, anchorX: 0, display: 'ALWAYS' },
|
})
|
};
|
};
|
|
const createCarMarker = () => {
|
if (!vehicleLocation) return null;
|
return {
|
id: vehicleMarkerId,
|
latitude: vehicleLocation.latitude,
|
longitude: vehicleLocation.longitude,
|
iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/marker-car.png',
|
width: 20,
|
height: 40,
|
anchor: { x: 0.5, y: 0.5 },
|
zIndex: 2,
|
rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0,
|
name: '车辆位置',
|
customCallout: { anchorY: 70, anchorX: 0, display: 'ALWAYS' }
|
};
|
};
|
|
const applyMarkerState = (nextMarkers: any[], nextMarkersSignature: string, shouldSkipUpdate: boolean, carMarker: any) => {
|
if (!shouldSkipUpdate && markersSignatureRef.current !== nextMarkersSignature) {
|
markersSignatureRef.current = nextMarkersSignature;
|
setMarkers(nextMarkers);
|
}
|
if (!isAndroid && [4, 6].includes(tripStatus) && statusRule.showCar && !!carMarker && !tripStatus6MarkerRefreshedRef.current) {
|
tripStatus6MarkerRefreshedRef.current = true;
|
}
|
carMarkerInitialized.current = statusRule.showCar && !!carMarker;
|
};
|
|
// 构建并更新地图标记点(与路线解耦,避免路线更新导致标记刷新)
|
useEffect(() => {
|
const pickupSite = startSite?.latitude && startSite?.longitude ? startSite : Taro.getStorageSync('startSite');
|
const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
|
const useAndroidNativeTrip1Callout = isAndroid && [1, 5, 8, 9, 10].includes(tripStatus);
|
const pickupMarker = createPickupMarker(pickupSite, useAndroidNativeTrip1Callout);
|
const dropoffMarker = createDropoffMarker(dropoffSite, useAndroidNativeTrip1Callout);
|
const carMarker = createCarMarker();
|
|
const nextMarkers: any[] = [];
|
if (statusRule.showPickup && pickupMarker) nextMarkers.push(pickupMarker);
|
if (statusRule.showCar && carMarker) nextMarkers.push(carMarker);
|
if (statusRule.showDropoff && dropoffMarker) nextMarkers.push(dropoffMarker);
|
const nextMarkersSignature = nextMarkers
|
.map((m: any) => [
|
m.id,
|
Number(m.latitude).toFixed(6),
|
Number(m.longitude).toFixed(6),
|
m.name || '',
|
typeof m.rotate === 'number' ? m.rotate : '',
|
m?.customCallout?.anchorY ?? '',
|
].join('|'))
|
.join(';');
|
const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2);
|
const shouldHoldMarkerByTranslate = [4, 6].includes(tripStatus) && carMarkerInitialized.current && statusRule.showCar && (!statusRule.showDropoff || hasDropoffMarker);
|
const shouldSkipUpdate = shouldHoldMarkerByTranslate && (isAndroid || tripStatus6MarkerRefreshedRef.current);
|
applyMarkerState(nextMarkers, nextMarkersSignature, shouldSkipUpdate, carMarker);
|
}, [tripStatus, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation, vehicleDirection, vehicleMarkerId]);
|
|
const getDisplayRoutePoints = () => {
|
const isEraseMode = [4, 6].includes(tripStatus);
|
return isEraseMode ? routePoints : reducePoints(routePoints, isAndroid ? 120 : 200);
|
};
|
|
const buildPolylineKey = (displayRoutePoints: any[]) => {
|
const lastRoutePoint = displayRoutePoints[displayRoutePoints.length - 1];
|
const lastWalkingPoint = walkingPoints[walkingPoints.length - 1];
|
return [
|
shouldShowRoute ? 1 : 0,
|
statusRule.showWalking ? 1 : 0,
|
displayRoutePoints.length,
|
walkingPoints.length,
|
lastRoutePoint?.latitude ?? '',
|
lastRoutePoint?.longitude ?? '',
|
lastWalkingPoint?.latitude ?? '',
|
lastWalkingPoint?.longitude ?? '',
|
].join('|');
|
};
|
|
const buildPolylineLines = (displayRoutePoints: any[]) => {
|
const lines: any[] = [];
|
if (displayRoutePoints.length > 0) {
|
lines.push({
|
id: 2001,
|
points: displayRoutePoints,
|
color: '#7B91FF',
|
width: 10,
|
dottedLine: false,
|
arrowLine: true,
|
});
|
}
|
if (statusRule.showWalking && walkingPoints.length > 0) {
|
lines.push({
|
id: 2002,
|
points: walkingPoints,
|
color: '#7B91FF',
|
width: 6,
|
dottedLine: true,
|
arrowLine: false,
|
});
|
}
|
return lines;
|
};
|
|
useEffect(() => {
|
const displayRoutePoints = getDisplayRoutePoints();
|
const nextKey = buildPolylineKey(displayRoutePoints);
|
if (nextKey === polylineKeyRef.current) return;
|
polylineKeyRef.current = nextKey;
|
const shouldDraw = shouldShowRoute && (displayRoutePoints.length > 0 || (statusRule.showWalking && walkingPoints.length > 0));
|
if (shouldDraw) {
|
setPolyline(buildPolylineLines(displayRoutePoints));
|
lastPolylineUpdateAtRef.current = Date.now();
|
return;
|
}
|
if (![8, 9].includes(tripStatus)) {
|
setPolyline([]);
|
}
|
lastPolylineUpdateAtRef.current = 0;
|
}, [tripStatus, shouldShowRoute, routePoints, walkingPoints, statusRule.showWalking, isAndroid]);
|
|
const resolveViewportSites = () => {
|
const pickupSite = startSite?.latitude && startSite?.longitude ? startSite : Taro.getStorageSync('startSite');
|
const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
|
return { pickupSite, dropoffSite };
|
};
|
|
const buildViewportPayload = (viewportType: string, pickupSite: any, dropoffSite: any) => {
|
const points: any[] = [];
|
const keyParts: any[] = [viewportType === 'order_detail' ? 8 : tripStatus, viewportType];
|
if (viewportType === 'route_walking') {
|
if (routePoints.length > 0) points.push(...routePoints);
|
if (walkingPoints.length > 0) points.push(...walkingPoints);
|
keyParts.push(routePoints.length, walkingPoints.length);
|
return { points, keyParts };
|
}
|
if (viewportType === 'pickup') {
|
if (pickupSite?.latitude && pickupSite?.longitude) {
|
points.push({ latitude: pickupSite.latitude, longitude: pickupSite.longitude });
|
}
|
keyParts.push(pickupSite?.latitude, pickupSite?.longitude);
|
return { points, keyParts };
|
}
|
if (viewportType === 'route') {
|
if (routePoints.length > 0) points.push(...routePoints);
|
keyParts.push(routePoints.length);
|
return { points, keyParts };
|
}
|
if (viewportType === 'car_user') {
|
if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
|
points.push({ latitude, longitude });
|
}
|
if (vehicleLocation?.latitude && vehicleLocation?.longitude) {
|
points.push({ latitude: vehicleLocation.latitude, longitude: vehicleLocation.longitude });
|
}
|
keyParts.push(latitude, longitude, vehicleLocation?.latitude, vehicleLocation?.longitude);
|
return { points, keyParts };
|
}
|
if (viewportType === 'dropoff') {
|
if (dropoffSite?.latitude && dropoffSite?.longitude) {
|
points.push({ latitude: dropoffSite.latitude, longitude: dropoffSite.longitude });
|
}
|
keyParts.push(dropoffSite?.latitude, dropoffSite?.longitude);
|
return { points, keyParts };
|
}
|
if (viewportType === 'order_detail') {
|
if (routePoints.length > 0) points.push(...routePoints);
|
if (pickupSite?.latitude && pickupSite?.longitude) points.push({ latitude: pickupSite.latitude, longitude: pickupSite.longitude });
|
if (dropoffSite?.latitude && dropoffSite?.longitude) points.push({ latitude: dropoffSite.latitude, longitude: dropoffSite.longitude });
|
keyParts.push(routePoints.length, pickupSite?.latitude, pickupSite?.longitude, dropoffSite?.latitude, dropoffSite?.longitude);
|
}
|
return { points, keyParts };
|
};
|
|
const updateViewportByPayload = (viewportType: string, points: any[]) => {
|
const mapCtx = Taro.createMapContext('map');
|
const bottomPadding = computeBottomPadding(points, viewportType);
|
const baseTopPadding = viewportType === 'order_detail' ? 60 : 24;
|
const topPadding = Math.max(baseTopPadding, resolveSafeTopInset() + 32);
|
const padding = viewportType === 'order_detail'
|
? [topPadding, 50, bottomPadding, 50]
|
: [topPadding, 100, bottomPadding, 100];
|
Taro.nextTick(() => {
|
mapCtx.includePoints({ points, padding });
|
if (viewportType === 'dropoff') {
|
setTimeout(() => {
|
setMapScale(13);
|
}, 50);
|
}
|
});
|
};
|
|
useEffect(() => {
|
const { pickupSite, dropoffSite } = resolveViewportSites();
|
const viewportType = statusRule.viewport;
|
const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite);
|
if (points.length === 0) return;
|
if (tripStatus === 6 && viewportType === 'route' && tripStatus6ViewportLockedRef.current) return;
|
const nextKey = keyParts.join('|');
|
if (nextKey === viewportKeyRef.current) return;
|
viewportKeyRef.current = nextKey;
|
updateViewportByPayload(viewportType, points);
|
if (tripStatus === 6 && viewportType === 'route') {
|
tripStatus6ViewportLockedRef.current = true;
|
}
|
}, [tripStatus, statusRule, routePoints, walkingPoints, startSite?.latitude, startSite?.longitude, endSite?.latitude, endSite?.longitude, vehicleLocation, longitude, latitude, mapBottomPadding]);
|
|
useEffect(() => {
|
if (!statusRule.translateCar) return;
|
if (!carMarkerInitialized.current) return;
|
if (!vehicleLocation) return;
|
const mapCtx = Taro.createMapContext('map');
|
|
// 增加延迟,确保 markers 已经在原生视图中完成渲染,避免 translateMarker 找不到 markerId 导致 fail
|
const timer = setTimeout(() => {
|
mapCtx.translateMarker({
|
markerId: vehicleMarkerId,
|
destination: {
|
latitude: vehicleLocation.latitude,
|
longitude: vehicleLocation.longitude,
|
},
|
autoRotate: false,
|
moveWithRotate: true,
|
rotate: typeof vehicleDirection === 'number' ? Number(vehicleDirection.toFixed(0)) : 0,
|
duration: 100,
|
fail: (err) => {
|
console.log('translateMarker fail:', err, 'markerId:', vehicleMarkerId);
|
// 如果移动失败,可能是 marker 被意外卸载,强制解除一次拦截以重绘
|
tripStatus6MarkerRefreshedRef.current = false;
|
},
|
});
|
}, 100);
|
|
return () => clearTimeout(timer);
|
}, [tripStatus, vehicleLocation, vehicleDirection, vehicleMarkerId]);
|
|
const syncCachedOrderVehicle = (cachedOrderId: string) => {
|
if (!cachedOrderId) return;
|
setOrderId(cachedOrderId);
|
queryVehicleByOrderId({ orderId: cachedOrderId })
|
.then((res: any) => {
|
setVehicleInfo(res);
|
})
|
.catch(() => { });
|
};
|
|
const syncSitesFromActiveOrder = (cachedTripStatusStorage: any, s: any, e: any) => {
|
if (!(cachedTripStatusStorage && cachedTripStatusStorage != 1)) {
|
if (s) setStartSite(s);
|
if (e) setEndSite(e);
|
return;
|
}
|
getOrderActive()
|
.then((res: any) => {
|
if (!res) return;
|
setTrunkStatus([0, 2].includes(res.openTrunk) ? false : true);
|
const nextStartSite = {
|
id: res.startPointId || '',
|
name: res.startPointName || '',
|
latitude: res.startLat || 0,
|
longitude: res.startLon || 0,
|
electronicAreaId: res.xpElectronicFenceAreaId || '',
|
time: res.startParkingTimeLimit || 0,
|
};
|
const nextEndSite = {
|
id: res.endPointId || '',
|
name: res.endPointName || '',
|
latitude: res.endLat || 0,
|
longitude: res.endLon || 0,
|
electronicAreaId: res.xpElectronicFenceAreaId || '',
|
time: res.endParkingTimeLimit || 0,
|
};
|
const nextStatus = mapOrderStatusToTripStatus(res.status || 1);
|
Taro.setStorageSync('tripStatus', nextStatus);
|
setTripStatus(nextStatus);
|
Taro.setStorageSync('startSite', nextStartSite);
|
Taro.setStorageSync('endSite', nextEndSite);
|
setStartSite(nextStartSite);
|
setEndSite(nextEndSite);
|
})
|
.catch(() => { });
|
};
|
|
const syncLocationAndInitRoute = (s: any, e: any, cachedTripStatus: number) => {
|
if (s && s.latitude && s.longitude) {
|
setLatitude(s.latitude);
|
setLongitude(s.longitude);
|
} else {
|
getLocationInfo();
|
}
|
const originLng = s?.longitude || longitude;
|
const originLat = s?.latitude || latitude;
|
if (cachedTripStatus === 1 && originLng && originLat && e?.longitude && e?.latitude) {
|
requestRouteAndFare(originLat, originLng, e.latitude, e.longitude, true);
|
}
|
if (tripStatus === 1 && s?.latitude && s?.longitude) {
|
updateWalkingRouteForStart();
|
}
|
};
|
|
const ensurePageSocketReady = () => {
|
if (!wsManager.isConnectedStatus()) {
|
wsManager.connect(_ROBOTAXI_WS_HOST);
|
}
|
ensureSocketListeners(_ROBOTAXI_WS_HOST);
|
};
|
|
const showSkipPageToastIfNeeded = () => {
|
const { skipPageFlag } = router.params;
|
if (!skipPageFlag) return;
|
Taro.showToast({
|
title: '您已有进行中的订单',
|
icon: 'none',
|
});
|
};
|
|
const restorePageStateFromStorage = () => {
|
const s = Taro.getStorageSync('startSite');
|
const e = Taro.getStorageSync('endSite');
|
const cachedOrderId = Taro.getStorageSync('orderId');
|
const cachedTripStatusStorage = Taro.getStorageSync('tripStatus');
|
const cachedTripStatus = Number(cachedTripStatusStorage) || tripStatus;
|
syncCachedOrderVehicle(cachedOrderId);
|
syncSitesFromActiveOrder(cachedTripStatusStorage, s, e);
|
if (cachedOrderId && [4, 5, 6, 7, 8, 9].includes(cachedTripStatus)) {
|
requestOrderRoute(cachedOrderId, cachedTripStatus);
|
}
|
syncLocationAndInitRoute(s, e, cachedTripStatus);
|
};
|
|
useDidShow(() => {
|
ensurePageSocketReady();
|
showSkipPageToastIfNeeded();
|
restorePageStateFromStorage();
|
updateMapViewportPadding();
|
});
|
|
useEffect(() => {
|
const originLng = startSite?.longitude || longitude;
|
const originLat = startSite?.latitude || latitude;
|
if (tripStatus === 1 && originLng && originLat && endSite?.longitude && endSite?.latitude) {
|
requestRouteAndFare(originLat, originLng, endSite.latitude, endSite.longitude);
|
}
|
// 选择或更新上车点后重新计算步行路线
|
if (tripStatus === 1 && startSite?.latitude && startSite?.longitude) {
|
updateWalkingRouteForStart();
|
}
|
|
}, [startSite?.longitude, startSite?.latitude, endSite?.longitude, endSite?.latitude, longitude, latitude]);
|
|
useEffect(() => {
|
updateMapViewportPadding();
|
}, [tripStatus]);
|
|
useEffect(() => {
|
const timer = setTimeout(() => {
|
updateMapViewportPadding();
|
}, 50);
|
return () => clearTimeout(timer);
|
}, [
|
tripStatus,
|
startSite?.name,
|
endSite?.name,
|
walkingTime,
|
fareEstimate?.waitingTime,
|
fareEstimate?.finalFareStr,
|
fareEstimate?.originalFareStr,
|
fareEstimate?.discountAmount,
|
]);
|
|
|
// 获取用户位置信息
|
const getLocationInfo = () => {
|
Taro.getLocation({ type: 'gcj02' })
|
.then(res => {
|
setLatitude(res.latitude);
|
setLongitude(res.longitude);
|
})
|
}
|
|
// 返回当前位置或车辆位置
|
const toOrigin = () => {
|
const mapCtx = Taro.createMapContext('map');
|
// 如果是状态6(车辆行驶中)且有路线数据,则展示完整路线
|
if (tripStatus === 6 && routePoints.length > 0) {
|
const bottomPadding = computeBottomPadding(routePoints, 'route');
|
const topPadding = Math.max(24, resolveSafeTopInset() + 32);
|
mapCtx.includePoints({
|
points: routePoints,
|
padding: [topPadding, 100, bottomPadding, 100]
|
});
|
// 锁定视口状态,防止后续路线数据更新时频繁改变缩放比例
|
tripStatus6ViewportLockedRef.current = true;
|
} else {
|
mapCtx.moveToLocation();
|
}
|
}
|
|
const openService = () => {
|
const phoneNumber = Taro.getStorageSync('customerServicePhone');
|
Taro.makePhoneCall({
|
phoneNumber,
|
})
|
}
|
|
const ensureDistanceConfig = async () => {
|
let maxDistance = distanceConfig?.maxDistance;
|
let minDistance = distanceConfig?.minDistance;
|
if (maxDistance && minDistance) {
|
return { maxDistance, minDistance };
|
}
|
try {
|
const res = await getOrderDistance();
|
if (res) {
|
const nextConfig = {
|
maxDistance: res.maxDistance,
|
minDistance: res.minDistance,
|
adsorptionDistance: res.adsorptionDistance
|
};
|
setDistanceConfig(nextConfig);
|
maxDistance = nextConfig.maxDistance;
|
minDistance = nextConfig.minDistance;
|
}
|
} catch { }
|
return { maxDistance, minDistance };
|
};
|
|
const validateDistanceLimit = async (s: any, maxDistance: number, minDistance: number) => {
|
if (maxDistance) {
|
try {
|
const loc = await Taro.getLocation({ type: 'gcj02' });
|
const distance = getDistance(loc.latitude, loc.longitude, s.latitude, s.longitude);
|
if (distance > maxDistance) {
|
setTipType('2');
|
setShowTipPopup(true);
|
return false;
|
}
|
} catch { }
|
}
|
if (minDistance) {
|
const distance = getDistance(s.latitude, s.longitude, endSite.latitude, endSite.longitude);
|
if (distance < minDistance) {
|
setTipType('1');
|
setShowTipPopup(true);
|
return false;
|
}
|
}
|
return true;
|
};
|
|
const resolveOpenCityId = async () => {
|
const selectedCity = Taro.getStorageSync('selectedCity');
|
let openCityId = selectedCity?.id || 0;
|
if (openCityId) return openCityId;
|
try {
|
const loc = await Taro.getLocation({ type: 'gcj02' });
|
const data = await getAddressByLocation(loc.longitude, loc.latitude);
|
const addressComponent = data?.regeocodeData?.addressComponent;
|
if (!addressComponent) return 0;
|
const { city, province, adcode } = addressComponent;
|
const cityName = (city && typeof city === 'string' && city.length > 0) ? city : province;
|
const cityCode = convertAdcodeToCityCode(adcode, cityName, province);
|
const cityInfo = await getCityInfoWithId(cityCode, cityName);
|
if (cityInfo?.id) {
|
openCityId = cityInfo.id;
|
Taro.setStorageSync('selectedCity', cityInfo);
|
}
|
} catch (e) {
|
console.error('Failed to fetch city info before order:', e);
|
}
|
return openCityId;
|
};
|
|
const submitOrder = async (s: any, openCityId: number) => {
|
const waitingTime = Number(fareEstimate?.waitingTime || 0);
|
const estimatedTime = formatDateTime(new Date(Date.now() + waitingTime * 60 * 1000));
|
const cachedCouponId = Taro.getStorageSync('selectedCouponId') || '';
|
const payload: any = {
|
endPointId: endSite.id,
|
openCityId,
|
placementAddress: s?.name,
|
placementLat: s?.latitude,
|
placementLon: s?.longitude,
|
startPointId: s?.id,
|
xpElectronicFenceAreaId: s?.electronicAreaId || endSite?.electronicAreaId || '',
|
estimatedTime
|
};
|
if (cachedCouponId) {
|
payload.userCouponId = cachedCouponId;
|
}
|
const res = await saveOrder(payload);
|
const status_code = res?.orderId ? 1 : 0;
|
reportEvent('RT_xcx_order_submit', {
|
total_amount: fareEstimate.originalFare,
|
payable_amount: fareEstimate.finalFare,
|
coupon_id: cachedCouponId,
|
status_code
|
});
|
if (typeof res === 'string') {
|
Taro.showToast({
|
title: res,
|
icon: 'none',
|
mask: true
|
});
|
setTimeout(() => {
|
Taro.reLaunch({
|
url: '/pages/home/index'
|
})
|
}, 1500);
|
return null;
|
}
|
if (!res?.orderId) {
|
Taro.showToast({
|
title: '下单失败,请重试',
|
icon: 'none',
|
});
|
return null;
|
}
|
Taro.removeStorageSync('selectedCouponId');
|
Taro.setStorageSync('orderId', res.orderId);
|
return res.orderId;
|
};
|
|
const stopCallSubmitting = () => {
|
setCallSubmitting(false);
|
};
|
|
const checkCallAuth = async () => {
|
const otp = await checkOtp(false);
|
if (otp) return true;
|
stopCallSubmitting();
|
jumpTo({ url: '/pages/user/login?hideBack=1' });
|
return false;
|
};
|
|
const checkRouteReady = () => {
|
if (endSite.name && !routeAndFareError) return true;
|
if (!endSite.name) {
|
Taro.showToast({ title: '请选择站点', icon: 'none' });
|
}
|
stopCallSubmitting();
|
return false;
|
};
|
|
const getStartSiteForCall = () => {
|
const s = Taro.getStorageSync('startSite');
|
if (s?.latitude && s?.longitude && endSite?.latitude && endSite?.longitude) {
|
return s;
|
}
|
Taro.showToast({ title: '请先选择起终点', icon: 'none' });
|
stopCallSubmitting();
|
return null;
|
};
|
|
const checkDistanceAndCityReady = async (s: any) => {
|
const { maxDistance, minDistance } = await ensureDistanceConfig();
|
const passDistanceCheck = await validateDistanceLimit(s, maxDistance, minDistance);
|
if (!passDistanceCheck) {
|
stopCallSubmitting();
|
return null;
|
}
|
const openCityId = await resolveOpenCityId();
|
if (openCityId) return openCityId;
|
Taro.showToast({ title: '未能获取所在城市信息,请重试', icon: 'none' });
|
stopCallSubmitting();
|
return null;
|
};
|
|
const callImmediately = debounce(async () => {
|
if (callSubmitting) return;
|
setCallSubmitting(true);
|
const authReady = await checkCallAuth();
|
if (!authReady) return;
|
const routeReady = checkRouteReady();
|
if (!routeReady) return;
|
const s = getStartSiteForCall();
|
if (!s) return;
|
const openCityId = await checkDistanceAndCityReady(s);
|
if (!openCityId) return;
|
|
try {
|
const nextOrderId = await submitOrder(s, openCityId);
|
if (!nextOrderId) {
|
stopCallSubmitting();
|
return;
|
}
|
setOrderId(nextOrderId);
|
setTripStatus(2);
|
bindSocketListeners();
|
} catch (error) {
|
stopCallSubmitting();
|
}
|
}, 500)
|
|
const clearActiveOrderRuntime = () => {
|
setOrderId('');
|
clearOrderTripStorage();
|
Taro.removeStorageSync('pickupEndTime');
|
Taro.removeStorageSync('waitingTimerStartAt');
|
Taro.removeStorageSync('waitingTimerElapsed');
|
};
|
|
const restartPlanningRoute = () => {
|
setShowCancelPopup(false);
|
setTripStatus(1);
|
updateWalkingRouteForStart();
|
const s = Taro.getStorageSync('startSite');
|
const e = Taro.getStorageSync('endSite');
|
if (s?.latitude && s?.longitude && e?.longitude && e?.latitude) {
|
requestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude, true);
|
}
|
};
|
|
// 确认取消
|
const handleConfirmCancel = async () => {
|
if (cancelType === 5) {
|
handleOpenCancelPopupWithType(5);
|
return;
|
}
|
const targetOrderId = orderId || Taro.getStorageSync('orderId');
|
if (targetOrderId) {
|
try {
|
await cancelOrder(targetOrderId);
|
Taro.showToast({
|
title: '行程已取消',
|
icon: 'success',
|
});
|
clearActiveOrderRuntime();
|
setTripStatus(10);
|
setCancelType(4);
|
return
|
} catch (error) {
|
Taro.showToast({
|
title: '取消订单失败,请重试',
|
icon: 'none',
|
});
|
return;
|
}
|
} else {
|
restartPlanningRoute();
|
}
|
}
|
|
// 弹窗操作
|
// 订单超时再来一单
|
const handleOpenCancelPopupWithType = (type: number) => {
|
clearActiveOrderRuntime();
|
setCancelType(type);
|
restartPlanningRoute();
|
};
|
// 打开取消弹窗
|
const handleOpenCancelPopupByTripStatus = (status: number, durationDisplay?: any) => {
|
if (durationDisplay && ![3, 5].includes(status)) {
|
setCancelType(2);
|
setShowCancelPopup(true);
|
return
|
}
|
if ([2, 3].includes(status)) {
|
setCancelType(1);
|
}
|
if (status == 4) {
|
setCancelType(2);
|
}
|
if (status == 5) {
|
setCancelType(3);
|
}
|
setShowCancelPopup(true);
|
};
|
|
// 确认结束行程
|
const handleConfirmOverRoute = () => {
|
setShowOverRoutePopup(false);
|
setTripStatus(8);
|
}
|
const handleFinishCountdownTimeout = async () => {
|
const targetOrderId = orderId || Taro.getStorageSync('orderId');
|
if (!targetOrderId) return;
|
try {
|
await endOrder(targetOrderId);
|
} catch (error) {
|
}
|
setTripStatus(8);
|
}
|
|
// 确认支付
|
const handlePay = async () => {
|
const targetOrderId = orderId || Taro.getStorageSync('orderId');
|
if (!targetOrderId) {
|
Taro.showToast({
|
title: '订单信息缺失,无法支付',
|
icon: 'none',
|
})
|
return
|
}
|
const payload = {
|
orderId: targetOrderId,
|
};
|
try {
|
await getOrderPayment(payload);
|
Taro.showToast({
|
title: '支付成功',
|
icon: 'none',
|
})
|
setTripStatus(9);
|
Taro.setStorageSync('tripStatus', 9);
|
} catch (error) {
|
Taro.showToast({
|
title: '支付失败,请重试',
|
icon: 'none',
|
})
|
return
|
}
|
}
|
|
// 打开评价弹窗
|
const handleCloseEvaluatePopup = () => {
|
setEvaluateCloseVersion(v => v + 1);
|
setShowEvaluatePopup(false);
|
};
|
|
// 确认评价
|
const handleConfirmEvaluate = () => {
|
setEvaluateSubmitVersion(v => v + 1);
|
handleCloseEvaluatePopup();
|
};
|
|
const toSelectSite = (pageType: 'start' | 'end') => {
|
Taro.navigateTo({
|
url: `/robotaxi/pages/site-search/index?pageType=${pageType}&longitude=${startSite.longitude}&latitude=${startSite.latitude}&electronicAreaId=${startSite.electronicAreaId}`
|
})
|
}
|
const changeCheckedEnter = (checked: boolean) => {
|
isCheckedEnter.current = checked;
|
}
|
|
// 协议检查后执行页面跳转,统一处理“未同意协议”提示
|
const guardPrivacyThenNavigate = (url: string) => {
|
if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') {
|
Taro.showToast({
|
title: '请先同意协议',
|
icon: 'none'
|
});
|
return;
|
}
|
Taro.navigateTo({ url });
|
};
|
|
// 点击气泡
|
const handleCalloutTap = (e) => {
|
if (isCheckedEnter.current) {
|
isCheckedEnter.current = false;
|
return;
|
}
|
if (tripStatus === 1) {
|
// 仅在发布行程时,点击标记用于选择起点或终点
|
const markerId = e.detail.markerId;
|
toSelectSite(markerId === 1 ? 'start' : 'end');
|
}
|
}
|
|
const toCoupon = () => {
|
guardPrivacyThenNavigate('/robotaxi/pages/coupon/index');
|
}
|
|
const toOrder = () => {
|
guardPrivacyThenNavigate('/robotaxi/pages/order/index');
|
}
|
|
const toBack = () => {
|
if (tripStatus == 9 || tripStatus == 1) {
|
clearOrderTripStorage();
|
Taro.removeStorageSync('endSite');
|
}
|
Taro.reLaunch({
|
url: '/pages/robotaxi/home/index'
|
})
|
}
|
|
const handleRegionChange = (e) => {
|
if (e?.type === 'begin') {
|
setWaitingRadarVisible(false);
|
setCarCalloutVisible(false);
|
setArriveCalloutVisible(false);
|
return;
|
}
|
if (e?.type === 'end') {
|
setWaitingRadarVisible(true);
|
updateWaitingRadarPosition();
|
updateCarCalloutPosition();
|
updateArriveCalloutPosition();
|
}
|
};
|
|
const handleToggleDetailFade = (expanded: boolean, hasFare: boolean) => {
|
if (expanded && hasFare) {
|
setOriginOpacity(0);
|
} else {
|
setOriginOpacity(1);
|
}
|
updateMapViewportPadding();
|
};
|
|
const renderWaitingCallout = (item: any, calloutKey: string) => (
|
<CoverView slot='callout' marker-id={item.id} key={calloutKey} className='waiting-callout'>
|
<CoverView className='waiting-callout-content'>
|
<CoverView className={`waiting-callout-txt ${!isAndroid && 'pr-6'}`}>
|
{item.name}
|
{!isAndroid && <CoverView style={{ color: 'transparent' }}>占</CoverView>}
|
</CoverView>
|
</CoverView>
|
<CoverImage className='waiting-radar' src={waitingRadarSrc} />
|
</CoverView>
|
);
|
|
const renderDefaultCallout = (item: any, calloutKey: string) => (
|
<CoverView slot='callout' marker-id={item.id} key={calloutKey} className='custom-callout'>
|
{(item.name !== '车辆位置' && !(item.id === 2 && [7].includes(tripStatus))) && (
|
<CoverView className={`custom-callout-content ${isAndroid && 'txt-center '}`} style={{ zIndex: 1 }}>
|
<CoverView style={{ lineHeight: '22px', display: 'flex', alignItems: 'center' }}>{item.name}</CoverView>
|
{isAndroid && <CoverView style={{ width: '2px' }}></CoverView>}
|
{tripStatus == 1 && <CoverImage className='custom-callout-icon' src={customCalloutIconSrc} />}
|
</CoverView>
|
)}
|
{(item.name === '车辆位置' && [4, 6].includes(tripStatus) && !isAndroid) && (
|
<CoverView className='custom-callout-content' style={{ padding: '6px 10px', minWidth: '40px', justifyContent: 'center', zIndex: 2 }}>
|
<CoverView className='car-time'>
|
<CoverView className='car-time-num'>{routeDistance?.value ?? 0}</CoverView>
|
<CoverView>{routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView>
|
<CoverView style={{ marginLeft: '2.5px' }} className='car-time-num'>{durationDisplay.value}</CoverView>
|
<CoverView>{durationDisplay.unit}</CoverView>
|
</CoverView>
|
</CoverView>
|
)}
|
{(item.id === 2 && [7].includes(tripStatus) && !isAndroid) && (
|
<CoverView className='custom-callout-content'>
|
已到达
|
</CoverView>
|
)}
|
</CoverView>
|
);
|
|
const renderMarkerCallout = (item: any, index: number) => {
|
const calloutKey = `callout-${item.id}-${item.name || ''}`;
|
const isWaiting = [2, 3].includes(tripStatus) && index === 0;
|
|
if (isWaiting) {
|
return renderWaitingCallout(item, calloutKey);
|
}
|
|
if (item.name === '车辆位置' && isAndroid && [4, 6].includes(tripStatus)) {
|
return null;
|
}
|
|
return renderDefaultCallout(item, calloutKey);
|
};
|
|
const durationDisplay = formatDurationDisplay(routeDuration);
|
const mapLongitude = longitude ?? 116.397128;
|
const mapLatitude = latitude ?? 39.916527;
|
|
const renderMapOverlays = () => (
|
<>
|
{!(isAndroid && [1, 5, 8, 9, 10].includes(tripStatus)) && markers.map(renderMarkerCallout)}
|
{shouldShowWaitingRadar && waitingRadarVisible && (
|
<Image className="waiting-radar-img" src={waitingRadarSrc} webp={true} style={waitingRadarStyle} />
|
)}
|
{isAndroid && carCalloutVisible && (
|
<View className="car-callout-android" style={carCalloutStyle}>
|
<View className="custom-callout-content" style={{ padding: '6px 10px' }}>
|
<View className="car-time" style={{ alignItems: 'flex-end' }}>
|
<View className="car-time-num">{routeDistance?.value ?? 0}</View>
|
<View style={{ flexShrink: 0 }}>{routeDistance?.unit === 'm' ? '米' : '公里'}</View>
|
<View style={{ marginLeft: '2.5px' }} className="car-time-num">{durationDisplay.value}</View>
|
<View style={{ flexShrink: 0 }}>{durationDisplay.unit}</View>
|
</View>
|
</View>
|
</View>
|
)}
|
{isAndroid && arriveCalloutVisible && (
|
<View className="car-callout-android" style={arriveCalloutStyle}>
|
<View className="custom-callout-content">
|
已到达
|
</View>
|
</View>
|
)}
|
</>
|
);
|
|
const renderTripCard = () => {
|
if (tripStatus == 1 && startSite && endSite) {
|
return (
|
<ReleaseRoute
|
startSite={startSite}
|
endSite={endSite}
|
callImmediately={callImmediately}
|
fareEstimate={fareEstimate}
|
handleOpenPassengerNotice={handleOpenPassengerNotice}
|
walkingTime={walkingTime}
|
isCallDisabled={routeAndFareError || callSubmitting}
|
/>
|
);
|
}
|
if (tripStatus == 2) {
|
return (
|
<ReleaseBeBeing
|
handleOpenCancelPopup={() => handleOpenCancelPopupByTripStatus(2)}
|
onFinish={() => {
|
setTripStatus(3);
|
Taro.setStorageSync('tripStatus', 3);
|
}}
|
/>
|
);
|
}
|
if (tripStatus >= 3 && tripStatus !== 10) {
|
return (
|
<CallingCar
|
key={tripStatus}
|
orderId={orderId}
|
tripStatus={tripStatus}
|
finishCountdownEnabled={finishCountdownEnabled}
|
vehicleInfo={vehicleInfo}
|
handleOpenCancelPopup={(displayInfo) => handleOpenCancelPopupByTripStatus(tripStatus, displayInfo)}
|
handleCancelTimeout={() => handleOpenCancelPopupWithType(4)}
|
handleFinishTimeout={handleFinishCountdownTimeout}
|
resetDetailVersion={resetDetailVersion}
|
priceUpdateVersion={priceUpdateVersion}
|
evaluateSubmitVersion={evaluateSubmitVersion}
|
evaluateCloseVersion={evaluateCloseVersion}
|
handleOpenAnnouncementsPopup={handleOpenAnnouncementsPopup}
|
handleOpenOverRoutePopup={handleOpenOverRoutePopup}
|
handlePay={handlePay}
|
handleOpenEvaluatePopup={handleOpenEvaluatePopup}
|
estimatedWaitMinutes={fareEstimate?.waitingTime}
|
routeDurationSeconds={routeDuration}
|
onToggleDetailFade={handleToggleDetailFade}
|
orderTrunkStatus={trunkStatus}
|
/>
|
);
|
}
|
return null;
|
};
|
|
const shouldShowOrigin = ![7, 8, 9].includes(tripStatus);
|
const handleServiceEntry = () => {
|
isCheckedEnter.current = true;
|
openService();
|
};
|
const handleCouponEntry = () => {
|
isCheckedEnter.current = true;
|
toCoupon();
|
};
|
const handleOrderEntry = () => {
|
isCheckedEnter.current = true;
|
toOrder();
|
};
|
|
return (
|
<BeginRouteView
|
mapLongitude={mapLongitude}
|
mapLatitude={mapLatitude}
|
mapScale={mapScale}
|
markers={markers}
|
polyline={polyline}
|
showLocation={!!longitude && showLocationFlag}
|
handleCalloutTap={handleCalloutTap}
|
handleRegionChange={handleRegionChange}
|
renderMapOverlays={renderMapOverlays}
|
toBack={toBack}
|
backIcon={backIcon}
|
onServiceEntry={handleServiceEntry}
|
onCouponEntry={handleCouponEntry}
|
onOrderEntry={handleOrderEntry}
|
contactService={contactService}
|
coupon={coupon}
|
suggest={suggest}
|
shouldShowOrigin={shouldShowOrigin}
|
originBoxHidden={originBoxHidden}
|
originOpacity={originOpacity}
|
toOrigin={toOrigin}
|
originIcon={originIcon}
|
renderTripCard={renderTripCard}
|
showCustomerService={showCustomerService}
|
handleCloseCustomerService={handleCloseCustomerService}
|
showCancelPopup={showCancelPopup}
|
cancelType={cancelType}
|
startSite={startSite}
|
endSite={endSite}
|
fareEstimate={fareEstimate}
|
tripStatus={tripStatus}
|
routeDuration={routeDuration}
|
handleCloseCancelPopup={handleCloseCancelPopup}
|
handleConfirmCancel={handleConfirmCancel}
|
showAnnouncementsPopup={showAnnouncementsPopup}
|
handleCloseAnnouncementsPopup={handleCloseAnnouncementsPopup}
|
showOverRoutePopup={showOverRoutePopup}
|
handleCloseOverRoutePopup={handleCloseOverRoutePopup}
|
handleConfirmOverRoute={handleConfirmOverRoute}
|
showTipPopup={showTipPopup}
|
handleCloseTipPopup={handleCloseTipPopup}
|
tipType={tipType}
|
showPassengerNotice={showPassengerNotice}
|
handleClosePassengerNotice={handleClosePassengerNotice}
|
changeCheckedEnter={changeCheckedEnter}
|
showEvaluatePopup={showEvaluatePopup}
|
orderId={orderId}
|
handleCloseEvaluatePopup={handleCloseEvaluatePopup}
|
handleConfirmEvaluate={handleConfirmEvaluate}
|
/>
|
)
|
}
|