feat(robotaxi): 新增场站地图聚焦Hook并修复相关问题
新增`use-station-scale-focus`自定义Hook,实现选中场站时自动缩放地图至18级并过滤展示场站专属围栏
整合该Hook到首页和场站选择页面,修复地图围栏参数传递错误
优化地图自动缩放逻辑,跳过场站选中状态下的自动缩放
修复蓝牙解锁弹窗重复弹出问题,通过本地存储标记避免同一订单多次提示
清理冗余副作用代码,调整蓝牙相关逻辑的依赖项
| | |
| | | fencePolylines: any[]; |
| | | /** 地图多边形围栏。 */ |
| | | polygons: any[]; |
| | | /** 地图多边形虚线边框围栏。 */ |
| | | fencePolylines: any[]; |
| | | /** 是否拒绝定位。 */ |
| | | isLocationAuthDenied: boolean; |
| | | /** 当前选中城市名称。 */ |
| | |
| | | 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} |
| | |
| | | shouldRetainManualSelectedHomeStartSiteOnNextHome, |
| | | writeHomeStartSiteDisplayState |
| | | } from './use-home-start-site-display'; |
| | | import useStationScaleFocus from '../../../../robotaxi/hooks/use-station-scale-focus'; |
| | | |
| | | type HomeTripStatus = TripStatus | 0; |
| | | |
| | |
| | | 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) => { |
| | |
| | | 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'; |
| | |
| | | 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 { |
| | |
| | | latitude={latitude} |
| | | mapScale={mapScale} |
| | | mergedMapMarkers={mergedMapMarkers} |
| | | fencePolylines={fencePolylines} |
| | | polygons={polygons} |
| | | fencePolylines={displayFencePolylines} |
| | | polygons={displayPolygons} |
| | | isLocationAuthDenied={isLocationAuthDenied} |
| | | selectedCityName={selectedCity?.name} |
| | | weatherData={weatherData} |
| New file |
| | |
| | | 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 |
| | | }; |
| | | } |
| | |
| | | return '车辆到达后,手机靠近车门可自动解锁'; |
| | | }, [status]); |
| | | |
| | | useEffect(() => { |
| | | if (bluetoothAuthSuccess) { |
| | | setShowSuccessAuthPopup(true); |
| | | } |
| | | }, [bluetoothAuthSuccess]); |
| | | |
| | | |
| | | useEffect(() => { |
| | | return () => { |
| | |
| | | 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); |
| | |
| | | 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)) { |
| | |
| | | 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) => { |
| | |
| | | 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 |
| | |
| | | */ |
| | | 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); |
| | | |
| | |
| | | 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} |