概要设计文档:Robotaxi 车载香氛系统

0. 文档信息

项 内容
功能名称 车载香氛系统(智能香氛远程控制)
所属模块 Robotaxi 接驾界面(begin-route / 行程开始页)
目标分支 rt/feature/v2.0_ui_xj_730
文档状态 概要设计(需求已对齐,待评审后进入编码)
关联能力 空调温控(temperature-control.tsx)—— 本功能与之左右并排,复用同一套远控范式

1. 需求背景

Robotaxi 接驾阶段(车辆即将到达,TripStatus.VehicleApproaching)已支持空调温控、车门/后备箱远程控制。本次新增 车载香氛 远程控制能力,让乘客在上车前即可通过小程序开启/关闭车内香氛并选择香型,提升乘车体验。

香氛能力与空调对齐:
- 同为「接驾态可操作」的车机远控
- 同样抽取为**独立组件**,挂载在接驾卡片区
- UI 上与空调卡片**左右并排**(左空调、右香氛)


2. 需求概述

2.1 卡片入口(接驾界面)

接驾卡片区采用**左右双卡片**布局:

┌─────────────┬─────────────┐
│  空调温控     │  智能香氛     │
│ (左卡片)     │ (右卡片)     │
│             │  主标题:智能香氛 │
│             │  副标题:木质雪松 / 已关闭 │
└─────────────┴─────────────┘
  • 右卡片为「智能香氛」入口卡片(金色渐变,左上角「香氛」标签 + 图标)
  • 卡片主标题由查询接口返回的 switchOn + currentScent 映射:
  • switchOn=false → 已关闭
  • switchOn=true → 当前香型名称(如 香杉影木)
  • 卡片副标题为固定文案(如 车载香氛·畅享出行),右下角箭头 >
  • 点击右卡片 → 切换面板开合(再次点击卡片可关闭面板)

2.2 控制面板(底部弹窗)

面板以底部弹窗形式呈现,覆盖接驾界面下半部分。**面板无总开关 Switch、无「应用到车辆」按钮**,改为「点击香型即时下发」:

┌───────────────────────────────┐
│           车内香氛           [ × ]│  ← 顶部:标题居中 + 关闭
├───────────────────────────────┤
│  ┌──────┐ ┌──────┐ ┌──────┐    │
│  │ 香杉  │ │ 白色  │ │ 琥珀  │    │  ← 香型卡片(竖排名 + 香调描述)
│  │ 影木  │ │ 清幽  │ │ 光橘  │    │
│  │沉稳·笃定│ │清新·淡雅│ │愉悦·活力│  │
│  └──────┘ └──────┘ └──────┘    │
└───────────────────────────────┘

香型选择区展示逻辑:
- 车端返回的 availableScents:正常显示、可点击
- 车端未返回的香型:不展示
- availableScents 为空:显示占位文案「当前车辆不支持香型选择」
- 香调描述(如「沉稳·笃定」):接口 desc 优先,缺失时按 id 走前端本地兜底表

当前生效态:
- switchOn=true 时,currentScent 命中的香型卡片高亮(米金底 + 金边)
- switchOn=false 时,无高亮

2.3 点击香型即时下发(无独立按钮)

用户**点击香型卡片即触发下发**,无需二次确认:

  1. 点击**非当前生效**香型 → 开启香氛并切换到该香型(switchOn=true + channelId)
  2. 点击**当前生效(已高亮)**香型 → 关闭香氛(switchOn=false,channelId 可不传)
  3. 下发期间禁止重复点击(applying 锁 + 香型卡片置灰)
  4. 成功:
  • Toast:开启 设置已应用至车辆 / 关闭 已关闭香氛
  • 面板保持打开,高亮态更新为最新
  • 「智能香氛」卡片主标题更新(香型名 / 已关闭)
  1. 失败:
  • Toast:网络异常,请重试
  • 状态回滚到下发前,面板保持,用户可重试

关闭方式:① 点击已选中香型关闭香氛功能;② 点击关闭 X 或蒙层收起面板;③ 再次点击入口卡片收起面板。


3. 接口契约

服务域 vehicleConvoy,路径前缀 _ROBOTAXI_HOST。注意本功能接口为 base 段(vehicleConvoy/base/v1/...),与空调(vehicleConvoy/api/v1/...)不同。鉴权:needLogin=true、**needSign=false**(对齐空调,不做签名)。

响应遵循项目统一两层结构(见 src/utils/request.ts):{ code, msg, data },code===CODE_SUCCESS(200/0) 视为成功,request 默认解包返回第二层 data。

3.1 查询香氛状态

GET  /vehicleConvoy/base/v1/mini-program/cabin/fragrance?orderId={orderId}

请求参数(query):

字段 类型 必填 说明
orderId string 是 订单 ID

