Vue3 复杂表单校验实战:el-form 表单+表格+树形结构的高级校验+基于 async-validator 的全场景解析

1,398 阅读8分钟

Vue3 复杂表单校验实战:从基础到 Element UI 高级应用async-validator​

前言

在 Vue3 项目中,表单校验是前端开发的核心需求之一。无论是简单的注册表单,还是复杂的表格、树形结构表单,校验逻辑的合理设计直接影响用户体验和系统稳定性。本文将结合 ​​async-validator​​ 基础库与 ​​Element UI​​ 组件库,从基础校验到高级场景(表格+表单、树形控件+表单),全面解析复杂表单校验的实现方案。


一、快速上手:安装与环境配置

1.1 安装依赖

npm install async-validator element-plus --save
# 或
yarn add async-validator element-plus

1.2 核心概念

  • ​async-validator​​:基于 Promise 的异步校验库,支持自定义规则、异步验证、嵌套对象/数组校验。
  • ​Element UI 表单组件​​:提供 el-formel-form-item等组件,内置校验功能,支持与 async-validator集成。
  • ​校验规则(Schema)​​:描述字段的校验要求(如必填、格式、自定义函数等)。
  • ​校验器实例​​:通过 new AsyncValidator(rules)创建,负责执行具体校验逻辑。

二、基础场景:简单表单校验(async-validator)

2.1 需求描述

用户注册表单,包含以下字段:

  • 用户名(必填,长度 3-12 字符)
  • 密码(必填,包含字母+数字,长度 6-16 字符)
  • 确认密码(必填,需与密码一致)

2.2 代码实现

2.2.1 定义校验规则(Schema)
// src/utils/validateRules.js
export const registerRules = {
  username: [
    { required: true, message: '用户名不能为空' },
    { min: 3, max: 12, message: '用户名长度需在 3-12 字符之间' }
  ],
  password: [
    { required: true, message: '密码不能为空' },
    { 
      pattern: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,16}$/, 
      message: '密码需包含字母和数字,长度 6-16 字符' 
    }
  ],
  confirmPassword: [
    { required: true, message: '确认密码不能为空' }
  ]
};
2.2.2 Vue3 组件中集成校验
<!-- src/views/Register.vue -->
<template>
  <div class="register-form">
    <h2>用户注册</h2>
    <el-form :model="formData" :rules="registerRules" ref="formRef">
      <el-form-item label="用户名" prop="username">
        <el-input v-model="formData.username" />
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input v-model="formData.password" type="password" />
      </el-form-item>
      <el-form-item label="确认密码" prop="confirmPassword">
        <el-input v-model="formData.confirmPassword" type="password" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleSubmit">注册</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup>
import { reactive, ref } from 'vue';
import { AsyncValidator } from 'async-validator';
import { registerRules } from '@/utils/validateRules';

const formData = reactive({
  username: '',
  password: '',
  confirmPassword: ''
});
const formRef = ref(null);

const handleSubmit = async () => {
  try {
    // 执行校验(基于 async-validator)
    await formRef.value.validate();
    console.log('校验通过,提交数据:', formData);
    alert('注册成功!');
  } catch (err) {
    console.error('校验失败:', err);
  }
};
</script>

2.3 关键逻辑说明

  • ​校验器绑定​​:通过 el-form的 :rules属性绑定校验规则,ref获取校验器实例。
  • ​触发校验​​:调用 formRef.value.validate()方法,返回 Promise 并行执行所有校验规则。
  • ​错误反馈​​:校验失败时,Element UI 会自动高亮错误字段并显示提示信息。

三、进阶场景:自定义校验与异步校验(async-validator)

3.1 需求升级

增加以下复杂校验逻辑:

  1. 密码强度校验(自定义函数:至少包含 1 个大写字母、1 个小写字母、1 个数字)。
  2. 用户名唯一性校验(异步请求:调用后端接口检查用户名是否已存在)。

3.2 自定义校验函数

通过 validator函数实现自定义逻辑,支持同步返回错误信息或抛出异常。

