---
schema: "hl-changelog/v1"
ticket: "5132"
title: "车务派单详情补全产品、行程节点、出行人与大交通"
consumer: "admin"
backend: "verified"
gateway: "verified"
frontend: "pending"
base: "dev-v3"
generated: "2026-07-22T10:35:00+08:00"
---

# 【修改接口·前端待处理·管理后台】车务派单详情补全产品、行程节点与出行人

> **服务**: hl-order-service-v3 + hl-fleet-service  
> **日期**: 2026-07-22  
> **工单**: #5132
> **影响范围**: 管理后台车务管理 / 派车看板 / 派单弹窗 Step1「订单详情」

---

## 关键变化

派单弹窗 Step1 不能再只展示人数、日期和每日一句简介。`GET /admin/fleet/board/orders/{orderId}` 现一次返回：

- `productName`：订单产品名（原字段，前端本次必须展示）。
- `tags[]`：订单在 `order_tag` 中真实挂载的标签名称与颜色；无标签返回 `[]`。
- `itinerary.days[].nodes[]`：每日真实行程节点，含开始时间、时段、时长、名称和简介。
- `travelers[]`：出行人脱敏基本信息，不含生日和任何明文字段。
- `transport`：抵达、返程及分批大交通信息（原字段，前端本次必须完整展示时间和班次，不能只显示站点）。

行程数据仍以订单当前 `order_itinerary_day` 和 `order_itinerary_node` 为权威源，禁止从产品模板反推。

---

## 变更接口

```http
GET /admin/fleet/board/orders/{orderId}
```

响应 VO：`BoardOrderDetailVO`

### 新增字段

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `tags` | Array | 真实订单标签；无标签返回 `[]` |
| `tags[].tagId` | String | 标签雪花 ID，必须按字符串处理 |
| `tags[].name` | String/null | 标签名称 |
| `tags[].color` | String/null | 标签颜色，如 `#52C41A` |
| `itinerary.days[].nodes` | Array | 当日节点，按 `sortOrder` 升序；无节点返回 `[]` |
| `itinerary.days[].nodes[].nodeId` | String | 节点雪花 ID，必须按字符串处理 |
| `itinerary.days[].nodes[].nodeType` | String/null | 节点类型，如 `SCENIC`、`RESTAURANT`、`ACTIVITY`、`SERVICE`、`CUSTOM` |
| `itinerary.days[].nodes[].nodeName` | String/null | 节点展示名；节点名为空时后端回退资源名 |
| `itinerary.days[].nodes[].startTime` | String/null | 开始时间，格式 `HH:mm` |
| `itinerary.days[].nodes[].timePeriod` | String/null | 时段，如上午、下午、全天 |
| `itinerary.days[].nodes[].durationMinutes` | Number/null | 时长，单位分钟 |
| `itinerary.days[].nodes[].description` | String/null | 节点简介 |
| `itinerary.days[].nodes[].sortOrder` | Number/null | 同天排序 |
| `travelers` | Array | 出行人脱敏基本信息；无出行人或下游降级时返回 `[]` |
| `travelers[].travelerId` | String | 出行人雪花 ID，必须按字符串处理 |
| `travelers[].travelerType` | String/null | `ADULT` / `CHILD` / `YOUNG_CHILD` / `BABY` |
| `travelers[].travelerTypeName` | String/null | 人员类型中文名，如“成人”“儿童” |
| `travelers[].nameMasked` | String/null | 脱敏姓名 |
| `travelers[].gender` | String/null | 性别字典值 |
| `travelers[].genderName` | String/null | 性别中文名 |
| `travelers[].ageAtDeparture` | Number/null | 按订单出发日计算的周岁 |
| `travelers[].idType` | String/null | 证件类型字典值 |
| `travelers[].idTypeName` | String/null | 证件类型中文名 |
| `travelers[].idNoMasked` | String/null | 脱敏证件号 |
| `travelers[].phoneMasked` | String/null | 脱敏手机号 |
| `travelers[].nationality` | String/null | 国籍 |
| `travelers[].race` | String/null | 民族 |
| `travelers[].emergencyContactMasked` | String/null | 脱敏紧急联系人姓名 |
| `travelers[].emergencyPhoneMasked` | String/null | 脱敏紧急联系人电话 |
| `travelers[].roomGroupNo` | Number/null | 同住分组号 |
| `travelers[].transportPlanIds` | String[] | 关联大交通批次 ID，必须按字符串处理 |
| `travelers[].profileStatus` | String/null | 资料状态：`PENDING` / `COMPLETED` |
| `travelers[].profileStatusName` | String/null | 资料状态中文名 |

