feat(robotaxi): 更新订单列表、路线偏好和温度显示
- 在订单列表页添加“去支付”按钮并优化状态重置逻辑
- 重构路线偏好弹窗,使用新的API数据格式并调整z-index
- 修复温度显示单位重复问题(移除多余的°符号)
- 优化修改目的地组件,修正起点站点显示
- 调整路线摘要气泡样式,使用图片背景并更新定位
- 修复优惠活动显示条件,仅当优惠金额大于0时展示
- 优化接驾阶段路线拉取逻辑,避免误渲染行程中路线
| | |
| | | /> |
| | | ) |
| | | } |
| | | {/* 是否登录 */} |
| | | {/* 是否展示登录入口 */} |
| | | { |
| | | isLogin && |
| | | <View className={`login-bar ${isLogin ? '' : 'hide'}`}> |
| | |
| | | <Image src={require('../../../../assets/ccc-icon-right.png')} /> |
| | | </View> |
| | | { |
| | | (fareBreakdown?.operateFree != 1 && [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) && |
| | | (fareBreakdown?.operateFree != 1 && [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) && Number(fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney) > 0 && |
| | | <View className="car-card-end-through"> |
| | | <View className="car-card-end-through-label">优惠活动</View> |
| | | <View className="car-card-end-through-content" onClick={onOpenCouponSelect}> |
| | |
| | | } |
| | | </View> |
| | | <View className="car-card-temp-num-text"> |
| | | <View className="car-card-temp-num-text-temp color-000-25">{typeof tempStatus === 'number' ? `${tempStatus.toFixed(1)}°℃` : '--'}</View> |
| | | <View className="car-card-temp-num-text-temp color-000-25">{typeof tempStatus === 'number' ? `${tempStatus.toFixed(1)}℃` : '--'}</View> |
| | | <View className="car-card-temp-num-text-desc color-000-25">设定温度</View> |
| | | </View> |
| | | <View className="car-card-temp-num-btn"> |
| | |
| | | import tipDefaultIcon from '../../../assets/tip-default-icon.png'; |
| | | |
| | | type Props = { |
| | | previousEndSite: any; |
| | | startSite: any; |
| | | nextEndSite: any; |
| | | previousFareEstimate?: any; |
| | | fareEstimate: any; |
| | |
| | | }; |
| | | |
| | | export default function ModifyDestinationReview({ |
| | | previousEndSite, |
| | | startSite, |
| | | nextEndSite, |
| | | previousFareEstimate, |
| | | fareEstimate, |
| | |
| | | <View className='modify-destination-review-sites'> |
| | | <View className='modify-destination-review-site-item'> |
| | | <View className='modify-destination-review-site-dot modify-destination-review-site-dot-old' /> |
| | | <View className='modify-destination-review-site-text start-txt'>{previousEndSite?.name || '-'}</View> |
| | | <View className='modify-destination-review-site-text start-txt'>{startSite?.name || '-'}</View> |
| | | </View> |
| | | <View |
| | | className='modify-destination-review-site-item modify-destination-review-site-item-active' |
| | |
| | | width: 100%; |
| | | height: 100%; |
| | | background: rgba(0, 0, 0, 0.4); |
| | | z-index: 1; |
| | | z-index: 102; |
| | | } |
| | | |
| | | .popup-content { |
| | | position: relative; |
| | | z-index: 2; |
| | | z-index: 103; |
| | | background: #FFFFFF; |
| | | border-radius: 40px 40px 0 0; |
| | | width: 100%; |
| | |
| | | onSelect: (item: RoutePreferenceOption) => void; |
| | | }; |
| | | |
| | | const DEFAULT_OPTIONS: RoutePreferenceOption[] = [ |
| | | { label: '智能推荐', value: '智能推荐', code: '智能推荐' }, |
| | | { label: '时间优先', value: '时间优先', code: '时间优先' }, |
| | | { label: '低价优先', value: '低价优先', code: '低价优先' } |
| | | ]; |
| | | const DEFAULT_OPTIONS: RoutePreferenceOption[] = []; |
| | | |
| | | const normalizeOptions = (res: any): RoutePreferenceOption[] => { |
| | | const list = Array.isArray(res) |
| | | ? res |
| | | : Array.isArray(res?.list) |
| | | ? res.list |
| | | : Array.isArray(res?.data) |
| | | ? res.data |
| | | : Array.isArray(res?.routePreferences) |
| | | ? res.routePreferences |
| | | : Array.isArray(res?.preferences) |
| | | ? res.preferences |
| | | : []; |
| | | const list = Array.isArray(res) ? res : []; |
| | | |
| | | 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; |
| | | const label = item?.name; |
| | | const value = item?.amapStrategy; |
| | | const code = item?.code; |
| | | if (!label || value === undefined || value === null) { |
| | | return null; |
| | | } |
| | |
| | | <Image className='close-icon' src={closeIcon} onClick={onClose} /> |
| | | </View> |
| | | <View className='popup-list-wrap'> |
| | | <View className='popup-list-inner'> |
| | | {options.map((item, index) => { |
| | | const isSelected = item.value === value; |
| | | return ( |
| | | <View |
| | | key={item.value || index} |
| | | className={`popup-item ${isSelected ? 'active' : ''}`} |
| | | onClick={() => { |
| | | onSelect(item); |
| | | onClose(); |
| | | }} |
| | | > |
| | | <View className='popup-name'>{item.label}</View> |
| | | {isSelected && ( |
| | | <Image className='select-icon' src={selectTypeLineIcon} /> |
| | | )} |
| | | </View> |
| | | ); |
| | | })} |
| | | </View> |
| | | <View className='popup-list-inner'> |
| | | {options.map((item, index) => { |
| | | const isSelected = item.value === value; |
| | | return ( |
| | | <View |
| | | key={item.value || index} |
| | | className={`popup-item ${isSelected ? 'active' : ''}`} |
| | | onClick={() => { |
| | | onSelect(item); |
| | | onClose(); |
| | | }} |
| | | > |
| | | <View className='popup-name'>{item.label}</View> |
| | | {isSelected && ( |
| | | <Image className='select-icon' src={selectTypeLineIcon} /> |
| | | )} |
| | | </View> |
| | | ); |
| | | })} |
| | | </View> |
| | | </View> |
| | | </View> |
| | | </View> |
| | |
| | | selectedRouteIndex = 0, |
| | | handleSelectRoute |
| | | }: Props) { |
| | | const DEFAULT_PREFERENCE: RoutePreferenceOption = { label: '智能推荐', value: 'default', code: 'default' }; // 路线偏好默认值,避免初始渲染空指针 |
| | | const [showPreferencePopup, setShowPreferencePopup] = useState(false); |
| | | const [preference, setPreference] = useState<RoutePreferenceOption>({ |
| | | label: '智能推荐', |
| | | value: '智能推荐', |
| | | code: '智能推荐' |
| | | }); |
| | | const [preference, setPreference] = useState<RoutePreferenceOption>(DEFAULT_PREFERENCE); |
| | | const [preferenceOptions, setPreferenceOptions] = useState<RoutePreferenceOption[]>([]); |
| | | const openCityId = Number(Taro.getStorageSync('selectedCity')?.id || 0); |
| | | |
| | |
| | | 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; |
| | | const label = item?.name; |
| | | const value = item?.amapStrategy; |
| | | const code = item?.code; |
| | | if (!label || value === undefined || value === null) return null; |
| | | return { label: String(label), value: String(value), code: String(code) }; |
| | | }).filter(Boolean) as RoutePreferenceOption[]; |
| | |
| | | } |
| | | |
| | | setPreference(prev => { |
| | | if (prev.label !== mappedLabel) { |
| | | if (prev?.label !== mappedLabel) { |
| | | const newCode = currentRoute.preference !== undefined && currentRoute.preference !== null ? String(currentRoute.preference) : mappedLabel; |
| | | return { |
| | | label: mappedLabel, |
| | |
| | | try { |
| | | await saveRoutePreference({ code: item.code }); |
| | | } catch (error) { |
| | | // Keep local state even if persistence fails. |
| | | } |
| | | }; |
| | | |
| | |
| | | <View className='route-select-type'> |
| | | <View className='route-select-title'>路线选择:</View> |
| | | <View className='route-select-preference' onClick={() => setShowPreferencePopup(true)}> |
| | | <View className='route-select-preference-text'>{preference.label}</View> |
| | | <View className='route-select-preference-text'>{preference?.label || '智能推荐'}</View> |
| | | <Image src={selectLineIcon} /> |
| | | </View> |
| | | </View> |
| | |
| | | key={index} |
| | | onClick={() => handleSelectRoute && handleSelectRoute(index)} |
| | | > |
| | | <View className='route-select-content-item-title'>{item.label || (index === 0 ? '智能推荐' : `路线${index + 1}`)}</View> |
| | | <View className='route-select-content-item-title'>{item.route?.name}</View> |
| | | <View className='route-select-content-item-time'>{time.value}{time.unit}</View> |
| | | <View className='route-select-content-item-distance'> |
| | | <View>{dist}</View> |
| | |
| | | |
| | | <RoutePreferencePopup |
| | | show={showPreferencePopup} |
| | | value={preference.value} |
| | | value={preference?.value} |
| | | openCityId={openCityId} |
| | | onClose={() => setShowPreferencePopup(false)} |
| | | onSelect={handleSelectPreference} |
| | |
| | | import ModifyDestinationReview from '../components/modify-destination-review'; |
| | | import { TripStatus } from '../constants/trip-status'; |
| | | import { formatRemainingDurationDisplay } from '../../../utils/format'; |
| | | import summaryIcon from '../../../assets/summary-icon.png'; |
| | | |
| | | type Deps = { |
| | | /** 是否安卓 */ |
| | |
| | | {item.isRouteSummaryBubble ? ( |
| | | routeSummaryText ? ( |
| | | <CoverView className='route-summary-callout'> |
| | | <CoverView className='route-summary-callout-bubble'> |
| | | <CoverImage className='route-summary-callout-bg' src={summaryIcon} /> |
| | | <CoverView className='route-summary-callout-text'> |
| | | {routeSummaryText} |
| | | </CoverView> |
| | | <CoverView className='route-summary-callout-arrow' /> |
| | | </CoverView> |
| | | ) : null |
| | | ) : item.isBeforeUserSite ? ( |
| | |
| | | if (deps.isModifyDestinationReviewVisible) { |
| | | return ( |
| | | <ModifyDestinationReview |
| | | previousEndSite={deps.previousEndSite} |
| | | startSite={deps.startSite} |
| | | nextEndSite={deps.previewEndSite} |
| | | previousFareEstimate={deps.previousFareEstimate} |
| | | fareEstimate={deps.fareEstimate} |
| | |
| | | |
| | | .origin-box { |
| | | position: absolute; |
| | | bottom: 100%; /* 相对于父级(底部卡片容器)顶部 */ |
| | | margin-bottom: 24px; /* 距离底部卡片24px */ |
| | | bottom: 100%; |
| | | /* 相对于父级(底部卡片容器)顶部 */ |
| | | margin-bottom: 24px; |
| | | /* 距离底部卡片24px */ |
| | | right: 24px; |
| | | z-index: 101; |
| | | z-index: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding-bottom: 12rpx; |
| | | min-width: 169px; |
| | | height: 45px; |
| | | padding-left: 15px; |
| | | padding-right: 8px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .route-summary-callout-bubble { |
| | | .route-summary-callout-bg { |
| | | position: absolute; |
| | | left: 0; |
| | | top: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .route-summary-callout-text { |
| | | position: relative; |
| | | z-index: 2; |
| | | white-space: nowrap; |
| | | border-radius: 10rpx; |
| | | background: rgba(0, 0, 0, 0.6); |
| | | font-size: 20rpx; |
| | | font-size: 20px; |
| | | font-weight: 500; |
| | | line-height: 39px; |
| | | color: #FFFFFF; |
| | | line-height: 47rpx; |
| | | padding: 0 12rpx 0 14rpx; |
| | | } |
| | | |
| | | .route-summary-callout-arrow { |
| | | position: absolute; |
| | | left: 50%; |
| | | bottom: 6rpx; |
| | | width: 12rpx; |
| | | height: 12rpx; |
| | | margin-left: -6rpx; |
| | | background: rgba(0, 0, 0, 0.6); |
| | | transform: rotate(45deg); |
| | | transform-origin: center; |
| | | z-index: 1; |
| | | white-space: nowrap; |
| | | text-align: center; |
| | | } |
| | | |
| | | .txt-center { |
| | |
| | | setRouteOptions([]); |
| | | setRoutePoints([]); |
| | | setFareEstimate({}); |
| | | const shouldSkipOrderRouteSync = [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus); // 接驾阶段不拉取订单路线,避免误渲染行程中路线 |
| | | if (shouldSkipOrderRouteSync) { |
| | | return; |
| | | } |
| | | routeRequestOrderRoute(orderId, tripStatus); |
| | | } |
| | | }); |
| | |
| | | alpha: 0, |
| | | zIndex: 3, |
| | | isRouteSummaryBubble: true, |
| | | customCallout: { anchorY: 0, anchorX: 0, display: 'ALWAYS' } |
| | | customCallout: { anchorY: -0.1, anchorX: 50, display: 'ALWAYS' } |
| | | }; |
| | | }, [allowSelectableRoutes, routeDistance, routeDuration, routePoints]); |
| | | const viewportState = useMapViewport({ |
| | |
| | | ); |
| | | const couponOrderMoney = Number(fareEstimate?.originalFare || fareEstimate?.finalFare || 0); // 优惠券可用性计算订单金额 |
| | | const showWalkNavigationButton = useMemo(() => { |
| | | if (isModifyDestinationReviewVisible) { |
| | | return false; |
| | | } |
| | | if (![TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup].includes(tripStatus)) { |
| | | return false; |
| | | } |
| | |
| | | } |
| | | const walkingDistance = getDistance(currentLongitude, currentLatitude, startLongitude, startLatitude); |
| | | return Number.isFinite(walkingDistance) && walkingDistance >= walkingNavigationDistance; |
| | | }, [tripStatus, startSite?.latitude, startSite?.longitude, latitude, longitude, walkingNavigationDistance]); |
| | | }, [isModifyDestinationReviewVisible, tripStatus, startSite?.latitude, startSite?.longitude, latitude, longitude, walkingNavigationDistance]); |
| | | const { renderMapOverlays, renderTripCard } = useViewDelegates({ |
| | | isAndroid, |
| | | tripStatus, |
| | |
| | | right: 30px; |
| | | } |
| | | |
| | | .order-pay-box-position { |
| | | position: absolute; |
| | | padding: 10px; |
| | | bottom: 14px; |
| | | right: 194px; |
| | | } |
| | | |
| | | .order-again-box { |
| | | margin-top: 4px; |
| | | display: flex; |
| | |
| | | color: #181916; |
| | | } |
| | | |
| | | .order-pay-text { |
| | | width: 144px; |
| | | z-index: 0; |
| | | border-radius: 3112px; |
| | | box-sizing: border-box; |
| | | border: 2px solid #181916; |
| | | font-family: PingFang SC; |
| | | font-size: 24px; |
| | | font-weight: normal; |
| | | line-height: 48px; |
| | | text-align: center; |
| | | letter-spacing: normal; |
| | | color: #181916; |
| | | } |
| | | |
| | | } |
| | | |
| | | } |
| | |
| | | } |
| | | |
| | | useEffect(() => { |
| | | setGroups([]); |
| | | setPageNum(1); |
| | | setHasMore(true); |
| | | // 切换筛选条件后重置列表并重新拉取第一页。 |
| | |
| | | <View className='order-again-text' hoverClass='common-hover-class'>再来一单</View> |
| | | </View> |
| | | } |
| | | { |
| | | formatOrderStatus(item.status) == '待支付' && |
| | | <View className='order-pay-box-position' onClick={(e) => e.stopPropagation()}> |
| | | <View className='order-pay-text' hoverClass='common-hover-class'>去支付</View> |
| | | </View> |
| | | } |
| | | </View> |
| | | })} |
| | | </View>) |