// 更新 registerRules.js
export const registerRules = {
  // ...其他规则
  password: [
    { required: true, message: '密码不能为空' },
    { 
      validator: (rule, value, callback) => {
        const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{6,16}$/;
        if (!reg.test(value)) {
          callback(new Error('密码需包含大小写字母和数字,长度 6-16 字符'));
        } else {
          callback(); // 校验通过
        }
      } 
    }
  ]
};

3.3 异步校验(用户名唯一性检查)

通过返回 Promise或使用 async/await实现异步校验,适用于需要调用接口的场景。

// 更新 registerRules.js
export const registerRules = {
  // ...其他规则
  username: [
    { required: true, message: '用户名不能为空' },
    { 
      asyncValidator: async (rule, value) => {
        // 模拟异步请求(实际项目中替换为真实 API)
        const isUsernameExist = await checkUsernameExist(value);
        if (isUsernameExist) {
          throw new Error('该用户名已被注册');
        }
      } 
    }
  ]
};

// 模拟接口:检查用户名是否存在
const checkUsernameExist = (username) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(username === 'admin'); // 假设用户名为 "admin" 时已存在
    }, 500);
  });
};

3.4 组件中适配异步校验

<script setup>
// ...其他代码
const handleSubmit = async () => {
  try {
    // 异步校验会自动等待 Promise 解决
    await formRef.value.validate();
    console.log('校验通过');
  } catch (err) {
    // err.errors 包含异步校验的错误信息
    console.error('校验失败:', err.errors);
  }
};
</script>

四、高阶场景:复杂对象与数组校验(async-validator)

4.1 需求描述

表单包含 ​​联系地址​​(省市区三级联动)和 ​​标签列表​​(动态添加/删除),要求:

  • 省份、城市、区县均为必填。
  • 标签列表必填(至少 1 个),最多 5 个,且不重复。

4.2 嵌套对象与数组校验规则

// src/utils/validateRules.js
export const complexFormRules = {
  address: {
    type: 'object', // 声明为对象类型
    required: true,
    message: '地址信息不能为空',
    fields: {
      province: { required: true, message: '请选择省份' },
      city: { required: true, message: '请选择城市' },
      district: { required: true, message: '请选择区县' }
    }
  },
  tags: {
    type: 'array', // 声明为数组类型
    required: true,
    message: '至少选择一个标签',
    min: 1,
    max: 5,
    validator: (rule, value) => {
      const uniqueTags = new Set(value);
      if (uniqueTags.size !== value.length) {
        throw new Error('标签不能重复');
      }
    }
  }
};

4.3 组件中集成嵌套校验

<!-- src/views/ComplexForm.vue -->
<template>
  <div class="complex-form">
    <h2>复杂表单</h2>
    <el-form :model="formData" :rules="complexFormRules" ref="formRef">
      <!-- 地址信息 -->
      <el-form-item label="省份" prop="address.province">
        <el-select v-model="formData.address.province" clearable>
          <el-option label="北京市" value="beijing" />
          <el-option label="上海市" value="shanghai" />
        </el-select>
      </el-form-item>
      <el-form-item label="城市" prop="address.city">
        <el-select v-model="formData.address.city" clearable>
          <el-option v-if="formData.address.province === 'beijing'" label="北京市" value="beijing" />
          <el-option v-if="formData.address.province === 'shanghai'" label="上海市" value="shanghai" />
        </el-select>
      </el-form-item>
      <el-form-item label="区县" prop="address.district">
        <el-select v-model="formData.address.district" clearable>
          <el-option v-if="formData.address.city === 'beijing'" label="朝阳区" value="chaoyang" />
          <el-option v-if="formData.address.city === 'shanghai'" label="浦东新区" value="pudong" />
        </el-select>
      </el-form-item>

      <!-- 标签列表 -->
      <el-form-item label="标签列表">
        <el-input 
          v-model="tagInput" 
          placeholder="输入标签后按回车"
          @keyup.enter="addTag"
        />
        <el-tag 
          v-for="(tag, index) in formData.tags" 
          :key="index" 
          closable 
          @close="removeTag(index)"
        >
          {{ tag }}
        </el-tag>
      </el-form-item>

      <el-form-item>
        <el-button type="primary" @click="handleSubmit">提交</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup>
import { reactive, ref } from 'vue';
import { AsyncValidator } from 'async-validator';
import { complexFormRules } from '@/utils/validateRules';

