import Taro, { useDidShow, useDidHide } from "@tarojs/taro"
|
import { useEffect, useState, useRef, useMemo } from 'react';
|
import { View, Map, Text, Image, CoverView, CoverImage } from '@tarojs/components';
|
import './index.less';
|
|
import CustomerServicePopup from '../../components/customer-service';
|
import ReleaseRoute from './components/release-route';
|
import ReleaseBeBeing from './components/release-be-being';
|
import CancelPopup from './components/cancel-popup';
|
import CallingCar from './components/calling-car';
|
import AnnouncementsPopup from './components/announcements-popup';
|
import OverRoute from './components/over-route';
|
import PassengerNotice from './components/passenger-notice';
|
import EvaluatePopup from './components/evaluate-popup';
|
import TipPopup from '../select-map-site/components/tip-popup';
|
|
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,
|
mapOrderStatusToTripStatus,
|
reportEvent
|
} 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';
|
|
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] = useState(false); // 客服弹窗是否显示
|
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 [showCancelPopup, setShowCancelPopup] = useState(false); // 取消弹窗是否显示
|
const [showAnnouncementsPopup, setShowAnnouncementsPopup] = useState(false); // 安全注意事项动画弹窗是否显示
|
const [showOverRoutePopup, setShowOverRoutePopup] = useState(false); // 结束行程弹窗是否显示
|
const [showPassengerNotice, setShowPassengerNotice] = useState(false); // 乘车需知弹窗是否显示
|
const [cancelType, setCancelType] = useState(1); // 取消类型
|
const [showEvaluatePopup, setShowEvaluatePopup] = useState(false); // 评价弹窗是否显示
|
const [showTipPopup, setShowTipPopup] = useState(false);
|
const [tipType, setTipType] = useState('');
|
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);
|
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://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/f9e285fc-4c09-404d-90f8-4bac178565d0.png';
|
const waitingCalloutBgUrl = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/f55ce277-68de-4615-9c7c-807f588afaac.png';
|
const waitingRadarUrl = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/63ff36fc-e84c-4078-a53b-467870645105.webp';
|
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);
|
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: false, 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 authSuccessBound = useRef(false); // 认证成功是否绑定
|
const messageBound = useRef(false); // 消息是否绑定
|
const orderStatusBound = useRef(false); // 订单状态是否绑定
|
const orderRouteBound = useRef(false); // 订单路线是否绑定
|
const vehicleLocationBound = useRef(false); // 车辆位置是否绑定
|
const openTrunkBound = useRef(false);
|
const orderUpdatePriceBound = useRef(false);
|
const authSuccessHandlerRef = useRef<(data: any) => void>(); // 认证成功处理函数
|
const messageHandlerRef = useRef<(data: any) => void>(); // 消息处理函数
|
const orderStatusHandlerRef = useRef<(data: any) => void>(); // 订单状态处理函数
|
const orderRouteHandlerRef = useRef<(data: any) => void>(); // 订单路线处理函数
|
const vehicleLocationHandlerRef = useRef<(data: any) => void>(); // 车辆位置处理函数
|
const openTrunkHandlerRef = useRef<(data: any) => void>();
|
const orderUpdatePriceHandlerRef = useRef<(data: any) => void>();
|
const socketOpenHandlerRef = useRef<(() => void) | null>(null);
|
// 记录已为某订单拉取过车辆信息,避免重复
|
const lastFetchedVehicleOrderIdRef = useRef<string | null>(null);
|
const mapRectRef = useRef<any>(null);
|
const viewportKeyRef = useRef<string>('');
|
const tripStatus6ViewportLockedRef = useRef(false);
|
const tripStatus6MarkerRefreshedRef = useRef(false);
|
const polylineKeyRef = useRef<string>('');
|
const orderDetailRouteRequestIdRef = useRef(0);
|
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);
|
const vehicleMarkerId = !isAndroid && tripStatus === 6 ? 30 : 3;
|
const isCheckedEnter = useRef(false);
|
const router = Taro.useRouter();
|
|
useEffect(() => {
|
const wasHold = [4, 6].includes(lastTripStatusRef.current);
|
const isHold = [4, 6].includes(tripStatus);
|
if (tripStatus == 6) {
|
setShowLocationFlag(false);
|
} else {
|
setShowLocationFlag(true);
|
}
|
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 + 45}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);
|
const erasePoint = oldPoints[bestMatchIndex];
|
|
mapCtx.eraseLines({
|
lines: [{
|
id: 2001,
|
index: eraseIndex,
|
point: {
|
latitude: erasePoint.latitude,
|
longitude: erasePoint.longitude,
|
},
|
clear: true
|
}],
|
success: (res) => {
|
console.log('擦除路线成功', res);
|
},
|
fail: (err) => {
|
console.error('擦除路线失败', err);
|
}
|
});
|
|
lastErasedIndexRef.current = bestMatchIndex;
|
} else if (bestMatchIndex === startIndex && bestDistance < 500) {
|
// 新路线起点与旧路线起点一致,无需擦除
|
} 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 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 formatDurationDisplay = (value: any) => {
|
const num = Number(value);
|
if (Number.isNaN(num)) {
|
return { value: 0, unit: '分钟' };
|
}
|
if (num < 60) {
|
return { value: Math.max(0, Math.round(num)), unit: '秒' };
|
}
|
return { value: Math.ceil(num / 60), unit: '分钟' };
|
};
|
|
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);
|
}
|
setVehicleDirection(data?.direction);
|
})
|
.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);
|
}
|
};
|
|
// 认证成功处理函数
|
useEffect(() => {
|
authSuccessHandlerRef.current = () => {
|
setTripStatus(2);
|
if (!messageBound.current && messageHandlerRef.current) {
|
wsManager.on('message', messageHandlerRef.current);
|
messageBound.current = true;
|
}
|
};
|
messageHandlerRef.current = (data) => {
|
console.log('ws message:', data);
|
};
|
openTrunkHandlerRef.current = (payload) => {
|
const isOpen = Number(payload?.isOpen);
|
if (isOpen === 1) {
|
setFinishCountdownEnabled(true);
|
}
|
};
|
orderUpdatePriceHandlerRef.current = (payload) => {
|
const incomingOrderId = payload?.orderId || payload?.content?.orderId || Taro.getStorageSync('orderId');
|
const currentOrderId = orderId || Taro.getStorageSync('orderId');
|
if (incomingOrderId && currentOrderId && String(incomingOrderId) !== String(currentOrderId)) {
|
return;
|
}
|
setPriceUpdateVersion((v) => v + 1);
|
};
|
// 优化订单状态回调中的车辆信息拉取逻辑
|
orderStatusHandlerRef.current = (payload) => {
|
const mappedTripStatus = payload?.tripStatus;
|
if (typeof mappedTripStatus === 'number') {
|
const currentLocalStatus = Number(Taro.getStorageSync('tripStatus'));
|
// 如果本地状态已经比推送的状态新(状态递增原则),则忽略旧的推送消息
|
if (currentLocalStatus > mappedTripStatus && currentLocalStatus !== 10) {
|
return;
|
}
|
setTripStatus(mappedTripStatus);
|
Taro.setStorageSync('tripStatus', mappedTripStatus);
|
}
|
// 车辆信息拉取:当收到有效的订单号时,在首次达到呼叫及后续阶段拉取车辆信息
|
const incomingOrderId = payload?.content?.orderId || Taro.getStorageSync('orderId');
|
const vehicleInfoEmpty =
|
!vehicleInfo ||
|
(!vehicleInfo.plateNumber && !vehicleInfo.vehicleCode && !vehicleInfo.vehicleColor && !vehicleInfo.vehiclePictures);
|
const shouldFetchVehicle =
|
!!incomingOrderId &&
|
(lastFetchedVehicleOrderIdRef.current !== incomingOrderId) &&
|
(vehicleInfoEmpty || orderId !== incomingOrderId);
|
if (incomingOrderId && orderId !== incomingOrderId) {
|
setOrderId(incomingOrderId);
|
}
|
if (shouldFetchVehicle) {
|
lastFetchedVehicleOrderIdRef.current = incomingOrderId;
|
queryVehicleByOrderId({ orderId: incomingOrderId })
|
.then((res: any) => {
|
setVehicleInfo(res);
|
})
|
.catch(() => { });
|
}
|
if (mappedTripStatus === 6) {
|
setShowAnnouncementsPopup(false);
|
}
|
if (mappedTripStatus === 5) {
|
setRoutePoints([]);
|
setPolyline([]);
|
polylineKeyRef.current = '';
|
lastPolylineUpdateAtRef.current = 0;
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
lastVehiclePointRef.current = null;
|
lastVehicleUpdateAtRef.current = 0;
|
currentFullRouteRef.current = [];
|
lastErasedIndexRef.current = 0;
|
}
|
if (mappedTripStatus === 4) {
|
setRoutePoints([]);
|
setPolyline([]);
|
polylineKeyRef.current = '';
|
lastPolylineUpdateAtRef.current = 0;
|
lastRoutePointRef.current = null;
|
lastRouteUpdateAtRef.current = 0;
|
lastVehiclePointRef.current = null;
|
lastVehicleUpdateAtRef.current = 0;
|
currentFullRouteRef.current = [];
|
lastErasedIndexRef.current = 0;
|
}
|
if (payload?.showCancelPopup) {
|
setTripStatus(10);
|
if (tripStatus == 4 || tripStatus == 5) {
|
setOrderId('');
|
clearOrderTripStorage();
|
Taro.removeStorageSync('pickupEndTime');
|
Taro.removeStorageSync('waitingTimerStartAt');
|
Taro.removeStorageSync('waitingTimerElapsed');
|
setTripStatus(10);
|
setCancelType(4);
|
} else {
|
setCancelType(5);
|
}
|
setShowCancelPopup(true);
|
}
|
if (payload?.showPassengerNotice) {
|
checkAndShowPassengerNotice();
|
}
|
};
|
|
orderRouteHandlerRef.current = (payload) => {
|
if (tripStatusRef.current === 5) {
|
return;
|
}
|
const points = payload?.points || [];
|
const direction = payload?.direction;
|
if (typeof direction === 'number' && Number.isFinite(direction)) {
|
setVehicleDirection(direction);
|
}
|
const distance = Number(JSON.parse(payload?.content?.route)?.distance);
|
const duration = Number(JSON.parse(payload?.content?.route)?.duration);
|
const distanceKm = normalizeDistanceKm(distance);
|
if (distanceKm !== null) {
|
setRouteDistance(distanceKm);
|
}
|
const durationSeconds = normalizeDurationSeconds(duration);
|
if (durationSeconds !== null) {
|
setRouteDuration(durationSeconds);
|
}
|
|
// 状态4/6:使用 eraseLines 擦除已行驶路段,不再全量重绘
|
const isEraseStatus = [4, 6].includes(tripStatusRef.current);
|
if (isEraseStatus) {
|
const firstPoint = points?.[0];
|
if (orderRouteInitializedRef.current && currentFullRouteRef.current.length > 0) {
|
// 后续更新:擦除已行驶路段
|
erasePassedRouteSegments(points);
|
} else {
|
// 首次绘制:全量渲染并存储完整路线
|
orderRouteInitializedRef.current = true;
|
currentFullRouteRef.current = [...points];
|
lastErasedIndexRef.current = 0;
|
Taro.nextTick(() => {
|
applyRoutePoints(points);
|
});
|
}
|
if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
|
updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
|
}
|
return;
|
}
|
|
// 其他状态保持原有逻辑
|
Taro.nextTick(() => {
|
const updated = applyRoutePoints(points);
|
if (updated && [4, 5, 6].includes(tripStatusRef.current)) {
|
const firstPoint = points?.[0];
|
if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
|
updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
|
}
|
}
|
});
|
|
};
|
// 仅在呼叫及后续阶段允许更新车辆位置
|
vehicleLocationHandlerRef.current = (payload) => {
|
const latitude = payload?.latitude;
|
const longitude = payload?.longitude;
|
if (typeof latitude === 'number' && typeof longitude === 'number') {
|
if ([4, 5, 6, 7].includes(tripStatusRef.current)) {
|
updateVehicleLocationThrottled(latitude, longitude);
|
}
|
}
|
};
|
return () => {
|
if (authSuccessBound.current && authSuccessHandlerRef.current) {
|
wsManager.off('auth_success', authSuccessHandlerRef.current);
|
authSuccessBound.current = false;
|
}
|
if (messageBound.current && messageHandlerRef.current) {
|
wsManager.off('message', messageHandlerRef.current);
|
messageBound.current = false;
|
}
|
if (orderStatusBound.current && orderStatusHandlerRef.current) {
|
wsManager.off('order_status', orderStatusHandlerRef.current);
|
orderStatusBound.current = false;
|
}
|
if (orderRouteBound.current && orderRouteHandlerRef.current) {
|
wsManager.off('order_route', orderRouteHandlerRef.current);
|
orderRouteBound.current = false;
|
}
|
if (vehicleLocationBound.current && vehicleLocationHandlerRef.current) {
|
wsManager.off('vehicle_location', vehicleLocationHandlerRef.current);
|
vehicleLocationBound.current = false;
|
}
|
if (openTrunkBound.current && openTrunkHandlerRef.current) {
|
wsManager.off('open_trunk', openTrunkHandlerRef.current);
|
openTrunkBound.current = false;
|
}
|
if (orderUpdatePriceBound.current && orderUpdatePriceHandlerRef.current) {
|
wsManager.off('order_update_price', orderUpdatePriceHandlerRef.current);
|
orderUpdatePriceBound.current = false;
|
}
|
};
|
}, []);
|
|
|
// 监听行程状态,处理等待计时
|
useEffect(() => {
|
Taro.setStorageSync('tripStatus', tripStatus);
|
tripStatusRef.current = tripStatus;
|
const prevTripStatus = tripStatusForMapSyncRef.current;
|
if (tripStatus === 1) {
|
setCallSubmitting(false);
|
}
|
let timer: any = null;
|
let requoteTimer: any = null;
|
// tripStatus=1:开启发布行程停留计时,用于10分钟后提示需重新估价
|
if (tripStatus == 1) {
|
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) {
|
if (!requoteTriggeredRef.current) {
|
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();
|
requoteTimer = setInterval(updateRequoteElapsed, 1000);
|
} else {
|
requoteTriggeredRef.current = false;
|
// 离开发布阶段后清空步行路线,避免在进行中/支付成功阶段仍显示步行线
|
setWalkingPoints([]);
|
setWalkingTime(0);
|
}
|
// tripStatus=2/3:等待计时
|
if (tripStatus == 2 || tripStatus == 3) {
|
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) {
|
if (Number.isFinite(cachedElapsed) && cachedElapsed >= 0) {
|
startAt = Date.now() - cachedElapsed * 1000;
|
} else {
|
startAt = 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();
|
timer = setInterval(() => {
|
updateElapsed();
|
}, 1000);
|
} else if (tripStatus == 7) {
|
const e = Taro.getStorageSync('endSite');
|
if (e && e.latitude && e.longitude) {
|
setLatitude(e.latitude);
|
setLongitude(e.longitude);
|
}
|
setElapsedSeconds(0);
|
Taro.removeStorageSync(WAITING_TIMER_START_KEY);
|
Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
|
} else if ([8, 9].includes(tripStatus)) {
|
const shouldSkipOrderDetailRefresh = prevTripStatus === 8 && tripStatus === 9;
|
if (!shouldSkipOrderDetailRefresh) {
|
orderDetailRouteRequestIdRef.current += 1;
|
const currentRequestId = orderDetailRouteRequestIdRef.current;
|
const requestTripStatus = tripStatus;
|
getOrderDetail(Taro.getStorageSync('orderId')).then((res: any) => {
|
if (currentRequestId !== orderDetailRouteRequestIdRef.current) return;
|
if (tripStatusRef.current !== requestTripStatus) return;
|
const mapCtx = Taro.createMapContext('map');
|
if (res.trajectories) {
|
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);
|
} else {
|
mapCtx.moveToLocation({
|
latitude: 39.916527,
|
longitude: 116.397128,
|
});
|
}
|
});
|
}
|
setElapsedSeconds(0);
|
Taro.removeStorageSync(WAITING_TIMER_START_KEY);
|
Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
|
} else {
|
setElapsedSeconds(0);
|
Taro.removeStorageSync(WAITING_TIMER_START_KEY);
|
Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
|
}
|
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);
|
}
|
});
|
|
// 构建并更新地图标记点(与路线解耦,避免路线更新导致标记刷新)
|
useEffect(() => {
|
const pickupSite = startSite?.latitude && startSite?.longitude ? startSite : Taro.getStorageSync('startSite');
|
const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
|
const useAndroidNativeTrip1Callout = isAndroid && tripStatus === 1;
|
const pickupMarker = pickupSite?.latitude && pickupSite?.longitude ? {
|
id: 1,
|
latitude: pickupSite.latitude,
|
longitude: pickupSite.longitude,
|
iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png',
|
width: 22,
|
height: 40,
|
anchor: { x: 0.5, y: 1 },
|
zIndex: 1,
|
name: pickupSite?.name || '上车点',
|
...(useAndroidNativeTrip1Callout
|
? {
|
callout: {
|
content: `${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'
|
},
|
})
|
} : null;
|
const dropoffMarker = dropoffSite?.latitude && dropoffSite?.longitude ? {
|
id: 2,
|
latitude: dropoffSite.latitude,
|
longitude: dropoffSite.longitude,
|
iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png',
|
width: 22,
|
height: 40,
|
anchor: { x: 0.5, y: 1 },
|
zIndex: 1,
|
name: [7].includes(tripStatus) ? '已到达' : (dropoffSite.name || '目的地'),
|
...(useAndroidNativeTrip1Callout
|
? {
|
callout: {
|
content: `${[7].includes(tripStatus) ? '已到达' : (dropoffSite.name || '目的地')} >`,
|
color: '#000000',
|
fontSize: 12,
|
bgColor: '#FFFFFF',
|
borderRadius: 8,
|
padding: 8,
|
display: 'ALWAYS',
|
textAlign: 'center',
|
},
|
}
|
: {
|
customCallout: {
|
anchorY: -5,
|
anchorX: 0,
|
display: 'ALWAYS'
|
},
|
})
|
} : null;
|
const carMarker = vehicleLocation ? {
|
id: vehicleMarkerId,
|
latitude: vehicleLocation.latitude,
|
longitude: vehicleLocation.longitude,
|
iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e51e92e6-700d-4e83-b3e0-14e6f18e0b9a.png',
|
width: 40,
|
height: 20,
|
anchor: { x: 0.5, y: 0.5 },
|
zIndex: 2,
|
rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0,
|
name: '车辆位置',
|
customCallout: { anchorY: 70, anchorX: 0, display: 'ALWAYS' }
|
} : null;
|
|
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);
|
if (shouldSkipUpdate) {
|
} else {
|
if (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;
|
}, [tripStatus, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation, vehicleDirection, vehicleMarkerId]);
|
|
// 绘制路线与视野适配
|
useEffect(() => {
|
// 状态4/6使用 eraseLines,不缩减点数以保证坐标匹配
|
const isEraseMode = [4, 6].includes(tripStatus);
|
const displayRoutePoints = isEraseMode ? routePoints : reducePoints(routePoints, isAndroid ? 120 : 200);
|
const lastRoutePoint = displayRoutePoints[displayRoutePoints.length - 1];
|
const lastWalkingPoint = walkingPoints[walkingPoints.length - 1];
|
const isTripMoving = [4, 5, 6].includes(tripStatus);
|
const now = Date.now();
|
const throttle = isAndroid ? 1200 : 800;
|
if (shouldShowRoute && isTripMoving && displayRoutePoints.length > 0 && now - lastPolylineUpdateAtRef.current < throttle) {
|
return;
|
}
|
const nextKey = [
|
shouldShowRoute ? 1 : 0,
|
statusRule.showWalking ? 1 : 0,
|
displayRoutePoints.length,
|
walkingPoints.length,
|
lastRoutePoint?.latitude ?? '',
|
lastRoutePoint?.longitude ?? '',
|
lastWalkingPoint?.latitude ?? '',
|
lastWalkingPoint?.longitude ?? '',
|
].join('|');
|
if (nextKey === polylineKeyRef.current) {
|
return;
|
}
|
polylineKeyRef.current = nextKey;
|
if (shouldShowRoute && (displayRoutePoints.length > 0 || (statusRule.showWalking && walkingPoints.length > 0))) {
|
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,
|
});
|
}
|
setPolyline(lines);
|
lastPolylineUpdateAtRef.current = now;
|
} else {
|
setPolyline([]);
|
lastPolylineUpdateAtRef.current = 0;
|
}
|
}, [tripStatus, shouldShowRoute, routePoints, walkingPoints, statusRule.showWalking, isAndroid]);
|
|
useEffect(() => {
|
const pickupSite = startSite?.latitude && startSite?.longitude ? startSite : Taro.getStorageSync('startSite');
|
const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
|
const viewportType = statusRule.viewport;
|
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);
|
} else if (viewportType === 'pickup') {
|
if (pickupSite?.latitude && pickupSite?.longitude) {
|
points.push({ latitude: pickupSite.latitude, longitude: pickupSite.longitude });
|
}
|
keyParts.push(pickupSite?.latitude, pickupSite?.longitude);
|
} else if (viewportType === 'route') {
|
if (routePoints.length > 0) {
|
points.push(...routePoints);
|
}
|
keyParts.push(routePoints.length);
|
} else 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);
|
} else if (viewportType === 'dropoff') {
|
if (dropoffSite?.latitude && dropoffSite?.longitude) {
|
points.push({ latitude: dropoffSite.latitude, longitude: dropoffSite.longitude });
|
}
|
keyParts.push(dropoffSite?.latitude, dropoffSite?.longitude);
|
} else 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);
|
}
|
|
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;
|
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);
|
}
|
});
|
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');
|
// Taro.nextTick(() => {
|
mapCtx.translateMarker({
|
markerId: vehicleMarkerId,
|
destination: {
|
latitude: vehicleLocation.latitude,
|
longitude: vehicleLocation.longitude,
|
},
|
autoRotate: false,
|
rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0,
|
duration: 100,
|
fail: (err) => {
|
console.log(err);
|
|
},
|
});
|
// });
|
}, [tripStatus, vehicleLocation, vehicleDirection, vehicleMarkerId]);
|
|
const bindSocketListeners = () => {
|
if (!orderStatusBound.current && orderStatusHandlerRef.current) {
|
wsManager.on('order_status', orderStatusHandlerRef.current);
|
orderStatusBound.current = true;
|
}
|
if (!orderRouteBound.current && orderRouteHandlerRef.current) {
|
wsManager.on('order_route', orderRouteHandlerRef.current);
|
orderRouteBound.current = true;
|
}
|
if (!vehicleLocationBound.current && vehicleLocationHandlerRef.current) {
|
wsManager.on('vehicle_location', vehicleLocationHandlerRef.current);
|
vehicleLocationBound.current = true;
|
}
|
if (!openTrunkBound.current && openTrunkHandlerRef.current) {
|
wsManager.on('open_trunk', openTrunkHandlerRef.current);
|
openTrunkBound.current = true;
|
}
|
if (!orderUpdatePriceBound.current && orderUpdatePriceHandlerRef.current) {
|
wsManager.on('order_update_price', orderUpdatePriceHandlerRef.current);
|
orderUpdatePriceBound.current = true;
|
}
|
};
|
|
const ensureSocketListeners = () => {
|
if (wsManager.isConnectedStatus()) {
|
bindSocketListeners();
|
return;
|
}
|
if (socketOpenHandlerRef.current) {
|
return;
|
}
|
const handleOpen = () => {
|
bindSocketListeners();
|
};
|
socketOpenHandlerRef.current = handleOpen;
|
wsManager.on('open', handleOpen);
|
};
|
|
// 页面初始化
|
useDidShow(() => {
|
const { skipPageFlag } = router.params;
|
if (skipPageFlag) {
|
Taro.showToast({
|
title: '您已有进行中的订单',
|
icon: 'none',
|
})
|
}
|
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;
|
if (cachedOrderId) {
|
setOrderId(cachedOrderId);
|
queryVehicleByOrderId({ orderId: cachedOrderId })
|
.then((res: any) => {
|
setVehicleInfo(res);
|
})
|
.catch(() => { });
|
}
|
if (cachedTripStatusStorage && cachedTripStatusStorage != 1) {
|
getOrderActive()
|
.then((res: any) => {
|
if (!res) {
|
return;
|
}
|
setTrunkStatus(res.trunkStatus == 2 ? 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,
|
};
|
Taro.setStorageSync('tripStatus', mapOrderStatusToTripStatus(res.status || 1));
|
setTripStatus(mapOrderStatusToTripStatus(res.status || 1));
|
Taro.setStorageSync('startSite', nextStartSite);
|
Taro.setStorageSync('endSite', nextEndSite);
|
setStartSite(nextStartSite);
|
setEndSite(nextEndSite);
|
})
|
.catch(() => { });
|
} else {
|
if (s) {
|
setStartSite(s);
|
}
|
if (e) {
|
setEndSite(e);
|
}
|
}
|
if (cachedOrderId && [4, 5, 6, 7, 8, 9].includes(cachedTripStatus)) {
|
requestOrderRoute(cachedOrderId, cachedTripStatus);
|
}
|
|
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();
|
}
|
|
ensureSocketListeners();
|
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');
|
mapCtx.moveToLocation();
|
}
|
|
const setPopupVisible = (setter: (value: boolean) => void, visible: boolean) => () => setter(visible);
|
const handleCloseCustomerService = setPopupVisible(setShowCustomerService, false);
|
const handleCloseTipPopup = () => {
|
setShowTipPopup(false);
|
setTimeout(() => {
|
setTipType('');
|
}, 600);
|
};
|
|
const openService = () => {
|
const phoneNumber = Taro.getStorageSync('customerServicePhone');
|
Taro.makePhoneCall({
|
phoneNumber,
|
})
|
}
|
|
// 立即呼叫
|
const callImmediately = debounce(async () => {
|
if (callSubmitting) {
|
return;
|
}
|
setCallSubmitting(true);
|
const otp = await checkOtp(false);
|
if (!otp) {
|
setCallSubmitting(false);
|
jumpTo({ url: '/pages/user/login?hideBack=1' });
|
return
|
}
|
if (!endSite.name) {
|
Taro.showToast({
|
title: '请选择站点',
|
icon: 'none',
|
})
|
setCallSubmitting(false);
|
return
|
}
|
if (routeAndFareError) {
|
setCallSubmitting(false);
|
return
|
}
|
const s = Taro.getStorageSync('startSite');
|
if (!s?.latitude || !s?.longitude || !endSite?.latitude || !endSite?.longitude) {
|
Taro.showToast({
|
title: '请先选择起终点',
|
icon: 'none',
|
})
|
setCallSubmitting(false);
|
return
|
}
|
let maxDistance = distanceConfig?.maxDistance;
|
let minDistance = distanceConfig?.minDistance;
|
if (!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 { }
|
}
|
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);
|
setCallSubmitting(false);
|
return;
|
}
|
} catch { }
|
}
|
if (minDistance) {
|
const distance = getDistance(s.latitude, s.longitude, endSite.latitude, endSite.longitude);
|
if (distance < minDistance) {
|
setTipType('1');
|
setShowTipPopup(true);
|
setCallSubmitting(false);
|
return;
|
}
|
}
|
const selectedCity = Taro.getStorageSync('selectedCity');
|
let openCityId = selectedCity?.id || 0;
|
|
// 如果缓存中没有城市ID,尝试通过定位重新匹配一次
|
if (!openCityId) {
|
try {
|
const loc = await Taro.getLocation({ type: 'gcj02' });
|
const data = await getAddressByLocation(loc.longitude, loc.latitude);
|
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);
|
if (cityInfo?.id) {
|
openCityId = cityInfo.id;
|
Taro.setStorageSync('selectedCity', cityInfo);
|
}
|
}
|
} catch (e) {
|
console.error('Failed to fetch city info before order:', e);
|
}
|
}
|
|
// 如果还是拿不到城市ID,拦截下单
|
if (!openCityId) {
|
Taro.showToast({
|
title: '未能获取所在城市信息,请重试',
|
icon: 'none',
|
});
|
setCallSubmitting(false);
|
return;
|
}
|
|
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: openCityId,
|
placementAddress: s?.name,
|
placementLat: s?.latitude,
|
placementLon: s?.longitude,
|
startPointId: s?.id,
|
xpElectronicFenceAreaId: s?.electronicAreaId || endSite?.electronicAreaId || '',
|
estimatedTime: estimatedTime,
|
};
|
if (cachedCouponId) {
|
payload.userCouponId = cachedCouponId;
|
}
|
try {
|
const res = await saveOrder(payload);
|
let status_code = res.orderId ? 1 : 0;
|
reportEvent('RT_xcx_order_submit', {
|
total_amount: fareEstimate.originalFare,
|
payable_amount: fareEstimate.finalFare,
|
coupon_id: cachedCouponId,
|
status_code: status_code,
|
})
|
if (typeof res == 'string') {
|
Taro.showToast({
|
title: res,
|
icon: 'none',
|
mask: true,
|
})
|
setTimeout(() => {
|
Taro.reLaunch({
|
url: '/pages/home/index',
|
})
|
}, 1500);
|
return
|
}
|
if (!res?.orderId) {
|
Taro.showToast({
|
title: '下单失败,请重试',
|
icon: 'none',
|
});
|
setCallSubmitting(false);
|
return;
|
}
|
Taro.removeStorageSync('selectedCouponId');
|
Taro.setStorageSync('orderId', res.orderId);
|
setOrderId(res.orderId);
|
} catch (error) {
|
setCallSubmitting(false);
|
return
|
}
|
setTripStatus(2);
|
if (!authSuccessBound.current && authSuccessHandlerRef.current) {
|
wsManager.on('auth_success', authSuccessHandlerRef.current);
|
authSuccessBound.current = true;
|
}
|
if (!orderStatusBound.current && orderStatusHandlerRef.current) {
|
wsManager.on('order_status', orderStatusHandlerRef.current);
|
orderStatusBound.current = true;
|
}
|
if (!orderRouteBound.current && orderRouteHandlerRef.current) {
|
wsManager.on('order_route', orderRouteHandlerRef.current);
|
orderRouteBound.current = true;
|
}
|
if (!vehicleLocationBound.current && vehicleLocationHandlerRef.current) {
|
wsManager.on('vehicle_location', vehicleLocationHandlerRef.current);
|
vehicleLocationBound.current = true;
|
}
|
if (!orderUpdatePriceBound.current && orderUpdatePriceHandlerRef.current) {
|
wsManager.on('order_update_price', orderUpdatePriceHandlerRef.current);
|
orderUpdatePriceBound.current = true;
|
}
|
}, 500)
|
|
// 确认取消
|
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',
|
});
|
setOrderId('');
|
clearOrderTripStorage();
|
Taro.removeStorageSync('pickupEndTime');
|
Taro.removeStorageSync('waitingTimerStartAt');
|
Taro.removeStorageSync('waitingTimerElapsed');
|
setTripStatus(10);
|
setCancelType(4);
|
return
|
} catch (error) {
|
Taro.showToast({
|
title: '取消订单失败,请重试',
|
icon: 'none',
|
});
|
return;
|
}
|
} else {
|
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 handleCloseCancelPopup = setPopupVisible(setShowCancelPopup, false);
|
// 订单超时再来一单
|
const handleOpenCancelPopupWithType = (type: number) => {
|
setOrderId('');
|
clearOrderTripStorage();
|
setCancelType(type);
|
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 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 handleCloseAnnouncementsPopup = setPopupVisible(setShowAnnouncementsPopup, false);
|
const handleOpenAnnouncementsPopup = setPopupVisible(setShowAnnouncementsPopup, true);
|
const handleOpenOverRoutePopup = setPopupVisible(setShowOverRoutePopup, true);
|
const handleCloseOverRoutePopup = setPopupVisible(setShowOverRoutePopup, false);
|
|
// 确认结束行程
|
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 handleClosePassengerNotice = () => {
|
setShowPassengerNotice(false);
|
};
|
const handleOpenPassengerNotice = () => {
|
setShowPassengerNotice(true);
|
};
|
|
// 打开评价弹窗
|
const handleOpenEvaluatePopup = setPopupVisible(setShowEvaluatePopup, true);
|
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 handleCalloutTap = (e) => {
|
if (isCheckedEnter.current) {
|
isCheckedEnter.current = false;
|
return;
|
}
|
if (tripStatus === 1) {
|
// 仅在发布行程时,点击标记用于选择起点或终点
|
const markerId = e.detail.markerId;
|
toSelectSite(markerId === 1 ? 'start' : 'end');
|
}
|
}
|
|
const toCoupon = () => {
|
if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') {
|
Taro.showToast({
|
title: '请先同意协议',
|
icon: 'none'
|
});
|
return;
|
}
|
Taro.navigateTo({
|
url: '/robotaxi/pages/coupon/index',
|
})
|
}
|
|
const toOrder = () => {
|
if (Taro.getStorageSync('has_show_privicy_Robotaxi') !== 'true') {
|
Taro.showToast({
|
title: '请先同意协议',
|
icon: 'none'
|
});
|
return;
|
}
|
Taro.navigateTo({
|
url: '/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 durationDisplay = formatDurationDisplay(routeDuration);
|
const mapLongitude = longitude ?? 116.397128;
|
const mapLatitude = latitude ?? 39.916527;
|
|
return (
|
<View className="home-container">
|
<Map
|
id="map"
|
longitude={mapLongitude}
|
latitude={mapLatitude}
|
scale={mapScale}
|
markers={markers}
|
polyline={polyline}
|
showLocation={!!longitude && showLocationFlag}
|
onCalloutTap={handleCalloutTap}
|
onRegionChange={handleRegionChange}
|
style={{ width: '100%', height: '87vh' }}
|
>
|
{/* 自定义callout */}
|
{
|
!(isAndroid && tripStatus === 1) && markers.map((item, index) => {
|
const calloutKey = `callout-${item.id}-${item.name || ''}`;
|
const isWaiting = [2, 3].includes(tripStatus) && index === 0;
|
|
if (isWaiting) {
|
return (
|
<CoverView slot="callout" marker-id={item.id} key={calloutKey} className="waiting-callout">
|
<CoverView className="waiting-callout-content">
|
{/* <CoverView className="waiting-callout-bg">
|
<CoverImage className="waiting-callout-bg-img" src={waitingCalloutBgSrc} />
|
<CoverView className="waiting-callout-bg-text">{formatDurationToClock(elapsedSeconds)}</CoverView>
|
</CoverView> */}
|
<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>
|
)
|
}
|
|
if (item.name === '车辆位置' && isAndroid && [4, 6].includes(tripStatus)) {
|
return null;
|
}
|
return (
|
<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>
|
)
|
})
|
}
|
{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>
|
)}
|
{/* 返回按钮 */}
|
<View onClick={toBack} className="select-city">
|
<Image className="select-city-icon" src={backIcon} />
|
</View>
|
{/* 地图浮层入口 */}
|
<View className="map-entry">
|
<Image onClick={(e) => { isCheckedEnter.current = true; openService() }} className="map-entry-icon" src={contactService} />
|
<View className="split-line"></View>
|
<Image onClick={(e) => { isCheckedEnter.current = true; toCoupon() }} className="map-entry-icon" src={coupon} />
|
<View className="split-line"></View>
|
<Image onClick={(e) => { isCheckedEnter.current = true; toOrder() }} className="map-entry-icon" src={suggest} />
|
</View>
|
<View className="container">
|
<View className="container-inner">
|
{/* 复位按钮 */}
|
{
|
![7, 8, 9].includes(tripStatus) &&
|
<View className="origin-box" style={{ opacity: originBoxHidden ? 0 : originOpacity, transition: 'opacity 0.2s ease-out', pointerEvents: originBoxHidden ? 'none' : 'auto' }}>
|
<Image onClick={toOrigin} className="origin-icon" src={originIcon} />
|
</View>
|
}
|
<View catchMove onClick={(e) => e.stopPropagation()}>
|
{/* 发布路线 */}
|
{
|
(tripStatus == 1 && startSite && endSite) &&
|
<ReleaseRoute
|
startSite={startSite}
|
endSite={endSite}
|
callImmediately={callImmediately}
|
fareEstimate={fareEstimate}
|
handleOpenPassengerNotice={handleOpenPassengerNotice}
|
walkingTime={walkingTime}
|
isCallDisabled={routeAndFareError || callSubmitting}
|
/>
|
}
|
{/* 发布中弹窗 */}
|
{
|
tripStatus == 2 &&
|
<ReleaseBeBeing
|
handleOpenCancelPopup={() => handleOpenCancelPopupByTripStatus(2)}
|
onFinish={() => {
|
setTripStatus(3);
|
Taro.setStorageSync('tripStatus', 3);
|
}}
|
/>
|
}
|
{/* 呼叫车辆弹窗 */}
|
{
|
tripStatus >= 3 && tripStatus !== 10 &&
|
<CallingCar
|
key={tripStatus}
|
orderId={orderId}
|
tripStatus={tripStatus}
|
finishCountdownEnabled={finishCountdownEnabled}
|
vehicleInfo={vehicleInfo}
|
handleOpenCancelPopup={(durationDisplay) => handleOpenCancelPopupByTripStatus(tripStatus, durationDisplay)}
|
handleCancelTimeout={() => handleOpenCancelPopupWithType(4)}
|
handleFinishTimeout={handleFinishCountdownTimeout}
|
resetDetailVersion={resetDetailVersion}
|
priceUpdateVersion={priceUpdateVersion}
|
evaluateSubmitVersion={evaluateSubmitVersion}
|
evaluateCloseVersion={evaluateCloseVersion}
|
handleOpenAnnouncementsPopup={handleOpenAnnouncementsPopup}
|
handleOpenOverRoutePopup={handleOpenOverRoutePopup}
|
handlePay={handlePay}
|
updateStatus={() => setTripStatus(prev => prev + 1)}
|
handleOpenEvaluatePopup={handleOpenEvaluatePopup}
|
estimatedWaitMinutes={fareEstimate?.waitingTime}
|
routeDurationSeconds={routeDuration}
|
onToggleDetailFade={handleToggleDetailFade}
|
orderTrunkStatus={trunkStatus}
|
/>
|
}
|
</View>
|
</View>
|
</View>
|
</Map>
|
{/* 客服弹窗 */}
|
<CustomerServicePopup
|
show={showCustomerService}
|
onClose={handleCloseCustomerService}
|
/>
|
{/* 取消弹窗 */}
|
<CancelPopup
|
show={showCancelPopup}
|
cancelType={cancelType}
|
startSite={startSite}
|
endSite={endSite}
|
waitMinutes={fareEstimate?.waitingTime}
|
tripStatus={tripStatus}
|
routeDurationSeconds={routeDuration}
|
discountAmount={fareEstimate?.discountAmount}
|
onClose={handleCloseCancelPopup}
|
onConfirm={handleConfirmCancel}
|
/>
|
{/* 安全注意事项弹窗 */}
|
<AnnouncementsPopup
|
show={showAnnouncementsPopup}
|
onClose={handleCloseAnnouncementsPopup}
|
/>
|
{/* 结束行程弹窗 */}
|
<OverRoute
|
show={showOverRoutePopup}
|
onClose={handleCloseOverRoutePopup}
|
onConfirm={handleConfirmOverRoute}
|
/>
|
<TipPopup
|
show={showTipPopup}
|
onClose={handleCloseTipPopup}
|
tipType={tipType}
|
/>
|
{/* 乘车需知弹窗 */}
|
{
|
!showCancelPopup &&
|
<PassengerNotice
|
show={showPassengerNotice}
|
onClose={handleClosePassengerNotice}
|
/>
|
}
|
{/* 评价弹窗 */}
|
<EvaluatePopup
|
show={showEvaluatePopup}
|
orderId={orderId}
|
orderRating={1}
|
onClose={handleCloseEvaluatePopup}
|
onConfirm={handleConfirmEvaluate}
|
/>
|
</View>
|
)
|
}
|