# 订单详情「联系车务」独立未读红点

> 日期：2026-07-23
> 工单：HL #5180
> 影响范围：管理后台订单详情 → 行程安排 → 用车安排；聊天 SSE 实时状态
> 状态：前端待处理

## 1. 问题与口径

车务通过 `FLEET:{orderId}` 会话给定制师发送消息后，顶部全局铃铛能显示未读，但当前订单「联系车务」按钮没有订单维度红点。房务按钮已有同款能力，本次车务必须复用相同的角标样式、实时刷新和已读清零交互。

房务与车务未读是两个独立业务会话，禁止继续共用一个字段：

- `unreadMessageCount`：当前登录定制师在本订单 `HOUSE:{orderId}` 会话的未读数，只供「联系房务」使用。
- `fleetUnreadMessageCount`：当前登录定制师在本订单 `FLEET:{orderId}` 会话的未读数，只供「联系车务」使用。
- 顶部铃铛全局未读只能说明“存在未读”，不能作为当前订单按钮是否亮红点的判断依据。

## 2. 接口变更

```http
GET /v3/admin/order/{id}/itinerary
```

响应新增：

```json
{
  "unreadMessageCount": 0,
  "fleetUnreadMessageCount": 2,
  "canContactFleet": true,
  "contactFleetDisabledReason": null
}
```

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `unreadMessageCount` | Integer | HOUSE 订单会话未读；既有字段，语义不变 |
| `fleetUnreadMessageCount` | Integer | FLEET 订单会话未读；新增字段；无会话、未登录或软依赖降级时为 `0` |

两个字段必须分别消费，不能用 `fleetUnreadMessageCount || unreadMessageCount` 一类兜底混用，否则房务消息会错误点亮车务按钮。

后端内部取数同时增加可选 `unreadScope`：

- FLEET 不传或传 `TEAM`：保持车务看板既有团队共享未读口径。
- FLEET 传 `PERSONAL`：按指定 `adminId` 返回车务发给该定制师的个人未读；订单详情的 `fleetUnreadMessageCount` 使用此口径。
- HOUSE：仍按成员行个人未读统计，行为不变。

该字段属于 order-v3 → user-service 内部契约，管理后台无需直接传递。

## 3. 前端实现要求

### 3.1 按钮角标

`VehicleArrangeCard.vue` 的「联系车务」按钮按 `RoomArrangeCard.vue` 原样复用 `NBadge`：

- `fleetUnreadMessageCount > 0` 时显示红色数字角标。
- `max=99`，`0` 自动隐藏。
- 按钮禁用时仍可保留未读提示，不能因为 `canContactFleet=false` 静默吞掉既有会话未读；是否允许重新开会话继续遵守后端门控。
- 样式、偏移、尺寸与「联系房务」保持一致，不新增另一套红点 CSS。

`v3Adapter.js` 需把行程接口的 `fleetUnreadMessageCount` 映射为独立本地字段（建议 `itineraryFleetUnreadCount`），不要覆盖现有 `itineraryUnreadCount`。

### 3.2 SSE 实时刷新

订单详情现有 `lastChatSignal` 监听只匹配 `HOUSE:{orderId}`。需要同时支持：

```text
HOUSE:{orderId} -> 刷新联系房务角标
FLEET:{orderId} -> 刷新联系车务角标
```

收到当前订单的 `FLEET:{orderId}` `im-chat` / `im-chat-read` 信令后，轻量重拉行程接口并只合并 `fleetUnreadMessageCount`；不能整页闪骨架屏，也不能把其他订单的全局未读数套到当前订单。

### 3.3 已读清零与竞态

- 打开「联系车务」并收到聊天抽屉 `read` 事件后，立即把当前订单车务角标本地清零。
- 房务已读只清 HOUSE 字段，车务已读只清 FLEET 字段，互不影响。
- 复用房务现有 `chatReadEpoch`（或等价版本号）防竞态：已读期间较早发出的刷新请求返回时，不得把旧未读数重新覆盖成红点。
- 切换订单时按新 `orderId` 重算，不能沿用上一个订单角标。

## 4. 验收场景

- [ ] 当前订单无未读时，「联系车务」不显示角标。
- [ ] 车务给本单定制师发送 1 条消息后，不刷新页面，顶部铃铛和本单「联系车务」都立即显示红点/数字 `1`。
- [ ] 当前订单无车务未读、其他订单有车务未读时，顶部铃铛可亮，但当前订单「联系车务」不亮。
- [ ] 当前订单只有房务未读时，只点亮「联系房务」，不得点亮「联系车务」。
- [ ] 打开本单车务会话并读完后，「联系车务」角标立即消失，顶部铃铛与站内信列表同步收敛。
- [ ] 已读操作与 SSE 刷新并发时，旧请求不会让已清零红点复现。
- [ ] 角标样式、最大数字和按钮布局与房务模块一致。

