src/pages/cashier/index.tsx
@@ -2,7 +2,7 @@ import { Component } from 'react'; import { connect } from 'react-redux'; import { View, Text, Image, Button } from '@tarojs/components'; import { dispatchGetOrderInfo, dispatchMakePay, dispatchGetResult, dispatchCreditPaySign, dispatchCreditPaySignQuery, dispatchCreditOrderCreate, dispatchCreditOrderQuery } from '../../actions/cashier'; import { dispatchGetOrderInfo, dispatchMakePay, dispatchGetResult } from '../../actions/cashier'; import { getStorage } from '../../utils/tools'; import { jumpTo } from '../../utils/jump'; import { dispatchSubscribeCreateFree } from '../../actions/im'; @@ -17,10 +17,6 @@ dispatchMakePay: any dispatchGetResult: any dispatchSubscribeCreateFree: any dispatchCreditPaySign: any dispatchCreditPaySignQuery: any dispatchCreditOrderCreate: any dispatchCreditOrderQuery: any } type State = { @@ -32,7 +28,7 @@ const ENV = process.env.TARO_ENV; // @ts-ignore @connect(() => ({}), { dispatchGetOrderInfo, dispatchMakePay, dispatchGetResult, dispatchSubscribeCreateFree, dispatchCreditPaySign, dispatchCreditPaySignQuery, dispatchCreditOrderCreate, dispatchCreditOrderQuery }) @connect(() => ({}), { dispatchGetOrderInfo, dispatchMakePay, dispatchGetResult, dispatchSubscribeCreateFree }) /** * 收银台页面 * @@ -129,7 +125,7 @@ return; } if (ENV === 'weapp') { if (item.payType === 1 || item.payType === 11 || item.payType === 17 || item.payType === 19 || item.payType === 13) { if (item.payType === 1 || item.payType === 11 || item.payType === 17 || item.payType === 19) { payType = item.payType; } } else if (ENV === 'alipay') { @@ -165,12 +161,6 @@ const { payType } = this.state; // 微信支付分走单独流程 if (payType === 13) { this.creditPay(); return; } const { openId } = await this.$app.getIdData() || {}; getStorage(_UID) .then(res => { @@ -199,297 +189,6 @@ console.log(err) }) .then(() => this.loading = false); } // 微信支付分支付流程 creditPay = async () => { try { // 第一步:查询微信支付分开通状态 const status = await this.queryCreditPaySignStatus(); console.log('creditPay sign status:', status); if (status === 'VALID') { // 已开通,进入信用下单流程 await this.creditOrderCreate(); } else if (status === 'INVALID' || status === 'INIT') { // 未签约,走签约开通流程 await this.creditPaySign(); } else { Taro.showToast({ title: `当前状态: ${status || '未知'},暂不支持`, icon: 'none' }); this.resetLoading(); } } catch (err) { console.error('creditPay error:', err); Taro.showToast({ title: '查询支付分状态失败', icon: 'none' }); this.resetLoading(); } } // 微信支付分签约开通 creditPaySign = async () => { try { // 调用签约接口获取 signStr(预授权token) const signRes = await this.props.dispatchCreditPaySign({ token: this.token, payType: 13, returnUrl: null, cancelUrl: null, }); console.log('creditPaySign result:', signRes); const signStr = signRes?.signStr; if (!signStr) { Taro.showToast({ title: '获取授权信息失败', icon: 'none' }); this.resetLoading(); return; } // 拉起微信支付分授权窗口 // @ts-ignore if (wx.openBusinessView) { // 即将跳转到微信侧,解锁 loading(用户在外部小程序中无法操作当前页面) this.resetLoading(); // @ts-ignore wx.openBusinessView({ businessType: 'wxpayScoreEnable', extraData: { apply_permissions_token: signStr, }, success: () => { console.log('openBusinessView success'); }, fail: (err) => { console.error('openBusinessView fail:', err); Taro.showToast({ title: '拉起授权失败', icon: 'none' }); }, complete: () => { console.log('openBusinessView complete'); }, }); // 监听从微信支付分小程序返回 const handleAppShow = async (res) => { if (res && res.scene === 1038) { const { appId } = res.referrerInfo || {}; if (appId === 'wxd8f3793ea3b935b8') { Taro.offAppShow(handleAppShow); this.appShowListener = null; // 用户返回,重新加锁 this.loading = true; try { const newStatus = await this.queryCreditPaySignStatus(); console.log('sign callback status:', newStatus); if (newStatus === 'VALID') { Taro.showToast({ title: '开通成功', icon: 'success' }); await this.creditOrderCreate(); } else { Taro.showToast({ title: '开通未完成', icon: 'none' }); this.resetLoading(); } } catch (e) { console.error('query sign status after auth error:', e); Taro.showToast({ title: '查询签约结果失败', icon: 'none' }); this.resetLoading(); } } } }; this.appShowListener = handleAppShow; Taro.onAppShow(handleAppShow); } else { Taro.showToast({ title: '请升级微信版本后重试', icon: 'none' }); this.resetLoading(); } } catch (err) { console.error('creditPaySign error:', err); Taro.showToast({ title: '签约请求失败', icon: 'none' }); this.resetLoading(); } } // 信用下单 creditOrderCreate = async () => { try { Taro.showLoading({ title: '下单中...' }); const { openId } = await this.$app.getIdData() || {}; const orderRes = await this.props.dispatchCreditOrderCreate({ token: this.token, payType: 13, openId, }); Taro.hideLoading(); console.log('creditOrderCreate result:', orderRes); if (!orderRes) { Taro.showToast({ title: '信用下单失败', icon: 'none' }); this.resetLoading(); return; } const { businessOrderId, authOrderId, channelOrderId } = orderRes; const packageStr = orderRes.package; // 拉起微信支付分订单确认页 // @ts-ignore if (wx.openBusinessView) { // 即将跳转到微信侧,解锁 loading this.resetLoading(); // @ts-ignore wx.openBusinessView({ businessType: 'wxpayScoreUse', extraData: { package: packageStr, }, success: () => { console.log('openBusinessView wxpayScoreUse success'); }, fail: (err) => { console.error('openBusinessView wxpayScoreUse fail:', err); Taro.showToast({ title: '拉起支付分订单失败', icon: 'none' }); }, complete: () => { console.log('openBusinessView wxpayScoreUse complete'); }, }); // 监听从微信支付分小程序返回 const handleAppShow = async (res) => { if (res && res.scene === 1038) { const { appId } = res.referrerInfo || {}; if (appId === 'wxd8f3793ea3b935b8') { Taro.offAppShow(handleAppShow); this.appShowListener = null; this.loading = true; // 轮询查询信用订单状态 await this.pollCreditOrderStatus({ businessOrderId, authOrderId, channelOrderId }); } } }; this.appShowListener = handleAppShow; Taro.onAppShow(handleAppShow); } else {/* */ Taro.showToast({ title: '请升级微信版本后重试', icon: 'none' }); this.resetLoading(); } } catch (err) { Taro.hideLoading(); console.error('creditOrderCreate error:', err); Taro.showToast({ title: '信用下单失败', icon: 'none' }); this.resetLoading(); } } // 轮询查询信用订单状态 pollCreditOrderStatus = ({ businessOrderId, authOrderId, channelOrderId }): Promise<void> => { const maxRetries = 10; const interval = 2000; Taro.showLoading({ title: '查询订单状态...' }); return new Promise((resolve) => { let retries = 0; const poll = async () => { retries++; try { const queryRes = await this.props.dispatchCreditOrderQuery({ token: this.token, businessOrderId, authOrderId, channelOrderId, payType: 13, }); console.log('creditOrderQuery result:', queryRes); const status = queryRes?.creditOrderStatus; if (status === 'DOING' || status === 'DONE') { Taro.hideLoading(); this.resetLoading(); this.creditOrderSuccess(queryRes); resolve(); return; } if (status === 'REVOKED' || status === 'EXPIRED') { Taro.hideLoading(); this.resetLoading(); Taro.showToast({ title: '订单已取消或失效', icon: 'none' }); resolve(); return; } if (retries >= maxRetries) { Taro.hideLoading(); this.resetLoading(); Taro.showToast({ title: '查询超时,请稍后再试', icon: 'none' }); resolve(); return; } // 继续轮询 this.timer = setTimeout(poll, interval); } catch (err) { console.error('pollCreditOrderStatus error:', err); if (retries >= maxRetries) { Taro.hideLoading(); this.resetLoading(); Taro.showToast({ title: '查询订单状态失败', icon: 'none' }); resolve(); return; } // 出错也继续重试 this.timer = setTimeout(poll, interval); } }; poll(); }); } // 统一重置 loading 状态 resetLoading = () => { this.loading = false; } // 信用订单成功回调 creditOrderSuccess = (orderRes) => { console.log('creditOrderSuccess:', orderRes); if (this.from === 'app') { this.setState({ showModal: true }); return; } if (this.from === 'miniapp') { Taro.eventCenter.trigger('cashier:paySuccess', { businessOrderId: orderRes.businessOrderId, channelOrderId: orderRes.channelOrderId, authOrderId: orderRes.authOrderId, creditOrderStatus: orderRes.creditOrderStatus, }); jumpTo({ type: 'back' }); return; } } // 查询微信支付分签约状态 queryCreditPaySignStatus = async (): Promise<string> => { const signRes = await this.props.dispatchCreditPaySignQuery({ token: this.token, payType: 13, }); console.log('creditPaySignQuery result:', signRes); const signList = signRes?.signList || []; const signItem = signList.find((item: any) => String(item.payType) === '13'); if (signItem) { return signItem.status || ''; } // 未签约时 signList 仅一项且 payType 为空 if (signList.length === 1 && (signList[0].payType === '' || signList[0].payType == null)) { return signList[0]?.status || ''; } return ''; } // 银联云闪付支付 @@ -720,7 +419,7 @@ {ENV === 'weapp' && this.from !== 'app' && ( <View className='pay-list'> {payTypeList.map(item => { //普通微信:1, 建行在微信:17,微信支付分13 //普通微信:1, 建行在微信:17 if (item.payType === 1 || item.payType === 11 || item.payType === 17) { return ( <View key={item.payType} className='pay-item' onClick={() => this.selectPayType(item.payType)}> @@ -739,19 +438,6 @@ <View key={item.payType} className='pay-item' onClick={() => this.selectPayType(item.payType)}> <Image className='icon' src={item.icon}></Image> <View className='name'>{item.description}</View> {payType === item.payType ? ( <Image className='ic-right' src={require('./assets/ic_select.png')}></Image> ) : ( <Image className='ic-right' src={require('./assets/ic_se_default.png')}></Image> )} </View> ); } if (item.payType === 13) { return ( <View key={item.payType} className='pay-item' onClick={() => this.selectPayType(item.payType)}> <Image className='icon' src={require('./assets/ic_wxf.png')}></Image> <View className='name'>微信支付分 | 先乘后付</View> {payType === item.payType ? ( <Image className='ic-right' src={require('./assets/ic_select.png')}></Image> ) : ( src/pages/robotaxi/home/components/privacy-popup/index.less
@@ -1,4 +1,4 @@ .privacy-popup { .rt-privacy-popup { position: fixed; top: 0; left: 0; @@ -9,7 +9,7 @@ align-items: center; justify-content: center; .privacy-mask { .rt-privacy-mask { position: absolute; top: 0; left: 0; @@ -18,14 +18,14 @@ background: rgba(0, 0, 0, 0.5); } .privacy-content { .rt-privacy-content { position: relative; background: #ffffff; padding: 56px 40px 40px 40px; border-radius: 32px; margin: 0 75px; .title { .rt-title { margin-bottom: 34px; text-align: center; font-size: 36px; @@ -35,7 +35,7 @@ color: rgba(0, 0, 0, 0.9); } .privacy-text { .rt-privacy-text { font-size: 28px; font-weight: normal; line-height: 48px; @@ -43,28 +43,28 @@ color: rgba(0, 0, 0, 0.9); margin-bottom: 0; .txt-tip { .rt-txt-tip { margin-top: 24px; } } .privacy-link { .rt-privacy-link { margin-top: 16px; .link-item { .rt-link-item { display: flex; align-items: flex-start; margin-bottom: 8px; } .item-radio { .rt-item-radio { flex-shrink: 0; width: 36px; height: 36px; margin-right: 14px; } .item-name { .rt-item-name { flex: 1; font-size: 24px; font-weight: normal; @@ -76,7 +76,7 @@ } } .agree-btn { .rt-agree-btn { margin: 0 auto; margin-top: 30px; width: 472px; @@ -90,11 +90,11 @@ color: #FFFFFF; } .btn-no-agree { .rt-btn-no-agree { background: rgba(0, 0, 0, 0.1); } .close-txt { .rt-close-txt { text-align: center; margin-top: 40px; font-size: 28px; src/pages/robotaxi/home/components/privacy-popup/index.tsx
@@ -135,40 +135,40 @@ if (!visible) return null; return ( <View className='privacy-popup' catchMove> <View className='privacy-mask'></View> <View className='privacy-content' animation={animationData}> <View className='title'>用户协议与隐私政策</View> <View className='rt-privacy-popup' catchMove> <View className='rt-privacy-mask'></View> <View className='rt-privacy-content' animation={animationData}> <View className='rt-title'>用户协议与隐私政策</View> <View className='privacy-text'> <Text className='text-content'> <View className='rt-privacy-text'> <Text className='rt-text-content'> 欢迎使用小鹏Robotaxi小程序!为了更好地保障您的个人权益,我们将通过 {agreementNamesText || ''} 等文档来保护您的个人信息。 </Text> <View className='txt-tip'>我已满十四周岁,阅读并同意:</View> <View className='rt-txt-tip'>我已满十四周岁,阅读并同意:</View> </View> <View className='privacy-link'> <View className='rt-privacy-link'> { agreementList.map((item) => ( <View className='link-item' key={String(item.id)}> <Image onClick={checkItem(String(item.id))} className='item-radio' src={isAgree[String(item.id)] ? 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/check.png' : 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/uncheck.png'} <View className='rt-link-item' key={String(item.id)}> <Image onClick={checkItem(String(item.id))} className='rt-item-radio' src={isAgree[String(item.id)] ? 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/check.png' : 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/uncheck.png'} mode='aspectFill' /> <View className='item-name' onClick={() => handleLinkClick(item.id)}>《 {item.name} 》</View> <View className='rt-item-name' onClick={() => handleLinkClick(item.id)}>《 {item.name} 》</View> </View> )) } <View className='link-item'> <Image onClick={checkAll} className='item-radio' src={isAllAgree ? 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/check.png' : 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/uncheck.png'} <View className='rt-link-item'> <Image onClick={checkAll} className='rt-item-radio' src={isAllAgree ? 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/check.png' : 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/uncheck.png'} mode='aspectFill' /> <View onClick={checkAll} className='item-name'>全部勾选</View> <View onClick={checkAll} className='rt-item-name'>全部勾选</View> </View> </View> <View className={'agree-btn ' + (!isAllAgree && 'btn-no-agree')} onClick={handleAgree}> <View className={`rt-agree-btn ${!isAllAgree ? 'rt-btn-no-agree' : ''}`} onClick={handleAgree}> 同意并进入 </View> <View className='close-txt' onClick={handleClose}>暂不同意,留在游客模式</View> <View className='rt-close-txt' onClick={handleClose}>暂不同意,留在游客模式</View> </View> </View> ) src/pages/robotaxi/home/hooks/use-nearby-vehicles.ts
@@ -295,7 +295,6 @@ }, duration: ANIMATION_DURATION_MS, autoRotate: false, moveWithRotate: !isAndroidRef.current, rotate: resolveMarkerRotate( interpolateDirection(track.fromDirection, track.toDirection, 1), isAndroidRef.current src/robotaxi/api/types/begin-route.ts
@@ -417,8 +417,6 @@ duration?: NumericLike; /** 方向角。 */ direction?: NumericLike; /** 行驶速度。 */ speed?: NumericLike; } /** 订单轨迹响应 content。 */ @@ -447,8 +445,6 @@ distance?: NumericLike; /** 路线时长。 */ duration?: NumericLike; /** 行驶速度。 */ speed?: NumericLike; /** 方向角。 */ direction?: NumericLike; /** 行程状态。 */ src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx
@@ -7,10 +7,14 @@ }; export default function BluetoothOperateCard({ description, actionText = '', onClick }: Props) { const iconSrc = actionText === '去开启' ? "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/close-bluetooth.png" : "https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/bluetooth-icon.png"; return ( <View className="bluetooth-operate-card" onClick={onClick}> <View className="bluetooth-operate-card-left"> <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/bluetooth-icon.png" /> <Image src={iconSrc} /> <View>{description}</View> </View> {!!actionText && ( src/robotaxi/pages/begin-route/hooks/runtime-sync.ts
@@ -26,8 +26,6 @@ duration: number | null; /** 车辆朝向(0-359) */ direction: number | null; /** 当前速度(m/s)。 */ speedMps: number | null; /** 消息时间戳(用于乱序保护) */ timestamp: number | null; /** 已行驶轨迹点(servedPoints) */ @@ -38,6 +36,8 @@ eraseVehiclePointMode: VehiclePointMode; /** 非擦线态车辆点策略 */ normalVehiclePointMode: VehiclePointMode; /** 当前车速 (m/s) */ speed: number | null; }; /** @@ -67,34 +67,22 @@ } /** * 将后端下发的速度统一规整为 m/s。 */ export function normalizeSpeedMps(rawSpeed: any) { const speedMetersPerSecond = Number(rawSpeed); if (!Number.isFinite(speedMetersPerSecond)) { return null; } return Math.max(0, speedMetersPerSecond); } /** * 归一化 socket 路线消息,统一路线点、概要和车辆移动点结构。 */ export function normalizeSocketRouteSnapshot(payload: any): RouteSnapshot { const route = safeParse(payload?.content?.route, {}); const timestamp = Number(payload?.data?.timestamp); const speedMps = normalizeSpeedMps(route?.speed); return { points: normalizeRoutePoints(payload?.points), distance: normalizeDistanceKm(Number(route?.distance)), duration: normalizeDurationSeconds(Number(route?.duration)), direction: normalizeDirection(payload?.content?.direction), speedMps, timestamp: Number.isFinite(timestamp) ? timestamp : null, vehicleMoveAlongPoints: normalizeRoutePoints(payload?.servedPoints), forceFullRedraw: payload?.forceFullRedraw === true, eraseVehiclePointMode: 'first_then_served_last', normalVehiclePointMode: 'served_last_then_first', speed: Number.isFinite(Number(route?.speed)) ? Number(route?.speed) : null, }; } src/robotaxi/pages/begin-route/hooks/trip-motion-utils.ts
@@ -269,3 +269,29 @@ } return Math.max(searchStartDistance, bestProjectedDistance); } /** * 根据最大距离截取路线片段(用于车速分级自适应缩放视口)。 * 从起点开始,累加线段距离,直到超过 maxDistanceMeters,截取这部分点返回。 */ export function sliceRoutePointsByDistance(points: RoutePoint[], maxDistanceMeters: number): RoutePoint[] { if (!Array.isArray(points) || points.length <= 1) { return points; } const result: RoutePoint[] = [points[0]]; let accumulatedDistance = 0; for (let index = 1; index < points.length; index += 1) { const prev = points[index - 1]; const curr = points[index]; const segmentDistance = getDistanceMeters(prev, curr); if (accumulatedDistance + segmentDistance > maxDistanceMeters) { const ratio = (maxDistanceMeters - accumulatedDistance) / segmentDistance; result.push(interpolatePoint(prev, curr, ratio)); break; } else { accumulatedDistance += segmentDistance; result.push(curr); } } return result; } src/robotaxi/pages/begin-route/hooks/use-active-trip-scale.ts
@@ -1,7 +1,7 @@ import Taro from '@tarojs/taro'; import { useCallback, useMemo } from 'react'; import { useCallback, useMemo, useEffect, useRef } from 'react'; import { TripStatus } from '../constants/trip-status'; import useVehicleSpeedScale from './use-vehicle-speed-scale'; import { sliceRoutePointsByDistance } from './trip-motion-utils'; type RoutePoint = { latitude: number; @@ -11,51 +11,91 @@ type Deps = { /** 当前行程状态。 */ tripStatus: TripStatus; /** 当前速度,单位 m/s。 */ speedMps: number | null; /** 当前地图比例尺。 */ mapScale: number; /** 设置地图比例尺。 */ setMapScale: (value: number) => void; /** 动态缩放生效后同步车辆视口中心。 */ syncActiveTripViewportToVehicle: () => void; /** 车辆速度 (m/s) */ vehicleSpeed: number | null; /** 当前路线点集 */ routePoints: RoutePoint[]; /** 获取速度缩放暂停截止时间 */ getDynamicScaleHoldUntil: () => number; /** 计算 includePoints 所需底部留白。 */ computeBottomPadding: (points: any[], viewportType: string) => number; /** 读取顶部安全区高度。 */ resolveSafeTopInset: () => number; /** 暂停动态缩放。 */ holdDynamicScale: (durationMs?: number) => void; /** 读取动态缩放暂停截止时间。 */ getDynamicScaleHoldUntil: () => number; }; const ACTIVE_TRIP_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService]; // 启用速度缩放的行程状态 const USER_GESTURE_CAUSED_BY = new Set(['gesture', 'drag', 'scale']); // 需要暂停速度缩放的用户手势来源 const USER_GESTURE_HOLD_MS = 3000; // 用户手势后暂停速度缩放时长 const ORDER_UPDATE_LINE_HOLD_MS = 2500; // ORDER_UPDATE_LINE 全路线入屏后暂停速度缩放时长 const ACTIVE_TRIP_STATUSES = [TripStatus.VehicleApproaching, TripStatus.InService]; // 启用视口编排的行程状态 const USER_GESTURE_CAUSED_BY = new Set(['gesture', 'drag', 'scale']); // 需要暂停动态缩放的用户手势来源 const USER_GESTURE_HOLD_MS = 3000; // 用户手势后暂停动态缩放时长 const ORDER_UPDATE_LINE_HOLD_MS = 2500; // ORDER_UPDATE_LINE 全路线入屏后暂停动态缩放时长 const ORDER_UPDATE_LINE_SIDE_PADDING = 100; // ORDER_UPDATE_LINE 路线入屏左右留白 const ORDER_UPDATE_LINE_TOP_PADDING = 24; // ORDER_UPDATE_LINE 路线入屏最小顶部留白 const ORDER_UPDATE_LINE_TOP_EXTRA_PADDING = 32; // ORDER_UPDATE_LINE 顶部安全区附加留白 const DYNAMIC_SCALE_INTERVAL_MS = 8000; // 动态缩放巡航周期 /** * 接驾中/行程中动态缩放入口:速度缩放单独交给专用 Hook,这里仅保留视口编排职责。 * 接驾中/行程中视口编排入口:仅保留视口编排职责。 */ export default function useActiveTripScale(deps: Deps) { const isActiveStatus = useMemo(() => { return ACTIVE_TRIP_STATUSES.includes(deps.tripStatus); }, [deps.tripStatus]); useVehicleSpeedScale({ tripStatus: deps.tripStatus, speedMps: deps.speedMps, mapScale: deps.mapScale, setMapScale: deps.setMapScale, syncActiveTripViewportToVehicle: deps.syncActiveTripViewportToVehicle, getDynamicScaleHoldUntil: deps.getDynamicScaleHoldUntil }); const depsRef = useRef(deps); useEffect(() => { depsRef.current = deps; }, [deps]); /** * 处理地图手势,手动缩放/拖拽后短暂暂停速度接管。 * 基于车速区间进行路线截取,推导视口缩放。 */ const tryApplySpeedBasedScale = useCallback(() => { const currentDeps = depsRef.current; if (currentDeps.tripStatus !== TripStatus.InService) { return; // 仅在行程中开启速度级缩放 } const now = Date.now(); if (currentDeps.getDynamicScaleHoldUntil() > now) { return; // 处于手势或强行全览冷却期 } const speed = currentDeps.vehicleSpeed || 0; const points = currentDeps.routePoints; if (!Array.isArray(points) || points.length < 2) { return; } let targetDistance = 5000; // 高速段 5公里 if (speed < 8.3) { // 约 30km/h targetDistance = 300; // 低速段 300米 } else if (speed < 16.6) { // 约 60km/h targetDistance = 1500; // 中速段 1.5公里 } const slicedPoints = sliceRoutePointsByDistance(points, targetDistance); if (slicedPoints.length < 2) { return; } const mapCtx = Taro.createMapContext('map'); const bottomPadding = currentDeps.computeBottomPadding(slicedPoints, 'route'); const topPadding = Math.max(ORDER_UPDATE_LINE_TOP_PADDING, currentDeps.resolveSafeTopInset() + ORDER_UPDATE_LINE_TOP_EXTRA_PADDING); mapCtx.includePoints({ points: slicedPoints, padding: [topPadding, ORDER_UPDATE_LINE_SIDE_PADDING, bottomPadding, ORDER_UPDATE_LINE_SIDE_PADDING] }); }, []); useEffect(() => { if (!isActiveStatus) return; const timer = setInterval(() => { tryApplySpeedBasedScale(); }, DYNAMIC_SCALE_INTERVAL_MS); return () => clearInterval(timer); }, [isActiveStatus, tryApplySpeedBasedScale]); /** * 处理地图手势,手动缩放/拖拽后短暂暂停视口同步。 */ const handleRegionChange = useCallback((event: any) => { const causedBy = String(event?.causedBy || event?.detail?.causedBy || '').toLowerCase(); src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts
@@ -55,8 +55,6 @@ distanceMeters: number | null; /** 预估行程时长,单位秒。 */ durationSeconds: number | null; /** 预估速度,单位米/秒。 */ speedMps: number | null; }; /** 路线备选项。 */ routeOptions?: any[]; src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts
@@ -105,8 +105,6 @@ distanceMeters: number | null; /** 时长,单位秒。 */ durationSeconds: number | null; /** 速度,单位 m/s。 */ speedMps: number | null; }) => void; /** 设置当前用户定位。 */ setCurrentUserLocation?: (location: CurrentUserLocation) => void; src/robotaxi/pages/begin-route/hooks/use-route-request.ts
@@ -121,7 +121,6 @@ distance: normalizeDistanceKm(Number(response?.distance)), duration: normalizeDurationSeconds(Number(response?.duration)), direction, speedMps: normalizeSpeedMps(response?.speed), timestamp: Date.now(), vehicleMoveAlongPoints: [], forceFullRedraw: true, @@ -480,8 +479,7 @@ if (syncOrderRouteMetrics) { deps.setOrderRouteMetrics?.({ distanceMeters: Number.isFinite(Number(route?.distance)) ? Number(route?.distance) : null, durationSeconds: Number.isFinite(Number(route?.duration)) ? Number(route?.duration) : null, speedMps: normalizeSpeedMps(route?.speed) durationSeconds: Number.isFinite(Number(route?.duration)) ? Number(route?.duration) : null }); } @@ -562,8 +560,7 @@ deps.setOrderRouteMetrics?.({ distanceMeters: Number.isFinite(Number(response?.distance)) ? Number(response?.distance) : null, durationSeconds: Number.isFinite(Number(response?.duration)) ? Number(response?.duration) : null, speedMps: normalizeSpeedMps(response?.speed) durationSeconds: Number.isFinite(Number(response?.duration)) ? Number(response?.duration) : null }); const normalizedDirection = resolveDirectionValue(response); src/robotaxi/pages/begin-route/hooks/use-vehicle-speed-scale.ts
File was deleted src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts
@@ -58,7 +58,6 @@ longitude: task.longitude, }, autoRotate: false, moveWithRotate: !isAndroid, rotate: task.rotate, duration: TRANSLATE_DURATION_MS, fail: () => { src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -26,7 +26,6 @@ orderRouteMetrics: { distanceMeters: number | null; durationSeconds: number | null; speedMps: number | null; }; setOrderId: (orderId: string) => void; setTripStatus: (status: TripStatus) => void; @@ -58,11 +57,11 @@ setVehicleMoveAlongPoints: (points: any[]) => void; setRouteDistance: (value: any) => void; setRouteDuration: (value: number) => void; setVehicleSpeed?: (speed: number) => void; setModifyDestinationSocketStatus: (value: number | null) => void; setOrderRouteMetrics?: (metrics: { distanceMeters: number | null; durationSeconds: number | null; speedMps: number | null; }) => void; setBeforeUserEndSite: (site: { longitude: number; latitude: number } | null) => void; isModifyDestinationReviewVisible?: boolean; src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-route-socket-handler.ts
@@ -57,7 +57,6 @@ const route = safeParse(payload?.content?.route, {}); const distanceMeters = Number(route?.distance); const durationSecondsRaw = Number(route?.duration); const speedMps = normalizeSpeedMps(route?.speed); const distanceKm = normalizeDistanceKm(distanceMeters); if (distanceKm !== null) { deps.setRouteDistance(distanceKm); @@ -68,8 +67,7 @@ } deps.setOrderRouteMetrics?.({ distanceMeters: Number.isFinite(distanceMeters) ? distanceMeters : null, durationSeconds: Number.isFinite(durationSecondsRaw) ? durationSecondsRaw : null, speedMps durationSeconds: Number.isFinite(durationSecondsRaw) ? durationSecondsRaw : null }); }; @@ -107,6 +105,9 @@ return; } const routeSnapshot = normalizeSocketRouteSnapshot(payload); if (typeof routeSnapshot.speed === 'number') { currentDeps.setVehicleSpeed?.(routeSnapshot.speed); } const shouldOnlySyncModifyPreviewVehicle = isModifyDestinationPreviewActive && currentTripStatus === TripStatus.InService; const shouldFreezeModifyPreviewRoute = isModifyDestinationPreviewActive && currentTripStatus !== TripStatus.InService; if (isModifyDestinationPreviewActive) { src/robotaxi/pages/begin-route/index.tsx
@@ -99,7 +99,6 @@ type OrderRouteMetrics = { distanceMeters: number | null; durationSeconds: number | null; speedMps: number | null; }; /** @@ -203,10 +202,10 @@ const [showCouponSelectPopup, setShowCouponSelectPopup] = useState(false); // 优惠券选择弹窗展示状态 const [showModifyDestinationFailPopup, setShowModifyDestinationFailPopup] = useState(false); // 修改目的地失败弹窗 const [modifyDestinationFailMessage, setModifyDestinationFailMessage] = useState(''); // 修改目的地失败文案(默认空,避免闪文案) const [vehicleSpeed, setVehicleSpeed] = useState<number | null>(null); // 车辆当前速度 (m/s) const [orderRouteMetrics, setOrderRouteMetrics] = useState<OrderRouteMetrics>({ distanceMeters: null, durationSeconds: null, speedMps: null durationSeconds: null }); const [currentUserLocation, setCurrentUserLocation] = useState<CurrentUserLocation>(null); // 当前用户真实定位,避免被地图中心点覆盖 const [modifyDestinationSocketStatus, setModifyDestinationSocketStatus] = useState<number | null>(null); // 后端推送的修改目的地展示状态 @@ -252,8 +251,7 @@ useEffect(() => { setOrderRouteMetrics({ distanceMeters: null, durationSeconds: null, speedMps: null durationSeconds: null }); const cachedSocketStatus = Taro.getStorageSync(MODIFY_DESTINATION_SOCKET_STATUS_KEY); const cachedOrderId = String(cachedSocketStatus?.orderId || ''); @@ -624,15 +622,13 @@ } = viewportState; // 视口状态解构 const activeTripScale = useActiveTripScale({ tripStatus, speedMps: orderRouteMetrics?.speedMps, mapScale, setMapScale: viewportState.setMapScale, syncActiveTripViewportToVehicle, vehicleSpeed, routePoints, getDynamicScaleHoldUntil, computeBottomPadding, resolveSafeTopInset, holdDynamicScale, getDynamicScaleHoldUntil }); // 行程中速度缩放与 ORDER_UPDATE_LINE 视口编排 holdDynamicScale }); // 行程中 ORDER_UPDATE_LINE 视口编排 const arrivedMarkerReady = tripStatus !== TripStatus.ArrivedDestination || typeof vehicleDirection === 'number'; const arrivedTooltipReady = tripStatus !== TripStatus.ArrivedDestination || (arrivedMarkerReady && mapScale >= 20); const markerState = useMapMarkers({ @@ -929,6 +925,7 @@ setVehicleMoveAlongPoints, setRouteDistance, setRouteDuration, setVehicleSpeed, setModifyDestinationSocketStatus, setOrderRouteMetrics, setBeforeUserEndSite, src/robotaxi/pages/share-trip/hooks/use-vehicle-tracking.ts
@@ -62,7 +62,6 @@ longitude: vehicleLocation.longitude }, autoRotate: false, moveWithRotate: true, rotate: typeof vehicleDirection === "number" ? ((vehicleDirection % 360) + 360) % 360