hejianhao
2026-06-26 5a98574b1cdfab6d5a0b69a3e15ea1684d2c2a03
Merge branch 'feature_v2.0' into hc/fix_xpd_626
12个文件已修改
255 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/calling-car-modules/ac-storage.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-request.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-viewport.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/index.tsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/ac-storage.ts
@@ -1,23 +1,26 @@
import Taro from '@tarojs/taro';
const AC_STORAGE_KEY_PREFIX = 'rt_control_';
const AC_STORAGE_KEY = 'rt_control';
export interface AcStorageData {
  acOpen: boolean;
  temperature: number;
}
function getStorageKey(orderId: string): string {
  return `${AC_STORAGE_KEY_PREFIX}${orderId}`;
interface AcStorageEntry {
  orderId: string;
  acOpen: boolean;
  temperature: number;
}
export function getAcStorage(orderId: string): AcStorageData | null {
  try {
    const raw = Taro.getStorageSync(getStorageKey(orderId));
    const raw = Taro.getStorageSync(AC_STORAGE_KEY);
    if (!raw) return null;
    const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
    const data: AcStorageEntry = typeof raw === 'string' ? JSON.parse(raw) : raw;
    if (String(data.orderId || '') !== orderId) return null;
    if (typeof data.acOpen === 'boolean' && typeof data.temperature === 'number') {
      return data;
      return { acOpen: data.acOpen, temperature: data.temperature };
    }
    return null;
  } catch {
@@ -27,16 +30,9 @@
export function saveAcStorage(orderId: string, data: AcStorageData): void {
  try {
    Taro.setStorageSync(getStorageKey(orderId), JSON.stringify(data));
    Taro.setStorageSync(AC_STORAGE_KEY, JSON.stringify({ orderId, acOpen: data.acOpen, temperature: data.temperature }));
  } catch {
    // 静默失败,不阻断交互
  }
}
export function removeAcStorage(orderId: string): void {
  try {
    Taro.removeStorageSync(getStorageKey(orderId));
  } catch {
    // 静默失败
  }
}
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
@@ -13,7 +13,7 @@
import { getOrderId } from '../../../../utils/order';
import { TripStatus } from '../../../../constants/trip-status';
import { MODIFY_DESTINATION_SKIP_AC_QUERY_ON_RETURN_KEY } from '../../../../constants/modify-destination';
import { getAcStorage, saveAcStorage, removeAcStorage } from './ac-storage';
import { getAcStorage, saveAcStorage } from './ac-storage';
import useMinimumSpinLoading from './use-minimum-spin-loading';
const AC_SWITCH_OPEN_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/T-switch-open.png';
@@ -28,18 +28,15 @@
    tripStatus: TripStatus;
    orderId?: string;
    pageDidShowVersion?: number;
    /** 车是否还在执行上一单 */
    hasBeforeUserEndSite?: boolean;
    /** “上一单下车点”状态是否已由路线接口/Socket 确认 */
    beforeUserEndSiteResolved?: boolean;
    /** 后端推送:当前被指派车辆是否仍在执行上一单(连环单) */
    hasPreviousOrder?: boolean;
};
export default function TemperatureControl({ tripStatus, orderId, pageDidShowVersion = 0, hasBeforeUserEndSite = false, beforeUserEndSiteResolved = false }: Props) {
export default function TemperatureControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props) {
    const [tempStatus, setTempStatus] = useState<number | null>(null);
    const [acSwitchOn, setAcSwitchOn] = useState(false);
    const [acStatusLoaded, setAcStatusLoaded] = useState(false);
    const tempTimerRef = useRef<any>(null);
    const firstInitFallbackTimerRef = useRef<any>(null);
    const lastConfirmedTempRef = useRef<number | null>(null);
    const skipQueryOnThisCycleRef = useRef(false);
    const userActionSeqRef = useRef(0);
@@ -49,10 +46,6 @@
    const TEMP_MAX = 32.5;
    const TEMP_UPDATE_DEBOUNCE_MS = 500;
    const DEFAULT_FIRST_TEMP = 23.5;
    // 确定性版兜底超时:正常情况下进入接驾态会很快通过路线接口/Socket 确认“上一单下车点”状态(beforeUserEndSiteResolved 置真),
    // 届时立即下发默认温度,无固定延迟。此超时仅为路线数据异常迟迟未达(如接口失败)时的安全兜底,
    // 避免空调永久不初始化;正常 happy path 不会走到这里。
    const FIRST_INIT_RESOLVE_FALLBACK_MS = 3000;
    const isTogglingAc = acLoadingKey === 'ac';
    const canToggleSwitch = tripStatus === TripStatus.VehicleApproaching && !isTogglingAc;
@@ -69,15 +62,6 @@
    const increaseButtonIconSuffix = increaseButtonDisabled ? '-no' : '';
    const tempTextDisabledClassName = acSwitchOn ? '' : 'color-000-25';
    useEffect(() => {
        if (tripStatus === TripStatus.VehicleApproaching) {
            return;
        }
        const targetOrderId = getOrderId(orderId);
        if (targetOrderId) {
            removeAcStorage(targetOrderId);
        }
    }, [tripStatus, orderId]);
    useEffect(() => {
        const skipOnce = Number(Taro.getStorageSync(MODIFY_DESTINATION_SKIP_AC_QUERY_ON_RETURN_KEY)) === 1;
@@ -206,10 +190,10 @@
        if (tripStatus !== TripStatus.VehicleApproaching) {
            return;
        }
        // 连环派单场景:车辆仍在执行上一单(hasBeforeUserEndSite 为 true)时,
        // 当前用户的空调卡片尚未展示,不应初始化/下发温度,否则会把车内温度强制改回默认 23.5°C,
        // 覆盖上一单乘客的设置。待车辆完成上一单后该副作用会重新执行并正常初始化。
        if (hasBeforeUserEndSite) {
        // 连环派单场景:后端明确告知车辆仍在执行上一单(hasPreviousOrder 为 true)时,
        // 不应初始化/下发温度,否则会把车内温度强制改回默认 23.5°C,覆盖上一单乘客的设置。
        // 该字段与 tripStatus 原子下发、首次即正确,无竞态窗口。
        if (hasPreviousOrder) {
            return;
        }
        if (skipQueryOnThisCycleRef.current) {
@@ -223,36 +207,12 @@
        const storageData = getAcStorage(targetOrderId);
        if (storageData) {
            // 先不展示 storageData,等待校准(限定时间内接口返回 → 展示接口数据,超时 → 展示 storageData 并同步远程)
            handleCalibration(targetOrderId, storageData.acOpen, storageData.temperature);
            return;
        }
        // 首次初始化(无本地缓存):必须等到“上一单下车点”状态被确认后再决定是否下发默认温度。
        // 连环派单刚接单时,订单状态消息(推进到接驾中)通常早于订单线路消息(携带上一单下车点)到达,
        // 存在 hasBeforeUserEndSite 暂为 false 的竞态窗口,若此刻直接下发默认 23.5°C 会覆盖上一单乘客的温度设置。
        // 因此:已确认(resolved)→ 立即按 hasBeforeUserEndSite 据实初始化(普通单无固定延迟);
        //       未确认 → 仅挂兜底定时器,确认到达时本副作用会重跑并据实判断(命中上方 hasBeforeUserEndSite 守卫或下面的 resolved 分支)。
        if (beforeUserEndSiteResolved) {
            handleFirstTimeInit(targetOrderId);
            return;
        }
        firstInitFallbackTimerRef.current = setTimeout(() => {
            firstInitFallbackTimerRef.current = null;
            // 兜底触发前若已写入空调缓存(如用户手动操作/校准落库),不再强制下发默认温度。
            if (getAcStorage(targetOrderId)) {
                return;
            }
            handleFirstTimeInit(targetOrderId);
        }, FIRST_INIT_RESOLVE_FALLBACK_MS);
        return () => {
            if (firstInitFallbackTimerRef.current) {
                clearTimeout(firstInitFallbackTimerRef.current);
                firstInitFallbackTimerRef.current = null;
            }
        };
    }, [tripStatus, orderId, pageDidShowVersion, hasBeforeUserEndSite, beforeUserEndSiteResolved]);
        handleFirstTimeInit(targetOrderId);
    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder]);
    useEffect(() => {
        lastConfirmedTempRef.current = tempStatus;
@@ -378,8 +338,8 @@
        }
    };
    // 车还在上一单时不展示空调卡片
    if (tripStatus !== TripStatus.VehicleApproaching || hasBeforeUserEndSite) {
    // 连环单(车还在执行上一单)时不展示空调卡片
    if (tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
        return null;
    }
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -72,8 +72,8 @@
    orderId?: string;
    /** WebSocket 推送的上一单下车点坐标(有值表示车还在执行上一单) */
    hasBeforeUserEndSite?: boolean;
    /** “上一单下车点”状态是否已由路线接口/Socket 确认(用于规避连环派单接单瞬间温度被重置的竞态) */
    beforeUserEndSiteResolved?: boolean;
    /** 后端推送:当前被指派车辆是否仍在执行上一单(连环单) */
    hasPreviousOrder?: boolean;
    orderTrunkStatus?: boolean;
    /** 当前页面内是否已经解锁过车门 */
    doorUnlockedInCurrentPage?: boolean;
@@ -123,7 +123,7 @@
    vehicleInfo,
    orderId,
    hasBeforeUserEndSite = false,
    beforeUserEndSiteResolved = false,
    hasPreviousOrder = false,
    orderTrunkStatus,
    doorUnlockedInCurrentPage = false,
    bluetoothStatusReady = false,
@@ -207,7 +207,7 @@
                <View className="car-card" style={{ marginBottom: tripStatus === TripStatus.TripFinished ? '70px' : '0px' }}>
                    <VehicleInfoCard vehicleInfo={vehicleInfo} />
                    <TemperatureControl tripStatus={tripStatus} orderId={orderId} pageDidShowVersion={pageDidShowVersion} hasBeforeUserEndSite={hasBeforeUserEndSite} beforeUserEndSiteResolved={beforeUserEndSiteResolved} />
                    <TemperatureControl tripStatus={tripStatus} orderId={orderId} pageDidShowVersion={pageDidShowVersion} hasPreviousOrder={hasPreviousOrder} />
                    <DoorTrunkControl
                        tripStatus={tripStatus}
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
@@ -1,6 +1,6 @@
import Taro from '@tarojs/taro';
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
import { computeAdaptiveBottomPadding } from '../../../../utils/Robotaxi-public-method';
import { computeAdaptiveBottomPadding, debounce } from '../../../../utils/Robotaxi-public-method';
import { TripStatus } from '../../../constants/trip-status';
import { buildActiveTripSafeAreaMetrics } from './active-trip-camera-policy';
@@ -40,6 +40,8 @@
const ACTIVE_TRIP_VIEWPORT_SYNC_INTERVAL_MS = 600; // 行程安全区跟随的最小同步间隔,避免地图频繁漂移
const ACTIVE_TRIP_VIEWPORT_SYNC_ANIMATION_MS = 300; // 跟车中心移动缓动时长,等待原生地图动画后再回写受控中心点
const ORDER_DETAIL_SCALE_SYNC_DELAY_MS = 600; // 完成态 includePoints 后等待原生地图落稳,再同步实际 scale
const VIEWPORT_FIT_DEBOUNCE_MS = 300; // 视口 fit 统一防抖:状态切换瞬间路线点/视口类型会先残留旧值再被正确值覆盖,
                                      // 防抖把窗口内的多次 includePoints/moveToLocation 合并为最后一次,避免连续命令式调用动画相互打断导致缩放卡死
export const ACTIVE_TRIP_VIEWPORT_SAFE_X_MARGIN_RATIO = 0.2; // 横向两侧保留的边距比例,调大可让车辆更早触发横向回中,避免跑到贴边只剩一小段线路
type ViewportPoint = {
@@ -512,7 +514,7 @@
        }, ORDER_DETAIL_SCALE_SYNC_DELAY_MS);
    }, [deps.setLatitude, deps.setLongitude, deps.tripStatus, getMapCenterLocation, getMapScale]);
    const updateViewportByPayload = (viewportType: string, points: any[]) => {
    const updateViewportByPayloadRaw = (viewportType: string, points: any[]) => {
        const mapCtx = Taro.createMapContext('map');
        const bottomPadding = computeBottomPadding(points, viewportType, deps.tripStatus);
        const baseTopPadding = viewportType === 'order_detail' ? 60 : 24;
@@ -546,6 +548,9 @@
            }
        });
    };
    // 防抖包裹:状态切换瞬间多次连续调用只执行最后一次,避免 includePoints 动画相互打断
    const updateViewportByPayload = debounce(updateViewportByPayloadRaw, VIEWPORT_FIT_DEBOUNCE_MS);
    const toOrigin = () => {
        const mapCtx = Taro.createMapContext('map');
@@ -928,6 +933,7 @@
         if (deps.tripStatus === TripStatus.InService && viewportType === 'route') {
            tripStatus6ViewportLockedRef.current = true;
        }
        // updateViewportByPayload 已用 debounce 包裹:状态切换瞬间多次调用只执行最后一次
        updateViewportByPayload(viewportType, points);
        if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(deps.tripStatus) && viewportType === 'order_detail') {
            const hasOrderDetailRouteAfterEnter = orderDetailRouteUpdatedAfterEnterRef.current && deps.routePoints.length > 0;
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts
@@ -91,6 +91,8 @@
  setEndSite?: (site: any) => void;
  /** 设置上一单乘客下车点。 */
  setBeforeUserEndSite?: (site: { longitude: number; latitude: number } | null) => void;
  /** 设置后端推送的「是否仍在执行上一单」标识。 */
  setHasPreviousOrder?: (value: boolean) => void;
  /** 计算顶部安全区。 */
  resolveSafeTopInset: () => number;
  /** 计算底部视口内边距。 */
src/robotaxi/pages/begin-route/hooks/use-route-request.ts
@@ -543,6 +543,9 @@
          response?.endLatitude ?? response?.endlatitude
        );
        deps.setBeforeUserEndSite?.(beforeUserSite);
        const hasPreviousOrderValue = Boolean(response?.hasPreviousOrder);
        deps.setHasPreviousOrder?.(hasPreviousOrderValue);
        Taro.setStorageSync('hasPreviousOrder', { orderId: targetOrderId, value: hasPreviousOrderValue });
        const rawPoints = Array.isArray(response?.polyline) ? response.polyline : [];
        const points = rawPoints
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -1,5 +1,5 @@
import Taro from '@tarojs/taro';
import { memo, useCallback, useMemo } from 'react';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CoverImage, CoverView, Image, View } from '@tarojs/components';
import ReleaseRoute from '../components/release-route';
import ReleaseBeBeing from '../components/release-be-being';
@@ -47,8 +47,8 @@
  routeDuration: number | null;
  /** 是否存在上一单终点 */
  hasBeforeUserEndSite: boolean;
  /** “上一单下车点”状态是否已由路线接口/Socket 确认(用于规避连环派单接单瞬间温度被重置的竞态) */
  beforeUserEndSiteResolved: boolean;
  /** 后端推送:当前被指派车辆是否仍在执行上一单(连环单) */
  hasPreviousOrder: boolean;
  /** 自定义气泡图标 */
  customCalloutIconSrc: string;
  /** 路线概览气泡背景图 */
