# 【新增接口·管理后台】车型管理库 9 接口（含新增的大类/型号分页 2 接口） (#2785)

> **PR**: #2786 | **服务**: hl-fleet-service | **更新时间**: 2026-05-21 14:25
>
> **存放目录**: `changelogs-v2/2026-05/`（前端 v3 项目仓库读取）
> **影响范围**: 管理后台「车管 / 车型管理库」页（左侧大类导航 + 右侧型号列表 + 全模块 CRUD）

---

## ⚠️ 关键变化（30 秒速读）

**本次实质变化是 2 个新增分页接口**（§3.2 大类分页 / §3.7 型号分页），原 7 个 CRUD/树接口契约零变化。一并把整模块 9 接口契约打包发给前端，方便对接「左侧大类导航 + 右侧型号列表」联动场景。

| # | 接口 | 状态 | 用途 |
|---|---|---|---|
| §3.1 | GET `/admin/fleet/vehicle-types` | 已存在·未变 | 一次拉全模块树（大类+型号） |
| **§3.2** | **GET `/admin/fleet/vehicle-types/page`** | **✨ 新增** | 大类分页+过滤（左侧导航专用） |
| §3.3 | POST `/admin/fleet/vehicle-types` | 已存在·未变 | 新增大类 |
| §3.4 | PUT `/admin/fleet/vehicle-types/{typeId}` | 已存在·未变 | 编辑大类 |
| §3.5 | DELETE `/admin/fleet/vehicle-types/{typeId}` | 已存在·未变 | 删除大类（软删） |
| §3.6 | POST `/admin/fleet/vehicle-types/{typeId}/models` | 已存在·未变 | 新增型号 |
| **§3.7** | **GET `/admin/fleet/vehicle-types/models/page`** | **✨ 新增** | 型号分页+过滤（vehicleTypeId 可选） |
| §3.8 | PUT `/admin/fleet/vehicle-types/models/{modelId}` | 已存在·未变 | 编辑型号 |
| §3.9 | DELETE `/admin/fleet/vehicle-types/models/{modelId}` | 已存在·未变 | 删除型号（软删） |

---

## 1. 接口背景

车型管理库是车队基础字典：大类（如 SUV/MPV/BUS/Sedan）→ 型号（如 丰田汉兰达/本田奥德赛）。管理后台需要：

1. **左侧大类导航 + 右侧型号列表**联动 → 大类分页 + 型号按 typeId 过滤的分页
2. **跨大类按车型名搜索** → 型号分页 typeId 可选 + modelName 模糊
3. 已有的 listTree 一次拉树仍保留（其他场景如派单弹窗筛选 / 价格日历分组继续用）

---

## 2. 变更清单

| # | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|----------|------|
| 1 | GET | `/admin/fleet/vehicle-types` | 未变 | 列出大类+型号整树 |
| 2 | GET | `/admin/fleet/vehicle-types/page` | **新增** | 大类分页 |
| 3 | POST | `/admin/fleet/vehicle-types` | 未变 | 新增大类 |
| 4 | PUT | `/admin/fleet/vehicle-types/{typeId}` | 未变 | 编辑大类（typeKey 不可改） |
| 5 | DELETE | `/admin/fleet/vehicle-types/{typeId}` | 未变 | 删除大类（软删） |
| 6 | POST | `/admin/fleet/vehicle-types/{typeId}/models` | 未变 | 新增型号 |
| 7 | GET | `/admin/fleet/vehicle-types/models/page` | **新增** | 型号分页 |
| 8 | PUT | `/admin/fleet/vehicle-types/models/{modelId}` | 未变 | 编辑型号 |
| 9 | DELETE | `/admin/fleet/vehicle-types/models/{modelId}` | 未变 | 删除型号（软删） |

---

## 3. 接口详情

### 3.1 列出大类 + 型号树

**GET** `/admin/fleet/vehicle-types`

- **使用场景**：一次拉取整棵车型树（大类含 models 数组），用于派单弹窗、价格日历分组等需要全量树的场景
- **认证**：管理后台 JWT
- **幂等**：是（只读）
- **入参**：无

