hejianhao
2026-06-10 1d283cf65adf32110e474ba74ff0751ed1095b1a
feat(begin-route): 优化蓝牙、温控与车辆跟踪功能及UI

- 更新蓝牙卡片提示文案与操作按钮的显示逻辑
- 新增setTemperatureIgnoreBusinessError API用于初始化越界温度纠正
- 优化车辆跟踪的旋转计算逻辑与地图动画参数
- 为结束行程弹窗和温度调整添加防抖处理防止重复触发
- 处理初始温度越界的自动修正逻辑
- 调整UI样式、注释无用代码并修复CSS缩进问题
- 简化运营区域页面的描述文本
- 清理无用的控制台日志输出
12个文件已修改
141 ■■■■■ 已修改文件
src/robotaxi/api/begin-route.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/modify-destination-review.less 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/modify-destination-review.tsx 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.less 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/operating-area/index.tsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/begin-route.ts
@@ -120,6 +120,17 @@
    }, true);
}
// 设置温度并跳过业务异常拦截,供初始化越界温度纠正场景使用
export const setTemperatureIgnoreBusinessError = async (payload: SetTemperaturePayload): Promise<SetTemperatureResponse> => {
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/ac/set-temperature',
        method: 'POST',
        payload,
        skipException: true,
        showToast: false
    }, true);
}
// 解锁车辆
export const unlockVehicle = async (payload: UnlockVehiclePayload): Promise<UnlockVehicleResponse> => {
    return request({
src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx
@@ -19,7 +19,7 @@
                <Image src={iconSrc} />
                <View>{description}</View>
            </View>
            {!!actionText && (
            {(!!actionText && actionText !== '去开启') && (
                <View className="bluetooth-operate-card-right">
                    <View>{actionText}</View>
                    <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/blue-right-icon.png" />
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -23,6 +23,8 @@
    const [trunkStatus, setTrunkStatus] = useState(orderTrunkStatus); // 后备箱开关状态
    const [lockStatus, setLockStatus] = useState(false); // 车门解锁状态
    const trunkPollTimerRef = useRef<any>(null); // 后备箱状态轮询定时器
    const overRoutePopupDebounceTimerRef = useRef<any>(null); // 结束行程弹窗防抖定时器
    const OVER_ROUTE_POPUP_DEBOUNCE_MS = 1000; // 结束行程弹窗点击防抖时长
    // 外部后备箱状态变化时同步本地状态
    useEffect(() => {
@@ -35,8 +37,22 @@
            if (trunkPollTimerRef.current) {
                clearTimeout(trunkPollTimerRef.current);
            }
            if (overRoutePopupDebounceTimerRef.current) {
                clearTimeout(overRoutePopupDebounceTimerRef.current);
            }
        };
    }, []);
    // 打开结束行程弹窗并在短时间内忽略重复点击
    const handleOpenOverRoutePopupWithDebounce = () => {
        if (!handleOpenOverRoutePopup || overRoutePopupDebounceTimerRef.current) {
            return;
        }
        handleOpenOverRoutePopup();
        overRoutePopupDebounceTimerRef.current = setTimeout(() => {
            overRoutePopupDebounceTimerRef.current = null;
        }, OVER_ROUTE_POPUP_DEBOUNCE_MS);
    };
    // 获取解锁请求所需定位参数,优先高精定位,失败后降级普通定位
    const getUnlockLocationPayload = async () => {
@@ -83,7 +99,7 @@
        const actionLoadingText = doorType === 'trunk'
            ? (isCloseAction ? '正在关闭后备箱' : '正在开启后备箱')
            : '正在解锁车门';
        Taro.showLoading({ title: actionLoadingText, mask: true });
        // Taro.showLoading({ title: actionLoadingText, mask: true });
        unlockVehicle({
            orderId: targetOrderId,
            doorType,
@@ -92,7 +108,7 @@
            console.log('解锁结果:', res);
            
            if (res?.code !== 200 || !res?.data) {
                Taro.hideLoading();
                // Taro.hideLoading();
                Taro.showToast({ title: res?.msg || '解锁失败,请重试或联系客服', icon: 'none' });
                return;
            }
@@ -119,7 +135,7 @@
                        trunkPollTimerRef.current = setTimeout(doPoll, pollInterval);
                    } else {
                        stopPolling();
                        Taro.hideLoading();
                        // Taro.hideLoading();
                        Taro.showToast({
                            title: failToastText,
                            icon: 'none'
@@ -151,7 +167,7 @@
                            stopPolling();
                            const isOpen = !isCloseAction;
                            setTrunkStatus(isOpen);
                            Taro.hideLoading();
                            // Taro.hideLoading();
                            Taro.showToast({
                                title: successToastText,
                                icon: 'none'
@@ -168,13 +184,13 @@
            }
            
            if (doorType === 'door') {
                Taro.hideLoading();
                // Taro.hideLoading();
                Taro.showToast({ title: '右侧车门已解锁', icon: 'none' });
                onDoorUnlockSuccess?.();
                onSuccess?.();
            }
        }).catch(() => {
            Taro.hideLoading();
            // Taro.hideLoading();
            Taro.showToast({ title: '解锁失败,请重试或联系客服', icon: 'none' });
        });
    };
@@ -204,7 +220,7 @@
                        <Image className="car-card-reach-btn-item-img" src={trunkStatus ? 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/trunk-open.png' : 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/trunk-close.png'} />
                        <View className="car-card-reach-btn-item-text">{trunkStatus ? '关闭后备箱' : '打开后备箱'}</View>
                    </View>
                    <View className="car-card-reach-btn-item" onClick={handleOpenOverRoutePopup}>
                    <View className="car-card-reach-btn-item" onClick={handleOpenOverRoutePopupWithDebounce}>
                        <Image className="car-card-reach-btn-item-img" src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/over-gps-icon.png" />
                        <View className="car-card-reach-btn-item-text">结束行程</View>
                    </View>
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
@@ -5,7 +5,8 @@
    airConditionerUnlock,
    queryTemperatureByOrderId,
    queryVehicleAirConditionerSwitch,
    setTemperature
    setTemperature,
    setTemperatureIgnoreBusinessError
} from '../../../../api/begin-route';
import { getOrderId } from '../../../../utils/order';
import { TripStatus } from '../../../../constants/trip-status';
@@ -35,6 +36,8 @@
    const lastConfirmedTempRef = useRef<number | null>(null); // 最近一次设置成功温度
    const acQuerySeqRef = useRef(0); // 空调状态查询请求序号,避免旧请求覆盖新状态
    const skipQueryOnThisCycleRef = useRef(false); // 修改目的地返回本次页面恢复是否跳过温度/空调查询
    const INITIAL_OUT_OF_RANGE_TARGET_TEMP = 22; // 初始化查询温度越界时回写的目标温度
    const TEMP_UPDATE_DEBOUNCE_MS = 1000; // 温度调整后提交接口的防抖时长
    const TEMP_MIN = 22; // 空调最低温度
    const TEMP_MAX = 28; // 空调最高温度
@@ -106,8 +109,23 @@
            const res: any = await queryTemperatureByOrderId({ orderId: targetOrderId });
            const currentTemperature = Number(res);
            if (!Number.isNaN(currentTemperature)) {
                setTempStatus(currentTemperature);
                lastConfirmedTempRef.current = currentTemperature;
                const shouldResetTemperature = currentTemperature < TEMP_MIN || currentTemperature > TEMP_MAX;
                if (!shouldResetTemperature) {
                    setTempStatus(currentTemperature);
                    lastConfirmedTempRef.current = currentTemperature;
                    return;
                }
                try {
                    await setTemperatureIgnoreBusinessError({
                        orderId: targetOrderId,
                        targetTemperature: INITIAL_OUT_OF_RANGE_TARGET_TEMP
                    });
                    setTempStatus(INITIAL_OUT_OF_RANGE_TARGET_TEMP);
                    lastConfirmedTempRef.current = INITIAL_OUT_OF_RANGE_TARGET_TEMP;
                } catch (innerError) {
                    setTempStatus(currentTemperature);
                    lastConfirmedTempRef.current = currentTemperature;
                }
            }
        } catch (error) {
            // ignore
@@ -191,7 +209,7 @@
                    icon: 'none'
                });
            });
        }, 2000);
        }, TEMP_UPDATE_DEBOUNCE_MS);
    };
    // 处理温度增减并做边界校验
