Vee-validate在嵌套表单中的使用

813 阅读4分钟

在 Vue.js 中,表单验证是常见的需求,而 vee-validate 库提供了一个简单而强大的解决方案。随着 vee-validate 从 3.x 版本升级到 4.x 版本,很多 API 和使用方式发生了变化。本文将带你从头到尾了解这两个版本的用法


Vee-Validate 简介

vee-validate 是一个专为 Vue.js 设计的表单验证库,它提供了灵活的验证规则、表单状态管理和错误信息展示等功能,支持 Vue 2 和 Vue 3。

为什么要学习 vee-validate

  • 易于集成:与 Vue.js 深度集成,表单验证非常简洁。
  • 规则扩展:内置了大量的验证规则,支持自定义规则。
  • 实时验证:支持表单控件的实时验证。
  • 支持异步验证:可以与后端交互进行异步验证。

版本差异概览

vee-validate@3vee-validate@4 版本中,主要的变化集中在:

  • API 设计:从基于选项式的 API(如 useForm, useField)改为 Composition API 风格。
  • 更简化的语法:通过 Vue 3 的 reactiveref,使表单验证更加灵活。
  • 验证规则的引入方式:4.x 改进了验证规则的引入和使用方式。

Vee-Validate 3.x 版本使用

安装和基础配置

npm install vee-validate@3

在main.js引入

import Vue from 'vue'
import App from '@/App.vue'
import router from '@/router/index.js'
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate'
import { required, max, min, email } from 'vee-validate/dist/rules'
import validate from '@/utils/validate.js'
// 必填校验
extend('required', {
    ...required, // 官方自带的校验规则
    message: '{_field_}为必填项!' // 自定义错误消息 
})
// 邮箱校验规则
extend('email', {
    ...email,
    message: `邮箱格式不正确`
})
console.log('min', min.params)
extend('min', {
    ...min, // 官方自带的校验规则
    message: `{_field_}输入的值至少{length}位!`, // 自定义错误消息 
    params: ['length']
})
extend('max', {
    ...max,  // 官方自带的校验规则
    message: `{_field_}输入的值至多{length}位!`, // 自定义错误消息 
    params: ['length']
})
validate.forEach(item => {
    extend(item.name, {validate: item.validate, params: item.params})
});

//全局注册
Vue.component("ValidationObserver", ValidationObserver)
Vue.component("ValidationProvider", ValidationProvider)
new Vue({
    router,
    render: h => h(App)
}).$mount('#app')

vee-validate@3项目实例

<template>
  <div class="customForm">
    商铺表单
    <ValidationObserver tag="form" class="form" ref="form">
      <ValidationProvider rules="required|min:3|max:5" name="姓名" v-slot="{ errors }">
        <div class="form-item">
          <label for="name">代理人姓名:
            <input v-model="form.name" id="name" type="text" placeholder="请输入代理人姓名">
          </label>
          <span class="error">{{ errors[0] }}</span>
        </div>
      </ValidationProvider>
      <ValidationProvider rules="required" name="类型" v-slot="{ errors }">
        <div class="form-item">
          <label for="selectType">
            类型: 
          <select name="" id="selectType" placeholder="请选择类型" v-model="form.selectType" rules="reuired">
            <option value="">--请选择类型--</option>
            <option value="1">单区域</option>
            <option value="2">多区域</option>
          </select>
          </label>
          <span class="error">{{ errors[0] }}</span>
        </div>
      </ValidationProvider>
      <h4>关联区域属性:</h4>
      <template v-if="form.selectType == 1" >
        <div v-if="oneTypeArr.length === 0">
          <button class="btn" style="margin-bottom: 15px;" type="submit" @click="addChildren">添加选项</button>
        </div>
        <div class="flex-center" v-for="(item, i) in oneTypeArr" :key="i">
          <ValidationProvider rules="required" :name="`店名${i+1}`" v-slot="{ errors }">
            <div class="form-item">
              <label for="storeName">
                {{`店名${i+1}:`}}
                <input type="text" id="storeName" v-model="item.storeName" placeholder="请输入店名">
              </label>
              <br>
              <span>{{ errors[0] }}</span>
            </div>
          </ValidationProvider>
          <ValidationProvider rules="required" :name="`经理${i+1}`" v-slot="{ errors }">
            <div class="form-item">
              <label for="manager">
                {{`经理${i+1}:`}}
                <input type="text" id="manager" v-model="item.manager" placeholder="请输入店铺经理">
              </label>
              <br>
              <span>{{ errors[0] }}</span>
            </div>
          </ValidationProvider>
          <button class="btn" type="submit" @click="addChildren">添加选项</button>
          <button class="btn" style="margin-left: 15px" type="submit" @click="subChildren($event, item.index)">删除选项</button>
        </div>
      </template>
      <template v-if="form.selectType == 2">
        <div class="flex-center" v-for="(item,i) in twoTypeArr" :key="i">
          <ValidationProvider rules="required" :name="`店铺名称${i+1}`" v-slot="{ errors }">
            <div class="form-item">
              <label for="storeName">
                {{`店铺名称${i+1}:`}}
                <input type="text" name="" id="storeName" v-model="item.storeName" placeholder="请输入店铺名称">
              </label>
              <br>
              <span>{{ errors[0] }}</span>
            </div>
          </ValidationProvider>
          <ValidationProvider rules="required" :name="`店铺标识${i+1}`" v-slot="{ errors }">
            <div class="form-item">
              <label for="storeCode">
                {{`店铺标识${i+1}:`}}
                <input type="text" id="storeCode" v-model="item.storeCode" placeholder="请输入店铺标识">
              </label>
              <br>
              <span>{{ errors[0] }}</span>
            </div>
          </ValidationProvider>
        </div>
      </template>
      <div class="form-item">
        <button type="submit" @click="submit">提交</button>
      </div>
    </ValidationObserver>
  </div>
