hejianhao
2026-03-16 ad46472f91c18c8184ad62b6abbc523eb4a9a997
feat(行程): 优化订单详情与费用展示逻辑

- 订单详情页地址字段改用 startPointName/endPointName 展示
- 费用明细组件增加价格更新监听,支持订单完成后价格变更刷新
- 优化地图标记点坐标校验与视口计算,避免无效坐标
- 修复 Android 平台到达目的地气泡显示问题
- 站点搜索接口支持 electronicAreaId 参数传递
- 优惠券详情页解码逻辑移至详情获取时处理
- 调整样式类名,增加文本居中与右侧间距工具类
9个文件已修改
388 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/calling-car.tsx 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.less 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/coupon/index.tsx 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/detail/index.tsx 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/index.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/index.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/index.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -36,6 +36,7 @@
    handleCancelTimeout?: () => void; // 取消超时处理
    handleFinishTimeout?: () => void; // 到达目的地倒计时结束处理
    resetDetailVersion?: number; // 父组件触发重置 showDetail
    priceUpdateVersion?: number; // 父组件触发刷新费用明细
    evaluateSubmitVersion?: number; // 父组件触发设置 activeIndex=1
    evaluateCloseVersion?: number; // 父组件触发设置 activeIndex=0
    handleOpenOverRoutePopup?: () => void; // 打开超出行程时间弹窗
@@ -62,6 +63,7 @@
    handleCancelTimeout,
    handleFinishTimeout,
    resetDetailVersion,
    priceUpdateVersion,
    evaluateSubmitVersion,
    evaluateCloseVersion,
    handleOpenOverRoutePopup,
