# 概要设计文档:Robotaxi 座椅通风加热 ## 0. 文档信息 | 项 | 内容 | |----|------| | 功能名称 | 座椅通风 / 加热(在空调温控面板内扩展) | | 所属模块 | Robotaxi 接驾界面(begin-route / 行程开始页) | | 目标分支 | `rt/feature/v2.0_ui_xj_730` | | 文档状态 | 概要设计(需求已对齐,待评审后进入编码) | | 关联能力 | 空调温控(`temperature-control.tsx`)—— 复用其 `setTemperature` / `airConditionerUnlock` 及温度区间/防抖逻辑 | | 参照文档 | `docs/车载香氛系统概要设计.md`(同一套接驾态远控范式) | --- ## 1. 需求背景 Robotaxi 接驾阶段(`TripStatus.VehicleApproaching`)已支持空调温控、车门/后备箱、车载香氛远控。本次在**空调能力基础上扩展座椅通风 / 加热**: - 交互稿将原空调卡片的内联温控,升级为「卡片展示 + 点击打开『温度调节』面板」 - 「温度调节」面板内**同时承载**:座椅通风/加热(顶部)+ 空调温度调节与开关(底部) - 空调卡片上仍保留**空调总开关**,可直接在卡片上交互 > 采用**方案 A**:重构空调卡片为「展示态 + 面板」,温控迁入面板(滚动温度选择器 + 面板内开关),座椅区位于面板顶部。空调下发逻辑**复用现有接口**。 --- ## 2. 需求概述 ### 2.1 卡片入口(接驾界面 · 左卡片 = 空调) ``` ┌─────────────┬─────────────┐ │ ❄ 空调 [开关]│ 智能香氛 │ │ 23.5 度 │ (右卡片) │ │ 调节温度|通风加热 │ │ └─────────────┴─────────────┘ ``` - 左卡片主体展示当前温度(如「23.5 度」),副标题「调节温度|通风加热」 - 卡片右上角保留**空调总开关 Switch**(复用现有开关下发逻辑,直接在卡片上可交互) - **点击卡片主体 → 打开「温度调节」底部弹窗面板** ### 2.2 温度调节面板(底部弹窗) ``` ┌───────────────────────────────┐ │ 温度调节 [ × ]│ ← 顶部:标题居中 + 关闭 ├───────────────────────────────┤ │ [车内俯视图 · 座椅区] │ │ ┌──────────┐ ┌──────────┐ │ │ │第二排左侧座椅 │ │第二排右侧座椅 │ │ │ │ ❋通风 ⌇加热 │ │ ❋通风 ⌇加热 │ │ ← 每座椅两个档位按钮 │ └──────────┘ └──────────┘ │ ├───────────────────────────────┤ │ 空调 │ │ …23.5 [24]℃ 24.5… │ ← 横向温度滚动选择器 │ ——● 空调开关 │ ← 面板内空调开关 └───────────────────────────────┘ ``` 面板自上而下分两区: 1. **座椅区**(本次新增):车内俯视图 + 按接口返回渲染的座椅,每座椅含「通风」「加热」两个档位按钮 2. **空调区**(迁移自现有卡片):横向温度滚动选择器 + 空调开关 ### 2.3 座椅区渲染逻辑 - **按接口返回的 `seats` 数组渲染**,兼容二排 / 三排(`position` 决定座位标签文案) - 每个座椅两个按钮:**通风**(风扇图标)、**加热**(热浪图标) - **能力可用性**(按档数字段): - `ventilationLevels === 0` → 该座椅**通风按钮置灰/不可点**(如三排不支持通风),仅保留加热按钮 - `heatingLevels === 0` → 加热按钮置灰/不可点 - 按钮上展示当前档位(如「通风 2」/ 关闭态展示「通风」或档位 0 的视觉) ### 2.4 档位循环与互斥(点击即时下发) **档位循环**:点击按钮在 `0 → 1 → 2 → … → N → 0` 间循环,上限 `N` 由该座椅对应的 `ventilationLevels` / `heatingLevels` 决定(0 = 关)。 - 例:`ventilationLevels=3` → 循环 `0→1→2→3→0` - 例:`heatingLevels=2` → 循环 `0→1→2→0` **互斥规则**:同一座椅通风与加热不能同时开启。 - 用户将**通风**调到非 0 档 → 该座椅**加热自动回退至 0** - 反之,将**加热**调到非 0 档 → 该座椅**通风自动回退至 0** **即时下发**(对齐香氛,无独立"应用"按钮): 1. 点击某座椅的通风/加热按钮 → 计算该座椅下一档位 + 互斥联动 2. **仅下发该被改动的座椅**,`ventilationLevel` 与 `heatingLevel` 一起下发 3. 下发期间该座椅按钮置灰防重复点击 4. **成功**:按钮档位更新,Toast 提示(文案见 §8) 5. **失败**:该座椅状态回滚到下发前,Toast「网络异常,请重试」,面板保持 ### 2.5 空调区(复用现有能力) - 温度调节:横向滚动选择器,区间 `15.5 ~ 32.5℃`、步进 `0.5`,**复用现有 `setTemperature` + 防抖(500ms)** - 空调开关:面板内开关与**卡片上开关状态同源**,复用 `airConditionerUnlock` - 温度/开关的初始化、连环单跳过、本地缓存等逻辑**沿用现有 `temperature-control` 实现**,本次不改其接口契约 --- ## 3. 接口契约 服务域 `vehicleConvoy`,路径前缀 `_ROBOTAXI_HOST`,段为 **`base`**(`vehicleConvoy/base/v1/...`)。鉴权:`needLogin=true`、`needSign=false`(对齐香氛/空调)。 > 响应遵循项目统一两层结构(见 `src/utils/request.ts`):`{ code, msg, data }`,`code===CODE_SUCCESS` 视为成功,`request` 默认解包返回第二层 `data`。 ### 3.1 查询座椅通风/加热状态 ``` GET /vehicleConvoy/base/v1/mini-program/cabin/seat/hv?orderId={orderId} ``` **请求参数**(query): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | orderId | string | 是 | 订单 ID | **响应示例**: ```json { "code": 200, "msg": null, "data": { "seats": [ { "position": "SECOND_ROW_LEFT", "ventilation": 1, "ventilationLevels": 3, "heating": 0, "heatingLevels": 3 }, { "position": "SECOND_ROW_RIGHT", "ventilation": 0, "ventilationLevels": 3, "heating": 3, "heatingLevels": 3 }, { "position": "THIRD_ROW_LEFT", "ventilation": 0, "ventilationLevels": 0, "heating": 0, "heatingLevels": 3 }, { "position": "THIRD_ROW_RIGHT", "ventilation": 0, "ventilationLevels": 0, "heating": 0, "heatingLevels": 3 } ] } } ``` **data.seats[] 字段说明**: | 字段 | 类型 | 说明 | |------|------|------| | position | string | 座位标识:`SECOND_ROW_LEFT` / `SECOND_ROW_RIGHT`(目前仅支持二排,前端兼容三排 `THIRD_ROW_*`) | | ventilation | int | 通风当前档位 0~3,0=关 | | ventilationLevels | int | 通风支持的总档数(如 3 即可设 0~3);0 表示不支持通风 | | heating | int | 加热当前档位 0~3,0=关 | | heatingLevels | int | 加热支持的总档数(如 3 即可设 0~3);0 表示不支持加热 | ### 3.2 设置座椅通风/加热 ``` POST /vehicleConvoy/base/v1/mini-program/cabin/seat/hv ``` **请求体**: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | orderId | string/number | 是 | 订单 ID | | seats | SeatSet[] | 是 | 本次改动的座椅集合(**仅传被改动座椅**) | | SeatSet.position | string | 是 | 座位标识 | | SeatSet.ventilationLevel | int | 是 | 目标通风档位 0~N | | SeatSet.heatingLevel | int | 是 | 目标加热档位 0~N(互斥:与 ventilationLevel 至多一个非 0) | **请求示例**(仅下发改动的座椅,通风与加热一起下发): ```json { "orderId": 123456, "seats": [ { "position": "SECOND_ROW_LEFT", "ventilationLevel": 2, "heatingLevel": 0 }, { "position": "THIRD_ROW_RIGHT", "ventilationLevel": 0, "heatingLevel": 3 } ] } ``` **响应示例**: ```json { "code": 200, "msg": null, "data": null } ``` **成功判定**:`code===CODE_SUCCESS` 即视为下发成功,`data` 可为 null。 --- ## 4. 交互流程 ### 4.1 主流程(进入接驾态 → 打开面板 → 点击座椅按钮即时下发) ``` 进入接驾态 (VehicleApproaching) │ ├─ 查询座椅状态 GET .../cabin/seat/hv(面板打开时或进入接驾态时) │ ├─ 成功 → 渲染座椅区,按 levels 决定档位上限与按钮可用性 │ └─ 失败 → 座椅区兜底(见 8) ├─ 空调温度/开关:沿用现有 temperature-control 初始化 │ 用户点击「空调」卡片(主体) → 打开「温度调节」面板 │ 用户点击某座椅的 通风/加热 按钮: │ ├─ 计算下一档位:curr → (curr+1) % (levels+1) ├─ 互斥联动:通风非0 ⇒ 加热=0;加热非0 ⇒ 通风=0 ├─ 该座椅按钮置灰防重复 ├─ POST .../cabin/seat/hv │ payload: { orderId, seats: [{ position, ventilationLevel, heatingLevel }] } │ ├─ 成功 → 该座椅档位更新 + Toast └─ 失败 → 该座椅状态回滚 + Toast"网络异常,请重试";面板保持 │ (空调温度/开关:复用现有下发逻辑,防抖 500ms) ``` ### 4.2 座椅按钮可用性分支 | 接口字段 | 通风按钮 | 加热按钮 | |----------|---------|---------| | ventilationLevels>0 & heatingLevels>0 | 可点,循环 0~V | 可点,循环 0~H | | ventilationLevels=0 | 置灰/隐藏 | 可点 | | heatingLevels=0 | 可点 | 置灰/隐藏 | | seats 为空 | 座椅区占位提示(见 8) | — | --- ## 5. 状态机 ### 5.1 单座椅档位循环 ``` 通风:0 → 1 → 2 → … → V → 0 (V = ventilationLevels) 加热:0 → 1 → 2 → … → H → 0 (H = heatingLevels) ``` ### 5.2 单座椅互斥联动(点击后、下发前计算) | 用户操作 | 联动结果 | 下发 payload(该座椅) | |----------|---------|------------------------| | 通风 0→非0 | 加热强制 0 | `{ ventilationLevel: n, heatingLevel: 0 }` | | 加热 0→非0 | 通风强制 0 | `{ ventilationLevel: 0, heatingLevel: n }` | | 通风 →0(关) | 加热保持 0 | `{ ventilationLevel: 0, heatingLevel: 0 }` | | 加热 →0(关) | 通风保持 0 | `{ ventilationLevel: 0, heatingLevel: 0 }` | ### 5.3 即时下发流程(单座椅) ``` [座椅按钮] --点击--> [该座椅 applying(置灰,禁点)] │ ┌─────────┴─────────┐ 成功 失败 │ │ 档位更新 + Toast 该座椅状态回滚 + Toast"网络异常,请重试" (面板保持) (面板保持) ``` --- ## 6. 组件结构 方案 A:新建独立座椅组件,空调卡片改造为「展示 + 面板」。座椅与空调温控同处「温度调节」面板。 ### 6.1 目录结构(建议) ``` src/robotaxi/pages/begin-route/components/calling-car-modules/ ├── temperature-control.tsx (改造:卡片展示态 + 卡片开关 + 打开面板) ├── climate-panel/ (新增:温度调节面板容器,可选) │ └── index.tsx 面板:座椅区 + 空调温度选择器 + 开关 ├── seat-hv-control/ (新增:座椅通风加热) │ └── index.tsx 座椅区渲染 + 档位循环 + 互斥 + 即时下发 └── ... src/robotaxi/pages/begin-route/components/css-folder/ ├── climate-panel.less (新增) └── seat-hv-control.less (新增) ``` > 面板容器 `climate-panel` 是否单独拆分可在编码时按复杂度决定;核心是座椅逻辑独立在 `seat-hv-control`。 ### 6.2 组件职责 | 组件/文件 | 职责 | |-----------|------| | `TemperatureControl`(改造) | 卡片展示当前温度 + 卡片空调开关 + 点击打开面板;温度/开关下发逻辑复用现有 | | 「温度调节」面板 | 承载座椅区 + 空调温度滚动选择器 + 面板内开关;底部弹窗动画复用 `use-bottom-popup-animation` | | `SeatHvControl`(新增) | 查询座椅状态、渲染座椅、档位循环、互斥联动、点击即时下发(仅改动座椅) | | `use-bottom-popup-animation` | 复用现有底部弹窗滑入/滑出动画 | ### 6.3 Props 设计(对齐空调/香氛) ``` type Props = { tripStatus: TripStatus; // 仅 VehicleApproaching 展示 orderId?: string; pageDidShowVersion?: number; // 页面 didShow 版本,触发重查 hasPreviousOrder?: boolean; // 连环单时不展示/不下发 } ``` ### 6.4 API 层 新增 `src/robotaxi/api/seat-hv.ts`: ``` querySeatHv(orderId) // GET .../cabin/seat/hv setSeatHv(payload) // POST .../cabin/seat/hv(seats 仅含改动座椅) ``` 类型新增 `src/robotaxi/api/types/seat-hv.ts`:`SeatHvItem`、`QuerySeatHvResponse`、`SetSeatHvPayload`、`SeatSetItem`、`SeatPosition`。 ### 6.5 座椅区视觉 - 车内俯视图:**纯 CSS 布局 + 占位背景图**,座椅按钮绝对定位叠放(设计稿未提供切图,先用占位图) - 座位标签文案按 `position` 映射:`SECOND_ROW_LEFT`→「第二排左侧座椅」等 --- ## 7. 埋点 沿用项目 xpReport(`this.$app.xpReportSend({ pageId, buttonId, para })`),接驾页 pageId 沿用 begin-route 现有值,buttonId 待与埋点侧确认,建议: | 事件 | 时机 | 建议 para | |------|------|-----------| | 打开温度调节面板 | 点击空调卡片 | `{}` | | 座椅通风点击下发 | 点击通风按钮 | `{ position, ventilationLevel }` | | 座椅加热点击下发 | 点击加热按钮 | `{ position, heatingLevel }` | | 座椅下发结果 | 成功/失败回调 | `{ position, ventilationLevel, heatingLevel, success }` | > 空调温度/开关埋点沿用现有实现,不在本次新增范围。 --- ## 8. 异常处理与边界 | 场景 | 处理 | |------|------| | 查询座椅状态失败 | 座椅区兜底:展示占位提示(如「座椅调节暂不可用」)或空态;不阻塞空调温控 | | seats 为空 | 座椅区展示占位提示,不渲染座椅按钮 | | ventilationLevels=0 | 该座椅通风按钮置灰/隐藏,仅保留加热 | | heatingLevels=0 | 该座椅加热按钮置灰/隐藏,仅保留通风 | | 下发接口失败/超时 | 该座椅状态回滚到下发前 + Toast「网络异常,请重试」+ 面板保持 | | 下发进行中重复点击 | 该座椅 `applying` 期间按钮置灰禁点,防重复下发 | | 连环单(hasPreviousOrder=true) | 空调卡片与座椅区均不展示/不查询/不下发(对齐空调,避免影响上一单乘客) | | 非接驾态(tripStatus≠VehicleApproaching) | 组件返回 null,不展示 | | 面板打开期间订单切换 | 按 orderId 重新查询并刷新状态,避免串单 | --- ## 9. 落地文件清单 | 文件 | 类型 | 说明 | 落地 | |------|------|------|------| | `src/robotaxi/api/seat-hv.ts` | 新增 | `querySeatHv` / `setSeatHv` | ✅ | | `src/robotaxi/api/types/seat-hv.ts` | 新增 | 座椅通风加热请求/响应类型 | ✅ | | `.../calling-car-modules/seat-hv-control/index.tsx` | 新增 | 入口 + 座椅面板 + 档位循环 + 互斥 + 即时下发 | ✅ | | `.../css-folder/seat-hv-control.less` | 新增 | 座椅区样式(俯视图 + 按钮) | ✅ | | `.../components/calling-car.tsx` | 改 | 挂载 ``(空调卡片下方) | ✅ | > **本期实现说明**:座椅通风加热作为**独立组件 `SeatHvControl`** 落地(含自带入口 + 底部弹窗面板),未改动已上线的 `temperature-control.tsx`,降低回归风险。温度选择器/空调开关合并进「温度调节」面板(方案 A 完整形态)作为**后续迭代**,待设计切图到位后再合并;当前空调仍沿用现有内联卡片,座椅入口以独立一行挂在其下。 --- ## 10. 已决策事项(需求对齐结论) 1. **方案 A**:空调卡片改为「展示 + 点击打开『温度调节』面板」,卡片上保留空调总开关;座椅区在面板内。 2. **座椅渲染**:按接口返回渲染,兼容二/三排;`ventilationLevels=0` 通风置灰/隐藏仅留加热。 3. **档位循环**:`0→1→…→N→0`,上限 N 由该座椅 `ventilationLevels`/`heatingLevels` 决定。 4. **互斥**:同座椅通风与加热至多一个非 0;开一方则另一方自动归 0,一起下发。 5. **下发范围**:仅下发被改动的座椅(`seats` 数组含该座椅 ventilation+heating 两字段)。 6. **下发时机**:点击按钮即时下发,无独立"应用"按钮。 7. **空调下发**:复用现有 `setTemperature` / `airConditionerUnlock` 及温区/防抖逻辑。 8. **座椅俯视图**:纯 CSS 布局 + 占位背景图 + 绝对定位。 9. **接口鉴权**:`needLogin=true`、`needSign=false`。 --- ## 11. 待确认项(进入编码前建议对齐) 1. 埋点 pageId / buttonId 的具体数值(与埋点侧对齐)。 2. 座椅区俯视图、通风/加热图标、档位视觉(不同档位的视觉呈现)的正式设计切图与色值。 3. 下发成功后的 Toast 文案(如「已设置第二排左侧座椅通风2档」是否需要,或统一「设置已应用至车辆」)。 4. 温度滚动选择器的具体交互(是否复用现有步进逻辑仅换 UI,还是全新滚动组件)——本期倾向复用现有下发逻辑,仅调整展示。 5. 查询座椅状态失败时座椅区的确切占位文案。