liaof
2026-08-10 73af95f940e41ea3810aeefec2b132d7e83bde05
feature_v2.5.0
1个文件已删除
5个文件已添加
59个文件已修改
1705 ■■■■■ 已修改文件
src/pages/robotaxi/home/components/home-map-panel.tsx 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/map-overlay-tips/index.tsx 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/privacy-popup/index.less 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/privacy-popup/index.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.less 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/begin-route.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/fragrance.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/genre.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/payment.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/seat-hv.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/types/genre.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/components/coupon-select-popup/index.less 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/components/customer-service/index.less 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/components/tip-popup/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | 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 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/fragrance-control/index.tsx 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/music-control/index.tsx 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/seat-hv-control/index.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/types.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car.tsx 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/cancel-popup.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/announcements-popup.less 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/cancel-popup.less 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/credit-pay-popup.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/evaluate-popup.less 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/fragrance-control.less 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/music-control.less 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/no-route-popup.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/over-route.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/passenger-notice.less 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/pickup-exception-popup.less 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/temperature-control.less 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/evaluate-popup.tsx 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/pickup-exception-popup.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/release-route.tsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/route-preference-popup/index.less 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-page-actions.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.less 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/coupon/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/coupon/index.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/expense-description/index.tsx 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/feedback/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/operating-area/index.less 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/operating-area/index.tsx 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/detail/index.less 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/detail/index.tsx 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/passenger-notice/index.tsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/payment-setting-detail/index.tsx 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/payment-setting/index.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/select-map-site/hooks/use-select-map-site-walking-route.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/share-trip/index.less 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/site-search/components/favorite-popup/index.less 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/utils/api-logger.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/utils/login-agreement.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/home-map-panel.tsx
@@ -159,7 +159,25 @@
        }}
      >
        <View className='origin-box-container'>
          <Image onClick={props.onToOrigin} className="origin-icon" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/origin-icon-20260716.png' />
          <View className='tips-and-origin'>
            <Image onClick={props.onToOrigin} className="origin-icon" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/origin-icon-20260716.png' />
            <MapOverlayTips
              isGuest={props.shouldShowGuestTip}
              isLocationAuthDenied={props.isLocationAuthDenied}
              isOperatingArea={props.isOperatingArea}
              isMaintain={props.isMaintain}
              maintainBounce={props.maintainBounce}
              hotfixEndText={props.hotfixEndText}
              anchorBottom={props.overlayAnchorBottom ?? undefined}
              opacity={props.originOpacity}
              isDragging={props.isDragging}
              onShowPrivacy={props.onShowPrivacy}
              onOpenSetting={props.onOpenSetting}
              onViewOperatingArea={props.onViewOperatingArea}
              shouldShowOrderStatusCard={props.shouldShowOrderStatusCard}
            />
          </View>
          {props.shouldShowOrderStatusCard && (
            <View  className="order-status-overlay">
              <OrderStatusCard
@@ -172,21 +190,7 @@
          )}
        </View>
      </View>
      <MapOverlayTips
        isGuest={props.shouldShowGuestTip}
        isLocationAuthDenied={props.isLocationAuthDenied}
        isOperatingArea={props.isOperatingArea}
        isMaintain={props.isMaintain}
        maintainBounce={props.maintainBounce}
        hotfixEndText={props.hotfixEndText}
        anchorBottom={props.overlayAnchorBottom ?? undefined}
        opacity={props.originOpacity}
        isDragging={props.isDragging}
        onShowPrivacy={props.onShowPrivacy}
        onOpenSetting={props.onOpenSetting}
        onViewOperatingArea={props.onViewOperatingArea}
        shouldShowOrderStatusCard={props.shouldShowOrderStatusCard}
      />
      <View className="map-entry">
        <View className="map-entry-box">
          <Image onClick={props.onToPersonalCenter} className="map-entry-icon" src='https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/personal-center-20260716.png' />
src/pages/robotaxi/home/components/map-overlay-tips/index.tsx
@@ -32,33 +32,23 @@
 * 地图顶部提示层。
 */
export default function MapOverlayTips(props: Props) {
  const anchorStyle = typeof props.anchorBottom === 'number'
    ? {
        top: 'auto',
        bottom: `${props.anchorBottom}px`,
        opacity: props.opacity !== undefined ? props.opacity : 1,
        visibility: props.opacity !== undefined && props.opacity <= 0 ? 'hidden' : 'visible' as any,
        pointerEvents: props.opacity !== undefined && props.opacity <= 0 ? 'none' : 'auto' as any,
        transition: props.isDragging ? 'none' : 'opacity 0.2s ease-out'
      }
    : undefined;
  return (
    <View className="content-box-top" style={anchorStyle}>
    <View className="content-box-top">
      <View className="position-box">
        {
          props.isGuest && <View className="visitor-stste ml-32">
          props.isGuest && <View className="visitor-stste">
            <View className="visitor-stste-tip">同意隐私协议体验更多约车体验</View>
            <View onClick={props.onShowPrivacy} className="visitor-stste-go">去同意</View>
          </View>
        }
        {
          props.isLocationAuthDenied && <View className="visitor-stste ml-32 mt-20">
          props.isLocationAuthDenied && <View className="visitor-stste">
            <View className="visitor-stste-tip">未开启位置权限,开启后即可发布行程</View>
            <View onClick={props.onOpenSetting} className="visitor-stste-go">去开启</View>
          </View>
        }
        {
          props.isMaintain && <View className={`is-maintain ml-32 ${props.maintainBounce ? 'maintain-bounce' : ''}`}>系统维护中,预计{props.hotfixEndText || '-:-'}恢复服务</View>
          props.isMaintain && <View className={`is-maintain ${props.maintainBounce ? 'maintain-bounce' : ''}`}>系统维护中,预计{props.hotfixEndText || '-:-'}恢复服务</View>
        }
        {
          !props.isOperatingArea && !props.shouldShowOrderStatusCard && <View onClick={props.onViewOperatingArea} className="is-operating-area mt-20">查看运营区域</View>
src/pages/robotaxi/home/components/privacy-popup/index.less
@@ -85,8 +85,8 @@
    }
    .check-all-box {
      display: flex;
      align-items: center;
      justify-content: center;
      align-items: flex-start;
      justify-content: flex-start;
      margin-top: 14px;
      .all-name{
        font-size: 28px;
@@ -96,6 +96,7 @@
      .rt-item-radio {
        flex-shrink: 0;
        margin-right: 8px;
        margin-top: 6px;
      }
    }
src/pages/robotaxi/home/components/privacy-popup/index.tsx
@@ -157,7 +157,7 @@
            }
          </Text>
          <View className='rt-link-item mt-22 pt-10 check-all-box' onClick={checkAll}>
            <Image className='rt-item-radio' src={isAllAgree ? 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/check-20260630.png' : 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/uncheck.png'} mode='aspectFill' />
            <Image className='rt-item-radio' src={isAllAgree ? 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/check-20260630.png' : 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/uncheck-20260804.png'} mode='aspectFill' />
            <View className='rt-item-name all-name'>我已满18周岁,已阅读并同意:</View>
          </View>
        </View>
src/pages/robotaxi/home/index.less
@@ -103,8 +103,16 @@
  }
  .origin-icon {
    position: absolute;
    bottom: 0px;
    right: 24px;
    width: 80px;
    height: 80px;
  }
  .tips-and-origin {
    position: relative;
    width: 100%;
    min-height: 80px;
  }
}
@@ -215,10 +223,6 @@
.content-box-top {
  width: 100%;
  position: fixed;
  top: 55%;
  left: 0px;
  z-index: 2;
  .position-box {
    .is-operating-area {
@@ -255,9 +259,6 @@
      }
    }
    .ml-32 {
      margin-left: 32px;
    }
    .mt-20 {
      margin-top: 20px;
src/robotaxi/api/begin-route.ts
@@ -42,7 +42,6 @@
    QueryOrderPaymentResultResponse,
    QueryLedgerCreditOrderResultResponse
} from './types/begin-route';
import { withApiLog } from '../utils/api-logger';
// 导航-获取路线和费用
export const queryRouteAndFare = async (payload: QueryRouteAndFarePayload): Promise<QueryRouteAndFareResponse> => {
@@ -152,23 +151,23 @@
// 设置温度
export const setTemperature = async (payload: SetTemperaturePayload): Promise<SetTemperatureResponse> => {
    return withApiLog('设置空调温度', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/ac/set-temperature',
        method: 'POST',
        payload,
        showToast: false
    }, true));
    }, true);
}
// 设置温度并跳过业务异常拦截,供初始化越界温度纠正场景使用
export const setTemperatureIgnoreBusinessError = async (payload: SetTemperaturePayload): Promise<SetTemperatureResponse> => {
    return withApiLog('设置空调温度(忽略业务异常)', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/ac/set-temperature',
        method: 'POST',
        payload,
        skipException: true,
        showToast: false
    }, true));
    }, true);
}
// 解锁车辆
@@ -186,32 +185,32 @@
export const queryTemperatureByOrderId = async (
    payload: OrderIdPayload
): Promise<QueryTemperatureByOrderIdResponse> => {
    return withApiLog('查询车辆温度', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/queryTemperatureByOrderId',
        method: 'GET',
        payload
    }, true));
    }, true);
}
// 查询车辆空调开关状态
export const queryVehicleAirConditionerSwitch = async (
    payload: OrderIdPayload
): Promise<QueryVehicleAirConditionerSwitchResponse> => {
    return withApiLog('查询空调开关状态', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/queryVehicleAirConditionerSwitch',
        method: 'GET',
        payload
    }, true));
    }, true);
}
// 车辆空调开关控制
export const airConditionerUnlock = async (payload: AirConditionerUnlockPayload): Promise<void> => {
    return withApiLog('空调开关控制', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/airConditionerUnlock',
        method: 'POST',
        payload,
        showToast: false
    }, true));
    }, true);
}
// 查询后备箱开启状态
src/robotaxi/api/fragrance.ts
@@ -1,6 +1,5 @@
import request from '../../utils/request';
import { _ROBOTAXI_HOST } from '../../constants/api';
import { withApiLog } from '../utils/api-logger';
import type {
    QueryCabinFragranceResponse,
    SetCabinFragrancePayload
@@ -10,22 +9,21 @@
export const queryCabinFragrance = async (
    orderId: string
): Promise<QueryCabinFragranceResponse> => {
    const payload = { orderId };
    return withApiLog('查询车载香氛状态', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/base/v1/mini-program/cabin/fragrance',
        method: 'GET',
        payload
    }, true));
        payload: { orderId }
    }, true);
};
// 设置车载香氛(聚合下发开关状态,开启时同时下发香型通道)
export const setCabinFragrance = async (
    payload: SetCabinFragrancePayload
): Promise<void> => {
    return withApiLog('设置车载香氛', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/base/v1/mini-program/cabin/fragrance',
        method: 'POST',
        payload,
        skipException: true
    }, true));
    }, true);
};
src/robotaxi/api/genre.ts
New file
@@ -0,0 +1,31 @@
import request from '../../utils/request';
import { _ROBOTAXI_HOST } from '../../constants/api';
import type {
    QueryOrderGenreResponse,
    SetOrderGenrePayload
} from './types/genre';
const GENRE_API_URL = _ROBOTAXI_HOST + '/vehicleConvoy/api/v1/mini-program/order/genre';
// 查询当前订单曲风状态(音乐开关、当前曲风、已启用曲风列表)
export const queryOrderGenre = async (
    orderId: string
): Promise<QueryOrderGenreResponse> => {
    return request({
        url: GENRE_API_URL,
        method: 'GET',
        payload: { orderId }
    }, true);
};
// 设置当前订单曲风(下发音乐开关状态,开启时同时下发曲风 ID)
export const setOrderGenre = async (
    payload: SetOrderGenrePayload
): Promise<void> => {
    return request({
        url: GENRE_API_URL,
        method: 'POST',
        payload,
        skipException: true
    }, true);
};
src/robotaxi/api/payment.ts
@@ -37,12 +37,13 @@
/**
 * 设置支付分开通状态。
 */
export const setPayPwd = async (status: RobotaxiPaySettingStatus): Promise<SetPayPwdResponse> => {
export const setPayPwd = async (status: RobotaxiPaySettingStatus, showToast?: boolean): Promise<SetPayPwdResponse> => {
  return request({
    url: _ROBOTAXI_HOST + `/account/api/v1/mini-program/appUser/setPayPwd`,
    method: 'POST',
    payload: {
      status,
    }
    },
    showToast: showToast || false
  }, true);
};
src/robotaxi/api/seat-hv.ts
@@ -1,24 +1,22 @@
import request from '../../utils/request';
import { _ROBOTAXI_HOST } from '../../constants/api';
import { withApiLog } from '../utils/api-logger';
import type { QuerySeatHvResponse, SetSeatHvPayload } from './types/seat-hv';
// 查询座椅通风/加热状态
export const querySeatHv = async (orderId: string): Promise<QuerySeatHvResponse> => {
    const payload = { orderId };
    return withApiLog('查询座椅通风加热状态', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/base/v1/mini-program/cabin/seat/hv',
        method: 'GET',
        payload
    }, true));
        payload: { orderId }
    }, true);
};
// 设置座椅通风/加热(seats 仅含被改动的座椅,通风与加热一起下发)
export const setSeatHv = async (payload: SetSeatHvPayload): Promise<void> => {
    return withApiLog('设置座椅通风加热', payload, request({
    return request({
        url: _ROBOTAXI_HOST + '/vehicleConvoy/base/v1/mini-program/cabin/seat/hv',
        method: 'POST',
        payload,
        skipException: true
    }, true));
    }, true);
};
src/robotaxi/api/types/genre.ts
New file
@@ -0,0 +1,37 @@
import type { OrderId } from './begin-route';
/** 单个曲风。 */
export interface GenreItem {
    /** 曲风唯一标识 / 曲风 ID(下发时使用)。 */
    id: string;
    /** 曲风显示名称。 */
    name: string;
    /** 曲风描述(可选,后端未下发时前端可留空)。 */
    desc?: string;
}
/** 订单曲风状态对象。 */
export interface OrderGenreState {
    /** 音乐开关状态。 */
    switchOn: boolean;
    /** 当前选中曲风 ID(关闭时可为空)。 */
    currentGenreId?: string;
    /** 后台已启用曲风列表(动态),空数组表示暂无可选曲风。 */
    availableGenres?: GenreItem[];
}
/** 查询订单曲风状态响应对象。 */
export interface QueryOrderGenreResponse {
    /** 曲风状态。 */
    genre?: OrderGenreState;
}
/** 设置订单曲风请求。 */
export interface SetOrderGenrePayload {
    /** 订单 ID。 */
    orderId: OrderId;
    /** 目标开关状态。 */
    switchOn: boolean;
    /** 目标曲风 ID;switchOn 为 true 时必传,false 时可不传。 */
    genreId?: string;
}
src/robotaxi/components/coupon-select-popup/index.less
@@ -20,7 +20,7 @@
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.4);
    background: rgba(0, 0, 0, 0.8);
    z-index: 1;
  }