@@ -129,6 +131,21 @@
        7: { text: '倒计时结束时车辆将自动驶离,请带好物品安全下车', className: 'calling-car-content-desc' },
        8: { text: '订单待支付,请核对后完成支付', className: 'calling-car-content-desc' }
    }
    const requestFareBreakdown = (force = false) => {
        const targetOrderId = orderId || Taro.getStorageSync('orderId');
        if (!targetOrderId) {
            return;
        }
        if (!force && lastFareOrderIdRef.current === targetOrderId) {
            return;
        }
        lastFareOrderIdRef.current = targetOrderId;
        queryFareBreakdown(targetOrderId).then((res: any) => {
            const data = res?.data ?? res?.result ?? res;
            setFareBreakdown(data);
        }).catch(() => { });
    };
    // 根据行程状态初始化等待/限停/倒计时/温度信息,并管理对应定时器
    useEffect(() => {
@@ -295,14 +312,7 @@
            });
        } else if (tripStatus === 8 || tripStatus === 9) {
            Taro.removeStorageSync(FINISH_END_TIME_KEY);
            const targetOrderId = orderId || Taro.getStorageSync('orderId');
            if (targetOrderId && lastFareOrderIdRef.current !== targetOrderId) {
                lastFareOrderIdRef.current = targetOrderId;
                queryFareBreakdown(targetOrderId).then((res: any) => {
                    const data = res?.data ?? res?.result ?? res;
                    setFareBreakdown(data);
                }).catch(() => { });
            }
            requestFareBreakdown();
        }
        return () => {
            isActive = false;
@@ -332,6 +342,16 @@
            setShowDetail(false);
        }
    }, [resetDetailVersion]);
    useEffect(() => {
        if (typeof priceUpdateVersion !== 'number' || priceUpdateVersion <= 0) {
            return;
        }
        if (![8, 9].includes(tripStatus)) {
            return;
        }
        requestFareBreakdown(true);
    }, [priceUpdateVersion, tripStatus, orderId]);
    // 评价弹窗确认提交时,更新评价状态
    useEffect(() => {
@@ -589,10 +609,13 @@
                            <View className="car-card-line" style="margin:24px 0;"></View>
                            <View className="car-card-end-particulars">
                                <View className="car-card-end-particulars-money">
                                    <View>{formatMoney(fareBreakdown?.orderMoney)}</View>
                                    <View>{formatMoney(fareBreakdown?.orderMoney - fareBreakdown?.redPacketMoney - fareBreakdown?.couponMoney)}</View>
                                    <View>元</View>
                                    <View>{formatMoney(fareBreakdown?.orderMoney - fareBreakdown?.redPacketMoney - fareBreakdown?.couponMoney)}元</View>
                                    <View>已优惠{formatMoney(fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney)}元</View>
                                    <View>{formatMoney(fareBreakdown?.orderMoney)}元</View>
                                    {
                                        fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney > 0 &&
                                        <View>已优惠{formatMoney(fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney)}元</View>
                                    }
                                </View>
                                <View className="car-card-end-particulars-view" onClick={() => {
                                    const next = !showDetail;
src/robotaxi/pages/begin-route/index.less
@@ -109,6 +109,10 @@
    padding: 8px 20px;
}
.txt-center {
    text-align: center;
}
.car-time {
    font-size: 24px;
    letter-spacing: normal;
@@ -125,7 +129,6 @@
}
.custom-callout-icon {
    margin-left: 8px;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
@@ -215,4 +218,8 @@
.pr-6 {
    padding-right: 6px;
}
.pr-0 {
    padding-right: 0;
}
src/robotaxi/pages/begin-route/index.tsx
@@ -96,6 +96,7 @@
  const [waitingRadarStyle, setWaitingRadarStyle] = useState<any>({});
  const [waitingRadarVisible, setWaitingRadarVisible] = useState(true);
  const [resetDetailVersion, setResetDetailVersion] = useState(0);
  const [priceUpdateVersion, setPriceUpdateVersion] = useState(0);
  const [evaluateSubmitVersion, setEvaluateSubmitVersion] = useState(0);
  const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0);
  const [originOpacity, setOriginOpacity] = useState(1);
@@ -103,12 +104,15 @@
  const [mapBottomPadding, setMapBottomPadding] = useState(280);
  const [carCalloutStyle, setCarCalloutStyle] = useState<any>({});
  const [carCalloutVisible, setCarCalloutVisible] = useState(false);
  const [arriveCalloutStyle, setArriveCalloutStyle] = useState<any>({});
  const [arriveCalloutVisible, setArriveCalloutVisible] = useState(false);
  const [distanceConfig, setDistanceConfig] = useState<any>({});
  const [mapScale, setMapScale] = useState(14);
  const markersRef = useRef<any[]>([]);
  const tripStatusRef = useRef(tripStatus); // 行程状态引用
  const lastTripStatusRef = useRef(tripStatus);
  const tripStatusForMapSyncRef = useRef(tripStatus);
  const routeRequestKeyRef = useRef<string>(''); // 路线请求键
  const orderRouteInitializedRef = useRef(false);
  const STATUS_RULES: Record<number, any> = {
@@ -130,12 +134,14 @@
  const orderRouteBound = useRef(false); // 订单路线是否绑定
  const vehicleLocationBound = useRef(false); // 车辆位置是否绑定
  const openTrunkBound = useRef(false);
  const orderUpdatePriceBound = useRef(false);
  const authSuccessHandlerRef = useRef<(data: any) => void>(); // 认证成功处理函数
  const messageHandlerRef = useRef<(data: any) => void>(); // 消息处理函数
  const orderStatusHandlerRef = useRef<(data: any) => void>(); // 订单状态处理函数
  const orderRouteHandlerRef = useRef<(data: any) => void>(); // 订单路线处理函数
  const vehicleLocationHandlerRef = useRef<(data: any) => void>(); // 车辆位置处理函数
  const openTrunkHandlerRef = useRef<(data: any) => void>();
  const orderUpdatePriceHandlerRef = useRef<(data: any) => void>();
  const socketOpenHandlerRef = useRef<(() => void) | null>(null);
  // 记录已为某订单拉取过车辆信息,避免重复
  const lastFetchedVehicleOrderIdRef = useRef<string | null>(null);
@@ -143,13 +149,14 @@
  const viewportKeyRef = useRef<string>('');
  const tripStatus6ViewportLockedRef = useRef(false);
  const polylineKeyRef = useRef<string>('');
  const orderDetailRouteRequestIdRef = useRef(0);
  const lastRoutePointRef = useRef<{ latitude: number; longitude: number } | null>(null);
  const lastRouteUpdateAtRef = useRef<number>(0);
  const lastPolylineUpdateAtRef = useRef<number>(0);
  const isAndroid = useMemo(() => {
    try {
      const info = Taro.getSystemInfoSync();
      console.log(info);
      console.log('机型:', info);
      return info?.platform === 'android' || /android/i.test(info?.system || '');
    } catch (error) {
      return false;
@@ -318,6 +325,59 @@
      })
      .exec();
  };
  const updateArriveCalloutPosition = () => {
    if (!isAndroid) {
      return;
    }
    if (![7].includes(tripStatus)) {
      setArriveCalloutVisible(false);
      return;
    }
    const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
    if (!dropoffSite?.latitude || !dropoffSite?.longitude) {
      setArriveCalloutVisible(false);
      return;
    }
    const mapCtx = Taro.createMapContext('map');
    const resolveRect = (rect: any) => {
      if (!rect) {
        return;
      }
      mapCtx.getRegion({
        success: (res) => {
          const sw = res?.southwest;
          const ne = res?.northeast;
          const latRange = ne?.latitude - sw?.latitude;
          const lngRange = ne?.longitude - sw?.longitude;
          if (!latRange || !lngRange) {
            return;
          }
          const x = ((dropoffSite.longitude - sw.longitude) / lngRange) * rect.width;
          const y = ((ne.latitude - dropoffSite.latitude) / latRange) * rect.height;
          setArriveCalloutStyle({
            position: 'fixed',
            left: `${rect.left + x}px`,
            top: `${rect.top + y - 40}px`,
            transform: 'translate(-50%, -120%)'
          });
          setArriveCalloutVisible(true);
        }
      });
    };
    if (mapRectRef.current) {
      resolveRect(mapRectRef.current);
      return;
    }
    Taro.createSelectorQuery()
      .select('#map')
      .boundingClientRect((rect) => {
        if (rect) {
          mapRectRef.current = rect;
        }
        resolveRect(rect);
      })
      .exec();
  };
  // 更新等待雷达位置
  useEffect(() => {
@@ -327,6 +387,9 @@
  useEffect(() => {
    updateCarCalloutPosition();
  }, [isAndroid, vehicleLocation?.latitude, vehicleLocation?.longitude, tripStatus]);
  useEffect(() => {
    updateArriveCalloutPosition();
  }, [isAndroid, tripStatus, endSite?.latitude, endSite?.longitude]);
  const resolveSafeBottomInset = () => {
    try {
@@ -646,6 +709,14 @@
        setFinishCountdownEnabled(true);
      }
    };
    orderUpdatePriceHandlerRef.current = (payload) => {
      const incomingOrderId = payload?.orderId || payload?.content?.orderId || Taro.getStorageSync('orderId');
      const currentOrderId = orderId || Taro.getStorageSync('orderId');
      if (incomingOrderId && currentOrderId && String(incomingOrderId) !== String(currentOrderId)) {
        return;
      }
      setPriceUpdateVersion((v) => v + 1);
    };
    // 优化订单状态回调中的车辆信息拉取逻辑
    orderStatusHandlerRef.current = (payload) => {
      const mappedTripStatus = payload?.tripStatus;
@@ -778,6 +849,10 @@
        wsManager.off('open_trunk', openTrunkHandlerRef.current);
        openTrunkBound.current = false;
      }
      if (orderUpdatePriceBound.current && orderUpdatePriceHandlerRef.current) {
        wsManager.off('order_update_price', orderUpdatePriceHandlerRef.current);
        orderUpdatePriceBound.current = false;
      }
    };
  }, []);
@@ -786,6 +861,7 @@
  useEffect(() => {
    Taro.setStorageSync('tripStatus', tripStatus);
    tripStatusRef.current = tripStatus;
    const prevTripStatus = tripStatusForMapSyncRef.current;
    if (tripStatus === 1) {
      setCallSubmitting(false);
    }
@@ -854,25 +930,33 @@
      Taro.removeStorageSync(WAITING_TIMER_START_KEY);
      Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
    } else if ([8, 9].includes(tripStatus)) {
      getOrderDetail(Taro.getStorageSync('orderId')).then((res: any) => {
        const mapCtx = Taro.createMapContext('map');
        if (res.trajectories) {
          const points = res.trajectories
            .map((item: any) => {
              const [lonStr, latStr] = String(item.location || '').split(',');
              const lon = Number(lonStr);
              const lat = Number(latStr);
              return { latitude: lat, longitude: lon };
            })
            .filter(p => Number.isFinite(p.latitude) && Number.isFinite(p.longitude));
          applyRoutePoints(points);
        } else {
          mapCtx.moveToLocation({
            latitude: 39.916527,
            longitude: 116.397128,
          });
        }
      });
      const shouldSkipOrderDetailRefresh = prevTripStatus === 8 && tripStatus === 9;
      if (!shouldSkipOrderDetailRefresh) {
        orderDetailRouteRequestIdRef.current += 1;
        const currentRequestId = orderDetailRouteRequestIdRef.current;
        const requestTripStatus = tripStatus;
        getOrderDetail(Taro.getStorageSync('orderId')).then((res: any) => {
          if (currentRequestId !== orderDetailRouteRequestIdRef.current) return;
          if (tripStatusRef.current !== requestTripStatus) return;
          const mapCtx = Taro.createMapContext('map');
          if (res.trajectories) {
            const points = res.trajectories
              .map((item: any) => {
                const [lonStr, latStr] = String(item.location || '').split(',');
                const lon = Number(lonStr);
                const lat = Number(latStr);
                return { latitude: lat, longitude: lon };
              })
              .filter(p => Number.isFinite(p.latitude) && Number.isFinite(p.longitude));
            applyRoutePoints(points);
          } else {
            mapCtx.moveToLocation({
              latitude: 39.916527,
              longitude: 116.397128,
            });
          }
        });
      }
      setElapsedSeconds(0);
      Taro.removeStorageSync(WAITING_TIMER_START_KEY);
      Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
@@ -881,6 +965,7 @@
      Taro.removeStorageSync(WAITING_TIMER_START_KEY);
      Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
    }
    tripStatusForMapSyncRef.current = tripStatus;
    return () => {
      if (timer) clearInterval(timer);
      if (requoteTimer) clearInterval(requoteTimer);
@@ -938,7 +1023,7 @@
      height: 40,
      anchor: { x: 0.5, y: 1 },
      zIndex: 1,
      name: dropoffSite.name || '目的地',
      name: [7, 9].includes(tripStatus) ? '已到达' : (dropoffSite.name || '目的地'),
      customCallout: {
        anchorY: -5,
        anchorX: 0,
@@ -1030,7 +1115,7 @@
    const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
    const viewportType = statusRule.viewport;
    const points: any[] = [];
    const keyParts: any[] = [tripStatus, viewportType];
    const keyParts: any[] = [viewportType === 'order_detail' ? 8 : tripStatus, viewportType];
    if (viewportType === 'route_walking') {
      if (routePoints.length > 0) {
@@ -1145,6 +1230,10 @@
    if (!openTrunkBound.current && openTrunkHandlerRef.current) {
      wsManager.on('open_trunk', openTrunkHandlerRef.current);
      openTrunkBound.current = true;
    }
    if (!orderUpdatePriceBound.current && orderUpdatePriceHandlerRef.current) {
      wsManager.on('order_update_price', orderUpdatePriceHandlerRef.current);
      orderUpdatePriceBound.current = true;
    }
  };
@@ -1434,6 +1523,10 @@
      wsManager.on('vehicle_location', vehicleLocationHandlerRef.current);
      vehicleLocationBound.current = true;
    }
    if (!orderUpdatePriceBound.current && orderUpdatePriceHandlerRef.current) {
      wsManager.on('order_update_price', orderUpdatePriceHandlerRef.current);
      orderUpdatePriceBound.current = true;
    }
  }, 500)
  // 确认取消
@@ -1452,6 +1545,7 @@
        Taro.removeStorageSync('waitingTimerStartAt');
        Taro.removeStorageSync('waitingTimerElapsed');
        setCancelType(4);
        return
      } catch (error) {
        Taro.showToast({
          title: '取消订单失败,请重试',
@@ -1461,20 +1555,19 @@
      }
    } else {
      setShowCancelPopup(false);
      return
    }
    setTripStatus(1);
    updateWalkingRouteForStart();
    const s = Taro.getStorageSync('startSite');
    const e = Taro.getStorageSync('endSite');
    if (s?.latitude && s?.longitude && e?.longitude && e?.latitude) {
      requestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude, true);
      setTripStatus(1);
      updateWalkingRouteForStart();
      const s = Taro.getStorageSync('startSite');
      const e = Taro.getStorageSync('endSite');
      if (s?.latitude && s?.longitude && e?.longitude && e?.latitude) {
        requestRouteAndFare(s.latitude, s.longitude, e.latitude, e.longitude, true);
      }
    }
  }
  // 弹窗操作
  const handleCloseCancelPopup = setPopupVisible(setShowCancelPopup, false);
  // 再来一单
  // 订单超时再来一单
  const handleOpenCancelPopupWithType = (type: number) => {
    setOrderId('');
    clearOrderTripStorage();
@@ -1634,12 +1727,14 @@
    if (e?.type === 'begin') {
      setWaitingRadarVisible(false);
      setCarCalloutVisible(false);
      setArriveCalloutVisible(false);
      return;
    }
    if (e?.type === 'end') {
      setWaitingRadarVisible(true);
      updateWaitingRadarPosition();
      updateCarCalloutPosition();
      updateArriveCalloutPosition();
    }
  };
@@ -1702,11 +1797,11 @@
            return (
              <CoverView slot="callout" marker-id={item.id} key={`${tripStatus}-${item.id}`} className="custom-callout">
                {
                  (!['车辆位置', '已到达'].includes(item.name)) &&
                  <CoverView className={`custom-callout-content`}>
                    {item.name}
                  (item.name !== '车辆位置' && !(item.id === 2 && [7, 9].includes(tripStatus))) &&
                  <CoverView className={`custom-callout-content pr-6 ${isAndroid && 'txt-center '}`}>
                    <CoverView>{item.name}</CoverView>
                    {
                      !isAndroid &&
                      isAndroid &&
                      <CoverView style={{ width: '2px' }}></CoverView>
                    }
                    {
@@ -1726,9 +1821,9 @@
                  </CoverView>
                }
                {
                  item.name === '已到达' &&
                  (item.id === 2 && [7].includes(tripStatus) && !isAndroid) &&
                  <CoverView className="custom-callout-content">
                    <CoverView className="custom-callout-content-arrive">已到达</CoverView>
                    已到达
                  </CoverView>
                }
              </CoverView>
@@ -1747,6 +1842,13 @@
                <View style={{ marginLeft: '2.5px' }} className="car-time-num">{durationDisplay.value}</View>
                <View style={{ flexShrink: 0 }}>{durationDisplay.unit}</View>
              </View>
            </View>
          </View>
        )}
        {isAndroid && arriveCalloutVisible && (
          <View className="car-callout-android" style={arriveCalloutStyle}>
            <View className="custom-callout-content">
              已到达
            </View>
          </View>
        )}
@@ -1806,6 +1908,7 @@
                  handleCancelTimeout={() => handleOpenCancelPopupWithType(4)}
                  handleFinishTimeout={handleFinishCountdownTimeout}
                  resetDetailVersion={resetDetailVersion}
                  priceUpdateVersion={priceUpdateVersion}
                  evaluateSubmitVersion={evaluateSubmitVersion}
                  evaluateCloseVersion={evaluateCloseVersion}
                  handleOpenAnnouncementsPopup={handleOpenAnnouncementsPopup}
src/robotaxi/pages/coupon/index.tsx
@@ -1,5 +1,5 @@
import Taro, { useReachBottom } from '@tarojs/taro';
import { useState, useEffect, useMemo } from 'react';
import { useState, useEffect } from 'react';
import { View, Text, Image, RichText } from '@tarojs/components';
import './index.less';
import icon from '../../assets/Subtract.png'
@@ -21,16 +21,6 @@
  const [hasMore, setHasMore] = useState(true)
  const [currentCoupon, setCurrentCoupon] = useState<CouponDetail | null>(null)
  const pageSize = 10
  const decodedCouponDesc = useMemo(() => {
    const raw = currentCoupon?.couponDesc;
    if (!raw) return raw;
    if (typeof raw !== 'string') return raw;
    try {
      return decodeURIComponent(raw);
    } catch (e) {
      return raw;
    }
  }, [currentCoupon?.couponDesc]);
  const getList = async (page: number, status: string | number, isRefresh = false) => {
    try {
@@ -82,6 +72,9 @@
    try {
      Taro.showLoading({ title: '加载中...' });
      const detail = await getCouponDetail(item.couponId);
      detail.couponDesc = decodeURIComponent(detail.couponDesc);
      console.log(detail.couponDesc);
      if (detail.couponDesc) {
        detail.couponDesc = detail.couponDesc.replace(/<img/gi,
          '<img style="max-width:100%;height:auto;margin:0 auto;display:block"')
@@ -207,7 +200,7 @@
          </View>
          <View className='remark-card'>
            <Text className='remark-label'>使用说明</Text>
            <Text className='remark-value'><RichText nodes={decodedCouponDesc} /></Text>
            <RichText nodes={currentCoupon?.couponDesc} />
          </View>
        </View>
src/robotaxi/pages/order/detail/index.tsx
@@ -29,6 +29,7 @@
  const [latitude, setLatitude] = useState(39.916527) // 地图中心点纬度
  const [markers, setMarkers] = useState<any[]>([]) // 起点和终点标记
  const [polyline, setPolyline] = useState<any[]>([]) // 行驶路径
  const lastIncludeKeyRef = useRef('');
  const getPointsCenter = (points: Array<{ longitude: number; latitude: number }>) => {
    if (!points.length) {
@@ -45,6 +46,48 @@
      maxLat = Math.max(maxLat, point.latitude);
    });
    return { longitude: (minLng + maxLng) / 2, latitude: (minLat + maxLat) / 2 };
  };
  const normalizePoint = (longitude: any, latitude: any) => {
    const lon = Number(longitude);
    const lat = Number(latitude);
    if (Number.isNaN(lon) || Number.isNaN(lat)) return null;
    if (Math.abs(lon) > 180 || Math.abs(lat) > 90) return null;
    if (lon === 0 && lat === 0) return null;
    return { longitude: lon, latitude: lat };
  };
  const getMarkerPoints = (markerList: any[]) => {
    if (!Array.isArray(markerList) || markerList.length === 0) {
      return [] as Array<{ longitude: number; latitude: number }>;
    }
    return markerList
      .map((item) => {
        const longitude = Number(item?.longitude);
        const latitude = Number(item?.latitude);
        if (Number.isNaN(longitude) || Number.isNaN(latitude)) return null;
        return { longitude, latitude };
      })
      .filter(Boolean) as Array<{ longitude: number; latitude: number }>;
  };
  const getMarkerPadding = (markerList: any[]) => {
    let top = 80;
    let right = 80;
    let bottom = 80;
    let left = 80;
    markerList.forEach((item) => {
      const width = Number(item?.width) || 22;
      const height = Number(item?.height) || 40;
      const anchorX = typeof item?.anchor?.x === 'number' ? item.anchor.x : 0.5;
      const anchorY = typeof item?.anchor?.y === 'number' ? item.anchor.y : 1;
      const safeGap = 28;
      top = Math.max(top, Math.ceil(height * (1 - anchorY) + safeGap));
      bottom = Math.max(bottom, Math.ceil(height * anchorY + safeGap));
      left = Math.max(left, Math.ceil(width * anchorX + safeGap));
      right = Math.max(right, Math.ceil(width * (1 - anchorX) + safeGap));
    });
    return [top, right, bottom, left] as [number, number, number, number];
  };
  useEffect(() => {
@@ -100,37 +143,41 @@
  const initMap = (data: OrderDetail) => {
    const { startLat, startLon, endLat, endLon, trajectories, status } = data;
    const startPoint = normalizePoint(startLon, startLat);
    const endPoint = normalizePoint(endLon, endLat);
    // 设置起点和终点标记
    const markerList = [
      {
      startPoint ? {
        id: 1,
        longitude: startLon,
        latitude: startLat,
        longitude: startPoint.longitude,
        latitude: startPoint.latitude,
        width: 22,
        height: 40,
        anchor: { x: 0.5, y: 1 },
        iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png',
      },
      {
      } : null,
      endPoint ? {
        id: 2,
        longitude: endLon,
        latitude: endLat,
        longitude: endPoint.longitude,
        latitude: endPoint.latitude,
        width: 22,
        height: 40,
        anchor: { x: 0.5, y: 1 },
        iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png',
      }
    ];
      } : null
    ].filter(Boolean) as any[];
    setMarkers(markerList);
    // 处理行驶路径
    const routePath = trajectories?.length > 0
      ? trajectories.map(item => ({ longitude: Number(item.location.split(',')[0]), latitude: Number(item.location.split(',')[1]) }))
      : [
        { longitude: startLon, latitude: startLat },
        { longitude: endLon, latitude: endLat }
      ];
      ? trajectories
        .map(item => {
          const [lon, lat] = String(item.location || '').split(',');
          return normalizePoint(lon, lat);
        })
        .filter(Boolean) as Array<{ longitude: number; latitude: number }>
      : [startPoint, endPoint].filter(Boolean) as Array<{ longitude: number; latitude: number }>;
    const center = getPointsCenter(routePath);
    if (center) {
      setLongitude(center.longitude);
@@ -155,32 +202,36 @@
  useEffect(() => {
    const mapCtx = Taro.createMapContext('trip-map');
    const polylinePoints = polyline?.[0]?.points;
    const markerPoints = getMarkerPoints(markers);
    const markerPadding = getMarkerPadding(markers);
    if (Array.isArray(polylinePoints) && polylinePoints.length > 0) {
      mapCtx.includePoints({
        points: polylinePoints,
        padding: [40, 40, 40, 40]
      const pointsForViewport = [...polylinePoints, ...markerPoints];
      const includeKey = `${pointsForViewport.length}|${pointsForViewport[0]?.longitude}|${pointsForViewport[0]?.latitude}|${pointsForViewport[pointsForViewport.length - 1]?.longitude}|${pointsForViewport[pointsForViewport.length - 1]?.latitude}|${markerPadding.join(',')}`;
      if (includeKey === lastIncludeKeyRef.current) return;
      lastIncludeKeyRef.current = includeKey;
      Taro.nextTick(() => {
        mapCtx.includePoints({
          points: pointsForViewport,
          padding: markerPadding
        });
      });
      return;
    }
    if (Array.isArray(markers) && markers.length > 0) {
      const markerPoints = markers
        .map((item) => {
          const longitude = Number(item?.longitude);
          const latitude = Number(item?.latitude);
          if (Number.isNaN(longitude) || Number.isNaN(latitude)) return null;
          return { longitude, latitude };
        })
        .filter(Boolean) as Array<{ longitude: number; latitude: number }>;
      if (markerPoints.length > 0) {
    if (markerPoints.length > 0) {
      const includeKey = `${markerPoints.length}|${markerPoints[0]?.longitude}|${markerPoints[0]?.latitude}|${markerPoints[markerPoints.length - 1]?.longitude}|${markerPoints[markerPoints.length - 1]?.latitude}|${markerPadding.join(',')}`;
      if (includeKey === lastIncludeKeyRef.current) return;
      lastIncludeKeyRef.current = includeKey;
      Taro.nextTick(() => {
        mapCtx.includePoints({
          points: markerPoints,
          padding: [80, 80, 80, 80]
          padding: markerPadding
        });
      }
      });
    }
  }, [polyline, markers])
  const todescription = () => {
    showEvaluatePopupRef.current = true;
    Taro.navigateTo({
      url: `/robotaxi/pages/expense-description/index?orderId=${orderId}`,
    })
@@ -273,7 +324,7 @@
          orderData.status != 0 &&
          <View className='trip-top'>
            <View className='price-card'>
              <Text className='price'>{orderData.payMoney || '-'}元</Text>
              <Text className='price'>{orderData.payMoney || '0'}元</Text>
              <Text className='unit'>元</Text>
              {orderData.orderMoney > orderData.payMoney && (
                <Text className='old-price'>{orderData.orderMoney || '-'}元</Text>
@@ -295,7 +346,7 @@
            markers={markers}
            polyline={polyline}
            showLocation={false}
            style={{ width: '100%', height: '100%', borderRadius: '16px' }}
            style={{ borderRadius: '16px' }}
          />
        </View>
        {/* 行程信息 */}
src/robotaxi/pages/order/index.tsx
@@ -111,8 +111,8 @@
                  <Image className='order-status-image' src={require('../../assets/order-to-detail.png')} />
                </View>
              </View>
              <View className='order-start'>起点:{item.originAddress || '-'}</View>
              <View className='order-start'>终点:{item.destinationAddress || '-'}</View>
              <View className='order-start'>起点:{item.startPointName || '-'}</View>
              <View className='order-start'>终点:{item.endPointName || '-'}</View>
              {
                formatOrderStatus(item.status) != '进行中' &&
                <View className='order-again-box'>
src/robotaxi/pages/select-map-site/index.tsx
@@ -18,9 +18,9 @@
  formatGeofencePolygons,
  fetchMapData,
  getWalkingRoute,
  extractPointsFromPolygons
  extractPointsFromPolygons,
  preloadImageToTemp
} from '../../../utils/Robotaxi-public-method';
import { preloadImageToTemp } from '../../../utils/Robotaxi-public-method';
import { getGeofence } from '../../api/home';
import { getOrderDistance } from '../../api/select-map-site';
import { checkOtp } from '../../../utils/tools';
@@ -172,8 +172,8 @@
  // 更新地图数据
  const updateMapData = async (lat: number, lng: number) => {
      const selectedCity = Taro.getStorageSync('selectedCity');
      const { isInside, electronicAreaId, newMarkers, sites } = await fetchMapData(lat, lng, selectedCity?.code);
    const selectedCity = Taro.getStorageSync('selectedCity');
    const { isInside, electronicAreaId, newMarkers, sites } = await fetchMapData(lat, lng, selectedCity?.code);
    setIsOperatingArea(isInside);
    setMarkers(newMarkers);
src/robotaxi/pages/site-search/index.tsx
@@ -34,7 +34,7 @@
  const router = Taro.useRouter();
  const fetchNearbySites = async (lat: number, lon: number) => {
  const fetchNearbySites = async (lat: number, lon: number, electronicAreaId?: string) => {
    const selectedCity = Taro.getStorageSync('selectedCity');
    try {
@@ -42,6 +42,7 @@
        cityCode: selectedCity?.code || '',
        latitude: Number(lat),
        longitude: Number(lon),
        electronicAreaId
      });
      const points = Array.isArray(res?.points) ? res.points : [];
      if (points.length > 0) {
@@ -63,7 +64,7 @@
    const { pageType: type, latitude: lat, longitude: lon, electronicAreaId } = router.params;
    if (lat && lon) {
      fetchNearbySites(Number(lat), Number(lon));
      fetchNearbySites(Number(lat), Number(lon), electronicAreaId);
    }
    checkOtp(false).then(otp => {
      if (otp && lat && lon) {
src/utils/websocket.ts
@@ -42,7 +42,7 @@
    const uid = await getStorage(_UID) || '-1';
    const task = Taro.connectSocket({
      header: {
        'XP-Uid':  uid.toString(),
        'XP-Uid': '20260227200715' || uid.toString(),
      },
      url: this.url,
      success: () => { },
@@ -88,7 +88,7 @@
        this.reconnectAttempts = 0;
        this.startHeartbeat();
        const uid = await getStorage(_UID) || '-1';
        this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() });
        this.send({ type: 'auth', appUserId: '20260227200715' || uid.toString(), timestamp: Date.now() });
        this.notifyListeners('open', { status: 'connected' });
      });
@@ -136,7 +136,7 @@
      this.reconnectAttempts = 0;
      this.startHeartbeat();
      const uid = await getStorage(_UID) || '-1';
      this.send({ type: 'auth', appUserId: uid.toString(), timestamp: Date.now() });
      this.send({ type: 'auth', appUserId:'20260227200715' || uid.toString(), timestamp: Date.now() });
      this.notifyListeners('open', { status: 'connected' });
    });
@@ -360,6 +360,10 @@
        this.notifyListeners('order_route', { routeType, direction, points, data, content });
      }
    }
    if (data?.type === 'ORDER_UPDATE_PRICE') {
      const orderId = content?.orderId ?? data?.orderId;
      this.notifyListeners('order_update_price', { orderId, data, content });
    }
    if (data?.type === 'OPEN_TRUNK') {
      const isOpen = content?.isOpen ?? content?.open;
      this.notifyListeners('open_trunk', { isOpen, data, content });