refactor(robotaxi): 统一围栏格式化逻辑并提取公共方法
- 将 `formatGeofenceByType` 和 `buildStationMetaMap` 从页面工具函数移至公共方法 `Robotaxi-public-method.ts`,供首页和选点页复用
- 首页地图状态新增 `fencePolylines` 以支持场站围栏虚线边框渲染
- 更新首页与选点页的围栏数据处理逻辑,统一调用公共格式化方法,确保视觉表现一致
- 移除选点页中已迁移的冗余工具函数文件 `use-select-map-site-utils.ts`
| | |
| | | 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(''); |
| | |
| | | latitude, |
| | | markers, |
| | | polygons, |
| | | fencePolylines, |
| | | mapScale, |
| | | isOperatingArea, |
| | | electronicAreaId, |
| | |
| | | setLatitude, |
| | | setMarkers, |
| | | setPolygons, |
| | | setFencePolylines, |
| | | setMapScale, |
| | | setIsOperatingArea, |
| | | setElectronicAreaId, |
| | |
| | | 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'; |
| | | |
| | |
| | | selectedCity: any; |
| | | /** 地图围栏 */ |
| | | polygons: any[]; |
| | | /** 场站围栏虚线边框 */ |
| | | fencePolylines: any[]; |
| | | /** 设置地图围栏 */ |
| | | setPolygons: (v: any[]) => void; |
| | | /** 设置场站围栏虚线边框 */ |
| | | setFencePolylines: (v: any[]) => void; |
| | | /** 设置定位拒绝状态 */ |
| | | setIsLocationAuthDenied: (v: boolean) => void; |
| | | /** 设置地图缩放 */ |
| | |
| | | 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' }); |
| | |
| | | import wsManager from '../../../utils/websocket'; |
| | | import { |
| | | filterRenderableGeofence, |
| | | formatGeofencePolygons, |
| | | formatGeofenceByType, |
| | | getCityLocation, |
| | | mapOrderStatusToTripStatus, |
| | | safeParse, |
| | |
| | | latitude, |
| | | markers: siteMarkers, |
| | | polygons, |
| | | fencePolylines, |
| | | mapScale, |
| | | isOperatingArea, |
| | | electronicAreaId, |
| | |
| | | setLongitude, |
| | | setLatitude, |
| | | setPolygons, |
| | | setFencePolylines, |
| | | setMapScale, |
| | | setIsLocationAuthDenied, |
| | | shouldIgnoreRegionChange, |
| | |
| | | |
| | | 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) { |
| | |
| | | }).catch(err => { |
| | | console.error('获取电子围栏失败', err); |
| | | setPolygons([]); |
| | | setFencePolylines([]); |
| | | // 定位到城市中心 |
| | | if (selectedCity.name && allowMapMove) { |
| | | getCityLocation(selectedCity.name).then(loc => { |
| | |
| | | currentSite, |
| | | selectedCity, |
| | | polygons, |
| | | fencePolylines, |
| | | setPolygons, |
| | | setFencePolylines, |
| | | setIsLocationAuthDenied, |
| | | setMapScale, |
| | | getLocationInfo, |
| | |
| | | latitude={latitude ?? DEFAULT_LATITUDE} |
| | | scale={mapScale} |
| | | markers={isLocationAuthDenied ? [] : mergedMapMarkers.map(m => ({ ...m, title: '', name: '' }))} |
| | | polyline={fencePolylines} |
| | | polygons={polygons} |
| | | showLocation={!!longitude} |
| | | onRegionChange={handleRegionChange} |
| | |
| | | <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"> |
| | |
| | | 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 }; |
| | | }; |
| | | |
| | | /** |
| | | * 减少安卓端绘制点位,降低地图折线渲染压力。 |
| | | */ |
| | |
| | | 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; |
| | | }; |
| | |
| | | import Taro from "@tarojs/taro"; |
| | | import { useEffect } from 'react'; |
| | | import { |
| | | buildStationMetaMap, |
| | | findNearestMarker, |
| | | formatGeofenceByType, |
| | | getAutoScaleBySites, |
| | | getDistance, |
| | | fetchMapData, |
| | |
| | | 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; // 自动缩放最小等级 |
| | |
| | | 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 解析函数 |
| | |
| | | }; |
| | | |
| | | /** |
| | | * 过滤可渲染的围栏数据,仅保留 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[] = []; |