|
|
@@ -988,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 }
|
|
|
+);
|
|
|
+
|
|
|
// ==================== 生命周期钩子 ====================
|
|
|
|
|
|
/**
|
|
|
@@ -1230,116 +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;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 添加新分组
|
|
|
- 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: ""
|
|
|
}
|
|
|
- }, 150);
|
|
|
+ ]
|
|
|
});
|
|
|
- });
|
|
|
+ groupCollapsedStates.value.push(false);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -1437,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: ""
|
|
|
+ });
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -1673,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"
|
|
|
}
|
|
|
];
|
|
|
|
|
|
@@ -2180,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 {
|