**出参**：`Result<List<VehicleTypeTreeRespVO>>`

按 `sort_order` 升序。每个大类含 `models` 数组（同大类内按 `sort_order` 升序）。无大类时返回空数组 `[]`。

**典型示例 请求**：
```
GET /admin/fleet/vehicle-types
Authorization: Bearer <token>
（无请求体）
```

**典型示例 响应**：
```json
{
  "code": 200,
  "data": [
    {
      "id": "1234567890123456789",
      "typeKey": "suv",
      "typeName": "SUV 越野",
      "icon": "🚙",
      "description": "适合山地越野、多人出行",
      "sortOrder": 1,
      "models": [
        {
          "id": "9876543210987654321",
          "vehicleTypeId": "1234567890123456789",
          "modelName": "丰田汉兰达",
          "seats": 7,
          "basePrice": "800.00",
          "alias": "汉兰达,HIGHLANDER",
          "sortOrder": 1
        }
      ]
    }
  ],
  "msg": "成功"
}
```

**错误码**：仅 401 未登录

---

### 3.2 大类分页列表 ✨ 新增

**GET** `/admin/fleet/vehicle-types/page`

- **使用场景**：管理后台「车型管理库」左侧大类导航的分页查询，支持名称模糊、key 精确过滤
- **认证**：管理后台 JWT
- **幂等**：是（只读）

**Query 入参**：

| 字段 | 类型 | 必填 | 默认 | 校验 | 说明 |
|------|------|------|------|------|------|
| `page` | int | ❌ | 1 | `>= 1` | 页码 |
| `pageSize` | int | ❌ | 20 | `1-100` | 每页条数 |
| `typeName` | string | ❌ | — | — | 大类中文名模糊匹配 |
| `typeKey` | string | ❌ | — | — | 大类 key 精确匹配 |

**出参**：`Result<PageResult<VehicleTypeRespVO>>`

按 `sort_order` 升序。**不**含 `models` 数组（需要型号请调 §3.7）。

| 字段 | 类型 | 说明 |
|------|------|------|
| `records[].id` | string | 大类 ID（Long 序列化为 String 防精度丢失） |
| `records[].typeKey` | string | 大类 key（如 suv/mpv/bus/sedan） |
| `records[].typeName` | string | 大类中文名 |
| `records[].icon` | string | emoji 图标（可空） |
| `records[].description` | string | 描述（可空） |
| `records[].sortOrder` | int | 排序权重 |
| `total` | int | 总记录数 |
| `page` | int | 当前页码 |
| `pageSize` | int | 每页条数 |

**典型示例 请求**：
```
GET /admin/fleet/vehicle-types/page?page=1&pageSize=20&typeName=SUV
Authorization: Bearer <token>
（无请求体）
```

**典型示例 响应**：
```json
{
  "code": 200,
  "data": {
    "records": [
      {
        "id": "1234567890123456789",
        "typeKey": "suv",
        "typeName": "SUV 越野",
        "icon": "🚙",
        "description": "适合山地越野、多人出行",
        "sortOrder": 1
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  },
  "msg": "成功"
}
```

**边界 请求**（无过滤拿全部，pageSize=100 极限）：
```
GET /admin/fleet/vehicle-types/page?page=1&pageSize=100
```

**异常 请求**（pageSize=101 超限）：
```
GET /admin/fleet/vehicle-types/page?page=1&pageSize=101
```
响应：
```json
{ "code": 400, "msg": "每页条数最大为100", "data": null }
```

**错误码**：400 参数校验 / 401 未登录

---

### 3.3 新增大类

**POST** `/admin/fleet/vehicle-types`

- **使用场景**：扩展车型大类（初始化已灌 4 条 suv/mpv/bus/sedan，后续如新增小型客车等）
- **认证**：管理后台 JWT
- **幂等**：否（重复调用会触发 600101 重复）

**Body 入参**（`VehicleTypeSaveReqVO`）：

