Pārlūkot izejas kodu

feat(groupPackage): 优化分组菜品管理功能

- 修复分组图片显示条件判断逻辑
- 添加分组类别值变化监听器,实现实时重复性校验
- 重构添加分组逻辑,使用表单验证替代手动校验
- 重构添加菜品逻辑,使用表单验证替代手动校验
- 添加分组类别重复性校验规则
- 优化菜品列表样式,调整间距设置
congxuesong 1 mēnesi atpakaļ
vecāks
revīzija
2636ba4db1

+ 1 - 1
src/views/groupPackageManagement/index.vue

@@ -13,7 +13,7 @@
       <template #Information="scope">
         <div class="information">
           <el-image
-            v-if="scope.row.imageValueStr[0]"
+            v-if="scope.row.imageValueStr"
             :src="scope.row.imageValueStr[0]"
             :preview-src-list="scope.row.imageValueStr"
             preview-teleported

+ 185 - 190
src/views/groupPackageManagement/newGroup.vue

@@ -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 {