@@ -50,7 +50,7 @@
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.5);
      background: rgba(0, 0, 0, 0.8);
    }
    .coupon-detail-content {
src/robotaxi/components/customer-service/index.less
@@ -12,7 +12,7 @@
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    background: rgba(0, 0, 0, 0.8);
  }
  .customer-service-content {
@@ -41,4 +41,4 @@
      background: #ffffff;
    }
  }
}
}
src/robotaxi/components/tip-popup/index.less
@@ -15,7 +15,7 @@
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        background: rgba(0, 0, 0, 0.8);
    }
    .tip-content {
src/robotaxi/pages/begin-route/components/calling-car-modules/bluetooth-operate-card.tsx
@@ -28,7 +28,7 @@
            {shouldRenderRight && (
                <View className="bluetooth-operate-card-right">
                    {/* {!!actionText && <View>{actionText}</View>} */}
                    <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/blue-right-icon-20260706.png" />
                    <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/bluetooth-arrow-20260706.png" />
                </View>
            )}
        </View>
src/robotaxi/pages/begin-route/components/calling-car-modules/door-trunk-control.tsx
@@ -306,7 +306,7 @@
                </View>
                <View className={`car-card-arrive-item ${lockStatus && 'close-btn-item'} ${loadingControlType ? 'trip-action-bar-item-disabled' : ''}`.trim()} onClick={() => handleUnlock('door', onDoorUnlockSuccess)}>
                    <View className="car-card-arrive-item-text">车锁</View>
                    <View className="car-card-arrive-item-status">{lockStatus ? '点击关闭' : '点击打开'}</View>
                    <View className="car-card-arrive-item-status">{lockStatus ? '车门已解锁' : '点击打开'}</View>
                    {renderControlIcon('door', lockStatus ? LOCK_OPEN_ICON_URL : LOCK_CLOSE_ICON_URL, 'car-card-arrive-item-img')}
                </View>
            </View>
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
@@ -7,6 +7,7 @@
type Props = {
    tripStatus: TripStatus;
    fareBreakdown: any;
    manuallySelectedCouponName?: string;
    orderId?: string;
    resetDetailVersion?: number;
    onToggleDetailFade?: (expanded: boolean, hasFare: boolean) => void;
@@ -17,6 +18,7 @@
export default function FareDetailCard({
    tripStatus,
    fareBreakdown,
    manuallySelectedCouponName = '',
    orderId,
    resetDetailVersion,
    onToggleDetailFade,
@@ -25,6 +27,11 @@
}: Props) {
    const [showDetail, setShowDetail] = useState(false); // 费用明细展开状态
    const canOpenCouponSelect = tripStatus === TripStatus.TripFinished && typeof onOpenCouponSelect === 'function'; // 仅待支付支持切换优惠券
    const discountTitleText = manuallySelectedCouponName || '最佳优惠';
    const discountDescText = manuallySelectedCouponName
        ? `已选择${manuallySelectedCouponName}`
        : '已选择最佳优惠';
    const isCreditPayProcessing = (
        tripStatus === TripStatus.TripFinished
        && !!orderId
@@ -202,8 +209,8 @@
                    (fareBreakdown?.operateFree != 1 && [TripStatus.TripFinished].includes(tripStatus)) && Number(fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney) > 0 &&
                    <View className="car-card-end-through" onClick={canOpenCouponSelect ? onOpenCouponSelect : undefined}>
                        <View className="car-card-end-through-left">
                            <View className="car-card-end-through-content-title">最佳优惠</View>
                            <View className="car-card-end-through-desc">已为您选择最佳优惠</View>
                            <View className="car-card-end-through-content-title">{discountTitleText}</View>
                            <View className="car-card-end-through-desc">{discountDescText}</View>
                        </View>
                        <View className="car-card-end-through-right">
                            <View className="car-card-end-through-content-money">-{formatMoney(fareBreakdown?.redPacketMoney + fareBreakdown?.couponMoney)}元</View>
src/robotaxi/pages/begin-route/components/calling-car-modules/fragrance-control/index.tsx
@@ -7,6 +7,8 @@
import { getOrderId } from '../../../../../utils/order';
import { TripStatus } from '../../../../../constants/trip-status';
import useBottomPopupAnimation from '../../hooks/use-bottom-popup-animation';
import type { ControlNodes } from '../types';
import AcSwitch from '../ac-switch';
import '../../css-folder/fragrance-control.less';
const FC_HEADER_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/fragrance-icon-20260716.png';
@@ -27,8 +29,9 @@
    const cnNum = scentCount <= 10 ? CN_NUMBERS[scentCount] : String(scentCount);
    return `可选${cnNum}款香型`;
}
const FRAGRANCE_OPEN_SUCCESS_TEXT = '已开启香氛'; // 开启香氛成功提示文案
const FRAGRANCE_OPEN_PREFIX = '已开启香氛:'; // 开启/切换香氛成功提示前缀(后接香氛名称)
const CLOSE_SUCCESS_TEXT = '已关闭香氛';
const PANEL_CLOSE_DELAY_MS = 150; // 下发成功后延迟关闭弹窗,优化交互体验
const APPLY_FAIL_TEXT = '网络异常,请重试';
/** 香调描述本地兜底表:接口未返回 desc 时按 id 取用。 */
@@ -64,24 +67,30 @@
}
/**
 * 计算车端开启态下当前生效的香型通道。
 * 计算初始选中香型通道:优先车端当前香型,其次列表首项(关闭态也保留选中,用于卡片展示当前香氛)。
 */
function resolveActiveChannelId(scents: FragranceScent[], switchOn: boolean, currentScent?: string): string {
    if (!switchOn || !scents.length) {
        return '';
function resolveInitialChannelId(scents: FragranceScent[], currentScent?: string): string {
    if (currentScent) {
        const matched = scents.find((item) => item.id === currentScent);
        if (matched) {
            return matched.channelId;
        }
    }
    const matched = currentScent ? scents.find((item) => item.id === currentScent) : undefined;
    return (matched || scents[0]).channelId;
    return scents.length ? scents[0].channelId : '';
}
export default function FragranceControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props) {
/**
 * 香氛控制 Hook:返回「入口卡片」与「控制弹窗」两个可分别挂载的节点。
 * 拆分原因:卡片要放进横向滑动的 ScrollView,而弹窗是 position: fixed 全屏浮层;
 * 只要弹窗位于 scroll-view 子树内,iOS 会把它降级为相对滚动容器定位并裁切(Android 表现正常)。
 * 因此调用方必须把 card 放进 ScrollView、把 popup 放在 ScrollView 之外。
 */
export function useFragranceControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props): ControlNodes {
    // 车端真实状态
    const [switchOn, setSwitchOn] = useState(false);
    const [scents, setScents] = useState<FragranceScent[]>([]);
    // 当前生效香型通道(关闭时为空)
    const [activeChannelId, setActiveChannelId] = useState('');
    // 卡片主标题(开启时展示香型名,关闭时展示"已关闭")
    const [cardTitle, setCardTitle] = useState(CLOSED_TITLE);
    // 当前选中香型通道(关闭态也保留,用于卡片展示当前香氛)
    const [selectedChannelId, setSelectedChannelId] = useState('');
    // 面板显隐 + 下发锁
    const [panelVisible, setPanelVisible] = useState(false);
    const [applying, setApplying] = useState(false);
@@ -94,6 +103,11 @@
    const { animationData, shouldRender } = useBottomPopupAnimation(panelVisible);
    const hasScents = scents.length > 0;
    const selectedScent = findScentByChannel(scents, selectedChannelId);
    // 卡片主标题:始终展示当前选中香型名(关闭态由样式置灰),无香型时兜底"已关闭"
    const cardTitle = selectedScent?.name || (hasScents ? scents[0].name : CLOSED_TITLE);
    // 开关 loading 仅在由开关(而非香型磁贴)触发下发时展示
    const switchLoading = applying && applyingChannelId === '';
    // 进入接驾态查询香氛状态;连环单不查询、不展示
    useEffect(() => {
@@ -119,12 +133,9 @@
            const fragrance = res || {};
            const nextSwitchOn = fragrance.switchOn === true;
            const nextScents: FragranceScent[] = Array.isArray(fragrance.availableScents) ? fragrance.availableScents : [];
            const nextActiveChannelId = resolveActiveChannelId(nextScents, nextSwitchOn, fragrance.currentScent);
            setSwitchOn(nextSwitchOn);
            setScents(nextScents);
            setActiveChannelId(nextActiveChannelId);
            const activeName = findScentByChannel(nextScents, nextActiveChannelId)?.name || '';
            setCardTitle(nextSwitchOn && activeName ? activeName : CLOSED_TITLE);
            setSelectedChannelId(resolveInitialChannelId(nextScents, fragrance.currentScent));
        }).catch(() => {
            if (querySeqRef.current !== snapshotSeq) {
                return;
@@ -132,8 +143,7 @@
            // 查询失败按「已关闭」兜底
            setSwitchOn(false);
            setScents([]);
            setActiveChannelId('');
            setCardTitle(CLOSED_TITLE);
            setSelectedChannelId('');
        });
    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder]);
@@ -146,35 +156,41 @@
        setPanelVisible(false);
    };
    // 点击香型即时下发:点选中项 → 关闭香氛;点其它项 → 开启并切换香型
    const handleScentClick = async (channelId: string) => {
        if (applying) {
    // 点击卡片:只有一个香氛时卡片本体不可点击(仅右上角开关控制);多个香氛时打开选择面板
    const handleCardClick = () => {
        if (scents.length <= 1) {
            return;
        }
        togglePanel();
    };
    // 点击香型即时下发:点选中项 → 关闭香氛;点其它项 → 开启并切换香型
    /**
     * 下发香氛设置到车端;成功后同步开关与选中香型、关闭弹窗并 toast,失败提示重试。
     * @param loadingChannelId 触发下发的香型通道 ID(用于在该磁贴展示 loading);由开关触发时传空串。
     */
    const applyFragrance = async (nextSwitchOn: boolean, channelId: string, loadingChannelId = '') => {
        const targetOrderId = getOrderId(orderId);
        if (!targetOrderId) {
            return;
        }
        // 点击当前生效香型 → 关闭;否则 → 开启该香型
        const turningOff = switchOn && channelId === activeChannelId;
        const nextSwitchOn = !turningOff;
        setApplying(true);
        setApplyingChannelId(channelId);
        setApplyingChannelId(loadingChannelId);
        try {
            await setCabinFragrance({
                orderId: targetOrderId,
                switchOn: nextSwitchOn,
                ...(nextSwitchOn ? { channelId } : {})
            });
            // 下发成功后同步车端真实状态与卡片主标题
            // 下发成功后同步车端真实状态(关闭态保留选中香型不重置)
            setSwitchOn(nextSwitchOn);
            const nextActiveChannelId = nextSwitchOn ? channelId : '';
            setActiveChannelId(nextActiveChannelId);
            const activeName = findScentByChannel(scents, nextActiveChannelId)?.name || '';
            setCardTitle(nextSwitchOn && activeName ? activeName : CLOSED_TITLE);
            // 开启/关闭香氛均使用通用 Toast 组件提示(success),并关闭香氛设置弹窗
            showToast({ title: nextSwitchOn ? FRAGRANCE_OPEN_SUCCESS_TEXT : CLOSE_SUCCESS_TEXT, icon: 'success' });
            closePanel();
            if (nextSwitchOn && channelId) {
                setSelectedChannelId(channelId);
            }
            // 开启/关闭香氛:通用 Toast 提示(success),并延迟 150ms 关闭香氛设置弹窗(优化交互体验)
            const appliedScentName = findScentByChannel(scents, channelId)?.name || '';
            showToast({ title: nextSwitchOn ? `${FRAGRANCE_OPEN_PREFIX}${appliedScentName}` : CLOSE_SUCCESS_TEXT, icon: 'success' });
            setTimeout(() => closePanel(), PANEL_CLOSE_DELAY_MS);
        } catch (error) {
            showToast({ title: APPLY_FAIL_TEXT, icon: 'none' });
        } finally {
@@ -183,39 +199,62 @@
        }
    };
    // 点击香型磁贴:点当前生效香型 → 关闭;点其它 → 开启并切换到该香型
    const handleScentClick = (channelId: string) => {
        if (applying) {
            return;
        }
        const turningOff = switchOn && channelId === selectedChannelId;
        applyFragrance(!turningOff, channelId, channelId);
    };
    // 切换总开关(卡片主开关 / 弹窗底部开关共用):打开→下发当前选中香型;关闭→下发关闭并保留选中
    const handleToggleSwitch = () => {
        if (applying || !hasScents) {
            return;
        }
        const nextSwitchOn = !switchOn;
        const targetChannelId = selectedChannelId || scents[0]?.channelId || '';
        applyFragrance(nextSwitchOn, targetChannelId, '');
    };
    // 连环单或非接驾态不展示香氛卡片
    if (tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
        return null;
        return { card: null, popup: null };
    }
    // 香氛卡片背景随开关状态切换(复用 tf-card 视觉)
    const fragranceCardStateClass = switchOn ? 'is-open' : 'is-close';
    return (
    const card = (
        <View className='fragrance-control'>
            {/* 香氛入口卡片(复用 tf-card 视觉):点击打开面板 */}
            <View
                className={`tf-card tf-card-fragrance ${fragranceCardStateClass}`}
                onClick={togglePanel}
                onClick={handleCardClick}
            >
                <View className='tf-card-header'>
                    <View className='tf-card-header-left'>
                        <Image className='tf-card-header-icon' src={FC_HEADER_ICON} />
                        <Text className='tf-card-header-title'>香氛</Text>
                    </View>
                    <AcSwitch checked={switchOn} loading={switchLoading} onChange={handleToggleSwitch} />
                </View>
                <View className='tf-card-bottom'>
                    <View className='tf-card-fragrance-name'>{cardTitle}</View>
                    <View className='tf-card-footer'>
                        <Text className='tf-card-footer-text'>{resolveCardSubtitle(scents.length)}</Text>
                        <Image className='tf-card-footer-arrow' src={ARROW_ICON_URL} />
                        {scents.length > 1 && <Image className='tf-card-footer-arrow' src={ARROW_ICON_URL} />}
                    </View>
                </View>
            </View>
        </View>
    );
            {/* 控制面板(底部弹窗) */}
            {shouldRender && (
                <View className={`fragrance-popup ${panelVisible ? 'show' : ''}`} catchMove>
    // 控制面板(底部弹窗):与卡片分开挂载,必须渲染在 ScrollView 之外,否则 iOS 上会被滚动容器裁切
    const popup = shouldRender ? (
        <View className='fragrance-control'>
            <View className={`fragrance-popup ${panelVisible ? 'show' : ''}`} catchMove>
                    <View className='popup-mask' onClick={closePanel}></View>
                    <View className='popup-content' animation={animationData}>
                        <View className='popup-header'>
@@ -227,7 +266,7 @@
                            {hasScents ? (
                                <View className='popup-scent-list'>
                                    {scents.map((item) => {
                                        const isActive = switchOn && item.channelId === activeChannelId;
                                        const isActive = switchOn && item.channelId === selectedChannelId;
                                        const isLoading = applyingChannelId === item.channelId;
                                        const desc = resolveScentDesc(item);
                                        return (
@@ -247,8 +286,15 @@
                            )}
                        </View>
                    </View>
                </View>
            )}
            </View>
        </View>
    );
    ) : null;
    return { card, popup };
}
/** 兼容旧用法:卡片与弹窗一起渲染(不在 ScrollView 内的场景可直接使用本组件) */
export default function FragranceControl(props: Props) {
    const { card, popup } = useFragranceControl(props);
    return <>{card}{popup}</>;
}
src/robotaxi/pages/begin-route/components/calling-car-modules/music-control/index.tsx
New file
@@ -0,0 +1,304 @@
import { View, Text, Image } from '@tarojs/components';
import { useEffect, useRef, useState } from 'react';
import { queryOrderGenre, setOrderGenre } from '../../../../../api/genre';
import type { GenreItem } from '../../../../../api/types/genre';
import { getOrderId } from '../../../../../utils/order';
import { TripStatus } from '../../../../../constants/trip-status';
import useBottomPopupAnimation from '../../hooks/use-bottom-popup-animation';
import type { ControlNodes } from '../types';
import AcSwitch from '../ac-switch';
import { showToast } from '../../../../../components/toast';
import '../../css-folder/music-control.less';
const GENRE_HEADER_ICON = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/genre-0731@2x.png';
const ARROW_ICON_URL = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/temperature-fragrance-arrow-20260706.png';
const CLOSE_ICON_URL = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/close-icon.png';
const LOADING_ICON_URL = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/T-switch-loading-20260715.png';
const CARD_DEFAULT_TITLE = '车载音乐'; // 无可选曲风时卡片主标题兜底
const NO_GENRE_TIP = '当前暂无可选曲风'; // 空曲风列表占位
const PLAY_TIP_TEXT = '音乐将在您上车后开始播放'; // 弹窗底部开关文案
const OPEN_PLAY_PREFIX = '上车后将自动播放:'; // 开启/切换曲风成功提示前缀(后接曲风名称)
const CLOSE_SUCCESS_TEXT = '音乐已关闭';
const PANEL_CLOSE_DELAY_MS = 150; // 下发成功后延迟关闭弹窗,优化交互体验
const APPLY_FAIL_TEXT = '网络异常,请重试';
// 车端下发结果失败标识:接口 code 可能为 200,但 data 返回 'FAILED' 表示车端下发失败
const APPLY_FAIL_RESULT = 'FAILED';
// 卡片副标题轮播文案(上下滚动轮播,动效由 CSS 实现)
const SUBTITLE_TEXTS = [
    '下一站,跟着节奏出发',
    '好音乐,陪你出发',
    '长风相送,雅韵随行',
    '带上音乐,轻松启程',
    '给旅程多一些旋律'
];
type Props = {
    tripStatus: TripStatus;
    orderId?: string;
    pageDidShowVersion?: number;
    /** 后端推送:当前被指派车辆是否仍在执行上一单(连环单) */
    hasPreviousOrder?: boolean;
};
/**
 * 从曲风列表中按 ID 查找曲风。
 */
function findGenreById(genres: GenreItem[], genreId: string): GenreItem | undefined {
    return genres.find((item) => item.id === genreId);
}
/**
 * 计算初始选中曲风:优先车端当前曲风,其次列表首项。
 */
function resolveInitialGenreId(genres: GenreItem[], currentGenreId?: string): string {
    if (currentGenreId && findGenreById(genres, currentGenreId)) {
        return currentGenreId;
    }
    return genres.length ? genres[0].id : '';
}
/**
 * 音乐控制 Hook:返回「入口卡片」与「控制弹窗」两个可分别挂载的节点。
 * 拆分原因:卡片要放进横向滑动的 ScrollView,而弹窗是 position: fixed 全屏浮层;
 * 只要弹窗位于 scroll-view 子树内,iOS 会把它降级为相对滚动容器定位并裁切(Android 表现正常)。
 * 因此调用方必须把 card 放进 ScrollView、把 popup 放在 ScrollView 之外。
 */
export function useMusicControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props): ControlNodes {
    // 车端真实开关状态
    const [switchOn, setSwitchOn] = useState(false);
    // 后台配置曲风列表
    const [genres, setGenres] = useState<GenreItem[]>([]);
    // 当前选中曲风 ID(关闭态下也可有值,仅本地记录,不下发)
    const [selectedGenreId, setSelectedGenreId] = useState('');
    // 面板显隐 + 下发锁
    const [panelVisible, setPanelVisible] = useState(false);
    const [applying, setApplying] = useState(false);
    // 正在下发的曲风磁贴 ID(该磁贴显示 loading),空串表示由开关触发(loading 落在开关上)
    const [applyingGenreId, setApplyingGenreId] = useState('');
    const querySeqRef = useRef(0);
    // 记录本次接驾态周期内已查询过曲风状态的订单 ID,避免重复请求(同一订单在初始化恢复
    // 流程中 tripStatus/orderId/hasPreviousOrder 会分批异步更新,触发本 effect 多次)。
    const queriedOrderIdRef = useRef('');
    const { animationData, shouldRender } = useBottomPopupAnimation(panelVisible);
    const hasGenres = genres.length > 0;
    const selectedGenre = findGenreById(genres, selectedGenreId);
    // 卡片主标题:始终展示曲风名(关闭态由样式置灰),无曲风时兜底
    const cardTitle = selectedGenre?.name || (hasGenres ? genres[0].name : CARD_DEFAULT_TITLE);
    // 开关 loading 仅在由开关(而非磁贴)触发下发时展示
    const switchLoading = applying && applyingGenreId === '';
    // 进入接驾态查询曲风状态;连环单不查询、不展示
    useEffect(() => {
        if (tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
            // 离开接驾态:重置去重标记,允许下次进入接驾态时重新查询一次。
            queriedOrderIdRef.current = '';
            return;
        }
        const targetOrderId = getOrderId(orderId);
        if (!targetOrderId) {
            return;
        }
        // 去重:同一订单在本次接驾态周期内只查询一次。
        if (queriedOrderIdRef.current === targetOrderId) {
            return;
        }
        queriedOrderIdRef.current = targetOrderId;
        const snapshotSeq = ++querySeqRef.current;
        queryOrderGenre(targetOrderId).then((res: any) => {
            if (querySeqRef.current !== snapshotSeq) {
                return;
            }
            const genre = res || {};
            const nextSwitchOn = genre.switchOn === true;
            const nextGenres: GenreItem[] = Array.isArray(genre.availableGenres) ? genre.availableGenres : [];
            setSwitchOn(nextSwitchOn);
            setGenres(nextGenres);
            setSelectedGenreId(resolveInitialGenreId(nextGenres, genre.currentGenreId));
        }).catch(() => {
            if (querySeqRef.current !== snapshotSeq) {
                return;
            }
            // 查询失败按「已关闭」兜底
            setSwitchOn(false);
            setGenres([]);
            setSelectedGenreId('');
        });
    }, [tripStatus, orderId, pageDidShowVersion, hasPreviousOrder]);
    // 点击卡片:切换面板开合
    const togglePanel = () => {
        setPanelVisible((prev) => !prev);
    };
    const closePanel = () => {
        setPanelVisible(false);
    };
    /**
     * 下发曲风设置到车端;成功后同步开关状态、关闭弹窗并 toast,失败回滚并提示。
     * @param loadingGenreId 触发下发的曲风磁贴 ID(用于在该磁贴上展示 loading);由开关触发时传空串。
     */
    const applyGenre = async (nextSwitchOn: boolean, genreId: string, loadingGenreId = '') => {
        const targetOrderId = getOrderId(orderId);
        if (!targetOrderId) {
            return;
        }
        setApplying(true);
        setApplyingGenreId(loadingGenreId);
        try {
            const res: any = await setOrderGenre({
                orderId: targetOrderId,
                switchOn: nextSwitchOn,
                ...(nextSwitchOn ? { genreId } : {})
            });
            // skipException 下业务失败会以数据形式 resolve,需显式校验:
            // ① HTTP 状态码非 200;② code 为 200 但 data 返回 'FAILED'(车端下发失败)
            if (res && ((res.code !== undefined && res.code !== 200) || res.data === APPLY_FAIL_RESULT)) {
                throw res;
            }
            setSwitchOn(nextSwitchOn);
            if (nextSwitchOn && genreId) {
                setSelectedGenreId(genreId);
            }
            // 打开/关闭成功:toast 提示,并延迟 150ms 关闭控制弹窗(优化交互体验)
            const appliedGenreName = findGenreById(genres, genreId)?.name || '';
            showToast({ title: nextSwitchOn ? `${OPEN_PLAY_PREFIX}${appliedGenreName}` : CLOSE_SUCCESS_TEXT, icon: 'success' });
            setTimeout(() => setPanelVisible(false), PANEL_CLOSE_DELAY_MS);
        } catch (error) {
            // 下发失败:不改动本地状态(回滚),仅提示
            showToast({ title: APPLY_FAIL_TEXT, icon: 'none' });
        } finally {
            setApplying(false);
            setApplyingGenreId('');
        }
    };
    /**
     * 点击曲风磁贴:点当前生效曲风 → 关闭;点其它曲风 → 开启并切换到该曲风。
     * 该磁贴上方展示 loading,成功后同步底部总开关状态。
     */
    const handleGenreClick = (genreId: string) => {
        if (applying) {
            return;
        }
        const turningOff = switchOn && genreId === selectedGenreId;
        applyGenre(!turningOff, genreId, genreId);
    };
    /**
     * 切换总开关(卡片主开关 / 弹窗底部开关共用):
     * - 打开:下发 switchOn=true + 当前选中曲风(无则取列表首项);
     * - 关闭:下发 switchOn=false,保留已选曲风不重置。
     */
    const handleToggleSwitch = () => {
        if (applying || !hasGenres) {
            return;
        }
        const nextSwitchOn = !switchOn;
        const targetGenreId = selectedGenreId || genres[0]?.id || '';
        applyGenre(nextSwitchOn, targetGenreId, '');
    };
    // 连环单或非接驾态不展示音乐卡片
    if (tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
        return { card: null, popup: null };
    }
    // 音乐卡片背景随开关状态切换
    const musicCardStateClass = switchOn ? 'is-open' : 'is-close';
    const card = (
        <View className='music-control'>
            {/* 音乐入口卡片(复用 tf-card 视觉):点击打开面板 */}
            <View
                className={`tf-card tf-card-music ${musicCardStateClass}`}
                onClick={togglePanel}
            >
                <View className='tf-card-header'>
                    <View className='tf-card-header-left'>
                      <Image className='tf-card-header-icon' src={GENRE_HEADER_ICON} />
                        <Text className='tf-card-header-title'>音乐</Text>
                    </View>
                    <AcSwitch
                        checked={switchOn}
                        loading={switchLoading}
                        onChange={handleToggleSwitch}
                    />
                </View>
                <View className='tf-card-bottom'>
                    <View className='tf-card-music-name'>{cardTitle}</View>
                    <View className='tf-card-footer'>
                        {/* 副标题上下滚动轮播:末尾克隆首条实现无缝循环,动效由 CSS 驱动 */}
                        <View className='tf-card-footer-text tf-card-subtitle'>
                            <View className='tf-card-subtitle-track'>
                                {SUBTITLE_TEXTS.map((text, idx) => (
                                    <View key={idx} className='tf-card-subtitle-item'>{text}</View>
                                ))}
                                <View className='tf-card-subtitle-item'>{SUBTITLE_TEXTS[0]}</View>
                            </View>
                        </View>
                        <Image className='tf-card-footer-arrow' src={ARROW_ICON_URL} />
                    </View>
                </View>
            </View>
        </View>
    );
    // 控制面板(底部弹窗):与卡片分开挂载,必须渲染在 ScrollView 之外,否则 iOS 上会被滚动容器裁切
    const popup = shouldRender ? (
        <View className='music-control'>
            <View className={`music-popup ${panelVisible ? 'show' : ''}`} catchMove>
                    <View className='popup-mask' onClick={closePanel}></View>
                    <View className='popup-content' animation={animationData}>
                        <View className='popup-header'>
                            <Text className='popup-title'>车载音乐</Text>
                            <Image className='popup-close' src={CLOSE_ICON_URL} onClick={closePanel} />
                        </View>
                        <View className='popup-genre-area'>
                            {hasGenres ? (
                                <View className='popup-genre-list'>
                                    {genres.map((item) => {
                                        const isActive = switchOn && item.id === selectedGenreId;
                                        const isLoading = applyingGenreId === item.id;
                                        return (
                                            <View
                                                key={item.id}
                                                className={`popup-genre-item ${isActive ? 'active' : ''} ${applying ? 'disabled' : ''}`}
                                                onClick={() => handleGenreClick(item.id)}
                                            >
                                                {
                                                  isLoading ?
                                                    <Image className='popup-genre-loading' src={LOADING_ICON_URL} /> :
                                                    <Text className='popup-genre-name'>{item.name}</Text>
                                                }
                                            </View>
                                        );
                                    })}
                                </View>
                            ) : (
                                <View className='popup-genre-empty'>{NO_GENRE_TIP}</View>
                            )}
                        </View>
                        {/* 底部播放开关 */}
                        <View className='popup-play-switch'>
                            <Text className='popup-play-text'>{PLAY_TIP_TEXT}</Text>
                            <AcSwitch checked={switchOn} loading={switchLoading} onChange={handleToggleSwitch} />
                        </View>
                    </View>
            </View>
        </View>
    ) : null;
    return { card, popup };
}
/** 兼容旧用法:卡片与弹窗一起渲染(不在 ScrollView 内的场景可直接使用本组件) */
export default function MusicControl(props: Props) {
    const { card, popup } = useMusicControl(props);
    return <>{card}{popup}</>;
}
src/robotaxi/pages/begin-route/components/calling-car-modules/seat-hv-control/index.tsx
@@ -8,7 +8,7 @@
import { TripStatus } from '../../../../../constants/trip-status';
import '../../css-folder/seat-hv-control.less';
const SEAT_BG_URL = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/seat_bg_0803.webp';
const SEAT_BG_URL = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/seat_bg_0805@2x.png';
const IMG_BASE = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/';
const LOADING_ICON_URL = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/T-switch-loading-20260715.png';
const NO_SEAT_TIP = '座椅调节暂不可用';
src/robotaxi/pages/begin-route/components/calling-car-modules/temperature-control.tsx
@@ -14,6 +14,7 @@
import '../css-folder/temperature-control.less';
import { getOrderId } from '../../../../utils/order';
import { TripStatus } from '../../../../constants/trip-status';
import type { ControlNodes } from './types';
import { MODIFY_DESTINATION_SKIP_AC_QUERY_ON_RETURN_KEY } from '../../../../constants/modify-destination';
import { getAcStorage, saveAcStorage } from './ac-storage';
import useMinimumSpinLoading from './use-minimum-spin-loading';
@@ -31,6 +32,7 @@
const AC_TEMP_MAX_TIP_TEXT = '已到空调最高设定温度';
const TEMP_ITEM_WIDTH = 130; // 温度滑条单项宽度(所有项等宽)
const TEMP_SCROLL_SETTLE_MS = 120; // 滑动停止判定延时
const TEMP_LEFT = 10; // 滚动位移误差(适配中间放大效果)
type Props = {
    tripStatus: TripStatus;
@@ -40,7 +42,13 @@
    hasPreviousOrder?: boolean;
};
export default function TemperatureControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props) {
/**
 * 空调控制 Hook:返回「入口卡片」与「温度调节弹窗」两个可分别挂载的节点。
 * 拆分原因:卡片要放进横向滑动的 ScrollView,而弹窗是 position: fixed 全屏浮层;
 * 只要弹窗位于 scroll-view 子树内,iOS 会把它降级为相对滚动容器定位并裁切(Android 表现正常)。
 * 因此调用方必须把 card 放进 ScrollView、把 popup 放在 ScrollView 之外。
 */
export function useAcControl({ tripStatus, orderId, pageDidShowVersion = 0, hasPreviousOrder = false }: Props): ControlNodes {
    const [tempStatus, setTempStatus] = useState<number | null>(null);
    const [acSwitchOn, setAcSwitchOn] = useState(false);
    const [acStatusLoaded, setAcStatusLoaded] = useState(false);
@@ -116,7 +124,7 @@
            : -1;
        const safeIdx = idx >= 0 ? idx : 0;
        setCenteredTempIndex(safeIdx);
        setTempScrollLeft(safeIdx * itemPxRef.current);
        setTempScrollLeft(safeIdx * itemPxRef.current + TEMP_LEFT);
    }, [panelVisible]);
    const openPanel = () => setPanelVisible(true);
@@ -135,7 +143,7 @@
        tempScrollSettleTimerRef.current = setTimeout(() => {
            const value = TEMP_OPTIONS[clampedIdx];
            // 吸附居中
            setTempScrollLeft(clampedIdx * itemPxRef.current);
            setTempScrollLeft(clampedIdx * itemPxRef.current + TEMP_LEFT);
            if (typeof tempStatus !== 'number' || Math.abs(tempStatus - value) > 1e-6) {
                handleSelectTemp(value);
            }
@@ -145,7 +153,7 @@
    // 点击某温度项:滚动居中并选中(空调关闭时也可选,仅更新本地值)
    const handleTempItemClick = (index: number, value: number) => {
        setCenteredTempIndex(index);
        setTempScrollLeft(index * itemPxRef.current);
        setTempScrollLeft(index * itemPxRef.current + TEMP_LEFT);
        handleSelectTemp(value);
    };
@@ -450,10 +458,10 @@
    // 连环单(车还在执行上一单)时不展示空调卡片
    if (tripStatus !== TripStatus.VehicleApproaching || hasPreviousOrder) {
        return null;
        return { card: null, popup: null };
    }
    return (
    const card = (
        <View className='ac-control'>
            {/* 空调入口卡片(复用 tf-card 视觉):点击打开温度调节面板;右上开关直接切空调 */}
            <View
@@ -480,10 +488,14 @@
                    </View>
                </View>
            </View>
        </View>
    );
            {/* 温度调节面板(底部弹窗,fixed 定位覆盖全屏) */}
            {shouldRender && (
                <View className={`ac-popup ${panelVisible ? 'show' : ''}`} catchMove>
    // 温度调节面板(底部弹窗,fixed 定位覆盖全屏):与卡片分开挂载,
    // 必须渲染在 ScrollView 之外,否则 iOS 上会被滚动容器裁切
    const popup = shouldRender ? (
        <View className='ac-control'>
            <View className={`ac-popup ${panelVisible ? 'show' : ''}`} catchMove>
                    <View className='popup-mask' onClick={closePanel}></View>
                    <View className='popup-content' animation={animationData}>
                        {/* 座椅通风/加热区(顶部满宽,关闭按钮浮于右上角) */}
@@ -535,8 +547,15 @@
                            </View>
                        </View>
                    </View>
                </View>
            )}
            </View>
        </View>
    );
    ) : null;
    return { card, popup };
}
/** 兼容旧用法:卡片与弹窗一起渲染(不在 ScrollView 内的场景可直接使用本组件) */
export default function TemperatureControl(props: Props) {
    const { card, popup } = useAcControl(props);
    return <>{card}{popup}</>;
}
src/robotaxi/pages/begin-route/components/calling-car-modules/types.ts
New file
@@ -0,0 +1,16 @@
import type { ReactNode } from 'react';
/**
 * 车控组件(空调 / 音乐 / 香氛)拆分后的两个挂载点。
 *
 * 卡片需要放进横向滑动的 ScrollView,而弹窗是 position: fixed 的全屏浮层。
 * iOS(WKWebView)下 scroll-view 的滚动容器会成为 fixed 子元素的包含块,
 * 导致弹窗被限制在滚动区域内并被裁切(Android 正常),因此两者必须分开挂载:
 * card 放进 ScrollView,popup 放在 ScrollView 之外。
 */
export type ControlNodes = {
    /** 入口卡片节点:放进横向滑动容器 */
    card: ReactNode;
    /** 控制弹窗节点:必须挂在 ScrollView 之外 */
    popup: ReactNode;
};
src/robotaxi/pages/begin-route/components/calling-car.tsx
@@ -1,5 +1,5 @@
import { View, Image } from "@tarojs/components";
import { useState, useEffect } from 'react';
import { View, Image, ScrollView } from "@tarojs/components";
import { useState, useEffect, useRef } from 'react';
import './css-folder/calling-car.less'
import { formatRemainingDurationDisplay } from '../../../utils/format';
import { safeParse, formatTripDistance } from '../../../../utils/Robotaxi-public-method';
@@ -7,8 +7,9 @@
import { BLUETOOTH_ICON_OFF } from '../hooks/use-bluetooth-access'; // 蓝牙未开/未授权图标
import TripStatusHeader from './calling-car-modules/trip-status-header'; // 行程状态头
import VehicleInfoCard from './calling-car-modules/vehicle-info-card'; // 车辆信息卡片
import TemperatureControl from './calling-car-modules/temperature-control'; // 空调温控(含温度调节面板 + 座椅通风加热)
import FragranceControl from './calling-car-modules/fragrance-control'; // 车载香氛控制
import { useAcControl } from './calling-car-modules/temperature-control'; // 空调温控(含温度调节面板 + 座椅通风加热)
import { useFragranceControl } from './calling-car-modules/fragrance-control'; // 车载香氛控制
import { useMusicControl } from './calling-car-modules/music-control'; // 车载音乐曲风控制
import TripActionBar from './calling-car-modules/trip-action-bar'; // 行程操作栏
import DoorTrunkControl from './calling-car-modules/door-trunk-control'; // 门箱控制
import FareDetailCard from './calling-car-modules/fare-detail-card'; // 费用明细卡片
@@ -70,6 +71,8 @@
    finishCountdownSeconds?: number | null;
    /** 费用明细 */
    fareBreakdown?: any;
    /** 当前流程内用户手动切换后的优惠券名称 */
    manuallySelectedCouponName?: string;
    /** 车辆信息 */
    vehicleInfo?: {
        vehiclePictures?: string;
@@ -133,6 +136,7 @@
    pickupCountdownSeconds = null,
    finishCountdownSeconds = null,
    fareBreakdown = null,
    manuallySelectedCouponName = '',
    vehicleInfo,
    orderId,
    hasBeforeUserEndSite = false,
@@ -170,6 +174,25 @@
    /** 是否为“到达终点/待支付”状态(图一、图二) */
    const isTripFinished = tripStatus === TripStatus.TripFinished;
    const isCallingCar = tripStatus === TripStatus.CallingCar;
    /**
     * 车控(空调 / 音乐 / 香氛):卡片与弹窗分开挂载。
     * 卡片放进横向滑动的 ScrollView;弹窗是 fixed 全屏浮层,必须挂在 ScrollView 之外,
     * 否则 iOS(WKWebView)下滚动容器会成为 fixed 的包含块,弹窗被限制在卡片滑动区内。
     * 三个 hook 内部均按 tripStatus / hasPreviousOrder 守卫,非接驾态不发请求、返回空节点。
     */
    const acControl = useAcControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder });
    const musicControl = useMusicControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder });
    const fragranceControl = useFragranceControl({ tripStatus, orderId, pageDidShowVersion, hasPreviousOrder });
    /**
     * 车控卡片当前横向滚动位置。
     * 弹窗开合与其弹出动画会驱动本组件重渲染,weapp 的 scroll-view 在重渲染 / 重建后会回到
     * 初始位置(最左),表现为「弹窗一出现卡片就滑回去了」。这里用 ref 记录实时位置并回传
     * scroll-left,使重渲染后停留在原处;用 ref 而非 state 是为了滚动过程中不触发额外渲染。
     */
    const cardScrollLeftRef = useRef(0);
    const handleCardScroll = (e: any) => {
        cardScrollLeftRef.current = e?.detail?.scrollLeft ?? 0;
    };
    /** 费用明细是否展开(拦截 FareDetailCard 的回调本地记录,用于切换背景与卡片布局) */
    const [detailExpanded, setDetailExpanded] = useState(false);
    /** 大号里程文案:取全程里程,单位参考 formatTripDistance(米→M,公里→KM) */
@@ -272,10 +295,24 @@
            {
                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService].includes(tripStatus) &&
                <View className='car-card'>
                    <View className='temp-fragrance-control'>
                      <TemperatureControl tripStatus={tripStatus} orderId={orderId} pageDidShowVersion={pageDidShowVersion} hasPreviousOrder={hasPreviousOrder} />
                      <FragranceControl tripStatus={tripStatus} orderId={orderId} pageDidShowVersion={pageDidShowVersion} hasPreviousOrder={hasPreviousOrder} />
                    </View>
                    {/* 车控卡片横向滑动(仅接驾态展示):weapp 中普通 View 的 overflow 滚动无法响应拖动,必须用 ScrollView。
                        注意:这里只放卡片,三个控制弹窗在下方 ScrollView 之外单独挂载(iOS fixed 会被滚动容器裁切) */}
                    {
                        TripStatus.VehicleApproaching === tripStatus &&
                        <ScrollView
                          className='temp-fragrance-scroll'
                          scrollX
                          showScrollbar={false}
                          scrollLeft={cardScrollLeftRef.current}
                          onScroll={handleCardScroll}
                        >
                          <View className='temp-fragrance-control'>
                            {acControl.card}
                            {musicControl.card}
                            {fragranceControl.card}
                          </View>
                        </ScrollView>
                    }
                    <DoorTrunkControl
                        tripStatus={tripStatus}
@@ -290,6 +327,7 @@
                    <FareDetailCard
                      tripStatus={tripStatus}
                      fareBreakdown={fareBreakdown}
                      manuallySelectedCouponName={manuallySelectedCouponName}
                      orderId={orderId}
                      resetDetailVersion={resetDetailVersion}
                      onToggleDetailFade={onToggleDetailFade}
@@ -298,12 +336,22 @@
                    />
                </View>
            }
            {/* 车控弹窗(空调 / 音乐 / 香氛):必须挂在横向滑动 ScrollView 之外。
                iOS(WKWebView)下 scroll-view 的滚动容器会成为 fixed 子元素的包含块,
                弹窗若留在 ScrollView 内会被限制、裁切在卡片滑动区里(Android 无此问题)。
                非接驾态时三个 hook 均返回 null,无额外渲染开销。 */}
            {acControl.popup}
            {musicControl.popup}
            {fragranceControl.popup}
            {
                [TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus) &&
                <View className='car-card fare-box-card'>
                    <FareDetailCard
                      tripStatus={tripStatus}
                      fareBreakdown={fareBreakdown}
                      manuallySelectedCouponName={manuallySelectedCouponName}
                      orderId={orderId}
                      resetDetailVersion={resetDetailVersion}
                      onToggleDetailFade={handleFareToggle}
@@ -313,6 +361,7 @@
                </View>
            }
            {
                /* 行程操作栏:位于费用明细卡之后、门箱控制之前(对齐设计稿) */
                [TripStatus.VehicleApproaching, TripStatus.VehicleArrivedPickup, TripStatus.InService, TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus) &&
                <TripActionBar
                  tripStatus={tripStatus}
@@ -325,7 +374,6 @@
                  handleShareTrip={handleShareTrip}
                />
            }
            <DoorTrunkControl
                tripStatus={tripStatus === TripStatus.ArrivedDestination ? TripStatus.ArrivedDestination : -1}
                orderId={orderId}
src/robotaxi/pages/begin-route/components/cancel-popup.tsx
@@ -122,9 +122,9 @@
                                    <View className='cancel-site-box-icon-end'></View>
                                </View>
                                <View className='cancel-site-box-content'>
                                    <View>{startSiteName}</View>
                                    <View className='cancel-site-box-content-name'>{startSiteName}</View>
                                    <View className='cancel-site-box-divider'></View>
                                    <View>{endSiteName}</View>
                                    <View className='cancel-site-box-content-name'>{endSiteName}</View>
                                </View>
                            </View>
                            <View className='cancel-btn-box'>
src/robotaxi/pages/begin-route/components/css-folder/announcements-popup.less
@@ -12,7 +12,7 @@
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0);
        background: rgba(0, 0, 0, 0.8);
    }
    .announcements-content {
@@ -48,4 +48,4 @@
            height: 313px;
        }
    }
}
}
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -148,6 +148,7 @@
  .car-card {
    position: relative;
    z-index: 3;
    margin-top: 8px;
    border-radius: 60rpx;
  }
  .fare-box-card {
@@ -545,7 +546,7 @@
    align-items: center;
  }
  .car-card-end-through-desc {
    margin-left: 10px;
    margin-left: 8px;
    font-size: 24px;
    line-height: 40px;
    letter-spacing: normal;
@@ -582,7 +583,8 @@
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 32px 8px 0 8px;
    margin-top: 8px;
    padding: 32px 8px;
  }
  .trip-action-bar-item {
    display: flex;
@@ -828,6 +830,7 @@
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-top: 8px;
  padding: 24px 0px;
  border-radius: 16px;
  background: @gary-theme;
@@ -854,7 +857,7 @@
    font-weight: normal;
    line-height: 40px;
    letter-spacing: normal;
    color: #6d7fd9;
    color:  @black-text-main;
    image {
      flex-shrink: 0;
      width: 24px;
@@ -993,14 +996,46 @@
  }
}
/* 空调 + 香氛 左右布局容器(迁移自 temperature-fragrance-control.less) */
.temp-fragrance-control {
/* 车控入口卡片横向滑动容器(空调 / 音乐 / 香氛,卡片固定宽度,超出宽度可横向滑动)
   用 ScrollView 而非 View + overflow:weapp WebView 渲染器会拦截 View 上的触摸手势,
   CSS overflow 滚动在真机与模拟器均无法拖动。 */
.temp-fragrance-scroll {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  gap: 16px;
  margin-top: 16px;
  width: 100vw; /* 滑动区铺满整屏宽 */
  height: 240px; /* 与卡片等高,避免 scroll-view 高度塌陷 */
  margin-top: 8px;
  /* 左移 48px 抵消 .calling-car-content-box 的左内边距,使滑动区左边界对齐屏幕左边缘,
     卡片可一直滑到屏幕两侧边缘(首/末卡片的 48px 视觉留白由内部轨道的 padding 承担)。
     已显式指定 width: 100vw,故无需再写 margin-right(会因过约束被忽略)。 */
  margin-left: -48px;
  white-space: nowrap;
  /* 隐藏横向滚动条(show-scrollbar 仅 enhanced 模式生效,这里用 CSS 兜底) */
  &::-webkit-scrollbar,
  ::-webkit-scrollbar {
    display: none;
    width: 0;
    height: 0;
  }
}
/* 滑动轨道:宽度由卡片内容决定 */
.temp-fragrance-control {
  display: inline-flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 24px;
  /* 静止时首卡对齐 48px 内容左边界;滑到底时末卡右侧同样保留 48px 留白 */
  padding-left: 48px;
  padding-right: 48px;
  /* 卡片不参与压缩,保证各卡片保持设计稿宽度并整体溢出滚动 */
  > .ac-control,
  > .music-control,
  > .fragrance-control {
    flex-shrink: 0;
  }
}
/* 空调/香氛入口卡片开关 loading 旋转动画 */
@@ -1019,9 +1054,9 @@
  flex-shrink: 0;
  position: relative;
  box-sizing: border-box;
  width: 312px;
  width: 304px; /* 设计稿 152px(375 设计宽)× 2 */
  height: 240px;
  padding: 24px;
  padding: 8px 24px 24px;
  border-radius: 24px;
  overflow: hidden;
  display: flex;
@@ -1051,8 +1086,8 @@
  .tf-card-header-icon {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    width: 32px;
    height: 32px;
    margin-right: 8px;
  }
src/robotaxi/pages/begin-route/components/css-folder/cancel-popup.less
@@ -12,7 +12,7 @@
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        background: rgba(0, 0, 0, 0.8);
    }
    .cancel-content {
@@ -105,10 +105,14 @@
                line-height: 48px;
                letter-spacing: normal;
                color: #141E27;
                font-weight: 500;
                .cancel-site-box-divider {
                  margin: 34px 32px 40px 0px;
                  border-top: 1px solid rgba(204, 208, 215, 0.25);
                }
                .cancel-site-box-content-name {
                  font-weight: 500;
                }
            }
        }
