hejianhao
2026-06-03 af3bb4993760f0c0d461fe09bf30762b1dde9583
fix: 优化路线点击交互并调整多处UI细节

- 下调路线点击命中阈值至20米,提升点击精准度
- 重写路线交互点击逻辑,移除路线点降采样步骤,修复距离计算误差问题
- 新增未选中路线优先判定逻辑,修复多路线切换点击异常问题
- 调整站点搜索页"显示更多"区域的间距样式
- 移除站点列表项收藏按钮的冗余hover类配置
- 更新计价详情卡片的箭头图标资源与旋转角度
6个文件已修改
85 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-interaction.ts 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/components/site-list-item.tsx 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
@@ -84,7 +84,7 @@
                                    fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney > 0 &&
                                    <View className="car-card-end-particulars-money-through">{formatMoney(fareBreakdown?.orderMoney)}元</View>
                                }
                                <Image style={{ transform: showDetail ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.3s' }} src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/ccc-icon-right.png" />
                                <Image style={{ transform: showDetail ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.3s' }} src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/coupon-arrow.png" />
                            </View>
                    }
                </View>
src/robotaxi/pages/begin-route/hooks/use-map-viewport.ts
@@ -478,11 +478,17 @@
                deps.setLongitude(res.longitude);
                deps.setLatitude(res.latitude);
                if (deps.tripStatus === TripStatus.InService && deps.routePoints.length > 0) {
                    const bottomPadding = computeBottomPadding(deps.routePoints, 'route');
                    const { dropoffSite } = resolveViewportSites();
                    const dropoffPoint = buildSiteViewportPoint(dropoffSite);
                    const pointsToInclude = [...deps.routePoints];
                    if (dropoffPoint) pointsToInclude.push(dropoffPoint);
                    const bottomPadding = computeBottomPadding(pointsToInclude, 'route');
                    const topPadding = Math.max(24, resolveSafeTopInset() + 32);
                    holdDynamicScale();
                    Taro.createMapContext('map').includePoints({
                        points: deps.routePoints,
                        points: pointsToInclude,
                        padding: [topPadding, 100, bottomPadding, 100]
                    });
                    tripStatus6ViewportLockedRef.current = true;
src/robotaxi/pages/begin-route/hooks/use-route-interaction.ts
@@ -243,14 +243,15 @@
    if (polylineId !== undefined && polylineId !== null) {
      const matchedIndex = Number(polylineId) - ROUTE_POLYLINE_BASE_ID;
      if (matchedIndex >= 0 && matchedIndex < allRoutePoints.length) {
        targetIndex = matchedIndex;
        // 如果点中的确实是未选中的线,直接信任原生事件
        if (matchedIndex !== selectedRouteIndex) {
          return matchedIndex;
        }
      }
    }
    if (targetIndex !== -1) {
      return targetIndex;
    }
    // 原生抛出的 polylineId 不可靠(没点中,或是点中了已被选中的线)
    // 使用经纬度距离计算兜底
    const tapLatitude = Number(event?.detail?.latitude ?? event?.latitude);
    const tapLongitude = Number(event?.detail?.longitude ?? event?.longitude);
    if (!Number.isFinite(tapLatitude) || !Number.isFinite(tapLongitude)) {
@@ -258,26 +259,73 @@
    }
    let nearestDistance = Infinity;
    let fallbackIndex = -1;
    allRoutePoints.forEach((routeLine, index) => {
      if (!Array.isArray(routeLine) || routeLine.length === 0) {
        return;
      }
      const sampledPoints = reducePoints(routeLine, resolveRoutePointLimit(deps.isAndroid));
      sampledPoints.forEach((point: any) => {
      // 不进行降采样,直接使用全量点位进行计算,解决因为抽稀导致的距离计算误差过大的问题
      routeLine.forEach((point: any) => {
        const latitude = Number(point?.latitude);
        const longitude = Number(point?.longitude);
        if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
          return;
        }
        const distance = getDistance(tapLatitude, tapLongitude, latitude, longitude);
        // 如果找到了更近的线,记录下来。
        // 但如果距离非常接近(比如相差小于 50 米),并且其中一条是未选中的线,优先选择未选中的线。
        if (distance < nearestDistance) {
          nearestDistance = distance;
          targetIndex = index;
          if (
            fallbackIndex !== -1 &&
            fallbackIndex !== selectedRouteIndex &&
            index === selectedRouteIndex &&
            Math.abs(distance - nearestDistance) < 50
          ) {
            // 保留未选中的线,不更新 fallbackIndex
          } else {
            nearestDistance = distance;
            fallbackIndex = index;
          }
        }
      });
    });
    return nearestDistance > ROUTE_TAP_MAX_DISTANCE_METERS ? -1 : targetIndex;
    // 如果最近的线是当前已选中的线,但距离未选中的线也很近(比如相差不到 150 米),
    // 则认为用户可能想点击的是未选中的线(因为未选中的线被压在下面或者有偏差)
    if (fallbackIndex === selectedRouteIndex) {
      let secondNearestDistance = Infinity;
      let secondFallbackIndex = -1;
      allRoutePoints.forEach((routeLine, index) => {
        if (!Array.isArray(routeLine) || routeLine.length === 0 || index === selectedRouteIndex) {
          return;
        }
        // 同上,不进行降采样,使用全量点计算
        routeLine.forEach((point: any) => {
          const latitude = Number(point?.latitude);
          const longitude = Number(point?.longitude);
          if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
            return;
          }
          const distance = getDistance(tapLatitude, tapLongitude, latitude, longitude);
          if (distance < secondNearestDistance) {
            secondNearestDistance = distance;
            secondFallbackIndex = index;
          }
        });
      });
      // 如果第二近的线(未选中)也在合理点击范围内(比如 2000 米内),且距离没有离谱地大,优先判定为切换路线
      if (secondFallbackIndex !== -1 && secondNearestDistance <= 2000) {
        return secondFallbackIndex;
      }
    }
    // 放宽点击命中阈值,避免在地图缩小(比例尺较小)时,地理距离阈值在屏幕上仅有几个像素导致无法命中
    if (fallbackIndex !== -1 && nearestDistance <= 1000) {
      return fallbackIndex !== selectedRouteIndex ? fallbackIndex : -1;
    }
    return -1;
  };
  /**
src/robotaxi/pages/begin-route/hooks/use-route-manager.shared.ts
@@ -9,7 +9,7 @@
/** 步行路线 polyline 起始偏移量。 */
export const WALKING_POLYLINE_ID_OFFSET = 2002;
/** 路线点击命中阈值,单位米。 */
export const ROUTE_TAP_MAX_DISTANCE_METERS = 120;
export const ROUTE_TAP_MAX_DISTANCE_METERS = 20;
/** 路线擦除命中阈值,单位米。 */
export const ROUTE_ERASE_MAX_DISTANCE_METERS = 120;
/** 擦线扫描提前终止的倍数阈值。 */
src/robotaxi/pages/site-search/components/site-list-item.tsx
@@ -176,7 +176,6 @@
          <View
            className='collect-box'
            hoverStopPropagation
            hoverClass='common-hover-class'
            onClick={(e) => {
              e.stopPropagation();
              handleCollect(e);
src/robotaxi/pages/site-search/index.less
@@ -223,7 +223,7 @@
  }
  .show-more {
    margin-bottom: 22px;
    padding-bottom: 22px;
    display: flex;
    align-items: center;
    justify-content: center;