| 字段 | 类型 | 必填 | 校验 | 说明 |
|------|------|------|------|------|
| `typeKey` | string | ✅ | `@Size(max=16)` | 大类 key（全局唯一） |
| `typeName` | string | ✅ | `@Size(max=64)` | 大类中文名 |
| `icon` | string | ❌ | `@Size(max=8)` | emoji 图标（可为空） |
| `description` | string | ❌ | `@Size(max=256)` | 描述（可为空） |
| `sortOrder` | int | ✅ | — | 排序权重（升序） |

**出参**：`Result<VehicleTypeRespVO>`（同 §3.2 records 项字段）

**典型示例 请求**：
```
POST /admin/fleet/vehicle-types
Authorization: Bearer <token>
Content-Type: application/json

{
  "typeKey": "minibus",
  "typeName": "小型客车",
  "icon": "🚐",
  "description": "10-19 座中巴",
  "sortOrder": 5
}
```

**典型示例 响应**：
```json
{
  "code": 200,
  "data": {
    "id": "1234567890123456789",
    "typeKey": "minibus",
    "typeName": "小型客车",
    "icon": "🚐",
    "description": "10-19 座中巴",
    "sortOrder": 5
  },
  "msg": "成功"
}
```

**异常 请求**（typeKey 重复）：
```json
{ "typeKey": "suv", "typeName": "重复 SUV", "sortOrder": 99 }
```
响应：
```json
{ "code": 600101, "msg": "车型大类 key 已存在", "data": null }
```

**错误码**：400 参数校验 / **600101** typeKey 已存在 / 401 未登录

---

### 3.4 编辑大类

**PUT** `/admin/fleet/vehicle-types/{typeId}`

- **使用场景**：修改大类的 typeName / icon / description / sortOrder。**typeKey 不可改**（后端忽略入参中的 typeKey 字段）
- **认证**：管理后台 JWT
- **幂等**：是（相同 body 重复调结果一致）

**路径入参**：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `typeId` | Long | ✅ | 大类 ID |

**Body 入参**：同 §3.3（typeKey 字段后端忽略；其他字段同新增）

**出参**：`Result<VehicleTypeRespVO>`（更新后的大类）

**典型示例 请求**：
```
PUT /admin/fleet/vehicle-types/1234567890123456789
Authorization: Bearer <token>
Content-Type: application/json

{
  "typeKey": "suv",
  "typeName": "SUV 越野 (改名)",
  "icon": "🚙",
  "description": "新描述",
  "sortOrder": 2
}
```

**典型示例 响应**：
```json
{
  "code": 200,
  "data": {
    "id": "1234567890123456789",
    "typeKey": "suv",
    "typeName": "SUV 越野 (改名)",
    "icon": "🚙",
    "description": "新描述",
    "sortOrder": 2
  },
  "msg": "成功"
}
```

**异常 请求**（typeId 不存在）：
```json
{ "code": 600102, "msg": "车型大类不存在", "data": null }
```

**错误码**：400 参数校验 / **600102** 大类不存在 / 401 未登录

---

### 3.5 删除大类（软删）

**DELETE** `/admin/fleet/vehicle-types/{typeId}`

- **使用场景**：下架某个大类。**大类下存在型号时禁止删除**（应用层校验）
- **认证**：管理后台 JWT
- **幂等**：是（重复删返同样结果）

**路径入参**：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `typeId` | Long | ✅ | 大类 ID |

**出参**：`Result<Void>`（成功时 `data: null`）

**典型示例 请求**：
```
DELETE /admin/fleet/vehicle-types/1234567890123456789
Authorization: Bearer <token>
（无请求体）
```

**典型示例 响应**：
```json
{ "code": 200, "data": null, "msg": "成功" }
```

**异常 请求**（大类下有型号）：
```json
{ "code": 600104, "msg": "大类下存在型号，不能删除", "data": null }
```

**错误码**：**600102** 大类不存在 / **600104** 大类下存在型号 / 401 未登录

---

### 3.6 新增型号（挂在指定大类下）

**POST** `/admin/fleet/vehicle-types/{typeId}/models`

- **使用场景**：在某大类下新增型号（如 SUV 大类下加"路虎揽胜"）
- **认证**：管理后台 JWT
- **幂等**：否（同名重复抛 600103）

