hejianhao
2026-06-02 209b9667ca98c7a11b683136bcfdad8b641f7d54
feat(robotaxi): 新增场站地图聚焦Hook并修复相关问题

新增`use-station-scale-focus`自定义Hook,实现选中场站时自动缩放地图至18级并过滤展示场站专属围栏
整合该Hook到首页和场站选择页面,修复地图围栏参数传递错误
优化地图自动缩放逻辑,跳过场站选中状态下的自动缩放
修复蓝牙解锁弹窗重复弹出问题,通过本地存储标记避免同一订单多次提示
清理冗余副作用代码,调整蓝牙相关逻辑的依赖项
1个文件已添加
8个文件已修改
140 ■■■■■ 已修改文件
src/pages/robotaxi/home/components/home-map-panel.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-home-bootstrap.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-map-state.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/hooks/use-station-scale-focus.ts 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/index.tsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/home-map-panel.tsx
@@ -19,6 +19,8 @@
  fencePolylines: any[];
  /** 地图多边形围栏。 */
  polygons: any[];
  /** 地图多边形虚线边框围栏。 */
  fencePolylines: any[];
  /** 是否拒绝定位。 */
  isLocationAuthDenied: boolean;
  /** 当前选中城市名称。 */
@@ -101,8 +103,8 @@
      latitude={props.latitude ?? DEFAULT_LATITUDE}
      scale={props.mapScale}
      markers={displayMarkers}
      polyline={props.fencePolylines}
      polygons={props.polygons}
      polyline={props.fencePolylines}
      showLocation={!!props.longitude}
      onRegionChange={props.onRegionChange}
      onMarkerTap={props.onMarkerTap}
