hejianhao
2026-03-21 c49d27cd91a47ffa0f1514f77219a76a64ab17aa
fix(robotaxi): 修复行程状态4/6路线擦除、吸附逻辑及UI显示问题

修复行程状态4/6时路线更新使用eraseLines擦除已行驶路段,避免全量重绘
修正首页地图吸附逻辑,确保吸附时自动移动视角至站点位置
优化行程详情页时长/距离显示字段,统一使用正确数据源
调整取消弹窗文案语序,增加未服务范围提示样式和步行信息显示
移除WebSocket消息的pong过滤,统一输出所有消息日志
修复状态4/5取消后跳转逻辑,避免错误显示取消弹窗
9个文件已修改
351 ■■■■■ 已修改文件
src/pages/robotaxi/home/index.tsx 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/cancel-popup.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/expense-detail/index.tsx 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/index.less 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/index.tsx 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/Robotaxi-public-method.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx
@@ -138,8 +138,6 @@
    if (status === 10) return 9;
    return 0;
  };
  const skipFenceIncludePointsRef = useRef(false);
  // 监听页面显示
  useDidShow(() => {
    getServicePhone().then((res) => {
@@ -241,8 +239,14 @@
              return;
            }
          }
          setMapScale(16);
          setLatitude(startSite.latitude);
          setLongitude(startSite.longitude);
          const mapCtx = Taro.createMapContext('map');
          mapCtx.moveToLocation({
            latitude: startSite.latitude,
            longitude: startSite.longitude,
          });
          setCurrentSite(startSite);
          setIsAdsorbed(true);
          shouldIgnoreRegionChange.current = true;
@@ -281,7 +285,6 @@
            skipNextLocationCenterRef.current = false;
            const hasEntered = Taro.getStorageSync(FIRST_ENTER_KEY) === 'true';
            if (!hasEntered) {
              skipFenceIncludePointsRef.current = true;
              getLocationInfo(true);
              Taro.setStorageSync(FIRST_ENTER_KEY, 'true');
              return;
@@ -294,6 +297,8 @@
            getCityLocation(switchedCity.name).then((loc) => {
              shouldIgnoreRegionChange.current = true;
              skipNextLocationCenterRef.current = true;
              setLatitude(loc.latitude);
              setLongitude(loc.longitude);
              const mapCtx = Taro.createMapContext('map');
              mapCtx.moveToLocation({
                latitude: loc.latitude,
@@ -315,12 +320,15 @@
        skipNextLocationCenterRef.current = false;
        const hasEntered = Taro.getStorageSync(FIRST_ENTER_KEY) === 'true';
        if (!hasEntered) {
          skipFenceIncludePointsRef.current = true;
          getLocationInfo(true);
          Taro.setStorageSync(FIRST_ENTER_KEY, 'true');
          return;
        }
        getLocationInfo(shouldUpdateCenter);
        // 如果没有缓存上车点,且有定位权限,调用 toOrigin 回到用户当前位置
        if (!hasStartSite && auth === true) {
           toOrigin();
        }
      }
    });
  });
@@ -422,28 +430,6 @@
      if (res && Array.isArray(res) && res.length > 0) {
        const newPolygons = formatGeofencePolygons(res);
        setPolygons(newPolygons);
        const shouldIncludeOperatingArea = allowMapMove && !Taro.getStorageSync('startSite');
        if (shouldIncludeOperatingArea) {
          if (skipFenceIncludePointsRef.current) {
            skipFenceIncludePointsRef.current = false;
          } else {
            const mapCtx = Taro.createMapContext('map');
            if (!isLocationAuthDenied && typeof latitude === 'number' && typeof longitude === 'number') {
              mapCtx.moveToLocation({
                latitude,
                longitude,
              });
            } else {
              const points = extractPointsFromPolygons(newPolygons);
              if (!isLocationAuthDenied) {
                mapCtx.includePoints({
                  points: points,
                  padding: [100, 100, 100, 100]
                });
              }
            }
          }
        }
      } else {
        setPolygons([]);
        if ((orderData || orderData?.id) && JSON.stringify(orderData) != '{}') return;
@@ -548,11 +534,28 @@
          const cityInfo = await ensureSelectedCityReady(res.longitude, res.latitude, false, forceCityRefresh);
          if (cityInfo?.code) {
            const { newMarkers, sites } = await updateMapData(res.latitude, res.longitude, cityInfo.code);
            const { targetMarker, minDistance } = findNearestMarker(res.latitude, res.longitude, sites.length > 0 ? sites : newMarkers);
            if (minDistance < adsorptionDistance && targetMarker) {
            const nearestCandidates = sites.length > 0 ? sites : newMarkers;
            const { targetMarker, minDistance } = findNearestMarker(res.latitude, res.longitude, nearestCandidates);
            // 获取吸附距离(如果本地 state 还没加载完,给个默认值 1000)
            const currentAdsorptionDistance = adsorptionDistance || 1000;
            if (minDistance < currentAdsorptionDistance && targetMarker) {
              setIsAdsorbed(true);
              setCurrentSite(targetMarker);
              setShowToolTip(true);
              // 自动将地图视角移动到吸附站点
              if (updateCenter) {
                const mapCtx = Taro.createMapContext('map');
                // 使用 nextTick 或者 setTimeout 确保地图上下文能正确处理 moveToLocation
                setTimeout(() => {
                  mapCtx.moveToLocation({
                    latitude: targetMarker.latitude,
                    longitude: targetMarker.longitude,
                  });
                }, 100);
              }
              return;
            }
          }
@@ -562,12 +565,24 @@
        if (addressData) {
          const address = addressData.name || addressData.desc;
          setCurrentAddress(address);
          setCurrentSite({
            name: address,
            latitude: res.latitude,
            longitude: res.longitude
          // 如果没有被吸附(isAdsorbed为false),才使用地址名称作为当前站点的显示名称
          setCurrentSite(prevSite => {
            // 如果之前没有站点数据或者只有基础的经纬度/名字(非正式站点)
            if (!prevSite?.id) {
              return {
                name: address,
                latitude: res.latitude,
                longitude: res.longitude
              };
            }
            return prevSite;
          });
          // 仅在没有吸附到站点时才将状态置为未吸附
          // 如果前面已经因为距离近而 return 了,这里就不会执行到
          setIsAdsorbed(false);
          setShowToolTip(true);
          const cachedCity = Taro.getStorageSync('selectedCity');
          if (!cachedCity && addressData.regeocodeData && addressData.regeocodeData.addressComponent) {
@@ -825,8 +840,11 @@
            const nearestCandidates = sites.length > 0 ? sites : newMarkers;
            const { targetMarker, minDistance } = findNearestMarker(lat, lng, nearestCandidates);
            // 获取吸附距离(如果本地 state 还没加载完,给个默认值 1000)
            const currentAdsorptionDistance = adsorptionDistance || 1000;
            // 用户手势拖动或点击回到原点触发的移动,进行吸附判断
            if ((causedBy === 'gesture' || causedBy === 'drag' || wasReturningToOrigin) && targetMarker && minDistance < adsorptionDistance) {
            if ((causedBy === 'gesture' || causedBy === 'drag' || wasReturningToOrigin) && targetMarker && minDistance < currentAdsorptionDistance) {
              // 使用 moveToLocation 实现吸附效果
              mapCtx.moveToLocation({
                latitude: targetMarker.latitude,
@@ -837,7 +855,7 @@
            // 根据距离决定是否显示 ToolTip
            setTimeout(() => {
              setBounce(false);
              if (minDistance < adsorptionDistance) {
              if (minDistance < currentAdsorptionDistance) {
                setIsAdsorbed(true);
                if (targetMarker) {
                  setCurrentSite(targetMarker);
@@ -882,9 +900,12 @@
      const nearestCandidates = sites.length > 0 ? sites : newMarkers;
      const { targetMarker, minDistance } = findNearestMarker(lat, lng, nearestCandidates);
      // 获取吸附距离(如果本地 state 还没加载完,给个默认值 1000)
      const currentAdsorptionDistance = adsorptionDistance || 1000;
      setTimeout(() => {
        setBounce(false);
        if (minDistance < adsorptionDistance) {
        if (minDistance < currentAdsorptionDistance) {
          setIsAdsorbed(true);
          if (targetMarker) {
            setCurrentSite(targetMarker);
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -439,6 +439,7 @@
            const longitude = Number(location?.longitude);
            const latitude = Number(location?.latitude);
            if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
                console.log('获取的高精度坐标:', { longitude, latitude });
                return { longitude, latitude };
            }
        } catch (_) {
src/robotaxi/pages/begin-route/components/cancel-popup.tsx
@@ -34,7 +34,7 @@
    const STATUS_MAP = {
        1: {
            title: `${waitMinutesValue}分钟内预计有车,确定取消吗?`,
            title: `预计${waitMinutesValue}分钟内有车,确定取消吗?`,
            tip: discountAmountValue > 0 ? `本单限时优惠${discountAmountValue}元,重新叫车可能等待更久` : '',
        },
        2: {
src/robotaxi/pages/begin-route/index.tsx
@@ -160,6 +160,8 @@
  const lastPolylineUpdateAtRef = useRef<number>(0);
  const lastRouteAndFareRequestAtRef = useRef<number>(0);
  const markersSignatureRef = useRef<string>('');
  const currentFullRouteRef = useRef<any[]>([]);
  const lastErasedIndexRef = useRef<number>(0);
  const isAndroid = useMemo(() => {
    try {
      const info = Taro.getSystemInfoSync();
@@ -181,6 +183,7 @@
      orderRouteInitializedRef.current = false;
      tripStatus6ViewportLockedRef.current = false;
      tripStatus6MarkerRefreshedRef.current = false;
      currentFullRouteRef.current = [];
    }
    if (!isHold) {
      tripStatus6ViewportLockedRef.current = false;
@@ -466,21 +469,10 @@
  // 应用路线点
  const applyRoutePoints = (points: any[]) => {
    if (Array.isArray(points) && points.length > 0) {
      // 使用代表车辆当前位置的第一个点来做节流判断
      const firstPoint = points[0];
      const newLat = Number(firstPoint?.latitude);
      const newLng = Number(firstPoint?.longitude);
      const now = Date.now();
      const prev = lastRoutePointRef.current;
      const lastUpdateAt = lastRouteUpdateAtRef.current;
      if (Number.isFinite(newLat) && Number.isFinite(newLng) && prev && lastUpdateAt > 0) {
        // 使用新的判断逻辑:100米和10秒
        const isUpdate = shouldUpdateRoute(prev.latitude, prev.longitude, newLat, newLng, lastUpdateAt);
        if (!isUpdate) {
          return false; // 未达到更新阈值,直接返回
        }
      }
      if (Number.isFinite(newLat) && Number.isFinite(newLng)) {
        lastRoutePointRef.current = { latitude: newLat, longitude: newLng };
@@ -499,25 +491,69 @@
    }
  };
  // 更新车辆位置(带节流)
  const updateVehicleLocationThrottled = (lat: number, lng: number, force = false) => {
    if (force) {
      lastVehiclePointRef.current = { latitude: lat, longitude: lng };
      lastVehicleUpdateAtRef.current = Date.now();
      setVehicleLocation({ latitude: lat, longitude: lng });
      return;
    }
    const now = Date.now();
    const prev = lastVehiclePointRef.current;
    const lastUpdateAt = lastVehicleUpdateAtRef.current;
  // 使用 eraseLines 擦除已行驶路段(状态4/6专用)
  const erasePassedRouteSegments = (newPoints: any[]) => {
    const oldPoints = currentFullRouteRef.current;
    if (!oldPoints?.length || !newPoints?.length) return;
    if (Number.isFinite(lat) && Number.isFinite(lng) && prev && lastUpdateAt > 0) {
      const isUpdate = shouldUpdateRoute(prev.latitude, prev.longitude, lat, lng, lastUpdateAt);
      if (!isUpdate) {
        return;
    const newFirst = newPoints[0];
    let bestMatchIndex = -1;
    let bestDistance = Infinity;
    const startIndex = lastErasedIndexRef.current;
    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 && 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,
          },
          clear: true
        }],
        success: (res) => {
          console.log('擦除路线成功', res);
        },
        fail: (err) => {
          console.error('擦除路线失败', err);
        }
      });
      lastErasedIndexRef.current = bestMatchIndex;
    } else if (bestMatchIndex === startIndex && bestDistance < 500) {
      // 新路线起点与旧路线起点一致,无需擦除
    } else {
      // 路线发生较大变化,回退为全量重绘
      currentFullRouteRef.current = [...newPoints];
      lastErasedIndexRef.current = 0;
      lastRoutePointRef.current = null;
      lastRouteUpdateAtRef.current = 0;
      setRoutePoints([...newPoints]);
    }
  };
  // 更新车辆位置(移除节流,实时更新)
  const updateVehicleLocationThrottled = (lat: number, lng: number, force = false) => {
    const now = Date.now();
    if (Number.isFinite(lat) && Number.isFinite(lng)) {
      lastVehiclePointRef.current = { latitude: lat, longitude: lng };
      lastVehicleUpdateAtRef.current = now;
@@ -690,6 +726,12 @@
            applyRoutePoints(points);
            const first = points[0];
            updateVehicleLocationThrottled(first.latitude, first.longitude, true);
            // 状态4/6:存储完整路线用于后续 eraseLines 对比
            if ([4, 6].includes(status)) {
              currentFullRouteRef.current = [...points];
              lastErasedIndexRef.current = 0;
              orderRouteInitializedRef.current = true;
            }
          }
        }
        const distance = Number(data?.distance);
@@ -796,6 +838,8 @@
        lastRouteUpdateAtRef.current = 0;
        lastVehiclePointRef.current = null;
        lastVehicleUpdateAtRef.current = 0;
        currentFullRouteRef.current = [];
        lastErasedIndexRef.current = 0;
      }
      if (mappedTripStatus === 4) {
        setRoutePoints([]);
@@ -806,10 +850,22 @@
        lastRouteUpdateAtRef.current = 0;
        lastVehiclePointRef.current = null;
        lastVehicleUpdateAtRef.current = 0;
        currentFullRouteRef.current = [];
        lastErasedIndexRef.current = 0;
      }
      if (payload?.showCancelPopup) {
        setTripStatus(10);
        setCancelType(5);
        if (tripStatus == 4 || tripStatus == 5) {
          setOrderId('');
          clearOrderTripStorage();
          Taro.removeStorageSync('pickupEndTime');
          Taro.removeStorageSync('waitingTimerStartAt');
          Taro.removeStorageSync('waitingTimerElapsed');
          setTripStatus(10);
          setCancelType(4);
        } else {
          setCancelType(5);
        }
        setShowCancelPopup(true);
      }
      if (payload?.showPassengerNotice) {
@@ -836,25 +892,28 @@
      if (durationSeconds !== null) {
        setRouteDuration(durationSeconds);
      }
      const isHoldMapStatus = [5, 6].includes(tripStatusRef.current);
      // 仅在呼叫及后续阶段允许展示路线
      if (isHoldMapStatus && orderRouteInitializedRef.current) {
        setTimeout(() => {
          const updated = applyRoutePoints(points);
          if (updated) {
            const firstPoint = points?.[0];
            if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
              updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
            }
          }
        }, 500);
      // 状态4/6:使用 eraseLines 擦除已行驶路段,不再全量重绘
      const isEraseStatus = [4, 6].includes(tripStatusRef.current);
      if (isEraseStatus) {
        const firstPoint = points?.[0];
        if (orderRouteInitializedRef.current && currentFullRouteRef.current.length > 0) {
          // 后续更新:擦除已行驶路段
          erasePassedRouteSegments(points);
        } else {
          // 首次绘制:全量渲染并存储完整路线
          orderRouteInitializedRef.current = true;
          currentFullRouteRef.current = [...points];
          lastErasedIndexRef.current = 0;
          applyRoutePoints(points);
        }
        if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
          updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
        }
        return;
      }
      if (isHoldMapStatus) {
        orderRouteInitializedRef.current = true;
      }
      // 发布行程状态也允许展示路线
      // 其他状态保持原有逻辑
      setTimeout(() => {
        const updated = applyRoutePoints(points);
        if (updated && [4, 5, 6].includes(tripStatusRef.current)) {
@@ -1155,7 +1214,9 @@
  // 绘制路线与视野适配
  useEffect(() => {
    const displayRoutePoints = reducePoints(routePoints, isAndroid ? 120 : 200);
    // 状态4/6使用 eraseLines,不缩减点数以保证坐标匹配
    const isEraseMode = [4, 6].includes(tripStatus);
    const displayRoutePoints = isEraseMode ? routePoints : reducePoints(routePoints, isAndroid ? 120 : 200);
    const lastRoutePoint = displayRoutePoints[displayRoutePoints.length - 1];
    const lastWalkingPoint = walkingPoints[walkingPoints.length - 1];
    const isTripMoving = [4, 5, 6].includes(tripStatus);
@@ -1182,6 +1243,7 @@
      const lines: any[] = [];
      if (displayRoutePoints.length > 0) {
        lines.push({
          id: 2001,
          points: displayRoutePoints,
          color: '#7B91FF',
          width: 10,
@@ -1191,6 +1253,7 @@
      }
      if (statusRule.showWalking && walkingPoints.length > 0) {
        lines.push({
          id: 2002,
          points: walkingPoints,
          color: '#7B91FF',
          width: 6,
@@ -1945,7 +2008,7 @@
              <CoverView slot="callout" marker-id={item.id} key={calloutKey} className="custom-callout">
                {
                  (item.name !== '车辆位置' && !(item.id === 2 && [7].includes(tripStatus))) &&
                  <CoverView className={`custom-callout-content ${isAndroid && 'txt-center '}`}>
                  <CoverView className={`custom-callout-content ${isAndroid && 'txt-center '}`} style={{ zIndex: 1 }}>
                    <CoverView style={{ lineHeight: '22px', display: 'flex', alignItems: 'center' }}>{item.name}</CoverView>
                    {
                      isAndroid &&
@@ -1958,7 +2021,7 @@
                }
                {
                  (item.name === '车辆位置' && [4, 6].includes(tripStatus) && !isAndroid) &&
                  <CoverView className="custom-callout-content" style={{ padding: '6px 10px', minWidth: '90px', justifyContent: 'center' }}>
                  <CoverView className="custom-callout-content" style={{ padding: '6px 10px', minWidth: '40px', justifyContent: 'center', zIndex: 2 }}>
                    <CoverView className="car-time">
                      <CoverView className="car-time-num">{routeDistance?.value ?? 0}</CoverView>
                      <CoverView>{routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView>
src/robotaxi/pages/expense-detail/index.tsx
@@ -56,7 +56,7 @@
          <View className='price-value'>{pricingCard.startPriceStr}元</View>
        </View>
        <View className='flex-between-card mb-16'>
          <View className='price-notice-label'>{pricingCard.time}({formatTripDistance(pricingCard.distance)},{formatTripDurationFromSeconds(pricingCard.duration)})</View>
          <View className='price-notice-label'>{pricingCard.time}({formatTripDistance(pricingCard.distancePriceNormal)},{formatTripDurationFromSeconds(pricingCard.durationPriceNormal)})</View>
          <View className='price-notice-value'>{pricingCard.startPriceStr}元</View>
        </View>
        {
@@ -69,8 +69,8 @@
            <View className='flex-between-card mb-16'>
              <View className='price-notice-label'>普通时段
                {
                  pricingCard.distancePriceNormal > 0 &&
                  <Text>({formatTripDistance(pricingCard.distancePriceNormal)})</Text>
                  pricingCard.distance > 0 &&
                  <Text>({formatTripDistance(pricingCard.distance)})</Text>
                }
              </View>
              <View className='price-notice-value'>{pricingCard.distancePriceStr}元</View>
@@ -81,14 +81,19 @@
          pricingCard.durationPriceStr > 0 &&
          <View>
            <View className='flex-between-card'>
              <View className='price-label'>时长费({formatTripDurationFromSeconds(pricingCard.durationPriceNormal)})</View>
              <View className='price-label'>时长费
                {
                  pricingCard.duration > 0 &&
                  <Text>({formatTripDurationFromSeconds(pricingCard.duration)})</Text>
                }
              </View>
              <View className='price-value'>{pricingCard.durationPriceStr}元</View>
            </View>
            <View className='flex-between-card mb-16'>
              <View className='price-notice-label'>普通时段
                {
                  pricingCard.durationPriceNormal > 0 &&
                  <Text>({formatTripDurationFromSeconds(pricingCard.durationPriceNormal)})</Text>
                  pricingCard.duration > 0 &&
                  <Text>({formatTripDurationFromSeconds(pricingCard.duration)})</Text>
                }
              </View>
              <View className='price-notice-value'>{pricingCard.durationPriceStr}元</View>
src/robotaxi/pages/select-map-site/index.less
@@ -241,6 +241,15 @@
            background: #F1F2F6;
        }
        .no-service {
            margin-top: 132px;
            margin-bottom: 68px;
            font-size: 28px;
            line-height: 44px;
            text-align: center;
            color: rgba(0, 0, 0, 0.4);
        }
        .site-confirm {
            margin-top: 48px;
            height: 88px;
@@ -253,5 +262,9 @@
            letter-spacing: normal;
            color: #FFFFFF;
        }
        .no-confirm {
            background: rgba(0, 0, 0, 0.15);
        }
    }
}
src/robotaxi/pages/select-map-site/index.tsx
@@ -19,7 +19,9 @@
  fetchMapData,
  getWalkingRoute,
  extractPointsFromPolygons,
  preloadImageToTemp
  preloadImageToTemp,
  formatTripDistance,
  formatTripDurationFromSeconds
} from '../../../utils/Robotaxi-public-method';
import { getGeofence } from '../../api/home';
import { getOrderDistance } from '../../api/select-map-site';
@@ -47,6 +49,8 @@
  const [userLongitude, setUserLongitude] = useState<number | null>(null);
  const [userLatitude, setUserLatitude] = useState<number | null>(null);
  const [outOfServiceRange, setOutOfServiceRange] = useState(false);
  const [walkingDistance, setWalkingDistance] = useState<number | null>(null);
  const [walkingDuration, setWalkingDuration] = useState<number | null>(null);
  const tooltipBgUrl = require('../../../robotaxi/assets/tooltip-bg.png');
  const [tooltipBgSrc, setTooltipBgSrc] = useState(tooltipBgUrl);
  const [isAndroid, setIsAndroid] = useState(false);
@@ -385,17 +389,23 @@
  useEffect(() => {
    if (pageType !== 'start') {
      setPolyline([]);
      setWalkingDistance(null);
      setWalkingDuration(null);
      return;
    }
    const originLat = userLatitude ?? latitude;
    const originLng = userLongitude ?? longitude;
    if (!originLat || !originLng || !selectedSite?.latitude || !selectedSite?.longitude) {
      setPolyline([]);
      setWalkingDistance(null);
      setWalkingDuration(null);
      return;
    }
    const origin = `${originLng},${originLat}`;
    const destination = `${selectedSite.longitude},${selectedSite.latitude}`;
    getWalkingRoute(origin, destination).then(({ points }) => {
    getWalkingRoute(origin, destination).then(({ points, distance, duration }) => {
      setWalkingDistance(distance);
      setWalkingDuration(duration);
      if (!Array.isArray(points) || points.length === 0) {
        setPolyline([]);
        return;
@@ -493,27 +503,33 @@
              <Image onClick={toOrigin} className="origin-icon" src={originIcon} />
            </View>
            <View catchMove onClick={(e) => e.stopPropagation()}>
              {/* 确认站点 */}
              {
                selectedSite.name && <View className="content-box">
                  {/* <View className="content-limit-bg"></View>
              {/* 确认站点 */}<View className="content-box">
                {/* <View className="content-limit-bg"></View>
                  <View className="limit-tip">目的地附近道路有<Text className="emphasis-txt">通行限制</Text>,为您推荐适合的下车位置</View> */}
                  <View className="content-title">{pageType === 'start' ? '确认上车站点' : '确认目的地'}</View>
                  {
                    nearbySites.length > 0 &&
                    <View className="limit-tip">点击其他选项或紫色原点,可更改{pageType === 'start' ? '上车' : '下车'}点</View>
                  }
                  <ScrollView className="site-list" scrollY>
                    {/* {
                <View className="content-title">{pageType === 'start' ? '确认上车站点' : '确认目的地'}</View>
                {
                  pageType === 'start' && walkingDuration !== null && walkingDistance !== null &&
                  <View className="limit-tip">从当前位置出发步行{formatTripDurationFromSeconds(walkingDuration)}({formatTripDistance(walkingDistance)})上车</View>
                }
                {
                  (nearbySites.length > 0 && pageType === 'end') &&
                  <View className="limit-tip">点击其他选项或紫色原点,可更改{pageType === 'start' ? '上车' : '下车'}点</View>
                }
                <ScrollView className="site-list" scrollY>
                  {/* {
                      nearbySites.map(item => (
                        <View onClick={() => changeChildrenSite(item)} key={item.id} className={`site-name ${selectedSite.id == item.id && 'site-active'}`}>{item.name}</View>
                      ))
                    } */}
                    <View className={`site-name site-active`}>{selectedSite.name}</View>
                  </ScrollView>
                  <View onClick={confirmSite} className="site-confirm">{pageType === 'start' ? '确认上车站点' : '确认目的地站点'}</View>
                </View>
              }
                  {
                    selectedSite.name ?
                      <View className={`site-name site-active`}>{selectedSite.name}</View>
                      :
                      <View className="no-service">超出服务范围,请在运营区内选择</View>
                  }
                </ScrollView>
                <View onClick={confirmSite} className={`site-confirm ${selectedSite.name ? '' : 'no-confirm'}`} >{pageType === 'start' ? '确认上车站点' : '确认目的地站点'}</View>
              </View>
            </View>
          </View>
        </View>
src/utils/Robotaxi-public-method.ts
@@ -455,11 +455,19 @@
  if (!Number.isFinite(num)) {
    return '--';
  }
  const safe = Math.max(0, Math.floor(num));
  const safe = Math.max(0, Math.round(num));
  if (safe < 60) {
    return `${safe}秒`;
  }
  return `${Math.floor(safe / 60)}分钟`;
  if (safe < 3600) {
    return `${Math.floor(safe / 60)}分钟`;
  }
  const hours = Math.floor(safe / 3600);
  const minutes = Math.floor((safe % 3600) / 60);
  if (minutes === 0) {
    return `${hours}小时`;
  }
  return `${hours}小时${minutes}分钟`;
};
export const formatDurationDisplay = (value: any) => {
src/utils/websocket.ts
@@ -95,9 +95,7 @@
      this.socketTask.onMessage((res) => {
        try {
          const data = JSON.parse(res.data as string);
          if (data?.type !== 'pong') {
            console.log('收到 WebSocket 消息:', res.data);
          }
          console.log('收到 WebSocket 消息:', res.data);
          const content = JSON.parse(data.content || '{}');
          this.handleIncomingMessage(data, content);
          if (data.type) {
@@ -146,9 +144,7 @@
    Taro.onSocketMessage((res) => {
      try {
        const data = JSON.parse(res.data as string);
        if (data?.type !== 'pong') {
          console.log('收到 WebSocket 消息:', res.data);
        }
        console.log('收到 WebSocket 消息:', res.data);
        const content = JSON.parse(data.content || '{}');
        this.handleIncomingMessage(data, content);
        if (data.type) {