**路径入参**：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `typeId` | Long | ✅ | 所属大类 ID |

**Body 入参**（`VehicleModelSaveReqVO`）：

| 字段 | 类型 | 必填 | 校验 | 说明 |
|------|------|------|------|------|
| `modelName` | string | ✅ | `@Size(max=64)` | 型号名（同大类内唯一） |
| `seats` | int | ✅ | `@Min(4) @Max(19)` | 座位数 4-19 |
| `basePrice` | BigDecimal | ✅ | `@DecimalMin("0")` | 基础日单价（¥，≥ 0） |
| `alias` | string | ❌ | `@Size(max=256)` | 别名（搜索匹配用） |
| `sortOrder` | int | ✅ | — | 排序权重 |

**出参**：`Result<VehicleModelRespVO>`

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 型号 ID（Long 序列化为 String） |
| `vehicleTypeId` | string | 所属大类 ID |
| `modelName` | string | 型号名 |
| `seats` | int | 座位数 |
| `basePrice` | string | 基础日单价（BigDecimal 序列化） |
| `alias` | string | 别名 |
| `sortOrder` | int | 排序 |

**典型示例 请求**：
```
POST /admin/fleet/vehicle-types/1234567890123456789/models
Authorization: Bearer <token>
Content-Type: application/json

{
  "modelName": "丰田汉兰达",
  "seats": 7,
  "basePrice": "800.00",
  "alias": "汉兰达,HIGHLANDER",
  "sortOrder": 1
}
```

**典型示例 响应**：
```json
{
  "code": 200,
  "data": {
    "id": "9876543210987654321",
    "vehicleTypeId": "1234567890123456789",
    "modelName": "丰田汉兰达",
    "seats": 7,
    "basePrice": "800.00",
    "alias": "汉兰达,HIGHLANDER",
    "sortOrder": 1
  },
  "msg": "成功"
}
```

**异常 请求**（座位数 20 超限）：
```json
{ "modelName": "大巴车", "seats": 20, "basePrice": "1500.00", "sortOrder": 1 }
```
响应：
```json
{ "code": 400, "msg": "座位数最大为 19", "data": null }
```

**错误码**：400 参数校验 / **600102** 大类不存在 / **600103** 型号名已存在 / 401 未登录

---

### 3.7 型号分页列表 ✨ 新增

**GET** `/admin/fleet/vehicle-types/models/page`

- **使用场景**：
  1. 「左侧点击大类 → 右侧加载该大类型号」 → 传 `vehicleTypeId`
  2. 「跨大类按车型名/俗称搜索」 → 不传 `vehicleTypeId`，传 `modelName` 或 `alias`
- **认证**：管理后台 JWT
- **幂等**：是（只读）

**Query 入参**：

| 字段 | 类型 | 必填 | 默认 | 校验 | 说明 |
|------|------|------|------|------|------|
| `page` | int | ❌ | 1 | `>= 1` | 页码 |
| `pageSize` | int | ❌ | 20 | `1-100` | 每页条数 |
| `vehicleTypeId` | Long | ❌ | — | — | **可选**：传则按大类过滤，不传跨大类全量 |
| `modelName` | string | ❌ | — | — | 型号名模糊匹配 |
| `seats` | int | ❌ | — | — | 座位数精确匹配 |
| `alias` | string | ❌ | — | — | 别名模糊匹配（车型俗称） |

**出参**：`Result<PageResult<VehicleModelRespVO>>`

按 `sort_order` 升序。**不**返大类名称（前端从左侧大类列表自取）。

records 项字段同 §3.6 出参字段表。

**典型示例 请求**（按大类查）：
```
GET /admin/fleet/vehicle-types/models/page?page=1&pageSize=20&vehicleTypeId=1234567890123456789
Authorization: Bearer <token>
（无请求体）
```

**典型示例 响应**：
```json
{
  "code": 200,
  "data": {
    "records": [
      {
        "id": "9876543210987654321",
        "vehicleTypeId": "1234567890123456789",
        "modelName": "丰田汉兰达",
        "seats": 7,
        "basePrice": "800.00",
        "alias": "汉兰达,HIGHLANDER",
        "sortOrder": 1
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  },
  "msg": "成功"
}
```

