Просмотр исходного кода

fix(hdApp): 统一商品表单数值校验

- 新增与编辑商品共用价格、库存及扩展数值字段校验规则
- 多规格校验统一返回首条错误并补齐自定义库存必填限制
shizhongqi 3 дней назад
Родитель
Сommit
ee587a5af6
2 измененных файлов с 180 добавлено и 208 удалено
  1. 90 104
      hdApp/src/admin/goods/add.vue
  2. 90 104
      hdApp/src/admin/goods/detail.vue

+ 90 - 104
hdApp/src/admin/goods/add.vue

@@ -215,7 +215,6 @@
 <script>
 import TuiListCell from "@/components/plugin/list-cell";
 import AppUploader from "@/components/app-uploader";
-const form = require("@/utils/formValidation.js");
 import { getClass } from "@/api/category";
 import { addGoodsData, useCaseList } from "@/api/goods"
 import { delImage } from "@/api/pic-text";
@@ -448,50 +447,86 @@ export default {
       spec.stock = status === '0' ? 9999 : '';
       this.$set(this.form.specList, index, { ...spec });
     },
+    /**
+     * 校验单个数值字段。空值按 required 决定是否报错;exclusiveMin 时必须大于 min。
+     * @param {*} value 表单原始值
+     * @param {{label:string, required?:boolean, min?:number, exclusiveMin?:boolean}} options 校验配置
+     * @returns {string} 错误文案,空字符串表示通过
+     */
+    validateNumberField(value, options) {
+      const label = options.label;
+      const required = !!options.required;
+      const min = options.min !== undefined ? options.min : 0;
+      const exclusiveMin = !!options.exclusiveMin;
+      const isEmpty = value === '' || value === null || value === undefined;
+      if (isEmpty) {
+        return required ? `请输入${label}` : '';
+      }
+      const num = parseFloat(value);
+      if (isNaN(num)) {
+        return `${label}必须是数字`;
+      }
+      if (exclusiveMin && num <= min) {
+        return `${label}必须是大于${min}的数字`;
+      }
+      if (!exclusiveMin && num < min) {
+        return `${label}必须是大于等于${min}的数字`;
+      }
+      return '';
+    },
+    /**
+     * 校验一组商品数值字段。单规格与规格组共用,避免两套规则不一致。
+     * 有价格时价格必填且 >0;自定义库存时库存必填且 >=0;花支数/重量/已售选填,填了必须 >=0。
+     * @param {Object} data 单规格 form 或某个规格组
+     * @param {string} prefix 错误文案前缀,如「规格组1」
+     * @returns {string} 第一条错误文案
+     */
+    validateGoodsNumberFields(data, prefix) {
+      prefix = prefix || '';
+      const withPrefix = (label) => prefix ? `${prefix}${label}` : label;
+      const rules = [];
+      if (String(data.priceType) === '1') {
+        rules.push({ key: 'price', label: withPrefix('价格'), required: true, min: 0, exclusiveMin: true });
+      }
+      if (String(data.stockSet) === '1') {
+        rules.push({ key: 'stock', label: withPrefix('库存数量'), required: true, min: 0 });
+      }
+      rules.push(
+        { key: 'flowerNum', label: withPrefix('花支数'), required: false, min: 0 },
+        { key: 'weight', label: withPrefix('重量'), required: false, min: 0 },
+        { key: 'sold', label: withPrefix('已售数量'), required: false, min: 0 }
+      );
+      for (let i = 0; i < rules.length; i++) {
+        const rule = rules[i];
+        const msg = this.validateNumberField(data[rule.key], rule);
+        if (msg) {
+          return msg;
+        }
+      }
+      return '';
+    },
+    /**
+     * 多规格开启时校验规格列表。返回错误文案,空字符串表示通过。
+     */
     validateSpecList() {
-      if (this.form.specEnabled != 1) return true;
+      if (this.form.specEnabled != 1) {
+        return '';
+      }
       if (!this.form.specList.length) {
-        this.$msg('请至少添加一个规格组');
-        return false;
+        return '请至少添加一个规格组';
       }
       for (let i = 0; i < this.form.specList.length; i++) {
         const spec = this.form.specList[i];
         const name = `规格组${i + 1}`;
         if (!spec.specName || !String(spec.specName).trim()) {
-          this.$msg(`请输入${name}名称`);
-          return false;
-        }
-        if (String(spec.priceType) === '1') {
-          const price = parseFloat(spec.price);
-          if (spec.price === '' || isNaN(price) || price <= 0) {
-            this.$msg(`${name}价格必须是大于0的数字`);
-            return false;
-          }
-        }
-        if (String(spec.stockSet) === '1') {
-          const stock = parseFloat(spec.stock);
-          if (spec.stock === '' || isNaN(stock) || stock < 0) {
-            this.$msg(`${name}库存数量必须是大于等于0的数字`);
-            return false;
-          }
+          return `请输入${name}名称`;
         }
-        const flowerNum = parseFloat(spec.flowerNum || 0);
-        if (isNaN(flowerNum) || flowerNum < 0) {
-          this.$msg(`${name}花支数必须是大于等于0的数字`);
-          return false;
-        }
-        const weight = parseFloat(spec.weight);
-        if (spec.weight === '' || isNaN(weight) || weight < 0) {
-          this.$msg(`${name}重量必须是大于等于0的数字`);
-          return false;
-        }
-        const sold = parseFloat(spec.sold || 0);
-        if (isNaN(sold) || sold < 0) {
-          this.$msg(`${name}已售数量必须是大于等于0的数字`);
-          return false;
+        const numErr = this.validateGoodsNumberFields(spec, name);
+        if (numErr) {
+          return numErr;
         }
       }
-      return true;
+      return '';
     },
     normalizeSpecSubmitData(formData) {
       if (formData.specEnabled != 1) {
@@ -629,78 +664,29 @@ export default {
         })
       })
     },