`productName` 是已有字段，结构不变；本次页面必须消费，不再只保存在 `normalizeBoardOrder().product` 而不展示。

### 已有但本次必须完整展示的大交通字段

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `transport.arrive` | Object/null | 抵达接团段 |
| `transport.arrive.transportNo` | String/null | 抵达航班号/车次号 |
| `transport.arrive.time` | String/null | 抵达时间，ISO `LocalDateTime` |
| `transport.arrive.station` | String/null | 抵达机场/车站 |
| `transport.arrive.remark` | String/null | 抵达备注 |
| `transport.depart` | Object/null | 返程送站段 |
| `transport.depart.transportNo` | String/null | 返程航班号/车次号 |
| `transport.depart.time` | String/null | 返程时间，ISO `LocalDateTime` |
| `transport.depart.station` | String/null | 返程机场/车站 |
| `transport.depart.remark` | String/null | 返程备注 |
| `transport.batches` | Array | 分批接送列表 |
| `transport.batches[].travelerNames` | String/null | 本批出行人姓名摘要 |
| `transport.batches[].transportNo` | String/null | 本批航班号/车次号 |
| `transport.batches[].time` | String/null | 本批抵达/返程时间 |
| `transport.batches[].station` | String/null | 本批机场/车站 |
| `transport.transferTimeHint` | String/null | 无任何大交通时间时的后端提示，当前为“暂无接送机时间” |
| `transport.pickupRequired` | Boolean/null | 是否需要平台派车接送 |

### 响应示例

```json
{
  "code": 200,
  "data": {
    "orderNo": "HL20260721171011648",
    "productName": "草原亲子三日游",
    "tags": [
      {
        "tagId": "9001",
        "name": "亲子家庭",
        "color": "#52C41A"
      }
    ],
    "itinerary": {
      "theme": "草原亲子三日游",
      "route": null,
      "days": [
        {
          "dayNumber": 1,
          "date": "2026-07-29",
          "title": "接机",
          "detail": "抵达后入住酒店",
          "nodes": [
            {
              "nodeId": "2001",
              "nodeType": "SERVICE",
              "nodeName": "海拉尔机场接机",
              "startTime": "10:30",
              "timePeriod": "上午",
              "durationMinutes": 60,
              "description": "司机举牌接机",
              "sortOrder": 1
            }
          ]
        }
      ]
    },
    "travelers": [
      {
        "travelerId": "3001",
        "travelerType": "ADULT",
        "travelerTypeName": "成人",
        "nameMasked": "孔**",
        "gender": "2",
        "genderName": "女",
        "ageAtDeparture": 35,
        "idType": "ID_CARD",
        "idTypeName": "身份证",
        "idNoMasked": "150***********1234",
        "phoneMasked": "138****1234",
        "nationality": "中国",
        "race": "蒙古族",
        "emergencyContactMasked": "王*",
        "emergencyPhoneMasked": "139****5678",
        "roomGroupNo": 1,
        "transportPlanIds": ["4001"],
        "profileStatus": "COMPLETED",
        "profileStatusName": "已完善"
      }
    ],
    "transport": {
      "transferTimeHint": null,
      "arrive": {
        "transportNo": "CA1234",
        "time": "2026-07-29T10:30:00",
        "station": "海拉尔东山国际机场",
        "remark": "T2 出口举牌接机"
      },
      "depart": {
        "transportNo": "CA5678",
        "time": "2026-07-31T17:20:00",
        "station": "海拉尔东山国际机场",
        "remark": "提前 2 小时送达"
      },
      "batches": [],
      "pickupRequired": true
    }
  },
  "success": true
}
```