**边界 请求**（跨大类按 modelName 搜"丰田"）：
```
GET /admin/fleet/vehicle-types/models/page?modelName=丰田
```

**边界 请求**（按 seats=7 精确 + alias 模糊组合）：
```
GET /admin/fleet/vehicle-types/models/page?seats=7&alias=ODYSSEY
```

**异常 请求**（page=0 不合法）：
```
GET /admin/fleet/vehicle-types/models/page?page=0&pageSize=20
```
响应：
```json
{ "code": 400, "msg": "页码最小为1", "data": null }
```

**错误码**：400 参数校验 / 401 未登录

---

### 3.8 编辑型号

**PUT** `/admin/fleet/vehicle-types/models/{modelId}`

- **使用场景**：修改型号的 modelName / seats / basePrice / alias / sortOrder
- **认证**：管理后台 JWT
- **幂等**：是

**路径入参**：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `modelId` | Long | ✅ | 型号 ID |

**Body 入参**：同 §3.6（不含 typeId，型号所属大类不可改）

**出参**：`Result<VehicleModelRespVO>`

**典型示例 请求**：
```
PUT /admin/fleet/vehicle-types/models/9876543210987654321
Authorization: Bearer <token>
Content-Type: application/json

{
  "modelName": "丰田汉兰达 (2025 款)",
  "seats": 7,
  "basePrice": "900.00",
  "alias": "汉兰达,HIGHLANDER",
  "sortOrder": 1
}
```

**异常 请求**（型号名同大类内已被占用）：
```json
{ "code": 600103, "msg": "车型型号已存在", "data": null }
```

**错误码**：400 参数校验 / **600103** 型号名已存在 / **600106** 型号不存在 / 401 未登录

---

### 3.9 删除型号（软删）

**DELETE** `/admin/fleet/vehicle-types/models/{modelId}`

- **使用场景**：下架某个型号。**被车辆引用时禁止删除**（应用层校验）
- **认证**：管理后台 JWT
- **幂等**：是

**路径入参**：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `modelId` | Long | ✅ | 型号 ID |

**出参**：`Result<Void>`

**典型示例 请求**：
```
DELETE /admin/fleet/vehicle-types/models/9876543210987654321
Authorization: Bearer <token>
（无请求体）
```

**典型示例 响应**：
```json
{ "code": 200, "data": null, "msg": "成功" }
```

**异常 请求**（型号被车辆引用）：
```json
{ "code": 600105, "msg": "型号被车辆引用，不能删除", "data": null }
```

**错误码**：**600105** 型号被车辆引用 / **600106** 型号不存在 / 401 未登录

---

## 6. 枚举 / 数据字典

本模块**无静态枚举 / 字典**：

- `typeKey` 是 `fleet_vehicle_type` 表的一个 `VARCHAR(16)` 字段（行级数据），不是后端枚举常量，**没有字典表**。系统初始化灌入了 4 条标准记录（详见 §9 业务边界），但 typeKey 可通过 §3.3 自由扩展
- `seats` 是数值范围（4-19，`@Min` / `@Max` 校验），不是枚举
- 其他字段（modelName / basePrice / alias 等）均为自由文本/数字

---

## 7. 错误码（全模块汇总）

| code | 含义 | 触发条件 | 涉及接口 |
|------|------|----------|----------|
| 400 | 参数校验失败 | 字段缺失 / 长度 / 范围越界 | 所有写入接口 + 分页接口 |
| 401 | 未登录 | JWT 无效或缺失 | 全部 9 接口 |
| **600101** | 车型大类 key 已存在 | typeKey 全局唯一约束 | §3.3 新增大类 |
| **600102** | 车型大类不存在 | typeId 无效或已软删 | §3.4 §3.5 §3.6 |
| **600103** | 车型型号已存在 | 同大类内型号名唯一约束 | §3.6 §3.8 |
| **600104** | 大类下存在型号，不能删除 | 应用层校验 | §3.5 |
| **600105** | 型号被车辆引用，不能删除 | 应用层校验 | §3.9 |
| **600106** | 车型型号不存在 | modelId 无效或已软删 | §3.8 §3.9 |