const formData = reactive({
  address: {
    province: '',
    city: '',
    district: ''
  },
  tags: []
});
const formRef = ref(null);
const tagInput = ref('');

const handleSubmit = async () => {
  try {
    await formRef.value.validate();
    console.log('校验通过,提交数据:', formData);
    alert('提交成功!');
  } catch (err) {
    console.error('校验失败:', err.errors);
  }
};

const addTag = () => {
  const newTag = tagInput.value.trim();
  if (newTag && !formData.tags.includes(newTag)) {
    formData.tags.push(newTag);
    tagInput.value = '';
  }
};

const removeTag = (index) => {
  formData.tags.splice(index, 1);
};
</script>

4.4 关键逻辑说明

  • type: 'object'​:声明字段为对象类型,通过 fields配置子字段的校验规则。
  • type: 'array'​:声明字段为数组类型,通过 min/max控制长度,validator实现自定义逻辑(如去重)。
  • ​错误定位​​:嵌套对象错误的 field属性使用点语法(如 address.province),数组错误的 field属性直接为数组名(如 tags)。

五、Element UI 表单高级用法:表格与表单结合校验

5.1 场景描述

在 Element UI 的 el-table中嵌入 el-form表单(如输入框、下拉框),实现表格行数据的动态校验,并在界面上直观显示错误信息。

5.2 实现代码与思路

5.2.1 页面效果与需求

image.png

表格中每一行包含“审核状态”的下拉框,需校验每一行的“审核状态”是否选择,校验结果显示在界面上(如红色提示)。

image.png

5.2.2 关键代码解析
<template>
  <el-card :body-style="{ padding: '100px' }">
    <el-form ref="formRef" :model="{ checkList: tableData }" :rules="rules">
      <el-table :data="tableData" style="width: 60%" border>
        <!-- 其他列 -->
        <el-table-column label="审核状态">
          <template slot-scope="scope">
            <!-- 校验项的 prop 需指定为数组路径:`checkList.${scope.$index}.status` -->
            <el-form-item 
              label="审核状态" 
              :prop="`checkList.${scope.$index}.status`" 
              :rules="rules.status"
            >
              <el-select v-model="scope.row.status" clearable>
                <el-option label="通过" value="pass" />
                <el-option label="拒绝" value="reject" />
              </el-select>
            </el-form-item>
          </template>
        </el-table-column>
      </el-table>
      <el-button @click="validateForm()" class="mt16">验证</el-button>
    </el-form>
  </el-card>
</template>

<script>
export default {
  data() {
    return {
      tableData: [
        { date: '2016-05-02', name: '王小虎', status: '' },
        { date: '2016-05-03', name: '王小虎', status: '' }
      ],
      rules: {
        status: [
          { required: true, message: '请选择审核状态', trigger: 'change' }
        ]
      }
    };
  },
  methods: {
    validateForm() {
      this.$refs.formRef.validate((valid) => {
        if (valid) {
          alert('验证通过');
        } else {
          alert('验证失败');
        }
      });
    }
  }
};
</script>
5.2.3 核心要点
  • ​动态 prop绑定​​:表格行的校验项 prop需通过 checkList.${scope.$index}.status格式指定,其中 scope.$index是当前行的索引。
  • ​校验规则继承​​:el-form的 :rules中定义的 status规则会应用到每一行的校验项。
  • ​错误反馈​​:校验失败时,Element UI 会自动在对应行的下拉框旁显示错误提示。

六、Element UI 表单高级用法:树形控件与表单结合校验

6.1 场景描述

在 Element UI 的 el-tree树形控件中嵌入 el-form表单(如单选框),实现树形结构数据的动态校验,并准确定位错误节点。

6.2 实现代码与思路

image.png

6.2.1 页面效果与需求

树形控件中每个节点包含“作用域”的单选框,需校验每个节点的“作用域”是否选择,校验结果显示在对应节点旁。