@@ -228,10 +246,10 @@
        }
        const targetOpenState = !acSwitchOn;
        setIsTogglingAc(true);
        Taro.showLoading({
            title: targetOpenState ? AC_OPENING_TEXT : AC_CLOSING_TEXT,
            mask: true
        });
        // Taro.showLoading({
        //     title: targetOpenState ? AC_OPENING_TEXT : AC_CLOSING_TEXT,
        //     mask: true
        // });
        try {
            const res = await airConditionerUnlock({
                orderId: targetOrderId,
@@ -244,10 +262,10 @@
                queryTemperature(targetOrderId);
            }
        } catch (error) {
            Taro.hideLoading();
            // Taro.hideLoading();
            Taro.showToast({ title: AC_TOGGLE_FAIL_TEXT, icon: 'none' });
        } finally {
            Taro.hideLoading();
            // Taro.hideLoading();
            setIsTogglingAc(false);
        }
    };
@@ -269,7 +287,7 @@
                        }
                    </View>
                    <View className="car-card-temp-num-text">
                        <View className={`car-card-temp-num-text-temp ${tempTextDisabledClassName}`.trim()}>{typeof tempStatus === 'number' ? `${tempStatus.toFixed(1)}℃` : '--'}</View>
                        <View className={`car-card-temp-num-text-temp ${tempTextDisabledClassName}`.trim()}>{typeof tempStatus === 'number' ? `${tempStatus.toFixed(1)}℃` : `${TEMP_MIN}℃`}</View>
                        <View className={`car-card-temp-num-text-desc ${tempTextDisabledClassName}`.trim()}>设定温度</View>
                    </View>
                    <View className="car-card-temp-num-btn">
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -103,7 +103,7 @@
    orderId,
    orderTrunkStatus,
    bluetoothStatusReady = false,
    bluetoothCardDescription = '系统蓝牙未开启,无法自动解锁车辆',
    bluetoothCardDescription = '授权并打开蓝牙链接,享受无感出行',
    bluetoothCardActionText = '去开启',
    handleBluetoothCardClick,
    showAuthBluetoothPopup = false,
