hl-api-changelog/changelogs/2026-03/2026-03-19_room_assignment_fix.md

16 KiB

配房页面修正指南(紧急)

日期: 2026-03-19 后端状态: 已完成,无需后端修改 问题: 当前配房弹窗结构化分配 tab 与后端接口不匹配,缺少关键功能


当前问题

当前"结构化分配"tab 只有一组酒店/房型/数量/日期,但后端是按家庭维度批量分配的,需要支持多行。 另外缺少"配房可选酒店列表"接口调用,以及选择酒店后联动加载房型列表。


正确的页面布局

配房信息弹窗el-dialog

┌──────────────────────────────────────────────────────┐
│ 配房信息                                         [×] │
├──────────────────────────────────────────────────────┤
│                                                      │
│  [ 文本录入 ] [ 结构化分配 ]                          │
│                                                      │
│  订单号  HL202603111457027786                        │
│  出发日期  2026-03-11                                 │
│  行程天数  6天5晚                                     │
│                                                      │
│  ┌── 家庭 1 ──────────────────────────────── [删除] ┐│
│  │ 选择酒店 *   [ 下拉选择调接口9       ▼]      ││
│  │ 选择房型 *   [ 联动下拉调接口10      ▼]      ││
│  │ 入住日期 *   [2026-03-11] 退房日期 * [2026-03-16]││
│  └──────────────────────────────────────────────────┘│
│  ┌── 家庭 2 ──────────────────────────────── [删除] ┐│
│  │ 选择酒店 *   [ 下拉选择                  ▼]      ││
│  │ 选择房型 *   [ 联动下拉                  ▼]      ││
│  │ 入住日期 *   [2026-03-11] 退房日期 * [2026-03-16]││
│  └──────────────────────────────────────────────────┘│
│                                                      │
│  [+ 添加家庭]                                        │
│                                                      │
│                           [ 取消 ]  [ 确定 ]         │
└──────────────────────────────────────────────────────┘

关键交互

  1. 点击"配房"按钮 → 调用接口 9 获取可选酒店列表
  2. 选择酒店 → 用酒店ID调用接口 10 获取该酒店的房型列表
  3. 点击"+ 添加家庭" → 动态添加一组分配表单
  4. 入住/退房日期 → 默认为订单出发日期~结束日期
  5. 点击"确定" → 调用接口 2PUT /admin/order/{orderId}/hotel-assignment)提交

接口清单

# 接口 方法 路径 说明
1 更新房间信息(文本) PUT /api/order/admin/order/{orderId}/room-info 文本录入 tab 使用
2 分配酒店(结构化) PUT /api/order/admin/order/{orderId}/hotel-assignment 结构化分配 tab 使用
3 检测房型一致性 POST /api/order/admin/order/{orderId}/room-info/check-diff 提交后检查
9 配房可选酒店列表 GET /api/order/admin/order/{orderId}/available-hotels 新增关键接口
10 酒店房型列表 GET /api/resource/admin/hotel/{hotelId}/room-types 联动加载房型

接口 9配房可选酒店列表当前页面缺失

使用场景:打开配房弹窗时调用,获取酒店下拉列表。产品快照中的酒店排在最前面并标注"产品快照"。

GET /api/order/admin/order/{orderId}/available-hotels

响应示例

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "hotelId": "2028730814273536001",
      "hotelName": "阿尔善国际维景度假温泉酒店",
      "inSnapshot": true
    },
    {
      "hotelId": "2028730814273536002",
      "hotelName": "呼和浩特香格里拉大酒店",
      "inSnapshot": false
    }
  ]
}

响应字段

字段 类型 说明
hotelId String 酒店ID
hotelName String 酒店名称
inSnapshot Boolean 是否在产品快照中true=行程已安排的酒店,应优先选择)

前端实现

// 打开配房弹窗时调用
const loadAvailableHotels = async (orderId) => {
  const res = await request.get(`/api/order/admin/order/${orderId}/available-hotels`)
  // 快照酒店已自动排在前面
  hotelOptions.value = res.data.map(h => ({
    value: h.hotelId,
    label: h.inSnapshot ? `${h.hotelName}(产品快照)` : h.hotelName,
    hotelId: h.hotelId,
    hotelName: h.hotelName
  }))
}

接口 10酒店房型列表当前页面缺失

使用场景:选择酒店后联动加载该酒店的可用房型。

GET /api/resource/admin/hotel/{hotelId}/room-types

