Element-plus封装表格Validate

Element-plus实现表格内的表单验证:useAttrValidate Hook

在Vue.js项目中,表单验证是一个常见的需求,尤其在处理复杂表格编辑场景时。本文将详细介绍一个基于Vue3的自定义Hook——useAttrValidate,它提供了一种便捷的方式来处理表格组件中的字段校验。

效果如下:

请添加图片描述

1. 导入与初始化

Javascript

import { reactive, nextTick } from "vue";

首先引入Vue3的核心API——reactive(响应式对象)和nextTick(异步更新队列)。useAttrValidate Hook依赖这些API来实现状态管理和异步验证逻辑。

2. 完整代码useAttrValidateHook

 const useAttrValidate = () => {
  // 校验的每个输入框的集合
  const inputRef = reactive({});
  const FAIL_CLASS_NAME = "el-input_validatefail";

  /**
   * @param submitData 需要校验的数据
   * @param keyName 校验的具体字段
   * @param model 配置table或者的模板,具有中文名称
   * @param requiredList 必填项数组 类似 ['attrRemark', 'attrDefaultValue'];
   * @description 校验自定义数据
   */
  const attrValidate = (
    submitData,
    model,
    keyName = "tblAttrList",
    requiredList?: Array<string>
  ): Promise<string> => {
    // 需要校验的字段,可手动配置,也可以从model的required获取
    const tmpRequiredList = requiredList?.length
      ? requiredList
      : Object.keys(model)
          .map((key) => {
            if (model[key].required) return key;
          })
          .filter((item) => !!item);

    return new Promise(async (resolve, reject) => {
      const rulesArray: Array<Promise<string>> = [];
      if (submitData[keyName].length) {
        submitData[keyName].forEach(async (config) => {
          // 校验必选
          tmpRequiredList.forEach((requireProp) => {
            if (
              (config[requireProp!] === undefined ||
                config[requireProp!] === "" ||
                config[requireProp!] === null) &&
              tmpRequiredList.includes(requireProp!)
            ) {
              inputRef[requireProp + config.id]!.setAttribute(
                "data-after",
                "请输入"
              );
              setInputWarnning(inputRef[requireProp + config.id], "请输入");
              reject(`请输入${model[requireProp!].label}`);
            }
          });

          // 检验自定义rules
          for (let key in model) {
            // 拿到model内每个值
            const modelValue = model[key];
            // 遍历对应字段的rules规则
            if (modelValue?.rules && modelValue.rules.length) {
              const rules = modelValue.rules;
              rules.forEach((rule) => {
                rulesArray.push(
                  new Promise((res, rej) => {
                    if (typeof rule !== "object") reject(`rules书写有误`);
                    // 取出单个rule的validate方法
                    if ("validator" in rule) {
                      const {validator} = rule;
                      validator(key, config[key], (text) => {
                        if (text) {
                          inputRef[key + config.id]!.setAttribute(
                            "data-after",
                            text
                          );
                          setInputWarnning(inputRef[key + config.id], text);
                          rej(text);
                        } else {
                          res("");
                        }
                      });
                    }
                  })
                );
              });
            }
          }
        });
        // 对所有rules进行检测
        rulesArray.length &&
          (await Promise.all(rulesArray).catch((error) => {
            reject(error);
          }));
        resolve("成功");
      } else {
        reject("无法提交,至少需含1个属性");
      }
    });
  };

  const setInputWarnning = (ref, text, isAccess = false) => {
    const node = ref;
    const inputWrapper = node.querySelector(".el-input__wrapper");
    console.dir(inputWrapper);
    console.log(document.styleSheets);
    if (!inputWrapper) return;
    if (!isAccess) {
      console.log(document.styleSheets[0]);
      for (let cssRule of document.styleSheets[0].cssRules) {
        if (cssRule.selectorText === `.${FAIL_CLASS_NAME}::after`) {
          cssRule.style.content = `'${text}'`;
        }
      }
      node.classList.add("el-input_validatefail");
      inputWrapper.style.boxShadow = "0 0 0 1px #f56c6c inset";
      inputWrapper.style.transition = "all .3s";
    } else {
      for (let cssRule of document.styleSheets[0].cssRules) {
        if (cssRule.selectorText === `.${FAIL_CLASS_NAME}::after`) {
          cssRule.style.content = `''`;
        }
      }
      inputWrapper.style.boxShadow = "0 0 0 1px #dcdfe6 inset";
      inputWrapper.style.transition = "all .3s";
    }
  };

  /**
   *
   * @param key 字段名
   * @param config 字段对应的model配置
   * @param row 当前行的数据
   * @description 当前对于特殊字段的特定校验方式,可以实时的校验rules规则
   */
  const singleFieldValidate = (key, config, row) => {
    if (config?.rules && config.rules.length) {
      const rules = config.rules;
      rules.forEach((rule) => {
        if (typeof rule !== "object") return `rules书写有误`;
        // 取出单个rule的validate方法
        if ("validator" in rule) {
          const {validator} = rule;
          validator(key, row[key], (text) => {
            nextTick(() => {
              if (text) {
                console.dir(inputRef[key + row.id]);
                inputRef[key + row.id]!.setAttribute("data-after", text);
                setInputWarnning(inputRef[key + row.id], text, false);
                return text;
              } else {
                setInputWarnning(inputRef[key + row.id], "", true);
                return "";
              }
            });
          });
        }
      });
    }
    inputRef[key + row.id]!.setAttribute("data-after", "");
    setInputWarnning(inputRef[key + row.id], "", true);
  };

  /**
   *
   * @param key 字段名
   * @param row 当前行的数据
   */
  const clearValidate = (key, row) => {
    console.log(key, row, inputRef);
    inputRef[key + row.id]!.setAttribute("data-after", "");
    setInputWarnning(inputRef[key + row.id], "", true);
  };

  /**
   *
   * @param key 字段名
   * @param el 当前节点的htmlRef 等同于document.getElementById()返回的结果
   * @param row当前行的数据
   * @returns 当前节点的htmlRef
   * @description 获取当前节点的htmlRef,并储存于inputRef这个集合里面。用于后续定位并添加data-after伪元素
   */
  const getRef = (key, el: HTMLElement, row?) => {
    let id = new Date().getTime().toString();
    if(row && row?.id){
      id= row.id
    }
    // el.id = id;
    inputRef[key + id] = el;
    return el;
  };

  return {
    inputRef,
    getRef,
    attrValidate,
    singleFieldValidate,
    clearValidate,
  };
};

