hejianhao
2026-03-11 05960d5c356a728020c56cc07dd33b3e85421f9c
fix(robotaxi): 修复订单详情页状态显示与地图组件性能问题

- 移除未使用的 Figtree 字体文件及相关样式引用,优化资源加载
- 修复订单详情页导航标题动态显示逻辑,根据订单状态显示对应标题
- 优化地图组件性能,减少不必要的标记点更新和路线重绘
- 修复车辆图片数据解析逻辑,支持 JSON 字符串格式的 vehiclePictures
- 调整地图视野适配策略,根据不同行程状态显示对应标记点和路线
- 修复行程状态切换时的地图显示问题,避免状态6时重复刷新标记点
- 优化路线点更新频率,减少在 Android 和 iOS 上的性能消耗
- 修复乘客通知页面的 RichText 节点渲染问题
- 调整首页和行程页面的 UI 布局和样式细节
4个文件已删除
17个文件已修改
574 ■■■■■ 已修改文件
src/app.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/fonts/Figtree-Light.ttf 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/fonts/Figtree-Regular.ttf 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/fonts/Figtree-SemiBold.ttf 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/fonts/font.less 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.less 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/release-route.less 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/release-route.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.less 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 370 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/coupon/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/expense-description/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/expense-detail/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/detail/index.config.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/detail/index.less 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/detail/index.tsx 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/passenger-notice/index.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/index.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app.less
@@ -4,8 +4,6 @@
 * 但是 H5 上内置组件样式可能会错乱,因此暂时不这样处理,有用到 flex 的地方都显式声明主轴方向即可
 */
