hejianhao
2026-04-17 955c9066fb99669df5ab5050c4125bbb892ce0ce
fix: 修复行程状态切换时定时器与地图视角的竞争条件

修复行程状态为5时误清空上车时间存储的问题,确保只在状态5时清理。
在行程倒计时钩子中添加状态引用,防止状态切换后旧定时器回调错误触发取消逻辑。
在地图视口钩子中添加订单详情视图锁定,避免状态8/9时因依赖变化导致地图视角跳动。
6个文件已修改
99 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/use-trip-countdown.ts
@@ -36,6 +36,7 @@
    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);
@@ -108,12 +109,21 @@
    }, [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) {
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -159,26 +159,8 @@
      ].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]);
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
@@ -49,7 +49,10 @@
    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 {
@@ -183,6 +186,22 @@
        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);
@@ -274,11 +293,48 @@
    }, [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;
@@ -286,6 +342,10 @@
        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]);
@@ -314,6 +374,7 @@
        setShowLocationFlag,
        mapRectRef,
        viewportKeyRef,
        orderDetailViewportLockedRef,
        tripStatus6ViewportLockedRef,
        isAndroid,
        resolveSafeBottomInset,
src/robotaxi/pages/begin-route/hooks/use-trip-runtime.ts
@@ -231,7 +231,7 @@
  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;
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -202,12 +202,10 @@
    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;
        }
    }
src/robotaxi/pages/begin-route/index.tsx
@@ -110,7 +110,7 @@
    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 },