# 住宿安排保存:前端实现指南(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分组结构 ↔ 扁平记录 的转换。