src/robotaxi/pages/begin-route/components/css-folder/credit-pay-popup.less
@@ -7,7 +7,7 @@
.credit-pay-mask {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  background: rgba(0, 0, 0, 0.8);
}
.credit-pay-content {
src/robotaxi/pages/begin-route/components/css-folder/evaluate-popup.less
@@ -12,7 +12,7 @@
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        background: rgba(0, 0, 0, 0.8);
    }
    .evaluate-content {
@@ -68,7 +68,7 @@
        }
        .evaluate-rating-switcher {
            padding: 0 49px;
            display: flex;
            flex-direction: column;
@@ -93,6 +93,10 @@
                align-items: flex-start;
                justify-content: space-between;
                &.is-single {
                    justify-content: center;
                }
                &-item {
                    width: 180px;
                    display: flex;
@@ -116,6 +120,10 @@
                    .active-text {
                        color: #141E27;
                        font-weight: 500;
                    }
                    &.is-disabled {
                        pointer-events: none;
                    }
                }
            }
@@ -249,7 +257,7 @@
                    flex-direction: column;
                    align-items: center;
                    justify-content: center;
                    background: rgba(0, 0, 0, 0.4);
                    background: rgba(0, 0, 0, 0.8);
                    color: #FFF;
                    gap: 8px;