这是一个自定义Hook,通过调用它可以获得一组用于表单验证的方法和状态管理器。

2.1 inputRefFAIL_CLASS_NAME

Javascript

const inputRef = reactive({});
const FAIL_CLASS_NAME = "el-input_validatefail";

inputRef 是一个响应式对象,用于存储需要进行验证的输入框引用。FAIL_CLASS_NAME 是一个失败提示样式的类名,在验证失败时为输入框添加此样式。

2.2 attrValidate 方法

Javascript

 /**
   * @param submitData 需要校验的数据
   * @param keyName 校验的具体字段
   * @param model 配置table或者的模板,具有中文名称
   * @param requiredList 必填项数组 类似 ['attrRemark', 'attrDefaultValue'];
   * @description 校验自定义数据
   */
  const attrValidate = (
    submitData,
    model,
    keyName = "tblAttrList",
    requiredList?: Array<string>
  ): Promise<string> => {
    // 需要校验的字段,可手动配置,也可以从model的required获取
    const tmpRequiredList = requiredList?.length
      ? requiredList
      : Object.keys(model)
          .map((key) => {
            if (model[key].required) return key;
          })
          .filter((item) => !!item);

    return new Promise(async (resolve, reject) => {
      const rulesArray: Array<Promise<string>> = [];
      if (submitData[keyName].length) {
        submitData[keyName].forEach(async (config) => {
          // 校验必选
          tmpRequiredList.forEach((requireProp) => {
            if (
              (config[requireProp!] === undefined ||
                config[requireProp!] === "" ||
                config[requireProp!] === null) &&
              tmpRequiredList.includes(requireProp!)
            ) {
              inputRef[requireProp + config.id]!.setAttribute(
                "data-after",
                "请输入"
              );
              setInputWarnning(inputRef[requireProp + config.id], "请输入");
              reject(`请输入${model[requireProp!].label}`);
            }
          });

          // 检验自定义rules
          for (let key in model) {
            // 拿到model内每个值
            const modelValue = model[key];
            // 遍历对应字段的rules规则
            if (modelValue?.rules && modelValue.rules.length) {
              const rules = modelValue.rules;
              rules.forEach((rule) => {
                rulesArray.push(
                  new Promise((res, rej) => {
                    if (typeof rule !== "object") reject(`rules书写有误`);
                    // 取出单个rule的validate方法
                    if ("validator" in rule) {
                      const {validator} = rule;
                      validator(key, config[key], (text) => {
                        if (text) {
                          inputRef[key + config.id]!.setAttribute(
                            "data-after",
                            text
                          );
                          setInputWarnning(inputRef[key + config.id], text);
                          rej(text);
                        } else {
                          res("");
                        }
                      });
                    }
                  })
                );
              });
            }
          }
        });
        // 对所有rules进行检测
        rulesArray.length &&
          (await Promise.all(rulesArray).catch((error) => {
            reject(error);
          }));
        resolve("成功");
      } else {
        reject("无法提交,至少需含1个属性");
      }
    });
  };

attrValidate 函数是核心验证逻辑,接收四个参数:

  • submitData:待提交的数据对象。
  • model:包含各字段配置的对象,如是否必填、验证规则等。
  • keyName:提交数据中需要校验的子属性键名,默认为"tblAttrList"。
  • requiredList:必填项数组,若不为空则优先使用该数组。

此方法会遍历每一项数据,对必填项进行检查,并根据model中的rules执行自定义验证逻辑。如果全部验证通过,则返回"成功";否则返回错误信息。

2.3 辅助验证方法