响应示例

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "roomTypeId": "2029001234567890001",
      "name": "豪华大床房",
      "maxOccupancy": 2,
      "basePrice": 680.00,
      "category": "DELUXE",
      "bedType": "KING",
      "windowType": "WITH_WINDOW",
      "status": 1,
      "coverUrl": "https://oss.example.com/room1.jpg"
    },
    {
      "roomTypeId": "2029001234567890002",
      "name": "标准双人间",
      "maxOccupancy": 2,
      "basePrice": 480.00,
      "category": "STANDARD",
      "bedType": "TWIN",
      "windowType": "WITH_WINDOW",
      "status": 1,
      "coverUrl": "https://oss.example.com/room2.jpg"
    }
  ]
}

响应字段

字段 类型 说明
roomTypeId String 房型ID
name String 房型名称(用于展示和提交)
maxOccupancy Integer 最大入住人数
basePrice BigDecimal 参考价(门市价)
category String 房型分类字典room_category
bedType String 床型字典bed_type
windowType String 窗户类型字典window_type
status Integer 状态1=启用)
coverUrl String 房型封面图

前端实现

// 选择酒店后联动加载房型
const onHotelChange = async (hotelId, assignmentIndex) => {
  const res = await request.get(`/api/resource/admin/hotel/${hotelId}/room-types`)
  // 将房型选项绑定到对应的家庭行
  roomTypeOptions.value[assignmentIndex] = res.data.map(rt => ({
    value: rt.name,  // 提交时用 name 字段
    label: `${rt.name}(最多${rt.maxOccupancy}人,¥${rt.basePrice}`
  }))
}

接口 2分配酒店结构化模式 - 提交

使用场景:点击"确定"按钮提交配房数据。

PUT /api/order/admin/order/{orderId}/hotel-assignment

请求体

{
  "assignments": [
    {
      "familyIndex": 1,
      "hotelName": "阿尔善国际维景度假温泉酒店",
      "roomType": "豪华大床房",
      "checkInDate": "2026-03-11",
      "checkOutDate": "2026-03-16"
    },
    {
      "familyIndex": 2,
      "hotelName": "阿尔善国际维景度假温泉酒店",
      "roomType": "标准双人间",
      "checkInDate": "2026-03-11",
      "checkOutDate": "2026-03-16"
    }
  ]
}

请求字段

字段 类型 必填 说明
assignments Array 酒店分配列表,不能为空
assignments[].familyIndex Integer 家庭序号从1开始,每行递增
assignments[].hotelName String 酒店名称从接口9的返回中取 hotelName
assignments[].roomType String 房型名称从接口10的返回中取 name
assignments[].checkInDate String 入住日期yyyy-MM-dd
assignments[].checkOutDate String 退房日期yyyy-MM-dd

校验规则

  • assignments 不能为空数组
  • 每个 assignment 的所有字段都必填
  • 日期格式 yyyy-MM-dd
  • checkOutDate 必须晚于 checkInDate

接口 1更新房间信息文本模式

使用场景:文本录入 tab 使用,简单填写文字描述。

PUT /api/order/admin/order/{orderId}/room-info

请求体

{
  "roomInfo": "标准双人间2间,大床房1间"
}

业务规则

  • 仅限 ROOM_MANAGERSUPER_ADMIN 角色
  • 保存成功后自动完成 ARRANGE_ROOM 待办,流程自动推进到"待配车"

接口 3检测房型一致性

使用场景:配房提交后自动调用,检测当前配置与产品快照是否一致。

POST /api/order/admin/order/{orderId}/room-info/check-diff

响应

  • datanull → 一致,无需提示
  • data 为字符串 → 不一致描述,用 ElMessageBox.confirm 弹窗提示

完整代码示例