6.2.2 关键代码解析
<template>
  <el-card shadow="always">
    <el-form :model="{ treeOptions }" :rules="addEditFormRules" ref="ruleForm">
      <el-tree :data="treeOptions" node-key="menuId" :props="treeProps">
        <span slot-scope="{ data }" class="custom-tree-node">
          <span>{{ data.menuName }}</span>
          <el-form-item 
            :prop="getTreeProp(data.menuName)" 
            :rules="addEditFormRules.scope"
          >
            <el-radio-group v-model="data.scope">
              <el-radio :label="item.value" v-for="item in optionsData" :key="item.value" />
            </el-radio-group>
          </el-form-item>
        </span>
      </el-tree>
      <el-button @click="validateForm()" type="primary">验证</el-button>
    </el-form>
  </el-card>
</template>

<script>
export default {
  data() {
    return {
      treeOptions: [/* 树形数据 */],
      optionsData: [/* 单选框选项 */],
      addEditFormRules: {
        scope: [{ required: true, message: '请选择!', trigger: 'change' }]
      },
      treeProps: { label: 'menuName', children: 'children' }
    };
  },
  methods: {
    // 递归查找树形节点路径
    findPathToTarget(tree, targetValue, path = []) {
      for (let i = 0; i < tree.length; i++) {
        const currentNode = tree[i];
        path.push(i);
        if (currentNode.menuName === targetValue) return path;
        if (currentNode.children) {
          const result = this.findPathToTarget(currentNode.children, targetValue, path);
          if (result) return result;
        }
        path.pop();
      }
      return null;
    },
    // 生成校验 prop(如 `treeOptions.0.children.1.children.0.scope`)
    getTreeProp(menuName) {
      const path = this.findPathToTarget(this.treeOptions, menuName);
      return path ? `treeOptions.${path.join('.children.')}` : '';
    },
    validateForm() {
      this.$refs.ruleForm.validate((valid) => {
        if (valid) {
          alert('验证通过');
        } else {
          alert('验证失败');
        }
      });
    }
  }
};
</script>
6.2.3 核心要点
  • ​递归路径查找​​:通过 findPathToTarget函数递归遍历树形数据,生成节点的唯一路径(如 treeOptions.0.children.1.children.0)。
  • ​动态 prop生成​​:根据节点路径生成校验 prop(如 treeOptions.0.children.1.children.0.scope),确保校验规则精准定位到目标节点。
  • ​错误定位​​:校验失败时,Element UI 会在对应树形节点的单选框旁显示错误提示。

七、常见使用场景总结

场景类型典型需求解决方案
基础字段校验必填项、长度限制、格式匹配使用 async-validator的 requiredminmaxpattern规则
自定义逻辑校验密码强度、金额范围通过 validator函数实现自定义校验逻辑
异步数据校验用户名/手机号唯一性检查使用 asyncValidator返回 Promise 或 async 函数
嵌套对象校验地址信息(省市区)、用户详情(联系方式+紧急联系人)定义 type: 'object'并配置 fields子规则
数组校验标签列表、SKU 列表使用 type: 'array'min/max+ 自定义 validator
表格+表单结合校验表格行内输入框/下拉框校验动态绑定 prop为 数组名.${行索引}.字段名
树形控件+表单结合校验树形节点内单选框/复选框校验递归查找节点路径,动态生成 prop路径(如 treeOptions.0.children.1.scope

八、总结

本文从基础到高级,全面解析了 Vue3 中复杂表单校验的实现方案:

  • ​基础校验​​:通过 async-validator快速实现简单字段的必填、格式、长度校验。
  • ​自定义与异步校验​​:利用 validator和 asyncValidator处理密码强度、用户名唯一性等复杂逻辑。
  • ​嵌套结构校验​​:通过 type: 'object'和 type: 'array'实现对象、数组的深度校验。
  • ​Element UI 高级应用​​:结合 el-table和 el-tree实现表格行、树形节点的动态校验,通过动态 prop绑定和递归路径查找精准定位错误。

在实际项目中,建议根据业务复杂度选择合适的方案:

  • 简单表单:直接使用 Element UI 内置校验。
  • 复杂结构(如嵌套对象、数组):结合 async-validator实现深度校验。
  • 表格/树形表单:通过动态 prop和递归路径查找,确保校验规则与数据结构严格匹配。

​官方文档​​:

  • async-validator GitHub
  • Element UI 表单文档