@import './assets/fonts/font.less';
/* weapp */
page {
  height: 100%;
src/assets/fonts/Figtree-Light.ttf
Binary files differ
src/assets/fonts/Figtree-Regular.ttf
Binary files differ
src/assets/fonts/Figtree-SemiBold.ttf
Binary files differ
src/assets/fonts/font.less
File was deleted
src/pages/robotaxi/home/index.less
@@ -44,8 +44,8 @@
}
.origin-box {
  position: absolute;
  top: 64%;
  position: fixed;
  top: 55%;
  right: 32px;
  z-index: 1;
@@ -58,8 +58,8 @@
.content-box-top {
  width: 100%;
  position: absolute;
  bottom: 26%;
  position: fixed;
  top: 55%;
  left: 0px;
  z-index: 1;
@@ -543,7 +543,7 @@
        margin-left: 16px;
        view:first-child {
          font-family: 'Figtree-Regular';
          font-size: 28px;
          line-height: 44px;
          letter-spacing: normal;
src/pages/robotaxi/home/index.tsx
@@ -296,7 +296,6 @@
  });
  useEffect(() => {
    return
    let timer: any = null;
    const parseTime = (s: string) => {
      if (!s) return NaN;
@@ -308,7 +307,7 @@
      if (isNaN(d.getTime())) return '';
      const hh = String(d.getHours()).padStart(2, '0');
      const mm = String(d.getMinutes()).padStart(2, '0');
      return `${hh}:${mm}`;
      return `${hh}:${mm}`;
    };
    const check = () => {
      getHotFixInfo().then((res: any) => {
@@ -1072,7 +1071,7 @@
            }
            {/* 系统维护 */}
            {
              isMaintain && <View className={`is-maintain ml-32 ${maintainBounce ? 'maintain-bounce' : ''}`}>系统维护中,预计今日{hotfixEndText || '11:00'}恢复服务</View>
              isMaintain && <View className={`is-maintain ml-32 ${maintainBounce ? 'maintain-bounce' : ''}`}>系统维护中,预计今日{hotfixEndText || '-:-'}恢复服务</View>
            }
            {/* 查看运营区域 */}
            {
@@ -1182,7 +1181,12 @@
                {
                  [4, 5, 6, 7, 8].includes(tripStatus) &&
                  <View className="order-status-left">
                    <Image className="order-status-left-icon" src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png' />
                    {
                      orderData.vehiclePictures ?
                        <Image className="order-status-left-icon" src={JSON.parse(orderData.vehiclePictures)?.url || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png'} />
                        :
                        <Image className="order-status-left-icon" src='https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png' />
                    }
                    <View className="order-status-left-detail">
                      <View>{orderData.plateNumber}</View>
                      <View>{ORDER_STATUS[tripStatus]}</View>
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -36,8 +36,8 @@
    handleOpenAnnouncementsPopup?: () => void; // 打开公告弹窗
    estimatedWaitMinutes?: number; // 预计等待时间
    routeDurationSeconds?: number | null; // 预计行程时间
    vehicleInfo?: { // 车辆信息
        vehiclePictures?: string[] | string; // 车辆图片
    vehicleInfo?: {
        vehiclePictures?: string; // 车辆图片
        vehicleCode?: string; // 车辆编号
        vehicleColor?: string; // 车辆颜色
        plateNumber?: string; // 车牌号
@@ -473,7 +473,10 @@
                                车身编码: {vehicleInfo?.vehicleCode}
                            </View>
                        </View>
                        <Image className="car-card-detail-img" src={vehicleInfo?.vehiclePictures?.[0].url || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png'} />
                        {
                            vehicleInfo?.vehiclePictures &&
                            <Image className="car-card-detail-img" src={JSON.parse(vehicleInfo?.vehiclePictures)?.url || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png'} />
                        }
                    </View>
                    {
                        tripStatus == 4 && <View>
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -30,7 +30,7 @@
            align-items: center;
            .time {
                font-family: 'Figtree-Regular';
                margin: 0 4px;
                font-size: 40px;
                line-height: 40px;
@@ -87,7 +87,7 @@
            justify-content: space-between;
            &-num {
                font-family: 'Figtree-Regular';
                font-size: 48px;
                line-height: 64px;
                letter-spacing: normal;
@@ -216,7 +216,7 @@
                    align-items: center;
                    view:nth-child(1) {
                        font-family: 'Figtree-Regular';
                        font-size: 48px;
                        line-height: 40px;
                        letter-spacing: normal;
@@ -232,7 +232,7 @@
                    }
                    view:nth-child(3) {
                        font-family: 'Figtree-Light';
                        font-size: 28px;
                        line-height: 36px;
                        letter-spacing: normal;
@@ -377,7 +377,7 @@
        padding-top: 22px;
        &-text {
            font-family: 'Figtree-Light';
            font-size: 28px;
            line-height: 44px;
            letter-spacing: normal;
src/robotaxi/pages/begin-route/components/css-folder/release-route.less
@@ -141,7 +141,7 @@
                    }
                    .money-num {
                        font-family: 'Figtree-Regular';
                        margin: 0 4px;
                        font-size: 48px;
                        line-height: 40px;
@@ -200,7 +200,7 @@
                    }
                    .original-fare {
                        font-family: 'Figtree-Light';
                        font-size: 28px;
                        font-weight: normal;
                        line-height: 40px;
src/robotaxi/pages/begin-route/components/release-route.tsx
@@ -73,7 +73,7 @@
                        </View>
                        <View className="route-content-bottom-right">
                            {fareEstimate.discountAmount > 0 && <View className="discount-amount">已优惠{fareEstimate.discountAmount}元</View>}
                            <View className="original-fare">{fareEstimate.originalFare || '-'}元</View>
                            <View className="original-fare">{fareEstimate.originalFareStr || '-'}元</View>
                        </View>
                    </View>
                </View>
src/robotaxi/pages/begin-route/index.less
@@ -20,7 +20,7 @@
.map-entry {
    position: fixed;
    top: 236px;
    top: 200px;
    right: 32px;
    display: flex;
    align-items: center;
@@ -83,6 +83,10 @@
    flex-direction: column;
}
.pd-6-10 {
    padding: 12px 20px !important;
}
.waiting-callout-content {
    position: absolute;
    top: 240px;
@@ -93,21 +97,20 @@
.custom-callout-content {
    display: flex;
    align-items: center;
    // text-align: center;
    border-radius: 16px;
    background: rgba(255, 255, 255, 0.9);
    background: rgba(255, 255, 255, 1);
    padding: 12px 12px 12px 20px;
    font-size: 24px;
    font-weight: 500;
    font-weight: 400;
    color: rgba(0, 0, 0, 0.9);
    line-height: 48px;
    box-shadow: 0px 4px 16px 0px rgba(45, 48, 64, 0.2);
    padding: 12px 20px;
}
.car-time {
    font-size: 24px;
    letter-spacing: normal;
    color: #3D3D3D;
    display: flex;
    align-items: center;
    justify-content: center;
@@ -117,6 +120,7 @@
    font-size: 36px;
    letter-spacing: normal;
    color: #6D7FD9;
    margin-bottom: 4px;
}
.custom-callout-icon {
@@ -181,7 +185,7 @@
    position: relative;
    z-index: 100;
    font-size: 22px;
    font-weight: 500;
    font-weight: 400;
    letter-spacing: normal;
    color: #FFFFFF;
    line-height: 48px;
@@ -197,17 +201,17 @@
    display: flex;
    align-items: center;
    border-radius: 16px;
    background: rgba(255, 255, 255, 0.9);
    background: rgba(255, 255, 255, 1);
    padding: 12px 20px;
    // min-width: 155px;
}
.waiting-callout-txt-content {
    font-size: 24px;
    font-weight: 500;
    font-weight: 400;
    letter-spacing: normal;
    color: rgba(0, 0, 0, 0.9);
    line-height: 48px;
    word-break: break-word;
    // text-align: center;
}
}
src/robotaxi/pages/begin-route/index.tsx
@@ -58,6 +58,7 @@
  // 步行路径点
  const [walkingPoints, setWalkingPoints] = useState<any[]>([]);
  const [walkingTime, setWalkingTime] = useState<number>(0);
  const carMarkerInitialized = useRef(false);
  const [fareEstimate, setFareEstimate] = useState<any>({}); // 预估费用信息
  const [vehicleLocation, setVehicleLocation] = useState<{ latitude: number, longitude: number } | null>(null);
  const [vehicleDirection, setVehicleDirection] = useState<number | null>(null);
@@ -78,13 +79,24 @@
  const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0);
  const [originOpacity, setOriginOpacity] = useState(1);
  const markersRef = useRef<any[]>([]);
  const tripStatusRef = useRef(tripStatus); // 行程状态引用
  const lastTripStatusRef = useRef(tripStatus);
  const routeRequestKeyRef = useRef<string>(''); // 路线请求键
  const allowIncludePointsRef = useRef(true); // 是否允许包含地图点
  const orderRouteInitializedRef = useRef(false);
  // 在发布行程状态也允许展示路线
  const shouldShowRoute = [1, 4, 6, 8, 9].includes(tripStatus);
  const STATUS_RULES: Record<number, any> = {
    1: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: true, viewport: 'route_walking', translateCar: false },
    2: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    3: { showPickup: true, showDropoff: false, showCar: false, showRoute: false, showWalking: false, viewport: 'pickup', translateCar: false },
    4: { showPickup: true, showDropoff: false, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
    5: { showPickup: true, showDropoff: false, showCar: true, showRoute: false, showWalking: false, viewport: 'car_user', translateCar: true },
    6: { showPickup: false, showDropoff: true, showCar: true, showRoute: true, showWalking: false, viewport: 'route', translateCar: true },
    7: { showPickup: false, showDropoff: true, showCar: false, showRoute: false, showWalking: false, viewport: 'dropoff', translateCar: false },
    8: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
    9: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: false, viewport: 'order_detail', translateCar: false },
  };
  const statusRule = useMemo(() => STATUS_RULES[tripStatus] || STATUS_RULES[1], [tripStatus]);
  const shouldShowRoute = statusRule.showRoute;
  const authSuccessBound = useRef(false); // 认证成功是否绑定
  const messageBound = useRef(false); // 消息是否绑定
  const orderStatusBound = useRef(false); // 订单状态是否绑定
@@ -99,6 +111,12 @@
  // 记录已为某订单拉取过车辆信息,避免重复
  const lastFetchedVehicleOrderIdRef = useRef<string | null>(null);
  const mapRectRef = useRef<any>(null);
  const viewportKeyRef = useRef<string>('');
  const tripStatus6ViewportLockedRef = useRef(false);
  const polylineKeyRef = useRef<string>('');
  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();
@@ -113,11 +131,18 @@
    const wasHold = [6].includes(lastTripStatusRef.current);
    const isHold = [6].includes(tripStatus);
    if (!wasHold && isHold) {
      allowIncludePointsRef.current = true;
      orderRouteInitializedRef.current = false;
      tripStatus6ViewportLockedRef.current = false;
    }
    if (!isHold) {
      tripStatus6ViewportLockedRef.current = false;
    }
    lastTripStatusRef.current = tripStatus;
  }, [tripStatus]);
  useEffect(() => {
    markersRef.current = markers;
  }, [markers]);
  const checkAndShowPassengerNotice = () => {
    const checkedTimeRaw = Taro.getStorageSync('passengerNoticeCheckedTime');
@@ -218,10 +243,48 @@
  // 应用路线点
  const applyRoutePoints = (points: any[]) => {
    if (Array.isArray(points) && points.length > 0) {
      const lastPoint = points[points.length - 1];
      const lastLatitude = Number(lastPoint?.latitude);
      const lastLongitude = Number(lastPoint?.longitude);
      const now = Date.now();
      const prev = lastRoutePointRef.current;
      if (Number.isFinite(lastLatitude) && Number.isFinite(lastLongitude) && prev) {
        const delta = Math.abs(prev.latitude - lastLatitude) + Math.abs(prev.longitude - lastLongitude);
        const minDelta = isAndroid ? 0.00003 : 0.00002;
        const minInterval = isAndroid ? 1200 : 800;
        if (delta < minDelta && now - lastRouteUpdateAtRef.current < minInterval) {
          return;
        }
      }
      if (Number.isFinite(lastLatitude) && Number.isFinite(lastLongitude)) {
        lastRoutePointRef.current = { latitude: lastLatitude, longitude: lastLongitude };
        lastRouteUpdateAtRef.current = now;
      } else {
        lastRoutePointRef.current = null;
        lastRouteUpdateAtRef.current = 0;
      }
      setRoutePoints(points);
    } else {
      lastRoutePointRef.current = null;
      lastRouteUpdateAtRef.current = 0;
      setRoutePoints([]);
    }
  };
  const reducePoints = (points: any[], maxCount: number) => {
    if (!Array.isArray(points) || points.length <= maxCount) {
      return points;
    }
    const step = Math.ceil(points.length / maxCount);
    const reduced = [];
    for (let i = 0; i < points.length; i += step) {
      reduced.push(points[i]);
    }
    const last = points[points.length - 1];
    if (reduced[reduced.length - 1] !== last) {
      reduced.push(last);
    }
    return reduced;
  };
  // 归一化距离(公里或米)
@@ -299,6 +362,16 @@
      }
      if (Array.isArray(points) && points.length > 0) {
        applyRoutePoints(points);
        if (tripStatusRef.current === 1) {
          const mapCtx = Taro.createMapContext('map');
          const allPoints = [...points, ...walkingPoints];
          if (allPoints.length > 0) {
            mapCtx.includePoints({
              points: allPoints,
              padding: [100, 100, 280, 100]
            });
          }
        }
      } else {
        applyRoutePoints([]);
      }
@@ -413,6 +486,18 @@
      if (mappedTripStatus === 5) {
        setRoutePoints([]);
        setPolyline([]);
        polylineKeyRef.current = '';
        lastPolylineUpdateAtRef.current = 0;
        lastRoutePointRef.current = null;
        lastRouteUpdateAtRef.current = 0;
      }
      if (mappedTripStatus === 4) {
        setRoutePoints([]);
        setPolyline([]);
        polylineKeyRef.current = '';
        lastPolylineUpdateAtRef.current = 0;
        lastRoutePointRef.current = null;
        lastRouteUpdateAtRef.current = 0;
      }
      if (payload?.showCancelPopup) {
        handleOpenCancelPopupWithType(5);
@@ -423,6 +508,9 @@
    };
    orderRouteHandlerRef.current = (payload) => {
      if (tripStatusRef.current === 5) {
        return;
      }
      const points = payload?.points || [];
      const direction = payload?.direction;
      if (typeof direction === 'number' && Number.isFinite(direction)) {
@@ -535,14 +623,10 @@
    // tripStatus=2/3:等待计时
    if (tripStatus == 2 || tripStatus == 3) {
      const s = Taro.getStorageSync('startSite');
      if (s && s.latitude && s.longitude) {
      const hasPlannedRoute = Array.isArray(routePoints) && routePoints.length > 0;
      if (!hasPlannedRoute && s && s.latitude && s.longitude) {
        setLatitude(s.latitude);
        setLongitude(s.longitude);
        const mapCtx = Taro.createMapContext('map');
        mapCtx.moveToLocation({
          latitude: s.latitude,
          longitude: s.longitude,
        });
      }
      const cachedStartAt = Number(Taro.getStorageSync(WAITING_TIMER_START_KEY));
@@ -564,33 +648,11 @@
      timer = setInterval(() => {
        updateElapsed();
      }, 1000);
    } else if (tripStatus == 5) {
      const s = Taro.getStorageSync('startSite');
      if (s && s.latitude && s.longitude) {
        setLatitude(s.latitude);
        setLongitude(s.longitude);
        const mapCtx = Taro.createMapContext('map');
        mapCtx.moveToLocation({
          latitude: s.latitude,
          longitude: s.longitude,
        });
      }
      setElapsedSeconds(0);
      Taro.removeStorageSync(WAITING_TIMER_START_KEY);
      Taro.removeStorageSync(WAITING_TIMER_ELAPSED_KEY);
    } else if (tripStatus == 7) {
      const e = Taro.getStorageSync('endSite');
      if (e && e.latitude && e.longitude) {
        setLatitude(e.latitude);
        setLongitude(e.longitude);
        const mapCtx = Taro.createMapContext('map');
        mapCtx.includePoints({
          points: [{
            latitude: e.latitude,
            longitude: e.longitude,
          }],
          padding: [100, 100, 280, 100]
        });
      }
      setElapsedSeconds(0);
      Taro.removeStorageSync(WAITING_TIMER_START_KEY);
@@ -599,25 +661,15 @@
      getOrderDetail(Taro.getStorageSync('orderId')).then((res: any) => {
        const mapCtx = Taro.createMapContext('map');
        if (res.trajectories) {
          const points = res.trajectories.map((item: any) => ({
            latitude: item.location.split(',')[1],
            longitude: item.location.split(',')[0],
          }));
          const s = Taro.getStorageSync('startSite');
          const e = Taro.getStorageSync('endSite');
          points.push({
            latitude: s.latitude,
            longitude: s.longitude,
          })
          points.push({
            latitude: e.latitude,
            longitude: e.longitude,
          })
          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);
          mapCtx.includePoints({
            points: points,
          padding: [100, 100, 280, 100]
          })
        } else {
          mapCtx.moveToLocation({
            latitude: 39.916527,
@@ -661,35 +713,36 @@
    }
  });
  // 在tripStatus=1时也绘制预估路线与步行路线
  // 构建并更新地图标记点(与路线解耦,避免路线更新导致标记刷新)
  useEffect(() => {
    const s = Taro.getStorageSync('startSite');
    const pickupMarker = s?.latitude && s?.longitude ? {
    const pickupSite = startSite?.latitude && startSite?.longitude ? startSite : Taro.getStorageSync('startSite');
    const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
    const pickupMarker = pickupSite?.latitude && pickupSite?.longitude ? {
      id: 1,
      latitude: s.latitude,
      longitude: s.longitude,
      latitude: pickupSite.latitude,
      longitude: pickupSite.longitude,
      iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/51e7cb75-72af-4994-9d6e-d999efb2fbc7.png',
      width: 22,
      height: 40,
      anchor: { x: 0.5, y: 1 },
      zIndex: 1,
      name: s?.name || '上车点',
      name: pickupSite?.name || '上车点',
      customCallout: {
        anchorY: [2, 3].includes(tripStatus) ? 150 : -5,
        anchorX: 0,
        display: 'ALWAYS'
      }
    } : null;
    const dropoffMarker = endSite?.latitude && endSite?.longitude ? {
    const dropoffMarker = dropoffSite?.latitude && dropoffSite?.longitude ? {
      id: 2,
      latitude: endSite.latitude,
      longitude: endSite.longitude,
      latitude: dropoffSite.latitude,
      longitude: dropoffSite.longitude,
      iconPath: 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/6a56b1eb-ec37-4839-8733-262942af070d.png',
      width: 22,
      height: 40,
      anchor: { x: 0.5, y: 1 },
      zIndex: 1,
      name: endSite.name || '目的地',
      name: dropoffSite.name || '目的地',
      customCallout: {
        anchorY: -5,
        anchorX: 0,
@@ -710,28 +763,56 @@
      customCallout: { anchorY: 50, anchorX: 0, display: 'ALWAYS' }
    } : null;
    const includePickup = [1, 2, 3, 4, 5, 8, 9].includes(tripStatus);
    const includeDropoff = [1, 6, 7, 8, 9].includes(tripStatus);
    const includeCar = [4, 5, 6, 7].includes(tripStatus);
    const nextMarkers: any[] = [];
    if (includePickup && pickupMarker) nextMarkers.push(pickupMarker);
    if (includeCar && carMarker) nextMarkers.push(carMarker);
    if (includeDropoff && dropoffMarker) nextMarkers.push(dropoffMarker);
    setMarkers(nextMarkers);
    if (statusRule.showPickup && pickupMarker) nextMarkers.push(pickupMarker);
    if (statusRule.showCar && carMarker) nextMarkers.push(carMarker);
    if (statusRule.showDropoff && dropoffMarker) nextMarkers.push(dropoffMarker);
    const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2);
    const shouldSkipUpdate = tripStatus === 6 && carMarkerInitialized.current && statusRule.showCar && (!statusRule.showDropoff || hasDropoffMarker);
    if (shouldSkipUpdate) {
    } else {
      setMarkers(nextMarkers);
    }
    carMarkerInitialized.current = statusRule.showCar && !!carMarker;
  }, [tripStatus, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation, vehicleDirection]);
    if (shouldShowRoute && (routePoints.length > 0 || (tripStatus === 1 && walkingPoints.length > 0))) {
  // 绘制路线与视野适配
  useEffect(() => {
    const displayRoutePoints = reducePoints(routePoints, isAndroid ? 120 : 200);
    const lastRoutePoint = displayRoutePoints[displayRoutePoints.length - 1];
    const lastWalkingPoint = walkingPoints[walkingPoints.length - 1];
    const isTripMoving = [4, 5, 6].includes(tripStatus);
    const now = Date.now();
    const throttle = isAndroid ? 1200 : 800;
    if (shouldShowRoute && isTripMoving && displayRoutePoints.length > 0 && now - lastPolylineUpdateAtRef.current < throttle) {
      return;
    }
    const nextKey = [
      shouldShowRoute ? 1 : 0,
      statusRule.showWalking ? 1 : 0,
      displayRoutePoints.length,
      walkingPoints.length,
      lastRoutePoint?.latitude ?? '',
      lastRoutePoint?.longitude ?? '',
      lastWalkingPoint?.latitude ?? '',
      lastWalkingPoint?.longitude ?? '',
    ].join('|');
    if (nextKey === polylineKeyRef.current) {
      return;
    }
    polylineKeyRef.current = nextKey;
    if (shouldShowRoute && (displayRoutePoints.length > 0 || (statusRule.showWalking && walkingPoints.length > 0))) {
      const lines: any[] = [];
      if (routePoints.length > 0) {
      if (displayRoutePoints.length > 0) {
        lines.push({
          points: routePoints,
          points: displayRoutePoints,
          color: '#7B91FF',
          width: 10,
          dottedLine: false,
          arrowLine: true,
        });
      }
      // 仅在发布行程阶段展示步行路线
      if (tripStatus === 1 && walkingPoints.length > 0) {
      if (statusRule.showWalking && walkingPoints.length > 0) {
        lines.push({
          points: walkingPoints,
          color: '#7B91FF',
@@ -741,30 +822,106 @@
        });
      }
      setPolyline(lines);
      if (![6].includes(tripStatus) || allowIncludePointsRef.current) {
        const mapCtx = Taro.createMapContext('map');
        if ([8, 9].includes(tripStatus) && routePoints.length > 0) {
          mapCtx.includePoints({
            points: routePoints,
          padding: [100, 100, 280, 100]
          });
        } else {
          const allPoints = tripStatus === 1
            ? [...routePoints, ...walkingPoints]
            : [...routePoints];
          mapCtx.includePoints({
            points: allPoints,
            padding: [100, 100, ![1, 6].includes(tripStatus) ? 280 : 100, 100]
          });
        }
        if ([6].includes(tripStatus)) {
          allowIncludePointsRef.current = false;
        }
      }
      lastPolylineUpdateAtRef.current = now;
    } else {
      setPolyline([]);
      lastPolylineUpdateAtRef.current = 0;
    }
  }, [tripStatus, shouldShowRoute, routePoints, walkingPoints, startSite?.name, startSite?.latitude, startSite?.longitude, endSite.name, endSite?.latitude, endSite?.longitude, vehicleLocation, vehicleDirection, routeDistance?.value, routeDistance?.unit, routeDuration]);
  }, [tripStatus, shouldShowRoute, routePoints, walkingPoints, statusRule.showWalking, isAndroid]);
  useEffect(() => {
    const pickupSite = startSite?.latitude && startSite?.longitude ? startSite : Taro.getStorageSync('startSite');
    const dropoffSite = endSite?.latitude && endSite?.longitude ? endSite : Taro.getStorageSync('endSite');
    const viewportType = statusRule.viewport;
    const points: any[] = [];
    const keyParts: any[] = [tripStatus, viewportType];
    if (viewportType === 'route_walking') {
      if (routePoints.length > 0) {
        points.push(...routePoints);
      }
      if (walkingPoints.length > 0) {
        points.push(...walkingPoints);
      }
      keyParts.push(routePoints.length, walkingPoints.length);
    } else if (viewportType === 'pickup') {
      if (pickupSite?.latitude && pickupSite?.longitude) {
        points.push({ latitude: pickupSite.latitude, longitude: pickupSite.longitude });
      }
      keyParts.push(pickupSite?.latitude, pickupSite?.longitude);
    } else if (viewportType === 'route') {
      if (routePoints.length > 0) {
        points.push(...routePoints);
      }
      keyParts.push(routePoints.length);
    } else if (viewportType === 'car_user') {
      if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
        points.push({ latitude, longitude });
      }
      if (vehicleLocation?.latitude && vehicleLocation?.longitude) {
        points.push({ latitude: vehicleLocation.latitude, longitude: vehicleLocation.longitude });
      }
      keyParts.push(latitude, longitude, vehicleLocation?.latitude, vehicleLocation?.longitude);
    } else if (viewportType === 'dropoff') {
      if (dropoffSite?.latitude && dropoffSite?.longitude) {
        points.push({ latitude: dropoffSite.latitude, longitude: dropoffSite.longitude });
      }
      keyParts.push(dropoffSite?.latitude, dropoffSite?.longitude);
    } else if (viewportType === 'order_detail') {
      if (routePoints.length > 0) {
        points.push(...routePoints);
      }
      if (pickupSite?.latitude && pickupSite?.longitude) {
        points.push({ latitude: pickupSite.latitude, longitude: pickupSite.longitude });
      }
      if (dropoffSite?.latitude && dropoffSite?.longitude) {
        points.push({ latitude: dropoffSite.latitude, longitude: dropoffSite.longitude });
      }
      keyParts.push(routePoints.length, pickupSite?.latitude, pickupSite?.longitude, dropoffSite?.latitude, dropoffSite?.longitude);
    }
    if (points.length === 0) return;
    if (tripStatus === 6 && viewportType === 'route' && tripStatus6ViewportLockedRef.current) {
      return;
    }
    const nextKey = keyParts.join('|');
    if (nextKey === viewportKeyRef.current) return;
    viewportKeyRef.current = nextKey;
    const mapCtx = Taro.createMapContext('map');
    const padding = viewportType === 'order_detail' ? [50, 50, 200, 50] : [100, 100, 280, 100];
    Taro.nextTick(() => {
      mapCtx.includePoints({
        points,
        padding
      });
    });
    if (tripStatus === 6 && viewportType === 'route') {
      tripStatus6ViewportLockedRef.current = true;
    }
  }, [tripStatus, statusRule, routePoints, walkingPoints, startSite?.latitude, startSite?.longitude, endSite?.latitude, endSite?.longitude, vehicleLocation, longitude, latitude]);
  useEffect(() => {
    if (!statusRule.translateCar) return;
    if (!carMarkerInitialized.current) return;
    if (!vehicleLocation) return;
    const mapCtx = Taro.createMapContext('map');
    Taro.nextTick(() => {
      mapCtx.translateMarker({
        markerId: 3,
        destination: {
          latitude: vehicleLocation.latitude,
          longitude: vehicleLocation.longitude,
        },
        autoRotate: false,
        rotate: typeof vehicleDirection === 'number' ? vehicleDirection : 0,
        duration: 500,
        fail: (err) => {
          console.log(err);
        },
      });
    });
  }, [tripStatus, vehicleLocation, vehicleDirection]);
  const bindSocketListeners = () => {
    if (!orderStatusBound.current && orderStatusHandlerRef.current) {
@@ -840,9 +997,7 @@
      updateWalkingRouteForStart();
    }
    if (cachedOrderId) {
      ensureSocketListeners();
    }
    ensureSocketListeners();
  });
  useEffect(() => {
@@ -921,8 +1076,21 @@
      estimatedTime: estimatedTime,
    };
    try {
      const { orderId } = await saveOrder(payload);
      Taro.setStorageSync('orderId', orderId);
      const res = await saveOrder(payload);
      if (typeof res == 'string') {
        Taro.showToast({
          title: res,
          icon: 'none',
          mask: true,
        })
        setTimeout(() => {
          Taro.reLaunch({
            url: '/robotaxi/pages/begin-route/index',
          })
        }, 1500);
        return
      }
      Taro.setStorageSync('orderId', res?.orderId || '');
    } catch (error) {
      return
    }
@@ -1197,8 +1365,8 @@
              <CoverView slot="callout" marker-id={item.id} key={`${tripStatus}-${item.id}`} className="custom-callout">
                {
                  (!['车辆位置', '已到达'].includes(item.name)) &&
                  <CoverView className="custom-callout-content">
                    {item.name}
                  <CoverView className={`custom-callout-content`}>
                    {item.name}<CoverView style={{ width: '2px' }}></CoverView>
                    {
                      tripStatus == 1 && <CoverImage className="custom-callout-icon" src={customCalloutIconSrc} />
                    }
@@ -1210,7 +1378,7 @@
                    <CoverView className="car-time">
                      <CoverView className="car-time-num">{routeDistance?.value ?? 0}</CoverView>
                      <CoverView>{routeDistance?.unit === 'm' ? '米' : '公里'}</CoverView>
                      <CoverView style={{ marginLeft: '4px' }} className="car-time-num">{durationDisplay.value}</CoverView>
                      <CoverView style={{ marginLeft: '2.5px' }} className="car-time-num">{durationDisplay.value}</CoverView>
                      <CoverView>{durationDisplay.unit}</CoverView>
                    </CoverView>
                  </CoverView>
src/robotaxi/pages/coupon/index.less
@@ -79,7 +79,7 @@
      }
      .price {
        font-family: 'Figtree-Regular';
        font-size: 68px;
        line-height: 72px;
        letter-spacing: normal;
src/robotaxi/pages/expense-description/index.less
@@ -114,7 +114,7 @@
    }
    .num {
      font-family: 'Figtree-Regular';
      font-size: 48px;
      line-height: 40px;
      font-variation-settings: "opsz" auto;
src/robotaxi/pages/expense-detail/index.less
@@ -132,7 +132,7 @@
    }
    .num {
      font-family: 'Figtree-Regular';
      font-size: 48px;
      line-height: 40px;
      font-variation-settings: "opsz" auto;
src/robotaxi/pages/order/detail/index.config.ts
@@ -1,3 +1,3 @@
export default {
  navigationBarTitleText: '订单已结束',
  navigationBarTitleText: '',
};
src/robotaxi/pages/order/detail/index.less
@@ -156,7 +156,7 @@
        align-items: center;
        .price {
          font-family: 'Figtree-Regular';
          font-size: 48px;
          line-height: 40px;
          letter-spacing: normal;
@@ -174,7 +174,7 @@
        }
        .old-price {
          font-family: 'Figtree-Light';
          font-size: 28px;
          font-weight: normal;
          line-height: 40px;
src/robotaxi/pages/order/detail/index.tsx
@@ -1,4 +1,4 @@
import Taro, { useRouter } from '@tarojs/taro';
import Taro, { useRouter, useDidShow } from '@tarojs/taro';
import { useState, useEffect } from 'react';
import { View, Text, Image, Map } from '@tarojs/components';
import './index.less';
@@ -34,9 +34,25 @@
    }
  }, [orderId]);
  const getNavTitle = (status: number) => {
    if (status == 0) {
      return '已取消'
    }
    if (status >= 1 && status <= 7) {
      return '进行中'
    }
    if (status == 8) {
      return '待支付'
    }
    if (status >= 9) {
      return '已完成'
    }
  }
  const fetchOrderDetail = async (id: string) => {
    try {
      const res = await getOrderDetail(id);
      Taro.setNavigationBarTitle({ title: getNavTitle(res.status) || '订单详情' });
      setOrderData(res);
      initMap(res);
      setActiveIndex(res.orderRating);
@@ -147,44 +163,50 @@
  return (
    <View className='order-detail-page'>
      {/* 车辆信息 */}
      <View className='car-info'>
        {
          orderData.status != 1 &&
          <View className='info-top'>
            <View>
              <View className='car-name'>{orderData.plateNumber}</View>
              <View className='info'>
                <Text className='car-color'>{orderData.vehicleColor}</Text>
                <Text className='info-line'></Text>
                <Text className='car-code'>车身编码: </Text>
                <Text className='code'>{orderData.vehicleCode}</Text>
      {
        (orderData.vehicleId || orderData.status >= 9) &&
        <View className='car-info'>
          {
            orderData.vehicleId &&
            <View className='info-top'>
              <View>
                <View className='car-name'>{orderData.plateNumber}</View>
                <View className='info'>
                  <Text className='car-color'>{orderData.vehicleColor}</Text>
                  <Text className='info-line'></Text>
                  <Text className='car-code'>车身编码: </Text>
                  <Text className='code'>{orderData.vehicleCode}</Text>
                </View>
              </View>
              <Image src={JSON.parse(orderData.vehiclePictures).url || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png'} className='car-img' />
            </View>
          }
          {
            orderData.status >= 9 &&
            <View className='evaluate-card'>
              <View className='card-title'>您对本次行程满意吗?</View>
              <View className='card-content'>
                <View className={`evaluate-item ${activeIndex == 1 ? 'active' : ''}`} onClick={() => changeEvaluate(1)}>
                  <Image src={activeIndex == 1 ? activeBad : bad} className='evaluate-img' />
                  <Text className={`evaluate-text ${activeIndex == 1 ? 'active-text' : ''}`}>非常不满意</Text>
                </View>
                <View className={`evaluate-item ${activeIndex == 2 ? 'active' : ''}`} onClick={() => changeEvaluate(2)}>
                  <Image src={activeIndex == 2 ? activeNormal : normal} className='evaluate-img' />
                  <Text className={`evaluate-text ${activeIndex == 2 ? 'active-text' : ''}`}>一般般</Text>
                </View>
                <View className={`evaluate-item ${activeIndex == 3 ? 'active' : ''}`} onClick={() => changeEvaluate(3)}>
                  <Image src={activeIndex == 3 ? activeGood : good} className='evaluate-img' />
                  <Text className={`evaluate-text ${activeIndex == 3 ? 'active-text' : ''}`}>非常满意</Text>
                </View>
              </View>
            </View>
            <Image src={JSON.parse(orderData.vehiclePictures).url || 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/552a3a31-e4dd-4a28-8748-fd8b87dd3055.png'} className='car-img' />
          </View>
        }
        <View className='evaluate-card'>
          <View className='card-title'>您对本次行程满意吗?</View>
          <View className='card-content'>
            <View className={`evaluate-item ${activeIndex == 1 ? 'active' : ''}`} onClick={() => changeEvaluate(1)}>
              <Image src={activeIndex == 1 ? activeBad : bad} className='evaluate-img' />
              <Text className={`evaluate-text ${activeIndex == 1 ? 'active-text' : ''}`}>非常不满意</Text>
            </View>
            <View className={`evaluate-item ${activeIndex == 2 ? 'active' : ''}`} onClick={() => changeEvaluate(2)}>
              <Image src={activeIndex == 2 ? activeNormal : normal} className='evaluate-img' />
              <Text className={`evaluate-text ${activeIndex == 2 ? 'active-text' : ''}`}>一般般</Text>
            </View>
            <View className={`evaluate-item ${activeIndex == 3 ? 'active' : ''}`} onClick={() => changeEvaluate(3)}>
              <Image src={activeIndex == 3 ? activeGood : good} className='evaluate-img' />
              <Text className={`evaluate-text ${activeIndex == 3 ? 'active-text' : ''}`}>非常满意</Text>
            </View>
          </View>
          }
        </View>
      </View>
      }
      {/* 行程信息 */}
      <View className='trip-info'>
        {
          orderData.status != 1 &&
          orderData.status != 0 &&
          <View className='trip-top'>
            <View className='price-card'>
              <Text className='price'>{orderData.payMoney || '-'}元</Text>
@@ -215,7 +237,7 @@
        {/* 行程信息 */}
        <View className='trip-line'>
          {
            orderData.status != 1 &&
            orderData.status != 0 &&
            <View className='line-name'>全程{orderData.totalMileage || '-'}公里|{orderData.orderTime || '-'}分钟</View>
          }
          <View className='line-address'>
src/robotaxi/pages/passenger-notice/index.tsx
@@ -28,10 +28,7 @@
    <View className='passenger-notice'>
      <View className="passenger-notice-title">{title}</View>
      <View className="passenger-notice-content">
        <RichText nodes={[{
          type: 'text',
          text: content
        }]} />
        <RichText nodes={content} />
      </View>
    </View>
  )
src/robotaxi/pages/site-search/index.tsx
@@ -315,9 +315,11 @@
    }
  }
  const recommendList = searchValue ? siteList : nearbySiteList;
  const recommendList = searchValue
    ? siteList
    : (pageType === 'end' && Taro.getStorageSync('startSite') ? [] : nearbySiteList);
  const visibleHistory = showAllHistory ? historySite.slice(0, MAX_EXPANDED_COUNT) : historySite.slice(0, MAX_SHOW_COUNT);
  const visibleRecommend = showAllRecommend ? recommendList : recommendList.slice(0, MAX_SHOW_COUNT);
  const visibleRecommend = showAllRecommend ? recommendList.slice(0, MAX_EXPANDED_COUNT) : recommendList.slice(0, MAX_SHOW_COUNT);
  // 使用数据截断展示,不再通过高度裁剪内容