src/robotaxi/pages/begin-route/components/css-folder/modify-destination-review.less
@@ -45,6 +45,16 @@
    font-weight: 500;
    line-height: 40px;
    color: rgba(0, 0, 0, 0.9);
  }
  .modify-destination-review-tip {
    position: relative;
    z-index: 1;
    margin-top: 4px;
    font-size: 24px;
    line-height: 36px;
    letter-spacing: normal;
    color: rgba(0, 0, 0, 0.4);
    margin-bottom: 32px;
  }
@@ -103,7 +113,7 @@
    }
  }
  .start-txt{
  .start-txt {
    color: rgba(0, 0, 0, 0.4);
  }
@@ -132,7 +142,8 @@
  &-fare {
    min-width: 0;
    .modify-destination-review-operate-free{
    .modify-destination-review-operate-free {
      margin-top: 28px;
    }
  }
@@ -221,4 +232,4 @@
    background: rgba(255, 255, 255, 0.45);
    z-index: 2;
  }
}
}
src/robotaxi/pages/begin-route/components/modify-destination-review.tsx
@@ -202,6 +202,7 @@
            <View className='modify-destination-review-title'>
              由于目的地变更,本次行程费用已更新
            </View>
            {/* <View className='modify-destination-review-tip'>仅可修改一次</View> */}
            <View className='modify-destination-review-select-site'>
              <View className='modify-destination-review-sites-card'>
                <View className='modify-destination-review-site-item'>
