| | |
| | | import suggest from '../../../robotaxi/assets/suggest.png' |
| | | import originIcon from '../../../robotaxi/assets/origin-icon.png' |
| | | import wsManager from '../../../utils/websocket'; |
| | | import { getWalkingRoute, formatDurationToClock, formatDateTime, clearOrderTripStorage, preloadImageToTemp, debounce } from '../../../utils/Robotaxi-public-method'; |
| | | import { getWalkingRoute, formatDurationToClock, formatDateTime, clearOrderTripStorage, preloadImageToTemp, debounce, computeAdaptiveBottomPadding } from '../../../utils/Robotaxi-public-method'; |
| | | import { |
| | | saveOrder, |
| | | queryRouteAndFare, |
| | |
| | | const [evaluateSubmitVersion, setEvaluateSubmitVersion] = useState(0); |
| | | const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0); |
| | | const [originOpacity, setOriginOpacity] = useState(1); |
| | | const [mapBottomPadding, setMapBottomPadding] = useState(280); |
| | | const [carCalloutStyle, setCarCalloutStyle] = useState<any>({}); |
| | | const [carCalloutVisible, setCarCalloutVisible] = useState(false); |
| | | |
| | | const markersRef = useRef<any[]>([]); |
| | | const tripStatusRef = useRef(tripStatus); // 行程状态引用 |
| | |
| | | .exec(); |
| | | }; |
| | | |
| | | const updateCarCalloutPosition = () => { |
| | | if (!isAndroid) { |
| | | return; |
| | | } |
| | | if (!vehicleLocation || ![4, 6].includes(tripStatus)) { |
| | | setCarCalloutVisible(false); |
| | | return; |
| | | } |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const resolveRect = (rect: any) => { |
| | | if (!rect) { |
| | | return; |
| | | } |
| | | mapCtx.getRegion({ |
| | | success: (res) => { |
| | | const sw = res?.southwest; |
| | | const ne = res?.northeast; |
| | | const latRange = ne?.latitude - sw?.latitude; |
| | | const lngRange = ne?.longitude - sw?.longitude; |
| | | if (!latRange || !lngRange) { |
| | | return; |
| | | } |
| | | const x = ((vehicleLocation.longitude - sw.longitude) / lngRange) * rect.width; |
| | | const y = ((ne.latitude - vehicleLocation.latitude) / latRange) * rect.height; |
| | | setCarCalloutStyle({ |
| | | position: 'fixed', |
| | | left: `${rect.left + x}px`, |
| | | top: `${rect.top + y}px`, |
| | | transform: 'translate(-50%, -120%)' |
| | | }); |
| | | setCarCalloutVisible(true); |
| | | } |
| | | }); |
| | | }; |
| | | if (mapRectRef.current) { |
| | | resolveRect(mapRectRef.current); |
| | | return; |
| | | } |
| | | Taro.createSelectorQuery() |
| | | .select('#map') |
| | | .boundingClientRect((rect) => { |
| | | if (rect) { |
| | | mapRectRef.current = rect; |
| | | } |
| | | resolveRect(rect); |
| | | }) |
| | | .exec(); |
| | | }; |
| | | |
| | | // 更新等待雷达位置 |
| | | useEffect(() => { |
| | | updateWaitingRadarPosition(); |
| | | }, [shouldShowWaitingRadar, startSite?.latitude, startSite?.longitude, longitude, latitude]); |
| | | |
| | | useEffect(() => { |
| | | updateCarCalloutPosition(); |
| | | }, [isAndroid, vehicleLocation?.latitude, vehicleLocation?.longitude, tripStatus]); |
| | | |
| | | const resolveSafeBottomInset = () => { |
| | | try { |
| | | const info = Taro.getSystemInfoSync(); |
| | | const screenHeight = info?.screenHeight; |
| | | const safeBottom = info?.safeArea?.bottom; |
| | | if (Number.isFinite(screenHeight) && Number.isFinite(safeBottom)) { |
| | | return Math.max(0, screenHeight - safeBottom); |
| | | } |
| | | } catch (error) { |
| | | return 0; |
| | | } |
| | | return 0; |
| | | }; |
| | | |
| | | const resolveSafeTopInset = () => { |
| | | try { |
| | | const info = Taro.getSystemInfoSync(); |
| | | const safeTop = info?.safeArea?.top; |
| | | if (Number.isFinite(safeTop)) { |
| | | return Math.max(0, safeTop); |
| | | } |
| | | } catch (error) { |
| | | return 0; |
| | | } |
| | | return 0; |
| | | }; |
| | | |
| | | const updateMapViewportPadding = () => { |
| | | Taro.nextTick(() => { |
| | | Taro.createSelectorQuery() |
| | | .select('.container') |
| | | .boundingClientRect((rect) => { |
| | | const safeInset = resolveSafeBottomInset(); |
| | | const measured = rect?.height ? rect.height + safeInset + 16 : 0; |
| | | const nextPadding = Math.max(280, Math.round(measured)); |
| | | setMapBottomPadding(nextPadding); |
| | | }) |
| | | .exec(); |
| | | }); |
| | | }; |
| | | |
| | | const computeBottomPadding = (points: any[], viewportType: string) => { |
| | | const basePadding = viewportType === 'order_detail' ? Math.max(200, mapBottomPadding) : mapBottomPadding; |
| | | return computeAdaptiveBottomPadding(points, basePadding); |
| | | }; |
| | | |
| | | // 应用路线点 |
| | | const applyRoutePoints = (points: any[]) => { |
| | |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const allPoints = [...points, ...walkingPoints]; |
| | | if (allPoints.length > 0) { |
| | | const topPadding = Math.max(24, resolveSafeTopInset() + 32); |
| | | const bottomPadding = computeBottomPadding(allPoints, 'route_walking'); |
| | | mapCtx.includePoints({ |
| | | points: allPoints, |
| | | padding: [100, 100, 280, 100] |
| | | padding: [topPadding, 100, bottomPadding, 100] |
| | | }); |
| | | } |
| | | } |
| | |
| | | if (nextKey === viewportKeyRef.current) return; |
| | | viewportKeyRef.current = nextKey; |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | const padding = viewportType === 'order_detail' ? [50, 50, 200, 50] : [100, 100, 280, 100]; |
| | | const bottomPadding = computeBottomPadding(points, viewportType); |
| | | const baseTopPadding = viewportType === 'order_detail' ? 60 : 24; |
| | | const topPadding = Math.max(baseTopPadding, resolveSafeTopInset() + 32); |
| | | const padding = viewportType === 'order_detail' |
| | | ? [topPadding, 50, bottomPadding, 50] |
| | | : [topPadding, 100, bottomPadding, 100]; |
| | | Taro.nextTick(() => { |
| | | mapCtx.includePoints({ |
| | | points, |
| | |
| | | if (tripStatus === 6 && viewportType === 'route') { |
| | | tripStatus6ViewportLockedRef.current = true; |
| | | } |
| | | }, [tripStatus, statusRule, routePoints, walkingPoints, startSite?.latitude, startSite?.longitude, endSite?.latitude, endSite?.longitude, vehicleLocation, longitude, latitude]); |
| | | }, [tripStatus, statusRule, routePoints, walkingPoints, startSite?.latitude, startSite?.longitude, endSite?.latitude, endSite?.longitude, vehicleLocation, longitude, latitude, mapBottomPadding]); |
| | | |
| | | useEffect(() => { |
| | | if (!statusRule.translateCar) return; |
| | |
| | | } |
| | | |
| | | ensureSocketListeners(); |
| | | updateMapViewportPadding(); |
| | | }); |
| | | |
| | | useEffect(() => { |
| | |
| | | } |
| | | |
| | | }, [startSite?.longitude, startSite?.latitude, endSite?.longitude, endSite?.latitude, longitude, latitude]); |
| | | |
| | | useEffect(() => { |
| | | updateMapViewportPadding(); |
| | | }, [tripStatus]); |
| | | |
| | | |
| | | // 获取用户位置信息 |
| | |
| | | |
| | | const toSelectSite = (pageType: 'start' | 'end') => { |
| | | Taro.navigateTo({ |
| | | url: `/robotaxi/pages/site-search/index?pageType=${pageType}` |
| | | url: `/robotaxi/pages/site-search/index?pageType=${pageType}&longitude=${startSite.longitude}&latitude=${startSite.latitude}` |
| | | }) |
| | | } |
| | | |
| | |
| | | const handleRegionChange = (e) => { |
| | | if (e?.type === 'begin') { |
| | | setWaitingRadarVisible(false); |
| | | setCarCalloutVisible(false); |
| | | return; |
| | | } |
| | | if (e?.type === 'end') { |
| | | setWaitingRadarVisible(true); |
| | | updateWaitingRadarPosition(); |
| | | updateCarCalloutPosition(); |
| | | } |
| | | }; |
| | | |
| | |
| | | } else { |
| | | setOriginOpacity(1); |
| | | } |
| | | updateMapViewportPadding(); |
| | | }; |
| | | |
| | | const durationDisplay = formatDurationDisplay(routeDuration); |
| | |
| | | <CoverImage className="waiting-callout-bg-img" src={waitingCalloutBgSrc} /> |
| | | <CoverView className="waiting-callout-bg-text">{formatDurationToClock(elapsedSeconds)}</CoverView> |
| | | </CoverView> */} |
| | | <CoverView className="waiting-callout-txt"> |
| | | <CoverView className={`waiting-callout-txt ${!isAndroid && 'pr-6'}`}> |
| | | {item.name} |
| | | <CoverView style={{ color: 'transparent' }}>占</CoverView> |
| | | { |
| | | !isAndroid && |
| | | <CoverView style={{ color: 'transparent' }}>占</CoverView> |
| | | } |
| | | </CoverView> |
| | | </CoverView> |
| | | <CoverImage className="waiting-radar" src={waitingRadarSrc} /> |
| | |
| | | ) |
| | | } |
| | | |
| | | if (item.name === '车辆位置' && isAndroid && [4, 6].includes(tripStatus)) { |
| | | return null; |
| | | } |
| | | return ( |
| | | <CoverView slot="callout" marker-id={item.id} key={`${tripStatus}-${item.id}`} className="custom-callout"> |
| | | { |
| | |
| | | {shouldShowWaitingRadar && waitingRadarVisible && ( |
| | | <Image className="waiting-radar-img" src={waitingRadarSrc} webp={true} style={waitingRadarStyle} /> |
| | | )} |
| | | {isAndroid && carCalloutVisible && ( |
| | | <View className="car-callout-android" style={carCalloutStyle}> |
| | | <View className="custom-callout-content" style={{ padding: '6px 10px' }}> |
| | | <View className="car-time" style={{ alignItems: 'flex-end' }}> |
| | | <View className="car-time-num">{routeDistance?.value ?? 0}</View> |
| | | <View style={{ flexShrink: 0 }}>{routeDistance?.unit === 'm' ? '米' : '公里'}</View> |
| | | <View style={{ marginLeft: '2.5px' }} className="car-time-num">{durationDisplay.value}</View> |
| | | <View style={{ flexShrink: 0 }}>{durationDisplay.unit}</View> |
| | | </View> |
| | | </View> |
| | | </View> |
| | | )} |
| | | {/* 返回按钮 */} |
| | | <View onClick={toBack} className="select-city"> |
| | | <Image className="select-city-icon" src={backIcon} /> |