|
|
@@ -1,6 +1,10 @@
|
|
|
<template>
|
|
|
<!-- 团购包管理 - 新增/编辑页面 -->
|
|
|
<div class="table-box" style="width: 100%; min-height: 100%; background-color: white">
|
|
|
+ <div class="header">
|
|
|
+ <el-button @click="goBack"> 返回 </el-button>
|
|
|
+ <h2 class="title">新建团购</h2>
|
|
|
+ </div>
|
|
|
<el-form :model="storeInfoModel" ref="ruleFormRef" :rules="rules" label-width="120px" class="formBox">
|
|
|
<div class="content">
|
|
|
<!-- 左侧内容区域 -->
|
|
|
@@ -235,7 +239,7 @@
|
|
|
<h3 style="font-weight: bold">购买须知:</h3>
|
|
|
<el-form-item label="有效期" prop="effectiveDateType">
|
|
|
<el-radio-group v-model="storeInfoModel.effectiveDateType" class="ml-4">
|
|
|
- <el-radio v-for="status in expirationDateList" :value="status.value" :key="status.value">
|
|
|
+ <el-radio v-for="status in expirationDateStatusList" :value="status.value" :key="status.value">
|
|
|
{{ status.label }}
|
|
|
</el-radio>
|
|
|
</el-radio-group>
|
|
|
@@ -247,9 +251,9 @@
|
|
|
<span class="expiration-label">天内有效</span>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="" prop="effectiveDateValue" v-else>
|
|
|
+ <el-form-item label="" prop="expirationDateList" v-else>
|
|
|
<el-date-picker
|
|
|
- v-model="storeInfoModel.effectiveDateValue"
|
|
|
+ v-model="storeInfoModel.expirationDateList"
|
|
|
type="daterange"
|
|
|
value-format="YYYY-MM-DD"
|
|
|
range-separator="-"
|
|
|
@@ -308,9 +312,9 @@
|
|
|
<el-form-item label="" prop="customUnavailableDates" v-else-if="storeInfoModel.disableDateType == 2">
|
|
|
<div class="date-picker-container">
|
|
|
<el-button :icon="Plus" class="add-date-btn" type="primary" @click="addDate"> 添加日期 </el-button>
|
|
|
- <div v-for="(item, index) in dates" :key="index" class="date-item">
|
|
|
+ <div v-for="(item, index) in storeInfoModel.disableDateList" :key="index" class="date-item">
|
|
|
<el-date-picker
|
|
|
- v-model="dates[index]"
|
|
|
+ v-model="storeInfoModel.disableDateList[index]"
|
|
|
type="daterange"
|
|
|
value-format="YYYY-MM-DD"
|
|
|
range-separator="-"
|
|
|
@@ -326,7 +330,7 @@
|
|
|
size="small"
|
|
|
class="delete-btn"
|
|
|
@click="removeDate(index)"
|
|
|
- v-show="dates.length > 1"
|
|
|
+ v-show="storeInfoModel.disableDateList.length > 1"
|
|
|
/>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -383,7 +387,6 @@
|
|
|
</el-form>
|
|
|
<!-- 底部按钮区域 -->
|
|
|
<div class="button-container">
|
|
|
- <el-button @click="goBack"> 返回 </el-button>
|
|
|
<el-button @click="handleSubmit('cg')"> 存草稿 </el-button>
|
|
|
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
|
|
|
</div>
|
|
|
@@ -448,7 +451,7 @@
|
|
|
*/
|
|
|
import { ref, reactive, onMounted, watch, nextTick, computed } from "vue";
|
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
-import { Plus, Delete, ArrowDown, ArrowUp, Picture, Check } from "@element-plus/icons-vue";
|
|
|
+import { Plus, Delete, ArrowDown, ArrowUp, Picture } from "@element-plus/icons-vue";
|
|
|
import {
|
|
|
saveStoreInfo,
|
|
|
editStoreInfo,
|
|
|
@@ -666,7 +669,7 @@ const rules = reactive({
|
|
|
trigger: "blur"
|
|
|
}
|
|
|
],
|
|
|
- effectiveDateValue: [
|
|
|
+ expirationDateList: [
|
|
|
{
|
|
|
required: true,
|
|
|
validator: (rule: any, value: any, callback: any) => {
|
|
|
@@ -704,7 +707,7 @@ const rules = reactive({
|
|
|
{
|
|
|
required: true,
|
|
|
validator: (rule: any, value: any, callback: any) => {
|
|
|
- if (storeInfoModel.value.unavailableDates === 1) {
|
|
|
+ if (storeInfoModel.value.disableDateType === 1) {
|
|
|
if (!value || value.length === 0) {
|
|
|
callback(new Error("至少需要选择一个星期"));
|
|
|
return;
|
|
|
@@ -719,7 +722,7 @@ const rules = reactive({
|
|
|
{
|
|
|
required: true,
|
|
|
validator: (rule: any, value: any, callback: any) => {
|
|
|
- if (storeInfoModel.value.unavailableDates === 1) {
|
|
|
+ if (storeInfoModel.value.disableDateType === 1) {
|
|
|
if (!value || value.length === 0) {
|
|
|
callback(new Error("至少需要选择一个节日"));
|
|
|
return;
|
|
|
@@ -735,21 +738,21 @@ const rules = reactive({
|
|
|
required: true,
|
|
|
validator: (rule: any, value: any, callback: any) => {
|
|
|
if (storeInfoModel.value.disableDateType === 2) {
|
|
|
- if (!dates.value || dates.value.length === 0) {
|
|
|
+ if (!storeInfoModel.value.disableDateList || storeInfoModel.value.disableDateList.length === 0) {
|
|
|
callback(new Error("至少需要添加一个自定义不可用日期"));
|
|
|
return;
|
|
|
}
|
|
|
const today = new Date();
|
|
|
today.setHours(0, 0, 0, 0);
|
|
|
// 验证每个日期项是否已填写
|
|
|
- for (let i = 0; i < dates.value.length; i++) {
|
|
|
- if (!dates.value[i] || dates.value[i].length !== 2) {
|
|
|
+ for (let i = 0; i < storeInfoModel.value.disableDateList.length; i++) {
|
|
|
+ if (!storeInfoModel.value.disableDateList[i] || storeInfoModel.value.disableDateList[i].length !== 2) {
|
|
|
callback(new Error(`第${i + 1}个日期项未完整填写`));
|
|
|
return;
|
|
|
}
|
|
|
// 验证开始时间和结束时间不能早于当前时间
|
|
|
- const startDate = new Date(dates.value[i][0]);
|
|
|
- const endDate = new Date(dates.value[i][1]);
|
|
|
+ const startDate = new Date(storeInfoModel.value.disableDateList[i][0]);
|
|
|
+ const endDate = new Date(storeInfoModel.value.disableDateList[i][1]);
|
|
|
if (startDate < today) {
|
|
|
callback(new Error(`第${i + 1}个日期项的开始时间不能早于当前时间`));
|
|
|
return;
|
|
|
@@ -814,7 +817,7 @@ const storeInfoModel = ref<any>({
|
|
|
// 库存数量
|
|
|
inventoryNum: "",
|
|
|
// 每人限购设置:0-不限量,1-自定义限购数量
|
|
|
- quotaValueStr: 0,
|
|
|
+ quotaType: 0,
|
|
|
// 自定义限购数量(当quotaType为1时必填)
|
|
|
quotaValue: 0,
|
|
|
// 套餐内容(虚拟字段,用于表单验证,实际数据在 lifeGroupBuyThalis 变量中)
|
|
|
@@ -826,13 +829,15 @@ const storeInfoModel = ref<any>({
|
|
|
// 有效期设置:0-指定天数,1-指定时间段内可用
|
|
|
effectiveDateType: 0,
|
|
|
expirationDate: 0,
|
|
|
- effectiveDateValue: [],
|
|
|
+ expirationDateList: [],
|
|
|
// 不可用日期设置:0-全部日期可用,1-限制日期,2-自定义不可用日期
|
|
|
disableDateType: 0,
|
|
|
// 限制日期 - 星期选择(数组,存储选中的星期值)
|
|
|
unavailableWeekdays: [],
|
|
|
// 限制日期 - 节日选择(数组,存储选中的节日值)
|
|
|
unavailableHolidays: [],
|
|
|
+ // 自定义不可用日期列表
|
|
|
+ disableDateList: [],
|
|
|
// 预约规则
|
|
|
reservationRules: "",
|
|
|
// 使用规则
|
|
|
@@ -861,26 +866,22 @@ const storeStatusList = ref([
|
|
|
{ value: 1, label: "设置售卖时间" }
|
|
|
]);
|
|
|
|
|
|
-// 每人限购设置列表
|
|
|
+// 每人限购设置
|
|
|
const perList = ref([
|
|
|
{ value: 0, label: "不限量" },
|
|
|
{ value: 1, label: "自定义限购数量" }
|
|
|
]);
|
|
|
-// 每人限购设置列表
|
|
|
-const expirationDateList = ref([
|
|
|
+// 有效期设置
|
|
|
+const expirationDateStatusList = ref([
|
|
|
{ value: 0, label: "指定天数" },
|
|
|
{ value: 1, label: "指定时间段内可用" }
|
|
|
]);
|
|
|
-// 每人限购设置列表
|
|
|
+// 有效期设置
|
|
|
const unavailableDatesList = ref([
|
|
|
{ value: 0, label: "全部日期可用" },
|
|
|
{ value: 1, label: "限制日期" },
|
|
|
{ value: 2, label: "自定义不可用日期" }
|
|
|
]);
|
|
|
-//图片集合
|
|
|
-const videoUrlList = ref<string[]>([]);
|
|
|
-// 自定义不可用日期列表
|
|
|
-const dates = ref([]);
|
|
|
|
|
|
// 星期选项列表
|
|
|
const weekdayList = ref([
|
|
|
@@ -896,8 +897,8 @@ const weekdayList = ref([
|
|
|
// 节日选项列表
|
|
|
const holidayList: any = ref([]);
|
|
|
|
|
|
-// 菜品选项列表(这里需要根据实际API获取,暂时使用示例数据)
|
|
|
-const dishOptions = ref([]);
|
|
|
+// 菜品选项列表
|
|
|
+const dishOptions: any = ref([]);
|
|
|
|
|
|
// 套餐内容(数组,每个元素是一个分组)
|
|
|
const lifeGroupBuyThalis = ref([
|
|
|
@@ -938,9 +939,6 @@ const visibleGroups = computed(() => {
|
|
|
return lifeGroupBuyThalis.value.map((group, index) => ({ group, originalIndex: index }));
|
|
|
});
|
|
|
|
|
|
-// 标记是否跳过最后一个分组的验证(用于添加新分组时)
|
|
|
-let skipLastGroupValidation = false;
|
|
|
-
|
|
|
// ==================== 监听器 ====================
|
|
|
|
|
|
/**
|
|
|
@@ -948,12 +946,12 @@ let skipLastGroupValidation = false;
|
|
|
* 当切换到自定义不可用日期时,确保至少有一个日期项
|
|
|
*/
|
|
|
watch(
|
|
|
- () => storeInfoModel.value.unavailableDates,
|
|
|
+ () => storeInfoModel.value.disableDateType,
|
|
|
newVal => {
|
|
|
if (newVal === 2) {
|
|
|
- // 切换到自定义不可用日期时,如果dates为空,则添加一个默认项
|
|
|
- if (!dates.value || dates.value.length === 0) {
|
|
|
- dates.value = [null];
|
|
|
+ // 切换到自定义不可用日期时,如果disableDateList为空,则添加一个默认项
|
|
|
+ if (!storeInfoModel.value.disableDateList || storeInfoModel.value.disableDateList.length === 0) {
|
|
|
+ storeInfoModel.value.disableDateList = [null];
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
@@ -990,6 +988,26 @@ watch(
|
|
|
}
|
|
|
);
|
|
|
|
|
|
+/**
|
|
|
+ * 监听所有分组的类别值变化
|
|
|
+ * 当任何一个分组的类别值改变时,重新验证所有分组的类别字段,确保重复性校验实时生效
|
|
|
+ */
|
|
|
+watch(
|
|
|
+ () => lifeGroupBuyThalis.value.map(group => group.groupName),
|
|
|
+ () => {
|
|
|
+ // 当类别值改变时,重新验证所有分组的类别字段
|
|
|
+ nextTick(() => {
|
|
|
+ if (packageFormRef.value) {
|
|
|
+ lifeGroupBuyThalis.value.forEach((_, groupIndex) => {
|
|
|
+ const prop = `groups.${groupIndex}.groupName`;
|
|
|
+ packageFormRef.value?.validateField(prop, () => {});
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+);
|
|
|
+
|
|
|
// ==================== 生命周期钩子 ====================
|
|
|
|
|
|
/**
|
|
|
@@ -999,25 +1017,31 @@ watch(
|
|
|
onMounted(async () => {
|
|
|
id.value = (route.query.id as string) || "";
|
|
|
type.value = (route.query.type as string) || "";
|
|
|
- let param = {
|
|
|
- // phone: localGet("iphone")
|
|
|
- phone: "18641153170"
|
|
|
- };
|
|
|
- const resP: any = await getUserByPhone(param);
|
|
|
- if (resP.data && resP.data.storeId) {
|
|
|
- localSet("createdId", resP.data.storeId);
|
|
|
- const resD: any = await getDetail({
|
|
|
- id: localGet("createdId")
|
|
|
- });
|
|
|
- if (resD.data && resD.data.commissionRate) {
|
|
|
- localSet("commissionRate", resD.data.commissionRate);
|
|
|
- }
|
|
|
- if (resD.data && resD.data.businessSection) {
|
|
|
- localSet("businessSection", resD.data.businessSection);
|
|
|
- }
|
|
|
- } else {
|
|
|
- ElMessage.warning("请完成商家入驻后再进行新建团购");
|
|
|
- }
|
|
|
+ // 不要删除-开始
|
|
|
+ // let param = {
|
|
|
+ // // phone: localGet("iphone")
|
|
|
+ // phone: "18641153170"
|
|
|
+ // };
|
|
|
+ // const resP: any = await getUserByPhone(param);
|
|
|
+ // if (resP.data && resP.data.storeId) {
|
|
|
+ // localSet("createdId", resP.data.storeId);
|
|
|
+ // const resD: any = await getDetail({
|
|
|
+ // id: localGet("createdId")
|
|
|
+ // });
|
|
|
+ // if (resD.data && resD.data.commissionRate) {
|
|
|
+ // localSet("commissionRate", resD.data.commissionRate);
|
|
|
+ // }
|
|
|
+ // if (resD.data && resD.data.businessSection) {
|
|
|
+ // localSet("businessSection", resD.data.businessSection);
|
|
|
+ // }
|
|
|
+ // } else {
|
|
|
+ // ElMessage.warning("请完成商家入驻后再进行新建团购");
|
|
|
+ // }
|
|
|
+ // if (!getGroupCombination()) {
|
|
|
+ // ElMessage.warning("请完成商家入驻后重新登录再进行新建团购");
|
|
|
+ // return;
|
|
|
+ // }
|
|
|
+ // 不要删除-结束
|
|
|
let params = {
|
|
|
year: new Date().getFullYear(),
|
|
|
page: 1,
|
|
|
@@ -1025,25 +1049,22 @@ onMounted(async () => {
|
|
|
openFlag: 1,
|
|
|
holidayName: ""
|
|
|
};
|
|
|
- let res = await getHolidayList(params);
|
|
|
+ let res: any = await getHolidayList(params);
|
|
|
if (res && res.code == 200) {
|
|
|
holidayList.value = res.data.records;
|
|
|
}
|
|
|
if (type.value != "add") {
|
|
|
let res: any = await getStoreDetail({ id: id.value } as any);
|
|
|
- storeInfoModel.value = res.data as any;
|
|
|
- videoUrlList.value = (res.data as any).businessLicenseAddress || [];
|
|
|
+ storeInfoModel.value = { ...storeInfoModel.value, ...res.data.lifeGroupBuyMain };
|
|
|
let imageList: any[] = [];
|
|
|
handleImageParam((res.data as any).businessLicenseAddress || [], imageList);
|
|
|
storeInfoModel.value.imageList = imageList;
|
|
|
+ storeInfoModel.value.expirationDateList = storeInfoModel.value.expirationDateList.split(";");
|
|
|
+ storeInfoModel.value.invoiceInformation = storeInfoModel.value.invoiceType.split(";");
|
|
|
// 确保星期和节日字段存在
|
|
|
- if (!storeInfoModel.value.unavailableWeekdays) {
|
|
|
- storeInfoModel.value.unavailableWeekdays = [];
|
|
|
- }
|
|
|
- if (!storeInfoModel.value.unavailableHolidays) {
|
|
|
- storeInfoModel.value.unavailableHolidays = [];
|
|
|
- }
|
|
|
- // 确保套餐内容字段存在
|
|
|
+ const listVal = storeInfoModel.value.disableDateValue.split(";");
|
|
|
+ storeInfoModel.value.unavailableWeekdays = listVal[0].split(",");
|
|
|
+ storeInfoModel.value.unavailableHolidays = listVal[1].split(",");
|
|
|
if (res.data.lifeGroupBuyThalis && res.data.lifeGroupBuyThalis.length > 0) {
|
|
|
lifeGroupBuyThalis.value = res.data.lifeGroupBuyThalis;
|
|
|
// 确保每个分组都有必要的字段
|
|
|
@@ -1080,22 +1101,15 @@ onMounted(async () => {
|
|
|
groupCollapsedStates.value = [false];
|
|
|
}
|
|
|
// 确保自定义不可用日期字段存在
|
|
|
- if (storeInfoModel.value.unavailableDates === 2) {
|
|
|
- if (!dates.value || dates.value.length === 0) {
|
|
|
- dates.value = [null];
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 新增模式下,如果默认选择自定义不可用日期,确保dates至少有一个元素
|
|
|
- if (storeInfoModel.value.unavailableDates === 2) {
|
|
|
- if (!dates.value || dates.value.length === 0) {
|
|
|
- dates.value = [null];
|
|
|
+ if (storeInfoModel.value.disableDateType === 2) {
|
|
|
+ // 后端直接返回 disableDateList 字段,如果为空则初始化为默认值
|
|
|
+ if (!storeInfoModel.value.disableDateList || storeInfoModel.value.disableDateList.length === 0) {
|
|
|
+ storeInfoModel.value.disableDateList = [null];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
// ==================== 事件处理函数 ====================
|
|
|
-
|
|
|
/**
|
|
|
* 返回上一页
|
|
|
*/
|
|
|
@@ -1161,7 +1175,7 @@ const handlePictureCardPreview = (file: any) => {
|
|
|
* 添加自定义不可用日期
|
|
|
*/
|
|
|
const addDate = () => {
|
|
|
- dates.value.push(null);
|
|
|
+ storeInfoModel.value.disableDateList.push(null);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -1169,11 +1183,15 @@ const addDate = () => {
|
|
|
* @param index 要删除的日期索引
|
|
|
*/
|
|
|
const removeDate = (index: number) => {
|
|
|
- if (dates.value.length <= 1) {
|
|
|
+ if (storeInfoModel.value.disableDateList.length <= 1) {
|
|
|
ElMessage.warning("至少需要保留一个日期项");
|
|
|
return;
|
|
|
}
|
|
|
- dates.value.splice(index, 1);
|
|
|
+ storeInfoModel.value.disableDateList.splice(index, 1);
|
|
|
+ // 删除日期项后,重新验证表单以清除旧的验证错误
|
|
|
+ nextTick(() => {
|
|
|
+ ruleFormRef.value?.validateField("customUnavailableDates");
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -1232,120 +1250,71 @@ const addGroup = () => {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 验证所有现有分组是否填写完整
|
|
|
- for (let i = 0; i < lifeGroupBuyThalis.value.length; i++) {
|
|
|
- const group = lifeGroupBuyThalis.value[i];
|
|
|
-
|
|
|
- // 验证类别
|
|
|
- if (!group.groupName || group.groupName.trim() === "") {
|
|
|
- ElMessage.warning("请先完成现有分组的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 验证是否有菜品
|
|
|
- if (!group.dishes || group.dishes.length === 0) {
|
|
|
- ElMessage.warning("请先完成现有分组的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 验证当前分组中每个菜品是否填写完整
|
|
|
- for (let j = 0; j < group.dishes.length; j++) {
|
|
|
- const dish = group.dishes[j];
|
|
|
-
|
|
|
- // 验证菜品是否已选择
|
|
|
- if (!dish.detailId) {
|
|
|
+ // 使用表单验证来验证所有现有分组
|
|
|
+ if (packageFormRef.value) {
|
|
|
+ packageFormRef.value.validate((valid: boolean) => {
|
|
|
+ if (!valid) {
|
|
|
ElMessage.warning("请先完成现有分组的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
- });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 验证数量是否已填写
|
|
|
- if (!dish.qty || dish.qty.toString().trim() === "") {
|
|
|
- ElMessage.warning("请先完成现有分组的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 验证数量格式
|
|
|
- const quantityNum = Number(dish.qty);
|
|
|
- if (isNaN(quantityNum) || quantityNum <= 0 || !Number.isInteger(quantityNum)) {
|
|
|
- ElMessage.warning("请先完成现有分组的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
+ // 验证通过,添加新分组
|
|
|
+ lifeGroupBuyThalis.value.push({
|
|
|
+ groupName: "",
|
|
|
+ dishes: [
|
|
|
+ {
|
|
|
+ detailId: "",
|
|
|
+ dishName: "",
|
|
|
+ dishPrice: "",
|
|
|
+ dishImage: "",
|
|
|
+ qty: "",
|
|
|
+ dishesUnit: ""
|
|
|
}
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 记录旧分组的数量,用于后续只验证旧分组
|
|
|
- const oldGroupsCount = lifeGroupBuyThalis.value.length;
|
|
|
-
|
|
|
- // 添加新分组
|
|
|
- lifeGroupBuyThalis.value.push({
|
|
|
- groupName: "",
|
|
|
- dishes: [
|
|
|
- {
|
|
|
- detailId: "",
|
|
|
- dishName: "",
|
|
|
- dishPrice: "",
|
|
|
- dishImage: "",
|
|
|
- qty: "",
|
|
|
- dishesUnit: ""
|
|
|
- }
|
|
|
- ]
|
|
|
- });
|
|
|
+ ]
|
|
|
+ });
|
|
|
|
|
|
- // 初始化新分组的收起状态为展开
|
|
|
- groupCollapsedStates.value.push(false);
|
|
|
+ // 初始化新分组的收起状态为展开
|
|
|
+ groupCollapsedStates.value.push(false);
|
|
|
|
|
|
- // 清除表单验证状态,避免新分组显示验证错误
|
|
|
- // 使用延迟清除,确保在验证规则更新和 DOM 渲染完成后再清除
|
|
|
- nextTick(() => {
|
|
|
- requestAnimationFrame(() => {
|
|
|
- setTimeout(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- // 只清除新添加分组的验证状态
|
|
|
- const newGroupIndex = lifeGroupBuyThalis.value.length - 1;
|
|
|
- const propsToClear = [
|
|
|
- `groups.${newGroupIndex}.groupName`,
|
|
|
- `groups.${newGroupIndex}.dishes.0.detailId`,
|
|
|
- `groups.${newGroupIndex}.dishes.0.qty`
|
|
|
- ];
|
|
|
- // Element Plus 的 clearValidate 可以接受字符串数组
|
|
|
- propsToClear.forEach(prop => {
|
|
|
- packageFormRef.value?.clearValidate(prop);
|
|
|
- });
|
|
|
+ // 清除表单验证状态,避免新分组显示验证错误
|
|
|
+ // 使用延迟清除,确保在验证规则更新和 DOM 渲染完成后再清除
|
|
|
+ nextTick(() => {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ if (packageFormRef.value) {
|
|
|
+ // 只清除新添加分组的验证状态
|
|
|
+ const newGroupIndex = lifeGroupBuyThalis.value.length - 1;
|
|
|
+ const propsToClear = [
|
|
|
+ `groups.${newGroupIndex}.groupName`,
|
|
|
+ `groups.${newGroupIndex}.dishes.0.detailId`,
|
|
|
+ `groups.${newGroupIndex}.dishes.0.qty`
|
|
|
+ ];
|
|
|
+ // Element Plus 的 clearValidate 可以接受字符串数组
|
|
|
+ propsToClear.forEach(prop => {
|
|
|
+ packageFormRef.value?.clearValidate(prop);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, 150);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 如果表单引用不存在,直接添加(兜底逻辑)
|
|
|
+ lifeGroupBuyThalis.value.push({
|
|
|
+ groupName: "",
|
|
|
+ dishes: [
|
|
|
+ {
|
|
|
+ detailId: "",
|
|
|
+ dishName: "",
|
|
|
+ dishPrice: "",
|
|
|
+ dishImage: "",
|
|
|
+ qty: "",
|
|
|
+ dishesUnit: ""
|
|
|
}
|
|
|
- skipLastGroupValidation = false;
|
|
|
- }, 150);
|
|
|
+ ]
|
|
|
});
|
|
|
- });
|
|
|
+ groupCollapsedStates.value.push(false);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -1443,106 +1412,103 @@ const addDish = (groupIndex: number) => {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 验证当前分组是否填写完整
|
|
|
- // 验证类别
|
|
|
- if (!group.groupName || group.groupName.trim() === "") {
|
|
|
- ElMessage.warning("请先完成现有菜品的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
+ // 使用表单验证来验证当前分组的所有字段
|
|
|
+ if (packageFormRef.value) {
|
|
|
+ // 收集当前分组的所有字段路径
|
|
|
+ const fieldsToValidate: string[] = [];
|
|
|
|
|
|
- // 验证是否有菜品
|
|
|
- if (!group.dishes || group.dishes.length === 0) {
|
|
|
- ElMessage.warning("请先完成现有菜品的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
+ // 添加类别字段
|
|
|
+ fieldsToValidate.push(`groups.${groupIndex}.groupName`);
|
|
|
|
|
|
- // 验证当前分组中每个菜品是否填写完整
|
|
|
- for (let j = 0; j < group.dishes.length; j++) {
|
|
|
- const dish = group.dishes[j];
|
|
|
-
|
|
|
- // 验证菜品是否已选择
|
|
|
- if (!dish.detailId) {
|
|
|
- ElMessage.warning("请先完成现有菜品的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
+ // 添加当前分组所有菜品的字段
|
|
|
+ if (group.dishes && group.dishes.length > 0) {
|
|
|
+ group.dishes.forEach((_, dishIndex) => {
|
|
|
+ fieldsToValidate.push(`groups.${groupIndex}.dishes.${dishIndex}.detailId`);
|
|
|
+ fieldsToValidate.push(`groups.${groupIndex}.dishes.${dishIndex}.qty`);
|
|
|
});
|
|
|
- return;
|
|
|
}
|
|
|
|
|
|
- // 验证数量是否已填写
|
|
|
- if (!dish.qty || dish.qty.toString().trim() === "") {
|
|
|
- ElMessage.warning("请先完成现有菜品的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
+ if (fieldsToValidate.length === 0) {
|
|
|
+ // 如果没有字段需要验证,直接添加
|
|
|
+ if (!group.dishes) {
|
|
|
+ group.dishes = [];
|
|
|
+ }
|
|
|
+ group.dishes.push({
|
|
|
+ detailId: "",
|
|
|
+ dishName: "",
|
|
|
+ dishPrice: "",
|
|
|
+ dishImage: "",
|
|
|
+ qty: "",
|
|
|
+ dishesUnit: ""
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 验证数量格式
|
|
|
- const quantityNum = Number(dish.qty);
|
|
|
- if (isNaN(quantityNum) || quantityNum <= 0 || !Number.isInteger(quantityNum)) {
|
|
|
- ElMessage.warning("请先完成现有菜品的填写");
|
|
|
- // 触发表单验证,显示验证错误
|
|
|
- nextTick(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- packageFormRef.value.validate(() => {});
|
|
|
- }
|
|
|
+ // 使用 Promise 等待所有字段验证完成
|
|
|
+ const validatePromises = fieldsToValidate.map(field => {
|
|
|
+ return new Promise<boolean>(resolve => {
|
|
|
+ packageFormRef.value?.validateField(field, (isValid: boolean) => {
|
|
|
+ resolve(isValid);
|
|
|
+ });
|
|
|
});
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
+ });
|
|
|
|
|
|
- // 所有验证通过,添加新菜品
|
|
|
- if (!group.dishes) {
|
|
|
- group.dishes = [];
|
|
|
- }
|
|
|
- group.dishes.push({
|
|
|
- detailId: "",
|
|
|
- dishName: "",
|
|
|
- dishPrice: "",
|
|
|
- dishImage: "",
|
|
|
- qty: "",
|
|
|
- dishesUnit: ""
|
|
|
- });
|
|
|
+ Promise.all(validatePromises).then(results => {
|
|
|
+ const allValid = results.every(result => result === true);
|
|
|
|
|
|
- // 清除新添加菜品的验证状态,避免立即显示验证错误
|
|
|
- // 使用延迟清除,确保在验证规则更新和 DOM 渲染完成后再清除
|
|
|
- nextTick(() => {
|
|
|
- requestAnimationFrame(() => {
|
|
|
- setTimeout(() => {
|
|
|
- if (packageFormRef.value) {
|
|
|
- // 只清除新添加菜品的验证状态
|
|
|
- const newDishIndex = group.dishes.length - 1;
|
|
|
- const propsToClear = [
|
|
|
- `groups.${groupIndex}.dishes.${newDishIndex}.detailId`,
|
|
|
- `groups.${groupIndex}.dishes.${newDishIndex}.qty`
|
|
|
- ];
|
|
|
- // Element Plus 的 clearValidate 可以接受字符串
|
|
|
- propsToClear.forEach(prop => {
|
|
|
- packageFormRef.value?.clearValidate(prop);
|
|
|
- });
|
|
|
- }
|
|
|
- }, 150);
|
|
|
+ if (!allValid) {
|
|
|
+ ElMessage.warning("请先完成现有菜品的填写");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证通过,添加新菜品
|
|
|
+ if (!group.dishes) {
|
|
|
+ group.dishes = [];
|
|
|
+ }
|
|
|
+ group.dishes.push({
|
|
|
+ detailId: "",
|
|
|
+ dishName: "",
|
|
|
+ dishPrice: "",
|
|
|
+ dishImage: "",
|
|
|
+ qty: "",
|
|
|
+ dishesUnit: ""
|
|
|
+ });
|
|
|
+
|
|
|
+ // 清除新添加菜品的验证状态,避免立即显示验证错误
|
|
|
+ // 使用延迟清除,确保在验证规则更新和 DOM 渲染完成后再清除
|
|
|
+ nextTick(() => {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ if (packageFormRef.value) {
|
|
|
+ // 只清除新添加菜品的验证状态
|
|
|
+ const newDishIndex = group.dishes.length - 1;
|
|
|
+ const propsToClear = [
|
|
|
+ `groups.${groupIndex}.dishes.${newDishIndex}.detailId`,
|
|
|
+ `groups.${groupIndex}.dishes.${newDishIndex}.qty`
|
|
|
+ ];
|
|
|
+ // Element Plus 的 clearValidate 可以接受字符串
|
|
|
+ propsToClear.forEach(prop => {
|
|
|
+ packageFormRef.value?.clearValidate(prop);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, 150);
|
|
|
+ });
|
|
|
+ });
|
|
|
});
|
|
|
- });
|
|
|
+ } else {
|
|
|
+ // 如果表单引用不存在,直接添加(兜底逻辑)
|
|
|
+ if (!group.dishes) {
|
|
|
+ group.dishes = [];
|
|
|
+ }
|
|
|
+ group.dishes.push({
|
|
|
+ detailId: "",
|
|
|
+ dishName: "",
|
|
|
+ dishPrice: "",
|
|
|
+ dishImage: "",
|
|
|
+ qty: "",
|
|
|
+ dishesUnit: ""
|
|
|
+ });
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -1569,7 +1535,7 @@ const openDishDialog = async (groupIndex: number, dishIndex: number) => {
|
|
|
currentDishIndex.value = dishIndex;
|
|
|
dishSearchKeyword.value = "";
|
|
|
const params = {
|
|
|
- storeId: 361,
|
|
|
+ storeId: 104,
|
|
|
phoneId: 18641153170,
|
|
|
dishType: 0
|
|
|
};
|
|
|
@@ -1679,6 +1645,31 @@ const packageFormRules = computed(() => {
|
|
|
required: true,
|
|
|
message: `第${groupIndex + 1}个分组的类别不能为空`,
|
|
|
trigger: "blur"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ validator: (rule: any, value: any, callback: any) => {
|
|
|
+ // 如果值为空,由 required 规则处理,这里直接通过
|
|
|
+ if (!value || value.toString().trim() === "") {
|
|
|
+ callback();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const trimmedValue = value.toString().trim();
|
|
|
+ // 检查是否与其他分组的类别重复
|
|
|
+ for (let i = 0; i < lifeGroupBuyThalis.value.length; i++) {
|
|
|
+ // 跳过当前分组
|
|
|
+ if (i === groupIndex) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const otherGroupName = lifeGroupBuyThalis.value[i].groupName;
|
|
|
+ // 如果其他分组的类别值与当前值相同(忽略前后空格),则重复
|
|
|
+ if (otherGroupName && otherGroupName.toString().trim() === trimmedValue) {
|
|
|
+ callback(new Error(`第${groupIndex + 1}个分组的类别与第${i + 1}个分组的类别重复`));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ callback();
|
|
|
+ },
|
|
|
+ trigger: "blur"
|
|
|
}
|
|
|
];
|
|
|
|
|
|
@@ -1734,29 +1725,30 @@ const packageFormRules = computed(() => {
|
|
|
const handleSubmit = (type?) => {
|
|
|
let params: any = { ...storeInfoModel.value };
|
|
|
if (params.effectiveDateType == 0) {
|
|
|
- params.couponCompDate = `用户购买${params.expirationDate}天内有效`;
|
|
|
+ // params.couponCompDate = `用户购买${params.expirationDate}天内有效`;
|
|
|
} else {
|
|
|
- params.effectiveDateValue = params.effectiveDateValue.join(",");
|
|
|
+ params.expirationDateList = params.expirationDateList.join(",");
|
|
|
}
|
|
|
if (params.disableDateType == 1) {
|
|
|
params.disableDateValue = params.unavailableWeekdays.join(",") + ";" + params.unavailableHolidays.join(",");
|
|
|
+ // params.disableDateValue = [params.unavailableWeekdays, params.unavailableHolidays];
|
|
|
delete params.unavailableWeekdays;
|
|
|
delete params.unavailableHolidays;
|
|
|
} else if (params.disableDateType == 2) {
|
|
|
- params.disableDateValue = dates.value.map(subArray => subArray.join(",")).join(";");
|
|
|
+ // params.disableDateValue = params.disableDateList.map(subArray => subArray.join(",")).join(";");
|
|
|
}
|
|
|
params.invoiceType = params.invoiceInformation.join(",");
|
|
|
- const output = lifeGroupBuyThalis.value.flatMap(group =>
|
|
|
- group.dishes.map(dish => ({
|
|
|
- groupName: group.groupName,
|
|
|
- detailId: dish.detailId,
|
|
|
- qty: dish.qty,
|
|
|
- dishPrice: dish.dishPrice
|
|
|
- }))
|
|
|
- );
|
|
|
+ // const output = lifeGroupBuyThalis.value.flatMap(group =>
|
|
|
+ // group.dishes.map(dish => ({
|
|
|
+ // groupName: group.groupName,
|
|
|
+ // detailId: dish.detailId,
|
|
|
+ // qty: dish.qty,
|
|
|
+ // dishPrice: dish.dishPrice
|
|
|
+ // }))
|
|
|
+ // );
|
|
|
const paramsObj = {
|
|
|
lifeGroupBuyMain: params,
|
|
|
- lifeGroupBuyThalis: output
|
|
|
+ lifeGroupBuyThalis: lifeGroupBuyThalis.value
|
|
|
};
|
|
|
console.log(paramsObj);
|
|
|
if (type) {
|
|
|
@@ -1892,6 +1884,21 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
min-height: 100%;
|
|
|
}
|
|
|
|
|
|
+/* 头部区域 */
|
|
|
+.header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px;
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+}
|
|
|
+.title {
|
|
|
+ flex: 1;
|
|
|
+ margin: 0;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
/* 内容区域布局 */
|
|
|
.content {
|
|
|
display: flex;
|
|
|
@@ -1957,7 +1964,7 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
|
|
|
// background-color: #f5f7fa;
|
|
|
border-radius: 4px;
|
|
|
- transition: background-color 0.3s;
|
|
|
+ transition: background-color 0.1s;
|
|
|
&:hover {
|
|
|
background-color: #ecf5ff;
|
|
|
}
|
|
|
@@ -2035,7 +2042,7 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
margin: 0;
|
|
|
font-size: 14px;
|
|
|
border-radius: 4px;
|
|
|
- transition: all 0.3s;
|
|
|
+ transition: all 0.1s;
|
|
|
}
|
|
|
|
|
|
/* 套餐内容相关样式 */
|
|
|
@@ -2170,11 +2177,9 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
margin-top: 0;
|
|
|
}
|
|
|
.extra-dishes-container .dish-row {
|
|
|
- padding-bottom: 22px;
|
|
|
margin-bottom: 4px;
|
|
|
}
|
|
|
.extra-dishes-container .dish-row:last-child {
|
|
|
- padding-bottom: 0;
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
.package-group-collapsed {
|
|
|
@@ -2378,7 +2383,7 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
cursor: pointer;
|
|
|
border: 1px solid #e4e7ed;
|
|
|
border-radius: 4px;
|
|
|
- transition: all 0.3s;
|
|
|
+ transition: all 0.1s;
|
|
|
}
|
|
|
.dish-item:hover {
|
|
|
background-color: #f5f7fa;
|
|
|
@@ -2387,7 +2392,7 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
.dish-item-selected {
|
|
|
background-color: #ecf5ff;
|
|
|
border-color: #409eff;
|
|
|
- border-width: 2px;
|
|
|
+ border-width: 1px;
|
|
|
}
|
|
|
.dish-item-selected:hover {
|
|
|
background-color: #d9ecff;
|
|
|
@@ -2449,14 +2454,14 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
.slide-fade-enter-active {
|
|
|
overflow: hidden;
|
|
|
transition:
|
|
|
- max-height 0.3s ease-out,
|
|
|
- opacity 0.3s ease-out;
|
|
|
+ max-height 0.1s ease-out,
|
|
|
+ opacity 0.1s ease-out;
|
|
|
}
|
|
|
.slide-fade-leave-active {
|
|
|
overflow: hidden;
|
|
|
transition:
|
|
|
- max-height 0.3s ease-in,
|
|
|
- opacity 0.3s ease-in;
|
|
|
+ max-height 0.1s ease-in,
|
|
|
+ opacity 0.1s ease-in;
|
|
|
}
|
|
|
.slide-fade-enter-from {
|
|
|
max-height: 0;
|
|
|
@@ -2474,10 +2479,10 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
|
|
|
/* 分组收起全部动画 */
|
|
|
.group-fade-enter-active {
|
|
|
- transition: all 0.3s ease-out;
|
|
|
+ transition: all 0.1s ease-out;
|
|
|
}
|
|
|
.group-fade-leave-active {
|
|
|
- transition: all 0.3s ease-in;
|
|
|
+ transition: all 0.1s ease-in;
|
|
|
}
|
|
|
.group-fade-enter-from {
|
|
|
opacity: 0;
|
|
|
@@ -2488,6 +2493,6 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
transform: translateY(-10px);
|
|
|
}
|
|
|
.group-fade-move {
|
|
|
- transition: transform 0.3s ease;
|
|
|
+ transition: transform 0.1s linear;
|
|
|
}
|
|
|
</style>
|