## 5. 兼容性

新增响应字段为加性变更。未接入新字段的旧前端行为不变；前端接入后仍使用既有 `POST /admin/message/chat/open-fleet` 打开会话，`orderId` 必须使用数字雪花字符串，不能使用 `HL...` 展示号。

## 6. 2026-07-23 车务看板回归补充

本节针对车务管理员在「派单看板」点击「联系定制师」的反向会话入口。它使用车务团队
`TEAM` 未读口径，不得复用定制师订单详情的 `PERSONAL` 字段。

### 6.1 首次点击必须立即打开真实会话

当前 `FleetBoard` 在首次点击时同一轮设置 `chatOrder` 和 `chatOpen=true`，随后通过
`v-if="chatOrder"` 首次挂载 `ChatDrawer`。`ChatDrawer` 对 `props.show` 的 watcher 没有
立即执行，因此组件以 `show=true` 首次挂载时不会调用 `open-fleet`，只显示默认「对端」
和空线程；关闭后第二次发生 `false -> true` 才会正常调用接口。

前端需要修复该生命周期缺口：

- `ChatDrawer` 首次挂载且 `show=true` 时必须执行一次 `openFlow()`；建议在现有合并 watcher
  保留 `flush: 'post'` 并增加 `immediate: true`，或采用等价的挂载处理。
- 首次点击只能调用一次 `POST /admin/message/chat/open-fleet`，不能因 `show/bizId` 同轮变化
  重复打开或让后一个请求取消前一个请求。
- 首次接口响应后立即展示真实 `peerName/peerRoleLabel/thread`；加载完成前保持 loading，
  不能先落成可交互的「对端」空会话。
- `show=false` 首次挂载不得调用打开接口；之后每次 `false -> true` 仍只调用一次。

### 6.2 车务看板未读角标必须实时刷新

车务看板列表、密集视图和详情抽屉虽然已经消费 `unreadMessageCount`，但当前只订阅
`useFleetDispatchRefresh` 的派车业务信令，没有订阅聊天总线 `lastChatSignal`。因此定制师
发来 `FLEET:{orderId}` 新消息后只能整页刷新才出现角标。

前端需要按房务模块的方式补齐：

- 监听全局 `lastChatSignal`，仅处理当前页订单的 `FLEET:{orderId}` `im-chat/im-chat-read`
  信令；其他模块和其他订单不得误刷新角标。
- 信令只表示“数据变化”，不能把顶部铃铛的合并未读数直接写进订单。应轻量重拉当前筛选/
  分页的车务看板订单接口，并只合并对应行的 `unreadMessageCount`。
- 轻量刷新不得切换全页 loading、闪白、重置筛选、分页或滚动位置；短时间连续信令需要合并。
- 列表 `orders`、当前 `activeOrder`、当前 `chatOrder` 的同一订单计数必须一起收敛。
- 打开会话收到 `read` 后立即本地清零，并使用读态纪元/刷新序号防止较早发出的异步刷新把
  旧未读数重新覆盖回来。
- 聊天抽屉正在打开当前会话时由 `ChatDrawer` 实时拉消息并标已读；看板 watcher 不得与其
  抢写非零计数。

### 6.3 回归验收

- [ ] 清缓存后首次点击任一订单「联系定制师」，只发起一次 `open-fleet`，直接显示真实定制师及历史消息，不出现「对端」空会话。
- [ ] 关闭再打开同一订单，行为与首次一致，不依赖“点第二次才正常”。
- [ ] 定制师给该订单发送 1 条新消息后，车务不刷新页面即可在对应订单按钮看到房务同款红色数字角标。
- [ ] 新消息只更新对应订单；其他订单、HOUSE 会话和顶部全局未读不得误点亮该按钮。
- [ ] 实时更新角标时页面不闪 loading，筛选、分页、滚动位置保持不变。
- [ ] 打开会话后角标立即清零；异步刷新晚返回也不会让红点复现。
- [ ] 列表视图、密集视图、订单详情抽屉三处计数一致。
- [ ] 增加组件测试：`ChatDrawer(show=true)` 首挂打开一次、`show=false` 首挂不打开、聊天信令轻量更新/已读竞态不复亮。
