|
|
@@ -26,13 +26,13 @@
|
|
|
{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="酒店名称" align="center" prop="hotelName" min-width="120" show-overflow-tooltip />
|
|
|
- <el-table-column label="酒店简介" align="center" prop="introduction" min-width="140" show-overflow-tooltip>
|
|
|
+ <el-table-column label="酒店名称" align="center" prop="hotelName" min-width="100" show-overflow-tooltip />
|
|
|
+ <el-table-column label="酒店简介" align="center" prop="introduction" min-width="110" show-overflow-tooltip>
|
|
|
<template #default="scope">
|
|
|
{{ scope.row.introduction || '-' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="宣传图片" align="center" width="180">
|
|
|
+ <el-table-column label="宣传图片" align="center" width="140">
|
|
|
<template #default="scope">
|
|
|
<div v-if="scope.row.imageList && scope.row.imageList.length > 0" class="cell-images">
|
|
|
<el-image
|
|
|
@@ -50,11 +50,18 @@
|
|
|
<span v-else class="cell-empty">暂无</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="房型与时段" align="center" min-width="220">
|
|
|
+ <el-table-column label="关联道具" align="center" min-width="100" show-overflow-tooltip>
|
|
|
+ <template #default="scope">
|
|
|
+ {{ itemNameById(scope.row.itemId) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="房型与时段" align="center" min-width="180">
|
|
|
<template #default="scope">
|
|
|
<div v-if="scope.row.roomList && scope.row.roomList.length > 0" class="cell-room-block">
|
|
|
<div v-for="(room, ri) in scope.row.roomList" :key="room.id || ri" class="cell-room-item">
|
|
|
- <div class="cell-room-title">{{ ri + 1 }}.{{ room.roomName }}:</div>
|
|
|
+ <div class="cell-room-title">
|
|
|
+ {{ ri + 1 }}.{{ room.roomName }}:<span v-if="room.roomValue != null" class="cell-room-value">(价值:{{ room.roomValue }})</span>
|
|
|
+ </div>
|
|
|
<div v-for="(p, pi) in room.periodList || []" :key="p.id || pi" class="cell-room-line">
|
|
|
时间段{{ pi + 1 }}:{{ p.startDate }}-{{ p.endDate }}
|
|
|
</div>
|
|
|
@@ -64,7 +71,7 @@
|
|
|
<span v-else class="cell-empty">暂无</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="剩余数量" align="center" min-width="150">
|
|
|
+ <el-table-column label="剩余数量" align="center" min-width="120">
|
|
|
<template #default="scope">
|
|
|
<div v-if="scope.row.roomList && scope.row.roomList.length > 0" class="cell-room-block">
|
|
|
<div v-for="(room, ri) in scope.row.roomList" :key="room.id || ri" class="cell-room-item">
|
|
|
@@ -76,7 +83,7 @@
|
|
|
<span v-else class="cell-empty">暂无</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="房间介绍" align="center" min-width="140">
|
|
|
+ <el-table-column label="房间介绍" align="center" min-width="120">
|
|
|
<template #default="scope">
|
|
|
<div v-if="scope.row.roomList && scope.row.roomList.length > 0" class="cell-room-block">
|
|
|
<div v-for="(room, ri) in scope.row.roomList" :key="room.id || ri" class="cell-room-line cell-room-item" style="white-space: pre-line">
|
|
|
@@ -86,7 +93,7 @@
|
|
|
<span v-else class="cell-empty">暂无</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="房间图片" align="center" width="180">
|
|
|
+ <el-table-column label="房间图片" align="center" width="140">
|
|
|
<template #default="scope">
|
|
|
<div v-if="scope.row.roomList && scope.row.roomList.length > 0" class="cell-room-block">
|
|
|
<div v-for="(room, ri) in scope.row.roomList" :key="room.id || ri" class="cell-room-images-row cell-room-item">
|
|
|
@@ -109,12 +116,23 @@
|
|
|
<span v-else class="cell-empty">暂无</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
+ <el-table-column label="酒店时效" align="center" min-width="130">
|
|
|
+ <template #default="scope">
|
|
|
+ <span v-if="scope.row.effectStartDate || scope.row.effectEndDate">
|
|
|
+ {{ scope.row.effectStartDate || '不限' }} 至 {{ scope.row.effectEndDate || '不限' }}
|
|
|
+ </span>
|
|
|
+ <span v-else class="cell-empty">不限</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
<el-table-column label="置顶排序" align="center" prop="sortOrder" width="90" />
|
|
|
- <el-table-column label="操作" align="center" width="140" fixed="right">
|
|
|
+ <el-table-column label="操作" align="center" width="160" fixed="right">
|
|
|
<template #default="scope">
|
|
|
<div class="action-buttons">
|
|
|
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
|
|
|
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
|
|
|
+ <el-button link :type="scope.row.isShow === '1' ? 'warning' : 'success'" @click="handleChangeShow(scope.row)">
|
|
|
+ {{ scope.row.isShow === '1' ? '隐藏' : '展示' }}
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
@@ -128,6 +146,25 @@
|
|
|
<el-form-item label="酒店" prop="hotelName">
|
|
|
<el-input v-model="form.hotelName" placeholder="请输入酒店名称" />
|
|
|
</el-form-item>
|
|
|
+ <el-form-item label="道具关联">
|
|
|
+ <el-select v-model="form.itemId" placeholder="请选择关联的服务券-酒店道具" clearable filterable style="width: 300px">
|
|
|
+ <el-option v-for="item in itemOptions" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
+ </el-select>
|
|
|
+ <span class="form-inline-tip">用于APP判定订单是否需补差价</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="酒店时效">
|
|
|
+ <el-date-picker
|
|
|
+ :model-value="form.effectStartDate && form.effectEndDate ? [form.effectStartDate, form.effectEndDate] : null"
|
|
|
+ @update:model-value="(v) => setEffectRange(v)"
|
|
|
+ type="daterange"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ style="width: 300px"
|
|
|
+ />
|
|
|
+ <span class="form-inline-tip">仅时效区间内APP列表展示,留空不限制</span>
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="酒店简介" prop="introduction">
|
|
|
<el-input v-model="form.introduction" placeholder="请输入酒店简介" />
|
|
|
</el-form-item>
|
|
|
@@ -197,6 +234,10 @@
|
|
|
<el-form-item label="房间" :prop="'rooms.' + ri + '.roomName'" :rules="rules.roomName" label-width="80px">
|
|
|
<el-input v-model="room.roomName" placeholder="请输入房间名称" style="width: 260px" />
|
|
|
</el-form-item>
|
|
|
+ <el-form-item label="房间价值" label-width="80px">
|
|
|
+ <el-input-number v-model="room.roomValue" :min="0" :controls="false" placeholder="积分值" style="width: 160px" />
|
|
|
+ <span class="form-inline-tip">高于关联道具价值时APP显示补差价</span>
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="款式名" label-width="80px">
|
|
|
<el-input v-model="room.styleName" placeholder="APP房型标签" style="width: 260px" />
|
|
|
</el-form-item>
|
|
|
@@ -316,7 +357,8 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup name="HotelBooking">
|
|
|
-import { listHotel, getHotel, addHotel, updateHotel, delHotel } from '@/api/content/hotel';
|
|
|
+import { listHotel, getHotel, addHotel, updateHotel, delHotel, changeHotelShow } from '@/api/content/hotel';
|
|
|
+import { selectServiceItemsSelList } from '@/api/system/business/items';
|
|
|
import { uploadImageOnly } from '@/api/content/competitionZoneImage';
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
|
|
@@ -332,6 +374,15 @@ const queryParams = reactive({
|
|
|
});
|
|
|
const queryRef = ref(null);
|
|
|
|
|
|
+// 服务券-酒店道具选项(用于“道具关联”下拉与列表“关联道具”列名称回显)
|
|
|
+const itemOptions = ref([]);
|
|
|
+// 根据 itemId 取道具名称(列表展示用,未匹配到则显示ID)
|
|
|
+function itemNameById(itemId) {
|
|
|
+ if (!itemId) return '-';
|
|
|
+ const hit = itemOptions.value.find((i) => String(i.id) === String(itemId));
|
|
|
+ return hit ? hit.name : 'ID:' + itemId;
|
|
|
+}
|
|
|
+
|
|
|
// ========== 弹窗表单 ==========
|
|
|
const dialogVisible = ref(false);
|
|
|
const dialogTitle = ref('');
|
|
|
@@ -343,7 +394,16 @@ const MAX_IMAGES = 5;
|
|
|
|
|
|
// 时段:只含当天,剩余间数创建后可二次修改
|
|
|
const createPeriod = () => ({ id: null, startDate: null, endDate: null, remainingCount: null });
|
|
|
-const createRoom = () => ({ id: null, roomName: '', styleName: '', coverImage: '', roomIntro: '', roomImages: [], periods: [createPeriod()] });
|
|
|
+const createRoom = () => ({
|
|
|
+ id: null,
|
|
|
+ roomName: '',
|
|
|
+ styleName: '',
|
|
|
+ coverImage: '',
|
|
|
+ roomIntro: '',
|
|
|
+ roomValue: null,
|
|
|
+ roomImages: [],
|
|
|
+ periods: [createPeriod()]
|
|
|
+});
|
|
|
|
|
|
// 空白时段行/空白房型块:保存时过滤掉,不提交给后端
|
|
|
function isPeriodEmpty(p) {
|
|
|
@@ -361,6 +421,11 @@ const initForm = {
|
|
|
hotelName: '',
|
|
|
introduction: '',
|
|
|
location: '',
|
|
|
+ // 关联道具(服务券-酒店类型 items.id),用于APP判定是否补差价
|
|
|
+ itemId: null,
|
|
|
+ // 酒店时效(天到天),为空表示不限制;APP列表仅今天处于时效区间内才展示
|
|
|
+ effectStartDate: null,
|
|
|
+ effectEndDate: null,
|
|
|
// 留空由后端自动分配为「当前最大值+1」,避免默认值与已有记录撞车导致新增失败
|
|
|
sortOrder: null,
|
|
|
promoImages: [],
|
|
|
@@ -480,6 +545,10 @@ async function handleUpdate(row) {
|
|
|
hotelName: data.hotelName || '',
|
|
|
introduction: data.introduction || '',
|
|
|
location: data.location || '',
|
|
|
+ itemId: data.itemId ?? null,
|
|
|
+ effectStartDate: data.effectStartDate || null,
|
|
|
+ effectEndDate: data.effectEndDate || null,
|
|
|
+ isShow: data.isShow || '0',
|
|
|
// 库里若为 0/NULL 这类无效值,保持为空强制用户重填,不能兜底成 0(后端会拒绝保存)
|
|
|
sortOrder: data.sortOrder && data.sortOrder >= 1 ? data.sortOrder : null,
|
|
|
promoImages: (data.imageList || []).map((img) => img.imageUrl),
|
|
|
@@ -489,6 +558,7 @@ async function handleUpdate(row) {
|
|
|
styleName: room.styleName || '',
|
|
|
coverImage: room.coverImage || '',
|
|
|
roomIntro: room.roomIntro || '',
|
|
|
+ roomValue: room.roomValue ?? null,
|
|
|
roomImages: (room.imageList || []).map((img) => img.imageUrl),
|
|
|
periods: (room.periodList || []).map((p) => ({
|
|
|
id: p.id,
|
|
|
@@ -587,6 +657,12 @@ function setPeriodRange(period, value) {
|
|
|
period.endDate = value && value.length === 2 ? value[1] : null;
|
|
|
}
|
|
|
|
|
|
+// 酒店时效日期范围:选两个日期回填到 effectStartDate/effectEndDate,清空则均为 null(不限制)
|
|
|
+function setEffectRange(value) {
|
|
|
+ form.value.effectStartDate = value && value.length === 2 ? value[0] : null;
|
|
|
+ form.value.effectEndDate = value && value.length === 2 ? value[1] : null;
|
|
|
+}
|
|
|
+
|
|
|
// ========== 图片上传(仅上传OSS,不存图库) ==========
|
|
|
async function uploadFileToUrl(rawFile) {
|
|
|
if (!rawFile.type.startsWith('image/')) {
|
|
|
@@ -660,7 +736,35 @@ function moveImageDown(arr, index) {
|
|
|
[arr[index], arr[index + 1]] = [arr[index + 1], arr[index]];
|
|
|
}
|
|
|
|
|
|
+// ========== 展示/隐藏切换 ==========
|
|
|
+function handleChangeShow(row) {
|
|
|
+ // 当前为展示(1)则本次切为隐藏(0),反之切为展示
|
|
|
+ const nextShow = row.isShow === '1' ? '0' : '1';
|
|
|
+ const actionText = nextShow === '1' ? '展示' : '隐藏';
|
|
|
+ ElMessageBox.confirm(`确定${actionText}酒店「${row.hotelName}」在APP端的显示吗?`, '系统提示', {
|
|
|
+ type: 'warning',
|
|
|
+ confirmButtonText: '确认'
|
|
|
+ })
|
|
|
+ .then(async () => {
|
|
|
+ await changeHotelShow(row.id, nextShow);
|
|
|
+ ElMessage.success(`${actionText}成功`);
|
|
|
+ getList();
|
|
|
+ })
|
|
|
+ .catch(() => {});
|
|
|
+}
|
|
|
+
|
|
|
+// 加载服务券道具选项(供“道具关联”下拉与列表“关联道具”列名称回显)
|
|
|
+async function loadItemOptions() {
|
|
|
+ try {
|
|
|
+ const res = await selectServiceItemsSelList();
|
|
|
+ itemOptions.value = res.data || [];
|
|
|
+ } catch (err) {
|
|
|
+ console.error('加载道具列表失败:', err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
getList();
|
|
|
+loadItemOptions();
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
@@ -714,6 +818,11 @@ getList();
|
|
|
color: #303133;
|
|
|
}
|
|
|
|
|
|
+.cell-room-value {
|
|
|
+ font-weight: normal;
|
|
|
+ color: #e6a23c;
|
|
|
+}
|
|
|
+
|
|
|
.cell-room-line {
|
|
|
color: #606266;
|
|
|
padding-left: 8px;
|