hejianhao
2026-04-16 7013e4cc72b30956bb962671de9ef07be08106f8
fix(robotaxi): 优化行程状态7的地图显示和车辆位置更新

- 在行程状态7(已到达)时,地图视口以下车点为中心并限制缩放等级
- 避免车辆位置在状态7时频繁更新,减少不必要的重绘
- 修复状态7时路线请求和车辆标记的显示逻辑
- 移除WebSocket和HTTP请求中的冗余日志上传代码
- 防止关键行程状态(4/6)被延迟的WebSocket消息回退
9个文件已修改
144 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.ts 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -154,7 +154,8 @@
      .join(';');
    const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2);
    const shouldHoldMarkerByTranslate = [4, 6].includes(deps.tripStatus) && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
    const shouldSkipUpdate = shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current);
    const shouldHoldMarkerWhenArrived = deps.tripStatus === 7 && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
    const shouldSkipUpdate = (shouldHoldMarkerByTranslate && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current)) || shouldHoldMarkerWhenArrived;
    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
@@ -30,6 +30,8 @@
    setLatitude: () => { }
};
const DROPOFF_MAX_SCALE = 20;
export default function useMapViewport(rawDeps?: Partial<Deps>) {
    const deps = { ...defaultDeps, ...(rawDeps || {}) } as Deps;
    // 地图底部留白
@@ -190,11 +192,17 @@
            ? [topPadding, 50, bottomPadding, 50]
            : [topPadding, 100, bottomPadding, 100];
        Taro.nextTick(() => {
            mapCtx.includePoints({ points, padding });
            if (viewportType === 'dropoff') {
                mapCtx.moveToLocation({
                    latitude: points[0].latitude,
                    longitude: points[0].longitude
                });
                setTimeout(() => {
                    setMapScale(13);
                    setMapScale(DROPOFF_MAX_SCALE);
                }, 50);
                return;
            } else {
                mapCtx.includePoints({ points, padding });
            }
        });
    };