---

## 前端页面调整要求

目标文件：`src/views/fleet/board/components/Step1OrderDetail.vue`。

1. 顶部订单摘要展示产品名，读取 `order.productName || order.product`；产品名为空才显示 `—`。
   - 当前 `Step1OrderDetail.vue` 中的 `order.bookingType || '企业包车'` 是硬编码占位，不是订单标签，必须删除。
   - 该位置改为遍历详情响应 `tags[]`，使用 `name` 作为文案、`color` 作为颜色；`tags=[]` 时不显示标签，也不回退“企业包车”。
2. 在顶部订单摘要下增加“大交通”信息卡，抵达与返程分栏展示 `transportNo + time + station + remark`：
   - 时间使用完整月日和时分，不只展示日期。
   - `arrive`、`depart` 独立判空，只有一段时仍正常展示该段。
   - `batches[]` 非空时增加“分批接送”，展示本批出行人、班次、时间和站点。
   - 无任何时间时展示 `transport.transferTimeHint`，不得伪造航班或时间。
   - 当前顶部“接送”统计可保留站点摘要，但不能替代大交通详情卡。
3. 左侧“每日安排”保留日标题和 `detail`，并在每一天下面渲染 `nodes[]`：
   - 时间优先显示 `startTime`，为空时显示 `timePeriod`，两者都有可组合展示。
   - `startTime` 和 `timePeriod` 都为空时显示“时间待定”，不得根据节点顺序或描述猜测具体时间。
   - 主文案显示 `nodeName`。
   - `durationMinutes` 有值时显示易读时长。
   - `description` 有值且与日简介不重复时显示节点简介。
4. 右侧新增“出行人信息”区，默认展示脱敏姓名、人员类型、性别、年龄、国籍/民族、脱敏手机号和资料状态；证件、同住分组、关联大交通批次及紧急联系人可在行内展开或次要信息区展示。
   - 年龄文案使用自然表达“年龄 29 岁”，不要显示成“出发时 29岁”。
   - `ageAtDeparture` 的业务口径仍是按订单出发日计算；如需说明，将“按出发日计算”放在字段提示或帮助文案中，不与年龄值拼成标签。
5. 禁止为了展示此页面调用明文接口 `POST /admin/fleet/board/orders/{orderId}/travelers/plain`。Step1 只使用详情响应中的脱敏 `travelers[]`。
6. 空态明确：无节点显示“暂无行程节点”，无出行人显示“暂未填写出行人信息”；不得生成模拟节点或模拟出行人。
7. 雪花 ID 禁止 `Number()` / `parseInt()`，统一按字符串处理。
8. 用车备注与通用特殊诉求必须按字段来源分区展示，不能混在同一个“特殊要求”警示框：
   - `requirementRemark` 来源于定制师在“调整订单 → 车辆安排 → 备注/其他诉求”填写的自由文本，应显示在“定制师备注”或更准确的“用车备注”卡片中；例如“司机会蒙语”。
   - `specialTags[]` 来源于“通用特殊诉求”的多选标签，只在“特殊要求”区域展示标签；例如“儿童安全座椅”“大行李空间”“中文司机”。
   - `plannerNote` 是订单级定制师备注，与 `requirementRemark` 不是同一字段；两者同时存在时分行展示并标明来源，不得互相覆盖。
   - `requirements` 仅作为历史订单兼容文本；当 `requirementRemark` 或 `specialTags[]` 已有值时，不得把它们重复拼入 `requirements`。

推荐布局：顶部摘要下放横向“大交通”卡；左栏继续承载逐日节点时间线；右栏顺序为“出行人信息 → 客人留言 → 定制师/用车备注 → 特殊要求 → 操作记录”。

---

## 兼容与降级

- 仅新增响应字段，不修改请求参数，不影响旧调用方。
- 历史订单无订单标签时 `tags=[]`，禁止使用产品类型、预订类型或固定文案冒充订单标签。
- 历史行程没有节点时 `nodes=[]`，每日标题和简介仍照常返回。
- order-v3 聚合上下文失败并回退 fleet 本地快照时，`relatedDetailReady=false`，`travelers=[]`，行程节点不可用；前端显示真实空态。
- 原独立脱敏接口 `GET /admin/fleet/board/orders/{orderId}/travelers` 保留兼容，但此页面无需再发第二次请求。
- 不返回 `birthday`、明文姓名、明文证件号、明文手机号或明文紧急联系人。

