# 房务管家（订单详情 / 抢单池）渲染缺口 + 抢单门控（#4237，后端 PR #4239）

> 变更类型：🐛 后端数据已齐全 → 前端渲染对齐 + 交互门控
> 端类型：房务管家（housekeeper）前端（订单详情弹窗 + 抢单池列表）
> 日期：2026-06-22
> 工单：#4237 ｜ 服务：hl-order-service-v3（数据已就绪）

---

## 总览

wx 评审房务管家发现多处「前端渲染不全 / 交互缺门控」。**后端数据基本都齐全**（段×候选完整结构、productName、claim 信息），下列 1-3 项前端按完整结构渲染即可，**可立即接**。另有进度条 6 态 + 留言计数两项后端在改（PR #4239，合并部署后出字段更新版）。

## 1. 抢单前隐藏「联系定制师 / 给定制师留言」（可立即接）

**规则**：抢单后才能和定制师聊天（聊天 = 留言）。订单还在抢单池（未被本房务抢单）时，**不显示**「联系定制师」「给定制师留言」入口（订单详情顶部两个按钮 + 内部留言区的「给定制师留言」）。

**前端**：按订单详情的 `claim.isMine`（未抢单为 false）门控——未抢单 → 隐藏/禁用这些入口；抢单后 → 显示。

## 2. 抢单池列表补「产品名」（可立即接）

**现状**：列表只显挡位名（如"舒适·N晚"），无产品名。后端 pool VO 已含 `productName`（如"测试核心产品-单档-固定比例"）。
**前端**：列表行补显 `productName`（产品名 + 挡位，别只显挡位名）。

## 3.「定制师需求」按完整段×候选结构渲染（可立即接）

**现状**：① 内部留言 tab 的「定制师需求」② 配房行程行 —— 都只显 `第N晚 · X间 · 房型 · ¥价 · 备注` 的简化视图，**漏候选酒店 + 各候选备注 + 段备注**（例：DAY1 实有候选 呼伦贝尔香格里拉(备注777555) / 海拉尔嘉世豪(备注77777222)，现只显一个备注、无酒店）。
**后端已就绪**：`requirement.current.days[].segments[]` = `{roomCategory, roomCount, budget, remark(段备注), candidates:[{hotelId, hotelName, remark(候选备注)}]}`（房务侧 RequirementSnapshot，#4128 起完整；配房行程的 DayDetail 同样含 segments / hotels）。
**前端**：按完整段×候选渲染——每段 `房型 · X间 · ¥预算 · 候选(择一)：酒店A(候选备注..) / 酒店B(候选备注..) · 段备注`，对齐 admin 侧（#4220）。内部留言「定制师需求」与配房行程行都补全。

**✅ 实测复证（本单 HL20260620152849917，房务侧端点 `GET /admin/house/orders/{orderId}` → `data.requirement.current.days[]`）**：候选酒店字段后端**已返齐**，房务侧与管理后台定制师侧『行程安排 → 住宿安排』**同源同数据**，前端照渲即可一致：

```
DAY1 普通标间 ×1 ¥380：candidates = [呼伦贝尔香格里拉大酒店(remark 777555), 海拉尔嘉世豪酒店(remark 77777222)]
DAY2 俄式标准房 ×1 ¥280：candidates = [恩和瓦西里民宿, 呼伦贝尔香格里拉大酒店]
```

> 取值：每段 `segments[].candidates[].hotelName`（+ `.remark` 候选备注）；兼容字段 `days[].hotels[]` 同数据。**验收目标 = 房务管家「内部留言→定制师需求」渲染与管理后台定制师侧「住宿安排」逐段逐候选一致**。

## 4-5. 进度条 6 态 + 内部留言计数（✅ PR #4239 已合并部署测试服 + API 实测验证，可接）

- **进度条**：原 4 步硬编码「接单/配房中/待最终确认/已最终确认」+ 待配房单误显「配房中」。后端已改由房务侧 6 态驱动，字段契约：
  - `progress.steps[].label` 校正为固定 4 步「待配房 / 配房中 / 待最终确认 / 已确认」，`completed` 按是否越过该步。
  - `progress.currentStep`：待配房=1 / 配房中(含询房中)=2 / 待最终确认=3 / 已确认=4。
  - **新增** `progress.houseStatus`（6 态枚举 name：`PENDING_CLAIM`/`CLAIMING`/`IN_INQUIRY`/`PENDING_FINALIZE`/`CONFIRMED`/`EXCEPTION`）+ `progress.houseStatusLabel`（中文，含 4 步未单列的 **询房中 / 异常**）。**前端进度条按这两个权威字段渲染，别再用旧硬标签**。
  - 实测（抢单池单 `GET /admin/house/orders/{id}`）：`houseStatus=PENDING_CLAIM, houseStatusLabel=待配房, currentStep=1, steps[0].completed=false`。