-    commitForm(e) {
-   
-      let rules = [
-        {
-          name: "name",
-          rule: ["required"],
-          msg: ["请输入名称"]
-        }
-      ];
-      
-      if (!this.validateSpecList()) {
-        return false;
+    /**
+     * 提交前统一校验。用 this.form,避免原生 form 取值漏掉无 name 的规格字段。
+     * @returns {string} 第一条错误文案,空字符串表示通过
+     */
+    getFormValidateError() {
+      if (!this.form.name || !String(this.form.name).trim()) {
+        return '请输入名称';
       }
-
-      // 只有在有价格类型时才验证价格
-      if (this.form.specEnabled != 1 && this.form.priceType === '1') {
-        rules.push({
-          name: "price",
-          rule: ["required"],
-          msg: ["请输入价格"]
-        });
-        
-        // 检查价格是否为有效数字且大于0
-        if (this.form.price !== "" && (isNaN(parseFloat(this.form.price)) || parseFloat(this.form.price) <= 0)) {
-          this.$msg('价格必须是大于0的数字!');
-          return false;
-        }
-      }
-      
-      // 验证库存数量
-      if (this.form.specEnabled != 1 && this.form.stockSet === '1' && this.form.stock !== "") {
-        const stockNum = parseFloat(this.form.stock);
-        if (isNaN(stockNum) || stockNum < 0) {
-          this.$msg('库存数量必须是大于等于0的数字!');
-          return false;
-        }
-      }
-      
-      // 验证已售数量
-      if (this.form.specEnabled != 1 && this.form.sold !== "") {
-        const soldNum = parseFloat(this.form.sold);
-        if (isNaN(soldNum) || soldNum < 0) {
-          this.$msg('已售数量必须是大于等于0的数字!');
-          return false;
-        }
+      if (this.form.specEnabled == 1) {
+        return this.validateSpecList();
       }
-      
-      // 验证花支数
-      if (this.form.specEnabled != 1 && this.form.flowerNum !== "" && this.form.flowerNum !== 0) {
-        const flowerNum = parseFloat(this.form.flowerNum);
-        if (isNaN(flowerNum) || flowerNum < 0) {
-          this.$msg('花支数必须是大于0的数字!');
-          return false;
-        }
-      }
-
-      // 验证重量(千克)
-      if (this.form.specEnabled != 1 && this.form.weight !== "") {
-        const weightNum = parseFloat(this.form.weight);
-        if (isNaN(weightNum) || weightNum < 0) {
-          this.$msg('重量必须是大于等于0的数字!');
-          return false;
-        }
-      }
-      
-      let formData = e.detail.value;
-      let checkRes = form.validation(formData, rules);
-      if (!checkRes) {
-         this.confirmFn();
-      } else {
-        this.$msg(checkRes);
+      return this.validateGoodsNumberFields(this.form, '');
+    },
+    /**
+     * 表单提交入口。先校验再走确认弹窗,校验失败只提示第一条错误。
+     */
+    commitForm() {
+      const err = this.getFormValidateError();
+      if (err) {
+        this.$msg(err);
+        return false;
       }
+      this.confirmFn();
     },
     // 库存输入框聚焦
     onStockFocus() {

+ 90 - 104
hdApp/src/admin/goods/detail.vue

@@ -259,7 +259,6 @@
 </template>
 <script>
 import TuiListCell from "@/components/plugin/list-cell";
-const form = require("@/utils/formValidation.js");
 import { getClass } from "@/api/category";
 import { getGoodsDetail, updateGoodsData, createSn, useCaseList } from "@/api/goods";
 import { delImages } from "@/api/pic-text";
@@ -603,50 +602,86 @@ export default {
       spec.stock = status === '0' ? 9999 : '';
       this.$set(this.form.specList, index, { ...spec });
     },
+    /**
+     * 校验单个数值字段。空值按 required 决定是否报错;exclusiveMin 时必须大于 min。
+     * @param {*} value 表单原始值
+     * @param {{label:string, required?:boolean, min?:number, exclusiveMin?:boolean}} options 校验配置
+     * @returns {string} 错误文案,空字符串表示通过
+     */
+    validateNumberField(value, options) {
+      const label = options.label;
+      const required = !!options.required;
+      const min = options.min !== undefined ? options.min : 0;
+      const exclusiveMin = !!options.exclusiveMin;
+      const isEmpty = value === '' || value === null || value === undefined;
+      if (isEmpty) {
+        return required ? `请输入${label}` : '';
+      }
+      const num = parseFloat(value);
+      if (isNaN(num)) {
+        return `${label}必须是数字`;
+      }
+      if (exclusiveMin && num <= min) {
+        return `${label}必须是大于${min}的数字`;
+      }
+      if (!exclusiveMin && num < min) {
+        return `${label}必须是大于等于${min}的数字`;
+      }
+      return '';
+    },
+    /**
+     * 校验一组商品数值字段。单规格与规格组共用,避免两套规则不一致。
+     * 有价格时价格必填且 >0;自定义库存时库存必填且 >=0;花支数/重量/已售选填,填了必须 >=0。
+     * @param {Object} data 单规格 form 或某个规格组
+     * @param {string} prefix 错误文案前缀,如「规格组1」
+     * @returns {string} 第一条错误文案
+     */
+    validateGoodsNumberFields(data, prefix) {
+      prefix = prefix || '';
+      const withPrefix = (label) => prefix ? `${prefix}${label}` : label;
+      const rules = [];
+      if (String(data.priceType) === '1') {
+        rules.push({ key: 'price', label: withPrefix('价格'), required: true, min: 0, exclusiveMin: true });
+      }
+      if (String(data.stockSet) === '1') {
+        rules.push({ key: 'stock', label: withPrefix('库存数量'), required: true, min: 0 });
+      }
+      rules.push(
+        { key: 'flowerNum', label: withPrefix('花支数'), required: false, min: 0 },
+        { key: 'weight', label: withPrefix('重量'), required: false, min: 0 },
+        { key: 'sold', label: withPrefix('已售数量'), required: false, min: 0 }
+      );
+      for (let i = 0; i < rules.length; i++) {
+        const rule = rules[i];
+        const msg = this.validateNumberField(data[rule.key], rule);
+        if (msg) {
+          return msg;
+        }
+      }
+      return '';
+    },
+    /**
+     * 多规格开启时校验规格列表。返回错误文案,空字符串表示通过。
+     */
     validateSpecList() {
-      if (this.form.specEnabled != 1) return true;
+      if (this.form.specEnabled != 1) {
+        return '';
+      }
       if (!this.form.specList.length) {
-        this.$msg('请至少添加一个规格组');
-        return false;
+        return '请至少添加一个规格组';
       }
       for (let i = 0; i < this.form.specList.length; i++) {
         const spec = this.form.specList[i];
         const name = `规格组${i + 1}`;
         if (!spec.specName || !String(spec.specName).trim()) {
-          this.$msg(`请输入${name}名称`);
-          return false;
-        }
-        if (String(spec.priceType) === '1') {
-          const price = parseFloat(spec.price);
-          if (spec.price === '' || isNaN(price) || price <= 0) {
-            this.$msg(`${name}价格必须是大于0的数字`);
-            return false;
-          }
-        }
-        if (String(spec.stockSet) === '1') {
-          const stock = parseFloat(spec.stock);
-          if (spec.stock === '' || isNaN(stock) || stock < 0) {
-            this.$msg(`${name}库存数量必须是大于等于0的数字`);
-            return false;
-          }
+          return `请输入${name}名称`;
         }
-        const flowerNum = parseFloat(spec.flowerNum || 0);
-        if (isNaN(flowerNum) || flowerNum < 0) {
-          this.$msg(`${name}花支数必须是大于等于0的数字`);
-          return false;
-        }
-        const weight = parseFloat(spec.weight);
-        if (spec.weight === '' || isNaN(weight) || weight < 0) {
-          this.$msg(`${name}重量必须是大于等于0的数字`);
-          return false;
-        }
-        const sold = parseFloat(spec.sold || 0);
-        if (isNaN(sold) || sold < 0) {
-          this.$msg(`${name}已售数量必须是大于等于0的数字`);
-          return false;
+        const numErr = this.validateGoodsNumberFields(spec, name);
+        if (numErr) {
+          return numErr;
         }
       }
-      return true;
+      return '';
     },
     normalizeSpecSubmitData(formData) {
       if (formData.specEnabled != 1) {
@@ -678,78 +713,29 @@ export default {
       formData.sold = firstSpec.sold;
       return formData;
     },
-    formSubmit(e) {
-
-      let rules = [
-        {
-          name: "name",
-          rule: ["required"],
-          msg: ["请输入名称"]
-        }
-      ];
-
-      if (!this.validateSpecList()) {
-        return false;
-      }
-
-      // 只有在有价格类型时才验证价格
-      if (this.form.specEnabled != 1 && this.form.priceType === '1') {
-        rules.push({
-          name: "price",
-          rule: ["required"],
-          msg: ["请输入价格"]
-        });
-
-        // 检查价格是否为有效数字且大于0
-        if (this.form.price !== "" && (isNaN(parseFloat(this.form.price)) || parseFloat(this.form.price) <= 0)) {
-          this.$msg('价格必须是大于0的数字!');
-          return false;
-        }
+    /**
+     * 提交前统一校验。用 this.form,避免原生 form 取值漏掉无 name 的规格字段。
+     * @returns {string} 第一条错误文案,空字符串表示通过
+     */
+    getFormValidateError() {
+      if (!this.form.name || !String(this.form.name).trim()) {
+        return '请输入名称';
       }
-
-      // 验证库存数量
-      if (this.form.specEnabled != 1 && this.form.stockSet === '1' && this.form.stock !== "") {
-        const stockNum = parseFloat(this.form.stock);
-        if (isNaN(stockNum) || stockNum < 0) {
-          this.$msg('库存数量必须是大于等于0的数字!');
-          return false;
-        }
-      }
-
-      // 验证已售数量
-      if (this.form.specEnabled != 1 && this.form.sold !== "") {
-        const soldNum = parseFloat(this.form.sold);
-        if (isNaN(soldNum) || soldNum < 0) {
-          this.$msg('已售数量必须是大于等于0的数字!');
-          return false;
-        }
+      if (this.form.specEnabled == 1) {
+        return this.validateSpecList();
       }
-
-      // 验证花支数
-      if (this.form.specEnabled != 1 && this.form.flowerNum !== "" && this.form.flowerNum !== 0) {
-        const flowerNum = parseFloat(this.form.flowerNum);
-        if (isNaN(flowerNum) || flowerNum < 0) {
-          this.$msg('花支数必须是大于0的数字!');
-          return false;
-        }
-      }
-
-      // 验证重量(千克)
-      if (this.form.specEnabled != 1 && this.form.weight !== "") {
-        const weightNum = parseFloat(this.form.weight);
-        if (isNaN(weightNum) || weightNum < 0) {
-          this.$msg('重量必须是大于等于0的数字!');
-          return false;
-        }
-      }
-
-      let formData = e.detail.value;
-      let checkRes = form.validation(formData, rules);
-      if (!checkRes) {
-        this.confirmFn();
-      } else {
-        this.$msg(checkRes);
+      return this.validateGoodsNumberFields(this.form, '');
+    },
+    /**
+     * 表单提交入口。先校验再走确认保存,校验失败只提示第一条错误。
+     */
+    formSubmit() {
+      const err = this.getFormValidateError();
+      if (err) {
+        this.$msg(err);
+        return false;
       }
+      this.confirmFn();
     },
     confirmFn() {
       if (this.form.categoryIdList == 0) {