src/pages/robotaxi/home/hooks/use-home-bootstrap.ts
@@ -12,6 +12,7 @@
  shouldRetainManualSelectedHomeStartSiteOnNextHome,
  writeHomeStartSiteDisplayState
} from './use-home-start-site-display';
import useStationScaleFocus from '../../../../robotaxi/hooks/use-station-scale-focus';
type HomeTripStatus = TripStatus | 0;
src/pages/robotaxi/home/hooks/use-map-state.ts
@@ -111,7 +111,13 @@
      if (options?.commitMarkers !== false) {
        scheduleMarkerCommit(result.newMarkers, options?.markerCommitDelayMs || 0);
      }
      if (autoScaleAppliedCityCodeRef.current !== code) {
      // 检查当前选中的站点是否为场站子站点
      // 因为 autoScale 会强制覆盖 mapScale,如果当前处于场站聚焦态(应当是 18),需要跳过 autoScale 逻辑
      const currentSite = Taro.getStorageSync('startSite');
      const isStationFocused = currentSite && currentSite.stationId;
      if (!isStationFocused && autoScaleAppliedCityCodeRef.current !== code) {
        const autoScale = getAutoScaleBySites(result?.sites || result?.newMarkers || [], lat, lng);
        if (typeof autoScale === 'number') {
          setMapScale((prev) => {
src/pages/robotaxi/home/index.tsx
@@ -23,6 +23,7 @@
import useWeather from './hooks/use-weather';
import useMarketing from './hooks/use-marketing';
import useFenceManager from './hooks/use-fence-manager';
import useStationScaleFocus from '../../../robotaxi/hooks/use-station-scale-focus';
import { DEFAULT_TEMPERATURE_UNIT } from '../../../utils/weather';
import { _ROBOTAXI_WS_HOST } from '../../../constants/api';
import { DEFAULT_LATITUDE, DEFAULT_LONGITUDE, FIRST_ENTER_KEY } from '../../../robotaxi/constants/map';
@@ -175,6 +176,13 @@
    syncSelectedCityFromStorage,
    consumeCitySwitchFlag
  } = useCitySwitch();
  const { displayPolygons, displayFencePolylines } = useStationScaleFocus({
    currentSite: siteState.currentSite,
    setMapScale: mapState.setMapScale,
    originalPolygons: mapState.polygons,
    originalFencePolylines: mapState.fencePolylines
  }); // 场站缩放聚焦与围栏展示控制
  const { isMaintain, maintainBounce, hotfixEndText, triggerMaintainBounce } = useHotfixCheck();
  const isAndroid = useMemo(() => {
    try {
@@ -507,8 +515,8 @@
        latitude={latitude}
        mapScale={mapScale}
        mergedMapMarkers={mergedMapMarkers}
        fencePolylines={fencePolylines}
        polygons={polygons}
        fencePolylines={displayFencePolylines}
        polygons={displayPolygons}
        isLocationAuthDenied={isLocationAuthDenied}
        selectedCityName={selectedCity?.name}
        weatherData={weatherData}
src/robotaxi/hooks/use-station-scale-focus.ts
New file
@@ -0,0 +1,74 @@
import { useEffect, useRef, useMemo } from 'react';
import { GEOFENCE_STATION_FILL_COLOR, GEOFENCE_STATION_STROKE_COLOR } from '../constants/geofence';
type Deps = {
  /** 当前选中的站点对象 */
  currentSite: any;
  /** 设置地图缩放等级的方法 */
  setMapScale: (scale: number | ((prev: number) => number)) => void;
  /** 原始的多边形围栏列表 */
  originalPolygons: any[];
  /** 原始的虚线边框列表 */
  originalFencePolylines: any[];
};
/**
 * 场站站点地图缩放与围栏展示聚焦控制 Hook。
 *
 * 职责:
 * 1. 当选中“场站内”的站点时,自动将地图缩放等级设置为 18,并仅展示场站围栏(隐藏蓝色运营区围栏)。
 * 2. 当选中非场站站点或取消选中时,恢复展示所有围栏。
 * 3. 使用 ref 记录最后一次处理的站点,避免用户手动缩放地图后被反复拉回。
 */
export default function useStationScaleFocus({ currentSite, setMapScale, originalPolygons, originalFencePolylines }: Deps) {
  const lastHandledStationSiteIdRef = useRef<string>('');
  const isStationSite = useMemo(() => {
    if (!currentSite) return false;
    return !!currentSite.stationId;
  }, [currentSite]);
  useEffect(() => {
    const stationId = currentSite?.stationId ? String(currentSite.stationId) : '';
    // 如果当前选中的是场站子站点,并且是一个新的场站(或者第一次进来),则触发缩放
    if (isStationSite && stationId) {
      if (lastHandledStationSiteIdRef.current !== stationId) {
        // 通过微调强制触发 React 状态与原生组件属性的同步
        // 避免因为用户手动缩放地图但未同步到 React 状态,导致 setMapScale(18) 被认为没改变而不生效
        setMapScale(prev => {
          if (prev === 18) return 17.99999;
          if (prev === 17.99999) return 18;
          return 18;
        });
        lastHandledStationSiteIdRef.current = stationId;
      }
    } else {
      // 离开场站站点,清除记录
      lastHandledStationSiteIdRef.current = '';
    }
  }, [isStationSite, currentSite?.stationId, setMapScale]);
  // 根据当前是否选中场站站点,过滤围栏数据
  const displayPolygons = useMemo(() => {
    if (!isStationSite) {
      return originalPolygons;
    }
    // 选中场站时,仅保留场站的围栏(通过颜色或透明度特征区分,因为 format 后的多边形已经丢失了 type 属性)
    return originalPolygons.filter(polygon => polygon.fillColor === GEOFENCE_STATION_FILL_COLOR);
  }, [isStationSite, originalPolygons]);
  const displayFencePolylines = useMemo(() => {
    if (!isStationSite) {
      return originalFencePolylines;
    }
    // 选中场站时,仅保留场站的虚线边框(通常全都是场站的,这里保险起见通过颜色过滤)
    return originalFencePolylines.filter(polyline => polyline.color === GEOFENCE_STATION_STROKE_COLOR);
  }, [isStationSite, originalFencePolylines]);
  return {
    displayPolygons,
    displayFencePolylines,
    isStationSite
  };
}
src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts
@@ -482,11 +482,7 @@
    return '车辆到达后,手机靠近车门可自动解锁';
  }, [status]);
  useEffect(() => {
    if (bluetoothAuthSuccess) {
      setShowSuccessAuthPopup(true);
    }
  }, [bluetoothAuthSuccess]);
  useEffect(() => {
    return () => {
src/robotaxi/pages/begin-route/hooks/use-bluetooth-car-control.ts
@@ -175,14 +175,22 @@
          if (isAlreadyUnlocked) {
            unlockFinishedRef.current = true;
            setAuthError('');
            showUnlockToast(UNLOCK_SUCCESS_MESSAGE);
            const storageKey = `ROBOTAXI_UNLOCK_TOAST_SHOWN_${orderId}`;
            if (!Taro.getStorageSync(storageKey)) {
              showUnlockToast(UNLOCK_SUCCESS_MESSAGE);
              Taro.setStorageSync(storageKey, true);
            }
            closeConnectionRef.current();
            return;
          }
          if (result?.success) {
            unlockFinishedRef.current = true;
            setAuthError('');
            showUnlockToast(UNLOCK_SUCCESS_MESSAGE);
            const storageKey = `ROBOTAXI_UNLOCK_TOAST_SHOWN_${orderId}`;
            if (!Taro.getStorageSync(storageKey)) {
              showUnlockToast(UNLOCK_SUCCESS_MESSAGE);
              Taro.setStorageSync(storageKey, true);
            }
            closeConnectionRef.current();
          } else {
            setAuthError(RETRY_UNLOCK_MESSAGE);
@@ -319,10 +327,13 @@
    console.log('[蓝牙] 用户已手动解锁,停止后续蓝牙鉴权');
    manualDoorUnlockRef.current = true;
    unlockFinishedRef.current = true;
    if (orderId) {
      Taro.setStorageSync(`ROBOTAXI_UNLOCK_TOAST_SHOWN_${orderId}`, true);
    }
    acceptBluetoothCallbacksRef.current = false;
    setAuthError('');
    closeConnectionRef.current();
  }, []);
  }, [orderId]);
  useEffect(() => {
    if (!BLUETOOTH_AUTH_ACTIVE_TRIP_STATUSES.includes(tripStatus)) {
src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts
@@ -165,7 +165,10 @@
        const nextStationSites = sites.filter((item: any) => String(item?.stationId || '') === String(state.selectedStation.stationId));
        state.setStationSiteList(nextStationSites);
      }
      if (!state.autoScaleLockedRef.current) {
      const isStationFocused = state.selectedSite && state.selectedSite.stationId;
      if (!isStationFocused && !state.autoScaleLockedRef.current) {
        const autoScale = getAutoScaleBySites(sites?.length > 0 ? sites : newMarkers, lat, lng);
        if (typeof autoScale === 'number') {
          state.setMapScale((prev) => {
src/robotaxi/pages/select-map-site/index.tsx
@@ -3,6 +3,7 @@
import { reportEvent } from '../../../utils/Robotaxi-public-method';
import SelectMapSiteView from './components/select-map-site-view';
import useSelectMapSite from './hooks/use-select-map-site';
import useStationScaleFocus from '../../hooks/use-station-scale-focus';
import {
  MAP_SELECT_DROPOFF_PAGE_VIEW_EVENT_ID,
  PIN_PICKUP_LOCATION_PAGE_VIEW_EVENT_ID
@@ -14,6 +15,14 @@
 */
function SelectMapSite() {
  const selectMapSite = useSelectMapSite();
  const { displayPolygons, displayFencePolylines } = useStationScaleFocus({
    currentSite: selectMapSite.selectedSite,
    setMapScale: selectMapSite.setMapScale,
    originalPolygons: selectMapSite.polygons,
    originalFencePolylines: selectMapSite.mapPolylines
  }); // 场站缩放聚焦与围栏展示控制
  const hasReportedPickupPageViewRef = useRef(false);
  const hasReportedDropoffPageViewRef = useRef(false);
@@ -35,8 +44,8 @@
      latitude={selectMapSite.latitude}
      mapScale={selectMapSite.mapScale}
      markers={selectMapSite.markers}
      mapPolylines={selectMapSite.mapPolylines}
      polygons={selectMapSite.polygons}
      mapPolylines={displayFencePolylines}
      polygons={displayPolygons}
      handleRegionChange={selectMapSite.handleRegionChange}
      handleMarkerTap={selectMapSite.handleMarkerTap}
      toBack={selectMapSite.toBack}