src/robotaxi/pages/begin-route/components/css-folder/fragrance-control.less
@@ -11,56 +11,8 @@
  }
}
/* 香氛入口卡片 tf-card 样式(自包含,迁移自 temperature-fragrance-control.less) */
/* 香氛入口卡片:tf-card 基础样式(盒模型 + header/footer 等)统一由 calling-car.less 的全局 .tf-card 提供,此处只保留香氛专属样式 */
.fragrance-control {
  .tf-card {
    flex-shrink: 0;
    position: relative;
    box-sizing: border-box;
    width: 312px;
    height: 240px;
    padding: 24px;
    border-radius: 24px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    background-repeat: no-repeat;
    background-position: center;
    background-size: 100% 100%;
  }
  .tf-card-bottom {
    display: flex;
    flex-direction: column;
  }
  .tf-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .tf-card-header-left {
    display: flex;
    align-items: center;
  }
  .tf-card-header-icon {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    margin-right: 8px;
  }
  .tf-card-header-title {
    font-size: 24px;
    font-weight: 500;
    line-height: normal;
    letter-spacing: normal;
    color: @black-text-main;
  }
  /* 香氛名称,大小类似温度显示 */
  .tf-card-fragrance-name {
    font-size: 40px;
@@ -71,25 +23,6 @@
    white-space: nowrap;
    text-overflow: ellipsis;
    text-align: left;
  }
  .tf-card-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .tf-card-footer-text {
    font-size: 20px;
    line-height: normal;
    letter-spacing: normal;
    color: rgba(20, 30, 39, 0.5);
  }
  .tf-card-footer-arrow {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
  }
  /* 香氛入口卡片:背景随开关状态切换 */
@@ -132,7 +65,7 @@
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.4);
      background: rgba(0, 0, 0, 0.8);
      z-index: 102;
    }
