Merge branch 'feature_v1.0' into feature_v1.0_fix_614
| | |
| | | text, |
| | | scroll-view { |
| | | box-sizing: border-box; |
| | | -webkit-tap-highlight-color: transparent; |
| | | } |
| | | |
| | | button { |
| | |
| | | import Taro from '@tarojs/taro' |
| | | import { PureComponent } from 'react' |
| | | import { View, Text } from '@tarojs/components' |
| | | import './index.less' |
| | | // TODO(仅用于测试): 上线前删除以下空调测试相关引入及 DEMO_ORDER_ID |
| | | import TemperatureControl from '../../pages/begin-route/components/calling-car-modules/temperature-control'; |
| | | import DoorTrunkControl from '../../pages/begin-route/components/calling-car-modules/door-trunk-control'; |
| | | // TODO(仅用于测试): 上线前删除以下修改目的地卡片测试相关引入及 DEMO_MODIFY_* 数据 |
| | | import ModifyDestinationReview from '../../pages/begin-route/components/modify-destination-review'; |
| | | import { TripStatus } from '../../constants/trip-status'; |
| | | |
| | | const DEMO_ORDER_ID = '2063814874050772993'; |
| | | |
| | | // TODO(仅用于测试): 上线前删除以下修改目的地卡片 mock 数据 |
| | | const DEMO_MODIFY_START_SITE = { id: 'demo-start', name: '深圳北站' }; |
| | | const DEMO_MODIFY_NEXT_END_SITE = { id: 'demo-end', name: '腾讯滨海大厦' }; |
| | | const DEMO_MODIFY_FARE_ESTIMATE = { finalFareStr: '38.5', discountAmount: 5 }; |
| | | const DEMO_MODIFY_ROUTE_OPTIONS = [ |
| | | { |
| | | route: { routeID: 'demo-route-1' }, |
| | | primaryRoutePlanRule: 'FASTEST', |
| | | snapshotId: 'demo-snapshot-1', |
| | | operateFree: 0, |
| | | }, |
| | | ]; |
| | | |
| | | type Props = { |
| | | showIcon: boolean |
| | |
| | | orderId={DEMO_ORDER_ID} |
| | | /> |
| | | </View> |
| | | <Text>修改目的地卡片 Demo</Text> |
| | | {/* TODO(仅用于测试): 上线前删除 ModifyDestinationReview 测试渲染 */} |
| | | <View className='calling-car'> |
| | | <ModifyDestinationReview |
| | | orderId={DEMO_ORDER_ID} |
| | | startSite={DEMO_MODIFY_START_SITE} |
| | | nextEndSite={DEMO_MODIFY_NEXT_END_SITE} |
| | | fareEstimate={DEMO_MODIFY_FARE_ESTIMATE} |
| | | routeOptions={DEMO_MODIFY_ROUTE_OPTIONS} |
| | | selectedRouteIndex={0} |
| | | onEditDestination={() => Taro.showToast({ title: '编辑目的地(Demo)', icon: 'none' })} |
| | | onConfirm={() => Taro.showToast({ title: '确认修改(Demo)', icon: 'none' })} |
| | | /> |
| | | </View> |
| | | </View> |
| | | ) |
| | | } |
| | |
| | | * |
| | | * ============================================================================ |
| | | * 何时再用? |
| | | * - 调整 LOW_SPEED_MAX_KMH / MID_SPEED_MAX_KMH 阈值后想快速验证分档 |
| | | * - 调整 LOW_SPEED_SCALE / MID_SPEED_SCALE / HIGH_SPEED_SCALE 缩放等级后看效果 |
| | | * - 调整 LOW_SPEED_MAX_KMH 阈值后想快速验证分档 |
| | | * - 调整 LOW_SPEED_SCALE / HIGH_SPEED_SCALE 缩放等级后看效果 |
| | | * - 排查"速度变了但地图没缩放""车辆离开视区"等问题,临时模拟车速/订单状态/车辆位置 |
| | | * |
| | | * 启用步骤: |
| | | * 1. 把 `speed-scale-config.ts` 的 `SPEED_SCALE_DEBUG` 改为 `true` |
| | | * (会自动切换到 debug 阈值:低速 <10、中速 10~20、高速 >20 km/h) |
| | | * (会自动切换到 debug 阈值:低速 0~10、中高速 >10 km/h) |
| | | * 2. 在 `index.tsx` 的 `BeginRoute()` 返回 JSX 末尾(与 ModifyDestinationFailPopup 同级) |
| | | * 引入并挂载本组件,传入页面已有的状态: |
| | | * |
| | |
| | | * - 默认收起,左侧悬浮"调速"小按钮,点开展开完整面板 |
| | | * - Mock 行程状态(接驾中 / 行程中 / 还原) |
| | | * - Mock 车辆位置("把车放到地图中心" / "微移车辆") |
| | | * - 速度滑块 + 三档预设按钮(低速 / 中速 / 高速),喂 1 条或喂满窗口 |
| | | * - 速度滑块 + 两档预设按钮(低速 / 中高速),喂 1 条或喂满窗口 |
| | | * - 实时显示当前 tripStatus、mapScale、vehicleLocation、当前挡位/目标 scale |
| | | * |
| | | * 配套 console 工具(同样受 SPEED_SCALE_DEBUG 控制): |
| | |
| | | HIGH_SPEED_SCALE, |
| | | LOW_SPEED_MAX_KMH, |
| | | LOW_SPEED_SCALE, |
| | | MID_SPEED_MAX_KMH, |
| | | MID_SPEED_SCALE, |
| | | resolveSpeedTierByKmh, |
| | | SPEED_SAMPLE_WINDOW, |
| | | SPEED_SCALE_DEBUG |
| | |
| | | |
| | | /** 启用速度缩放的行程状态。 */ |
| | | const ACTIVE_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService]; |
| | | /** 滑块速度上限(km/h),按中速上限的 2 倍取整,保证能覆盖高速挡。 */ |
| | | const SLIDER_MAX_KMH = Math.ceil(MID_SPEED_MAX_KMH * 2); |
| | | /** 滑块速度上限(km/h),按低速上限的 4 倍取整,保证能覆盖中高速挡。 */ |
| | | const SLIDER_MAX_KMH = Math.ceil(LOW_SPEED_MAX_KMH * 4); |
| | | |
| | | /** 挡位标签配色。 */ |
| | | const TIER_COLOR: Record<string, string> = { |
| | | low: '#36b37e', |
| | | mid: '#ffab00', |
| | | high: '#ff5630' |
| | | }; |
| | | |
| | |
| | | */ |
| | | export default function DebugAutoscalePanel(props: Props) { |
| | | const [expanded, setExpanded] = useState(false); |
| | | const [speedKmh, setSpeedKmh] = useState(Math.round((LOW_SPEED_MAX_KMH + MID_SPEED_MAX_KMH) / 2)); |
| | | const [speedKmh, setSpeedKmh] = useState(Math.round(LOW_SPEED_MAX_KMH / 2)); |
| | | const originalTripStatusRef = useRef<TripStatus | null>(null); // 首次 mock 前的原状态,用于「还原」 |
| | | |
| | | const isActive = ACTIVE_STATUSES.includes(props.tripStatus); |
| | |
| | | }; |
| | | |
| | | const lowPreset = Math.max(0, LOW_SPEED_MAX_KMH - 2); |
| | | const midPreset = Math.round((LOW_SPEED_MAX_KMH + MID_SPEED_MAX_KMH) / 2); |
| | | const highPreset = MID_SPEED_MAX_KMH + Math.round(MID_SPEED_MAX_KMH * 0.5); |
| | | const highPreset = LOW_SPEED_MAX_KMH + Math.round(LOW_SPEED_MAX_KMH * 0.5); |
| | | |
| | | // 收起态:仅显示一个浮动小按钮。 |
| | | if (!expanded) { |
| | |
| | | </View> |
| | | |
| | | <View style={{ fontSize: '12px', lineHeight: '1.7', marginBottom: '6px', opacity: 0.85 }}> |
| | | <Text>{`阈值:低速<${LOW_SPEED_MAX_KMH} / 中速${LOW_SPEED_MAX_KMH}~${MID_SPEED_MAX_KMH} / 高速>${MID_SPEED_MAX_KMH} km/h`}</Text> |
| | | <Text style={{ display: 'block' }}>{`scale:低速 ${LOW_SPEED_SCALE} / 中速 ${MID_SPEED_SCALE} / 高速 ${HIGH_SPEED_SCALE}`}</Text> |
| | | <Text>{`阈值:低速 0~${LOW_SPEED_MAX_KMH} / 中高速 >${LOW_SPEED_MAX_KMH} km/h`}</Text> |
| | | <Text style={{ display: 'block' }}>{`scale:低速 ${LOW_SPEED_SCALE} / 中高速 ${HIGH_SPEED_SCALE}`}</Text> |
| | | </View> |
| | | |
| | | <View style={{ display: 'flex', justifyContent: 'space-between', fontSize: '13px', marginBottom: '4px' }}> |
| | |
| | | |
| | | <View style={{ display: 'flex', marginTop: '8px' }}> |
| | | <View onClick={() => applyPreset(lowPreset)} style={buttonStyle(TIER_COLOR.low)}>{`低速 ${lowPreset}`}</View> |
| | | <View onClick={() => applyPreset(midPreset)} style={buttonStyle(TIER_COLOR.mid)}>{`中速 ${midPreset}`}</View> |
| | | <View onClick={() => applyPreset(highPreset)} style={buttonStyle(TIER_COLOR.high)}>{`高速 ${highPreset}`}</View> |
| | | <View onClick={() => applyPreset(highPreset)} style={buttonStyle(TIER_COLOR.high)}>{`中高速 ${highPreset}`}</View> |
| | | </View> |
| | | |
| | | <View style={{ display: 'flex', marginTop: '8px' }}> |
| | |
| | | /** |
| | | * 速度自动缩放配置(纯常量与映射,无副作用),供 hook 与调试面板共享,避免阈值散落多处。 |
| | | * |
| | | * 速度分级与缩放等级(按 km/h,非 debug 模式): |
| | | * - 低速 <20:scale 19 |
| | | * - 中速 20~80:scale 17 |
| | | * - 高速 >80:scale 15 |
| | | * 速度分级与缩放等级(按 km/h,非 debug 模式,仅低速 / 中高速两档): |
| | | * - 低速 0~20:scale 19(看清路况细节) |
| | | * - 中高速 >20:scale 16(看清路线全貌) |
| | | * |
| | | * 注意:后端 ORDER_ROUTE 消息 route.speed 字段单位为 km/h(不是 m/s),全链路按 km/h 处理。 |
| | | */ |
| | |
| | | /** |
| | | * 调试开关:实车难以跑到高速,开启后用更低的速度阈值方便测试,并启用页面调速面板。 |
| | | * 测试完成后改回 false 即可恢复线上正常阈值,并自动隐藏调试面板。 |
| | | * - false(线上):低速 <20km/h,中速 20~80km/h,高速 >80km/h |
| | | * - true (测试):低速 <5km/h,中速 5~20km/h,高速 >20km/h |
| | | * - false(线上):低速 0~20km/h,中高速 >20km/h |
| | | * - true (测试):低速 0~10km/h,中高速 >10km/h |
| | | */ |
| | | export const SPEED_SCALE_DEBUG = false; |
| | | |
| | | /** 速度滑动平均窗口,取最近 N 条消息求均值,避免临界值来回闪烁。 */ |
| | | export const SPEED_SAMPLE_WINDOW = 1; |
| | | |
| | | /** 低速挡上限(km/h)。 */ |
| | | /** 低速挡上限(km/h),达到或超过即进入中高速挡。 */ |
| | | export const LOW_SPEED_MAX_KMH = SPEED_SCALE_DEBUG ? 10 : 20; |
| | | /** 中速挡上限(km/h)。 */ |
| | | export const MID_SPEED_MAX_KMH = SPEED_SCALE_DEBUG ? 20 : 60; |
| | | |
| | | /** 低速挡缩放等级。 */ |
| | | export const LOW_SPEED_SCALE = SPEED_SCALE_DEBUG ? 20 : 19; |
| | | export const LOW_SPEED_SCALE = SPEED_SCALE_DEBUG ? 19 : 19; |
| | | |
| | | /** 中速挡缩放等级。 */ |
| | | export const MID_SPEED_SCALE = SPEED_SCALE_DEBUG ? 16 : 17; |
| | | |
| | | /** 高速挡缩放等级。 */ |
| | | export const HIGH_SPEED_SCALE = SPEED_SCALE_DEBUG ? 13 : 16; |
| | | /** 中高速挡缩放等级(合并原中速、高速两档)。 */ |
| | | export const HIGH_SPEED_SCALE = SPEED_SCALE_DEBUG ? 15 : 15; |
| | | |
| | | |
| | | /** 速度挡位描述。 */ |
| | | export type SpeedTier = { |
| | | key: 'low' | 'mid' | 'high'; |
| | | key: 'low' | 'high'; |
| | | label: string; |
| | | scale: number; |
| | | }; |
| | |
| | | if (safeKmh < LOW_SPEED_MAX_KMH) { |
| | | return { key: 'low', label: '低速', scale: LOW_SPEED_SCALE }; |
| | | } |
| | | if (safeKmh < MID_SPEED_MAX_KMH) { |
| | | return { key: 'mid', label: '中速', scale: MID_SPEED_SCALE }; |
| | | } |
| | | return { key: 'high', label: '高速', scale: HIGH_SPEED_SCALE }; |
| | | return { key: 'high', label: '中高速', scale: HIGH_SPEED_SCALE }; |
| | | }; |
| | | |
| | | /** |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { useCallback, useEffect, useMemo, useRef } from 'react'; |
| | | import { TripStatus } from '../constants/trip-status'; |
| | | import { TripStatus } from '../../../constants/trip-status'; |
| | | |
| | | type RoutePoint = { |
| | | latitude: number; |
| | |
| | | 目标缩放等级: targetScale, |
| | | 均值速度_kmh: Number(averageSpeedKmh.toFixed(2)) |
| | | }); |
| | | // 先把受控中心 lat/lng 更新到车辆位置,再切 scale; |
| | | // 否则 setMapScale 触发的 React 重渲染会把 <Map> 按陈旧的中心+新 scale 重新定位,导致车辆离开视区。 |
| | | const vehicleLat = deps.vehicleLocation?.latitude; |
| | | const vehicleLng = deps.vehicleLocation?.longitude; |
| | | const hasVehicle = Number.isFinite(vehicleLat) && Number.isFinite(vehicleLng); |
| | | if (hasVehicle) { |
| | | deps.setLatitude(vehicleLat as number); |
| | | deps.setLongitude(vehicleLng as number); |
| | | } |
| | | |
| | | deps.setMapScale(targetScale); |
| | | // scale 应用后,让 use-map-viewport 的安全区同步逻辑(force 模式)按 targetMapRatio 把车辆精确居中。 |
| | | // 必须等 React 重渲染 + 原生地图应用新 scale 后再触发,否则 syncActive 内部 getRegion() 拿到的 latSpan |
| | | // 还是旧 scale 下的值,算出的偏移会失真。 |
| | | setTimeout(() => { |
| | | deps.syncMapToVehicleSafeArea(); |
| | | }, 500); |
| | | if(targetScale > deps.mapScale) { |
| | | // 放大时才存在车辆离开视区风险,缩小时地图本身就会扩大视野包含车辆;因此仅放大时才需要等 React 重渲染后强制同步一次安全区。 |
| | | setTimeout(() => { |
| | | deps.syncMapToVehicleSafeArea(); |
| | | }, 500); |
| | | } |
| | | |
| | | }, [deps.tripStatus, deps.getDynamicScaleHoldUntil, deps.setMapScale, deps.vehicleLocation, deps.mapScale, deps.setLatitude, deps.setLongitude, deps.syncMapToVehicleSafeArea]); |
| | | |
| | | // 退出接驾/行程态时清空采样,避免跨行程污染。 |
| | |
| | | import { View, Map, Text, Image, ScrollView } from '@tarojs/components'; |
| | | import { useMemo } from 'react'; |
| | | import Taro from '@tarojs/taro'; |
| | | import CustomerServicePopup from '../../../components/customer-service'; |
| | | import TipPopup from '../../../components/tip-popup'; |
| | | import { formatTripDistance, formatTripDurationFromSeconds } from '../../../../utils/Robotaxi-public-method'; |
| | |
| | | const shouldShowSelectedSiteEcho = !showRangeWarning && Boolean(deps.selectedSite.name); |
| | | const shouldShowStationSelection = deps.selectionMode === 'station' && !showRangeWarning; |
| | | |
| | | // 搜索框右侧与小程序胶囊按钮右边缘对齐 |
| | | const searchCardRightPx = useMemo(() => { |
| | | try { |
| | | const menuButton = Taro.getMenuButtonBoundingClientRect(); |
| | | const systemInfo = Taro.getSystemInfoSync(); |
| | | return systemInfo.screenWidth - menuButton.right; |
| | | } catch { |
| | | return 7; // fallback |
| | | } |
| | | }, []); |
| | | |
| | | return ( |
| | | <View className="home-container"> |
| | | <Map |
| | |
| | | style={{ width: '100%', height: '100%' }} |
| | | > |
| | | {/* 返回 */} |
| | | <View onClick={deps.toBack} className="select-city" style={{ left: `${searchCardRightPx}px` }}> |
| | | <View onClick={deps.toBack} className="select-city"> |
| | | <Image className="select-city-icon" src={deps.backIcon} /> |
| | | </View> |
| | | {/* 搜索框 */} |
| | | <View onClick={deps.toSiteSearch} className="search-card" style={{ left: `${searchCardRightPx}px`, right: `${searchCardRightPx}px` }}> |
| | | <View onClick={deps.toSiteSearch} className="search-card"> |
| | | <View className={`search-icon ${deps.pageType == 'end' ? 'end-icon' : ''}`}></View> |
| | | <View className="search-input-box"> |
| | | <View className="search-input-placeholder">{deps.pageType == 'start' ? '请输入上车点' : '请输入目的地'}</View> |
| | |
| | | .select-city { |
| | | position: fixed; |
| | | top: 92px; |
| | | left: 31px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | |
| | | .search-card { |
| | | position: fixed; |
| | | top: 180px; |
| | | left: 31px; |
| | | right: 31px; |
| | | display: flex; |
| | | align-items: center; |
| | | border-radius: 24px; |
| | |
| | | } |
| | | |
| | | .show-more { |
| | | -webkit-tap-highlight-color: transparent; |
| | | padding-bottom: 22px; |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | recommendList.length > 0 && |
| | | <View className='recommend-card'> |
| | | <View className='card-header'>{searchValue ? '搜索站点' : '推荐站点'}</View> |
| | | <View className='card-content list-content' id='recommend-card-content'> |
| | | <View className='card-content list-content' id='recommend-card-content' key={`recommend-${showAllRecommend ? 'expanded' : 'collapsed'}`}> |
| | | { |
| | | renderSiteItems(visibleRecommend, (item, index = 0) => { |
| | | if (pageType === 'start' && searchValue.trim()) { |
| | |
| | | } |
| | | </View> |
| | | {recommendList.length > MAX_SHOW_COUNT && ( |
| | | <View className='show-more' onClick={toggleRecommendShow}> |
| | | <View className='show-more' hoverClass='none' onClick={toggleRecommendShow}> |
| | | <Text className='more-text'>{showAllRecommend ? '收起' : '展示更多'}</Text> |
| | | <Image className={`more-icon ${showAllRecommend ? 'rotate-up' : ''}`} src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/arrow.png"></Image> |
| | | </View> |