Files
pj_admin_cloud/src/views/canteen/boxesBooking/form.vue
T
2025-08-14 12:05:10 +08:00

349 lines
9.7 KiB
Vue

<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable width="800px">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="120px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="商户" prop="merchantId">
<select-mch v-model="form.merchantId" @change="onMerchantChange" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="餐桌" prop="boxesId">
<el-select v-model="form.boxesId" placeholder="请选择餐桌" clearable>
<el-option
v-for="item in tableList"
:key="item.id"
:label="`${item.tableNumber} - ${item.name} (${item.capacity}人)`"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="餐次类型" prop="mealType">
<el-select v-model="form.mealType" placeholder="请选择餐次类型" @change="onMealTypeChange">
<el-option label="早餐(07:00-09:00)" value="1" />
<el-option label="中餐(11:30-13:30)" value="2" />
<el-option label="晚餐(17:30-19:30)" value="3" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="预约日期" prop="bookingDate">
<el-date-picker
v-model="form.bookingDate"
type="date"
placeholder="请选择预约日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:disabled-date="disabledDate"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="预约人数" prop="bookingPeople">
<el-input-number v-model="form.bookingPeople" :min="1" :max="20" placeholder="请输入预约人数" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="开始时间" prop="startTime">
<el-time-select
v-model="form.startTime"
placeholder="请选择开始时间"
:disabled="!form.mealType || !form.bookingDate"
:selectable-range="getSelectableTimeRange()"
:step="30"
format="HH:mm"
value-format="HH:mm"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="结束时间" prop="endTime">
<el-time-select
v-model="form.endTime"
placeholder="请选择结束时间"
:disabled="!form.startTime"
:selectable-range="getSelectableTimeRange()"
:step="30"
format="HH:mm"
value-format="HH:mm"
/>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" :rows="3" />
</el-form-item>
</el-col>
</el-row>
<!-- 时间段选择区域 -->
<div v-if="form.boxesId && form.mealType && form.bookingDate" class="time-slot-section">
<el-divider content-position="left">可用时间段</el-divider>
<div class="time-slots">
<div v-for="slot in availableTimeSlots" :key="slot.startTime" class="time-slot-item">
<el-button
:type="isTimeSlotSelected(slot) ? 'success' : 'primary'"
:plain="!isTimeSlotSelected(slot)"
size="small"
@click="toggleTimeSlot(slot)"
:disabled="slot.status === 'booked'"
>
{{ slot.startTime }} - {{ slot.endTime }}
<el-tag v-if="slot.status === 'booked'" type="danger" size="small">已预约</el-tag>
</el-button>
</div>
</div>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="BoxesBookingDialog">
import { useDict } from '/@/hooks/dict';
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/canteen/boxesBooking';
import { fetchList as fetchTableList } from '/@/api/canteen/boxesInfo';
import { rule } from '/@/utils/validate';
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const tableList = ref<any[]>([]);
const availableTimeSlots = ref<any[]>([]);
const selectedTimeSlots = ref<any[]>([]);
// 提交表单数据
const form = reactive({
id: '',
merchantId: '',
boxesId: '',
mealType: '',
bookingDate: '',
startTime: '',
endTime: '',
bookingPeople: 1,
remark: '',
status: '0',
});
// 定义校验规则
const dataRules = ref({
merchantId: [{ required: true, message: '商户不能为空', trigger: 'change' }],
boxesId: [{ required: true, message: '餐桌不能为空', trigger: 'change' }],
mealType: [{ required: true, message: '餐次类型不能为空', trigger: 'change' }],
bookingDate: [{ required: true, message: '预约日期不能为空', trigger: 'change' }],
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }],
bookingPeople: [{ required: true, message: '预约人数不能为空', trigger: 'blur' }],
});
// 禁用过去的日期
const disabledDate = (time: Date) => {
return time.getTime() < Date.now() - 8.64e7;
};
// 获取可选时间范围
const getSelectableTimeRange = () => {
switch (form.mealType) {
case '1': return '07:00:00,09:00:00'; // 早餐
case '2': return '11:30:00,13:30:00'; // 中餐
case '3': return '17:30:00,19:30:00'; // 晚餐
default: return '00:00:00,23:59:59';
}
};
// 商户变化时加载餐桌列表
const onMerchantChange = async (merchantId: string) => {
if (merchantId) {
try {
const res = await fetchTableList({ merchantId, status: '0' });
tableList.value = res.data?.records || [];
} catch (error) {
console.error('加载餐桌列表失败:', error);
}
} else {
tableList.value = [];
}
form.boxesId = '';
};
// 餐次类型变化时重置时间
const onMealTypeChange = () => {
form.startTime = '';
form.endTime = '';
selectedTimeSlots.value = [];
};
// 检查时间段是否被选中
const isTimeSlotSelected = (slot: any) => {
return selectedTimeSlots.value.some(selected =>
selected.startTime === slot.startTime && selected.endTime === slot.endTime
);
};
// 切换时间段选择
const toggleTimeSlot = (slot: any) => {
if (slot.status === 'booked') return;
const isSelected = isTimeSlotSelected(slot);
if (isSelected) {
selectedTimeSlots.value = selectedTimeSlots.value.filter(selected =>
!(selected.startTime === slot.startTime && selected.endTime === slot.endTime)
);
} else {
selectedTimeSlots.value.push(slot);
}
// 自动填充开始和结束时间
if (selectedTimeSlots.value.length > 0) {
const sortedSlots = selectedTimeSlots.value.sort((a, b) => a.startTime.localeCompare(b.startTime));
form.startTime = sortedSlots[0].startTime;
form.endTime = sortedSlots[sortedSlots.value.length - 1].endTime;
} else {
form.startTime = '';
form.endTime = '';
}
};
// 生成可用时间段
const generateTimeSlots = () => {
const slots = [];
let startHour, endHour;
switch (form.mealType) {
case '1': // 早餐
startHour = 7;
endHour = 9;
break;
case '2': // 中餐
startHour = 11.5;
endHour = 13.5;
break;
case '3': // 晚餐
startHour = 17.5;
endHour = 19.5;
break;
default:
return [];
}
for (let hour = startHour; hour < endHour; hour += 0.5) {
const startTime = `${Math.floor(hour).toString().padStart(2, '0')}:${(hour % 1 === 0.5 ? '30' : '00')}`;
const endTime = `${Math.floor(hour + 0.5).toString().padStart(2, '0')}:${((hour + 0.5) % 1 === 0.5 ? '30' : '00')}`;
slots.push({
startTime,
endTime,
status: 'available'
});
}
availableTimeSlots.value = slots;
};
// 监听表单变化
watch([() => form.boxesId, () => form.mealType, () => form.bookingDate], () => {
if (form.boxesId && form.mealType && form.bookingDate) {
generateTimeSlots();
// 这里可以调用API获取实际的时间段状态
// fetchTimeSlotStatus();
}
});
// 打开弹窗
const openDialog = (id: string) => {
visible.value = true;
form.id = '';
form.merchantId = '';
form.boxesId = '';
form.mealType = '';
form.bookingDate = '';
form.startTime = '';
form.endTime = '';
form.bookingPeople = 1;
form.remark = '';
selectedTimeSlots.value = [];
availableTimeSlots.value = [];
tableList.value = [];
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取预约信息
if (id) {
form.id = id;
getboxesBookingData(id);
}
};
// 提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
try {
loading.value = true;
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getboxesBookingData = (id: string) => {
loading.value = true;
getObj(id)
.then((res: any) => {
Object.assign(form, res.data);
// 加载餐桌列表
if (form.merchantId) {
onMerchantChange(form.merchantId);
}
})
.finally(() => {
loading.value = false;
});
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped>
.time-slot-section {
margin-top: 20px;
padding: 20px;
background: #f5f7fa;
border-radius: 8px;
}
.time-slots {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.time-slot-item {
margin-bottom: 10px;
}
</style>