@@ -185,7 +118,8 @@
    .popup-scent-list {
      display: flex;
      align-items: stretch;
      justify-content: space-between;
      justify-content: center;
      gap: 48px;
      // 香型卡片:竖排香型名 + 香调描述,背景使用设计切图(选中/未选)
      .popup-scent-item {
src/robotaxi/pages/begin-route/components/css-folder/music-control.less
New file
@@ -0,0 +1,271 @@
@import '../../../../style/ui-variables.less';
// 曲风磁贴下发 loading 旋转动画
@keyframes music-genre-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
// 副标题上下滚动轮播:5 条文案 + 末尾克隆首条 = 轨道共 6 条,单条行高 24px
// 位移量必须用「相对轨道自身高度」的百分比而非 px:px 经 pxtransform 变成 rpx 后,
// 小程序会对每个 rpx 值独立换算成物理像素并取整,条目高度(24rpx)与位移量(96rpx/120rpx)
// 的取整结果不再是整数倍,误差逐条累计 —— 表现为滚到最后一条时露出下一条顶部的少量文字,
// 且回环瞬间克隆条与首条位置错开、出现上下抖动。
// 百分比按轨道实际渲染高度解析,1 步恒等于 1 条(100% / 6 ≈ 16.66667%),不会累计误差。
@keyframes music-subtitle-roll {
  0%, 16% {
    transform: translateY(0);
  }
  20%, 36% {
    transform: translateY(-16.66667%);
  }
  40%, 56% {
    transform: translateY(-33.33333%);
  }
  60%, 76% {
    transform: translateY(-50%);
  }
  80%, 96% {
    transform: translateY(-66.66667%);
  }
  100% {
    transform: translateY(-83.33333%);
  }
}
/* 音乐入口卡片:tf-card 基础样式(盒模型 + header/footer 等)统一由 calling-car.less 的全局 .tf-card 提供,此处只保留音乐专属样式 */
.music-control {
  /* 音符图标(暂用字形占位,待设计切图后替换为 <Image>) */
  .tf-card-header-note {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    margin-right: 8px;
    font-size: 28px;
    line-height: 28px;
    text-align: center;
    color: @black-text-main;
  }
  /* 曲风名称,大小类似温度/香氛名 */
  .tf-card-music-name {
    font-size: 40px;
    font-weight: 500;
    line-height: 64px;
    color: @black-text-main;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    text-align: left;
  }
  // 副标题上下滚动轮播:仅露出一行(24px),轨道竖排承载多条文案
  .tf-card-subtitle {
    flex: 1;
    min-width: 0;
    height: 24px;
    overflow: hidden;
  }
  .tf-card-subtitle-track {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    animation: music-subtitle-roll 25s ease infinite;
  }
  .tf-card-subtitle-item {
    flex-shrink: 0; // 锁定单条高度,避免被父级压缩后「1 步 = 1 条」的对应关系被破坏
    height: 24px;
    line-height: 24px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  /* 音乐入口卡片:背景随开关状态切换 */
  .tf-card-music {
    &.is-open {
      background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/genre-close-0731@2x.png');
    }
    &.is-close {
      background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/genre-open-0731@2x.png');
      // 关闭态卡片文字统一弱化
      .tf-card-header-note,
      .tf-card-header-title,
      .tf-card-music-name,
      .tf-card-footer-text {
        color: rgba(20, 30, 39, 0.3);
      }
    }
  }
  // 底部弹窗面板
  .music-popup {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    pointer-events: none;
    &.show {
      pointer-events: auto;
    }
    .popup-mask {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.4);
      z-index: 102;
    }
    .popup-content {
      position: relative;
      z-index: 103;
      background: #F1F5FA;
      border-radius: 60px 60px 0 0;
      width: 100%;
      padding: 0 0 calc(48px + env(safe-area-inset-bottom));
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      transform: translateY(100%);
    }
    .popup-header {
      height: 112px;
      padding-top: 8px;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      flex-shrink: 0;
      .popup-title {
        font-size: 32px;
        font-weight: 500;
        line-height: 32px;
        color: rgba(0, 0, 0, 0.9);
      }
      .popup-close {
        position: absolute;
        right: 32px;
        top: 50%;
        transform: translateY(-50%);
        width: 40px;
        height: 40px;
      }
    }
    // 曲风磁贴区(每行 3 个自动换行)
    .popup-genre-area {
      margin-top: 24px;
      padding: 0 40px;
    }
    .popup-genre-list {
      display: flex;
      flex-wrap: wrap;
      gap: 22px;
    }
    // 曲风磁贴
    .popup-genre-item {
      position: relative;
      box-sizing: border-box;
      width: calc((100% - 22px) / 2);
      height: 152px;
      flex-shrink: 0;
      border-radius: 32px;
      display: flex;
      align-items: center;
      justify-content: center;
      background-repeat: no-repeat;
      background-position: center;
      background-size: 100% 100%;
      background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/genre-item-close-0731@2x.png');
      // 下发中 loading 图标(叠加于磁贴上方,水平居中,旋转)
      .popup-genre-loading {
        //position: absolute;
        //top: 50%;
        //left: 50%;
        width: 64px;
        height: 64px;
        //transform: translate(-50%, -50%);
        //z-index: 1;
        animation: music-genre-spin 1s linear infinite;
      }
      .popup-genre-name {
        font-size: 28px;
        font-weight: 500;
        line-height: 40px;
        color: rgba(20, 30, 39, 0.3);
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
      }
      // 选中态:高亮描边 + 主题渐变底
      &.active {
        background-image: url('https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/genre-item-open-0731@2x.png');
        .popup-genre-name {
          color: #fff;
        }
      }
      // 下发中禁止重复点击
      &.disabled {
        opacity: 0.6;
      }
    }
    .popup-genre-empty {
      padding: 96px 0;
      text-align: center;
      font-size: 28px;
      line-height: 40px;
      color: rgba(0, 0, 0, 0.4);
    }
    // 底部播放开关行
    .popup-play-switch {
      margin-top: 48px;
      padding: 0 48px;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      .popup-play-text {
        font-size: 24px;
        line-height: 40px;
        color: rgba(20, 30, 39, 0.5);
      }
      .ac-switch {
        position: absolute;
        bottom: -12px;
        right: 40px;
      }
    }
  }
}
src/robotaxi/pages/begin-route/components/css-folder/no-route-popup.less
@@ -14,7 +14,7 @@
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.6);
        background: rgba(0, 0, 0, 0.8);
    }
    .no-route-content {
src/robotaxi/pages/begin-route/components/css-folder/over-route.less
@@ -15,7 +15,7 @@
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        background: rgba(0, 0, 0, 0.8);
    }
    .over-content {
src/robotaxi/pages/begin-route/components/css-folder/passenger-notice.less
@@ -60,6 +60,7 @@
                height: 100%;
                overflow-y: scroll;
                margin-top: 0;
                padding-right: 8px;
            }
            .passenger-content-desc-inner {
src/robotaxi/pages/begin-route/components/css-folder/pickup-exception-popup.less
@@ -1,3 +1,5 @@
@import '../../../../style/ui-variables.less';
.pickup-exception-popup {
  position: fixed;
  inset: 0;
@@ -7,7 +9,7 @@
.pickup-exception-mask {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  background: rgba(0, 0, 0, 0.8);
}
.pickup-exception-content {
@@ -15,9 +17,9 @@
  left: 0;
  right: 0;
  bottom: 0;
  background: #ffffff;
  border-radius: 32px 32px 0 0;
  padding: 48px 40px 80px;
  background: @gary-theme;
  border-radius: 60px 60px 0 0;
  padding: 48px 48px 80px 48px;
  box-sizing: border-box;
}
@@ -39,7 +41,7 @@
  font-weight: 500;
  line-height: 44px;
  letter-spacing: normal;
  color: rgba(0, 0, 0, 0.9);
  color: @black-text-main;
}
.pickup-exception-desc {
@@ -48,7 +50,7 @@
  font-weight: normal;
  letter-spacing: normal;
  /* 文字色/黑色/辅助文字 40% */
  color: rgba(0, 0, 0, 0.4);
  color: @black-text-04;
  margin-top: 8px;
}
@@ -56,9 +58,9 @@
  position: relative;
  width: 100%;
  height: 358px;
  border-radius: 16px;
  border-radius: 32px;
  overflow: hidden;
  background: #D8D8D8;
  background: @gary-theme;
  margin-top: 48px;
}
@@ -86,7 +88,7 @@
.pickup-exception-message {
  margin-top: 32px;
  color: rgba(0, 0, 0, 0.9);
  color: @black-text-main;
  font-size: 28px;
  line-height: 44px;
  text-align: center;
@@ -100,9 +102,8 @@
  line-height: 88px;
  text-align: center;
  color: #ffffff;
  border-radius: 16px;
  background: linear-gradient(96deg, #8475FF 1%, #7DD6FF 103%);
  border-radius: 2000px;
  background: @black-text-main;
  font-size: 28px;
  font-weight: 500;
  border-radius: 16px;
}
src/robotaxi/pages/begin-route/components/css-folder/temperature-control.less
@@ -26,7 +26,7 @@
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.4);
      background: rgba(0, 0, 0, 0.8);
      z-index: 102;
    }
