hejianhao
2026-03-28 0d864cc9c143e1e64161dd4cddf24e910ef6d431
fix: 修复路线擦除算法和车辆位置更新逻辑

- 移除 `erasePassedRouteSegments` 对 `served` 参数的依赖,改为纯距离匹配逻辑
- 优化路线匹配算法,避免因索引回退导致的路线全量重绘
- 调整车辆位置更新时机,增加延迟确保擦除指令先于平移执行
- 修复状态6下返回按钮功能,使其能回到车辆当前位置
- 修正Android原生callout的显示状态条件
- 为取消按钮增加外层容器以修复布局问题
3个文件已修改
168 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/calling-car.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -257,7 +257,7 @@
        } else if (tripStatus === 7) {
            const cachedEndTime = Taro.getStorageSync(FINISH_END_TIME_KEY);
            const hasValidCached = typeof cachedEndTime === 'number' && cachedEndTime > Date.now();
            // 无论 finishCountdownEnabled 是否为 true,只要有缓存或者接口返回数据就应该展示倒计时
            const initFinishCountdown = (endTimeMs: number | null) => {
                finishEndTimeRef.current = endTimeMs;
@@ -553,7 +553,9 @@
                </View>
                {
                    [3, 4, 5].includes(tripStatus) &&
                    <Image onClick={() => handleOpenCancelPopup?.(durationDisplay)} src={require('../../../assets/cancel-btn.png')} className="cancel" />
                    <View className="cancel-btn">
                        <Image onClick={() => handleOpenCancelPopup?.(durationDisplay)} src={require('../../../assets/cancel-btn.png')} className="cancel" />
                    </View>
                }
            </View>
            {
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -59,10 +59,16 @@
            color: #F53F3F;
        }
        .cancel {
            flex-shrink: 0;
            width: 144px;
            height: 48px;
        .cancel-btn {
            height: 50px;
            display: flex;
            align-items: center;
            justify-content: center;
            .cancel {
                flex-shrink: 0;
                width: 144px;
                height: 48px;
            }
        }
    }
src/robotaxi/pages/begin-route/index.tsx
@@ -507,59 +507,40 @@
  };
  // 使用 eraseLines 擦除已行驶路段(状态4/6专用)
  const erasePassedRouteSegments = (newPoints: any[], served?: number) => {
  const erasePassedRouteSegments = (newPoints: any[]) => {
    const oldPoints = currentFullRouteRef.current;
    if (!oldPoints?.length || !newPoints?.length) return newPoints?.[0];
    if (!oldPoints?.length || !newPoints?.length) return;
    const newFirst = newPoints[0];
    let bestMatchIndex = -1;
    let bestDistance = Infinity;
    const startIndex = lastErasedIndexRef.current;
    // 如果后端提供了明确的已行驶点数 served (作为增量:这次少了几个点)
    if (typeof served === 'number' && !Number.isNaN(served) && served >= 0) {
      bestMatchIndex = startIndex + served;
      bestMatchIndex = Math.min(bestMatchIndex, oldPoints.length - 1);
    } else {
      let bestDistance = Infinity;
      // 降级使用距离匹配逻辑,移除不合理的退出条件,使匹配更加敏感
      for (let i = startIndex; i < oldPoints.length; i++) {
        const dist = getDistance(
          oldPoints[i].latitude, oldPoints[i].longitude,
          newFirst.latitude, newFirst.longitude
        );
        if (dist < bestDistance) {
          bestDistance = dist;
          bestMatchIndex = i;
        }
        // 如果找到了非常近的点(例如10米内),就可以认为这就是车辆当前位置所在的索引,提前结束查找
        if (bestDistance < 10) break;
    for (let i = startIndex; i < oldPoints.length; i++) {
      const dist = getDistance(
        oldPoints[i].latitude, oldPoints[i].longitude,
        newFirst.latitude, newFirst.longitude
      );
      if (dist < bestDistance) {
        bestDistance = dist;
        bestMatchIndex = i;
      }
      // 已找到足够近的匹配点且距离开始增大,提前退出
      if (dist > bestDistance * 3 && bestDistance < 100) break;
    }
    if (bestMatchIndex < startIndex) {
      // 路线发生较大变化,回退为全量重绘
      currentFullRouteRef.current = [...newPoints];
      lastErasedIndexRef.current = 0;
      lastRoutePointRef.current = null;
      lastRouteUpdateAtRef.current = 0;
      setRoutePoints([...newPoints]);
      return newPoints[0];
    }
    // 只要找到了比之前更大的索引,就进行擦除(无论距离多远,只要是往后走就应该擦除)
    if (bestMatchIndex > startIndex) {
    if (bestMatchIndex >= startIndex && bestDistance < 500) {
      const mapCtx = Taro.createMapContext('map');
      const eraseIndex = Math.max(0, bestMatchIndex - 1);
      const erasePoint = oldPoints[bestMatchIndex];
      mapCtx.eraseLines({
        lines: [{
          id: 2001,
          index: eraseIndex,
          point: {
            latitude: erasePoint.latitude,
            longitude: erasePoint.longitude,
            latitude: newFirst.latitude,
            longitude: newFirst.longitude,
          },
          clear: true
        }],
@@ -572,13 +553,14 @@
      });
      lastErasedIndexRef.current = bestMatchIndex;
    } else {
      // 路线发生较大变化,回退为全量重绘
      currentFullRouteRef.current = [...newPoints];
      lastErasedIndexRef.current = 0;
      lastRoutePointRef.current = null;
      lastRouteUpdateAtRef.current = 0;
      setRoutePoints([...newPoints]);
    }
    const nextIndex = Math.min(bestMatchIndex + 1, oldPoints.length - 1);
    const pointA = oldPoints[bestMatchIndex];
    const pointB = oldPoints[nextIndex];
    return getProjectedPoint(newFirst, pointA, pointB);
  };
  // 更新车辆位置(移除节流,实时更新)
@@ -937,7 +919,7 @@
    };
    orderRouteHandlerRef.current = (payload) => {
      if ([5, 8, 9].includes(tripStatusRef.current)) {
      if (tripStatusRef.current === 5) {
        return;
      }
      const points = payload?.points || [];
@@ -959,27 +941,10 @@
      // 状态4/6:使用 eraseLines 擦除已行驶路段,不再全量重绘
      const isEraseStatus = [4, 6].includes(tripStatusRef.current);
      if (isEraseStatus) {
        let vehiclePoint = points?.[0];
        const firstPoint = points?.[0];
        if (orderRouteInitializedRef.current && currentFullRouteRef.current.length > 0) {
          // 后续更新:擦除已行驶路段
          let servedCount = 0;
          try {
            const servedData = JSON.parse(payload?.content?.route || '{}')?.served;
            // 尝试将 served 解析为数组,获取其长度,否则如果是数字则直接使用,或者是字符串数组再解析
            if (Array.isArray(servedData)) {
              servedCount = servedData.length;
            } else if (typeof servedData === 'string') {
              const parsedServed = JSON.parse(servedData);
              if (Array.isArray(parsedServed)) {
                servedCount = parsedServed.length;
              }
            } else if (typeof servedData === 'number') {
              servedCount = servedData;
            }
          } catch (e) {
            console.error('解析 served 数据失败', e);
          }
          vehiclePoint = erasePassedRouteSegments(points, servedCount) || points?.[0];
          erasePassedRouteSegments(points);
        } else {
          // 首次绘制:全量渲染并存储完整路线
          orderRouteInitializedRef.current = true;
@@ -989,8 +954,12 @@
            applyRoutePoints(points);
          });
        }
        if (vehiclePoint && typeof vehiclePoint.latitude === 'number' && typeof vehiclePoint.longitude === 'number') {
          updateVehicleLocationThrottled(vehiclePoint.latitude, vehiclePoint.longitude, true);
        // 延迟执行车辆位置更新,确保路线擦除(同步调用或极短时间生效)先于小车平移发生
        if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
          setTimeout(() => {
            updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
          }, 50); // 50ms 延迟足以让擦除指令先发给底层地图
        }
        return;
      }
@@ -1177,7 +1146,7 @@
  useEffect(() => {
    const pickupSite = startSite?.latitude && startSite?.longitude ? startSite : Taro.getStorageSync('startSite');
    const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
    const useAndroidNativeTrip1Callout = isAndroid && [1, 8, 9, 10].includes(tripStatus);
    const useAndroidNativeTrip1Callout = isAndroid && [1, 5, 8, 9, 10].includes(tripStatus);
    const pickupMarker = pickupSite?.latitude && pickupSite?.longitude ? {
      id: 1,
      latitude: pickupSite.latitude,
@@ -1424,22 +1393,27 @@
    if (!carMarkerInitialized.current) return;
    if (!vehicleLocation) return;
    const mapCtx = Taro.createMapContext('map');
    // Taro.nextTick(() => {
    mapCtx.translateMarker({
      markerId: vehicleMarkerId,
      destination: {
        latitude: vehicleLocation.latitude,
        longitude: vehicleLocation.longitude,
      },
      autoRotate: false,
      rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0,
      duration: 100,
      fail: (err) => {
        console.log(err);
    // 增加延迟,确保 markers 已经在原生视图中完成渲染,避免 translateMarker 找不到 markerId 导致 fail
    const timer = setTimeout(() => {
      mapCtx.translateMarker({
        markerId: vehicleMarkerId,
        destination: {
          latitude: vehicleLocation.latitude,
          longitude: vehicleLocation.longitude,
        },
        autoRotate: false,
        rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0,
        duration: 100,
        fail: (err) => {
          console.log('translateMarker fail:', err, 'markerId:', vehicleMarkerId);
          // 如果移动失败,可能是 marker 被意外卸载,强制解除一次拦截以重绘
          tripStatus6MarkerRefreshedRef.current = false;
        },
      });
    }, 100);
      },
    });
    // });
    return () => clearTimeout(timer);
  }, [tripStatus, vehicleLocation, vehicleDirection, vehicleMarkerId]);
  const bindSocketListeners = () => {
@@ -1608,10 +1582,20 @@
      })
  }
  // 返回当前位置
  // 返回当前位置或车辆位置
  const toOrigin = () => {
    const mapCtx = Taro.createMapContext('map');
    mapCtx.moveToLocation();
    // 如果是状态6(车辆行驶中)且有车辆位置,则回到车辆位置
    if (tripStatus === 6 && vehicleLocation?.latitude && vehicleLocation?.longitude) {
      mapCtx.moveToLocation({
        latitude: vehicleLocation.latitude,
        longitude: vehicleLocation.longitude,
      });
      // 恢复视口锁定状态,让地图重新跟随车辆
      tripStatus6ViewportLockedRef.current = false;
    } else {
      mapCtx.moveToLocation();
    }
  }
  const setPopupVisible = (setter: (value: boolean) => void, visible: boolean) => () => setter(visible);
@@ -2051,7 +2035,7 @@
      >
        {/* 自定义callout */}
        {
          !(isAndroid && [1, 8, 9].includes(tripStatus)) && markers.map((item, index) => {
          !(isAndroid && [1, 5, 8, 9, 10].includes(tripStatus)) && markers.map((item, index) => {
            const calloutKey = `callout-${item.id}-${item.name || ''}`;
            const isWaiting = [2, 3].includes(tripStatus) && index === 0;