zjk
2026-04-15 5c3a9d251a578eeaa6affc02a281d62633248bb4
Merge branch 'feature_v1.0' of http://gitlab.xiaopeng.local:18080/robotaxi1/xp-robotaxi-miniprogram into feature_v1.0
3个文件已添加
10个文件已修改
165 ■■■■■ 已修改文件
src/pages/robotaxi/home/index.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/begin-route.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/assets/blue-right-icon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/assets/bluetooth-icon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/begin-route-view.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/index.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.tsx
@@ -8,7 +8,7 @@
import MapOverlayTips from './components/map-overlay-tips';
import MapPointer from './components/map-pointer';
import selectCityIcon from '../../../robotaxi/assets/select-city.png'
import contactService from '../../../robotaxi/assets/contact-service.png'
import personalCenter from '../../../robotaxi/assets/personal-center.png'
import coupon from '../../../robotaxi/assets/coupon.png'
import suggest from '../../../robotaxi/assets/suggest.png'
import { getWeatherIconByDesc } from '../../../utils/weather';
@@ -463,7 +463,7 @@
        />
        {/* 地图浮层入口 */}
        <View className="map-entry">
          <Image onClick={openService} className="map-entry-icon" src={contactService} />
          <Image onClick={openService} className="map-entry-icon" src={personalCenter} />
          <View className="split-line"></View>
          <Image onClick={toCoupon} className="map-entry-icon" src={coupon} />
          <View className="split-line"></View>
src/robotaxi/api/begin-route.ts
@@ -51,7 +51,9 @@
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/vehicle/unlock',
        method: 'POST',
        payload
        payload,
        showToast: true,
        skipException: true
    }, true);
}
src/robotaxi/assets/blue-right-icon.png
src/robotaxi/assets/bluetooth-icon.png
src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -25,7 +25,7 @@
  onServiceEntry: () => void;
  onCouponEntry: () => void;
  onOrderEntry: () => void;
  contactService: string;
  personalCenter: string;
  coupon: string;
  suggest: string;
  shouldShowOrigin: boolean;
@@ -83,7 +83,7 @@
    onServiceEntry,
    onCouponEntry,
    onOrderEntry,
    contactService,
    personalCenter,
    coupon,
    suggest,
    shouldShowOrigin,
@@ -144,7 +144,7 @@
          <Image className='select-city-icon' src={backIcon} />
        </View>
        <View className='map-entry'>
          <Image onClick={onServiceEntry} className='map-entry-icon' src={contactService} />
          <Image onClick={onServiceEntry} className='map-entry-icon' src={personalCenter} />
          <View className='split-line'></View>
          <Image onClick={onCouponEntry} className='map-entry-icon' src={coupon} />
          <View className='split-line'></View>