@@ -128,11 +128,10 @@
      // 居中选中项:放大加粗(空调开启时高亮)
      &.active {
        width: 180px;
        width: 220px;
        .popup-temp-item-value {
          font-size: 64px;
          line-height: 72px;
          font-weight: 600;
          color: @black-text-main;
          opacity: 1;
        }
@@ -142,7 +141,6 @@
          font-family: @font-xpsans-bold;
          font-size: 28px;
          line-height: 40px;
          font-weight: 500;
          color: @black-text-main;
        }
      }
src/robotaxi/pages/begin-route/components/debug-order-simulator-panel.tsx
@@ -166,6 +166,45 @@
    }, 300);
  };
  /**
   * 立即置为接驾态并暂停:一键把页面冻结在「车辆即将到达(4)」态,便于纯静态调 UI/样式。
   *
   * 原理:新建模拟器后调用 start(),其内部 enterApproaching() 会同步注入
   * order_status(4) + 全量接驾路线事件;随即调用 stop() 清除定时器,
   * 使车辆停在接驾路线起点、剩余路线全量绘制,画面固定在接驾态不再向后推进。
   */
  const handleFreezeApproaching = () => {
    // 1) 绑定 socket 业务监听,确保注入事件能被消费(无真实连接也可)
    props.bindSocketListeners();
    // 2) 确保订单 ID 一致(守卫 isSameOrder)
    const orderId = resolveOrderId();
    ensureSimulationOrderId(orderId);
    props.setOrderId(orderId);
    // 3) 由上/下车点生成双腿路线
    const { approachRoute, serviceRoute } = buildDefaultRoutes(props.startSite, props.endSite);
    const preset = SPEED_PRESETS[speedKey];
    // 4) 新建模拟器,start() 后立即 stop(),冻结在接驾态初始帧
    simulatorRef.current?.reset();
    const simulator = new OrderFlowSimulator({
      orderId,
      approachRoute,
      serviceRoute,
      tickIntervalMs: preset.tickIntervalMs,
      pointsPerTick: preset.pointsPerTick,
      onState: (state) => setSimState(state),
    });
    simulatorRef.current = simulator;
    setRunning(false);
    // 延迟一帧等待订单 ID 写入页面 deps,再进入接驾态并立即暂停
    setTimeout(() => {
      simulator.start(); // enterApproaching:注入 status(4) + 全量接驾路线
      simulator.stop(); // 立即停止推进,冻结在接驾态
    }, 300);
  };
  /** 停止模拟(保留当前画面)。 */
  const handleStop = () => {
    simulatorRef.current?.stop();
@@ -457,6 +496,14 @@
      </View>
      <View style={{ display: 'flex', marginBottom: '4px' }}>
        <View
          onClick={handleFreezeApproaching}
          style={buttonStyle('#1d9bf0')}
        >
          接驾态并暂停(4)
        </View>
      </View>
      <View style={{ display: 'flex', marginBottom: '4px' }}>
        <View
          onClick={handleJumpToTripFinished}
          style={buttonStyle('#0e9f6e')}
        >
src/robotaxi/pages/begin-route/components/evaluate-popup.tsx
@@ -21,6 +21,10 @@
     * 是否隐藏问题类型选择
     */
    hideFeedbackType?: boolean;
    /**
     * 是否禁止切换满意度评分(点击图标/左右滑动均禁用),固定为传入的 orderRating
     */
    disableRatingSwitch?: boolean;
}
export default function EvaluatePopup({
@@ -30,7 +34,8 @@
    orderId,
    orderRating,
    submitMode = 'comment',
    hideFeedbackType = false
    hideFeedbackType = false,
    disableRatingSwitch = false
}: Props) {
    const bad = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/bad.png';
    const normal = 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/normal.png';
@@ -105,7 +110,7 @@
    };
    const handleSelectRating = (rating: number) => {
        if (rating === selectedRating) {
        if (disableRatingSwitch || rating === selectedRating) {
            return;
        }
        if (ratingSwitchTimerRef.current) {
@@ -154,7 +159,7 @@
    const handleRatingTouchEnd = (e: any) => {
        const start = ratingTouchStartRef.current;
        ratingTouchStartRef.current = null;
        if (!start || isRatingSwitching) {
        if (disableRatingSwitch || !start || isRatingSwitching) {
            return;
        }
        const touch = e?.changedTouches?.[0];
@@ -273,15 +278,20 @@
        setSelectedRating(Number(orderRating) || 1);
    }, [show, orderRating]);
    // 评分切换时重置本次填写内容并拉取对应问题类型
    // 评分切换时重置本次填写内容并拉取对应问题类型(隐藏问题类型区域时无需请求该接口)
    useEffect(() => {
        if (!show) {
            return;
        }
        const requestId = ++ratingOptionsRequestIdRef.current;
        setSelectedTypeIds([]);
        setFeedbackContent('');
        resetImages();
        if (hideFeedbackType) {
            setTypeOptions([]);
            setIsRatingSwitching(false);
            return;
        }
        const requestId = ++ratingOptionsRequestIdRef.current;
        getOrderCommentType(evaluateDimension).then((res: any) => {
            if (requestId !== ratingOptionsRequestIdRef.current) {
                return;
@@ -305,7 +315,7 @@
                setIsRatingSwitching(false);
            }, 120);
        });
    }, [evaluateDimension, resetImages, show]);
    }, [evaluateDimension, hideFeedbackType, resetImages, show]);
    useEffect(() => () => {
        if (ratingSwitchTimerRef.current) {
@@ -331,19 +341,25 @@
                </View>
                <View className='evaluate-rating-switcher'>
                    <View className='evaluate-rating-switcher-title'>您对本次行程满意吗?</View>
                    <View className='evaluate-rating-switcher-content'>
                        <View className='evaluate-rating-switcher-content-item' onClick={() => handleSelectRating(1)}>
                            <Image src={selectedRating === 1 ? activeBad : bad} className='evaluate-rating-switcher-content-item-img' />
                            <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 1 ? 'active-text' : ''}`}>不满意</Text>
                        </View>
                        <View className='evaluate-rating-switcher-content-item' onClick={() => handleSelectRating(2)}>
                            <Image src={selectedRating === 2 ? activeNormal : normal} className='evaluate-rating-switcher-content-item-img' />
                            <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 2 ? 'active-text' : ''}`}>一般般</Text>
                        </View>
                        <View className='evaluate-rating-switcher-content-item' onClick={() => handleSelectRating(3)}>
                            <Image src={selectedRating === 3 ? activeGood : good} className='evaluate-rating-switcher-content-item-img' />
                            <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 3 ? 'active-text' : ''}`}>很满意</Text>
                        </View>
                    <View className={`evaluate-rating-switcher-content ${disableRatingSwitch ? 'is-single' : ''}`}>
                        {(!disableRatingSwitch || selectedRating === 1) && (
                            <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(1)}>
                                <Image src={selectedRating === 1 ? activeBad : bad} className='evaluate-rating-switcher-content-item-img' />
                                <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 1 ? 'active-text' : ''}`}>不满意</Text>
                            </View>
                        )}
                        {(!disableRatingSwitch || selectedRating === 2) && (
                            <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(2)}>
                                <Image src={selectedRating === 2 ? activeNormal : normal} className='evaluate-rating-switcher-content-item-img' />
                                <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 2 ? 'active-text' : ''}`}>一般般</Text>
                            </View>
                        )}
                        {(!disableRatingSwitch || selectedRating === 3) && (
                            <View className={`evaluate-rating-switcher-content-item ${disableRatingSwitch ? 'is-disabled' : ''}`} onClick={() => handleSelectRating(3)}>
                                <Image src={selectedRating === 3 ? activeGood : good} className='evaluate-rating-switcher-content-item-img' />
                                <Text className={`evaluate-rating-switcher-content-item-text ${selectedRating === 3 ? 'active-text' : ''}`}>很满意</Text>
                            </View>
                        )}
                    </View>
                    <View className='evaluate-rating-switcher-summary'>{popupDisplayConfig.summary}</View>
                </View>
src/robotaxi/pages/begin-route/components/pickup-exception-popup.tsx
@@ -117,7 +117,7 @@
    if (Array.isArray(linePoints) && linePoints.length > 1) {
      nextPolylines.push({
        points: linePoints,
        color: '#7B91FF',
        color: '#DFB369',
        width: 6,
        dottedLine: true,
        arrowLine: false
src/robotaxi/pages/begin-route/components/release-route.tsx
@@ -10,6 +10,7 @@
    callImmediately: () => void;
    onSelectSite?: (pageType: 'start' | 'end') => void;
    fareEstimate?: any;
    manuallySelectedCouponName?: string;
    onOpenCouponSelect?: () => void;
    handleOpenPassengerNotice?: () => void;
    walkingTime?: number;
@@ -21,7 +22,7 @@
    handleSelectRoute?: (index: number) => void;
};
export default function ReleaseRoute({ startSite, endSite, callImmediately, onSelectSite, fareEstimate = {}, onOpenCouponSelect, handleOpenPassengerNotice, walkingTime = 0, isCallDisabled = false, isRouteSelectExpanded = false, onToggleRouteSelect, routeOptions = [], selectedRouteIndex = 0, handleSelectRoute }: Props) {
export default function ReleaseRoute({ startSite, endSite, callImmediately, onSelectSite, fareEstimate = {}, manuallySelectedCouponName = '', onOpenCouponSelect, handleOpenPassengerNotice, walkingTime = 0, isCallDisabled = false, isRouteSelectExpanded = false, onToggleRouteSelect, routeOptions = [], selectedRouteIndex = 0, handleSelectRoute }: Props) {
    const [boxHeight, setBoxHeight] = useState<number | null>(null);
    const [bottomPaddingHeight, setBottomPaddingHeight] = useState<number | null>(null);
    const startY = useRef(0);
@@ -43,6 +44,11 @@
        }
        return `${parseFloat(discountAmount.toFixed(2))}元`;
    };
    const discountTitleText = manuallySelectedCouponName || '最佳优惠';
    const discountDescText = manuallySelectedCouponName
        ? `已选择${manuallySelectedCouponName}`
        : '已选择最佳优惠';
    useEffect(() => {
        if (!isRouteSelectExpanded && boxHeight !== null && minHeight.current > 0) {
@@ -256,8 +262,8 @@
                          onClick={onOpenCouponSelect}
                        >
                            <View className="flex-center">
                                <View className="discount-amount-text">最佳优惠</View>
                                <View className="discount-amount-desc" >已为您选择最佳优惠</View>
                                <View className="discount-amount-text">{discountTitleText}</View>
                                <View className="discount-amount-desc" >{discountDescText}</View>
                            </View>
                            <View className="flex-center">
                                <View className="discount-amount-num">-{formatDiscountAmountText()}</View>
src/robotaxi/pages/begin-route/components/route-preference-popup/index.less
@@ -21,7 +21,7 @@
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.4);
        background: rgba(0, 0, 0, 0.8);
        z-index: 102;
    }
@@ -96,4 +96,4 @@
            }
        }
    }
}
}
src/robotaxi/pages/begin-route/hooks/use-map-markers.ts
@@ -20,7 +20,7 @@
  color: "#141E27",
  fontSize: 12,
  anchorX: 0,
  anchorY: 9, // 图标下方 9px
  anchorY: 7, // 图标下方 5px
  textAlign: "center" as const,
  bgColor: "#00000000",
  fontWeight: 'bold',
@@ -28,12 +28,12 @@
  borderRadius: 0,
  padding: 0
};
// 到达上车点/待支付/完成态:站点名称在图标下方 8px,11px #141E27 居中
// 到达上车点/待支付/完成态:站点名称在图标下方 7px,11px #141E27 居中
const SITE_NAME_LABEL_BELOW_LG = {
  color: "#141E27",
  fontSize: 12,
  anchorX: 0,
  anchorY: 8,
  anchorY: 7,
  textAlign: "center" as const,
  bgColor: "#00000000",
  fontWeight: 'bold',
src/robotaxi/pages/begin-route/hooks/use-page-actions.ts
@@ -25,6 +25,8 @@
  onPublishRouteSelectSite?: (pageType: 'start' | 'end') => void;
};
const PENDING_SELECTED_COUPON_ID_STORAGE_KEY = 'pendingSelectedCouponId';
/**
 * 页面动作编排:统一处理服务入口、页面跳转、地图气泡点击行为。
 */
@@ -99,6 +101,7 @@
  const toBack = () => {
    deps.onBeforeBack?.();
    Taro.removeStorageSync('selectedCouponId');
    Taro.removeStorageSync(PENDING_SELECTED_COUPON_ID_STORAGE_KEY);
    clearModifyDestinationStorage();
    clearPassedRouteCache();
    if (deps.tripStatus === TripStatus.PaymentSuccess || deps.tripStatus === TripStatus.PublishRoute) {
src/robotaxi/pages/begin-route/hooks/use-page-restore.ts
@@ -17,6 +17,8 @@
/** 已选优惠券缓存键。 */
const SELECTED_COUPON_ID_STORAGE_KEY = 'selectedCouponId';
/** 优惠券列表页“去使用”待消费缓存键。 */
const PENDING_SELECTED_COUPON_ID_STORAGE_KEY = 'pendingSelectedCouponId';
type RequestRouteOptions = {
  startPointId?: string | number;
@@ -89,6 +91,13 @@
  };
  /**
   * 清理优惠券列表页“去使用”的待消费缓存。
   */
  const clearPendingSelectedCouponId = () => {
    Taro.removeStorageSync(PENDING_SELECTED_COUPON_ID_STORAGE_KEY);
  };
  /**
   * 归一化已选优惠券 ID,过滤空值和脏缓存值。
   */
  const normalizeSelectedCouponId = () => {
@@ -101,13 +110,42 @@
  };
  /**
   * 归一化优惠券列表页“去使用”的待消费券 ID。
   */
  const normalizePendingSelectedCouponId = () => {
    const rawCouponId = Taro.getStorageSync(PENDING_SELECTED_COUPON_ID_STORAGE_KEY);
    const normalizedCouponId = String(rawCouponId ?? '').trim();
    if (!normalizedCouponId || ['null', 'undefined'].includes(normalizedCouponId)) {
      return '';
    }
    return normalizedCouponId;
  };
  /**
   * 进入预估态前处理优惠券缓存:
   * 无订单时校验券状态,行程中进入页面则直接清空。
   */
  const prepareSelectedCouponForEstimate = async (cachedOrderId: string, cachedTripStatus: TripStatus) => {
    const cachedCouponId = normalizeSelectedCouponId();
    let cachedCouponId = normalizeSelectedCouponId();
    const rawCouponId = Taro.getStorageSync(SELECTED_COUPON_ID_STORAGE_KEY);
    const rawPendingCouponId = Taro.getStorageSync(PENDING_SELECTED_COUPON_ID_STORAGE_KEY);
    const pendingCouponId = normalizePendingSelectedCouponId();
    const canConsumePendingCoupon = cachedTripStatus === TripStatus.PublishRoute && !cachedOrderId;
    // 优惠券列表页“去使用”的待消费券进入确认行程页后,应只消费一次。
    // 只要存在有效 pending 值,就由它决定本次无订单预估态使用的券,并立即清空 pending,
    // 避免旧 selectedCouponId 抢占优先级或 pending 残留到后续页面恢复中再次被消费。
    if (pendingCouponId) {
      if (canConsumePendingCoupon) {
        Taro.setStorageSync(SELECTED_COUPON_ID_STORAGE_KEY, pendingCouponId);
        cachedCouponId = pendingCouponId;
      }
      clearPendingSelectedCouponId();
    } else if (rawPendingCouponId !== '' && rawPendingCouponId !== undefined && rawPendingCouponId !== null) {
      clearPendingSelectedCouponId();
    }
    if (!cachedCouponId) {
      const rawCouponId = Taro.getStorageSync(SELECTED_COUPON_ID_STORAGE_KEY);
      if (rawCouponId !== '' && rawCouponId !== undefined && rawCouponId !== null) {
        clearSelectedCouponId();
      }
@@ -126,6 +164,7 @@
    ].includes(cachedTripStatus);
    if (cachedOrderId && isTripOrderStatus) {
      clearSelectedCouponId();
      clearPendingSelectedCouponId();
      return;
    }
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -71,6 +71,8 @@
  handleEstimateSelectSite: (pageType: "start" | "end") => void;
  /** 费用信息 */
  fareEstimate: any;
  /** 预估态用户手动切换后的优惠券名称 */
  manuallySelectedCouponName: string;
  /** 打开优惠券选择弹窗 */
  handleOpenCouponSelect: () => void;
  /** 打开乘客须知 */
@@ -772,12 +774,12 @@
                    }`.trim()}
                  style={{ minWidth: "40px", justifyContent: "center", zIndex: 2 }}
                >
                  {!deps.hasBeforeUserEndSite && (
                  {deps.hasBeforeUserEndSite && (
                    <CoverView className="car-time car-before-order-tip">
                      即将完成上一单
                    </CoverView>
                  )}
                  {!deps.hasBeforeUserEndSite ? (
                  {deps.hasBeforeUserEndSite ? (
                    <CoverView className="car-before-order-tip-text">
                      <CoverView>
                        全程
@@ -942,6 +944,7 @@
          callImmediately={deps.callImmediately}
          onSelectSite={deps.handleEstimateSelectSite}
          fareEstimate={deps.fareEstimate}
          manuallySelectedCouponName={deps.manuallySelectedCouponName}
          onOpenCouponSelect={deps.handleOpenCouponSelect}
          handleOpenPassengerNotice={deps.handleOpenPassengerNotice}
          walkingTime={deps.walkingTime}
@@ -1042,6 +1045,7 @@
          pickupCountdownSeconds={pickupCountdownSeconds}
          finishCountdownSeconds={finishCountdownSeconds}
          fareBreakdown={fareBreakdown}
          manuallySelectedCouponName={deps.manuallySelectedCouponName}
          evaluateSubmitVersion={deps.evaluateSubmitVersion}
          evaluateCloseVersion={deps.evaluateCloseVersion}
          evaluatePopupStatus={deps.evaluatePopupStatus}
src/robotaxi/pages/begin-route/index.less
@@ -224,14 +224,13 @@
    width: 24px;
    height: 24px;
    display: block;
    margin-right: 8px;
    margin-right: 4px;
}
.site-countdown-time {
    font-family: @font-xpsans-bold;
    font-size: 24px;
    font-weight: normal;
    line-height: 32px;
    text-align: center;
    letter-spacing: 0.04em;
    color: #FFD181;
src/robotaxi/pages/begin-route/index.tsx
@@ -59,11 +59,11 @@
// 蓝牙车控连接与鉴权编排
import useBluetoothCarControl from './hooks/use-bluetooth-car-control';
import { toTripStatus, TripStatus } from '../../constants/trip-status';
import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from '../../constants/modify-destination';
import { MODIFY_DESTINATION_SOCKET_STATUS_KEY, MODIFY_DESTINATION_SESSION_KEY } from '../../constants/modify-destination';
import { cancelOrder, selectNavigationRoute, getUpdateAddressNum } from '../../api/begin-route';
import { getAddressByLocation, getDistance, reportEvent } from '../../../utils/Robotaxi-public-method';
import { CurrentUserLocation, RouteDistanceValue } from './hooks/use-route-manager.shared';
import { MODIFY_DESTINATION_SESSION_KEY } from '../../constants/modify-destination';
// import { MODIFY_DESTINATION_SESSION_KEY } from '../../constants/modify-destination';
import { getOrderId } from '../../utils/order';
import { getOrderDetail } from '../../api/order';
@@ -313,6 +313,8 @@
  const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0); // 评价关闭版本号
  const [evaluateOrderRating, setEvaluateOrderRating] = useState(1); // 当前评价弹窗对应评分
  const [showCouponSelectPopup, setShowCouponSelectPopup] = useState(false); // 优惠券选择弹窗展示状态
  const [defaultPreviewCouponId, setDefaultPreviewCouponId] = useState(''); // 预估态默认命中的优惠券 ID
  const [manuallySelectedCouponName, setManuallySelectedCouponName] = useState(''); // 当前流程内用户手动切换后的优惠券名称
  const [orderDetailViewportRefreshVersion, setOrderDetailViewportRefreshVersion] = useState(0); // 完成态视口刷新版本,关闭优惠券弹窗后触发 includePoints 纠偏
  const [showModifyDestinationFailPopup, setShowModifyDestinationFailPopup] = useState(false); // 修改目的地失败弹窗
  const [modifyDestinationFailMessage, setModifyDestinationFailMessage] = useState(''); // 修改目的地失败文案(默认空,避免闪文案)
@@ -375,6 +377,7 @@
  const lastWaitingForPickupPageViewOrderIdRef = useRef(''); // 等待接驾页页面浏览埋点最近一次上报的订单 ID
  const handledPickupExceptionSignatureRef = useRef(''); // 已处理接驾异常签名,避免同一异常重复弹窗
  const previousOrderIdRef = useRef(''); // 上一个订单 ID,用于跨单清理异常弹窗状态
  const pendingFareCouponSelectionRef = useRef({ couponId: '', couponName: '' }); // 待支付态最近一次手选券,待账单返回后校正名称
  const confirmRideEventSnapshotRef = useRef({
    startSite: null as any,
    endSite: null as any,
@@ -2285,9 +2288,22 @@
      setShowCouponSelectPopup(false);
      return;
    }
    const selectedCouponName = String(item?.title || item?.couponName || '').trim();
    if (tripStatus === TripStatus.PublishRoute) {
      setManuallySelectedCouponName(
        selectedCouponId === defaultPreviewCouponId ? '' : selectedCouponName
      );
      pendingFareCouponSelectionRef.current = { couponId: '', couponName: '' };
    }
    Taro.setStorageSync('selectedCouponId', selectedCouponId);
    setShowCouponSelectPopup(false);
    if ([TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) {
      // 待支付态先即时回显用户刚选择的券名,接口返回后再按实际生效券校正。
      pendingFareCouponSelectionRef.current = {
        couponId: selectedCouponId,
        couponName: selectedCouponName
      };
      setManuallySelectedCouponName(selectedCouponName);
      setPriceUpdateVersion(v => v + 1);
      return;
    }
@@ -2308,6 +2324,17 @@
  const activeCouponId = String( // 优惠券弹窗默认选中券ID
    fareEstimate?.couponId || fareEstimate?.userCouponId || Taro.getStorageSync('selectedCouponId') || ''
  );
  useEffect(() => {
    if (tripStatus !== TripStatus.PublishRoute) {
      return;
    }
    if (manuallySelectedCouponName) {
      return;
    }
    setDefaultPreviewCouponId(String(
      fareEstimate?.couponId || fareEstimate?.userCouponId || ''
    ).trim());
  }, [tripStatus, fareEstimate?.couponId, fareEstimate?.userCouponId, manuallySelectedCouponName]);
  const canWalkNavigateToPickup = useMemo(() => {
    if (isModifyDestinationReviewVisible) {
      return false;
@@ -2411,6 +2438,7 @@
    isWaitingSignedCreditPayResult,
    handleEstimateSelectSite,
    fareEstimate,
    manuallySelectedCouponName,
    handleOpenCouponSelect,
    handleOpenPassengerNotice,
    walkingTime,
@@ -2478,6 +2506,23 @@
  const couponOrderMoney = tripStatus === TripStatus.TripFinished
    ? (Number(fareBreakdown?.orderMoney) || 0)
    : (Number(fareEstimate?.orderMoney) || 0);
  useEffect(() => {
    if (![TripStatus.TripFinished, TripStatus.PaymentSuccess].includes(tripStatus)) {
      return;
    }
    const pendingSelection = pendingFareCouponSelectionRef.current;
    if (!pendingSelection.couponId) {
      return;
    }
    const resolvedCouponId = String(fareBreakdown?.userCouponId || '').trim();
    // 账单返回后,以实际生效券为准:命中用户所选则保留名称,否则清空避免价格与名称不一致。
    if (resolvedCouponId && resolvedCouponId === pendingSelection.couponId) {
      setManuallySelectedCouponName(pendingSelection.couponName);
    } else {
      setManuallySelectedCouponName('');
    }
    pendingFareCouponSelectionRef.current = { couponId: '', couponName: '' };
  }, [tripStatus, fareBreakdown?.userCouponId]);
  // 渲染时优先使用 record*/set* 记录的最新值(ref),ref 为空再回退到 state,保证下次渲染拿到最新值
  // 【修复频繁切换目的地后地图闪现北京故宫的问题】
  // 当页面重新挂载且定位接口(getLocation)还未返回时,longitude/latitude 状态为 null。
@@ -2645,7 +2690,8 @@
        onClose={handleCloseCreditPayPopup}
        onConfirm={handleConfirmCreditPayPopup}
      />
      {/* <DebugOrderSimulatorPanel
      {/* 模拟器入口已关闭,如需本地调试可取消下方注释
      <DebugOrderSimulatorPanel
        orderId={orderId}
        setOrderId={setOrderId}
        setTripStatus={setTripStatus}
@@ -2656,7 +2702,8 @@
        setShowCancelPopup={setShowCancelPopup}
        setShowModifyDestinationFailPopup={setShowModifyDestinationFailPopup}
        setModifyDestinationFailMessage={setModifyDestinationFailMessage}
      /> */}
      />
      */}
    </>
  )
}
src/robotaxi/pages/coupon/index.less
@@ -206,7 +206,7 @@
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.5);
      background: rgba(0, 0, 0, 0.8);
    }
    .privacy-content {
src/robotaxi/pages/coupon/index.tsx
@@ -2,6 +2,8 @@
import { useState, useEffect, useRef } from "react";
import { View, Text, Image, RichText, ScrollView } from "@tarojs/components";
import "./index.less";
const PENDING_SELECTED_COUPON_ID_STORAGE_KEY = 'pendingSelectedCouponId';
import {
  getCouponList,
  receiveCoupon,
@@ -175,7 +177,9 @@
  const toHome = (couponId?: string) => {
    if (couponId) {
      Taro.setStorageSync("selectedCouponId", couponId);
      // 优惠券列表页“去使用”仅表达一次待带入发单页的意图,
      // 不能污染发单/结算流程内部复用的 selectedCouponId。
      Taro.setStorageSync(PENDING_SELECTED_COUPON_ID_STORAGE_KEY, couponId);
    }
    Taro.reLaunch({
      url: "/pages/robotaxi/home/index"
src/robotaxi/pages/expense-description/index.tsx
@@ -267,6 +267,7 @@
        orderRating={1}
        submitMode="complaint"
        hideFeedbackType
        disableRatingSwitch
        onClose={() => setShowEvaluatePopup(false)}
        onConfirm={() => {
          setShowEvaluatePopup(false);
src/robotaxi/pages/feedback/index.less
@@ -214,7 +214,7 @@
    align-items: center;
    justify-content: center;
    gap: 8px;
    background: rgba(0, 0, 0, 0.4);
    background: rgba(0, 0, 0, 0.8);
    color: #FFFFFF;
    &-text {
src/robotaxi/pages/operating-area/index.less
@@ -123,7 +123,7 @@
      border-radius: 24px;
      padding: 32rpx 32rpx 36rpx 32rpx;
      box-sizing: border-box;
      height: 410rpx;
      max-height: 410rpx;
      overflow: auto;
      .content-title {
src/robotaxi/pages/operating-area/index.tsx
@@ -5,13 +5,14 @@
import { getOpenCity } from '../../api/select-city';
import { getGeofence } from '../../api/home';
import { getServicePhone } from '../../api/customer-service';
import { extractPointsFromPolygons, findMatchedPolygon } from '../../../utils/Robotaxi-public-method';
import { withRobotaxiWhitelist } from '../../../utils/whitelist';
import CitySelect from '../../../pages/robotaxi/home/components/city-select';
const DEFAULT_MAP_LONGITUDE = 116.397128;
const DEFAULT_MAP_LATITUDE = 39.916527;
const MAP_INCLUDE_POINTS_PADDING = [10, 10, 10, 10];
const MAP_INCLUDE_POINTS_PADDING = [120, 20, 160, 20];
// includePoints 缩放动画的预估时长,动画结束后再显示地图,避免“先展示再放大”的跳动
const MAP_FIT_ANIMATION_DURATION = 700;
const POLYGON_STROKE_COLOR = '#DFB369'; // rgb(223, 179, 105)
@@ -20,6 +21,7 @@
const POLYGON_STROKE_WIDTH = 1.5;
const SELECTED_POLYGON_DASH_ARRAY = [0, 0];
const UNSELECTED_POLYGON_DASH_ARRAY = [10, 10];
const DEFAULT_CUSTOMER_SERVICE_PHONE = '';
function OperatingArea() {
  const [longitude, setLongitude] = useState<number | null>(null);
@@ -29,6 +31,7 @@
  const [rawGeofenceData, setRawGeofenceData] = useState<any[]>([]);
  const [selectedPolygonId, setSelectedPolygonId] = useState<number | null>(null);
  const [operatingTimeInfo, setOperatingTimeInfo] = useState<any>(null);
  const [customerServicePhone, setCustomerServicePhone] = useState(DEFAULT_CUSTOMER_SERVICE_PHONE);
  const [scrollIntoViewId, setScrollIntoViewId] = useState<string>('');
  // 地图是否处于加载/切换中:为 true 时用占位图覆盖地图,避免定位跳动可见
  const [isMapLoading, setIsMapLoading] = useState<boolean>(true);
@@ -82,6 +85,23 @@
      console.error('获取城市数据失败', err);
      getLocationInfo();
    });
  }, []);
  useEffect(() => {
    getServicePhone()
      .then((res: any) => {
        const servicePhone = String(
          res?.customerServicePhone
          || res?.servicePhone
          || DEFAULT_CUSTOMER_SERVICE_PHONE
        ).trim();
        if (servicePhone) {
          setCustomerServicePhone(servicePhone);
        }
      })
      .catch(() => {
        setCustomerServicePhone(DEFAULT_CUSTOMER_SERVICE_PHONE);
      });
  }, []);
  const normalizeGeofenceData = (geofenceList: any[] = []) => {
@@ -348,7 +368,7 @@
        // scale={30}
        polygons={polygons}
        showLocation={false}
        style={{ width: '100%', height: '65%' }}
        style={{ width: '100%', height: '70%' }}
        onPolygonTap={handlePolygonTap}
        onTap={handleMapTap}
      >
@@ -395,13 +415,13 @@
            </View>
            <View className="content-desc">
              <View className="desc-item">
                <Text className="text">1. 运营时间内可正常使用打车服务,超出运营时间无法使用打车功能</Text>
                <Text className="text">各站点运营时间可能存在调整,实际运营情况请以页面实时展示为准</Text>
              </View>
               <View className="desc-item">
                <Text className="text">2. 各站点运营时间可能存在差异,站点运营情况请依照实际情况</Text>
              </View>
               <View className="desc-item">
                <Text className="text">3. 有问题可联系区域负责人,联系电话:4008208820</Text>
                <Text className="text">3. 有问题可联系区域负责人,客服电话号码:{customerServicePhone}</Text>
              </View>
            </View>
          </View>
src/robotaxi/pages/order/detail/index.less
@@ -1,3 +1,5 @@
@import '../../../style/ui-variables.less';
.order-detail-page {
  background: #F2F3F7;
  min-height: 100vh;
@@ -6,21 +8,22 @@
  flex-direction: column;
  .car-info {
    padding: 18px 32px 32px 32px;
    border-radius: 24px;
    padding: 48px 48px 40px 48px;
    border-radius: 60px;
    background: #FFFFFF;
    .info-top {
      display: flex;
      justify-content: space-between;
      position: relative;
      .car-name {
        font-family: PingFang SC;
        font-size: 28px;
        font-weight: normal;
        font-weight: 500;
        line-height: 48px;
        letter-spacing: normal;
        color: rgba(0, 0, 0, 0.9);
        color: @black-text-main;
        margin-top: 14px;
      }
@@ -67,19 +70,21 @@
        font-size: 24px;
        font-variation-settings: "opsz" auto;
      }
    }
    .car-box {
      position: absolute;
      top: 50px;
      right: 52px;
      .car-img {
        width: 236px;
        height: 110px;
        margin-right: 4px;
        width: 272px;
        height: 234px;
      }
    }
    .evaluate-card {
      margin-top: 36px;
      margin-top: 32px;
      width: 638px;
      height: 248px;
      border-radius: 16px;
      border-radius: 40px;
      background: #F5F6FA;
      padding: 32px 28px 24px 30px;
@@ -90,7 +95,7 @@
        line-height: 40px;
        letter-spacing: normal;
        /* 文字色/黑色/重要字 90% */
        color: rgba(0, 0, 0, 0.9);
        color: @black-text-main;
        margin: 0px 0px 24px 2px;
      }
@@ -128,12 +133,12 @@
          text-align: center;
          letter-spacing: normal;
          /* 文字色/黑色/辅助文字 40% */
          color: rgba(0, 0, 0, 0.4);
          color: @black-text-04;
        }
        .active-text {
          /* 文字色/黑色/重要字 90% */
          color: rgba(0, 0, 0, 0.9);
          color: @black-text-main;
        }
      }
    }
@@ -141,9 +146,9 @@
  .trip-info {
    margin-top: 24px;
    border-radius: 20.29px;
    border-radius: 60px;
    background: #FFFFFF;
    padding: 32px 24px;
    padding: 48px 48px 40px 48px;
    .trip-top {
      display: flex;
@@ -160,7 +165,8 @@
          font-size: 48px;
          line-height: 40px;
          letter-spacing: normal;
          color: rgba(0, 0, 0, 0.9);
          color: @black-text-main;
          font-weight: 500;
        }
        .unit {
@@ -169,7 +175,7 @@
          font-weight: normal;
          line-height: 26px;
          letter-spacing: normal;
          color: rgba(0, 0, 0, 0.9);
          color: @black-text-main;
          margin: 0 4px;
        }
@@ -180,7 +186,7 @@
          line-height: 26px;
          letter-spacing: normal;
          text-decoration: line-through;
          color: rgba(0, 0, 0, 0.4);
          color: @black-text-04;
        }
      }
@@ -195,7 +201,7 @@
          line-height: 40px;
          letter-spacing: normal;
          /* 文字色/黑色/次要文字 60% */
          color: rgba(0, 0, 0, 0.6);
          color: @black-text-04;
          margin-right: 12px;
        }
@@ -224,7 +230,7 @@
        line-height: 44px;
        letter-spacing: normal;
        /* 文字色/黑色/重要字 90% */
        color: rgba(0, 0, 0, 0.9);
        color: @black-text-main;
        margin-bottom: 8px;
      }
@@ -234,10 +240,10 @@
        .line-icon {
          /* 自动布局子元素 */
          width: 8px;
          height: 8px;
          width: 10px;
          height: 10px;
          z-index: 0;
          background: #8CB654;
          background: @black-text-main;
          border-radius: 50%;
          margin-right: 16px;
          flex-shrink: 0;
@@ -250,15 +256,15 @@
          font-weight: normal;
          line-height: 40px;
          letter-spacing: normal;
          color: rgba(0, 0, 0, 0.4);
          color: @black-text-04;
        }
        .first-icon {
          background: #6D7FD9;
          background: @gold-light;
        }
      }
    }
  }
}
}
src/robotaxi/pages/order/detail/index.tsx
@@ -149,10 +149,10 @@
        id: 1,
        longitude: displayStartPoint.longitude,
        latitude: displayStartPoint.latitude,
        width: 20,
        height: 36,
        anchor: { x: 0.5, y: 1 },
        iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/start-text.png',
        width: 34,
        height: 46,
        anchor: { x: 0.7, y: 0.8 },
        iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/start-site-order-20260804-v2.png',
        viewportLongitude: startPoint?.longitude,
        viewportLatitude: startPoint?.latitude,
      } : null,
@@ -160,10 +160,10 @@
        id: 2,
        longitude: endPoint.longitude,
        latitude: endPoint.latitude,
        width: 20,
        height: 36,
        anchor: { x: 0.5, y: 1 },
        iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/end-text.png',
        width: 34,
        height: 46,
        anchor: { x: 0.7, y: 0.8 },
        iconPath: 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/end-site-order-20260804-v2.png',
      } : null
    ].filter(Boolean) as any[];
    setMarkers(markerList);