---

## 验收清单

- [ ] 顶部可看到订单产品名。
- [ ] 顶部只展示 `tags[]` 中的真实订单标签；无标签时不显示，“企业包车”硬编码已删除。
- [ ] 大交通卡分别展示抵达/返程的班次、完整时间、站点和备注。
- [ ] 有分批接送时展示每批出行人、班次、时间和站点；无大交通时间时展示真实空态。
- [ ] 每日安排按节点顺序展示时间、节点名、时长和简介。
- [ ] 节点无 `startTime` 时可回退显示 `timePeriod`，不会出现 `undefined`。
- [ ] 右侧可看到全部出行人的脱敏基本信息。
- [ ] 每位出行人以“年龄 N 岁”的自然文案展示年龄，并可查看人员类型、性别、证件、国籍/民族、同住分组、关联大交通及资料状态。
- [ ] 无节点/无出行人时展示真实空态，不生成模拟数据。
- [ ] 页面 Network 只需现有详情请求，不调用出行人明文接口。
- [ ] “备注/其他诉求”读取 `requirementRemark` 并显示在定制师/用车备注卡片；“特殊要求”只展示 `specialTags[]`，不会再把备注文本放入警示框。
- [ ] `plannerNote` 与 `requirementRemark` 同时存在时分别展示且不覆盖，历史 `requirements` 不造成重复文案。
- [ ] 现有留言、特殊要求、步骤条和操作记录不受影响。

---

## 验证证据

- `ItineraryServiceTest`：覆盖节点名称、开始时间、时段、时长、简介和排序装配。
- `OrderFleetProviderServiceTest`：覆盖节点随当前订单日期对齐且出行人脱敏进入聚合上下文。
- `BoardOrderServiceTest`：覆盖 shared DTO 到管理端 VO 的节点和出行人映射。
- `BoardControllerTest`：覆盖 `productName`、节点时间、String ID 与脱敏出行人的 JSON 契约。
- `OrderFleetProviderServiceTest`、`BoardOrderServiceTest` 与 `BoardControllerTest`：覆盖 `order_tag` 名称/颜色进入详情响应，标签 ID 按字符串序列化。
- 测试环境网关实测订单 `HL20260721171011648`：HTTP 200，返回 3 个行程日、14 个真实节点和 5 位出行人；5 位出行人均返回 `ageAtDeparture`，且未出现生日、明文姓名、明文证件号或明文手机号。
- 同一实测订单返回 2 个真实订单标签“自动化测试”“房务需求”，均包含颜色，`tagId` 均为字符串；响应不含 `bookingType`，前端无需也不得使用“企业包车”等硬编码兜底。
- 同一实测订单已返回抵达大交通的班次、抵达时间和站点；该订单无返程段、无分批接送，接口按真实数据返回空值或空数组。
- 该订单 14 个节点的 `startTime` 与 `timePeriod` 在订单行程源数据中均为空，接口如实返回 `null`；前端须展示“时间待定”，若要显示具体钟点需先补录订单行程节点时间。
- 测试环境团号 `26-7042` 的调整单中，“备注/其他诉求”为“司机会蒙语”；详情接口已分别返回 `requirementRemark` 和 `specialTags[]`。截至 `v2.1@6e6a11bf`，`Step1OrderDetail.vue` 仍使用 `requirementRemark || requirements` 渲染“特殊要求”警示框，并仅用 `plannerNote` 渲染定制师留言，造成截图中的字段串位；按上方规则调整前端展示即可，后端无需新增字段。
- 网关证据已由 `hl task` 登记，SHA-256：`8ff09cc804fb8d72fde6df3f338125b725c857d2c098b59f6b222f460da844a3`。

> 本文是前端接入通知，不代表已修改或发布 `mmg/hl-ui`。
