zjk
2026-06-13 c1bffa43dfea3d1ccaffb9b8d2bcdcf2c939faa2
fix(share-trip): 修复共享行程页多项路由与显示异常

- 新增路由标识校验,切换路线时自动清空旧运行时状态避免轨迹残影
- 调整下车点展示逻辑,仅在到达接客点后渲染下车标记
- 优化车辆标记动画状态管理,修复标记切换时的动画异常
- 修复行程完成后仍显示路线摘要的问题
- 优化地图视口同步逻辑,修复安卓端视口偏移异常
- 重构路线方向计算与快照生成逻辑,添加调试日志
8个文件已修改
286 ■■■■■ 已修改文件
src/robotaxi/pages/share-trip/hooks/runtime-sync.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-viewport.ts 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-route-manager.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-share-trip-runtime.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-smooth-route-motion.ts 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-vehicle-tracking.ts 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/index.tsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/runtime-sync.ts
@@ -20,6 +20,7 @@
    duration: number | null;
    direction: number | null;
    timestamp: number | null;
    routeKey: string;
    vehicleMoveAlongPoints: RoutePoint[];
    forceFullRedraw: boolean;
    eraseVehiclePointMode: VehiclePointMode;
@@ -56,6 +57,7 @@
    duration: any;
    direction: any;
    timestamp?: any;
    routeKey?: any;
    forceFullRedraw?: boolean;
}): RouteSnapshot {
    const timestamp = Number(payload?.timestamp);
@@ -65,6 +67,7 @@
        duration: normalizeDurationSeconds(Number(payload?.duration)),
        direction: normalizeDirection(payload?.direction),
        timestamp: Number.isFinite(timestamp) ? timestamp : Date.now(),
        routeKey: String(payload?.routeKey || ''),
        vehicleMoveAlongPoints: normalizeRoutePoints(payload?.servedPoints),
        forceFullRedraw: payload?.forceFullRedraw === true,
        eraseVehiclePointMode: 'first_then_served_last',
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -74,6 +74,7 @@
    const markersSignatureRef = useRef<string>("");
    const carMarkerInitialized = useRef(false);
    const tripStatus6MarkerRefreshedRef = useRef(false);
    const readyNotifiedMarkerIdRef = useRef<number | null>(null);
    const shouldUsePureTranslateCarMarker =
        [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus)
        && deps.statusRule.showCar;
@@ -126,6 +127,8 @@
        useAndroidNativeTrip1Callout: boolean
    ) => {
        if (!(dropoffSite?.latitude && dropoffSite?.longitude)) return null;
        // 分享行程只有从 5/6 及之后的状态才渲染下车点,避免 4 态提前入列引发地图多次缩放。
        if (deps.tripStatus < TripStatus.VehicleArrivedPickup) return null;
        const dropoffName =
            [TripStatus.ArrivedDestination].includes(deps.tripStatus) ? '已到达' : dropoffSite.name || '目的地';
        return {
@@ -215,8 +218,13 @@
                markersSignatureRef.current = nextMarkersSignature;
                const mergedMarkers = [...nextMarkers];
                const nextCar = mergedMarkers[nextCarIndex] || {};
                const shouldRefreshCarMarkerStructure =
                    buildCarMarkerStructureSignature(prevCar) !== buildCarMarkerStructureSignature(nextCar);
                const latestVehicleLatitude = Number(deps.vehicleLocation?.latitude);
                const latestVehicleLongitude = Number(deps.vehicleLocation?.longitude);
                if (!shouldRefreshCarMarkerStructure && !shouldClearCarRotate) {
                    return prevMarkers;
                }
                mergedMarkers[nextCarIndex] = {
                    ...nextCar,
                    latitude: Number.isFinite(latestVehicleLatitude)
@@ -252,8 +260,13 @@
    useEffect(() => {
        const hasVehicleMarker = deps.statusRule.showCar && markers.some((item: any) => item.id === deps.vehicleMarkerId);
        if (!hasVehicleMarker) {
            readyNotifiedMarkerIdRef.current = null;
            return;
        }
        if (readyNotifiedMarkerIdRef.current === deps.vehicleMarkerId) {
            return;
        }
        readyNotifiedMarkerIdRef.current = deps.vehicleMarkerId;
        Taro.nextTick(() => {
            deps.onVehicleMarkerReady();
        });
src/robotaxi/pages/share-trip/hooks/use-map-viewport.ts
@@ -75,6 +75,22 @@
    ].join("|");
};
const buildOrderDetailRouteSignature = (routePoints: any[], pickupSite: any, dropoffSite: any) => {
    const firstPoint = routePoints[0];
    const lastPoint = routePoints[routePoints.length - 1];
    return [
        routePoints.length,
        firstPoint?.latitude ?? "",
        firstPoint?.longitude ?? "",
        lastPoint?.latitude ?? "",
        lastPoint?.longitude ?? "",
        pickupSite?.latitude ?? "",
        pickupSite?.longitude ?? "",
        dropoffSite?.latitude ?? "",
        dropoffSite?.longitude ?? "",
    ].join("|");
};
export default function useMapViewport(rawDeps?: Partial<Deps>) {
    const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps;
    const [mapBottomPadding, setMapBottomPadding] = useState(280);
@@ -83,6 +99,7 @@
    const mapRectRef = useRef<any>(null);
    const viewportKeyRef = useRef<string>("");
    const orderDetailSyncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
    const completedOrderDetailViewportKeyRef = useRef<string>("");
    const dynamicScaleHoldUntilRef = useRef(0);
    const lastActiveTripViewportSyncAtRef = useRef(0);
    const activeTripViewportSyncingRef = useRef(false);
@@ -278,35 +295,14 @@
            if (pickupPoint) points.push(pickupPoint);
            if (dropoffPoint) points.push(dropoffPoint);
            keyParts.push(
                buildPointsSignature(deps.routePoints),
                buildOrderDetailRouteSignature(deps.routePoints, pickupSite, dropoffSite),
                pickupSite?.latitude,
                pickupSite?.longitude,
                dropoffSite?.latitude,
                dropoffSite?.longitude,
                Math.round(mapBottomPadding)
                dropoffSite?.longitude
            );
        }
        return { points, keyParts };
    };
    const syncOrderDetailViewportState = (mapCtx: ReturnType<typeof Taro.createMapContext>) => {
        mapCtx.getCenterLocation({
            success: (res) => {
                if (typeof deps.setLatitude === "function" && Number.isFinite(res?.latitude)) {
                    deps.setLatitude(res.latitude);
                }
                if (typeof deps.setLongitude === "function" && Number.isFinite(res?.longitude)) {
                    deps.setLongitude(res.longitude);
                }
            }
        });
        mapCtx.getScale({
            success: (res) => {
                if (Number.isFinite(res?.scale)) {
                    setMapScale(res.scale);
                }
            }
        });
    };
    const updateViewportByPayload = useCallback((viewportType: string, points: any[]) => {
@@ -341,7 +337,16 @@
                    clearTimeout(orderDetailSyncTimerRef.current);
                }
                orderDetailSyncTimerRef.current = setTimeout(() => {
                    syncOrderDetailViewportState(mapCtx);
                    // 分享页完整路线视口不再把地图中心反写回受控经纬度。
                    // 安卓真机在 includePoints 后读取 getCenterLocation 偶发返回偏移中心,
                    // 再写回 latitude/longitude 会把地图锁到一块没有点线的位置。
                    mapCtx.getScale({
                        success: (res) => {
                            if (Number.isFinite(res?.scale)) {
                                setMapScale(res.scale);
                            }
                        }
                    });
                    orderDetailSyncTimerRef.current = null;
                }, 360);
            }
@@ -466,10 +471,20 @@
    ]);
    useEffect(() => {
        viewportKeyRef.current = "";
        completedOrderDetailViewportKeyRef.current = "";
    }, [
        deps.tripStatus,
    ]);
    useEffect(() => {
        const viewportType = deps.statusRule.viewport;
        const { pickupSite, dropoffSite } = resolveViewportSites();
        const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite);
        if (points.length === 0) {
            return;
        }
        if (deps.tripStatus >= TripStatus.ArrivedDestination && viewportType === "order_detail") {
            return;
        }
        const nextKey = keyParts.join("|");
@@ -495,6 +510,60 @@
    ]);
    useEffect(() => {
        if (deps.tripStatus < TripStatus.ArrivedDestination) {
            return;
        }
        if (deps.statusRule.viewport !== "order_detail") {
            return;
        }
        const { pickupSite, dropoffSite } = resolveViewportSites();
        const points: ViewportPoint[] = [];
        const pickupPoint = buildSiteViewportPoint(pickupSite);
        const dropoffPoint = buildSiteViewportPoint(dropoffSite);
        if (deps.routePoints.length > 0) {
            points.push(...deps.routePoints);
        }
        if (pickupPoint) {
            points.push(pickupPoint);
        }
        if (dropoffPoint) {
            points.push(dropoffPoint);
        }
        if (points.length === 0) {
            return;
        }
        const nextKey = buildOrderDetailRouteSignature(deps.routePoints, pickupSite, dropoffSite);
        if (nextKey === completedOrderDetailViewportKeyRef.current) {
            return;
        }
        completedOrderDetailViewportKeyRef.current = nextKey;
        if (orderDetailSyncTimerRef.current) {
            clearTimeout(orderDetailSyncTimerRef.current);
            orderDetailSyncTimerRef.current = null;
        }
        const mapCtx = Taro.createMapContext("map");
        const bottomPadding = computeBottomPadding(points, "order_detail");
        const topPadding = Math.max(60, resolveSafeTopInset() + 32) + 24;
        const orderDetailBottomPadding = Math.max(200, bottomPadding - 56);
        Taro.nextTick(() => {
            mapCtx.includePoints({
                points,
                padding: [topPadding, 50, orderDetailBottomPadding, 50]
            });
        });
    }, [
        deps.tripStatus,
        deps.statusRule.viewport,
        deps.routePoints,
        deps.startSite?.latitude,
        deps.startSite?.longitude,
        deps.endSite?.latitude,
        deps.endSite?.longitude,
        mapBottomPadding,
        computeBottomPadding
    ]);
    useEffect(() => {
        return () => {
            if (orderDetailSyncTimerRef.current) {
                clearTimeout(orderDetailSyncTimerRef.current);
src/robotaxi/pages/share-trip/hooks/use-route-manager.ts
@@ -24,6 +24,7 @@
        polylineKeyRef: renderState.polylineKeyRef,
        lastPolylineUpdateAtRef: renderState.lastPolylineUpdateAtRef,
        applyRoutePoints: renderState.applyRoutePoints,
        resetRouteRuntimeState: renderState.resetRouteRuntimeState,
        erasePassedRouteSegments: renderState.erasePassedRouteSegments,
    };
}
src/robotaxi/pages/share-trip/hooks/use-share-trip-runtime.ts
@@ -69,17 +69,6 @@
  };
  /**
   * 计算两点方向角(0-360)。
   */
  const getDirectionByPoints = (from: LocationPoint, to?: LocationPoint) => {
    if (!from || !to) return 0;
    const deltaLng = to.longitude - from.longitude;
    const deltaLat = to.latitude - from.latitude;
    const angle = Math.atan2(deltaLng, deltaLat) * (180 / Math.PI);
    return ((angle % 360) + 360) % 360;
  };
  /**
   * 朝向值归一化到 0-360,非法值返回 null。
   */
  const normalizeDirection = (value: any) => {
@@ -302,21 +291,35 @@
      ?? getFieldValue(routeResponse?.content, ['duration'])
      ?? getFieldValue(routeResponse?.data, ['duration'])
      ?? getFieldValue(routeResponse, ['duration']);
    const direction = normalizeDirection(
      routeResponse?.direction ??
      routeResponse?.content?.direction ??
      routeMeta?.direction ??
      routeResponse?.vehicleDirection,
    const apiDirection = normalizeDirection(routeResponse?.direction);
    const contentDirection = normalizeDirection(routeResponse?.content?.direction);
    const resolvedDirection = typeof apiDirection === 'number'
      ? apiDirection
      : typeof contentDirection === 'number'
        ? contentDirection
        : null;
    const routeKey = String(
      getFieldValue(routeMeta, ['routeID', 'routeId'])
      ?? getFieldValue(routeResponse?.content, ['routeID', 'routeId'])
      ?? getFieldValue(routeResponse?.data, ['routeID', 'routeId'])
      ?? getFieldValue(routeResponse, ['routeID', 'routeId'])
      ?? '',
    );
    const fallbackDirection = typeof direction === 'number'
      ? direction
      : getDirectionByPoints(extractedRoutePoints[0], extractedRoutePoints[1]);
    console.log('[share-trip] route direction resolved', {
      routeKey,
      apiDirection,
      contentDirection,
      resolvedDirection,
      servedPointsLength: servedPoints.length,
      routePointsLength: extractedRoutePoints.length,
    });
    const routeSnapshot = buildShareRouteSnapshot({
      points: extractedRoutePoints,
      servedPoints,
      distance: distanceValue,
      duration: durationValue,
      direction: fallbackDirection,
      direction: resolvedDirection,
      routeKey,
      timestamp: routeResponse?.timestamp
        ?? routeResponse?.content?.timestamp
        ?? routeResponse?.data?.timestamp
src/robotaxi/pages/share-trip/hooks/use-smooth-route-motion.ts
@@ -5,6 +5,7 @@
import { normalizeDistanceKm } from '../../../utils/format';
import {
    buildRouteIndex,
    getDistanceMeters,
    getPointByDistance,
    getRouteCursorByDistance,
    projectPointToRoute,
@@ -29,6 +30,7 @@
type MotionRuntimeState = {
    routeIndex: RouteIndex | null;
    routeKey: string;
    baseRemainingDistanceMeters: number;
    displayDistanceMeters: number;
    targetDistanceMeters: number;
@@ -45,6 +47,7 @@
    ERASE_STEP_METERS = 6,
    PROJECT_CLOSE_THRESHOLD_METERS = 10,
    PROJECT_SOFT_THRESHOLD_METERS = 25,
    REBASE_ROUTE_DEVIATION_METERS = 30,
}
const ROUTE_POLYLINE_ID = 2001;
@@ -53,6 +56,7 @@
export default function useSmoothRouteMotion(deps: Deps) {
    const runtimeRef = useRef<MotionRuntimeState>({
        routeIndex: null,
        routeKey: '',
        baseRemainingDistanceMeters: 0,
        displayDistanceMeters: 0,
        targetDistanceMeters: 0,
@@ -107,6 +111,7 @@
        pendingRouteEraseDistanceRef.current = null;
        runtimeRef.current = {
            routeIndex: null,
            routeKey: '',
            baseRemainingDistanceMeters: 0,
            displayDistanceMeters: 0,
            targetDistanceMeters: 0,
@@ -157,13 +162,15 @@
        return Number(snapshot.timestamp) <= runtimeRef.current.lastSocketTimestamp;
    };
    const initializeBaselineRoute = (snapshot: RouteSnapshot) => {
    const initializeBaselineRoute = (snapshot: RouteSnapshot, options?: { clearPassedRoute?: boolean }) => {
        const shouldClearPassedRoute = options?.clearPassedRoute === true;
        const points = sanitizeRoutePoints(snapshot.points);
        const routeIndex = buildRouteIndex(points);
        const remainingDistanceMeters = toMeters(snapshot.distance) ?? routeIndex.totalDistance;
        resetRuntimeState();
        runtimeRef.current = {
            routeIndex,
            routeKey: String(snapshot.routeKey || ''),
            baseRemainingDistanceMeters: remainingDistanceMeters,
            displayDistanceMeters: 0,
            targetDistanceMeters: 0,
@@ -174,9 +181,8 @@
        syncRemainingDistance(remainingDistanceMeters);
        deps.resetRoutePolylineCache();
        deps.applyRoutePoints(points);
        if (snapshot.vehicleMoveAlongPoints.length > 0) {
            deps.setVehicleMoveAlongPoints(snapshot.vehicleMoveAlongPoints);
        }
        // 换线重建时先清空旧的已行驶轨迹,避免浅色轨迹沿着旧路线残留一小段。
        deps.setVehicleMoveAlongPoints(shouldClearPassedRoute ? [] : snapshot.vehicleMoveAlongPoints);
        syncStaticVehiclePoint(snapshot, true);
    };
@@ -269,8 +275,18 @@
        const runtime = runtimeRef.current;
        const firstPoint = snapshot.points?.[0] || null;
        const remainingDistanceMeters = toMeters(snapshot.distance);
        const nextRouteKey = String(snapshot.routeKey || '');
        if (!runtime.routeReady || !runtime.routeIndex) {
            initializeBaselineRoute(snapshot);
            startRenderLoop();
            return;
        }
        if (nextRouteKey && runtime.routeKey && nextRouteKey !== runtime.routeKey) {
            console.log('[share-trip] route baseline rebuilt by routeKey change', {
                prevRouteKey: runtime.routeKey,
                nextRouteKey,
            });
            initializeBaselineRoute(snapshot, { clearPassedRoute: true });
            startRenderLoop();
            return;
        }
@@ -295,33 +311,42 @@
            } else {
                nextTargetDistance = Math.max(runtime.targetDistanceMeters, projectedDistance);
            }
            const projectedPoint = getPointByDistance(runtime.routeIndex, projectedDistance);
            const routeDeviationMeters = getDistanceMeters(firstPoint, projectedPoint);
            if (routeDeviationMeters > TripMotionConfig.REBASE_ROUTE_DEVIATION_METERS) {
                console.log('[share-trip] route baseline rebuilt by geometry deviation', {
                    routeDeviationMeters,
                    projectedDistance,
                    nextTargetDistance,
                    nextRouteKey,
                });
                initializeBaselineRoute(snapshot, { clearPassedRoute: true });
                startRenderLoop();
                return;
            }
        }
        runtime.targetDistanceMeters = Math.max(runtime.targetDistanceMeters, nextTargetDistance);
        runtime.lastSocketTimestamp = Number.isFinite(snapshot.timestamp)
            ? Number(snapshot.timestamp)
            : runtime.lastSocketTimestamp;
        if (snapshot.vehicleMoveAlongPoints.length > 0) {
            deps.setVehicleMoveAlongPoints(snapshot.vehicleMoveAlongPoints);
        }
        deps.setVehicleMoveAlongPoints(snapshot.vehicleMoveAlongPoints);
        startRenderLoop();
    };
    const applyStaticRouteSnapshot = (snapshot: RouteSnapshot) => {
        stopRenderLoop();
        deps.applyRoutePoints(sanitizeRoutePoints(snapshot.points));
        if (snapshot.vehicleMoveAlongPoints.length > 0) {
            deps.setVehicleMoveAlongPoints(snapshot.vehicleMoveAlongPoints);
        }
        deps.setVehicleMoveAlongPoints(snapshot.vehicleMoveAlongPoints);
        syncStaticVehiclePoint(snapshot, false);
    };
    const applyRouteSnapshot = (snapshot: RouteSnapshot) => {
        const currentTripStatus = Number(deps.tripStatusRef.current);
        const shouldUseSmoothMotion = isSmoothMotionTripStatus(currentTripStatus);
        syncRouteSummary(snapshot, !shouldUseSmoothMotion);
        if (isStaleSnapshot(snapshot)) {
            return;
        }
        syncRouteSummary(snapshot, !shouldUseSmoothMotion);
        if (snapshot.forceFullRedraw) {
            initializeBaselineRoute(snapshot);
            if (!shouldUseSmoothMotion) {
src/robotaxi/pages/share-trip/hooks/use-vehicle-tracking.ts
@@ -74,6 +74,8 @@
    } | null>(null);
    const vehicleMarkerReadyRef = useRef(false);
    const vehicleMarkerReadyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
    const vehicleMarkerIdRef = useRef<number>(vehicleMarkerId);
    const translateSessionIdRef = useRef(0);
    const clearVehicleMarkerReadyTimer = () => {
        if (vehicleMarkerReadyTimerRef.current) {
@@ -82,17 +84,29 @@
        }
    };
    // 重置当前车辆 marker 对应的平移上下文,避免沿用旧 marker 的角度与 ready 状态。
    const resetTranslateRuntimeState = () => {
        isTranslatingRef.current = false;
        pendingTranslateRef.current = null;
        lastAppliedTranslateTaskRef.current = null;
        vehicleMarkerReadyRef.current = false;
        clearVehicleMarkerReadyTimer();
        translateSessionIdRef.current += 1;
    };
    const flushTranslateTask = useCallback(() => {
        if (!vehicleMarkerReadyRef.current) return;
        if (isTranslatingRef.current) return;
        if (!pendingTranslateRef.current) return;
        const task = pendingTranslateRef.current;
        const translateSessionId = translateSessionIdRef.current;
        pendingTranslateRef.current = null;
        isTranslatingRef.current = true;
        let finished = false;
        let fallbackTimer: ReturnType<typeof setTimeout> | null = null;
        const finishTranslate = () => {
            if (finished) return;
            if (translateSessionId !== translateSessionIdRef.current) return;
            finished = true;
            if (fallbackTimer) {
                clearTimeout(fallbackTimer);
@@ -118,6 +132,9 @@
                rotate: task.rotate,
                duration: task.duration,
                animationEnd: () => {
                    if (translateSessionId !== translateSessionIdRef.current) {
                        return;
                    }
                    lastAppliedTranslateTaskRef.current = {
                        latitude: task.latitude,
                        longitude: task.longitude,
@@ -126,12 +143,16 @@
                    finishTranslate();
                },
                fail: () => {
                    lastAppliedTranslateTaskRef.current = {
                        latitude: task.latitude,
                        longitude: task.longitude,
                        rotate: task.rotate
                    };
                    finishTranslate();
                    if (translateSessionId !== translateSessionIdRef.current) {
                        return;
                    }
                    // marker 尚未正确挂载时不要把本次旋转记成已完成,等待 marker ready 后重试。
                    pendingTranslateRef.current = task;
                    vehicleMarkerReadyRef.current = false;
                    isTranslatingRef.current = false;
                    if (fallbackTimer) {
                        clearTimeout(fallbackTimer);
                    }
                }
            });
        });
@@ -192,12 +213,20 @@
    }, [statusRule.translateCar, vehicleLocation, vehicleDirection, vehicleMarkerId, flushTranslateTask]);
    useEffect(() => {
        if (!statusRule.translateCar) {
            vehicleMarkerIdRef.current = vehicleMarkerId;
            return;
        }
        if (vehicleMarkerIdRef.current === vehicleMarkerId) {
            return;
        }
        vehicleMarkerIdRef.current = vehicleMarkerId;
        resetTranslateRuntimeState();
    }, [statusRule.translateCar, vehicleMarkerId]);
    useEffect(() => {
        if (statusRule.translateCar) return;
        isTranslatingRef.current = false;
        pendingTranslateRef.current = null;
        lastAppliedTranslateTaskRef.current = null;
        vehicleMarkerReadyRef.current = false;
        clearVehicleMarkerReadyTimer();
        resetTranslateRuntimeState();
    }, [statusRule.translateCar]);
    useEffect(() => () => {
src/robotaxi/pages/share-trip/index.tsx
@@ -69,7 +69,7 @@
    [TripStatus.Publishing]: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    [TripStatus.CallingCar]: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    [TripStatus.VehicleApproaching]: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
    [TripStatus.VehicleArrivedPickup]: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
    [TripStatus.VehicleArrivedPickup]: { showPickup: true, showDropoff: true, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
    [TripStatus.InService]: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
    [TripStatus.ArrivedDestination]: { showPickup: false, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
    [TripStatus.TripFinished]: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
@@ -148,6 +148,7 @@
    handleMapTap,
    polylineKeyRef,
    lastPolylineUpdateAtRef,
    resetRouteRuntimeState,
  } = useRouteManager({
    isAndroid,
    orderId,
@@ -185,6 +186,9 @@
  }, [displayRouteDuration, routeDistance]);
  const resetRoutePolylineCache = () => {
    // 分享页切换新路线基线时,先把旧路线/擦线/浅色已行驶轨迹整套运行时清空,
    // 避免地图短暂保留旧路线残影,再按新快照重绘。
    resetRouteRuntimeState();
    polylineKeyRef.current = '';
    lastPolylineUpdateAtRef.current = 0;
  };
@@ -218,7 +222,10 @@
    setLatitude,
  }); // 视口状态容器
  const routeSummaryMarker = useMemo(() => {
    const shouldShowRouteSummary = !statusRule.showCar && shouldShowRoute && tripStatus !== TripStatus.ArrivedDestination;
    // 到达目的地及后续完单态不再展示路线摘要气泡,避免出现“1分钟0米”的残留提示。
    const shouldShowRouteSummary = !statusRule.showCar
      && shouldShowRoute
      && tripStatus < TripStatus.ArrivedDestination;
    if (!shouldShowRouteSummary || !routeSummaryText || !Array.isArray(routePoints) || routePoints.length === 0) {
      return null;
    }