@@ -193,15 +193,15 @@
    }
    const polylineData = [{
    points: routePath,
    color: '#6D7BC1',
    width: 10,
    color: '#59B688',
    width: 7,
    dottedLine: false,
    arrowLine: false,
  },
  {
    points: routePath,
    color: '#7B91FF',
    width: 8,
    color: '#59B688',
    width: 7,
    dottedLine: false,
    arrowLine: true,
  }];
@@ -305,18 +305,22 @@
            orderData.vehicleId &&
            <View className='info-top'>
              <View>
                <View className='car-name'>{orderData.plateNumber}</View>
                <View className='car-name'>{orderData.plateNumber} · {orderData.vehicleColor}</View>
                <View className='info'>
                  <Text className='car-color'>小鹏GX</Text>
                  <Text className='info-line'></Text>
                  <Text className='car-color'>{orderData.vehicleColor}</Text>
                  {/* <Text className='car-code'>车身编码: </Text>
                  <Text className='code'>{orderData.vehicleCode}</Text> */}
                </View>
              </View>
              <Image src={safeParse(orderData.vehiclePictures, {})?.url || 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/default-vehicle-img-20260724-v2.png'} className='car-img' />
            </View>
          }
          {
            orderData.vehicleId && (
              <View className='car-box'>
                <Image src={safeParse(orderData.vehiclePictures, {})?.url || 'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/default-vehicle-img-20260724-v2.png'} className='car-img' />
            </View>
            )}
          {
            orderData.status >= 9 &&
            <View className='evaluate-card'>
@@ -353,7 +357,7 @@
            </View>
            <View onClick={todescription} className='price-remark'>
              <Text className='remark-text'>费用说明</Text>
              <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/right.png" className='remark-img' />
              <Image src="https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/text04-arrow-20260804.png" className='remark-img' />
            </View>
          </View>
        }