> 错误码段位 600101-600106 归属 hl-fleet-service，本次未新增段位。

---

## 9. 业务边界

- ✅ **大类 typeKey 编辑**：编辑大类时后端**忽略**入参的 typeKey 字段（不改），其他字段正常更新
- ✅ **大类软删保护**：大类下有未删除型号时禁删（600104）；先删型号或迁移型号到其他大类
- ✅ **型号软删保护**：型号被任一车辆（fleet_vehicle）引用时禁删（600105）
- ✅ **同名约束**：typeKey **全局**唯一（违反抛 600101）；modelName **同大类内**唯一（违反抛 600103）
- ✅ **分页默认值**：`page=1`、`pageSize=20`、`pageSize` 上限 100
- ✅ **跨大类搜索**：§3.7 不传 `vehicleTypeId` + 任意过滤 = 跨大类搜索（不报错）
- ⚠️ **新增型号副作用**：§3.6 新增型号后会触发价格日历初始化（见 FLEET §9.4），前端无需感知

### typeKey 系统初始化数据（不是枚举，可被 §3.3 扩展）

系统初始化时灌入了 4 条标准大类（来自 DB 初始化脚本）：

| typeKey | typeName | 用途参考 |
|---|---|---|
| `suv` | SUV 越野 | 高底盘 / 多人出行 |
| `mpv` | MPV 商务 | 7 座商务车 |
| `bus` | 中巴 | 10-19 座 |
| `sedan` | 轿车 | 5 座 |

> ⚠️ 这是**表里的现有数据**，**不是枚举校验**。运营可随时通过 §3.3 新增 `minibus` / `coach` 等任意 typeKey（仅约束：全局不重复）。前端展示时**建议从 §3.1 或 §3.2 实时查**，不要硬编码上面 4 个值。

---

## 11. 影响评估

- **是否破坏向后兼容**：否（§3.1-§3.6/§3.8/§3.9 契约零变化，仅新增 §3.2 §3.7 两个分页接口）
- **前端是否必须同步上线**：否（不调用新接口的页面不受影响）
- **影响已有数据**：无（无 DDL，无数据迁移）

---

## 12. 注意事项

- **分页参数名**：`page` / `pageSize`（**不是** pageNo），所有继承 PageParam 的入参都遵此约定
- **主键 Long 序列化**：`id` / `vehicleTypeId` 等 Long 字段 JSON 返回为 String，前端**不要**当 Number 解析（精度会丢失）。表单提交时仍可用 Number/String 双向兼容
- **整树 vs 分页的取舍**：低基数字典（< 20 大类 / < 200 型号）继续用 §3.1 整树拉一次缓存到内存；高基数或要服务端搜索时用 §3.2 / §3.7
- **basePrice 序列化**：`BigDecimal` 序列化为 String（如 `"800.00"`），前端按字符串接收避免精度问题

---

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#2785](https://git.1814.love:8443/wx/HL/issues/2785)
- **PR**: [#2786](https://git.1814.love:8443/wx/HL/pulls/2786)
- **Merge commit**: [`90109cd58`](https://git.1814.love:8443/wx/HL/commit/90109cd58e33a8c4e7bc802a92cf8cb1d6921050)
- **API 文档**: `docs/order-v3/api/API-SPEC-FLEET-V1.5.html` §1（车型管理库 6 接口；实际代码 9 端点 = §1.4 编辑/删除合并算 1 节）
- **同期相关 PR**:
  - [#2796](https://git.1814.love:8443/wx/HL/pulls/2796) 司机详情 relatedOrders mock
  - [#2799](https://git.1814.love:8443/wx/HL/pulls/2799) 司机自助 H5
  - [#2800](https://git.1814.love:8443/wx/HL/pulls/2800) 司机待审核
  - [#2804](https://git.1814.love:8443/wx/HL/pulls/2804) fleet 文档 v1.5 同步

### 13.2 联系人

- **后端负责人**: @yst（腰苏图）
- **前端对接（管理后台）**: 待指派