src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx
New file
@@ -0,0 +1,19 @@
import { View, Image } from "@tarojs/components";
import bluetoothIcon from "@/assets/images/bluetooth-icon.png";
import blueRightIcon from "@/assets/images/blue-right-icon.png";
export default function BluetoothOperateCard() {
    return (
        <View className="bluetooth-operate-card">
            <View className="bluetooth-operate-card-left">
                <Image src={bluetoothIcon} />
                <View>系统蓝牙未开启,无法自动解锁车辆</View>
            </View>
            <View className="bluetooth-operate-card-right">
                <View>去开启</View>
                <Image src={blueRightIcon} />
            </View>
        </View>
    );
}
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -67,6 +67,8 @@
            doorType,
            ...locationPayload
        }).then((res: any) => {
            console.log(res,'***********');
            if (!res) {
                Taro.showToast({ title: '解锁失败,请重试或联系客服', icon: 'none' });
                return;
@@ -130,6 +132,8 @@
                }
            }
        }).catch(() => {
            console.log(res,'***********');
            Taro.showToast({ title: '解锁失败,请重试或联系客服', icon: 'none' });
        });
    };
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -64,6 +64,7 @@
            display: flex;
            align-items: center;
            justify-content: center;
            .cancel {
                flex-shrink: 0;
                width: 144px;
@@ -501,4 +502,47 @@
        mask-composite: exclude;
        pointer-events: none;
    }
}
.bluetooth-operate-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 24px 16px 16px;
    border-radius: 16px;
    background: #FFFFFF;
    .bluetooth-operate-card-left {
        display: flex;
        align-items: center;
        gap: 12px;
        font-size: 24px;
        font-weight: normal;
        line-height: 40px;
        letter-spacing: normal;
        color: rgba(0, 0, 0, 0.9);
        image {
            flex-shrink: 0;
            width: 24px;
            height: 28px;
        }
    }
    .bluetooth-operate-card-right {
        display: flex;
        align-items: center;
        gap: 11px;
        font-size: 24px;
        font-weight: normal;
        line-height: 40px;
        letter-spacing: normal;
        color: #6D7FD9;
        image {
            flex-shrink: 0;
            width: 11.97px;
            height: 17.5px;
        }
    }
}
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
@@ -120,6 +120,7 @@
    const cachedOrderId = Taro.getStorageSync('orderId');
    const cachedTripStatusStorage = Taro.getStorageSync('tripStatus');
    const cachedTripStatus = Number(cachedTripStatusStorage) || deps.getCurrentTripStatus();
    deps.setTripStatus(cachedTripStatus);
    syncCachedOrderVehicle(cachedOrderId);
    syncSitesFromActiveOrder(cachedTripStatusStorage, s, e);
    if (cachedOrderId && [4, 5, 6, 7, 8, 9].includes(cachedTripStatus)) {
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts
@@ -22,10 +22,17 @@
  const [trunkStatus, setTrunkStatus] = useState(false);
  const lastVehiclePointRef = useRef<{ latitude: number; longitude: number } | null>(null);
  const lastVehicleUpdateAtRef = useRef<number>(0);
  const translateDurationRef = useRef<number>(900);
  // 更新车辆位置并维护最近位置引用
  const updateVehicleLocationThrottled = (lat: number, lng: number, force = false) => {
    const now = Date.now();
    const prevUpdateAt = lastVehicleUpdateAtRef.current;
    if (prevUpdateAt > 0) {
      // 基于推送间隔动态调整平移时长,适配 1-2 秒频率的轨迹推送
      const interval = now - prevUpdateAt;
      translateDurationRef.current = Math.max(350, Math.min(1600, Math.floor(interval * 0.85)));
    }
    if (Number.isFinite(lat) && Number.isFinite(lng)) {
      lastVehiclePointRef.current = { latitude: lat, longitude: lng };
      lastVehicleUpdateAtRef.current = now;
@@ -35,7 +42,6 @@
  useEffect(() => {
    if (!statusRule.translateCar) return;
    if (vehicleMoveAlongPoints.length > 1) return;
    if (!vehicleLocation) return;
    const mapCtx = Taro.createMapContext('map');
    const timer = setTimeout(() => {
@@ -48,27 +54,11 @@
        autoRotate: false,
        moveWithRotate: true,
        rotate: typeof vehicleDirection === 'number' ? ((vehicleDirection % 360) + 360) % 360 : 0,
        duration: 100
        duration: translateDurationRef.current
      });
    }, 100);
    return () => clearTimeout(timer);
  }, [statusRule.translateCar, vehicleLocation, vehicleDirection, vehicleMarkerId, vehicleMoveAlongPoints]);
  useEffect(() => {
    if (!statusRule.translateCar) return;
    if (vehicleMoveAlongPoints.length < 2) return;
    const mapCtx = Taro.createMapContext('map');
    const duration = Math.min(2000, Math.max(300, vehicleMoveAlongPoints.length * 100));
    const timer = setTimeout(() => {
      mapCtx.moveAlong({
        markerId: vehicleMarkerId,
        path: vehicleMoveAlongPoints,
        autoRotate: true,
        duration
      });
    }, 50);
    return () => clearTimeout(timer);
  }, [statusRule.translateCar, vehicleMoveAlongPoints, vehicleMarkerId]);
  }, [statusRule.translateCar, vehicleLocation, vehicleDirection, vehicleMarkerId]);
  return {
    vehicleLocation,
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -66,8 +66,8 @@
    if (typeof mappedTripStatus !== 'number') {
        return null;
    }
    const currentLocalStatus = Number(Taro.getStorageSync('tripStatus'));
    if (currentLocalStatus > mappedTripStatus && currentLocalStatus !== 10) {
    const currentTripStatus = Number(deps.tripStatusRef.current);
    if (currentTripStatus > mappedTripStatus && currentTripStatus !== 10) {
        return null;
    }
    deps.setTripStatus(mappedTripStatus);
@@ -168,16 +168,30 @@
    }
};
const syncVehiclePointWithRoute = (points: any[], deps: UseWebSocketEventsDeps) => {
const resolveVehiclePoint = (points: any[], servedPoints: any[] = []) => {
    const servedLastPoint = Array.isArray(servedPoints) && servedPoints.length > 0
        ? servedPoints[servedPoints.length - 1]
        : null;
    if (servedLastPoint && typeof servedLastPoint.latitude === 'number' && typeof servedLastPoint.longitude === 'number') {
        return servedLastPoint;
    }
    const firstPoint = points?.[0];
    if (!firstPoint || typeof firstPoint.latitude !== 'number' || typeof firstPoint.longitude !== 'number') {
    if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
        return firstPoint;
    }
    return null;
};
const syncVehiclePointWithRoute = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => {
    const targetPoint = resolveVehiclePoint(points, servedPoints);
    if (!targetPoint) {
        return;
    }
    deps.updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
    deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
};
const handleEraseRouteStatus = (points: any[], servedPoints: any[], deps: UseWebSocketEventsDeps) => {
    const firstPoint = points?.[0];
    const targetPoint = resolveVehiclePoint(points, servedPoints);
    if (deps.orderRouteInitializedRef.current && deps.currentFullRouteRef.current.length > 0) {
        deps.erasePassedRouteSegments(points, servedPoints);
    } else {
@@ -188,23 +202,23 @@
            deps.applyRoutePoints(points);
        });
    }
    if (firstPoint && typeof firstPoint.latitude === 'number' && typeof firstPoint.longitude === 'number') {
    if (targetPoint) {
        setTimeout(() => {
            deps.updateVehicleLocationThrottled(firstPoint.latitude, firstPoint.longitude, true);
            deps.updateVehicleLocationThrottled(targetPoint.latitude, targetPoint.longitude, true);
        }, 50);
    }
};
const handleNormalRouteStatus = (points: any[], deps: UseWebSocketEventsDeps) => {
const handleNormalRouteStatus = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => {
    Taro.nextTick(() => {
        const updated = deps.applyRoutePoints(points);
        if (!updated) return;
        if (![4, 5, 6].includes(deps.tripStatusRef.current)) return;
        syncVehiclePointWithRoute(points, deps);
        syncVehiclePointWithRoute(points, deps, servedPoints);
    });
};
const handleForceFullRouteRedraw = (points: any[], deps: UseWebSocketEventsDeps) => {
const handleForceFullRouteRedraw = (points: any[], deps: UseWebSocketEventsDeps, servedPoints: any[] = []) => {
    deps.setRoutePoints([]);
    deps.setPolyline([]);
    deps.orderRouteInitializedRef.current = false;
@@ -220,7 +234,7 @@
        deps.currentFullRouteRef.current = [...points];
        deps.lastErasedIndexRef.current = 0;
        deps.applyRoutePoints(points);
        syncVehiclePointWithRoute(points, deps);
        syncVehiclePointWithRoute(points, deps, servedPoints);
    });
};
@@ -234,7 +248,7 @@
        deps.setVehicleMoveAlongPoints(servedPoints);
    }
    if (forceFullRedraw) {
        handleForceFullRouteRedraw(points, deps);
        handleForceFullRouteRedraw(points, deps, servedPoints);
        return;
    }
    const isEraseStatus = [4, 6].includes(deps.tripStatusRef.current);
