fix: 住宿安排多房型保存不上 — 前端实现指南
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
25290f9b81
当前提交
5dcfd2c4e5
@ -0,0 +1,262 @@
|
|||||||
|
# 住宿安排保存:前端实现指南(BUG修复)
|
||||||
|
|
||||||
|
**日期**: 2026-03-20
|
||||||
|
**类型**: 前端BUG修复指南
|
||||||
|
**影响模块**: 产品管理 → 住宿安排
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
住宿安排页面:
|
||||||
|
1. **一个家庭保存多个房型保存不上**(如:家庭1 → 大床房 + 普通标间)
|
||||||
|
2. **第二个家庭的房型也保存不上**
|
||||||
|
|
||||||
|
**原因**:后端的住宿数据模型是 **每条记录 = 一个酒店 + 一个房型**,前端需要为每个房型单独调用接口保存。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 后端数据模型
|
||||||
|
|
||||||
|
`product_day_hotel` 表结构(每条记录 = 一个房型分配):
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| id | 记录ID |
|
||||||
|
| productId | 产品ID |
|
||||||
|
| dayNumber | 第几天 |
|
||||||
|
| hotelId | 酒店ID |
|
||||||
|
| hotelName | 酒店名称 |
|
||||||
|
| roomTypeId | **房型ID**(每条记录一个房型) |
|
||||||
|
| roomTypeName | 房型名称 |
|
||||||
|
| roomCount | 房间数量 |
|
||||||
|
| familyIds | 所属家庭ID数组 JSON |
|
||||||
|
|
||||||
|
**关键**:同一天、同一家庭、同一酒店可以有多条记录(不同房型),每个房型是独立的一条记录。
|
||||||
|
|
||||||
|
### 数据示例
|
||||||
|
|
||||||
|
家庭1 住香格里拉(大床房1间 + 标间1间)= **2条记录**:
|
||||||
|
|
||||||
|
```
|
||||||
|
记录1: hotelId=香格里拉, roomTypeId=大床房, roomCount=1, familyIds=["家庭1"]
|
||||||
|
记录2: hotelId=香格里拉, roomTypeId=标间, roomCount=1, familyIds=["家庭1"]
|
||||||
|
```
|
||||||
|
|
||||||
|
家庭2 住海棠酒店(标间1间)= **1条记录**:
|
||||||
|
|
||||||
|
```
|
||||||
|
记录3: hotelId=海棠酒店, roomTypeId=标间, roomCount=1, familyIds=["家庭2"]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 后端接口清单
|
||||||
|
|
||||||
|
### 1. 添加住宿(每个房型调一次)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /admin/product/item/{productId}/day/{dayNumber}/hotel
|
||||||
|
```
|
||||||
|
|
||||||
|
请求体:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hotelId": "2023714929877450753",
|
||||||
|
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||||
|
"roomTypeId": "3002000000000000013",
|
||||||
|
"roomTypeName": "大床房",
|
||||||
|
"roomCount": 1,
|
||||||
|
"familyIds": ["2034467435683393537"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 更新住宿
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /admin/product/hotel/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 删除住宿
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /admin/product/hotel/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 查询某天的住宿列表
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /admin/product/item/{productId}/day/{dayNumber}/hotels
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "2034468900841975809",
|
||||||
|
"dayNumber": 1,
|
||||||
|
"hotelId": "2023714929877450753",
|
||||||
|
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||||
|
"roomTypeId": "3002000000000000013",
|
||||||
|
"roomTypeName": "大床房",
|
||||||
|
"roomCount": 1,
|
||||||
|
"familyIds": ["2034467435683393537"],
|
||||||
|
"familyRoomConfig": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2034817340914429954",
|
||||||
|
"dayNumber": 1,
|
||||||
|
"hotelId": "2023714929877450753",
|
||||||
|
"hotelName": "呼伦贝尔香格里拉大酒店",
|
||||||
|
"roomTypeId": "3002000000000000014",
|
||||||
|
"roomTypeName": "普通标间",
|
||||||
|
"roomCount": 1,
|
||||||
|
"familyIds": ["2034467435683393537"],
|
||||||
|
"familyRoomConfig": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端实现方案
|
||||||
|
|
||||||
|
### 页面数据结构
|
||||||
|
|
||||||
|
前端页面按「家庭 → 酒店 → 房型列表」组织,但后端是扁平的(每个房型一条记录)。需要做转换:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 前端UI数据结构(按家庭分组)
|
||||||
|
const uiData = {
|
||||||
|
familyId: '2034467435683393537',
|
||||||
|
hotelId: '2023714929877450753',
|
||||||
|
hotelName: '呼伦贝尔香格里拉大酒店',
|
||||||
|
roomTypes: [
|
||||||
|
{ roomTypeId: '3002000000000000013', roomTypeName: '大床房', roomCount: 1 },
|
||||||
|
{ roomTypeId: '3002000000000000014', roomTypeName: '普通标间', roomCount: 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要转换为后端的扁平结构(每个房型一条记录)
|
||||||
|
const apiRequests = uiData.roomTypes.map(rt => ({
|
||||||
|
hotelId: uiData.hotelId,
|
||||||
|
hotelName: uiData.hotelName,
|
||||||
|
roomTypeId: rt.roomTypeId,
|
||||||
|
roomTypeName: rt.roomTypeName,
|
||||||
|
roomCount: rt.roomCount,
|
||||||
|
familyIds: [uiData.familyId]
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 保存逻辑(推荐:增量模式)
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function saveAccommodation(productId, dayNumber, familyId, hotelId, hotelName, roomTypes) {
|
||||||
|
// 1. 查询当前该天已有的住宿记录
|
||||||
|
const { data: existing } = await request.get(
|
||||||
|
`/admin/product/item/${productId}/day/${dayNumber}/hotels`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. 筛选出属于当前家庭的已有记录
|
||||||
|
const familyExisting = existing.filter(h =>
|
||||||
|
h.familyIds && h.familyIds.includes(familyId)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 3. 对比:哪些要删、哪些要更新、哪些要新增
|
||||||
|
const existingMap = new Map(familyExisting.map(h => [h.roomTypeId, h]))
|
||||||
|
|
||||||
|
for (const rt of roomTypes) {
|
||||||
|
const old = existingMap.get(rt.roomTypeId)
|
||||||
|
if (old) {
|
||||||
|
// 已存在 → 更新
|
||||||
|
await request.put(`/admin/product/hotel/${old.id}`, {
|
||||||
|
hotelId, hotelName,
|
||||||
|
roomTypeId: rt.roomTypeId,
|
||||||
|
roomTypeName: rt.roomTypeName,
|
||||||
|
roomCount: rt.roomCount,
|
||||||
|
familyIds: [familyId]
|
||||||
|
})
|
||||||
|
existingMap.delete(rt.roomTypeId)
|
||||||
|
} else {
|
||||||
|
// 不存在 → 新增
|
||||||
|
await request.post(
|
||||||
|
`/admin/product/item/${productId}/day/${dayNumber}/hotel`,
|
||||||
|
{
|
||||||
|
hotelId, hotelName,
|
||||||
|
roomTypeId: rt.roomTypeId,
|
||||||
|
roomTypeName: rt.roomTypeName,
|
||||||
|
roomCount: rt.roomCount,
|
||||||
|
familyIds: [familyId]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 剩余的旧记录 → 删除(用户已移除的房型)
|
||||||
|
for (const [, old] of existingMap) {
|
||||||
|
await request.delete(`/admin/product/hotel/${old.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 加载回显逻辑
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function loadAccommodation(productId, dayNumber, families) {
|
||||||
|
// 1. 查询该天所有住宿记录(扁平)
|
||||||
|
const { data: hotels } = await request.get(
|
||||||
|
`/admin/product/item/${productId}/day/${dayNumber}/hotels`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. 按家庭ID分组
|
||||||
|
const familyHotelMap = {}
|
||||||
|
for (const family of families) {
|
||||||
|
familyHotelMap[family.familyId] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const hotel of hotels) {
|
||||||
|
const fids = hotel.familyIds || []
|
||||||
|
for (const fid of fids) {
|
||||||
|
if (!familyHotelMap[fid]) familyHotelMap[fid] = []
|
||||||
|
familyHotelMap[fid].push(hotel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按家庭+酒店分组,合并同酒店的多个房型
|
||||||
|
for (const [familyId, hotelRecords] of Object.entries(familyHotelMap)) {
|
||||||
|
const hotelGrouped = {}
|
||||||
|
for (const h of hotelRecords) {
|
||||||
|
if (!hotelGrouped[h.hotelId]) {
|
||||||
|
hotelGrouped[h.hotelId] = {
|
||||||
|
hotelId: h.hotelId,
|
||||||
|
hotelName: h.hotelName,
|
||||||
|
roomTypes: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hotelGrouped[h.hotelId].roomTypes.push({
|
||||||
|
id: h.id, // 后端记录ID,更新/删除时需要
|
||||||
|
roomTypeId: h.roomTypeId,
|
||||||
|
roomTypeName: h.roomTypeName,
|
||||||
|
roomCount: h.roomCount
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// hotelGrouped 就是该家庭的UI展示数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
| 操作 | 接口 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 添加房型 | `POST .../day/{dayNumber}/hotel` | **每个房型调一次**,同一酒店不同房型 = 多次调用 |
|
||||||
|
| 更新房型 | `PUT .../hotel/{id}` | 按记录ID更新 |
|
||||||
|
| 删除房型 | `DELETE .../hotel/{id}` | 用户移除某个房型时调用 |
|
||||||
|
| 查询回显 | `GET .../day/{dayNumber}/hotels` | 返回扁平列表,前端按 familyIds + hotelId 分组展示 |
|
||||||
|
|
||||||
|
**核心要点**:后端是扁平的(每个房型一条记录),前端需要做 UI分组结构 ↔ 扁平记录 的转换。
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户