响应示例:

{
  "code": 200,
  "msg": null,
  "data": {
    "fragrance": {
      "switchOn": true,
      "currentScent": "SANDALWOOD",
      "availableScents": [
        { "channelId": "1", "id": "SANDALWOOD", "name": "香杉影木" },
        { "channelId": "2", "id": "WHITE", "name": "白色清幽" },
        { "channelId": "3", "id": "AMBER", "name": "琥珀光橘" }
      ]
    }
  }
}

data 字段说明:

字段 类型 说明
fragrance.switchOn boolean 香氛总开关的真实状态(决定 Switch 初始态)
fragrance.currentScent string 当前香型的 id(用于匹配默认选中项);关闭时可为空
fragrance.availableScents Scent[] 车端可用香型列表;空数组 = 不支持香型选择
Scent.channelId string 香型通道 ID,**下发时使用此字段**
Scent.id string 香型业务标识(与 currentScent 对应)
Scent.name string 香型展示名称

3.2 设置香氛

POST /vehicleConvoy/base/v1/mini-program/cabin/fragrance

请求体:

字段 类型 必填 说明
orderId string/number 是 订单 ID
switchOn boolean 是 目标开关状态
channelId string 否 目标香型通道 ID;switchOn=true 时必传,switchOn=false 时可不传

请求示例:

{ "orderId": 123456, "channelId": "1", "switchOn": true }

响应示例:

{ "code": 200, "msg": null, "data": null }

成功判定:code===CODE_SUCCESS(HTTP 200 且业务码成功)即视为下发成功,data 可为 null。


4. 交互流程

4.1 主流程(进入接驾态 → 打开面板 → 点击香型即时下发)

进入接驾态 (VehicleApproaching)
   │
   ├─ 查询香氛状态 GET .../cabin/fragrance
   │     ├─ 成功 → 卡片主标题按 switchOn+currentScent 映射
   │     │         (关→"已关闭" / 开→香型名);生效香型高亮
   │     └─ 失败 → 按"已关闭"兜底(见 8.异常处理)
   │
用户点击「智能香氛」卡片
   │
   ├─ 底部弹窗滑入(复用 use-bottom-popup-animation)
   ├─ 香型区渲染 availableScents;生效香型高亮
   │
用户点击某香型卡片(即时下发):
   │
   ├─ 点非生效香型 → nextSwitchOn=true, channelId=该香型
   ├─ 点已生效(高亮)香型 → nextSwitchOn=false(关闭香氛)
   ├─ 下发期间置灰防重复点击
   ├─ POST .../cabin/fragrance
   │     payload: { orderId, switchOn, channelId? }
   │
   ├─ 成功 → 高亮态更新 + 卡片主标题更新
   │         + Toast(开:"设置已应用至车辆" / 关:"已关闭香氛");面板保持
   └─ 失败 → 状态回滚 + Toast"网络异常,请重试";面板保持

4.2 香型选择区展示分支

车端返回 展示
availableScents 非空 渲染各香型,可点击选择
availableScents 为空 占位文案「当前车辆不支持香型选择」,隐藏香型项
未在 availableScents 中的香型 不展示

5. 状态机

5.1 点击香型的即时下发(无 Switch、无独立按钮)

点击对象 下发 payload 结果
非当前生效香型 { switchOn: true, channelId } 开启香氛并切换到该香型,该卡片高亮
当前生效(高亮)香型 { switchOn: false } 关闭香氛,取消高亮
下发进行中(applying) — 忽略点击(置灰防重复)

5.2 即时下发流程

[香型卡片] --点击--> [applying(置灰,禁点)]
                          │
                ┌─────────┴─────────┐
              成功                  失败
                │                   │
   高亮/标题更新+Toast        状态回滚+Toast"网络异常,请重试"
   (开:"设置已应用至车辆"       (面板保持)
    关:"已关闭香氛")
   面板保持

5.3 卡片主标题状态映射

数据来源 主标题文案
switchOn=false 已关闭
switchOn=true 当前生效香型 name(如 香杉影木)
查询失败/无数据 按「已关闭」兜底,主标题展示 已关闭(见 8)

6. 组件结构

对齐空调组件(temperature-control.tsx)的独立组件模式,香氛抽取为**独立可复用组件**。

6.1 目录结构(建议)

src/robotaxi/pages/begin-route/components/calling-car-modules/
├── temperature-control.tsx              (现有:空调,左卡片)
├── fragrance-control/                   (新增:香氛,右卡片)
│   └── index.tsx                        主组件(卡片入口 + 面板 + 即时下发编排)
└── ...
src/robotaxi/pages/begin-route/components/css-folder/
└── fragrance-control.less               (新增:香氛样式)

6.2 组件职责