@@ -242,7 +256,7 @@
        handleEraseRouteStatus(points, servedPoints, deps);
        return;
    }
    handleNormalRouteStatus(points, deps);
    handleNormalRouteStatus(points, deps, servedPoints);
};
export default function useWebSocketEvents(deps: UseWebSocketEventsDeps) {
src/robotaxi/pages/begin-route/index.tsx
@@ -32,7 +32,7 @@
import useViewDelegates from './hooks/use-view-delegates';
import backIcon from '../../../robotaxi/assets/back-icon.png'
import contactService from '../../../robotaxi/assets/contact-service.png'
import personalCenter from '../../../robotaxi/assets/personal-center.png'
import coupon from '../../../robotaxi/assets/coupon.png'
import suggest from '../../../robotaxi/assets/suggest.png'
import originIcon from '../../../robotaxi/assets/origin-icon.png'
@@ -101,6 +101,11 @@
  const tripStatusRef = useRef(tripStatus); // 行程状态引用
  const lastTripStatusRef = useRef(tripStatus); // 上一次行程状态,用于检测状态变化
  const tripStatusForMapSyncRef = useRef(tripStatus); // 用于地图同步的行程状态引用
  useEffect(() => {
    tripStatusRef.current = tripStatus;
    tripStatusForMapSyncRef.current = tripStatus;
    Taro.setStorageSync('tripStatus', tripStatus);
  }, [tripStatus]);
  // 行程状态显示规则配置:showPickup-显示上车点,showDropoff-显示下车点,showCar-显示车辆,showRoute-显示路线,showWalking-显示步行路线,viewport-视口类型,translateCar-车辆是否平移
  const STATUS_RULES: Record<number, any> = {
    1: { showPickup: true, showDropoff: true, showCar: false, showRoute: true, showWalking: true, viewport: 'route_walking', translateCar: false },
@@ -527,7 +532,7 @@
      onServiceEntry={handleServiceEntry}
      onCouponEntry={handleCouponEntry}
      onOrderEntry={handleOrderEntry}
      contactService={contactService}
      personalCenter={personalCenter}
      coupon={coupon}
      suggest={suggest}
      shouldShowOrigin={shouldShowOrigin}
src/robotaxi/pages/select-map-site/index.tsx
@@ -7,7 +7,7 @@
import TipPopup from './components/tip-popup';
import backIcon from '../../../robotaxi/assets/back-icon.png'
import contactService from '../../../robotaxi/assets/contact-service.png'
import personalCenter from '../../../robotaxi/assets/personal-center.png'
import coupon from '../../../robotaxi/assets/coupon.png'
import suggest from '../../../robotaxi/assets/suggest.png'
import originIcon from '../../../robotaxi/assets/origin-icon.png'
@@ -516,7 +516,7 @@
        </View>
        {/* 地图浮层入口 */}
        <View className="map-entry">
          <Image onClick={openService} className="map-entry-icon" src={contactService} />
          <Image onClick={openService} className="map-entry-icon" src={personalCenter} />
          <View className="split-line"></View>
          <Image onClick={toCoupon} className="map-entry-icon" src={coupon} />
          <View className="split-line"></View>