feat(路线选择): 增加地图点击选路和路线气泡展示
- 新增 `saveRoutePreference` API 用于保存路线偏好
- 为 `BeginRouteView` 组件添加 `onMapTap` 属性,支持地图点击选路
- 在 `use-route-manager` 中实现 `handleMapTap` 逻辑,作为 `handlePolylineTap` 的降级方案
- 在路线中点添加路线摘要气泡,显示时长和距离信息
- 在 `release-route` 组件中同步路线偏好选择,并展示"运营免费"标签
- 优化路线索引解析逻辑,提高点击匹配精度
| | |
| | | }, true); |
| | | } |
| | | |
| | | // 导航-设置路线偏好 |
| | | export const saveRoutePreference = async (payload) => { |
| | | return request({ |
| | | url: _ROBOTAXI_HOST + '/itinerary/base/v1/mini-program/navigation/route-preference', |
| | | method: 'POST', |
| | | payload |
| | | }, true); |
| | | } |
| | | |
| | | // 保存订单 |
| | | export const saveOrder = async (payload) => { |
| | | return request({ |
| | |
| | | handleCalloutTap: (e: any) => void; |
| | | handleRegionChange: (e: any) => void; |
| | | onPolylineTap?: (e: any) => void; |
| | | onMapTap?: (e: any) => void; |
| | | renderMapOverlays: () => JSX.Element; |
| | | toBack: () => void; |
| | | backIcon: string; |
| | |
| | | handleCalloutTap, |
| | | handleRegionChange, |
| | | onPolylineTap, |
| | | onMapTap, |
| | | renderMapOverlays, |
| | | toBack, |
| | | backIcon, |
| | |
| | | onCalloutTap={handleCalloutTap} |
| | | onRegionChange={handleRegionChange} |
| | | onPolylineTap={onPolylineTap} |
| | | onTap={onMapTap} |
| | | style={{ width: '100%', height: '87vh' }} |
| | | > |
| | | {renderMapOverlays()} |
| | |
| | | .money-box { |
| | | margin-top: 8px; |
| | | |
| | | .route-operate-free { |
| | | font-size: 28px; |
| | | line-height: 40px; |
| | | color: #F58900; |
| | | } |
| | | |
| | | .route-content-top-money { |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | background: rgba(255, 255, 255, 0.4); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | import selectLineIcon from '../../../assets/select-line.png' |
| | | import trafficSignalIcon from '../../../assets/traffic-signal.png' |
| | | import RoutePreferencePopup, { RoutePreferenceOption } from './route-preference-popup'; |
| | | import { saveRoutePreference, queryRoutePreferences } from '../../../api/begin-route'; |
| | | import { formatDurationDisplay, formatTripDistance } from '../../../../utils/Robotaxi-public-method'; |
| | | |
| | | type Props = { |
| | |
| | | const pendingHeight = useRef<number | null>(null); |
| | | |
| | | const [showPreferencePopup, setShowPreferencePopup] = useState(false); |
| | | const [preference, setPreference] = useState<RoutePreferenceOption>({ label: '智能推荐', value: '智能推荐' }); |
| | | const [preference, setPreference] = useState<RoutePreferenceOption>({ label: '智能推荐', value: '智能推荐', code: '智能推荐' }); |
| | | const [preferenceOptions, setPreferenceOptions] = useState<RoutePreferenceOption[]>([]); |
| | | const openCityId = Number(Taro.getStorageSync('selectedCity')?.id || 0); |
| | | const selectedRoute = routeOptions?.[selectedRouteIndex]; |
| | | const isOperateFree = Number(selectedRoute?.operateFree) === 1; |
| | | |
| | | useEffect(() => { |
| | | let cancelled = false; |
| | | queryRoutePreferences({ openCityId }).then((res: any) => { |
| | | if (cancelled) return; |
| | | const list = Array.isArray(res) ? res : (Array.isArray(res?.list) ? res.list : (Array.isArray(res?.data) ? res.data : [])); |
| | | const normalized = list.map((item: any) => { |
| | | const label = item?.label || item?.name || item?.displayName || item?.preferenceName || item?.value; |
| | | const value = item?.value ?? item?.code ?? item?.preferenceCode ?? label; |
| | | const code = item?.code ?? item?.preferenceCode ?? item?.value ?? label; |
| | | if (!label || value === undefined || value === null) return null; |
| | | return { label: String(label), value: String(value), code: String(code) }; |
| | | }).filter(Boolean) as RoutePreferenceOption[]; |
| | | if (normalized.length > 0) { |
| | | setPreferenceOptions(normalized); |
| | | } |
| | | }).catch(() => {}); |
| | | return () => { cancelled = true; }; |
| | | }, [openCityId]); |
| | | |
| | | // Sync preference label when route selection changes externally (e.g. clicking map or route card) |
| | | useEffect(() => { |
| | | if (routeOptions && routeOptions[selectedRouteIndex]) { |
| | | const currentRoute = routeOptions[selectedRouteIndex]; |
| | | |
| | | // Match label based on preference from API response |
| | | let mappedLabel = '智能推荐'; |
| | | if (preferenceOptions.length > 0 && currentRoute.preference !== undefined && currentRoute.preference !== null) { |
| | | const matchedOption = preferenceOptions.find(opt => String(opt.code) === String(currentRoute.preference)); |
| | | if (matchedOption) { |
| | | mappedLabel = matchedOption.label; |
| | | } else if (currentRoute.routePlanName) { |
| | | // Fallback to routePlanName if preference matching fails |
| | | const name = currentRoute.routePlanName; |
| | | mappedLabel = name.includes('-') ? name.split('-')[1] : name; |
| | | } |
| | | } else if (currentRoute.routePlanName) { |
| | | // Fallback to routePlanName if preference matching fails |
| | | const name = currentRoute.routePlanName; |
| | | mappedLabel = name.includes('-') ? name.split('-')[1] : name; |
| | | } |
| | | |
| | | setPreference(prev => { |
| | | if (prev.label !== mappedLabel) { |
| | | const newCode = currentRoute.preference !== undefined && currentRoute.preference !== null ? String(currentRoute.preference) : mappedLabel; |
| | | return { |
| | | label: mappedLabel, |
| | | value: newCode, |
| | | code: newCode |
| | | }; |
| | | } |
| | | return prev; |
| | | }); |
| | | } |
| | | }, [selectedRouteIndex, routeOptions, preferenceOptions]); |
| | | |
| | | useEffect(() => { |
| | | if (!isRouteSelectExpanded && boxHeight !== null && minHeight.current > 0) { |
| | |
| | | } |
| | | return `${safeMinutes}分钟`; |
| | | } |
| | | const handleSelectPreference = async (item: RoutePreferenceOption) => { |
| | | setPreference(item); |
| | | |
| | | if (routeOptions && routeOptions.length > 0 && handleSelectRoute) { |
| | | const targetIndex = routeOptions.findIndex(route => { |
| | | // Exact match by preference code |
| | | if (route.preference !== undefined && route.preference !== null && String(route.preference) === String(item.code)) { |
| | | return true; |
| | | } |
| | | |
| | | // Fallback match by name |
| | | if (route.routePlanName) { |
| | | const name = route.routePlanName; |
| | | const mappedLabel = name.includes('-') ? name.split('-')[1] : name; |
| | | if (mappedLabel === item.label) { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | }); |
| | | |
| | | if (targetIndex !== -1) { |
| | | handleSelectRoute(targetIndex); |
| | | } |
| | | } |
| | | |
| | | try { |
| | | await saveRoutePreference({ |
| | | code: item.code |
| | | }); |
| | | } catch (error) { |
| | | // keep local selection state to avoid interrupting the popup interaction |
| | | } |
| | | } |
| | | |
| | | return ( |
| | | <View className="release-route-container"> |
| | |
| | | </View> |
| | | <View className="confirm-btn-box"> |
| | | <View className="money-box"> |
| | | <View className="route-content-top-money" onClick={() => toPriceDescription()} > |
| | | <Image className="tip-icon" src={tipDefaultIcon} /> |
| | | 预估 |
| | | <Text className="money-num">{fareEstimate.finalFareStr || '-'}</Text> |
| | | 元 |
| | | </View> |
| | | {isOperateFree ? ( |
| | | <View className="route-operate-free">运营免费</View> |
| | | ) : ( |
| | | <View className="route-content-top-money" onClick={() => toPriceDescription()} > |
| | | <Image className="tip-icon" src={tipDefaultIcon} /> |
| | | 预估 |
| | | <Text className="money-num">{fareEstimate.finalFareStr || '-'}</Text> |
| | | 元 |
| | | </View> |
| | | )} |
| | | <View className="route-content-bottom-right"> |
| | | { |
| | | fareEstimate.discountAmount > 0 && |
| | |
| | | value={preference.value} |
| | | openCityId={openCityId} |
| | | onClose={() => setShowPreferencePopup(false)} |
| | | onSelect={(item) => setPreference(item)} |
| | | onSelect={handleSelectPreference} |
| | | /> |
| | | </View> |
| | | ) |
| | |
| | | export type RoutePreferenceOption = { |
| | | label: string; |
| | | value: string; |
| | | code: string; |
| | | }; |
| | | |
| | | type Props = { |
| | |
| | | }; |
| | | |
| | | const DEFAULT_OPTIONS: RoutePreferenceOption[] = [ |
| | | { label: '智能推荐', value: '智能推荐' }, |
| | | { label: '时间优先', value: '时间优先' }, |
| | | { label: '低价优先', value: '低价优先' } |
| | | { label: '智能推荐', value: '智能推荐', code: '智能推荐' }, |
| | | { label: '时间优先', value: '时间优先', code: '时间优先' }, |
| | | { label: '低价优先', value: '低价优先', code: '低价优先' } |
| | | ]; |
| | | |
| | | const normalizeOptions = (res: any): RoutePreferenceOption[] => { |
| | |
| | | const normalized = list.map((item: any) => { |
| | | const label = item?.label || item?.name || item?.displayName || item?.preferenceName || item?.value; |
| | | const value = item?.value ?? item?.code ?? item?.preferenceCode ?? label; |
| | | const code = item?.code ?? item?.preferenceCode ?? item?.value ?? label; |
| | | if (!label || value === undefined || value === null) { |
| | | return null; |
| | | } |
| | | return { |
| | | label: String(label), |
| | | value: String(value) |
| | | value: String(value), |
| | | code: String(code) |
| | | }; |
| | | }).filter(Boolean) as RoutePreferenceOption[]; |
| | | |
| | |
| | | startSite: any; |
| | | /** 费用信息 */ |
| | | fareEstimate: any; |
| | | /** 路线备选项 */ |
| | | routeOptions?: any[]; |
| | | /** 选中的路线索引 */ |
| | | selectedRouteIndex?: number; |
| | | /** 路线错误标记 */ |
| | | routeAndFareError: boolean; |
| | | /** 下单提交中 */ |
| | |
| | | xpElectronicFenceAreaId: s?.electronicAreaId || deps.endSite?.electronicAreaId || '', |
| | | estimatedTime |
| | | }; |
| | | |
| | | // Add selectedRouteId if available |
| | | if (deps.routeOptions && typeof deps.selectedRouteIndex === 'number') { |
| | | const selectedRoute = deps.routeOptions[deps.selectedRouteIndex]; |
| | | if (selectedRoute?.route?.routeID) { |
| | | payload.selectedRouteId = selectedRoute.route.routeID; |
| | | } |
| | | } |
| | | |
| | | if (cachedCouponId) payload.userCouponId = cachedCouponId; |
| | | const res = await saveOrder(payload); |
| | | const status_code = res?.orderId ? 1 : 0; |
| | |
| | | newEndLng = routeData.destination.longitude ?? newEndLng; |
| | | } |
| | | |
| | | if (newStartLat && newStartLng && typeof deps.setStartSite === 'function') { |
| | | if (Number.isFinite(newStartLat) && Number.isFinite(newStartLng) && typeof deps.setStartSite === 'function') { |
| | | deps.setStartSite({ |
| | | ...deps.startSite, |
| | | latitude: newStartLat, |
| | |
| | | }); |
| | | } |
| | | |
| | | if (newEndLat && newEndLng && typeof deps.setEndSite === 'function') { |
| | | if (Number.isFinite(newEndLat) && Number.isFinite(newEndLng) && typeof deps.setEndSite === 'function') { |
| | | deps.setEndSite({ |
| | | ...deps.endSite, |
| | | latitude: newEndLat, |
| | |
| | | } |
| | | }; |
| | | |
| | | // 处理地图上的 polyline 点击事件 |
| | | const handlePolylineTap = (e: any) => { |
| | | console.log('handlePolylineTap/////////////', e); |
| | | const resolveRouteIndexByTap = (e: any) => { |
| | | // 确保当前处于发单状态(有路线选项),并且有路线可供选择 |
| | | if (deps.tripStatusRef.current !== 1 || allRoutePoints.length <= 1) return; |
| | | if (deps.tripStatusRef.current !== 1 || allRoutePoints.length <= 1) return -1; |
| | | |
| | | // 优先使用 polylineId;如果运行时未返回 id,则退化为用点击坐标匹配最近路线 |
| | | let targetIndex = -1; |
| | |
| | | if (targetIndex === -1) { |
| | | const tapLatitude = Number(e?.detail?.latitude ?? e?.latitude); |
| | | const tapLongitude = Number(e?.detail?.longitude ?? e?.longitude); |
| | | if (!Number.isFinite(tapLatitude) || !Number.isFinite(tapLongitude)) return; |
| | | if (!Number.isFinite(tapLatitude) || !Number.isFinite(tapLongitude)) return -1; |
| | | |
| | | let nearestDistance = Infinity; |
| | | allRoutePoints.forEach((routeLine, index) => { |
| | |
| | | }); |
| | | }); |
| | | |
| | | if (nearestDistance > 80) return; |
| | | if (nearestDistance > 120) return -1; |
| | | } |
| | | |
| | | return targetIndex; |
| | | }; |
| | | |
| | | // 处理地图上的 polyline 点击事件 |
| | | const handlePolylineTap = (e: any) => { |
| | | const targetIndex = resolveRouteIndexByTap(e); |
| | | if (targetIndex >= 0 && targetIndex < allRoutePoints.length) { |
| | | handleSelectRoute(targetIndex); |
| | | } |
| | | }; |
| | | |
| | | // 某些端上浅色路线不会触发 polyline tap,退化为地图点击后按最近路线匹配 |
| | | const handleMapTap = (e: any) => { |
| | | const targetIndex = resolveRouteIndexByTap(e); |
| | | if (targetIndex >= 0 && targetIndex < allRoutePoints.length) { |
| | | handleSelectRoute(targetIndex); |
| | | } |
| | |
| | | points: reducePoints(points, deps.isAndroid ? 120 : 200), |
| | | color: '#C2CAF1', |
| | | width: 10, |
| | | clickable: true, |
| | | dottedLine: false, |
| | | arrowLine: true, |
| | | }); |
| | |
| | | points: displayRoutePoints, // 选中的路线使用已经降采样处理过的点 |
| | | color: '#7B91FF', |
| | | width: 10, |
| | | clickable: true, |
| | | dottedLine: false, |
| | | arrowLine: true, |
| | | }); |
| | |
| | | points: displayRoutePoints, |
| | | color: '#7B91FF', |
| | | width: 10, |
| | | clickable: true, |
| | | dottedLine: false, |
| | | arrowLine: true, |
| | | }); |
| | |
| | | selectedRouteIndex, |
| | | handleSelectRoute, |
| | | handlePolylineTap, |
| | | handleMapTap, |
| | | fareEstimate, |
| | | setFareEstimate, |
| | | routeAndFareError, |
| | |
| | | export default function useViewDelegates(deps: Deps) { |
| | | /** 时长文案 */ |
| | | const durationDisplay = formatDurationDisplay(deps.routeDuration); |
| | | const routeSummaryText = deps.routeDistance && deps.routeDuration !== null |
| | | ? `${durationDisplay.value}${durationDisplay.unit} ${deps.routeDistance.value}${deps.routeDistance.unit === 'm' ? '米' : '公里'}` |
| | | : ''; |
| | | |
| | | /** |
| | | * 渲染等待态气泡。 |
| | |
| | | */ |
| | | const renderDefaultCallout = (item: any, calloutKey: string) => ( |
| | | <CoverView slot='callout' marker-id={item.id} key={calloutKey} className='custom-callout'> |
| | | {(item.name !== '车辆位置' && !(item.id === 2 && [7].includes(deps.tripStatus))) && ( |
| | | <CoverView className={`custom-callout-content ${deps.isAndroid && 'txt-center '}`} style={{ zIndex: 1 }}> |
| | | <CoverView style={{ lineHeight: '22px', display: 'flex', alignItems: 'center' }}>{item.name}</CoverView> |
| | | {deps.isAndroid && <CoverView style={{ width: '2px' }}></CoverView>} |
| | | {deps.tripStatus == 1 && <CoverImage className='custom-callout-icon' src={deps.customCalloutIconSrc} />} |
| | | </CoverView> |
| | | )} |
| | | {(item.name === '车辆位置' && [4, 6].includes(deps.tripStatus) && !deps.isAndroid) && ( |
| | | <CoverView className='custom-callout-content' style={{ padding: '6px 10px', minWidth: '40px', justifyContent: 'center', zIndex: 2 }}> |
| | | <CoverView className='car-time'> |
| | | <CoverView className='car-time-num'>{deps.routeDistance?.value ?? 0}</CoverView> |
| | | <CoverView>{deps.routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView> |
| | | <CoverView style={{ marginLeft: '2.5px' }} className='car-time-num'>{durationDisplay.value}</CoverView> |
| | | <CoverView>{durationDisplay.unit}</CoverView> |
| | | {item.isRouteSummaryBubble ? ( |
| | | routeSummaryText ? ( |
| | | <CoverView className='route-summary-callout'> |
| | | <CoverView className='route-summary-callout-bubble'> |
| | | {routeSummaryText} |
| | | </CoverView> |
| | | <CoverView className='route-summary-callout-arrow' /> |
| | | </CoverView> |
| | | </CoverView> |
| | | )} |
| | | {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid && deps.mapScale >= 20) && ( |
| | | <CoverView className='custom-callout-content'> |
| | | 已到达 |
| | | </CoverView> |
| | | ) : null |
| | | ) : ( |
| | | <> |
| | | {(item.name !== '车辆位置' && !(item.id === 2 && [7].includes(deps.tripStatus))) && ( |
| | | <CoverView className={`custom-callout-content ${deps.isAndroid && 'txt-center '}`} style={{ zIndex: 1 }}> |
| | | <CoverView style={{ lineHeight: '22px', display: 'flex', alignItems: 'center' }}>{item.name}</CoverView> |
| | | {deps.isAndroid && <CoverView style={{ width: '2px' }}></CoverView>} |
| | | {deps.tripStatus == 1 && <CoverImage className='custom-callout-icon' src={deps.customCalloutIconSrc} />} |
| | | </CoverView> |
| | | )} |
| | | {(item.name === '车辆位置' && [4, 6].includes(deps.tripStatus) && !deps.isAndroid) && ( |
| | | <CoverView className='custom-callout-content' style={{ padding: '6px 10px', minWidth: '40px', justifyContent: 'center', zIndex: 2 }}> |
| | | <CoverView className='car-time'> |
| | | <CoverView className='car-time-num'>{deps.routeDistance?.value ?? 0}</CoverView> |
| | | <CoverView>{deps.routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView> |
| | | <CoverView style={{ marginLeft: '2.5px' }} className='car-time-num'>{durationDisplay.value}</CoverView> |
| | | <CoverView>{durationDisplay.unit}</CoverView> |
| | | </CoverView> |
| | | </CoverView> |
| | | )} |
| | | {(item.id === 2 && [7].includes(deps.tripStatus) && !deps.isAndroid && deps.mapScale >= 20) && ( |
| | | <CoverView className='custom-callout-content'> |
| | | 已到达 |
| | | </CoverView> |
| | | )} |
| | | </> |
| | | )} |
| | | </CoverView> |
| | | ); |
| | |
| | | */ |
| | | const renderMarkerCallout = (item: any, index: number) => { |
| | | const calloutKey = `callout-${item.id}-${item.name || ''}`; |
| | | if (item.isRouteSummaryBubble) return renderDefaultCallout(item, calloutKey); |
| | | const isWaiting = [2, 3].includes(deps.tripStatus) && index === 0; |
| | | if (isWaiting) return renderWaitingCallout(item, calloutKey); |
| | | if (item.name === '车辆位置' && deps.isAndroid && [4, 6].includes(deps.tripStatus)) return null; |
| | |
| | | padding: 8px 20px; |
| | | } |
| | | |
| | | .route-summary-callout { |
| | | position: relative; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding-bottom: 12rpx; |
| | | } |
| | | |
| | | .route-summary-callout-bubble { |
| | | position: relative; |
| | | z-index: 2; |
| | | white-space: nowrap; |
| | | border-radius: 10rpx; |
| | | background: rgba(0, 0, 0, 0.6); |
| | | font-size: 20rpx; |
| | | font-weight: 500; |
| | | color: #FFFFFF; |
| | | line-height: 28rpx; |
| | | padding: 10rpx 12rpx 9rpx 14rpx; |
| | | } |
| | | |
| | | .route-summary-callout-arrow { |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: 2rpx; |
| | | width: 0; |
| | | height: 0; |
| | | margin-left: -8rpx; |
| | | background: transparent; |
| | | border-style: solid; |
| | | border-width: 10rpx 8rpx 0 8rpx; |
| | | border-color: rgba(0, 0, 0, 0.6) transparent transparent transparent; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .txt-center { |
| | | text-align: center; |
| | | } |
| | |
| | | import walkIcon from '../../../robotaxi/assets/walk-icon.png' |
| | | import { _ROBOTAXI_WS_HOST } from '../../../constants/api'; |
| | | |
| | | const ROUTE_SUMMARY_MARKER_ID = 999001; |
| | | |
| | | export default function BeginRoute() { |
| | | const WAITING_TIMER_START_KEY = 'waitingTimerStartAt'; // 等待计时开始时间 |
| | | const WAITING_TIMER_ELAPSED_KEY = 'waitingTimerElapsed'; // 等待计时已过秒数 |
| | |
| | | selectedRouteIndex, |
| | | handleSelectRoute, |
| | | handlePolylineTap, |
| | | handleMapTap, |
| | | fareEstimate, |
| | | routeAndFareError, |
| | | routeDistance, |
| | |
| | | requestOrderRoute: routeRequestOrderRoute, |
| | | updateWalkingRouteForStart: routeUpdateWalkingRouteForStart |
| | | } = routeState; // 路线状态解构 |
| | | const routeSummaryMarker = useMemo(() => { |
| | | if (tripStatus !== 1 || !routeDistance || routeDuration === null || !Array.isArray(routePoints) || routePoints.length === 0) { |
| | | return null; |
| | | } |
| | | const midpoint = routePoints[Math.max(0, Math.floor(routePoints.length / 2))]; |
| | | if (!midpoint?.latitude || !midpoint?.longitude) { |
| | | return null; |
| | | } |
| | | return { |
| | | id: ROUTE_SUMMARY_MARKER_ID, |
| | | latitude: midpoint.latitude, |
| | | longitude: midpoint.longitude, |
| | | iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/marker-car.png', |
| | | width: 1, |
| | | height: 1, |
| | | alpha: 0, |
| | | zIndex: 3, |
| | | isRouteSummaryBubble: true, |
| | | customCallout: { anchorY: 0, anchorX: 0, display: 'ALWAYS' } |
| | | }; |
| | | }, [tripStatus, routeDistance, routeDuration, routePoints]); |
| | | const markersWithRouteSummary = useMemo(() => { |
| | | if (!routeSummaryMarker) return markers; |
| | | return [...markers, routeSummaryMarker]; |
| | | }, [markers, routeSummaryMarker]); |
| | | const viewportState = useMapViewport({ |
| | | tripStatus, |
| | | statusRule, |
| | |
| | | endSite, |
| | | startSite, |
| | | fareEstimate, |
| | | routeOptions, |
| | | selectedRouteIndex, |
| | | routeAndFareError, |
| | | callSubmitting, |
| | | cancelType, |
| | |
| | | isAndroid, |
| | | tripStatus, |
| | | mapScale, |
| | | markers, |
| | | markers: markersWithRouteSummary, |
| | | isRouteSelectExpanded, |
| | | onToggleRouteSelect: setIsRouteSelectExpanded, |
| | | shouldShowWaitingRadar, |
| | |
| | | trunkStatus, |
| | | routeOptions, |
| | | selectedRouteIndex, |
| | | handleSelectRoute |
| | | handleSelectRoute, |
| | | handleMapTap |
| | | }); // 视图渲染委托 |
| | | const mapLongitude = longitude ?? 116.397128; |
| | | const mapLatitude = latitude ?? 39.916527; |
| | |
| | | mapLongitude={mapLongitude} |
| | | mapLatitude={mapLatitude} |
| | | mapScale={mapScale} |
| | | markers={markers} |
| | | markers={markersWithRouteSummary} |
| | | polyline={polyline} |
| | | showLocation={!!longitude && showLocationFlag} |
| | | handleCalloutTap={handleCalloutTap} |
| | | handleRegionChange={handleRegionChange} |
| | | onPolylineTap={handlePolylineTap} |
| | | onMapTap={handleMapTap} |
| | | renderMapOverlays={renderMapOverlays} |
| | | toBack={toBack} |
| | | backIcon={backIcon} |