组件/文件 职责
FragranceControl(index) 卡片入口渲染、主标题状态、面板开合、进入接驾态查询、点击香型即时下发
use-bottom-popup-animation 复用现有底部弹窗滑入/滑出动画

6.3 Props 设计(对齐空调)

type Props = {
  tripStatus: TripStatus;          // 仅 VehicleApproaching 展示
  orderId?: string;
  pageDidShowVersion?: number;     // 页面 didShow 版本,触发重查
  hasPreviousOrder?: boolean;      // 连环单时不展示/不下发
}

6.4 挂载点

在 calling-car.tsx 车辆信息卡片区,将空调与香氛**左右并排**:
- 左:<TemperatureControl />(现有)
- 右:<FragranceControl />(新增)

布局改造需同步调整 css-folder/calling-car.less 的卡片容器为左右布局。可参考旁支 75689edaa 的 temperature-fragrance-control 左右双卡片样式作为视觉参照(该旁支未合入,仅作参考)。

6.5 API 层

在 src/robotaxi/api/ 下新增香氛接口(建议 api/car-control.ts 追加,或新建 api/fragrance.ts):

queryCabinFragrance(orderId)      // GET  .../cabin/fragrance
setCabinFragrance(payload)        // POST .../cabin/fragrance

类型定义追加至 api/types/begin-route.ts(或新建 api/types/fragrance.ts):CabinFragranceResponse、FragranceScent、SetFragrancePayload。


7. 埋点

沿用项目 xpReport 双系统(this.$app.xpReportSend({ pageId, buttonId, para })),接驾页 pageId 沿用 begin-route 现有值,buttonId 待与埋点侧确认,建议:

事件 时机 建议 para
香氛卡片点击 打开/关闭面板 { switchOn }
香型点击下发 点击某香型卡片即时下发 { channelId, switchOn }
下发结果 成功/失败回调 { switchOn, channelId, success }

具体 pageId / buttonId 数值需与埋点管理侧对齐后补充。


8. 异常处理与边界

场景 处理
查询香氛状态失败 按「已关闭」兜底:卡片主标题展示 已关闭,无高亮;不阻塞其他卡片
availableScents 为空 香型区展示「当前车辆不支持香型选择」,无可点击香型
currentScent 不在 availableScents 开启态回退取列表第一项作为生效香型
下发接口失败/超时 状态回滚到下发前 + Toast「网络异常,请重试」+ 面板保持
连环单(hasPreviousOrder=true) 不展示香氛卡片、不查询、不下发(对齐空调,避免影响上一单乘客)
非接驾态(tripStatus≠VehicleApproaching) 组件返回 null,不展示
下发进行中重复点击 applying 期间香型卡片置灰禁点,防重复下发
面板打开期间订单切换 按 orderId 重新查询并刷新状态,避免串单

9. 落地文件清单

文件 类型 说明 落地
src/robotaxi/api/fragrance.ts 新增 queryCabinFragrance / setCabinFragrance ✅
src/robotaxi/api/types/fragrance.ts 新增 香氛请求/响应类型 ✅
.../calling-car-modules/fragrance-control/index.tsx 新增 香氛卡片入口 + 底部弹窗面板主组件 ✅
.../css-folder/fragrance-control.less 新增 香氛卡片与面板样式 ✅
.../components/calling-car.tsx 改 挂载 <FragranceControl />,与空调左右并排 ✅
.../css-folder/calling-car.less 改 新增 .car-card-comfort 左右布局容器 ✅
src/robotaxi/components/demo/index.tsx 改(可选) Demo 页增加香氛卡片渲染,便于联调 待定

面板与卡片入口合并在单一 index.tsx(面板结构简单,未单拆 fragrance-panel.tsx)。


10. 已决策事项(需求对齐结论)

  1. 卡片布局:接驾卡片区采用左右双卡片,左=空调、右=香氛。
  2. 下发字段:channelId(非 scentId/id)。请求体 { orderId, channelId, switchOn }。
  3. 香型耗尽逻辑:本期忽略,默认选中 availableScents 第一项。
  4. 关闭态下发:switchOn=false,channelId 可不传。
  5. 副标题映射:关→已关闭,开→当前香型名。
  6. 组件化:抽取为独立组件(对齐空调 temperature-control 模式),便于复用与挂载。
  7. 接口鉴权:needLogin=true、needSign=false(对齐空调,不做签名)。
  8. 查询失败兜底:按「已关闭」处理,副标题展示 已关闭,面板 Switch 关闭态。
  9. 文档范围:本次仅概要设计,不含编码。

11. 待确认项(进入编码前建议对齐)

  1. 埋点 pageId / buttonId 的具体数值(与埋点侧对齐)。
  2. 香型选择区的视觉样式(单选圆点 / 卡片选中态)与设计稿对齐。