# ✨ 酒店与房型轻量下拉接口（#5183）

> **PR**: [#5184](https://git.1814.love:8443/wx/HL/pulls/5184)  
> **服务**: hl-resource-service  
> **更新时间**: 2026-07-23 14:05

## 1. 接口背景

管理后台在核单 Step1 手动新增住宿项时，需要异步搜索可用酒店，并在选定酒店后加载该酒店当前可用的房型。新增两个轻量接口，避免下拉框拉取完整资源详情或在前端过滤禁用数据。

## 2. 变更清单

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 酒店轻量下拉分页 | GET | `/admin/hotel/options` | 新增接口 | 按酒店名称异步分页搜索，仅返回启用酒店 |
| 2 | 启用房型轻量下拉 | GET | `/admin/hotel/{hotelId}/room-type-options` | 新增接口 | 返回指定酒店下的启用房型 |

## 3. 接口详情

### 3.1 酒店轻量下拉分页

- **使用场景**：酒店下拉框首次打开或用户输入关键词时调用。
- **认证**：需要管理后台 JWT，使用 `Authorization: Bearer <token>`。
- **幂等性**：幂等，只读查询。
- **响应类型**：`Result<PageResult<HotelSimpleRespVO>>`。

#### Query 入参

| 字段 | 类型 | 必填 | 默认值 | 校验规则 | 说明 |
|------|------|------|--------|----------|------|
| `keyword` | String | 否 | `null` | 最长 100 个字符 | 酒店名称模糊匹配；空字符串或仅空白字符等同未传 |
| `page` | Integer | 否 | `1` | 最小值 1 | 当前页码 |
| `pageSize` | Integer | 否 | `20` | 1～100 | 每页条数 |

无请求体。

#### 完整出参

| 字段 | 类型 | 可空 | 说明 |
|------|------|------|------|
| `code` | Integer | 否 | 业务状态码；成功为 `200` |
| `message` | String | 否 | 响应消息；成功为 `成功` |
| `success` | Boolean | 否 | `code == 200` 时为 `true` |
| `traceId` | String | 是 | 链路追踪 ID |
| `data` | Object | 否 | 分页数据 |
| `data.records` | Array | 否 | 本页酒店列表；无匹配项时为空数组 |
| `data.records[].hotelId` | String | 否 | 酒店 ID；必须按字符串保存和传递 |
| `data.records[].hotelName` | String | 否 | 酒店名称 |
| `data.total` | Integer | 否 | 符合条件的总记录数 |
| `data.page` | Integer | 否 | 当前页码 |
| `data.pageSize` | Integer | 否 | 每页条数 |

#### 错误码

| code | 含义 | 触发场景 |
|------|------|----------|
| `200` | 成功 | 查询完成，包括没有匹配数据 |
| `400` | 参数校验失败 | `page < 1`、`pageSize` 不在 1～100，或 `keyword` 超过 100 个字符 |

#### 业务边界

- 只返回启用状态的酒店，禁用酒店不会出现在任何分页结果中。
- `keyword` 仅对酒店名称做模糊匹配。
- 未传 `keyword` 时分页查询全部启用酒店。
- 页码超过最后一页时，`records` 返回空数组，`total` 仍是符合条件的总记录数。
- 结果按酒店名称、酒店 ID 升序排列。

#### 典型成功示例

**请求**

```http
GET /admin/hotel/options?keyword=伯爵&page=1&pageSize=20
Authorization: Bearer <token>
```

无请求体。

**响应**

```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [
      {
        "hotelId": "9007199254740993",
        "hotelName": "伯爵酒店"
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  },
  "traceId": "a1b2c3d4-e5f6-7890",
  "success": true
}
```

#### 边界示例：无匹配数据

**请求**

```http
GET /admin/hotel/options?keyword=不存在的酒店&page=1&pageSize=20
Authorization: Bearer <token>
```

无请求体。

**响应**

```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [],
    "total": 0,
    "page": 1,
    "pageSize": 20
  },
  "traceId": "b2c3d4e5-f6a7-8901",
  "success": true
}
```

#### 异常示例：每页条数越界

**请求**

```http
GET /admin/hotel/options?page=1&pageSize=101
Authorization: Bearer <token>
```

无请求体。

**响应**

```json
{
  "code": 400,
  "message": "每页条数最大为100",
  "data": null,
  "traceId": "c3d4e5f6-a7b8-9012",
  "success": false
}
```

### 3.2 启用房型轻量下拉

- **使用场景**：用户从酒店下拉框选定酒店后，加载该酒店可选择的房型。
- **认证**：需要管理后台 JWT，使用 `Authorization: Bearer <token>`。
- **幂等性**：幂等，只读查询。
- **响应类型**：`Result<List<RoomTypeOptionRespVO>>`。

#### Path 入参

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `hotelId` | String | 是 | 已选酒店 ID；URL 中按十进制数字字符串传递 |

无 Query 参数，无请求体。

#### 完整出参

| 字段 | 类型 | 可空 | 说明 |
|------|------|------|------|
| `code` | Integer | 否 | 业务状态码；成功为 `200` |
| `message` | String | 否 | 响应消息；成功为 `成功` |
| `success` | Boolean | 否 | `code == 200` 时为 `true` |
| `traceId` | String | 是 | 链路追踪 ID |
| `data` | Array | 否 | 启用房型列表；没有启用房型时为空数组 |
| `data[].roomTypeId` | String | 否 | 房型 ID；必须按字符串保存和传递 |
| `data[].roomTypeName` | String | 否 | 房型名称 |
| `data[].roomCategory` | String | 否 | 房型分类，取值见第 4 节 |

#### 错误码

| code | 含义 | 触发场景 |
|------|------|----------|
| `200` | 成功 | 查询完成，包括酒店不存在或没有启用房型 |
| `400` | 参数类型错误 | `hotelId` 不是可解析的十进制数字字符串 |

#### 业务边界

- 只返回 `hotelId` 对应酒店下的启用房型，禁用房型不会返回。
- 酒店不存在、酒店下没有房型或没有启用房型时，均返回成功和空数组。
- 结果按房型排序值、房型 ID 升序排列。
- 该接口不分页，应在酒店选择变化后重新请求，不能继续使用上一个酒店的房型结果。

#### 典型成功示例

**请求**

```http
GET /admin/hotel/9007199254740993/room-type-options
Authorization: Bearer <token>
```

无请求体。

**响应**

```json
{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "roomTypeId": "9007199254741993",
      "roomTypeName": "标准间",
      "roomCategory": "STANDARD"
    },
    {
      "roomTypeId": "9007199254741994",
      "roomTypeName": "家庭房",
      "roomCategory": "FAMILY"
    }
  ],
  "traceId": "d4e5f6a7-b8c9-0123",
  "success": true
}
```

#### 边界示例：没有启用房型

**请求**

```http
GET /admin/hotel/9007199254740999/room-type-options
Authorization: Bearer <token>
```

无请求体。

**响应**

```json
{
  "code": 200,
  "message": "成功",
  "data": [],
  "traceId": "e5f6a7b8-c9d0-1234",
  "success": true
}
```

#### 异常示例：酒店 ID 类型错误

**请求**

```http
GET /admin/hotel/not-a-number/room-type-options
Authorization: Bearer <token>
```

无请求体。

**响应**

```json
{
  "code": 400,
  "message": "参数类型错误: hotelId='not-a-number'（需要 Long 类型）",
  "data": null,
  "traceId": "f6a7b8c9-d0e1-2345",
  "success": false
}
```

## 4. 枚举 / 数据字典

### 4.1 `roomCategory`（字典类型 `room_category`）

**所属字段**：`RoomTypeOptionRespVO.roomCategory`  
**类型**：String  
**必填**：是

| 值 | 中文 | 说明 |
|----|------|------|
| `STANDARD` | 标间 | 标准房型 |
| `SINGLE` | 单人间 | 单人房型 |
| `TWIN` | 双床房 | 双床房型 |
| `QUEEN` | 大床房 | 大床房型 |
| `KING` | 豪华大床 | 豪华大床房型 |
| `SUITE` | 套房 | 套房房型 |
| `FAMILY` | 家庭房 | 家庭房型 |
| `YURT` | 蒙古包 | 蒙古包房型 |
| `SPECIAL` | 特色房 | 特色房型 |
| `PARENT_CHILD` | 亲子房 | 亲子房型 |

## 5. 影响评估

- 两个接口均为新增，只读接口，不破坏现有接口兼容性。
- 前端可按需接入；不要求与后端同步上线。
- 所有酒店 ID、房型 ID 都按 `String` 处理，不要转换为 JavaScript `Number`。

## 6. 关联

- **Issue**: [#5183](https://git.1814.love:8443/wx/HL/issues/5183)
- **PR**: [#5184](https://git.1814.love:8443/wx/HL/pulls/5184)
- **Merge commit**: [0163a7a03](https://git.1814.love:8443/wx/HL/commit/0163a7a0308344691f2379e154c40069fd8d34ba)
- **后端负责人**: @yaosutu
