---
schema: "hl-changelog/v1"
ticket: "5139"
title: "车务派单候选筛选、分页与任意车辆选择"
consumer: "admin"
backend: "verified"
gateway: "verified"
frontend: "pending"
base: "dev-v3"
generated: "2026-07-22T13:25:00+08:00"
---

# 【修改接口·前端待处理·管理后台】车务派单候选筛选、分页与任意车辆选择

## 目标前端

- 端类型：管理后台（Web）
- 目标仓库：`mmg/hl-ui`
- 仓库地址：<https://git.1814.love:8443/mmg/hl-ui.git>
- 联调/验收环境：<http://192.168.100.160:9527>
- 小程序：无需处理

> **服务**: hl-fleet-service  
> **日期**: 2026-07-22  
> **工单**: #5139  
> **影响范围**: 订单派车弹窗的车辆候选、司机候选与最终派单校验

## 关键变化

`POST /admin/fleet/assignments/candidates` 继续同时返回车辆和司机，但两侧必须按各自分页参数渲染。后端新增动态车队/车型筛选、车型需求匹配、协议参考价、车辆常驻司机、司机历史统计，以及“先选司机时回显常驻车”的契约。

车型或座位不符合订单需求时，车辆仍允许选择；只有真实档期冲突或资源不可用才禁止。车辆选中态不是强制单选，前端再次点击已选车辆时可把 `selectedVehicleId` 清为 `null` 后重新查询。

## 变更接口

| 方法 | 路径 | 说明 |
| --- | --- | --- |
| POST | `/admin/fleet/assignments/candidates` | 车辆、司机独立筛选和分页；任一侧可先选 |
| POST | `/admin/fleet/assignments/precheck` | 车型/座位不匹配只返回 warning |
| POST | `/admin/fleet/assignments` | `strictSeats` 历史字段不再阻断任意车辆派单 |

## 候选查询入参

在原请求基础上新增或明确以下字段：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `selectedVehicleId` | String/null | 否 | 当前已选车辆；传 `null` 表示取消车辆选择 |
| `selectedDriverId` | String/null | 否 | 当前已选司机；可在未选车辆时先传 |
| `fleetTeamId` | String/null | 否 | 独立车队主数据 ID；空为全部 |
| `vehicleTypeId` | String/null | 否 | 车型大类 ID；空为全部 |
| `requiredVehicleType` | String/null | 否 | 订单需求车型大类 key，只影响匹配标记，不限制选择 |
| `vehiclePage` / `vehiclePageSize` | Integer | 是 | 车辆独立分页，页大小 1～100 |
| `driverPage` / `driverPageSize` | Integer | 是 | 司机独立分页，页大小 1～100 |
| `driverAvailability` | String | 否 | `ALL` / `AVAILABLE`，接口默认 `ALL`；管理后台按原型首屏显式传 `AVAILABLE` |
| `driverSort` | String | 否 | `SMART` / `RATING` / `YEARS` / `RECENT_ORDER`，默认 `SMART` |

雪花 ID 一律按字符串保存和提交，禁止 `Number()`、`parseInt()`。

取消车辆但保留司机的请求示例：

```json
{
  "orderId": "2080000000000000001",
  "requirementId": "2080000000000000101",
  "fleetItemIndex": 0,
  "startDate": "2026-07-29",
  "endDate": "2026-07-31",
  "headcount": 5,
  "requiredVehicleType": "suv",
  "selectedVehicleId": null,
  "selectedDriverId": "2080000000000000201",
  "vehiclePage": 1,
  "vehiclePageSize": 10,
  "driverPage": 1,
  "driverPageSize": 10
}
```

## 响应结构

### 独立分页

`data.vehicles` 和 `data.drivers` 均返回：

```json
{
  "records": [],
  "list": [],
  "total": 106,
  "page": 1,
  "pageSize": 10
}
```

`records` 与 `list` 内容相同，前端统一使用 `records`。切换车辆筛选只重置 `vehiclePage`，切换司机筛选只重置 `driverPage`，不要一次性把所有候选渲染成长列表。

### 动态筛选项

- `fleetTeamFacets[]`: `fleetTeamId/fleetTeamName/fleetType/count`。
- `vehicleTypeFacets[]`: `vehicleTypeId/vehicleTypeKey/vehicleTypeName/count`。
- 数量按当前车辆关键词统计；“全部”数量可按 facet 求和或使用 `vehicles.total`。
- 不再写死“自有车队/合作车队 A/合作车队 B”或固定车型数组。

### 车辆候选新增字段

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `vehicleTypeId` | String/null | 车型大类 ID |
| `vehicleTypeKey` / `vehicleTypeName` | String/null | 车型大类编码和名称 |
| `fleetTeamId/fleetTeamName/fleetType` | String/null | 动态车队信息 |
| `primaryDriverId/primaryDriverName` | String/null | 常驻司机；为空显示“无常驻” |
| `protocolPrice` | Decimal/null | 用车开始日价格日历协议参考价；为空显示“未设价” |
| `passengerCapacity` | Integer | 载客数，已扣除司机座 |
| `seatsEnough` | Boolean | 座位是否满足人数，仅用于提示 |
| `requirementMatched` | Boolean | 车型和座位是否均符合需求；`false` 只做醒目标记 |
| `available` | Boolean | 是否可选的权威值；真实档期冲突时为 `false` |
| `selected` | Boolean | 是否为当前已选车辆 |