</template>

<script>

import { nanoid } from 'nanoid'
export default {
    name: 'customForm',
    data () {
        return {
            form: {
              selectType: void 0,
              config:{
              }
            },
            oneTypeArr:[{
              index: 1,
              storeName: '',
              manager: ''
            }],
            twoTypeArr: [
              {index: 1, storeName: '', storeCode: ''}
            ],
        }
    },
    methods: {
      // 添加选项
      addChildren(e){
        e.preventDefault();
        let index = nanoid()
        if(this.form.selectType === '1'){
          if(this.oneTypeArr.length == 5) {return alert('最多添加5条记录')}
          this.oneTypeArr.push({
            index,
            storeName: '',
            manager: ''
          })
        }else if(this.form.selectType === '2'){
          this.twoTypeArr.push({
            index,
            storeName: '',
            storeCode: ''
          })
        }
      },
      // 删除选项
      subChildren(e, index){
        e.preventDefault()
        console.log(index)
        if(this.form.selectType == 1){
          this.oneTypeArr = this.oneTypeArr.filter(item => item.index !== index)
        }else if(this.form.selectType === 2){
          this.twoTypeArr = this.twoTypeArr.filter(item => item.index !== index)
        }
      },
      // 表单提交
      submit(e){
        e.preventDefault()
        this.$refs.form.validate().then((status)=>{
          if (!status) return false;
          console.log('validate',status)
        })
        console.log('submit',this.$refs.form,this.form)
      }
    }
}
</script>

<style lang='less' scoped>
.flex-center{
  display: flex;
  justify-content: center;
  & .form-item{
    margin-right: 15px;
    & span{
      color: red;
    }
  }
}
.btn{
  width: 80px;
  height: 30px;
  font-size: 16px;
  background-color: skyblue;
  border: 1px solid #000;
  border-radius: 3px;
}
  .customForm{
    margin-top: 100px;
    text-align: center;
    & .form{
      display: block;
      height: 700px;
    }
    & .form-item{
      height: 60px;
      text-align: center;
      input{
        outline: none;
        height: 25px;
      }
      select{
        outline: none;
        width: 170px;
        height: 30px;
      }
      button{
        width: 80px;
        height: 30px;
        font-size: 16px;
        background-color: skyblue;
        border: 1px solid #000;
        border-radius: 3px;
      }
      & .error{
        display: block;
        color: red;
      }
    }
  }
</style>

vee-validate@4版本使用

第三方包安装

npm install vee-validate@4 @vee-validate/rules

vee-validate@4项目实例

