| | |
| | | import Taro from "@tarojs/taro"; |
| | | import Taro, { useDidShow } from "@tarojs/taro"; |
| | | import { useEffect, useRef, useState } from 'react'; |
| | | import { View, Map, Text, Image, ScrollView } from '@tarojs/components'; |
| | | import './index.less'; |
| | |
| | | const [longitude, setLongitude] = useState<number | null>(null); |
| | | const [latitude, setLatitude] = useState<number | null>(null); |
| | | const [polygons, setPolygons] = useState<any[]>([]); |
| | | const [cityList, setCityList] = useState<any[]>([]); |
| | | const [selectedCityCode, setSelectedCityCode] = useState<string>(''); |
| | | const [selectedCity, setSelectedCity] = useState<any>(null); |
| | | const [rawGeofenceData, setRawGeofenceData] = useState<any[]>([]); |
| | | const [selectedPolygonId, setSelectedPolygonId] = useState<number | null>(null); |
| | | const [operatingTimeInfo, setOperatingTimeInfo] = useState<any>(null); |
| | | const [scrollIntoViewId, setScrollIntoViewId] = useState<string>(''); |
| | | // 地图是否处于加载/切换中:为 true 时用占位图覆盖地图,避免定位跳动可见 |
| | | const [isMapLoading, setIsMapLoading] = useState<boolean>(true); |
| | | const lastPolygonTapTimeRef = useRef(0); |
| | |
| | | }; |
| | | }, []); |
| | | |
| | | useEffect(() => { |
| | | useDidShow(() => { |
| | | const cachedSelectedCity = Taro.getStorageSync('selectedCity'); |
| | | if (cachedSelectedCity && cachedSelectedCity.code && cachedSelectedCity.code !== selectedCity?.code) { |
| | | handleSelectCity(cachedSelectedCity); |
| | | } |
| | | }); |
| | | |
| | | useEffect(() => { |
| | | getOpenCity().then((res: any) => { |
| | | const openCities = res.cities || []; |
| | | setCityList(openCities); |
| | | |
| | | const cachedSelectedCity = Taro.getStorageSync('selectedCity'); |
| | | const cachedCityCode = cachedSelectedCity?.code; |
| | | const matchedCity = openCities.find((city: any) => (city.code || city.adcode) === cachedCityCode); |
| | | |
| | | if (matchedCity) { |
| | | handleSelectCity(matchedCity.code || matchedCity.adcode); |
| | | } else if (cachedCityCode) { |
| | | handleSelectCity(cachedCityCode); |
| | | handleSelectCity(matchedCity); |
| | | } else if (cachedSelectedCity) { |
| | | handleSelectCity(cachedSelectedCity); |
| | | } else if (openCities.length > 0) { |
| | | handleSelectCity(openCities[0].code || openCities[0].adcode); |
| | | handleSelectCity(openCities[0]); |
| | | } else { |
| | | getLocationInfo(); |
| | | } |
| | |
| | | return matchedPolygon?.mapPolygonId ?? null; |
| | | }; |
| | | |
| | | const handleSelectCity = (cityCode: string) => { |
| | | const handleSelectCity = (city: any) => { |
| | | const cityCode = city.code || city.adcode; |
| | | if (!cityCode) return; |
| | | // 进入/切换城市:先用占位图盖住地图,避免定位与缩放过程的跳动被看到 |
| | | setIsMapLoading(true); |
| | |
| | | clearTimeout(revealTimerRef.current); |
| | | revealTimerRef.current = null; |
| | | } |
| | | setSelectedCityCode(cityCode); |
| | | setSelectedCity(city); |
| | | getGeofence({ cityCode }).then((res: any) => { |
| | | if (res && Array.isArray(res)) { |
| | | res=res.filter(item =>item.type==1); |
| | | const normalizedGeofenceData = normalizeGeofenceData(res); |
| | | setRawGeofenceData(normalizedGeofenceData); |
| | | const firstPolygonId = normalizedGeofenceData.length > 0 ? normalizedGeofenceData[0].mapPolygonId : null; |
| | | setSelectedPolygonId(firstPolygonId); |
| | | if (firstPolygonId !== null) { |
| | | setScrollIntoViewId(`fence-${firstPolygonId}`); |
| | | } |
| | | |
| | | if (firstPolygonId !== null) { |
| | | const firstPolygonData = normalizedGeofenceData.find(item => item.mapPolygonId === firstPolygonId); |
| | |
| | | const newPolygons = updatePolygonsStyle(normalizedGeofenceData, firstPolygonId); |
| | | setPolygons(newPolygons); |
| | | |
| | | if (normalizedGeofenceData.length > 0 && normalizedGeofenceData[0]?.points?.length > 0) { |
| | | const firstPoint = normalizedGeofenceData[0].points[0]; |
| | | const lat = Number(firstPoint.lat); |
| | | const lng = Number(firstPoint.lng); |
| | | setLatitude(lat); |
| | | setLongitude(lng); |
| | | if (normalizedGeofenceData.length > 0 && normalizedGeofenceData[0]?.polygonPoints?.length > 0) { |
| | | const firstPolygonPoints = normalizedGeofenceData[0].polygonPoints; |
| | | const firstPoint = firstPolygonPoints[0]; |
| | | setLatitude(firstPoint.latitude); |
| | | setLongitude(firstPoint.longitude); |
| | | |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const points = extractPointsFromPolygons(newPolygons); |
| | | if (points.length > 0) { |
| | | setTimeout(() => { |
| | | mapCtx.includePoints({ |
| | | points: points, |
| | | padding: MAP_INCLUDE_POINTS_PADDING |
| | | }); |
| | | // includePoints 的缩放动画结束后再显示地图,避免“先展示再放大” |
| | | revealTimerRef.current = setTimeout(() => revealMap(), MAP_FIT_ANIMATION_DURATION); |
| | | }, 500); |
| | | } else { |
| | | revealMap(); |
| | | } |
| | | setTimeout(() => { |
| | | mapCtx.includePoints({ |
| | | points: firstPolygonPoints, |
| | | padding: MAP_INCLUDE_POINTS_PADDING |
| | | }); |
| | | // includePoints 的缩放动画结束后再显示地图,避免“先展示再放大” |
| | | revealTimerRef.current = setTimeout(() => revealMap(), MAP_FIT_ANIMATION_DURATION); |
| | | }, 500); |
| | | } else { |
| | | revealMap(); |
| | | } |
| | |
| | | }); |
| | | }; |
| | | |
| | | const handleFenceTabClick = (polygonId: number) => { |
| | | if (polygonId === selectedPolygonId) { |
| | | setScrollIntoViewId(''); |
| | | setTimeout(() => setScrollIntoViewId(`fence-${polygonId}`), 10); |
| | | return; |
| | | } |
| | | setScrollIntoViewId(`fence-${polygonId}`); |
| | | selectPolygon(polygonId, rawGeofenceData); |
| | | |
| | | const targetPolygon = rawGeofenceData.find(item => item.mapPolygonId === polygonId); |
| | | if (targetPolygon && targetPolygon.polygonPoints && targetPolygon.polygonPoints.length > 0) { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | mapCtx.includePoints({ |
| | | points: targetPolygon.polygonPoints, |
| | | padding: MAP_INCLUDE_POINTS_PADDING |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const handlePolygonTap = (e: any) => { |
| | | let tapPolygonId = null; |
| | | if (e.detail && e.detail.polygonId) { |
| | |
| | | |
| | | if (tapPolygonId !== null && rawGeofenceData.length > 0) { |
| | | lastPolygonTapTimeRef.current = Date.now(); |
| | | selectPolygon(tapPolygonId, rawGeofenceData); |
| | | handleFenceTabClick(tapPolygonId); |
| | | } |
| | | }; |
| | | |
| | |
| | | } |
| | | const nearestPolygonId = findNearestPolygonIdByTap(tapLatitude, tapLongitude); |
| | | if (nearestPolygonId !== null) { |
| | | selectPolygon(nearestPolygonId, rawGeofenceData); |
| | | handleFenceTabClick(nearestPolygonId); |
| | | } |
| | | }; |
| | | |
| | |
| | | }; |
| | | |
| | | const toOrigin = () => { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const points = extractPointsFromPolygons(polygons); |
| | | if (points.length > 0) { |
| | | if (selectedPolygonId === null) return; |
| | | const targetPolygon = rawGeofenceData.find(item => item.mapPolygonId === selectedPolygonId); |
| | | if (targetPolygon && targetPolygon.polygonPoints && targetPolygon.polygonPoints.length > 0) { |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | mapCtx.includePoints({ |
| | | points, |
| | | points: targetPolygon.polygonPoints, |
| | | padding: MAP_INCLUDE_POINTS_PADDING |
| | | }); |
| | | } |
| | |
| | | > |
| | | {isMapLoading && <View className="map-placeholder-grid" />} |
| | | </Map> |
| | | {/* 返回按钮 */} |
| | | <View onClick={toBack} className="back-btn"> |
| | | <Image className="back-icon" src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/back-icon.png" /> |
| | | {/* 顶部返回和城市选择 */} |
| | | <View className="top-bar-container"> |
| | | <View onClick={toBack} className="back-btn"> |
| | | <Image className="back-icon" src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/back-icon.png" /> |
| | | </View> |
| | | <View className="select-city" onClick={() => Taro.navigateTo({ url: '/robotaxi/pages/select-city/index' })}> |
| | | <Text className="city-name">{selectedCity?.name || '选择城市'}</Text> |
| | | <Image className="select-city-icon" src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/select-city.png" /> |
| | | </View> |
| | | </View> |
| | | |
| | | {/* 底部面板 */} |
| | | { |
| | | cityList.length != 0 && |
| | | rawGeofenceData.length !== 0 && |
| | | <View className="bottom-sheet"> |
| | | <Image className="origin-icon" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/origin-icon.png' onClick={toOrigin} /> |
| | | <View className="header"> |
| | | <Text className="title">查看运营区域</Text> |
| | | </View> |
| | | |
| | | <ScrollView scrollX className="city-scroll"> |
| | | <View className="city-list"> |
| | | {cityList.map((city, index) => { |
| | | const code = city.code || city.adcode; |
| | | <ScrollView scrollX className="fence-scroll" scrollIntoView={scrollIntoViewId} scrollWithAnimation> |
| | | <View className="fence-list"> |
| | | {rawGeofenceData.map((fence, index) => { |
| | | return ( |
| | | <View |
| | | key={index} |
| | | className={`city-item ${selectedCityCode === code ? 'active' : ''}`} |
| | | onClick={() => handleSelectCity(code)} |
| | | id={`fence-${fence.mapPolygonId}`} |
| | | className={`fence-item ${selectedPolygonId === fence.mapPolygonId ? 'active' : ''}`} |
| | | onClick={() => handleFenceTabClick(fence.mapPolygonId)} |
| | | > |
| | | {city.name} |
| | | {fence.name || `区域${index + 1}`} |
| | | </View> |
| | | ); |
| | | })} |
| | |
| | | </View> |
| | | <View className="content-desc"> |
| | | <View className="desc-item"> |
| | | <Text className="text">各站点运营时间可能存在调整,实际运营情况请以页面实时展示为准</Text> |
| | | <Text className="text">1. 运营时间内可正常使用打车服务,超出运营时间无法使用打车功能</Text> |
| | | </View> |
| | | {/* <View className="desc-item"> |
| | | <Text className="index">2.</Text> |
| | | <Text className="text">各站点运营时间可能存在调整,实际运营情况请以页面实时展示为准</Text> |
| | | </View> */} |
| | | <View className="desc-item"> |
| | | <Text className="text">2. 各站点运营时间可能存在差异,站点运营情况请依照实际情况</Text> |
| | | </View> |
| | | <View className="desc-item"> |
| | | <Text className="text">3. 有问题可联系区域负责人,联系电话:4008208820</Text> |
| | | </View> |
| | | </View> |
| | | </View> |
| | | </View> |