From 5dcfd2c4e50f3b513675b8280cf760de349b374f Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Fri, 20 Mar 2026 10:22:26 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BD=8F=E5=AE=BF=E5=AE=89=E6=8E=92?= =?UTF-8?q?=E5=A4=9A=E6=88=BF=E5=9E=8B=E4=BF=9D=E5=AD=98=E4=B8=8D=E4=B8=8A?= =?UTF-8?q?=20=E2=80=94=20=E5=89=8D=E7=AB=AF=E5=AE=9E=E7=8E=B0=E6=8C=87?= =?UTF-8?q?=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .../2026-03-20_accommodation_save_guide.md | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 changelogs/2026-03/2026-03-20_accommodation_save_guide.md diff --git a/changelogs/2026-03/2026-03-20_accommodation_save_guide.md b/changelogs/2026-03/2026-03-20_accommodation_save_guide.md new file mode 100644 index 0000000..c26d128 --- /dev/null +++ b/changelogs/2026-03/2026-03-20_accommodation_save_guide.md @@ -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分组结构 ↔ 扁平记录 的转换。