hejianhao
2026-06-14 469e504abd618bb7b1cbfeba4a98cddb8a6b4999
Merge branch 'feature_v1.0' into feature_v1.0_fix_614
10个文件已修改
135 ■■■■■ 已修改文件
src/app.less 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/components/demo/index.tsx 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/debug-autoscale-panel.tsx 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/speed-scale-config.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-auto-route-viewport.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/index.less 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/index.less 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/index.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app.less
@@ -13,6 +13,7 @@
text,
scroll-view {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}
button {
src/robotaxi/components/demo/index.tsx
@@ -1,12 +1,28 @@
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
@@ -61,6 +77,20 @@
            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>
    )
  }
src/robotaxi/pages/begin-route/components/debug-autoscale-panel.tsx
@@ -3,13 +3,13 @@
 *
 * ============================================================================
 * 何时再用?
 *   - 调整 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 同级)
 *      引入并挂载本组件,传入页面已有的状态:
 *
@@ -39,7 +39,7 @@
 *   - 默认收起,左侧悬浮"调速"小按钮,点开展开完整面板
 *   - Mock 行程状态(接驾中 / 行程中 / 还原)
 *   - Mock 车辆位置("把车放到地图中心" / "微移车辆")
 *   - 速度滑块 + 三档预设按钮(低速 / 中速 / 高速),喂 1 条或喂满窗口
 *   - 速度滑块 + 两档预设按钮(低速 / 中高速),喂 1 条或喂满窗口
 *   - 实时显示当前 tripStatus、mapScale、vehicleLocation、当前挡位/目标 scale
 *
 * 配套 console 工具(同样受 SPEED_SCALE_DEBUG 控制):
@@ -59,8 +59,6 @@
  HIGH_SPEED_SCALE,
  LOW_SPEED_MAX_KMH,
  LOW_SPEED_SCALE,
  MID_SPEED_MAX_KMH,
  MID_SPEED_SCALE,
  resolveSpeedTierByKmh,
  SPEED_SAMPLE_WINDOW,
  SPEED_SCALE_DEBUG
@@ -87,13 +85,12 @@
/** 启用速度缩放的行程状态。 */
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'
};
@@ -117,7 +114,7 @@
 */
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);
@@ -161,8 +158,7 @@
  };
  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) {
@@ -290,8 +286,8 @@
      </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' }}>
@@ -312,8 +308,7 @@
      <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' }}>
src/robotaxi/pages/begin-route/hooks/speed-scale-config.ts
@@ -1,10 +1,9 @@
/**
 * 速度自动缩放配置(纯常量与映射,无副作用),供 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 处理。
 */
@@ -12,32 +11,27 @@
/**
 * 调试开关:实车难以跑到高速,开启后用更低的速度阈值方便测试,并启用页面调速面板。
 * 测试完成后改回 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;
};
@@ -50,10 +44,7 @@
  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 };
};
/**
src/robotaxi/pages/begin-route/hooks/use-auto-route-viewport.ts
@@ -1,6 +1,6 @@
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;
src/robotaxi/pages/begin-route/hooks/use-speed-based-scale.ts
@@ -73,23 +73,18 @@
      目标缩放等级: 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]);
  // 退出接驾/行程态时清空采样,避免跨行程污染。
src/robotaxi/pages/select-map-site/components/select-map-site-view.tsx
@@ -1,6 +1,4 @@
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';
@@ -69,17 +67,6 @@
  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
@@ -96,11 +83,11 @@
        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>
src/robotaxi/pages/select-map-site/index.less
@@ -1,6 +1,7 @@
.select-city {
    position: fixed;
    top: 92px;
    left: 31px;
    display: flex;
    align-items: center;
    justify-content: center;
@@ -20,6 +21,8 @@
.search-card {
    position: fixed;
    top: 180px;
    left: 31px;
    right: 31px;
    display: flex;
    align-items: center;
    border-radius: 24px;
src/robotaxi/pages/site-search/index.less
@@ -226,6 +226,7 @@
  }
  .show-more {
    -webkit-tap-highlight-color: transparent;
    padding-bottom: 22px;
    display: flex;
    align-items: center;
src/robotaxi/pages/site-search/index.tsx
@@ -571,7 +571,7 @@
        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()) {
@@ -594,7 +594,7 @@
            }
          </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>