Merge branch 'feature_v2.6.0_fuqj' into 'feature_2.6.0'
抽离站点名称组件、首页和站点选择页,指针雷达tooltip三者的衔接动效逻辑 、其他UI改动
See merge request robotaxi1/xp-robotaxi-miniprogram!263
| | |
| | | |
| | | return ( |
| | | <View className="pointer-box"> |
| | | {/* 雷达只在真正命中吸附、指针跳动到具体站点时才展示(由 bounce 驱动),不随真实请求态 nearbyLoading 提前出现 */} |
| | | { |
| | | props.nearbyLoading && props.isAndroid && ( |
| | | props.bounce && props.isAndroid && ( |
| | | <Image className="waiting-radar-img" src={props.waitingRadarSrc} webp /> |
| | | ) |
| | | } |
| | |
| | | ) |
| | | } |
| | | { |
| | | props.nearbyLoading && !props.isAndroid && ( |
| | | props.bounce && !props.isAndroid && ( |
| | | <Image className="waiting-radar" src={props.waitingRadarSrc} /> |
| | | ) |
| | | } |
| | |
| | | /** 首页订单状态文案映射。 */ |
| | | export const HOME_ORDER_STATUS: Partial<Record<TripStatus, string>> = { |
| | | [TripStatus.VehicleApproaching]: '车辆正在赶来', |
| | | [TripStatus.VehicleArrivedPickup]: '车辆已到达上车点,请尽快前往', |
| | | [TripStatus.VehicleArrivedPickup]: '车辆已到达上车点', |
| | | [TripStatus.InService]: '正在前往目的地', |
| | | [TripStatus.ArrivedDestination]: '车辆已到达目的地,请尽快结束行程', |
| | | [TripStatus.ArrivedDestination]: '车辆已到达目的地', |
| | | [TripStatus.TripFinished]: '行程已结束,请尽快支付' |
| | | }; |
| | |
| | | writeHomeStartSiteDisplayState |
| | | } from '../../../../robotaxi/hooks/use-home-start-site-display'; |
| | | |
| | | /** 指针跳动到附近站点的动效总时长:指针跳动(600ms)+ 雷达扫描确认(320ms),需与 index.less 中对应动画时长保持一致。 */ |
| | | const POINTER_BOUNCE_SEQUENCE_DURATION_MS = 920; |
| | | |
| | | type Deps = { |
| | | mapState: any; |
| | | siteState: any; |
| | |
| | | */ |
| | | const queryPointerByCenter = async (lat: number, lng: number, options?: { allowAdsorb?: boolean }) => { |
| | | const allowAdsorb = options?.allowAdsorb !== false; |
| | | siteState.setBounce(true); |
| | | siteState.setShowToolTip(false); |
| | | try { |
| | | const { isInside, newMarkers, sites } = await mapState.updateMapData(lat, lng, undefined, { commitMarkers: false }); |
| | |
| | | if (allowAdsorb) { |
| | | siteState.setShowToolTip(true); // 运营区外恢复气泡提示 |
| | | } |
| | | siteState.setBounce(false); |
| | | return; |
| | | } |
| | | const nearestCandidates = sites.length > 0 ? sites : newMarkers; |
| | | const { adsorbed, marker } = allowAdsorb |
| | | ? siteState.tryAdsorb(lat, lng, nearestCandidates) |
| | | ? siteState.tryAdsorb(lat, lng, nearestCandidates, { deferShowToolTip: true }) |
| | | : { adsorbed: false, marker: null }; // 仅浏览运营区域时不吸附最近点位 |
| | | commitQueriedMarkers(newMarkers, true, adsorbed); |
| | | const finalLat = adsorbed && marker ? marker.latitude : lat; // 最终纬度 |
| | | const finalLng = adsorbed && marker ? marker.longitude : lng; // 最终经度 |
| | | if (allowAdsorb && adsorbed) { |
| | | // 地图中心点(硬切换)与指针跳动动画在同一时刻触发,避免二者时序脱钩导致的突兀感 |
| | | siteState.setBounce(true); |
| | | } |
| | | if (allowAdsorb) { |
| | | // 仅浏览运营区域时不回写地图中心,避免 Map 组件按旧 scale 重绘导致刚展开的运营区视野被收回 |
| | | mapState.setLatitude(finalLat); |
| | | mapState.setLongitude(finalLng); |
| | | } |
| | | settleRegionResult({ lat: finalLat, lon: finalLng, isInside: true }); |
| | | setTimeout(async () => { |
| | | siteState.setBounce(false); |
| | | if (!adsorbed) { |
| | | await siteState.resolveAddress(lat, lng); |
| | | } |
| | | if (adsorbed) { |
| | | setTimeout(() => { |
| | | siteState.setBounce(false); |
| | | // 跳动 + 雷达动效播放完毕后才展示 tooltip,避免与前两段动效叠加显得突兀 |
| | | siteState.setShowToolTip(true); |
| | | }, POINTER_BOUNCE_SEQUENCE_DURATION_MS); |
| | | } else { |
| | | await siteState.resolveAddress(lat, lng); |
| | | if (!allowAdsorb) { |
| | | siteState.setShowToolTip(false); // 查看运营区域期间隐藏气泡,避免遮挡围栏,拖动地图后自动恢复 |
| | | } |
| | | }, 600); |
| | | } |
| | | } catch (error) { |
| | | siteState.setBounce(false); |
| | | } |
| | | }; |
| | | |
| | | const handleRegionEnd = (causedBy: string, wasReturningToOrigin: boolean) => { |
| | | siteState.setBounce(true); |
| | | const mapCtx = Taro.createMapContext('map'); |
| | | mapCtx.getScale({ |
| | | success: (res) => { |
| | |
| | | return; |
| | | } |
| | | const nearestCandidates = sites.length > 0 ? sites : newMarkers; |
| | | const { adsorbed, marker } = siteState.tryAdsorb(lat, lng, nearestCandidates); |
| | | const { adsorbed, marker } = siteState.tryAdsorb(lat, lng, nearestCandidates, { deferShowToolTip: true }); |
| | | commitQueriedMarkers(newMarkers, true, adsorbed); |
| | | const finalLat = adsorbed && marker ? marker.latitude : lat; // 最终纬度 |
| | | const finalLng = adsorbed && marker ? marker.longitude : lng; // 最终经度 |
| | |
| | | syncManualSelectedStartSite(marker); |
| | | } |
| | | settleRegionResult({ lat: finalLat, lon: finalLng, isInside: true }); |
| | | if ((causedBy === 'gesture' || causedBy === 'drag' || wasReturningToOrigin) && marker) { |
| | | const isGestureSettle = causedBy === 'gesture' || causedBy === 'drag' || wasReturningToOrigin; |
| | | const willPlayBounceSequence = isGestureSettle && adsorbed && !!marker; |
| | | if (willPlayBounceSequence) { |
| | | // 命中吸附且为手势/拖拽结算:地图中心点(硬切换)与指针跳动动画在同一时刻触发,避免二者时序脱钩导致的突兀感 |
| | | siteState.setBounce(true); |
| | | mapCtx.moveToLocation({ |
| | | latitude: marker.latitude, |
| | | longitude: marker.longitude |
| | | }); |
| | | mapState.setLatitude(marker.latitude); |
| | | mapState.setLongitude(marker.longitude); |
| | | setTimeout(() => { |
| | | siteState.setBounce(false); |
| | | // 跳动 + 雷达动效播放完毕后才展示 tooltip,避免与前两段动效叠加显得突兀 |
| | | siteState.setShowToolTip(true); |
| | | }, POINTER_BOUNCE_SEQUENCE_DURATION_MS); |
| | | } else if (adsorbed) { |
| | | // 未触发跳动动效(如吸附命中但非手势导致的静默刷新)时,立即展示 tooltip |
| | | siteState.setShowToolTip(true); |
| | | } else { |
| | | // 未命中吸附:不播放跳动/雷达动效,立即回填地址,避免无意义的等待 |
| | | await siteState.resolveAddress(lat, lng); |
| | | } |
| | | setTimeout(async () => { |
| | | siteState.setBounce(false); |
| | | if (!adsorbed) { |
| | | await siteState.resolveAddress(lat, lng); |
| | | } |
| | | }, 600); |
| | | } catch (error) { |
| | | siteState.setBounce(false); |
| | | } |
| | |
| | | |
| | | /** |
| | | * 统一吸附判断,命中阈值则更新吸附站点并显示提示。 |
| | | * @param options.deferShowToolTip 为 true 时不立即展示 tooltip,由调用方在合适的时机(如指针跳动动效结束后)自行调用 setShowToolTip(true)。 |
| | | */ |
| | | const tryAdsorb = (lat: number, lng: number, candidates: any[]) => { |
| | | const tryAdsorb = (lat: number, lng: number, candidates: any[], options?: { deferShowToolTip?: boolean }) => { |
| | | const threshold = adsorptionDistance || 1000; |
| | | const { targetMarker, minDistance } = findNearestMarker(lat, lng, candidates); |
| | | if (minDistance < threshold && targetMarker) { |
| | | setIsAdsorbed(true); |
| | | setCurrentSite(targetMarker); |
| | | setShowToolTip(true); |
| | | if (!options?.deferShowToolTip) { |
| | | setShowToolTip(true); |
| | | } |
| | | Taro.setStorageSync('startSite', targetMarker); |
| | | writeConfirmRideSiteSourceType('start', RECOMMENDED_CONFIRM_RIDE_SOURCE_TYPE); |
| | | return { adsorbed: true, marker: targetMarker }; |
| | |
| | | .order-status { |
| | | position: relative; |
| | | border-radius: 24px; |
| | | background-color: #141E27; |
| | | background-color: @black-text-main; |
| | | background-image: url("https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/order-card-homepage-20260724.png"); |
| | | background-size: cover; |
| | | box-sizing: border-box; |
| | |
| | | font-size: 24px; |
| | | font-weight: 500; |
| | | letter-spacing: normal; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | color: @black-text-main; |
| | | border-radius: 200px; |
| | | background: #ECF0F5; |
| | | flex-shrink: 0; |
| | | flex-grow: 0; |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | .coupon-message-toast { |
| | | position: fixed; |
| | | top: 370px; |
| | | right: 144px; |
| | | top: 393px; |
| | | right: 124px; |
| | | z-index: 1000012; |
| | | max-width: 560px; |
| | | min-height: 64px; |
| | |
| | | .toolTip { |
| | | position: relative; |
| | | z-index: 10; |
| | | animation: tooltip-fade-in 240ms ease-out; |
| | | will-change: transform, opacity; |
| | | |
| | | .toolTip-bg { |
| | | background-image: url("https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/tooltip-bg-20260715.png"); |
| | |
| | | width: 64px; |
| | | height: 86px; |
| | | margin-top: -54px; |
| | | opacity: 1; |
| | | transition: opacity 240ms ease-out; |
| | | |
| | | &.bounce { |
| | | animation: pointer-bounce 600ms ease-in-out; |
| | |
| | | opacity: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .waiting-radar { |
| | | position: fixed; |
| | | width: 700px; |
| | | height: 350px; |
| | | top: 48%; |
| | | left: 50%; |
| | | transform: translate(-50%, -48%); |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | } |
| | | // 雷达以站点位置(.pointer-box 底边,即 translate(-50%,-100%) 后与地图中心点对齐的边)为圆心展开, |
| | | // 用绝对定位直接锚定该边,不受内部 flex 子元素排列影响 |
| | | .waiting-radar { |
| | | position: absolute; |
| | | width: 700px; |
| | | height: 350px; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translate(-50%, 50%); |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | animation: radar-fade-in 200ms ease-out; |
| | | } |
| | | |
| | | .waiting-radar-img { |
| | | position: fixed; |
| | | width: 700px; |
| | | height: 350px; |
| | | top: 48%; |
| | | left: 50%; |
| | | transform: translate(-50%, -48%); |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | .waiting-radar-img { |
| | | position: absolute; |
| | | width: 700px; |
| | | height: 350px; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translate(-50%, 50%); |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | animation: radar-fade-in 200ms ease-out; |
| | | } |
| | | } |
| | | |
| | | @keyframes pointer-bounce { |
| | |
| | | } |
| | | } |
| | | |
| | | @keyframes tooltip-fade-in { |
| | | 0% { |
| | | opacity: 0; |
| | | transform: translateY(6px); |
| | | } |
| | | |
| | | 100% { |
| | | opacity: 1; |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | |
| | | @keyframes radar-fade-in { |
| | | 0% { |
| | | opacity: 0; |
| | | } |
| | | |
| | | 100% { |
| | | opacity: 1; |
| | | } |
| | | } |
| | | |
| | | .home-container { |
| | | display: flex; |
| | | flex-direction: column; |
| New file |
| | |
| | | // SiteRouteCard 公共样式:抽离自 cancel-site-box / calling-car-site-box / share-site-box / |
| | | // modify-destination-review-select-site,与 release-be-being 的 route-card 结构对齐。 |
| | | // 各业务方通过外层 className(如 cancel-site-box)叠加背景/内边距等容器级样式, |
| | | // 组件自身只负责 item/marker/dot/check/line/divider/text 的结构样式。 |
| | | @import '../../style/ui-variables.less'; |
| | | |
| | | .site-route-card { |
| | | .site-route-card-item { |
| | | position: relative; |
| | | |
| | | display: flex; |
| | | align-items: flex-start; |
| | | gap: 16px; |
| | | |
| | | &.site-route-card-item-first { |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | // 虚线连接线:以 item 盒子为基准,用 top/bottom 锚定首尾 marker 中心, |
| | | // 文字换行导致 item 变高时自动拉伸,无需固定高度。 |
| | | .site-route-card-item-line { |
| | | position: absolute; |
| | | left: 14px; |
| | | top: 40px; |
| | | bottom: -8px; |
| | | transform: translateX(-50%); |
| | | width: 0; |
| | | border-left: 2px dashed #ccd0d7; |
| | | } |
| | | |
| | | // 两项之间的水平分割线:与文字列左对齐,居中于行间距内。 |
| | | .site-route-card-item-divider { |
| | | position: absolute; |
| | | left: 48px; |
| | | right: 0; |
| | | top: 88px; |
| | | height: 0; |
| | | border-top: 1px solid rgba(204, 208, 215, 0.25); |
| | | } |
| | | |
| | | .site-route-card-item-marker { |
| | | position: relative; |
| | | flex-shrink: 0; |
| | | width: 28px; |
| | | height: 44px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .site-route-card-item-dot { |
| | | width: 16px; |
| | | height: 16px; |
| | | border-radius: 50%; |
| | | background: @black-text-main; |
| | | |
| | | &.site-route-card-item-dot-first { |
| | | background: @gold-light; |
| | | } |
| | | } |
| | | |
| | | .site-route-card-item-check { |
| | | width: 28px; |
| | | height: 28px; |
| | | } |
| | | } |
| | | |
| | | .site-route-card-item-row { |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .site-route-card-item-text { |
| | | flex: 1; |
| | | min-width: 0; |
| | | font-size: 32px; |
| | | font-weight: 500; |
| | | line-height: 48px; |
| | | letter-spacing: normal; |
| | | color: @black-text-main; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | import { View, Image } from '@tarojs/components'; |
| | | import './index.less'; |
| | | |
| | | export type SiteRouteCardItem = { |
| | | /** 站点名称 */ |
| | | name: React.ReactNode; |
| | | /** 名称文案的额外 className,用于置灰等场景 */ |
| | | nameClassName?: string; |
| | | /** 右侧扩展内容(如时间文案),仅在需要时传入 */ |
| | | extra?: React.ReactNode; |
| | | /** 该项点击事件(如终点可点击编辑) */ |
| | | onClick?: () => void; |
| | | }; |
| | | |
| | | type Props = { |
| | | /** 站点列表,通常为 [起点, 终点] 两项 */ |
| | | sites: SiteRouteCardItem[]; |
| | | /** marker 类型:dot 为纯圆点,check 为图标(需配合 checkIconUrls 传入图标地址) */ |
| | | markerType?: 'dot' | 'check'; |
| | | /** markerType 为 check 时,按 sites 顺序传入对应图标地址 */ |
| | | checkIconUrls?: string[]; |
| | | /** 最外层 className,供各业务方沿用自身既有命名做样式覆盖 */ |
| | | className?: string; |
| | | }; |
| | | |
| | | /** |
| | | * 站点路线卡片:统一的起终点两项展示结构(marker + 虚线连接线 + 分割线 + 文本)。 |
| | | * 抽离自 cancel-site-box / calling-car-site-box / share-site-box / modify-destination-review-select-site |
| | | * 与 release-be-being 的 route-card 结构对齐。 |
| | | */ |
| | | export default function SiteRouteCard({ |
| | | sites, |
| | | markerType = 'dot', |
| | | checkIconUrls = [], |
| | | className = '' |
| | | }: Props) { |
| | | return ( |
| | | <View className={`site-route-card ${className}`.trim()}> |
| | | {sites.map((site, index) => { |
| | | const isFirst = index === 0; |
| | | return ( |
| | | <View |
| | | key={index} |
| | | className={`site-route-card-item${isFirst ? ' site-route-card-item-first' : ''}`} |
| | | onClick={site.onClick} |
| | | > |
| | | {isFirst && <View className='site-route-card-item-line'></View>} |
| | | {isFirst && <View className='site-route-card-item-divider'></View>} |
| | | <View className='site-route-card-item-marker'> |
| | | {markerType === 'check' ? ( |
| | | <Image className='site-route-card-item-check' src={checkIconUrls[index]} /> |
| | | ) : ( |
| | | <View |
| | | className={`site-route-card-item-dot${ |
| | | isFirst ? ' site-route-card-item-dot-first' : '' |
| | | }`} |
| | | ></View> |
| | | )} |
| | | </View> |
| | | <View className='site-route-card-item-row'> |
| | | <View className={`site-route-card-item-text ${site.nameClassName || ''}`.trim()}> |
| | | {site.name} |
| | | </View> |
| | | {site.extra} |
| | | </View> |
| | | </View> |
| | | ); |
| | | })} |
| | | </View> |
| | | ); |
| | | } |
| | |
| | | </View> |
| | | <View className={`car-card-arrive-item ${lockStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={() => handleUnlock('door', onDoorUnlockSuccess)}> |
| | | <View className="car-card-arrive-item-text">车锁</View> |
| | | <View className="car-card-arrive-item-status">{lockStatus ? '车门已解锁' : '点击打开'}</View> |
| | | <View className="car-card-arrive-item-status">{lockStatus ? '车门已解锁' : '点击解锁'}</View> |
| | | {renderControlIcon('door', lockStatus ? LOCK_OPEN_ICON_URL : LOCK_CLOSE_ICON_URL, 'car-card-arrive-item-img')} |
| | | </View> |
| | | </View> |
| | |
| | | import { useEffect, useState } from 'react'; |
| | | import { queryOrderQueueStatus } from '../../../../api/begin-route'; |
| | | import { TripStatus } from '../../../../constants/trip-status'; |
| | | import SiteRouteCard from '../../../../components/site-route-card'; |
| | | |
| | | type QueueStatus = { |
| | | currentPosition: number; |
| | |
| | | const endSiteName = cachedEndSite?.name || cachedEndSite?.address || '-'; |
| | | |
| | | // 状态栏右侧的距离/时间文案 |
| | | const distanceText = routeDistance ? `${routeDistance.value}${routeDistance.unit === 'm' ? 'M' : 'KM'}` : ''; |
| | | const approachingTimeText = `${normalizedDurationDisplay.value}MIN`; |
| | | const inServiceTimeText = `${normalizedDurationDisplay.value}MIN`; |
| | | const distanceText = routeDistance ? `${routeDistance.value}${routeDistance.unit === 'm' ? '米' : '公里'}` : ''; |
| | | const approachingTimeText = `${normalizedDurationDisplay.value}分钟`; |
| | | const inServiceTimeText = `${normalizedDurationDisplay.value}分钟`; |
| | | |
| | | return ( |
| | | <> |
| | |
| | | 取消行程 |
| | | </View> |
| | | </View> |
| | | <View className='calling-car-site-box'> |
| | | <View className='calling-car-site-box-icon'> |
| | | <Image className='calling-car-site-check'src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-golden-20260702.png' ></Image> |
| | | <Image className='dotted-line' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png' /> |
| | | <Image className='calling-car-site-check' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-black-20260702.png'></Image> |
| | | </View> |
| | | <View className='calling-car-site-box-content'> |
| | | <View className='calling-car-site-box-content-name'>{startSiteName}</View> |
| | | <View className='calling-car-site-box-divider'></View> |
| | | <View className='calling-car-site-box-content-name'>{endSiteName}</View> |
| | | </View> |
| | | </View> |
| | | <SiteRouteCard |
| | | className='calling-car-site-box' |
| | | markerType='check' |
| | | checkIconUrls={[ |
| | | 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-golden-20260702.png', |
| | | 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/route-state-icon-black-20260702.png' |
| | | ]} |
| | | sites={[ |
| | | { name: startSiteName }, |
| | | { name: endSiteName } |
| | | ]} |
| | | /> |
| | | </> |
| | | )} |
| | | |
| | |
| | | {/* 车辆到达上车点:直接展示图片 */} |
| | | {tripStatus === TripStatus.VehicleArrivedPickup && ( |
| | | <View className='calling-car-status-box calling-car-arrived-img'> |
| | | <View className='calling-car-status-box-left'>车辆已到达上车点,倒计时结束将自动取消订单</View> |
| | | <View className='calling-car-status-box-left'> |
| | | {doorUnlockedInCurrentPage ? '车门已开启,请尽快上车开启行程' : '车辆已到达上车点,倒计时结束将自动取消订单'} |
| | | </View> |
| | | </View> |
| | | )} |
| | | |
| | |
| | | import { View } from "@tarojs/components"; |
| | | import Taro from "@tarojs/taro"; |
| | | import { View, Image } from "@tarojs/components"; |
| | | import { TripStatus } from "../../../../constants/trip-status"; |
| | | |
| | | type Props = { |
| | | tripStatus?: TripStatus; |
| | | vehicleInfo?: { |
| | | vehiclePictures?: string; |
| | | vehicleCode?: string; |
| | |
| | | }; |
| | | }; |
| | | |
| | | export default function VehicleInfoCard({ vehicleInfo }: Props) { |
| | | export default function VehicleInfoCard({ tripStatus, vehicleInfo }: Props) { |
| | | // 行程中(前往目的地):车牌信息下方展示目的地图标 + 站点名称 |
| | | const showDestination = tripStatus === TripStatus.InService; |
| | | const cachedEndSite = showDestination ? (Taro.getStorageSync('endSite') || {}) : {}; |
| | | const endSiteName = cachedEndSite?.name || cachedEndSite?.address || '-'; |
| | | |
| | | // 展示车辆基础信息:左侧车牌·车身颜色,右侧车型 |
| | | return ( |
| | | <View className="car-card-detail"> |
| | | <View className="car-card-detail-plate"> |
| | | <View className="car-card-detail-num">{vehicleInfo?.plateNumber}</View> |
| | | {vehicleInfo?.vehicleColor && ( |
| | | <> |
| | | <View className="car-card-detail-dot"></View> |
| | | <View className="car-card-detail-color-text">{vehicleInfo?.vehicleColor}</View> |
| | | </> |
| | | )} |
| | | <View className="car-card-detail-wrap"> |
| | | <View className="car-card-detail"> |
| | | <View className="car-card-detail-plate"> |
| | | <View className="car-card-detail-num">{vehicleInfo?.plateNumber}</View> |
| | | {vehicleInfo?.vehicleColor && ( |
| | | <> |
| | | <View className="car-card-detail-dot"></View> |
| | | <View className="car-card-detail-color-text">{vehicleInfo?.vehicleColor}</View> |
| | | </> |
| | | )} |
| | | </View> |
| | | <View className="car-card-detail-model">小鹏GX</View> |
| | | </View> |
| | | <View className="car-card-detail-model">小鹏GX</View> |
| | | {showDestination && ( |
| | | <View className='car-card-detail-destination'> |
| | | <Image className='car-card-detail-destination-icon' src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/destination-icon-20260812.png' /> |
| | | <View className='car-card-detail-destination-name'>{endSiteName}</View> |
| | | </View> |
| | | )} |
| | | </View> |
| | | ); |
| | | } |
| | |
| | | } |
| | | { |
| | | [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.PaymentSuccess].includes(tripStatus) && |
| | | <VehicleInfoCard vehicleInfo={vehicleInfo} /> |
| | | <VehicleInfoCard tripStatus={tripStatus} vehicleInfo={vehicleInfo} /> |
| | | } |
| | | <TripStatusHeader |
| | | orderId={orderId} |
| | |
| | | import { View, Text, Image } from '@tarojs/components'; |
| | | import { useEffect, useState } from 'react'; |
| | | import './css-folder/cancel-popup.less' |
| | | import SiteRouteCard from '../../../components/site-route-card'; |
| | | import { formatRemainingDurationDisplay } from '../../../utils/format'; |
| | | import { |
| | | clearOrderTripStorage |
| | |
| | | } |
| | | </View> |
| | | { |
| | | [CancelType.OrderCanceled, CancelType.NoAvailableVehicle].includes(cancelType) ? |
| | | <View> |
| | | <View className='cancel-site-box'> |
| | | <View className='cancel-site-box-icon'> |
| | | <View className='cancel-site-box-icon-start'></View> |
| | | <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png" /> |
| | | <View className='cancel-site-box-icon-end'></View> |
| | | </View> |
| | | <View className='cancel-site-box-content'> |
| | | <View className='cancel-site-box-content-name'>{startSiteName}</View> |
| | | <View className='cancel-site-box-divider'></View> |
| | | <View className='cancel-site-box-content-name'>{endSiteName}</View> |
| | | </View> |
| | | </View> |
| | | <View className='cancel-btn-box'> |
| | | <View className='cancel-btn-again' onClick={onConfirm}>再来一单</View> |
| | | </View> |
| | | [CancelType.OrderCanceled, CancelType.NoAvailableVehicle].includes(cancelType) ? |
| | | <View> |
| | | <SiteRouteCard |
| | | className='cancel-site-box' |
| | | sites={[ |
| | | { name: startSiteName }, |
| | | { name: endSiteName } |
| | | ]} |
| | | /> |
| | | <View className='cancel-btn-box'> |
| | | <View className='cancel-btn-again' onClick={onConfirm}>再来一单</View> |
| | | </View> |
| | | </View> |
| | | : |
| | | <> |
| | | {/* 占位动画:优先 webp,加载失败回退 png */} |
| | | <Image className='cancel-animation' src={animationSrc} onError={handleAnimationError} /> |
| | | <View className='cancel-btn-box'> |
| | | <View className=' cancel-btn-cancel' onClick={onConfirm}>确认取消</View> |
| | | <View className='cancel-btn-again wait-btn' onClick={onClose}>再等等</View> |
| | | </View> |
| | | : |
| | | <> |
| | | {/* 占位动画:优先 webp,加载失败回退 png */} |
| | | <Image className='cancel-animation' src={animationSrc} onError={handleAnimationError} /> |
| | | <View className='cancel-btn-box'> |
| | | <View className=' cancel-btn-cancel' onClick={onConfirm}>确认取消</View> |
| | | <View className='cancel-btn-again wait-btn' onClick={onClose}>再等等</View> |
| | | </View> |
| | | </> |
| | | </> |
| | | } |
| | | </View> |
| | | </View> |
| | |
| | | } |
| | | .calling-car-content-box { |
| | | position: relative; |
| | | padding: 48px 48px 82px 48px; |
| | | padding: 48px 48px 66px 48px; |
| | | background: @gary-theme; |
| | | border-radius: 60px 60px 0 0; |
| | | } |
| | |
| | | .calling-car-vehicle-pic { |
| | | position: absolute; |
| | | top: -4px; |
| | | right: 116px; |
| | | right: 80px; |
| | | width: 342px; |
| | | height: 294px; |
| | | z-index: 1; |
| | |
| | | .fare-box-card { |
| | | padding-bottom: 0; |
| | | } |
| | | .car-card-detail-wrap { |
| | | margin-bottom: 32px; |
| | | } |
| | | .car-card-detail { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 32px; |
| | | } |
| | | .car-card-detail-destination { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-top: 12px; |
| | | .car-card-detail-destination-icon { |
| | | width: 28px; |
| | | height: 28px; |
| | | flex-shrink: 0; |
| | | } |
| | | .car-card-detail-destination-name { |
| | | margin-left: 8px; |
| | | font-size: 24px; |
| | | color: @black-text-main; |
| | | max-width: 620px; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | .car-card-detail-plate { |
| | | display: flex; |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-top: 8px; |
| | | padding: 32px 8px; |
| | | padding: 22px 8px 0 8px; |
| | | } |
| | | .trip-action-bar-item { |
| | | display: flex; |
| | |
| | | width: 28px; |
| | | height: 28px; |
| | | margin-right: 8px; |
| | | margin-top: 2px; |
| | | } |
| | | .trip-action-bar-item-destination { |
| | | .trip-action-bar-item-icon { |
| | |
| | | .trip-action-bar-item-text { |
| | | flex-shrink: 0; |
| | | font-size: 24px; |
| | | line-height: 24px; |
| | | line-height: 56px; |
| | | color: @black-text-main; |
| | | } |
| | | .trip-action-bar-share-btn { |
| | |
| | | margin: 0 12px; |
| | | color: rgba(20, 30, 39, 0.2); |
| | | } |
| | | .calling-car-site-box-content-name { |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | .calling-car-cancel-btn { |
| | | flex-shrink: 0; |
| | |
| | | border-radius: 40px; |
| | | background: rgba(204, 208, 215, 0.25); |
| | | padding: 32px; |
| | | display: flex; |
| | | gap: 16px; |
| | | height: fit-content; |
| | | .calling-car-site-box-icon { |
| | | flex-shrink: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | .calling-car-site-check { |
| | | width: 32px; |
| | | height: 32px; |
| | | } |
| | | .dotted-line { |
| | | flex: 1; |
| | | width: 2px; |
| | | margin: 6px 0; |
| | | height: fit-content; |
| | | } |
| | | } |
| | | .calling-car-site-box-content { |
| | | flex: 1; |
| | | font-size: 28px; |
| | | line-height: 44px; |
| | | letter-spacing: normal; |
| | | color: @black-text-main; |
| | | .calling-car-site-box-divider { |
| | | margin: 40px 24px 40px 0px; |
| | | border-top: 1px solid rgba(20, 30, 39, 0.08); |
| | | } |
| | | } |
| | | } |
| | | .calling-car-status-box { |
| | | position: relative; |
| | |
| | | |
| | | .cancel-site-box { |
| | | margin-top: 48px; |
| | | border-radius: 40px; |
| | | padding: 48px 40px; |
| | | background: rgba(204, 208, 215, 0.25); |
| | | padding: 48px 32px 42px 40px; |
| | | display: flex; |
| | | gap: 16px; |
| | | height: fit-content; |
| | | |
| | | .cancel-site-box-icon { |
| | | padding: 16px 0; |
| | | flex-shrink: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .cancel-site-box-icon-start { |
| | | width: 16px; |
| | | height: 16px; |
| | | background: #DFB369; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .cancel-site-box-icon-end { |
| | | width: 16px; |
| | | height: 16px; |
| | | background: #141E27; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | image { |
| | | flex: 1; |
| | | width: 2px; |
| | | margin: 7px 0; |
| | | height: fit-content; |
| | | } |
| | | } |
| | | |
| | | .cancel-site-box-content { |
| | | flex: 1; |
| | | font-size: 32px; |
| | | line-height: 48px; |
| | | letter-spacing: normal; |
| | | color: #141E27; |
| | | font-weight: 500; |
| | | .cancel-site-box-divider { |
| | | margin: 34px 32px 40px 0px; |
| | | border-top: 1px solid rgba(204, 208, 215, 0.25); |
| | | } |
| | | .cancel-site-box-content-name { |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | border-radius: 40px; |
| | | } |
| | | |
| | | .cancel-animation { |
| | |
| | | margin-bottom: 32px; |
| | | } |
| | | |
| | | // 站点样式参考 cancel-popup 的 cancel-site-box:灰底盒子 + 左侧起终点圆点虚线列 |
| | | // 站点样式参考 cancel-popup 的 cancel-site-box(route-card 风格),已抽离为 SiteRouteCard 公共组件, |
| | | // 此处只保留容器级样式(背景/圆角/内边距) |
| | | .modify-destination-review-select-site { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | gap: 16px; |
| | | padding: 40px 32px 40px 48px; |
| | | border-radius: 40px; |
| | | background: rgba(204, 208, 215, 0.25); |
| | | box-sizing: border-box; |
| | | height: fit-content; |
| | | .site-route-card-item-first { |
| | | .site-route-card-item-row { |
| | | .site-route-card-item-text { |
| | | color: @black-text-04; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | &-trip-card { |
| | |
| | | box-sizing: border-box; |
| | | border: 2px solid #ffffff; |
| | | box-shadow: 0 20px 40px 0 rgba(87, 99, 147, 0.08); |
| | | } |
| | | |
| | | .modify-destination-review-select-site-start { |
| | | color: @black-text-04; |
| | | } |
| | | |
| | | .modify-destination-review-select-site-icon { |
| | | padding: 16px 0; |
| | | flex-shrink: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | |
| | | .modify-destination-review-select-site-icon-start { |
| | | width: 16px; |
| | | height: 16px; |
| | | background: #DFB369; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .modify-destination-review-select-site-icon-end { |
| | | width: 16px; |
| | | height: 16px; |
| | | background: @black-text-main; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .modify-destination-review-select-site-icon image { |
| | | flex: 1; |
| | | width: 2px; |
| | | margin: 7px 0; |
| | | height: fit-content; |
| | | } |
| | | |
| | | .modify-destination-review-select-site-content { |
| | | flex: 1; |
| | | min-width: 0; |
| | | font-size: 32px; |
| | | line-height: 48px; |
| | | letter-spacing: normal; |
| | | color: @black-text-main; |
| | | } |
| | | |
| | | .modify-destination-review-select-site-start, |
| | | .modify-destination-review-select-site-end { |
| | | font-weight: 500; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .modify-destination-review-select-site-divider { |
| | | margin: 34px 32px 40px 0px; |
| | | border-top: 1px solid rgba(204, 208, 215, 0.25); |
| | | } |
| | | |
| | | &-footer { |
| | |
| | | background: @gary-theme; |
| | | border-radius: 60px 60px 0px 0px; |
| | | overflow: hidden; |
| | | padding-top: 84px; |
| | | padding-top: 94px; |
| | | .release-be-being-content { |
| | | position: relative; |
| | | z-index: 1; |
| | |
| | | .route-select-content-item { |
| | | flex: 1; |
| | | padding: 16px 0 24px 24px; |
| | | border-radius: 16px; |
| | | background: #F5F6FA; |
| | | border: 1px solid #F2F3F7; |
| | | border-radius: 20px; |
| | | background: @gary-theme; |
| | | |
| | | |
| | | .route-select-content-item-title { |
| | | font-size: 20px; |
| | |
| | | } |
| | | |
| | | .active-item { |
| | | background: linear-gradient(97deg, rgba(255, 209, 129, 0.2) 0%, rgba(223, 179, 105, 0.2) 103%) !important; |
| | | box-sizing: border-box; |
| | | background: rgba(223, 179, 105, 0.2); |
| | | position: relative; |
| | | |
| | | .route-select-content-item-title { |
| | | color: @gold-text; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | border-radius: 16px; |
| | | padding: 1px; |
| | | background: linear-gradient(180deg, @gold-light -10%, rgba(223, 179, 105, 0.0469) 108%, rgba(0, 0, 0, 0) 108%); |
| | | -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); |
| | | -webkit-mask-composite: xor; |
| | | mask-composite: exclude; |
| | | pointer-events: none; |
| | | } |
| | | } |
| | | } |
| | |
| | | .discount-amount-desc { |
| | | margin-left: 8px; |
| | | font-size: 24px; |
| | | font-weight: 500; |
| | | line-height: 40px; |
| | | letter-spacing: normal; |
| | | color: @gold-text; |
| | |
| | | .route-select-content-item { |
| | | flex: 1; |
| | | padding: 16px 0 24px 24px; |
| | | border-radius: 16px; |
| | | border-radius: 20px; |
| | | background: #F5F6FA; |
| | | border: 1px solid #F2F3F7; |
| | | |
| | | .route-select-content-item-title { |
| | | font-size: 20px; |
| | | line-height: 20px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | color: @black-text-main; |
| | | } |
| | | |
| | | .route-select-content-item-time { |
| | |
| | | font-size: 28px; |
| | | font-weight: 500; |
| | | line-height: 40px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | color: @black-text-main; |
| | | } |
| | | |
| | | .route-select-content-item-distance { |
| | |
| | | } |
| | | |
| | | .active-item { |
| | | background: linear-gradient(97deg, rgba(255, 209, 129, 0.2) 0%, rgba(223, 179, 105, 0.2) 103%) !important; |
| | | background: rgba(223, 179, 105, 0.2); |
| | | box-sizing: border-box; |
| | | position: relative; |
| | | .route-select-content-item-title { |
| | | color: @gold-text; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | border-radius: 16px; |
| | | padding: 1px; |
| | | background: linear-gradient(180deg, @gold-light -10%, rgba(223, 179, 105, 0.0469) 108%, rgba(0, 0, 0, 0) 108%); |
| | | -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); |
| | | -webkit-mask-composite: xor; |
| | | mask-composite: exclude; |
| | | pointer-events: none; |
| | | } |
| | | } |
| | | } |
| | |
| | | @import '../../../../style/ui-variables.less'; |
| | | .service-route-change-container { |
| | | background: #FFFFFF; |
| | | border-radius: 40px 40px 0 0; |
| | | padding: 40px 49px 68px 49px; |
| | | background: @gary-theme; |
| | | border-radius: 60px 60px 0 0; |
| | | padding: 40px 40px 80px 40px; |
| | | |
| | | .service-route-select-container { |
| | | display: flex; |
| | |
| | | .service-route-select-title { |
| | | font-size: 28px; |
| | | font-weight: 500; |
| | | line-height: 40px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 52px; |
| | | color: @black-text-main; |
| | | } |
| | | |
| | | .service-route-select-preference { |
| | |
| | | align-items: center; |
| | | |
| | | .service-route-select-preference-text { |
| | | font-size: 24px; |
| | | font-size: 28px; |
| | | font-weight: 500; |
| | | line-height: 40px; |
| | | color: @gold-text; |
| | | font-weight: 500; |
| | | line-height: 52px; |
| | | color: @dark-gold-text; |
| | | } |
| | | |
| | | image { |
| | |
| | | .service-route-select-content { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | gap: 10px; |
| | | margin-top: 16px; |
| | | gap: 14px; |
| | | margin-top: 34px; |
| | | |
| | | .service-route-select-content-item { |
| | | flex: 1; |
| | | padding: 16px 24px 30px 24px; |
| | | border-radius: 16px; |
| | | border-radius: 20px; |
| | | background: #F5F6FA; |
| | | border: 1px solid #F2F3F7; |
| | | |
| | | .service-route-select-content-item-title { |
| | | font-size: 20px; |
| | | line-height: 20px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | color: @black-text-04; |
| | | } |
| | | |
| | | .service-route-select-content-item-time { |
| | | margin-top: 10px; |
| | | margin-top: 18px; |
| | | font-size: 28px; |
| | | font-weight: 500; |
| | | line-height: 40px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | color: @black-text-main; |
| | | } |
| | | |
| | | .service-route-select-content-item-distance { |
| | |
| | | margin-top: 12px; |
| | | font-size: 20px; |
| | | line-height: 20px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | color: @black-text-main; |
| | | |
| | | .service-traffic-signal-item { |
| | | display: flex; |
| | |
| | | } |
| | | |
| | | .service-route-select-content-item-estimate { |
| | | margin-top: 34px; |
| | | margin-top: 38px; |
| | | font-size: 20px; |
| | | line-height: 20px; |
| | | color: #F58900; |
| | | color: @gold-text; |
| | | } |
| | | } |
| | | |
| | | .active { |
| | | background: linear-gradient(97deg, rgba(255, 209, 129, 0.2) 0%, rgba(223, 179, 105, 0.2) 103%) !important; |
| | | background: rgba(223, 179, 105, 0.2); |
| | | box-sizing: border-box; |
| | | position: relative; |
| | | |
| | | .service-route-select-content-item-title { |
| | | color: @gold-text; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | border-radius: 16px; |
| | | padding: 1px; |
| | | background: linear-gradient(180deg, @gold-light -10%, rgba(223, 179, 105, 0.0469) 108%, rgba(0, 0, 0, 0) 108%); |
| | | -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); |
| | | -webkit-mask-composite: xor; |
| | | mask-composite: exclude; |
| | | pointer-events: none; |
| | | } |
| | | } |
| | | } |
| | |
| | | display: flex; |
| | | justify-content: space-between; |
| | | gap: 22px; |
| | | margin-top: 48px; |
| | | margin-top: 58px; |
| | | font-size: 32px; |
| | | font-weight: 500; |
| | | text-align: center; |
| | | |
| | | .service-route-select-footer-item-cancel { |
| | | flex: 1; |
| | | color: #141E27; |
| | | border-radius: 1000px; |
| | | box-sizing: border-box; |
| | | border: 4px solid #141E27; |
| | | line-height: 104px; |
| | | height: 104px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex: 1; |
| | | color: @black-text-main; |
| | | border-radius: 1000px; |
| | | box-sizing: border-box; |
| | | border: 4px solid @black-text-main; |
| | | line-height: 104px; |
| | | height: 102px; |
| | | } |
| | | |
| | | .service-route-select-footer-item-confirm { |
| | | flex: 1; |
| | | color: #FFFFFF; |
| | | border-radius: 1000px; |
| | | line-height: 104px; |
| | | border: 4px solid #141E27; |
| | | box-sizing: border-box; |
| | | height: 104px; |
| | | background: #141E27; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex: 1; |
| | | color: #FFFFFF; |
| | | border-radius: 1000px; |
| | | line-height: 104px; |
| | | border: 4px solid @black-text-main; |
| | | box-sizing: border-box; |
| | | height: 102px; |
| | | background: @black-text-main; |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | import { getUpdateAddressNum } from "../../../api/begin-route"; |
| | | import "./css-folder/modify-destination-review.less"; |
| | | import RouteSelectionPanel from "./route-selection-panel"; |
| | | import SiteRouteCard from "../../../components/site-route-card"; |
| | | |
| | | type Props = { |
| | | orderId?: string; |
| | |
| | | {modifyDestinationTipText} |
| | | </View> |
| | | ) : null} |
| | | <View className="modify-destination-review-select-site"> |
| | | <View className="modify-destination-review-select-site-icon"> |
| | | <View className="modify-destination-review-select-site-icon-start"></View> |
| | | <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png" /> |
| | | <View className="modify-destination-review-select-site-icon-end"></View> |
| | | </View> |
| | | <View className="modify-destination-review-select-site-content"> |
| | | <View className="modify-destination-review-select-site-start"> |
| | | {startSite?.name || "-"} |
| | | </View> |
| | | <View className="modify-destination-review-select-site-divider"></View> |
| | | <View |
| | | className="modify-destination-review-select-site-end" |
| | | onClick={() => onEditDestination?.()} |
| | | > |
| | | {nextEndSite?.name || "-"} |
| | | </View> |
| | | </View> |
| | | </View> |
| | | <SiteRouteCard |
| | | className="modify-destination-review-select-site" |
| | | sites={[ |
| | | { name: startSite?.name || "-" }, |
| | | { |
| | | name: nextEndSite?.name || "-", |
| | | onClick: () => onEditDestination?.() |
| | | } |
| | | ]} |
| | | /> |
| | | <View className="modify-destination-review-footer"> |
| | | <View className="modify-destination-review-fare"> |
| | | {isOperateFree ? ( |
| | |
| | | .popup-content { |
| | | position: relative; |
| | | z-index: 103; |
| | | background: #FFFFFF; |
| | | background: @gary-theme; |
| | | border-radius: 40px 40px 0 0; |
| | | width: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | transform: translateY(100%); |
| | | min-height: 672px; |
| | | } |
| | | |
| | | .popup-header { |
| | |
| | | font-size: 32px; |
| | | font-weight: 500; |
| | | line-height: 32px; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | color: @black-text-main; |
| | | } |
| | | |
| | | .close-icon { |
| | |
| | | "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/change-site-up.png"; // 行程中修改目的地时的上车点图标 |
| | | // 接驾态(前往上车点)上车点三角图标 |
| | | const PICKUP_APPROACHING_ICON_URL = |
| | | "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260722.png"; |
| | | "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260812.png"; |
| | | // 送驾态(前往目的地)下车点三角图标 |
| | | const DROPOFF_INSERVICE_ICON_URL = |
| | | "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/down-triangle-20260722.png"; |
| | |
| | | !shouldUseChangePickupIcon; |
| | | // 修改目的地预览:起点(上车点)统一用三角图标 up-triangle + 名称显示在图标下方 |
| | | const isPickupModifyPreview = deps.isModifyDestinationReviewVisible; |
| | | // 发布路线态:上车点同样展示三角图标 |
| | | const isPickupPublishRoute = |
| | | deps.tripStatus === TripStatus.PublishRoute && !shouldUseChangePickupIcon; |
| | | // 接驾态路线切换卡片展示中:地图状态被切换为 PublishRoute 展示态,但上车点名称仍需按三角图标态展示 |
| | | const isPickupTriangle = isPickupApproaching || isPickupArrived || isPickupSettled || isPickupModifyPreview || deps.showTrip1StyleEndpoint; |
| | | const isPickupTriangle = isPickupApproaching || isPickupArrived || isPickupSettled || isPickupModifyPreview || isPickupPublishRoute || deps.showTrip1StyleEndpoint; |
| | | // 名称在图标下方:接驾态 11px,到达/待支付/完成态 22px |
| | | const pickupNameLabelConfig = isPickupApproaching |
| | | ? SITE_NAME_LABEL_BASE |
| | |
| | | : dropoffSite.name || "目的地"; |
| | | // 送驾态(前往目的地):下车点改用三角图标,名称移到图标下方,浮窗展示 end-site 图片; |
| | | // 待支付/完成态:下车点同样三角图标 + end-site 浮窗,名称移到图标左侧。 |
| | | // 发布路线态:下车点同样展示三角图标。 |
| | | const isDropoffInService = deps.tripStatus === TripStatus.InService; |
| | | const isDropoffSettled = [ |
| | | TripStatus.TripFinished, |
| | | TripStatus.PaymentSuccess |
| | | ].includes(deps.tripStatus); |
| | | const isDropoffTriangle = isDropoffInService || isDropoffSettled; |
| | | const isDropoffPublishRoute = deps.tripStatus === TripStatus.PublishRoute; |
| | | const isDropoffTriangle = isDropoffInService || isDropoffSettled || isDropoffPublishRoute; |
| | | const useDropoffRichCallout = isDropoffTriangle; // cover-view 富浮窗 |
| | | const getCustomCalloutAnchorY = () => { |
| | | if (!deps.isAndroid && isDropoffSettled) { |
| | |
| | | {routeDistance?.value ?? 0} |
| | | </View> |
| | | <View style={{ flexShrink: 0 }}> |
| | | {isPickup |
| | | ? routeDistance?.unit === "m" ? "M" : "KM" |
| | | : routeDistance?.unit === "m" ? "米" : "公里"} |
| | | {routeDistance?.unit === "m" ? "米" : "公里"} |
| | | </View> |
| | | {!isPickup && ( |
| | | <> |
| | |
| | | .join("|"), |
| | | [deps.markers] |
| | | ); |
| | | /** |
| | | * 触发一次 customCallout 回流重绘:翻转 borderRadius 兜底原生地图对该 cover-view 的重绘。 |
| | | */ |
| | | const toggleCalloutRepaint = useCallback(() => { |
| | | setCalloutRepaintStyle((prev) => ({ borderRadius: prev.borderRadius === '0px' ? '0.5px' : '0px' })); |
| | | }, []); |
| | | useEffect(() => { |
| | | // 仅 Android 保留一次性重绘兜底;iOS 完全不做 borderRadius 重绘(避免闪动)。 |
| | | // 依赖里额外加入 isModifyDestinationReviewVisible / isPickupRouteChangeVisible: |
| | | // 这两个状态切换时 customCallout 内容会切成目的地黑色浮窗(endpoint-summary), |
| | | // 但 marker 的 id/name(markersRepaintSignature)和 tripStatus 通常不变, |
| | | // 若不监听会导致本次内容切换错过重绘时机,出现"气泡背景已渲染但内部文字未绘制"。 |
| | | |
| | | if (!deps.isAndroid) { |
| | | return undefined; |
| | | } |
| | |
| | | calloutRepaintTimerRef.current = null; |
| | | } |
| | | }; |
| | | }, [markersRepaintSignature, deps.tripStatus, deps.isAndroid, toggleCalloutRepaint]); |
| | | }, [ |
| | | markersRepaintSignature, |
| | | deps.tripStatus, |
| | | deps.isAndroid, |
| | | deps.isModifyDestinationReviewVisible, |
| | | deps.isPickupRouteChangeVisible, |
| | | toggleCalloutRepaint |
| | | ]); |
| | | |
| | | |
| | | /** |
| | |
| | | toggleCalloutRepaint(); |
| | | return undefined; |
| | | }, [deps.elapsedSeconds, deps.tripStatus, deps.isAndroid, toggleCalloutRepaint]); |
| | | |
| | | /** |
| | | * Android 接驾到达态(VehicleArrivedPickup)上车点倒计时气泡(site-countdown-callout) |
| | | * 同样存在偶现不绘制、倒计时文案不刷新的问题:pickupCountdownSeconds 按秒递减更新, |
| | | * 但此前只有 Publishing/CallingCar 的 elapsedSeconds 变化会触发每秒重绘兜底, |
| | | * VehicleArrivedPickup 未被覆盖,导致该气泡文案长时间卡住不变,直到其他依赖 |
| | | * (markers/tripStatus 等)触发的单次重绘才一次性跳变。这里单独为该状态按秒重绘兜底。 |
| | | */ |
| | | useEffect(() => { |
| | | if (!deps.isAndroid) { |
| | | return undefined; |
| | | } |
| | | if (deps.tripStatus !== TripStatus.VehicleArrivedPickup) { |
| | | return undefined; |
| | | } |
| | | if (pickupCountdownSeconds === null || pickupCountdownSeconds === undefined) { |
| | | return undefined; |
| | | } |
| | | toggleCalloutRepaint(); |
| | | return undefined; |
| | | }, [pickupCountdownSeconds, deps.tripStatus, deps.isAndroid, toggleCalloutRepaint]); |
| | | |
| | | |
| | | |
| | |
| | | {item.id === 1 && |
| | | deps.tripStatus === TripStatus.VehicleApproaching && |
| | | !deps.isModifyDestinationReviewVisible && |
| | | !deps.isPickupRouteChangeVisible && |
| | | !item.hideNameCallout && ( |
| | | <> |
| | | <CoverView |
| | |
| | | src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/golden-car-icon-20260708.png" |
| | | /> |
| | | <CoverView className="site-status-time"> |
| | | 限停{deps.startSite?.time}MIN |
| | | 限停{deps.startSite?.time}分钟 |
| | | </CoverView> |
| | | </CoverView> |
| | | <CoverImage |
| | |
| | | {item.id === 1 && |
| | | deps.tripStatus === TripStatus.VehicleArrivedPickup && |
| | | !deps.isModifyDestinationReviewVisible && |
| | | !deps.doorUnlockedInCurrentPage && |
| | | !item.hideNameCallout && |
| | | Number(pickupCountdownSeconds) > 0 && ( |
| | | <CoverView |
| | |
| | | {item.id === 1 && |
| | | ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes( |
| | | deps.tripStatus |
| | | ) || deps.isModifyDestinationReviewVisible) && |
| | | ) || deps.isModifyDestinationReviewVisible |
| | | || (deps.tripStatus === TripStatus.VehicleApproaching && deps.isPickupRouteChangeVisible)) && |
| | | !item.hideNameCallout && ( |
| | | <CoverImage |
| | | className="site-status-end-img" |
| | |
| | | !item.hideNameCallout && ( |
| | | <CoverImage |
| | | className="site-status-end-img" |
| | | src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-20260722.png" |
| | | src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-20260812.png" |
| | | /> |
| | | )} |
| | | {item.name === "车辆位置" && |
| | |
| | | {deps.routeDistance?.value ?? 0} |
| | | </CoverView> |
| | | <CoverView className="car-before-order-tip-unit"> |
| | | {deps.routeDistance?.unit === "m" ? "M" : "KM"} |
| | | {deps.routeDistance?.unit === "m" ? "米" : "公里"} |
| | | </CoverView> |
| | | <CoverView className="car-before-order-tip-value"> |
| | | {vehicleMetricDurationDisplay.value} |
| | |
| | | {deps.routeDistance?.value ?? 0} |
| | | </CoverView> |
| | | <CoverView> |
| | | {deps.routeDistance?.unit === "m" ? "M" : "KM"} |
| | | {deps.routeDistance?.unit === "m" ? "米" : "公里"} |
| | | </CoverView> |
| | | </CoverView> |
| | | ) |
| | |
| | | [ |
| | | deps.arrivedTooltipReady, |
| | | deps.customCalloutIconSrc, |
| | | deps.doorUnlockedInCurrentPage, |
| | | deps.hasBeforeUserEndSite, |
| | | deps.isAndroid, |
| | | deps.isModifyDestinationReviewVisible, |
| | |
| | | setShowCancelPopup={setShowCancelPopup} |
| | | setShowModifyDestinationFailPopup={setShowModifyDestinationFailPopup} |
| | | setModifyDestinationFailMessage={setModifyDestinationFailMessage} |
| | | /> |
| | | */} |
| | | />*/} |
| | | |
| | | </> |
| | | ) |
| | | } |
| | |
| | | id: 1, |
| | | longitude: displayStartPoint.longitude, |
| | | latitude: displayStartPoint.latitude, |
| | | width: 34, |
| | | height: 46, |
| | | width: 36, |
| | | height: 56, |
| | | anchor: { x: 0.7, y: 0.8 }, |
| | | iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/start-site-order-20260804-v2.png', |
| | | iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/start-site-order-20260812.png', |
| | | viewportLongitude: startPoint?.longitude, |
| | | viewportLatitude: startPoint?.latitude, |
| | | } : null, |
| | |
| | | id: 2, |
| | | longitude: endPoint.longitude, |
| | | latitude: endPoint.latitude, |
| | | width: 34, |
| | | height: 46, |
| | | width: 36, |
| | | height: 56, |
| | | anchor: { x: 0.7, y: 0.8 }, |
| | | iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-order-20260804-v2.png', |
| | | iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-order-20260812.png', |
| | | } : null |
| | | ].filter(Boolean) as any[]; |
| | | setMarkers(markerList); |
| | |
| | | </View> |
| | | </View> |
| | | )} |
| | | {/* 地图浮层入口 - 选点页不展示 */} |
| | | {deps.nearbyLoading && deps.isAndroid && ( |
| | | <Image className="waiting-radar-img" src={deps.waitingRadarSrc} /> |
| | | )} |
| | | {/* 指针 */} |
| | | <View className="pointer-box"> |
| | | {deps.nearbyLoading && !deps.isAndroid && ( |
| | | {/* 雷达只在真正命中吸附、指针跳动到具体站点时才展示(由 bounce 驱动),与首页保持一致 */} |
| | | {deps.bounce && deps.isAndroid && ( |
| | | <Image className="waiting-radar-img" src={deps.waitingRadarSrc} /> |
| | | )} |
| | | {deps.bounce && !deps.isAndroid && ( |
| | | <Image className="waiting-radar" src={deps.waitingRadarSrc} /> |
| | | )} |
| | | {((deps.showToolTip && shouldShowSelectedSiteEcho) || showRangeWarning) && ( |
| | |
| | | const AUTO_SCALE_MAX = 17; // 自动缩放最大等级 |
| | | const AUTO_SCALE_MIN = 11; // 自动缩放最小等级 |
| | | const INCLUDE_POINTS_PADDING = [100, 100, 100, 100] as [number, number, number, number]; // 地图回视图内边距 |
| | | /** 指针跳动到附近站点的动效总时长:指针跳动(600ms)+ 雷达扫描确认(320ms),需与 index.less 中对应动画时长保持一致,与首页保持一致。 */ |
| | | const POINTER_BOUNCE_SEQUENCE_DURATION_MS = 920; |
| | | const CROSS_OPERATING_AREA_FILL_COLOR = '#0000001a'; // 非可选运营区围栏填充色 |
| | | const CROSS_OPERATING_AREA_STROKE_COLOR = '#DFB369'; // 非可选运营区围栏描边色 |
| | | const CROSS_OPERATING_AREA_STROKE_WIDTH = 1.5; // 非可选运营区围栏描边宽度 |
| | |
| | | return; |
| | | } |
| | | if (type !== 'end') return; |
| | | state.setBounce(true); |
| | | |
| | | const mapCtx = Taro.createMapContext('select-site-map'); |
| | | // 获取真实的缩放等级并通过回调传递出去,彻底阻断与 mapScale 状态的绑定,避免触发原生组件抖动 |
| | |
| | | const selectableMarkers = getSelectableMarkers(newMarkers, sites); |
| | | const { targetMarker, minDistance } = findNearestMarker(lat, lng, selectableMarkers); |
| | | if (targetMarker && minDistance < state.distanceConfig.adsorptionDistance) { |
| | | // 命中吸附:指针跳动 + 雷达确认动效(与地图硬切同一时刻触发),920ms 播完后才展示 tooltip,与首页保持一致 |
| | | state.setBounce(true); |
| | | mapCtx.moveToLocation({ |
| | | latitude: targetMarker.latitude, |
| | | longitude: targetMarker.longitude, |
| | |
| | | state.setLatitude(activeSite?.latitude || targetMarker.latitude); |
| | | state.setLongitude(activeSite?.longitude || targetMarker.longitude); |
| | | syncPinLocation(activeSite?.latitude || targetMarker.latitude, activeSite?.longitude || targetMarker.longitude); |
| | | state.setShowToolTip(true); |
| | | setTimeout(() => { |
| | | if (currentRegionSeq !== state.regionSeqRef.current) return; |
| | | state.setBounce(false); |
| | | state.setShowToolTip(true); |
| | | }, POINTER_BOUNCE_SEQUENCE_DURATION_MS); |
| | | } else { |
| | | // 未命中吸附:不播放跳动/雷达动效,立即展示提示,避免无意义等待 |
| | | resetStationSelection(); |
| | | state.setSelectedSite({}); |
| | | state.setRangeStatus( |
| | |
| | | } |
| | | }); |
| | | } |
| | | setTimeout(() => { |
| | | state.setBounce(false); |
| | | state.setShowToolTip(true); |
| | | }, 600); |
| | | }; |
| | | |
| | | /** |
| | |
| | | const targetSite = findSiteByMarker(targetMarker, state.nearbySites) || targetMarker; |
| | | state.pickupConfirmTypeRef.current = 'click'; |
| | | state.setShowToolTip(false); |
| | | // 点击 marker 是明确的跳转动作,指针跳动 + 雷达确认动效播完(920ms)后才展示 tooltip,与首页保持一致 |
| | | state.setBounce(true); |
| | | state.setLatitude(targetMarker.latitude); |
| | | state.setLongitude(targetMarker.longitude); |
| | |
| | | state.setLongitude(activeSite?.longitude || targetMarker.longitude); |
| | | syncPinLocation(activeSite?.latitude || targetMarker.latitude, activeSite?.longitude || targetMarker.longitude); |
| | | state.setShowToolTip(true); |
| | | }, 600); |
| | | }, POINTER_BOUNCE_SEQUENCE_DURATION_MS); |
| | | }; |
| | | |
| | | /** |
| | |
| | | .toolTip { |
| | | position: relative; |
| | | z-index: 10; |
| | | animation: tooltip-fade-in 240ms ease-out; |
| | | will-change: transform, opacity; |
| | | |
| | | .toolTip-bg { |
| | | position: relative; |
| | |
| | | width: 64px; |
| | | height: 86px; |
| | | margin-top: -54px; |
| | | opacity: 1; |
| | | transition: opacity 240ms ease-out; |
| | | |
| | | &.bounce { |
| | | animation: pointer-bounce 600ms ease-in-out; |
| | |
| | | opacity: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .waiting-radar { |
| | | position: fixed; |
| | | width: 700px; |
| | | height: 350px; |
| | | top: 48%; |
| | | left: 50%; |
| | | transform: translate(-50%, -48%); |
| | | pointer-events: none; |
| | | z-index: 1; |
| | | } |
| | | // 雷达以站点位置(.pointer-box 底边,即 translate(-50%,-100%) 后与地图中心点对齐的边)为圆心展开, |
| | | // 用绝对定位直接锚定该边,不受内部 flex 子元素排列影响,与首页保持一致 |
| | | .waiting-radar { |
| | | position: absolute; |
| | | width: 700px; |
| | | height: 350px; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translate(-50%, 50%); |
| | | pointer-events: none; |
| | | z-index: 1; |
| | | animation: radar-fade-in 200ms ease-out; |
| | | } |
| | | |
| | | .waiting-radar-img { |
| | | position: fixed; |
| | | width: 700px; |
| | | height: 350px; |
| | | top: 48%; |
| | | left: 50%; |
| | | transform: translate(-50%, -48%); |
| | | pointer-events: none; |
| | | z-index: 1; |
| | | .waiting-radar-img { |
| | | position: absolute; |
| | | width: 700px; |
| | | height: 350px; |
| | | bottom: 0; |
| | | left: 50%; |
| | | transform: translate(-50%, 50%); |
| | | pointer-events: none; |
| | | z-index: 1; |
| | | animation: radar-fade-in 200ms ease-out; |
| | | } |
| | | } |
| | | |
| | | @keyframes pointer-bounce { |
| | |
| | | } |
| | | } |
| | | |
| | | @keyframes tooltip-fade-in { |
| | | 0% { |
| | | opacity: 0; |
| | | transform: translateY(6px); |
| | | } |
| | | |
| | | 100% { |
| | | opacity: 1; |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | |
| | | @keyframes radar-fade-in { |
| | | 0% { |
| | | opacity: 0; |
| | | } |
| | | |
| | | 100% { |
| | | opacity: 1; |
| | | } |
| | | } |
| | | |
| | | |
| | | .home-container { |
| | | display: flex; |
| | |
| | | import { safeParse } from '../../../../utils/Robotaxi-public-method'; |
| | | import { formatRemainingDurationDisplay } from '../../../utils/format'; |
| | | import { TripStatus } from '../../../constants/trip-status'; |
| | | import SiteRouteCard from '../../../components/site-route-card'; |
| | | |
| | | type Props = { |
| | | isExpired: boolean; |
| | |
| | | </View> |
| | | </View> |
| | | {/* 站点信息卡:参考 cancel-site-box 布局与背景卡片样式 */} |
| | | <View className='share-site-box'> |
| | | <View className='share-site-box-icon'> |
| | | <View className='share-site-box-icon-start'></View> |
| | | <Image src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/dotted-line.png' /> |
| | | <View className='share-site-box-icon-end'></View> |
| | | </View> |
| | | <View className='share-site-box-content'> |
| | | <View className='share-site-row'> |
| | | <View className={`share-site-name ${isStartSiteGray ? 'gray-text' : ''}`}> |
| | | {tripData?.startPointName} |
| | | </View> |
| | | {showBoardingTime && ( |
| | | <SiteRouteCard |
| | | className='share-site-box' |
| | | sites={[ |
| | | { |
| | | name: tripData?.startPointName, |
| | | nameClassName: isStartSiteGray ? 'gray-text' : '', |
| | | extra: showBoardingTime && ( |
| | | <View className='share-site-time'>{tripData.boardingTime} 上车</View> |
| | | )} |
| | | </View> |
| | | <View className='share-site-box-divider'></View> |
| | | <View className='share-site-row'> |
| | | <View className={`share-site-name ${isEndSiteGray ? 'gray-text' : ''}`}> |
| | | {tripData?.endPointName} |
| | | </View> |
| | | {showGetoffTime && ( |
| | | ) |
| | | }, |
| | | { |
| | | name: tripData?.endPointName, |
| | | nameClassName: isEndSiteGray ? 'gray-text' : '', |
| | | extra: showGetoffTime && ( |
| | | <View className='share-site-time'>{tripData.getoffTime} 到达</View> |
| | | )} |
| | | </View> |
| | | </View> |
| | | </View> |
| | | ) |
| | | } |
| | | ]} |
| | | /> |
| | | </View> |
| | | </View> |
| | | </View> |
| | |
| | | |
| | | // 接驾态(前往上车点)/到达终点态 上车点三角图标(与 begin-route 对齐) |
| | | const PICKUP_APPROACHING_ICON_URL = |
| | | 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260722.png'; |
| | | 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260812.png'; |
| | | // 送驾态(前往目的地)/到达终点态 下车点三角图标(与 begin-route 对齐) |
| | | const DROPOFF_INSERVICE_ICON_URL = |
| | | 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/down-triangle-20260722.png'; |
| | |
| | | <View className='car-time' style={{ alignItems: 'baseline', paddingBottom: 0 }}> |
| | | <View className='car-time-num'>{routeDistance?.value ?? 0}</View> |
| | | <View style={{ flexShrink: 0 }}> |
| | | {isPickup |
| | | ? (routeDistance?.unit === 'm' ? 'M' : 'KM') |
| | | : (routeDistance?.unit === 'm' ? '米' : '公里')} |
| | | {routeDistance?.unit === 'm' ? '米' : '公里'} |
| | | </View> |
| | | {!isPickup && ( |
| | | <> |
| | |
| | | > |
| | | <CoverView className='car-time'> |
| | | <CoverView className='car-time-num'>{deps.routeDistance?.value ?? 0}</CoverView> |
| | | <CoverView>{deps.routeDistance?.unit === 'm' ? 'M' : 'KM'}</CoverView> |
| | | <CoverView>{deps.routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView> |
| | | </CoverView> |
| | | </CoverView> |
| | | )} |
| | |
| | | className='site-status-car-icon' |
| | | src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/golden-car-icon-20260708.png' |
| | | /> |
| | | <CoverView className='site-status-time'>限停{deps.startSite?.time}MIN</CoverView> |
| | | <CoverView className='site-status-time'>限停{deps.startSite?.time}分钟</CoverView> |
| | | </CoverView> |
| | | <CoverImage |
| | | className='callout-diamond-icon' |
| | |
| | | {(item.id === 2 && [TripStatus.InService, TripStatus.ArrivedDestination, TripStatus.TripFinished, TripStatus.CancelOnlyPickup].includes(deps.tripStatus) && !item.hideNameCallout) && ( |
| | | <CoverImage |
| | | className='site-status-end-img' |
| | | src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-20260722.png' |
| | | src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-20260812.png' |
| | | /> |
| | | )} |
| | | </> |
| | |
| | | .calling-car-vehicle-pic { |
| | | position: absolute; |
| | | top: -4px; |
| | | right: 116px; |
| | | right: 80px; |
| | | width: 342px; |
| | | height: 294px; |
| | | z-index: 10; |
| | |
| | | } |
| | | } |
| | | |
| | | // 站点信息卡:参考 cancel-site-box 的布局与背景卡片样式,独立于车辆信息卡 |
| | | // 站点信息卡:参考 cancel-site-box 的布局与背景卡片样式,独立于车辆信息卡; |
| | | // 结构已抽离为 SiteRouteCard 公共组件,此处只保留容器级样式与业务特有的置灰/时间文案样式 |
| | | .share-site-box { |
| | | margin-top: 32px; |
| | | display: flex; |
| | | gap: 16px; |
| | | border-radius: 40px; |
| | | background: rgba(204, 208, 215, 0.25); |
| | | padding: 48px 32px 42px 32px; |
| | | |
| | | .share-site-box-icon { |
| | | padding: 16px 0; |
| | | flex-shrink: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .share-site-box-icon-start { |
| | | width: 16px; |
| | | height: 16px; |
| | | background: @gold-light; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .share-site-box-icon-end { |
| | | width: 16px; |
| | | height: 16px; |
| | | background: @black-text-main; |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | image { |
| | | flex: 1; |
| | | width: 2px; |
| | | margin: 7px 0; |
| | | height: fit-content; |
| | | } |
| | | .site-route-card-item-text.gray-text { |
| | | color: @black-text-04; |
| | | } |
| | | |
| | | .share-site-box-content { |
| | | flex: 1; |
| | | |
| | | .share-site-row { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .share-site-name { |
| | | font-size: 32px; |
| | | line-height: 48px; |
| | | color: @black-text-main; |
| | | |
| | | &.gray-text { |
| | | color: @black-text-04; |
| | | } |
| | | } |
| | | |
| | | .share-site-time { |
| | | flex-shrink: 0; |
| | | min-width: 120px; |
| | | margin-left: 12px; |
| | | text-align: right; |
| | | font-size: 24px; |
| | | line-height: 40px; |
| | | color: @black-text-04; |
| | | } |
| | | |
| | | .share-site-box-divider { |
| | | margin: 34px 32px 40px 0; |
| | | border-top: 1px solid rgba(204, 208, 215, 0.4); |
| | | } |
| | | .share-site-time { |
| | | flex-shrink: 0; |
| | | min-width: 120px; |
| | | margin-left: 12px; |
| | | text-align: right; |
| | | font-size: 24px; |
| | | line-height: 40px; |
| | | color: @black-text-04; |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | @gold-text: #A68042; // 文字金 |
| | | @gold-light: #DFB369; // 主题金 |
| | | @dark-gold-text: #8F754A; // 黑金 |
| | | @black-text-main: #141E27; // 重要文字 |
| | | @black-text-04: rgba(20, 30, 39, 0.4); // 次要文字 |
| | | @gary-theme: #F1F5FA; // 浅色面板 |