- **内部留言计数**：`tabCounts.messageCount` 由恒 0 改为计入定制师需求（=1）。实测 `messageCount=1`。
- **H 留言空态对齐（前端）**：既然口径是「定制师需求算 1 条留言」(messageCount=1)，内部留言区**别再显「该订单暂无内部留言」**（与 count=1 自相矛盾，且该文案是前端写死，后端不返）。正确：把「定制师需求」当作**第 1 条留言**展示；真正空态只针对**抢单后**房务↔定制师聊天那部分——抢单前聊天关闭，可显「抢单后可与定制师沟通」之类引导而非笼统「暂无内部留言」。
- **H2 抢单池「抢单后可与定制师沟通」门控保持不变（与定制师留言不冲突）**：定制师在抢单前可**单向留言**，这些留言由后端压在 pending、**房务抢单那刻自动整批送达抢到的人**（带未读角标 + SSE）。房务在抢单池里未接单、无身份，先不聊是对的；**抢单后**这些留言会出现在房务↔定制师聊天里。聊天气泡的「已读/留言/已送达」显示口径见 **`22_4223` §10/§11**（后端原无「已读」字段，抢单前标「留言」；#4289 已补真已读 `readByPeer`，抢单后 true=「已读」/false=「已送达」，**别写死「已读」**）。

## 8.「内部留言」框显示定制师留言（✅ #4288 已上线测试服 + API 实测，可接）

**现状（你 Image #28 反馈「有留言没显示」）**：房务侧订单详情「内部留言」tab 只显「定制师需求」+「抢单后可与定制师沟通」占位，**定制师抢单前发的留言不显示**（你 Image #29 定制师侧已显 2 条留言，房务侧却看不到）。

**后端已补（实测 `GET /admin/house/orders/{orderId}`）**：详情 **新增顶层 `messages` 列表**（抢单前定制师留言），`tabCounts.messageCount` 由恒 1 改为 **`1(定制师需求) + N(留言)`**。**抢单池/未抢单也返**——房务在池里浏览即见定制师留了什么话。

**字段契约**：
```
data.messages: [ { messageId(String雪花), senderName, senderRole, content, sentAt("yyyy-MM-dd HH:mm:ss"), msgType } ]
data.tabCounts.messageCount = 1 + messages.length
```
**实测**（订单 2068234602970828802，抢单池态）：`messageCount=3`，`messages`=[「你好 测试消息」14:05、「确认单发我一下」14:32]，与定制师侧 Image #29 一致。

**前端**：「内部留言」框 = 第 1 条「定制师需求」（沿用现有 requirement 渲染）+ 下面按 `messages` 逐条渲染留言（`senderName` + `content` + `sentAt`）。`senderRole` 抢单前留言可能为 null（恒定制师，按「定制师」渲染即可）。抢单后这些留言并入房务↔定制师聊天，气泡已读口径见 `22_4223 §11`。

## 6. 操作日志「操作人」别打整串 JSON，渲染 `operator.name`（前端 bug，可立即接）

**现状**（订单详情弹窗 → 操作日志 Tab）：操作人显示成
`by { "userId": "2037350531801993218", "name": "腰苏图" }`
—— 把后端返回的**结构化 `operator` 对象**整个 `JSON.stringify` 打了出来。

**后端正确**（实测 `GET /v3/admin/house/orders/{orderId}/operation-log` → `data.records[]`）：每条 `operator` 是结构化对象 `{ userId, name }`，`operator.name` 已是干净姓名「腰苏图」：

```json
{
  "opType": "REQUIREMENT_SUBMIT",
  "opTypeLabel": "定制师提交需求",
  "summary": "定制师提交房型需求 v1",
  "operator": { "userId": "2037350531801993218", "name": "腰苏图" }
}
```

**前端**：操作人只渲染 `operator.name`（→「腰苏图」/「by 腰苏图」），**别 `JSON.stringify(operator)`、别直接把对象插进模板**。`operator.userId` 仅用于跳详情/排查，不展示。其余照常：`summary` 后端已拼好直接显示、`opTypeLabel` 当中文徽标、`time` 当时间。

## 7. 配房行程逐日城市改显区县（✅ 后端 #4264 已上线，前端无需改动）

**现状/变更**：配房行程 Tab 每天行的城市原来显**市级**（呼伦贝尔市），现后端改为返**区县**（海拉尔区/陈巴尔虎旗）。来源 = 订单产品快照 `dismissalPlace.districtName`（下单冻结）→ 资源酒店 `district` 兜底 → 市级 city 兜底。

**字段契约**：`itinerary[].cityName` = **区县**（解析得到时；否则回退市级，绝不空）；`itinerary[].cityCode` = **市级**（向后兼容，若前端某处按市级筛选/分组用这个）。

**前端**：继续渲染 `cityName` 即自动得区县，**无需改动**。实测（`GET /admin/house/orders/{orderId}`）：DAY1 cityName=海拉尔区、DAY2 cityName=陈巴尔虎旗，cityCode 均=呼伦贝尔市。

---

## 说明（非 bug）
- 抢单池列表「呼伦贝尔市」= 后端按候选酒店 hotelId 解析中文城市去重回填（#3172），非已下线的手填城市字段。是否保留待产品定。
