hejianhao
2026-04-28 7b692de6a7cf7f18e12c6e7e658fd69a9b555c66
refactor(robotaxi): 统一围栏格式化逻辑并提取公共方法

- 将 `formatGeofenceByType` 和 `buildStationMetaMap` 从页面工具函数移至公共方法 `Robotaxi-public-method.ts`,供首页和选点页复用
- 首页地图状态新增 `fencePolylines` 以支持场站围栏虚线边框渲染
- 更新首页与选点页的围栏数据处理逻辑,统一调用公共格式化方法,确保视觉表现一致
- 移除选点页中已迁移的冗余工具函数文件 `use-select-map-site-utils.ts`
6个文件已修改
190 ■■■■■ 已修改文件
src/pages/robotaxi/home/hooks/use-map-state.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-navigation.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/hooks/use-select-map-site-utils.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/Robotaxi-public-method.ts 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-map-state.ts
@@ -13,6 +13,7 @@
  const [latitude, setLatitude] = useState<number | null>(null);
  const [markers, setMarkers] = useState<any[]>([]);
  const [polygons, setPolygons] = useState<any[]>([]);
  const [fencePolylines, setFencePolylines] = useState<any[]>([]);
  const [mapScale, setMapScale] = useState(14);
  const [isOperatingArea, setIsOperatingArea] = useState(true);
  const [electronicAreaId, setElectronicAreaId] = useState('');
@@ -65,6 +66,7 @@
    latitude,
    markers,
    polygons,
    fencePolylines,
    mapScale,
    isOperatingArea,
    electronicAreaId,
@@ -74,6 +76,7 @@
    setLatitude,
    setMarkers,
    setPolygons,
    setFencePolylines,
    setMapScale,
    setIsOperatingArea,
    setElectronicAreaId,
src/pages/robotaxi/home/hooks/use-navigation.ts
@@ -1,5 +1,11 @@
import Taro from '@tarojs/taro';
import { formatGeofencePolygons, extractPointsFromPolygons, handleBannerJump, reportEvent, filterRenderableGeofence } from '../../../../utils/Robotaxi-public-method';
import {
  formatGeofenceByType,
  extractPointsFromPolygons,
  handleBannerJump,
  reportEvent,
  filterRenderableGeofence
} from '../../../../utils/Robotaxi-public-method';
import { jumpTo } from '../../../../utils/jump';
import { getGeofence } from '../../../../robotaxi/api/home';
@@ -20,8 +26,12 @@
  selectedCity: any;
  /** 地图围栏 */
  polygons: any[];
  /** 场站围栏虚线边框 */
  fencePolylines: any[];
  /** 设置地图围栏 */
  setPolygons: (v: any[]) => void;
  /** 设置场站围栏虚线边框 */
  setFencePolylines: (v: any[]) => void;
  /** 设置定位拒绝状态 */
  setIsLocationAuthDenied: (v: boolean) => void;
  /** 设置地图缩放 */
