fix: 修复行程状态切换时定时器与地图视角的竞争条件
修复行程状态为5时误清空上车时间存储的问题,确保只在状态5时清理。
在行程倒计时钩子中添加状态引用,防止状态切换后旧定时器回调错误触发取消逻辑。
在地图视口钩子中添加订单详情视图锁定,避免状态8/9时因依赖变化导致地图视角跳动。
| | |
| | | const pickupTimerRef = useRef<any>(null); |
| | | const finishTimerRef = useRef<any>(null); |
| | | const countdownFinishedRef = useRef(false); |
| | | const pickupTripStatusRef = useRef(tripStatus); |
| | | const pickupEndTimeRef = useRef<number | null>(null); |
| | | const finishEndTimeRef = useRef<number | null>(null); |
| | | const lastFareOrderIdRef = useRef<string | null>(null); |
| | |
| | | }, [tripStatus]); |
| | | |
| | | useEffect(() => { |
| | | pickupTripStatusRef.current = tripStatus; |
| | | }, [tripStatus]); |
| | | |
| | | useEffect(() => { |
| | | if (tripStatus !== 5) { |
| | | clearPickupTimer(); |
| | | return; |
| | | } |
| | | |
| | | const runPickupTick = () => { |
| | | // 防止状态已切换后,旧定时回调仍触发取消逻辑 |
| | | if (pickupTripStatusRef.current !== 5) { |
| | | clearPickupTimer(); |
| | | return; |
| | | } |
| | | const remaining = getRemainingSeconds(pickupEndTimeRef.current); |
| | | if (remaining !== null && remaining <= 0) { |
| | | if (!countdownFinishedRef.current) { |
| | |
| | | ].join('|')) |
| | | .join(';'); |
| | | const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2); |
| | | const hasCarMarker = markersRef.current.some((m: any) => m.id === deps.vehicleMarkerId); |
| | | const currentCarMarker = markersRef.current.find((m: any) => m.id === deps.vehicleMarkerId); |
| | | const currentRotate = Number(currentCarMarker?.rotate); |
| | | const targetRotate = Number(deps.vehicleDirection); |
| | | const angleDiff = (a: number, b: number) => { |
| | | const diff = Math.abs(((a - b + 540) % 360) - 180); |
| | | return diff; |
| | | }; |
| | | const isArrivedAngleSynced = Number.isFinite(currentRotate) |
| | | && Number.isFinite(targetRotate) |
| | | && angleDiff(currentRotate, targetRotate) < 1; |
| | | const shouldHoldMarkerByTranslate = [4, 6].includes(deps.tripStatus) && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker); |
| | | const shouldHoldMarkerWhenArrived = deps.tripStatus === 7 |
| | | && deps.arrivedTooltipReady |
| | | && carMarkerInitialized.current |
| | | && effectiveShowCar |
| | | && hasCarMarker |
| | | && isArrivedAngleSynced |
| | | && (!deps.statusRule.showDropoff || hasDropoffMarker); |
| | | const shouldSkipUpdate = (shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current)) || shouldHoldMarkerWhenArrived; |
| | | const shouldSkipUpdate = shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current); |
| | | applyMarkerState(nextMarkers, nextMarkersSignature, shouldSkipUpdate, carMarker); |
| | | }, [deps.tripStatus, deps.startSite?.name, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.name, deps.endSite?.latitude, deps.endSite?.longitude, deps.vehicleLocation, deps.vehicleDirection, deps.vehicleMarkerId, deps.isAndroid, deps.statusRule.showPickup, deps.statusRule.showCar, deps.statusRule.showDropoff]); |
| | | |
| | |
| | | |
| | | const mapRectRef = useRef<any>(null); |
| | | const viewportKeyRef = useRef<string>(''); |
| | | const orderDetailViewportLockedRef = useRef(false); |
| | | const tripStatus6ViewportLockedRef = useRef(false); |
| | | const orderDetailEnterSignatureRef = useRef(''); |
| | | const orderDetailRouteUpdatedAfterEnterRef = useRef(false); |
| | | |
| | | const isAndroid = useMemo(() => { |
| | | try { |
| | |
| | | return { points, keyParts }; |
| | | }; |
| | | |
| | | const buildOrderDetailRouteSignature = (pickupSite: any, dropoffSite: any) => { |
| | | const firstPoint = deps.routePoints[0]; |
| | | const lastPoint = deps.routePoints[deps.routePoints.length - 1]; |
| | | return [ |
| | | deps.routePoints.length, |
| | | firstPoint?.latitude ?? '', |
| | | firstPoint?.longitude ?? '', |
| | | lastPoint?.latitude ?? '', |
| | | lastPoint?.longitude ?? '', |
| | | pickupSite?.latitude ?? '', |
| | | pickupSite?.longitude ?? '', |
| | | dropoffSite?.latitude ?? '', |
| | | dropoffSite?.longitude ?? '', |
| | | ].join('|'); |
| | | }; |
| | | |
| | | const updateViewportByPayload = (viewportType: string, points: any[]) => { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const bottomPadding = computeBottomPadding(points, viewportType); |
| | |
| | | }, [deps.tripStatus, deps.statusRule?.viewport, deps.endSite?.latitude, deps.endSite?.longitude, mapScale]); |
| | | |
| | | useEffect(() => { |
| | | if (![8, 9].includes(deps.tripStatus)) { |
| | | orderDetailViewportLockedRef.current = false; |
| | | orderDetailRouteUpdatedAfterEnterRef.current = false; |
| | | orderDetailEnterSignatureRef.current = ''; |
| | | return; |
| | | } |
| | | const { pickupSite, dropoffSite } = resolveViewportSites(); |
| | | orderDetailEnterSignatureRef.current = buildOrderDetailRouteSignature(pickupSite, dropoffSite); |
| | | orderDetailRouteUpdatedAfterEnterRef.current = false; |
| | | orderDetailViewportLockedRef.current = false; |
| | | viewportKeyRef.current = ''; |
| | | setMapScale(14); |
| | | }, [deps.tripStatus]); |
| | | |
| | | useEffect(() => { |
| | | if (![8, 9].includes(deps.tripStatus)) return; |
| | | const { pickupSite, dropoffSite } = resolveViewportSites(); |
| | | const currentSignature = buildOrderDetailRouteSignature(pickupSite, dropoffSite); |
| | | if (!orderDetailEnterSignatureRef.current) { |
| | | orderDetailEnterSignatureRef.current = currentSignature; |
| | | return; |
| | | } |
| | | if (currentSignature === orderDetailEnterSignatureRef.current) return; |
| | | orderDetailRouteUpdatedAfterEnterRef.current = true; |
| | | orderDetailViewportLockedRef.current = false; |
| | | viewportKeyRef.current = ''; |
| | | }, [deps.tripStatus, deps.routePoints, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.latitude, deps.endSite?.longitude]); |
| | | |
| | | useEffect(() => { |
| | | const { pickupSite, dropoffSite } = resolveViewportSites(); |
| | | const viewportType = deps.statusRule.viewport; |
| | | if (viewportType === 'dropoff') return; |
| | | const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite); |
| | | if (points.length === 0) return; |
| | | if ( |
| | | [8, 9].includes(deps.tripStatus) && |
| | | viewportType === 'order_detail' && |
| | | orderDetailViewportLockedRef.current && |
| | | orderDetailRouteUpdatedAfterEnterRef.current |
| | | ) { |
| | | return; |
| | | } |
| | | if (deps.tripStatus === 6 && viewportType === 'route' && tripStatus6ViewportLockedRef.current) return; |
| | | const nextKey = keyParts.join('|'); |
| | | if (nextKey === viewportKeyRef.current) return; |
| | |
| | | updateViewportByPayload(viewportType, points); |
| | | if (deps.tripStatus === 6 && viewportType === 'route') { |
| | | tripStatus6ViewportLockedRef.current = true; |
| | | } |
| | | if ([8, 9].includes(deps.tripStatus) && viewportType === 'order_detail') { |
| | | const hasOrderDetailRouteAfterEnter = orderDetailRouteUpdatedAfterEnterRef.current && deps.routePoints.length > 0; |
| | | orderDetailViewportLockedRef.current = hasOrderDetailRouteAfterEnter; |
| | | } |
| | | }, [deps.tripStatus, deps.statusRule, deps.routePoints, deps.walkingPoints, deps.startSite?.latitude, deps.startSite?.longitude, deps.endSite?.latitude, deps.endSite?.longitude, deps.vehicleLocation, deps.longitude, deps.latitude, mapBottomPadding]); |
| | | |
| | |
| | | setShowLocationFlag, |
| | | mapRectRef, |
| | | viewportKeyRef, |
| | | orderDetailViewportLockedRef, |
| | | tripStatus6ViewportLockedRef, |
| | | isAndroid, |
| | | resolveSafeBottomInset, |
| | |
| | | useEffect(() => { |
| | | const wasHold = [4, 6].includes(deps.lastTripStatusRef.current); |
| | | const isHold = [4, 6].includes(deps.tripStatus); |
| | | if (deps.tripStatus != 4) Taro.removeStorageSync('pickupEndTime'); |
| | | if (deps.tripStatus != 5) Taro.removeStorageSync('pickupEndTime'); |
| | | if (deps.tripStatus != 7) Taro.removeStorageSync('finishEndTime'); |
| | | if (!wasHold && isHold) { |
| | | deps.orderRouteInitializedRef.current = false; |
| | |
| | | const currentTripStatus = Number(deps.tripStatusRef.current); |
| | | if (currentTripStatus === 7) { |
| | | const lastPoint = deps.lastVehiclePointRef.current; |
| | | const lastUpdateAt = Number(deps.lastVehicleUpdateAtRef.current || 0); |
| | | const now = Date.now(); |
| | | const latDiff = Math.abs(Number(lastPoint?.latitude || 0) - targetPoint.latitude); |
| | | const lngDiff = Math.abs(Number(lastPoint?.longitude || 0) - targetPoint.longitude); |
| | | const almostSamePoint = latDiff < 0.000003 && lngDiff < 0.000003; |
| | | if (almostSamePoint || (now - lastUpdateAt < 1200)) { |
| | | if (almostSamePoint) { |
| | | return; |
| | | } |
| | | } |
| | |
| | | 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 }, |
| | | 7: { showPickup: false, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true }, |
| | | 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 }, |