src/robotaxi/pages/begin-route/hooks/use-bluetooth-access.ts
@@ -474,7 +474,7 @@
  // 蓝牙卡片主文案
  const cardDescription = useMemo(() => {
    if (status === 'system_off') {
      return '系统蓝牙未开启,无法自动解锁车辆';
      return '授权并打开蓝牙链接,享受无感出行';
    }
    if (status === 'unauthorized') {
      return '授权蓝牙,车辆自动解锁';
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -288,19 +288,13 @@
    const nextMarkersSignature = buildMarkersSignature(nextMarkers, deps.vehicleMarkerId, shouldUsePureTranslateCarMarker);
    const hasDropoffMarker = markersRef.current.some((m: any) => m.id === 2);
    const shouldHoldMarkerByTranslate = [TripStatus.VehicleApproaching, TripStatus.InService].includes(deps.tripStatus) && carMarkerInitialized.current && deps.statusRule.showCar && (!deps.statusRule.showDropoff || hasDropoffMarker);
    const shouldRefreshInServiceRotate = deps.tripStatus === TripStatus.InService
      && !!carMarker
      && !!currentCarMarker
      && currentCarMarker.rotate !== carMarker.rotate;
    const shouldSkipUpdate = !shouldRefreshInServiceRotate
      && shouldHoldMarkerByTranslate
    const shouldSkipUpdate = shouldHoldMarkerByTranslate
      && (deps.isAndroid || tripStatus6MarkerRefreshedRef.current);
    if (deps.tripStatus === TripStatus.InService && carMarker) {
      console.log('[开始行程][送驾方向][Marker]', {
        车辆方向: deps.vehicleDirection,
        当前Marker角度: currentCarMarker?.rotate,
        下一次Marker角度: carMarker.rotate,
        是否触发角度刷新: shouldRefreshInServiceRotate,
        是否跳过Marker更新: shouldSkipUpdate,
        车辆位置: deps.vehicleLocation,
        车辆标记ID: deps.vehicleMarkerId,
src/robotaxi/pages/begin-route/hooks/use-vehicle-tracking.ts
@@ -81,6 +81,7 @@
    longitude: number;
    rotate: number;
  } | null>(null);
  const lastResolvedRotateRef = useRef<number | null>(null);
  // 执行一次平移动画,完成后自动消费最新待执行目标
  const flushTranslateTask = () => {
@@ -115,7 +116,7 @@
          longitude: task.longitude,
        },
        autoRotate: false,
        moveWithRotate: false,
        moveWithRotate: true,
        rotate: task.rotate,
        duration: task.duration,
        animationEnd: () => {
@@ -155,9 +156,13 @@
      ? ((vehicleDirection % 360) + 360) % 360
      : 0;
    const lastAppliedTask = lastAppliedTranslateTaskRef.current;
    if (lastAppliedTask) {
      targetRotate = resolveShortestRotatePath(targetRotate, lastAppliedTask.rotate);
    const previousResolvedRotate = typeof lastResolvedRotateRef.current === 'number'
      ? lastResolvedRotateRef.current
      : lastAppliedTask?.rotate;
    if (typeof previousResolvedRotate === 'number') {
      targetRotate = resolveShortestRotatePath(targetRotate, previousResolvedRotate);
    }
    lastResolvedRotateRef.current = targetRotate;
    const hasLocationDelta = hasMeaningfulLocationDelta(vehicleLocation, lastAppliedTask);
    const rotateDelta = lastAppliedTask
      ? getRotateDelta(targetRotate, lastAppliedTask.rotate)
@@ -181,6 +186,7 @@
    isTranslatingRef.current = false;
    pendingTranslateRef.current = null;
    lastAppliedTranslateTaskRef.current = null;
    lastResolvedRotateRef.current = null;
  }, [statusRule.translateCar]);
  return {
src/robotaxi/pages/begin-route/index.less
@@ -132,6 +132,7 @@
    padding-left: 15px;
    padding-right: 8px;
    box-sizing: border-box;
    overflow: visible;
}
.route-summary-callout-bg {
@@ -151,6 +152,8 @@
    color: #FFFFFF;
    white-space: nowrap;
    text-align: center;
    padding-right: 8px;
    overflow: visible;
}
.txt-center {
src/robotaxi/pages/operating-area/index.tsx
@@ -340,17 +340,12 @@
            </View>
            <View className="content-desc">
              <View className="desc-item">
                <Text className="index">1.</Text>
                <Text className="text">在运营时段可正常使用打车服务,非运营时段服务暂停</Text>
              </View>
              <View className="desc-item">
                <Text className="index">2.</Text>
                <Text className="text">各站点运营时间可能存在调整,实际运营情况请以页面实时展示为准</Text>
              </View>
              <View className="desc-item">
                <Text className="index">3.</Text>
                <Text className="text">如有问题,请拨打客服热线xxxxxx</Text>
              </View>
              {/* <View className="desc-item">
                <Text className="index">2.</Text>
                <Text className="text">各站点运营时间可能存在调整,实际运营情况请以页面实时展示为准</Text>
              </View> */}
            </View>
          </View>
        </View>