@@ -217,15 +227,19 @@
      const res: any = await getGeofence({ cityCode: deps.selectedCity.code });
      const geofenceList = filterRenderableGeofence(res);
      if (geofenceList.length > 0) {
        nextPolygons = formatGeofencePolygons(geofenceList);
        const { polygons: nextFormattedPolygons, fencePolylines: nextFencePolylines } = formatGeofenceByType(geofenceList); // 围栏格式统一复用公共实现
        nextPolygons = nextFormattedPolygons;
        deps.setPolygons(nextPolygons);
        deps.setFencePolylines(nextFencePolylines);
      } else {
        nextPolygons = [];
        deps.setPolygons([]);
        deps.setFencePolylines([]);
      }
    } catch (err) {
      nextPolygons = [];
      deps.setPolygons([]);
      deps.setFencePolylines([]);
    }
    if (!nextPolygons || nextPolygons.length === 0) {
      Taro.showToast({ title: '此城市暂未开通运营区域', icon: 'none' });
src/pages/robotaxi/home/index.tsx
@@ -20,7 +20,7 @@
import wsManager from '../../../utils/websocket';
import {
  filterRenderableGeofence,
  formatGeofencePolygons,
  formatGeofenceByType,
  getCityLocation,
  mapOrderStatusToTripStatus,
  safeParse,
@@ -100,6 +100,7 @@
    latitude,
    markers: siteMarkers,
    polygons,
    fencePolylines,
    mapScale,
    isOperatingArea,
    electronicAreaId,
@@ -107,6 +108,7 @@
    setLongitude,
    setLatitude,
    setPolygons,
    setFencePolylines,
    setMapScale,
    setIsLocationAuthDenied,
    shouldIgnoreRegionChange,
@@ -269,10 +271,12 @@
      const geofenceList = filterRenderableGeofence(res);
      if (geofenceList.length > 0) {
        const newPolygons = formatGeofencePolygons(geofenceList);
        const { polygons: newPolygons, fencePolylines: nextFencePolylines } = formatGeofenceByType(geofenceList); // 围栏面和场站虚线边框统一走公共格式化
        setPolygons(newPolygons);
        setFencePolylines(nextFencePolylines);
      } else {
        setPolygons([]);
        setFencePolylines([]);
        if ((orderData || orderData?.id) && JSON.stringify(orderData) != '{}') return;
        // 定位到城市中心
        if (selectedCity.name && allowMapMove) {
@@ -292,6 +296,7 @@
    }).catch(err => {
      console.error('获取电子围栏失败', err);
      setPolygons([]);
      setFencePolylines([]);
      // 定位到城市中心
      if (selectedCity.name && allowMapMove) {
        getCityLocation(selectedCity.name).then(loc => {
@@ -506,7 +511,9 @@
    currentSite,
    selectedCity,
    polygons,
    fencePolylines,
    setPolygons,
    setFencePolylines,
    setIsLocationAuthDenied,
    setMapScale,
    getLocationInfo,
@@ -596,6 +603,7 @@
        latitude={latitude ?? DEFAULT_LATITUDE}
        scale={mapScale}
        markers={isLocationAuthDenied ? [] : mergedMapMarkers.map(m => ({ ...m, title: '', name: '' }))}
        polyline={fencePolylines}
        polygons={polygons}
        showLocation={!!longitude}
        onRegionChange={handleRegionChange}
@@ -687,7 +695,7 @@
            <Image className="site-top-bg" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/site-top-bg.png' />
            <View className="site-boarding-point">
              {/* 起点 */}
              <View onClick={() => toSelectSitePage('start', true)} className="now-site">
              <View onClick={() => toSelectSitePage('start')} className="now-site">
                <View className="now-site-dot"></View>
                <View className="now-site-address">
                  <View className="now-site-address-content">
src/robotaxi/pages/select-map-site/hooks/use-select-map-site-utils.ts
@@ -1,55 +1,3 @@
import {
  GEOFENCE_TYPE_STATION,
  GEOFENCE_STATION_FILL_COLOR,
  GEOFENCE_STATION_STROKE_COLOR
} from '../../../constants/geofence';
/**
 * 按围栏类型格式化地图覆盖物:type=2 使用白色填充与白色虚线边框。
 */
export const formatGeofenceByType = (geofenceList: any[]) => {
  if (!Array.isArray(geofenceList)) {
    return { polygons: [], fencePolylines: [] };
  }
  const polygons = geofenceList.map((item) => {
    const type = Number(item?.type);
    const points = Array.isArray(item?.points) ? item.points : [];
    return {
      points: points.map((p: any) => ({
        latitude: p.lat,
        longitude: p.lng
      })),
      strokeWidth: type === GEOFENCE_TYPE_STATION ? 1 : 2,
      strokeColor: type === GEOFENCE_TYPE_STATION ? GEOFENCE_STATION_STROKE_COLOR : '#A1B1FF',
      fillColor: type === GEOFENCE_TYPE_STATION ? GEOFENCE_STATION_FILL_COLOR : '#6d7fd933',
      zIndex: 1
    };
  });
  const fencePolylines = geofenceList.reduce((acc: any[], item: any, index: number) => {
    const type = Number(item?.type);
    if (type !== GEOFENCE_TYPE_STATION || !Array.isArray(item?.points) || item.points.length < 2) {
      return acc;
    }
    const linePoints = item.points.map((p: any) => ({
      latitude: p.lat,
      longitude: p.lng
    }));
    // 虚线边框通过 polyline dottedLine 实现,首尾闭合形成围栏边界。
    acc.push({
      id: 900000 + index,
      points: [...linePoints, linePoints[0]],
      color: GEOFENCE_STATION_STROKE_COLOR,
      width: 2,
      dottedLine: true,
      arrowLine: false
    });
    return acc;
  }, []);
  return { polygons, fencePolylines };
};
/**
 * 减少安卓端绘制点位,降低地图折线渲染压力。
 */
@@ -67,22 +15,4 @@
    reduced.push(last);
  }
  return reduced;
};
/**
 * 从围栏数据提取场站元信息映射:stationId -> 场站名称/收藏状态。
 */
export const buildStationMetaMap = (geofenceList: any[]) => {
  const stationMetaMap: Record<string, any> = {};
  if (!Array.isArray(geofenceList)) return stationMetaMap;
  geofenceList.forEach((item) => {
    const stationId = item?.stationId;
    if (!stationId) return;
    stationMetaMap[String(stationId)] = {
      stationId: String(stationId),
      name: item?.name || '',
      isCollect: !!item?.stationIsCollect, // 围栏返回的场站收藏字段:stationIsCollect(boolean)
    };
  });
  return stationMetaMap;
};
src/robotaxi/pages/select-map-site/hooks/use-select-map-site.ts
@@ -1,7 +1,9 @@
import Taro from "@tarojs/taro";
import { useEffect } from 'react';
import {
  buildStationMetaMap,
  findNearestMarker,
  formatGeofenceByType,
  getAutoScaleBySites,
  getDistance,
  fetchMapData,
@@ -15,7 +17,6 @@
import { jumpTo } from '../../../../utils/jump';
import useSelectMapSiteState from './use-select-map-site-state';
import useSelectMapSiteWalkingRoute from './use-select-map-site-walking-route';
import { buildStationMetaMap, formatGeofenceByType } from './use-select-map-site-utils';
const AUTO_SCALE_MAX = 17; // 自动缩放最大等级
const AUTO_SCALE_MIN = 11; // 自动缩放最小等级
src/utils/Robotaxi-public-method.ts
@@ -5,7 +5,12 @@
import { getFenceCheck, getNearestSite } from '../robotaxi/api/home';
import { getOpenCity } from '../robotaxi/api/select-city';
import { getStorage, getOrCreateLocalId } from './tools';
import { GEOFENCE_TYPE_OPERATING } from '../robotaxi/constants/geofence';
import {
  GEOFENCE_STATION_FILL_COLOR,
  GEOFENCE_STATION_STROKE_COLOR,
  GEOFENCE_TYPE_OPERATING,
  GEOFENCE_TYPE_STATION
} from '../robotaxi/constants/geofence';
/**
 * 安全的 JSON 解析函数
@@ -287,32 +292,95 @@
};
/**
 * 过滤可渲染的围栏数据,仅保留 type=1 的运营区围栏。
 * 过滤可渲染的围栏数据,首页需要同时渲染运营区与场站围栏。
 */
export const filterRenderableGeofence = (geofenceList: any[]) => {
  if (!Array.isArray(geofenceList)) return [];
  return geofenceList.filter((item) => Number(item?.type) === GEOFENCE_TYPE_OPERATING);
  return geofenceList.filter((item) => [GEOFENCE_TYPE_OPERATING, GEOFENCE_TYPE_STATION].includes(Number(item?.type)));
};
// 格式化电子围栏数据
/**
 * 格式化电子围栏面覆盖物,场站围栏使用独立配色以便和运营区区分。
 */
export const formatGeofencePolygons = (geofenceList: any[]) => {
  if (!geofenceList || !Array.isArray(geofenceList)) {
    return [];
  }
  return geofenceList.map(item => {
    const type = Number(item?.type); // 围栏类型
    return {
      points: item.points.map(p => ({
        latitude: p.lat,
        longitude: p.lng
      })),
      strokeWidth: 2,
      strokeColor: '#A1B1FF',
      fillColor: '#6d7fd933',
      strokeWidth: type === GEOFENCE_TYPE_STATION ? 1 : 2,
      strokeColor: type === GEOFENCE_TYPE_STATION ? GEOFENCE_STATION_STROKE_COLOR : '#A1B1FF',
      fillColor: type === GEOFENCE_TYPE_STATION ? GEOFENCE_STATION_FILL_COLOR : '#6d7fd933',
      zIndex: 1
    }
  });
};
/**
 * 格式化场站围栏虚线边框,和选点页保持同一套表现。
 */
export const formatGeofenceFencePolylines = (geofenceList: any[]) => {
  if (!Array.isArray(geofenceList)) {
    return [];
  }
  return geofenceList.reduce((acc: any[], item: any, index: number) => {
    const type = Number(item?.type); // 围栏类型
    if (type !== GEOFENCE_TYPE_STATION || !Array.isArray(item?.points) || item.points.length < 2) {
      return acc;
    }
    const linePoints = item.points.map((p: any) => ({
      latitude: p.lat,
      longitude: p.lng
    }));
    acc.push({
      id: 900000 + index,
      points: [...linePoints, linePoints[0]],
      color: GEOFENCE_STATION_STROKE_COLOR,
      width: 2,
      dottedLine: true,
      arrowLine: false
    });
    return acc;
  }, []);
};
/**
 * 按统一规则格式化围栏覆盖物,供首页和选点页复用同一套渲染结果。
 */
export const formatGeofenceByType = (geofenceList: any[]) => {
  return {
    polygons: formatGeofencePolygons(geofenceList),
    fencePolylines: formatGeofenceFencePolylines(geofenceList)
  };
};
/**
 * 从围栏数据提取场站元信息映射,避免首页与选点页重复维护场站元数据格式。
 */
export const buildStationMetaMap = (geofenceList: any[]) => {
  const stationMetaMap: Record<string, any> = {};
  if (!Array.isArray(geofenceList)) {
    return stationMetaMap;
  }
  geofenceList.forEach((item) => {
    const stationId = item?.stationId; // 围栏关联的场站 ID
    if (!stationId) {
      return;
    }
    stationMetaMap[String(stationId)] = {
      stationId: String(stationId),
      name: item?.name || '',
      isCollect: !!item?.stationIsCollect,
    };
  });
  return stationMetaMap;
};
// 提取多边形所有顶点
export const extractPointsFromPolygons = (polygons: any[]) => {
  const points: any[] = [];