@@ -270,6 +270,36 @@
  ), [deps.hasBeforeUserEndSite, deps.routeDistance, vehicleMetricDurationDisplay]);
  /**
   * 安卓接驾态(VehicleApproaching)上车点气泡(customCallout)偶现不绘制,需拖动/缩放/点击后才出现。
   * 在 markers 更新后微调上车点气泡外层(透明容器,无可见样式)的 borderRadius,
   * 触发原生地图对该 cover-view 气泡的回流重绘。仅作用在上车点气泡,不影响其它气泡。
   */
  const [calloutRepaintStyle, setCalloutRepaintStyle] = useState<{ borderRadius: string }>({ borderRadius: '0px' });
  const calloutRepaintTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const markersRepaintSignature = useMemo(
    () => deps.markers.map((marker: any) => `${marker.id}-${marker.name || ''}`).join('|'),
    [deps.markers]
  );
  useEffect(() => {
    if (calloutRepaintTimerRef.current) {
      clearTimeout(calloutRepaintTimerRef.current);
    }
    calloutRepaintTimerRef.current = setTimeout(() => {
      setCalloutRepaintStyle((prev) => {
        const next = prev.borderRadius === '0px' ? '0.5px' : '0px';
        return { borderRadius: next };
      });
      calloutRepaintTimerRef.current = null;
    }, 800);
    return () => {
      if (calloutRepaintTimerRef.current) {
        clearTimeout(calloutRepaintTimerRef.current);
        calloutRepaintTimerRef.current = null;
      }
    };
  }, [markersRepaintSignature, deps.tripStatus]);
  /**
   * 渲染等待态气泡。
   */
  const renderWaitingCallout = useCallback((item: any, calloutKey: string) => (
@@ -288,7 +318,7 @@
   * 渲染默认气泡。
   */
  const renderDefaultCallout = useCallback((item: any, calloutKey: string) => (
    <CoverView slot='callout' marker-id={item.id} key={calloutKey} className='custom-callout'>
    <CoverView slot='callout' marker-id={item.id} key={calloutKey} className='custom-callout' style={item.id === 1 ? calloutRepaintStyle : undefined}>
      {item.isRouteSummaryBubble ? (
        routeSummaryText ? (
          <CoverView className='route-summary-callout'>
@@ -349,7 +379,8 @@
    deps.tripStatus,
    routeSummaryText,
    vehicleMetricDurationDisplay,
    shouldShowVehicleMetricCallout
    shouldShowVehicleMetricCallout,
    calloutRepaintStyle
  ]);
  /**
@@ -492,7 +523,7 @@
          key={deps.tripStatus}
          orderId={deps.orderId}
          hasBeforeUserEndSite={deps.hasBeforeUserEndSite}
          beforeUserEndSiteResolved={deps.beforeUserEndSiteResolved}
          hasPreviousOrder={deps.hasPreviousOrder}
          tripStatus={deps.tripStatus}
          finishCountdownEnabled={deps.finishCountdownEnabled}
          vehicleInfo={deps.vehicleInfo}
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -70,6 +70,7 @@
        durationSeconds: number | null;
    }) => void;
    setBeforeUserEndSite: (site: { longitude: number; latitude: number } | null) => void;
    setHasPreviousOrder: (value: boolean) => void;
    isModifyDestinationReviewVisible?: boolean;
    hasLocalDestinationOverride?: boolean;
    isPickupRouteChangeVisible?: boolean;
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts
@@ -396,6 +396,12 @@
  if (!shouldKeepPickupExceptionPopup) {
    currentDeps.handlePickupException?.(normalizedPickupException);
  }
  const rawHasPreviousOrder = payload?.hasPreviousOrder ?? payload?.content?.hasPreviousOrder;
  if (rawHasPreviousOrder !== undefined) {
    const hasPreviousOrder = Boolean(rawHasPreviousOrder);
    currentDeps.setHasPreviousOrder(hasPreviousOrder);
    Taro.setStorageSync('hasPreviousOrder', { orderId: effectiveOrderId, value: hasPreviousOrder });
  }
  handleCancelPopupPush(payload, changeStatusOld, currentDeps, pendingCancelContext);
  if (payload?.showPassengerNotice) {
    currentDeps.checkAndShowPassengerNotice();
src/robotaxi/pages/begin-route/index.tsx
@@ -231,12 +231,14 @@
  const [tripStatus, setTripStatus] = useState<TripStatus>(toTripStatus(Taro.getStorageSync('tripStatus'))); // 当前行程状态
  const [elapsedSeconds, setElapsedSeconds] = useState(0); // 等待计时秒数
  const [beforeUserEndSite, setBeforeUserEndSite] = useState<BeforeUserEndSite | null>(null); // 上一单乘客下车点
  const [beforeUserEndSiteResolved, setBeforeUserEndSiteResolved] = useState(false); // 当前订单的“上一单下车点”状态是否已由路线接口/Socket 确认过(用于规避连环派单接单瞬间温度被重置的竞态)
  // 统一写入“上一单下车点”:在更新坐标的同时标记其已被确认,确保温度模块能在确认后再决定是否下发默认温度。
  const markBeforeUserEndSite = useCallback((site: BeforeUserEndSite | null) => {
    setBeforeUserEndSite(site);
    setBeforeUserEndSiteResolved(true);
  }, []);
  const [hasPreviousOrder, setHasPreviousOrder] = useState(() => {
    const cached = Taro.getStorageSync('hasPreviousOrder');
    const cachedOrderId = String(Taro.getStorageSync('orderId') || '');
    if (cached && cachedOrderId && String(cached.orderId || '') === cachedOrderId) {
      return Boolean(cached.value);
    }
    return false;
  }); // 后端推送:当前被指派车辆是否仍在执行上一单(连环单)
  const requoteTriggeredRef = useRef(false); // 重新报价触发标记,防止重复触发
  const [callSubmitting, setCallSubmitting] = useState(false); // 呼叫车辆提交中状态
  const [orderId, setOrderId] = useState<string>(''); // 订单ID
@@ -603,7 +605,8 @@
    endSite: effectiveEndSite,
    setStartSite,
    setEndSite,
    setBeforeUserEndSite: markBeforeUserEndSite,
    setBeforeUserEndSite,
    setHasPreviousOrder,
    updateVehicleLocationThrottled: vehicleState.updateVehicleLocationThrottled,
    vehicleMoveAlongPoints,
    setVehicleDirection,
@@ -1364,7 +1367,8 @@
    setRouteDuration,
    setModifyDestinationSocketStatus,
    setOrderRouteMetrics,
    setBeforeUserEndSite: markBeforeUserEndSite,
    setBeforeUserEndSite,
    setHasPreviousOrder,
    isModifyDestinationReviewVisible,
    hasLocalDestinationOverride,
    isPickupRouteChangeVisible,
@@ -1416,7 +1420,6 @@
   */
  useEffect(() => {
    setDoorUnlockedInCurrentPage(false);
    setBeforeUserEndSiteResolved(false); // 订单切换后需重新确认“上一单下车点”状态,避免沿用上个订单的判定造成温度误重置
  }, [orderId]);
  const { queryPaymentResult: queryOrderPaymentResult } = useOrderPaymentResultQuery({
@@ -1983,7 +1986,7 @@
    routeDistance,
    routeDuration,
    hasBeforeUserEndSite: hasValidBeforeUserEndSite,
    beforeUserEndSiteResolved,
    hasPreviousOrder,
    customCalloutIconSrc,
    routeSummaryBgSrc,
    showTrip1StyleEndpoint,
src/robotaxi/pages/share-trip/hooks/use-map-viewport.ts
@@ -46,6 +46,8 @@
const ACTIVE_TRIP_VIEWPORT_SYNC_INTERVAL_MS = 600;
const ACTIVE_TRIP_VIEWPORT_SYNC_ANIMATION_MS = 300;
const ACTIVE_TRIP_VIEWPORT_SAFE_X_MARGIN_RATIO = 0.08;
const USER_GESTURE_HOLD_MS = 8000;
const USER_GESTURE_CAUSED_BY = new Set(["gesture", "drag", "scale"]);
const buildSiteViewportPoint = (site: any) => {
    if (!site?.latitude || !site?.longitude) {
@@ -155,6 +157,17 @@
            : mapBottomPadding;
        return computeAdaptiveBottomPadding(points, basePadding);
    }, [mapBottomPadding]);
    /**
     * 用户手势拖拽/缩放地图后,暂停一段时间的自动视口同步,避免地图被立刻抢回。
     */
    const handleRegionChange = useCallback((event: any) => {
        const causedBy = String(event?.causedBy || event?.detail?.causedBy || "").toLowerCase();
        if (!USER_GESTURE_CAUSED_BY.has(causedBy)) {
            return;
        }
        holdDynamicScale(USER_GESTURE_HOLD_MS);
    }, [holdDynamicScale]);
    const updateMapViewportPadding = useCallback(() => {
        Taro.nextTick(() => {
@@ -379,6 +392,25 @@
        });
    }, [computeBottomPadding, holdDynamicScale, mapBottomPadding]);
    /**
     * 接驾中/行程中复位地图时,优先把整条线路完整放回屏幕,而不是只把中心点拉回车辆位置。
     */
    const resetActiveTripViewportToRoute = useCallback(() => {
        const { pickupSite, dropoffSite } = resolveViewportSites();
        const { points } = buildViewportPayload("route", pickupSite, dropoffSite);
        if (points.length === 0) {
            return;
        }
        lastActiveTripViewportSyncAtRef.current = Date.now();
        updateViewportByPayload("route", points);
    }, [
        deps.endSite,
        deps.routePoints,
        deps.startSite,
        deps.tripStatus,
        updateViewportByPayload
    ]);
    const syncActiveTripViewportToVehicle = useCallback(() => {
        if (![TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus)) {
            return;
@@ -449,24 +481,7 @@
                if (!hasLatitudeDelta && !hasLongitudeDelta) {
                    return;
                }
                lastActiveTripViewportSyncAtRef.current = Date.now();
                const mapCtx = Taro.createMapContext("map");
                mapCtx.moveToLocation({
                    latitude: desiredCenterLatitude,
                    longitude: desiredCenterLongitude
                });
                if (activeTripViewportSyncTimerRef.current) {
                    clearTimeout(activeTripViewportSyncTimerRef.current);
                }
                activeTripViewportSyncTimerRef.current = setTimeout(() => {
                    if (typeof deps.setLatitude === "function") {
                        deps.setLatitude(desiredCenterLatitude);
                    }
                    if (typeof deps.setLongitude === "function") {
                        deps.setLongitude(desiredCenterLongitude);
                    }
                    activeTripViewportSyncTimerRef.current = null;
                }, ACTIVE_TRIP_VIEWPORT_SYNC_ANIMATION_MS);
                resetActiveTripViewportToRoute();
            })
            .finally(() => {
                activeTripViewportSyncingRef.current = false;
@@ -479,7 +494,8 @@
        ensureMapRect,
        getDynamicScaleHoldUntil,
        getMapCenterLocation,
        getMapRegion
        getMapRegion,
        resetActiveTripViewportToRoute
    ]);
    useEffect(() => {
@@ -618,6 +634,7 @@
        mapBottomPadding,
        updateMapViewportPadding,
        resolveSafeTopInset,
        computeBottomPadding
        computeBottomPadding,
        handleRegionChange
    };
}
src/robotaxi/pages/share-trip/index.tsx
@@ -1,5 +1,5 @@
import Taro, { useDidShow, useRouter } from '@tarojs/taro';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import './index.less';
import useMapMarkers from './hooks/use-map-markers';
import useVehicleTracking from './hooks/use-vehicle-tracking';
@@ -209,7 +209,11 @@
    updateVehicleLocationThrottled,
  });
  const { mapScale, updateMapViewportPadding } = useMapViewport({
  const {
    mapScale,
    updateMapViewportPadding,
    handleRegionChange: handleViewportRegionChange
  } = useMapViewport({
    tripStatus,
    statusRule,
    routePoints,
@@ -274,6 +278,10 @@
    longitude,
    latitude,
  }); // Android 地图浮层
  const handleMapRegionChange = useCallback((event: any) => {
    handleViewportRegionChange(event);
    handleRegionChange(event);
  }, [handleRegionChange, handleViewportRegionChange]);
  const markersWithRouteSummary = useMemo(() => {
    if (!routeSummaryMarker) {
      return markers;
@@ -371,7 +379,7 @@
      polyline={polyline}
      routeDuration={displayRouteDuration}
      renderMapOverlays={renderMapOverlays}
      handleMapRegionChange={handleRegionChange}
      handleMapRegionChange={handleMapRegionChange}
      handleMapPolylineTap={handlePolylineTap}
      handleMapTap={handleMapTap}
      goHome={goHome}