<template>
  <el-dialog title="配房信息" v-model="visible" width="700px">
    <!-- Tab 切换 -->
    <el-tabs v-model="activeTab">
      <el-tab-pane label="文本录入" name="text">
        <el-form>
          <el-form-item label="订单号">{{ order.orderNo }}</el-form-item>
          <el-form-item label="房间信息" required>
            <el-input type="textarea" v-model="textForm.roomInfo"
                       :rows="4" placeholder="如标准双人间2间,大床房1间" />
          </el-form-item>
        </el-form>
      </el-tab-pane>

      <el-tab-pane label="结构化分配" name="structured">
        <el-form>
          <el-form-item label="订单号">{{ order.orderNo }}</el-form-item>

          <!-- 按家庭动态表单 -->
          <div v-for="(item, index) in assignments" :key="index"
               class="family-card">
            <div class="family-header">
              <span>家庭 {{ index + 1 }}</span>
              <el-button v-if="assignments.length > 1"
                         type="danger" text size="small"
                         @click="removeFamily(index)">删除</el-button>
            </div>

            <el-form-item label="选择酒店" required>
              <el-select v-model="item.hotelId"
                         placeholder="选择酒店"
                         @change="onHotelChange($event, index)">
                <el-option v-for="h in hotelOptions" :key="h.hotelId"
                           :value="h.hotelId"
                           :label="h.inSnapshot
                             ? `${h.hotelName}(产品快照)`
                             : h.hotelName" />
              </el-select>
            </el-form-item>

            <el-form-item label="选择房型" required>
              <el-select v-model="item.roomType" placeholder="选择房型">
                <el-option v-for="rt in roomTypeOptions[index]"
                           :key="rt.name" :value="rt.name"
                           :label="`${rt.name}(最多${rt.maxOccupancy}人)`" />
              </el-select>
            </el-form-item>

            <el-row :gutter="16">
              <el-col :span="12">
                <el-form-item label="入住日期" required>
                  <el-date-picker v-model="item.checkInDate"
                                  type="date" value-format="YYYY-MM-DD" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="退房日期" required>
                  <el-date-picker v-model="item.checkOutDate"
                                  type="date" value-format="YYYY-MM-DD" />
                </el-form-item>
              </el-col>
            </el-row>
          </div>

          <el-button type="primary" plain @click="addFamily">
            + 添加家庭
          </el-button>
        </el-form>
      </el-tab-pane>
    </el-tabs>

    <template #footer>
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="handleSubmit">确定</el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { ElMessageBox } from 'element-plus'
import request from '@/utils/request'

const visible = ref(false)
const activeTab = ref('structured')
const order = ref({})
const hotelOptions = ref([])
const roomTypeOptions = ref({})
const textForm = reactive({ roomInfo: '' })
const assignments = ref([
  { familyIndex: 1, hotelId: '', hotelName: '', roomType: '',
    checkInDate: '', checkOutDate: '' }
])

// 打开弹窗
const open = async (orderData) => {
  order.value = orderData
  visible.value = true
  // 默认日期
  assignments.value.forEach(a => {
    a.checkInDate = orderData.departureDate
    // returnDate = departureDate + tripDays - 1
  })
  // 加载酒店列表
  const res = await request.get(
    `/api/order/admin/order/${orderData.orderId}/available-hotels`
  )
  hotelOptions.value = res.data
}

// 选择酒店后联动房型
const onHotelChange = async (hotelId, index) => {
  const hotel = hotelOptions.value.find(h => h.hotelId === hotelId)
  if (hotel) {
    assignments.value[index].hotelName = hotel.hotelName
  }
  const res = await request.get(
    `/api/resource/admin/hotel/${hotelId}/room-types`
  )
  roomTypeOptions.value[index] = res.data
  assignments.value[index].roomType = '' // 清空已选房型
}

// 添加/删除家庭
const addFamily = () => {
  assignments.value.push({
    familyIndex: assignments.value.length + 1,
    hotelId: '', hotelName: '', roomType: '',
    checkInDate: order.value.departureDate, checkOutDate: ''
  })
}
const removeFamily = (index) => {
  assignments.value.splice(index, 1)
  // 重新编号
  assignments.value.forEach((a, i) => a.familyIndex = i + 1)
}

// 提交
const handleSubmit = async () => {
  if (activeTab.value === 'text') {
    await request.put(
      `/api/order/admin/order/${order.value.orderId}/room-info`,
      { roomInfo: textForm.roomInfo }
    )
  } else {
    // 结构化分配
    const data = assignments.value.map((a, i) => ({
      familyIndex: i + 1,
      hotelName: a.hotelName,
      roomType: a.roomType,
      checkInDate: a.checkInDate,
      checkOutDate: a.checkOutDate
    }))
    await request.put(
      `/api/order/admin/order/${order.value.orderId}/hotel-assignment`,
      { assignments: data }
    )
  }

  // 检测房型一致性
  const diffRes = await request.post(
    `/api/order/admin/order/${order.value.orderId}/room-info/check-diff`
  )
  if (diffRes.data) {
    await ElMessageBox.confirm(
      `房型一致性提示:${diffRes.data}`,
      '提示', { type: 'warning' }
    )
  }

  ElMessage.success('配房成功')
  visible.value = false
  emit('success')
}

defineExpose({ open })
</script>

关联字典

字典类型 说明 用途
room_category 房型分类STANDARD-标准间, DELUXE-豪华间, SUITE-套房, FAMILY-家庭房 房型列表展示
bed_type 床型KING-大床, TWIN-双床, TATAMI-榻榻米 房型列表展示
window_type 窗户WITH_WINDOW-有窗, NO_WINDOW-无窗, SKYLIGHT-天窗 房型列表展示

删掉的字段

当前弹窗中的"房间数量"字段应该删除——后端按家庭分配,每个家庭一间房,不需要单独设置数量。如果一个家庭需要2间房,就添加2行同样的家庭。


🤖 Generated with Claude Code