@@ -217,6 +225,9 @@
    const getLocationInfo = () => {
        Taro.getLocation({ type: 'gcj02' })
            .then((res) => {
                if (deps.tripStatus === 7) {
                    return;
                }
                deps.setLongitude(res.longitude);
                deps.setLatitude(res.latitude);
                if (deps.tripStatus === 6 && deps.routePoints.length > 0) {
@@ -246,8 +257,21 @@
    }, [deps.tripStatus]);
    useEffect(() => {
        const viewportType = deps.statusRule.viewport;
        if (viewportType !== 'dropoff') return;
        const { pickupSite, dropoffSite } = resolveViewportSites();
        const { points, keyParts } = buildViewportPayload(viewportType, pickupSite, dropoffSite);
        if (points.length === 0) return;
        const nextKey = keyParts.join('|');
        if (nextKey === viewportKeyRef.current) return;
        viewportKeyRef.current = nextKey;
        updateViewportByPayload(viewportType, points);
    }, [deps.tripStatus, deps.statusRule?.viewport, 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 (deps.tripStatus === 6 && viewportType === 'route' && tripStatus6ViewportLockedRef.current) return;
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
@@ -85,7 +85,15 @@
   * 同步地图定位并按状态初始化路线。
   */
  const syncLocationAndInitRoute = (s: any, e: any, cachedTripStatus: number) => {
    if (s && s.latitude && s.longitude) {
    // 状态 7 直接以下车点为中心,避免 getLocationInfo 抢占视口中心
    if (cachedTripStatus === 7) {
      const dropoffLat = Number(e?.latitude);
      const dropoffLng = Number(e?.longitude);
      if (Number.isFinite(dropoffLat) && Number.isFinite(dropoffLng)) {
        deps.setLatitude(dropoffLat);
        deps.setLongitude(dropoffLng);
      }
    } else if (s && s.latitude && s.longitude) {
      deps.setLatitude(s.latitude);
      deps.setLongitude(s.longitude);
    } else {
src/robotaxi/pages/begin-route/hooks/use-route-manager.ts
@@ -238,7 +238,7 @@
  // 请求订单实时路线/轨迹
  const requestOrderRoute = (targetOrderId: string, status: number) => {
    const routeType = status <= 5 ? 1 : 2;
    const requestRoute = [7, 8, 9].includes(status) ? queryOrderTrajectory : queryOrderRoute;
    const requestRoute = [8, 9].includes(status) ? queryOrderTrajectory : queryOrderRoute;
    requestRoute({ orderId: targetOrderId, routeType })
      .then((res: any) => {
        const rawPoints = Array.isArray(res?.polyline) ? res.polyline : [];
@@ -254,14 +254,23 @@
            deps.updateVehicleLocationThrottled(last.latitude, last.longitude, true);
          } else {
            applyRoutePoints(points);
            const first = points[0];
            deps.updateVehicleLocationThrottled(first.latitude, first.longitude, true);
            const targetPoint = status === 7
              ? points[points.length - 1]
              : points[0];
            deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
            if ([4, 6].includes(status)) {
              currentFullRouteRef.current = [...points];
              lastErasedIndexRef.current = 0;
              orderRouteInitializedRef.current = true;
            }
          }
        } else if (status === 7) {
          const cachedEndSite = Taro.getStorageSync('endSite') || {};
          const fallbackLat = Number(cachedEndSite?.latitude);
          const fallbackLng = Number(cachedEndSite?.longitude);
          if (Number.isFinite(fallbackLat) && Number.isFinite(fallbackLng)) {
            deps.updateVehicleLocationThrottled(fallbackLat, fallbackLng, true);
          }
        }
        const distanceKm = normalizeDistanceKm(Number(res?.distance));
        if (distanceKm !== null) {
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -10,6 +10,8 @@
  isAndroid: boolean;
  /** 行程状态 */
  tripStatus: number;
  /** 地图缩放等级 */
  mapScale: number;
  /** 地图 markers */
  markers: any[];
  /** 是否显示等待雷达 */
@@ -128,7 +130,7 @@
          </CoverView>
        </CoverView>
      )}
      {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid) && (
      {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid && deps.mapScale >= 20) && (
        <CoverView className='custom-callout-content'>
          已到达
        </CoverView>
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -67,6 +67,10 @@
        return null;
    }
    const currentTripStatus = Number(deps.tripStatusRef.current);
    // 行程进行关键态不允许被回退,避免 4/6 被延迟状态覆盖导致路线链路重置
    if ([4, 6].includes(currentTripStatus) && mappedTripStatus < currentTripStatus) {
        return currentTripStatus;
    }
    if (currentTripStatus === mappedTripStatus) {
        return mappedTripStatus;
    }
@@ -195,6 +199,18 @@
    if (!targetPoint) {
        return;
    }
    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)) {
            return;
        }
    }
    deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
};
@@ -219,7 +235,7 @@
    Taro.nextTick(() => {
        const updated = deps.applyRoutePoints(points);
        if (!updated) return;
        if (![4, 5, 6].includes(deps.tripStatusRef.current)) return;
        if (![4, 5, 6, 7].includes(deps.tripStatusRef.current)) return;
        syncVehiclePointWithRoute(points, deps, servedPoints);
    });
};
@@ -250,15 +266,16 @@
    const points = payload?.points || [];
    const servedPoints = Array.isArray(payload?.servedPoints) ? payload.servedPoints : [];
    const forceFullRedraw = payload?.forceFullRedraw === true;
    const currentTripStatus = Number(deps.tripStatusRef.current);
    updateRouteSummary(payload, deps);
    if ([4, 6].includes(deps.tripStatusRef.current)) {
    if ([4, 6].includes(currentTripStatus)) {
        deps.setVehicleMoveAlongPoints(servedPoints);
    }
    if (forceFullRedraw) {
        handleForceFullRouteRedraw(points, deps, servedPoints);
        return;
    }
    const isEraseStatus = [4, 6].includes(deps.tripStatusRef.current);
    const isEraseStatus = ![7, 8, 9].includes(currentTripStatus);
    if (isEraseStatus) {
        handleEraseRouteStatus(points, servedPoints, deps);
        return;
@@ -339,6 +356,10 @@
        orderRouteHandlerRef.current = (payload) => {
            handleOrderRoutePayload(payload, depsRef.current);
        };
        // 处理重进页面时 useDidShow 先于 handler 初始化的场景,确保已连接状态下也能完成监听绑定
        if (wsManager.isConnectedStatus()) {
            bindSocketListeners();
        }
        return () => {
            if (authSuccessBound.current && authSuccessHandlerRef.current) {
src/robotaxi/pages/begin-route/index.tsx
@@ -448,6 +448,7 @@
  const { renderMapOverlays, renderTripCard } = useViewDelegates({
    isAndroid,
    tripStatus,
    mapScale,
    markers,
    shouldShowWaitingRadar,
    waitingRadarVisible,
src/utils/request.ts
@@ -10,11 +10,10 @@
  getStorage,
  objectSort,
} from '../utils/tools';
import { _ROBOTAXI_HOST, _XP_SECRET, _UID } from '../constants/api';
import { _XP_SECRET, _UID } from '../constants/api';
import { jumpTo } from './jump';
const CODE_SUCCESS = 200
const MINI_LOG_UPLOAD_PATH = '/operation/base/v1/mini-program/miniLog/uploadLog';
const CODE_AUTH_EXPIRED_ARRAY = [12010301, 12010302, 12010303, 12010304, 12010305, 12010306, 12010320, 12010002];
// 12010301, "登录状态已过期,请重新登录", "otp缓存已过期"),
// 12010302, "登录状态已过期,请重新登录", "otp已过期"),
@@ -53,17 +52,6 @@
    header
  })
    .then(async (res) => {
      if (!isMiniLogUploadUrl(url)) {
        uploadMiniLog(
          JSON.stringify({
            type: 'http_response',
            url,
            request: payload,
            response: res?.data,
            timestamp: Date.now()
          })
        );
      }
      // res为包含cookies、header的经过小程序封装的数据
      // 第一层data为接口返回数据,包含接口状态码
      // 第二层data一般为业务数据
@@ -98,17 +86,6 @@
      return data
    })
    .catch((err) => {
      if (!isMiniLogUploadUrl(url)) {
        uploadMiniLog(
          JSON.stringify({
            type: 'http_error',
            url,
            request: payload,
            response: err,
            timestamp: Date.now()
          })
        );
      }
      console.log('request err:', url);
      console.log(err);
@@ -138,28 +115,6 @@
      return Promise.reject(errData)
    });
}
const isMiniLogUploadUrl = (url: string = '') => {
  return typeof url === 'string' && url.includes(MINI_LOG_UPLOAD_PATH);
};
export const uploadMiniLog = async (logContent: string) => {
  if (!logContent) return;
  try {
    const header = await buildHeader({ method: 'POST', payload: logContent }, false);
    if (!header) return;
    const uploadUrl = `${_ROBOTAXI_HOST}${MINI_LOG_UPLOAD_PATH}`;
    Taro.request({
      url: uploadUrl,
      method: 'POST',
      data: logContent,
      header: {
        ...header,
        'Content-Type': 'application/json'
      }
    }).catch(() => { });
  } catch (_) { }
};
async function buildHeader(options, needLogin) {
  const { method = 'POST', payload, extraHeader = {} } = options;
src/utils/websocket.ts
@@ -2,7 +2,6 @@
import { getStorage } from './tools';
import { _UID } from '../constants/api';
import { safeParse } from './Robotaxi-public-method';
import { uploadMiniLog } from './request';
type MessageCallback = (data: any) => void;
@@ -117,10 +116,6 @@
        try {
          data = safeParse(res.data as string, {});
          console.log('收到 WebSocket 消息:', res.data);
          if (!this.isHeartbeatMessage(data)) {
            const messageContent = typeof res.data === 'string' ? res.data : JSON.stringify(res.data);
            uploadMiniLog(messageContent);
          }
        } catch (e) {
          console.warn('解析 WebSocket data 失败:', res.data);
          this.notifyListeners('message', res.data);
@@ -187,10 +182,6 @@
      try {
        data = safeParse(res.data as string, {});
        console.log('收到 WebSocket 消息:', res.data);
        if (!this.isHeartbeatMessage(data)) {
          const messageContent = typeof res.data === 'string' ? res.data : JSON.stringify(res.data);
          uploadMiniLog(messageContent);
        }
      } catch (e) {
        console.warn('解析 WebSocket data 失败:', res.data);
        this.notifyListeners('message', res.data);