setInputWarning
  • 用于设置输入框的警告信息以及样式更改。

      const setInputWarnning = (ref, text, isAccess = false) => {
        const node = ref;
        const inputWrapper = node.querySelector(".el-input__wrapper");
        console.dir(inputWrapper);
        console.log(document.styleSheets);
        if (!inputWrapper) return;
        if (!isAccess) {
          console.log(document.styleSheets[0]);
          for (let cssRule of document.styleSheets[0].cssRules) {
            if (cssRule.selectorText === `.${FAIL_CLASS_NAME}::after`) {
              cssRule.style.content = `'${text}'`;
            }
          }
          node.classList.add("el-input_validatefail");
          inputWrapper.style.boxShadow = "0 0 0 1px #f56c6c inset";
          inputWrapper.style.transition = "all .3s";
        } else {
          for (let cssRule of document.styleSheets[0].cssRules) {
            if (cssRule.selectorText === `.${FAIL_CLASS_NAME}::after`) {
              cssRule.style.content = `''`;
            }
          }
          inputWrapper.style.boxShadow = "0 0 0 1px #dcdfe6 inset";
          inputWrapper.style.transition = "all .3s";
        }
      };
    
singleFieldValidate
  • 针对单个字段及其规则进行实时验证。

    /**
       *
       * @param key 字段名
       * @param config 字段对应的model配置
       * @param row 当前行的数据
       * @description 当前对于特殊字段的特定校验方式,可以实时的校验rules规则
       */
      const singleFieldValidate = (key, config, row) => {
        if (config?.rules && config.rules.length) {
          const rules = config.rules;
          rules.forEach((rule) => {
            if (typeof rule !== "object") return `rules书写有误`;
            // 取出单个rule的validate方法
            if ("validator" in rule) {
              const {validator} = rule;
              validator(key, row[key], (text) => {
                nextTick(() => {
                  if (text) {
                    console.dir(inputRef[key + row.id]);
                    inputRef[key + row.id]!.setAttribute("data-after", text);
                    setInputWarnning(inputRef[key + row.id], text, false);
                    return text;
                  } else {
                    setInputWarnning(inputRef[key + row.id], "", true);
                    return "";
                  }
                });
              });
            }
          });
        }
        inputRef[key + row.id]!.setAttribute("data-after", "");
        setInputWarnning(inputRef[key + row.id], "", true);
      };
    
clearValidate
  • 清除指定字段的验证结果。

    /**
       *
       * @param key 字段名
       * @param row 当前行的数据
       */
      const clearValidate = (key, row) => {
        console.log(key, row, inputRef);
        inputRef[key + row.id]!.setAttribute("data-after", "");
        setInputWarnning(inputRef[key + row.id], "", true);
      };
    
getRef
  • 获取并存储输入框的HTML引用,便于后续操作。

    /**
       *
       * @param key 字段名
       * @param el 当前节点的htmlRef 等同于document.getElementById()返回的结果
       * @param row当前行的数据
       * @returns 当前节点的htmlRef
       * @description 获取当前节点的htmlRef,并储存于inputRef这个集合里面。用于后续定位并添加data-after伪元素
       */
      const getRef = (key, el: HTMLElement, row?) => {
        let id = new Date().getTime().toString();
        if(row && row?.id){
          id= row.id
        }
        // el.id = id;
        inputRef[key + id] = el;
        return el;
      };
    

3. 使用示例

在实际项目中,你可以这样使用useAttrValidate

Javascript

import useAttrValidate from './useValidate';

setup() {
  const { inputRef, getRef, attrValidate, clearValidate } = useAttrValidate();

  const handleValidate = async () => {
    try {
      await attrValidate(submitData, tableModel);
      // 提交表单...
    } catch (error) {
     console.error('验证失败:', error);
    }
  };

  // 在渲染输入框时绑定引用
  <input ref="(el) => getRef(fieldName, el, rowData)" />

  // 清除验证结果
  clearValidate(fieldName, rowData);

  // 调用验证方法
  handleValidate();
  
  // ...
    
    //model示例:
    
    const propertyConfigModel = {
  fieldName: {
    label: "xxx",
    required: true,
    rules: [
      {
        validator(field, value, cb) {
          const regExp = /^[a-zA-Z0-9_]+$/;
          const res = regExp.test(value);
          if (res) cb();
          else cb('字段名只支持字母、数字和"_"');
        },
      },
    ],
  }
  fieldType: {
    label: "xxxx",
    required: true,
    column: {
      slot: true,
      header: true,
    },
    rules:[]
  },
  fieldLength: {
    label: "xxxx",
    rules: [
      {
        validator(field, value, cb) {
          if (value && isNaN(Number(value))) {
            cb("长度必须为数字类型");
          } else {
            cb();
          }
        },
      },
    ],
  },]
}

总之,`useAttrValidate` Hook通过结合Vue3的响应式系统和自定义验证逻辑,为复杂表格表单提供了统一且灵活的验证方案。开发者可以根据具体业务需求调整模型配置,轻松实现各种复杂的表单验证场景。

你可能感兴趣的:(vue.js,前端,javascript,elementui)