src/robotaxi/pages/passenger-notice/index.tsx
@@ -16,7 +16,7 @@
function PassengerNotice() {
  const [content, setContent] = useState('');
  // const [title, setTitle] = useState('乘车须知');
  const [title, setTitle] = useState('乘车须知');
  const RIDE_NOTICE_TYPE = 5; // 乘车须知协议类型
  const normalizeContent = (rawContent: string) => {
@@ -53,7 +53,7 @@
    getLoginAgreementContentInfo(typeParam || '').then((res: any) => {
      const target = resolveAgreementItem(res);
      if (target) {
        // setTitle(String(target.name || '乘车须知'));
        setTitle(String(target.name || '乘车须知'));
        setContent(normalizeContent(target.content || ''));
        console.log('normalizeContent(target.content ||', normalizeContent(target.content || ''));
        return;
@@ -66,7 +66,7 @@
  return (
    <View className='passenger-notice'>
      {/* <View className="passenger-notice-title">{title}</View> */}
      <View className="passenger-notice-title">{title}</View>
      <View className="passenger-notice-content">
        <RichText nodes={content} />
      </View>
src/robotaxi/pages/payment-setting-detail/index.tsx
@@ -85,7 +85,7 @@
    }
    try {
      await setPayPwd(RobotaxiPaySettingStatus.Disable);
      await setPayPwd(RobotaxiPaySettingStatus.Disable, true);
      showToast({
        title: '微信免密支付已关闭',
        icon: 'none'
@@ -95,7 +95,7 @@
      }, 300);
    } catch (error) {
      showToast({
        title: '关闭失败',
        title: error?.msg || '关闭失败',
        icon: 'none'
      });
    } finally {
src/robotaxi/pages/payment-setting/index.tsx
@@ -145,7 +145,7 @@
    setIsOperating(true);
    try {
      const response = await setPayPwd(nextOperateStatus);
      const response = await setPayPwd(nextOperateStatus, true);
      if (nextOperateStatus === RobotaxiPaySettingStatus.Disable) {
        const nextStatus = await refreshPayStatus(false);
src/robotaxi/pages/select-map-site/hooks/use-select-map-site-walking-route.ts
@@ -51,7 +51,7 @@
      }
      deps.setPolyline([{
        points: displayPoints,
        color: '#6D7FD9',
        color: '#DFB369',
        width: deps.isAndroid ? 4 : 6,
        dottedLine: true,
        arrowLine: false,
src/robotaxi/pages/share-trip/hooks/use-map-markers.ts
@@ -4,7 +4,7 @@
// 接驾态(前往上车点)/到达终点态 上车点三角图标(与 begin-route 对齐)
const PICKUP_APPROACHING_ICON_URL =
    'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-2026022.png';
    'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/up-triangle-20260722.png';
// 送驾态(前往目的地)/到达终点态 下车点三角图标(与 begin-route 对齐)
const DROPOFF_INSERVICE_ICON_URL =
    'https://s.xiaopeng.com/xp-fe/robot-taxi/imgs/v2/down-triangle-20260722.png';
@@ -13,7 +13,7 @@
    color: '#141E27',
    fontSize: 12,
    anchorX: 0,
    anchorY: 9, // 图标下方 9px
    anchorY: 7, // 图标下方 7px
    textAlign: 'center' as const,
    bgColor: '#00000000',
    fontWeight: 'bold',
@@ -26,7 +26,7 @@
    color: '#141E27',
    fontSize: 12,
    anchorX: 0,
    anchorY: 8,
    anchorY: 7,
    textAlign: 'center' as const,
    bgColor: '#00000000',
    fontWeight: 'bold',
@@ -136,6 +136,16 @@
        const isPickupArrivedDest = [TripStatus.ArrivedDestination, TripStatus.TripFinished, TripStatus.CancelOnlyPickup].includes(deps.tripStatus);
        const isPickupTriangle = isPickupApproaching || isPickupArrivedDest;
        const pickupNameLabelConfig = isPickupApproaching ? SITE_NAME_LABEL_BASE : SITE_NAME_LABEL_BELOW_LG;
        const isVehicleArrivedPickup = deps.tripStatus === TripStatus.VehicleArrivedPickup;
        const getCustomCalloutAnchorY = () => {
          if (isPickupApproaching)  {
            return 12;
          }
          if (isVehicleArrivedPickup && deps.isAndroid) {
            return 8;
          }
          return 6;
        }
        return {
            id: 1,
            latitude: pickupSite.latitude,
@@ -171,7 +181,7 @@
                  }
                : {
                      customCallout: {
                          anchorY: [TripStatus.Publishing, TripStatus.CallingCar].includes(deps.tripStatus) ? 150 : -5,
                          anchorY: getCustomCalloutAnchorY(),
                          anchorX: 0,
                          display: 'ALWAYS'
                      }
@@ -229,7 +239,7 @@
                  }
                : {
                      customCallout: {
                          anchorY: -5,
                          anchorY: 6,
                          anchorX: 0,
                          display: 'ALWAYS'
                      }
@@ -356,7 +366,6 @@
            deps.isAndroid && [
                TripStatus.PublishRoute,
                TripStatus.VehicleApproaching,
                TripStatus.VehicleArrivedPickup,
                TripStatus.TripFinished,
                TripStatus.PaymentSuccess,
                TripStatus.CancelOnlyPickup
src/robotaxi/pages/share-trip/hooks/use-view-delegates.tsx
@@ -84,7 +84,7 @@
      : durationDisplay
  ), [durationDisplay]);
  const shouldHideAndroidMarkerCallout = deps.isAndroid
    && [TripStatus.PublishRoute, TripStatus.VehicleArrivedPickup, TripStatus.TripFinished, TripStatus.PaymentSuccess, TripStatus.CancelOnlyPickup].includes(deps.tripStatus);
    && [TripStatus.PublishRoute, TripStatus.TripFinished, TripStatus.PaymentSuccess, TripStatus.CancelOnlyPickup].includes(deps.tripStatus);
  const androidCarCalloutContent = useMemo(() => (
    <AndroidCarCalloutContent
      routeDistance={deps.routeDistance}
src/robotaxi/pages/share-trip/index.less
@@ -226,8 +226,8 @@
  // 车型图:参考 calling-car 的 calling-car-vehicle-pic,浮于背景图顶部右侧
  .calling-car-vehicle-pic {
    position: absolute;
    top: 24px;
    right: 24px;
    top: -4px;
    right: 116px;
    width: 342px;
    height: 294px;
    z-index: 10;
src/robotaxi/pages/site-search/components/favorite-popup/index.less
@@ -20,7 +20,7 @@
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.4);
    background: rgba(0, 0, 0, 0.8);
    z-index: 1;
  }
@@ -148,4 +148,4 @@
      z-index: 11;
    }
  }
}
}
src/robotaxi/utils/api-logger.ts
File was deleted
src/robotaxi/utils/login-agreement.ts
@@ -56,5 +56,5 @@
      ...normalized.slice(insertIndex),
    ];
  }
  return [...normalized, RIDE_NOTICE_ITEM];
  return [...normalized];
};