前端禁用判断只使用 `available === false`。禁止用 `requirementMatched === false`、`seatsEnough === false` 或车型不一致禁用车辆；这些情况应显示“需求不匹配/座位不足”提示，但允许车务选中。

需求不匹配必须使用车辆卡片内的显式标签，不能再以黄色外框作为主要提示：

- `requirementMatched === false`：在车辆名称/状态附近显示橙色 `需求不匹配` 标签。
- `seatsEnough === false`：额外显示红色或橙红色 `座位不足` 标签。
- 移除需求不匹配专用黄色外框；边框只保留选中态、档期冲突等已有交互语义，避免颜色含义不明。
- 标签只负责提醒，不改变 `available`、点击选择或最终派单规则。

### 先选司机与取消车辆

- 仅传 `selectedDriverId` 时，`selectedDriverResidentVehicle` 返回该司机常驻车的完整车辆候选；司机无常驻车时为 `null`。
- 常驻车即使不在当前车队、车型筛选页内，也会通过该独立字段返回，前端可置顶或单独提示。
- 再次点击已选车辆时，前端清空本地车辆 ID，并以 `selectedVehicleId: null` 查询；保留 `selectedDriverId` 时常驻车提示仍存在。
- 同时选定跨常驻车组合时，沿用 `selectedRelation.requiresConfirmation` 和候选项 `requiresCrossResidentConfirmation` 的确认流程。

### 司机候选统计

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `completedOrderCount` | Integer | 司机跨赛季历史完单量，按派车组去重 |
| `lastOrderAt` | Date/null | 最近完单日期 |
| `rating` | Decimal/null | 真实平均评分；无评价时为 `null` |
| `hasRating` | Boolean | 是否存在真实评分 |
| `residentVehicleId/residentVehiclePlate` | String/null | 司机常驻车辆 |

`hasRating=false` 时显示“暂无评价”，不要展示星标和 `0.0/5.0`；不得再用固定 `5.0` 兜底。单量为司机历史累计，不按赛季清零。

### 原型一致性：司机筛选控件

司机筛选必须按原型平铺展示，不能用两个下拉框折叠选项。平铺按钮让车务一眼看到当前范围和全部排序方式，并可单击切换：

- 范围：`仅空闲`（`AVAILABLE`，首屏默认选中）、`全部`（`ALL`）。
- 排序：`智能推荐`（`SMART`，首屏默认选中）、`评分`（`RATING`）、`驾龄`（`YEARS`）、`最近接单`（`RECENT_ORDER`）。
- 切换范围或排序时只把 `driverPage` 重置为 1，不重置车辆筛选、车辆页码或已选车辆。
- “全部司机/智能排序”两个 `NSelect` 不视为原型等价实现；验收以按钮全部可见、选中态明确为准。

## 最终派单规则

- 车型或座位不匹配：候选项仍可选，预检返回 warning，最终派单不阻断。
- 档期冲突、车辆/司机不可用、黑名单或跨常驻未确认：仍按现有业务守卫阻断。
- `strictSeats` 为历史兼容字段，可不再提交；即使提交 `true` 也不会把座位不足变成阻断。

## 前端处理清单

- [ ] 车辆和司机列表分别接 `records/total/page/pageSize` 并增加独立分页控件。
- [ ] 车队和车型筛选使用 `fleetTeamFacets/vehicleTypeFacets` 动态渲染及计数。
- [ ] 车辆行展示车型、常驻司机、协议参考价；需求不匹配改用卡片内显式标签并移除黄色外框，座位不足追加独立标签，均不禁选。
- [ ] 支持再次点击已选车辆取消选择，并传 `selectedVehicleId: null`。
- [ ] 支持先选司机，并展示/置顶 `selectedDriverResidentVehicle`。
- [ ] 司机范围与排序按原型平铺为 2+4 个按钮，默认“仅空闲 + 智能推荐”，不得折叠成两个下拉框。
- [ ] 司机无评价显示“暂无评价”，不伪造 `5.0` 或 `0.0`；完成单量读取 `completedOrderCount`。
- [ ] 雪花 ID 全程按字符串处理。

## 验证证据

- PR [wx/HL#5140](https://git.1814.love:8443/wx/HL/pulls/5140) 已合并到 `dev-v3`。
- 派单候选、派单服务、司机统计、可靠投影、价格日历和迁移审计定向测试全部通过。
- `spotless:check` 与 `mvn -pl hl-fleet-service -am verify` 通过。
- 测试环境 `hl-fleet-service` 8087/8187 双实例滚动部署健康。
- 测试网关实测 HTTP/业务码 200：车辆和司机独立分页一致，返回 3 个动态车队、4 个车型大类；协议价非空，车型不匹配车辆仍可选；车辆可清空，先选司机可返回常驻车。
- 测试库只读核验：`V20260722.002` 已成功执行，候选 `completedOrderCount/lastOrderAt` 与 `fleet_driver` 投影一致，无评分司机返回 `rating=null`。

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