<template>
  <div class="customForm">
    商铺表单
    <form class="form" @submit.prevent="submit">
      <ValidationObserver v-slot="{ handleSubmit, errors }">
        <div class="form-item">
          <label for="name">代理人姓名:
            <input v-model="form.name" id="name" type="text" placeholder="请输入代理人姓名" />
          </label>
          <span class="error">{{ errors.name ? errors.name[0] : '' }}</span>
        </div>

        <div class="form-item">
          <label for="selectType">
            类型:
            <select v-model="form.selectType" id="selectType" placeholder="请选择类型">
              <option value="">--请选择类型--</option>
              <option value="1">单区域</option>
              <option value="2">多区域</option>
            </select>
          </label>
          <span class="error">{{ errors.selectType ? errors.selectType[0] : '' }}</span>
        </div>

        <h4>关联区域属性:</h4>
        <template v-if="form.selectType == '1'">
          <div v-if="oneTypeArr.length === 0">
            <button class="btn" style="margin-bottom: 15px;" type="button" @click="addChildren">添加选项</button>
          </div>
          <div class="flex-center" v-for="(item, i) in oneTypeArr" :key="item.index">
            <div class="form-item">
              <label for="storeName">
                {{`店名${i + 1}:`}}
                <input v-model="item.storeName" type="text" id="storeName" placeholder="请输入店名" />
              </label>
              <span>{{ errors[`storeName${i}`] ? errors[`storeName${i}`][0] : '' }}</span>
            </div>

            <div class="form-item">
              <label for="manager">
                {{`经理${i + 1}:`}}
                <input v-model="item.manager" type="text" id="manager" placeholder="请输入店铺经理" />
              </label>
              <span>{{ errors[`manager${i}`] ? errors[`manager${i}`][0] : '' }}</span>
            </div>

            <button class="btn" type="button" @click="addChildren">添加选项</button>
            <button class="btn" style="margin-left: 15px" type="button" @click="subChildren($event, item.index)">删除选项</button>
          </div>
        </template>

        <template v-if="form.selectType == '2'">
          <div class="flex-center" v-for="(item, i) in twoTypeArr" :key="item.index">
            <div class="form-item">
              <label for="storeName">
                {{`店铺名称${i + 1}:`}}
                <input v-model="item.storeName" type="text" id="storeName" placeholder="请输入店铺名称" />
              </label>
              <span>{{ errors[`storeName${i}`] ? errors[`storeName${i}`][0] : '' }}</span>
            </div>

            <div class="form-item">
              <label for="storeCode">
                {{`店铺标识${i + 1}:`}}
                <input v-model="item.storeCode" type="text" id="storeCode" placeholder="请输入店铺标识" />
              </label>
              <span>{{ errors[`storeCode${i}`] ? errors[`storeCode${i}`][0] : '' }}</span>
            </div>
          </div>
        </template>

        <div class="form-item">
          <button class="btn" type="submit">提交</button>
        </div>
      </ValidationObserver>
    </form>
  </div>
</template>
<script>
import { defineRule, useForm, useField, ErrorMessage } from 'vee-validate';
import { required, min, max } from '@vee-validate/rules';
import { nanoid } from 'nanoid';

export default {
  name: 'customForm',
  data() {
    return {
      form: {
        selectType: void 0,
        config: {}
      },
      oneTypeArr: [
        {
          index: 1,
          storeName: '',
          manager: ''
        }
      ],
      twoTypeArr: [
        { index: 1, storeName: '', storeCode: '' }
      ]
    };
  },
  mounted() {
    defineRule('required', required);
    defineRule('min', min);
    defineRule('max', max);
  },
  methods: {
    // 添加选项
    addChildren(e) {
      e.preventDefault();
      let index = nanoid();
      if (this.form.selectType === '1') {
        if (this.oneTypeArr.length == 5) {
          return alert('最多添加5条记录');
        }
        this.oneTypeArr.push({
          index,
          storeName: '',
          manager: ''
        });
      } else if (this.form.selectType === '2') {
        this.twoTypeArr.push({
          index: nanoid(),
          storeName: '',
          storeCode: ''
        });
      }
    },
    // 删除选项
    subChildren(e, index) {
      e.preventDefault();
      if (this.form.selectType == '1') {
        this.oneTypeArr = this.oneTypeArr.filter(item => item.index !== index);
      } else if (this.form.selectType === '2') {
        this.twoTypeArr = this.twoTypeArr.filter(item => item.index !== index);
      }
    },
    // 表单提交
    submit() {
      this.$refs.form.validate().then((status) => {
        if (!status) return false;
        console.log('validate', status);
      });
      console.log('submit', this.$refs.form, this.form);
    }
  }
};
</script>

关键修改点总结:

  1. useFormuseField:使用 useForm 来管理整个表单的验证,使用 useField 来管理每个字段的验证。通过 useField 返回的 errors 对象获取每个字段的验证错误信息。
  2. 表单验证:使用 @vee-validate/rules 中的内置规则(如 required, min, max)并通过 defineRule 注册自定义规则。
  3. v-sloterrors:在 vee-validate@4 中,errors 不再是一个普通对象,而是响应式的,直接通过 useField 获取每个字段的验证错误。