form

rules 绑定

  1. 绑定到el-form

    <template>
        <el-form ref="form" :model="form" :rules="rules" label-width="100px">
            <el-form-item label="age" prop="age">
                <el-input v-model.number="form.age" type="age"></el-input>
            </el-form-item>
        </el-form>
    </template>
    
    <script lang="ts">
    const checkAge = (rule, value, callback) => {
        console.warn('checkAge');
        if (value < 18) {
            callback(new Error('Age must be greater than 18'));
        } else {
            callback();
        }
    };
    export default {
        data() {
            // const checkAge = (rule, value, callback) => {
            //     console.warn('checkAge');
            //     if (value < 18) {
            //         callback(new Error('Age must be greater than 18'));
            //     } else {
            //         callback();
            //     }
            // };
            return {
                form: {
                    age: ''
                },
                rules: {
                    age: [
                        { required: true, message: 'age is required' },
                        { type: 'number', message: 'age must be a number' },
                        { validator: checkAge, trigger: ['blur', 'change'] }
                    ]
                }
            };
        }
    };
    </script>
    
    

    checkAge 可以写到data里面或者export default 外面

    缺点:不能与其他data中的值关联校验。

  2. 绑定到el-form-item

    <template>
        <el-form ref="form" :model="form" label-width="100px">
            <el-form-item
                label="name"
                prop="name"
                :rules="[
                    { required: true, message: 'name is required' },
                    { type: 'string', message: 'name must be a string' },
                    { validator: checkName, trigger: ['blur'] }
                ]"
            >
                <el-input v-model="form.name"></el-input>
            </el-form-item>
        </el-form>
    </template>
    
    <script lang="ts">
    export default {
        data() {
            return {
                form: {
                    name: '',
                    age: ''
                },
                realName: 'zhangsan'
            };
        },
        methods: {
            checkName(rule, value, callback) {
                console.warn('checkName');
                if (value.length < 6 || value.length > 10) {
                    callback(new Error('name 长度6~10'));
                } else if (!value.startsWith(this.realName)) {
                    callback(new Error(`必须以 ${this.realName} 开头`));
                } else {
                    callback();
                }
            }
        }
    };
    </script>
    
    

    校验方法需写到methods中,🉑️与其他data中的值关联校验。

对象校验

  1. 一行

    image-20211203162810239

    缺点:校验不通过时,所有输入框变红。

    <template>
        <el-form ref="form" :model="form" label-width="120px" :rules="rules">
            <el-form-item label="地址" prop="origin">
                <el-row :gutter="10">
                    <el-col :span="8">
                        <el-input v-model="form.origin.protocol" placeholder="协议"></el-input>
                    </el-col>
                    <el-col :span="8">
                        <el-input v-model="form.origin.hostname" placeholder="域名"></el-input>
                    </el-col>
                    <el-col :span="8">
                        <el-input v-model.number="form.origin.port" placeholder="端口"></el-input>
                    </el-col>
                </el-row>
            </el-form-item>
        </el-form>
    </template>
    
    <script lang="ts">
    export default {
        data() {
            return {
                form: {
                    origin: {
                        protocol: '',
                        hostname: '',
                        port: ''
                    }
                },
                rules: {
                    origin: {
                        type: 'object',
                        required: true,
                        fields: {
                            protocol: [
                                { required: true, message: '协议必填', trigger: 'blur' },
                                { type: 'enum', enum: ['http', 'https'], message: '不在枚举范围', trigger: 'blur' }
                            ],
                            hostname: [
                                { required: true, message: '域名必填', trigger: 'blur' },
                                {
                                    message: '域名不正确',
                                    pattern:
                                        /^(?=^.{3,255}$)[*a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/
                                }
                            ],
                            port: [
                                { type: 'integer', required: true, message: '端口必填', trigger: 'blur' },
                                { type: 'integer', min: 1, max: 30000, message: '端口范围1~30000', trigger: 'blur' }
                            ]
                        }
                    }
                }
            };
        }
    };
    </script>
    <style scoped>
    .el-row + .el-row {
        margin-top: 10px;
    }
    </style>
    
    
  2. 每个输入框单独校验

    image-20211203165036004

    <template>
        <el-form ref="form" :model="form" label-width="120px" :rules="rules">
            <el-form-item label="地址">
                <el-row :gutter="10">
                    <el-col :span="8">
                        <el-form-item prop="origin.protocol">
                            <el-input v-model="form.origin.protocol" placeholder="协议"></el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="8">
                        <el-form-item prop="origin.hostname">
                            <el-input v-model="form.origin.hostname" placeholder="域名"></el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="8">
                        <el-form-item prop="origin.port">
                            <el-input v-model.number="form.origin.port" placeholder="端口"></el-input>
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form-item>
        </el-form>
    </template>
    
    <script lang="ts">
    export default {
        data() {
            return {
                form: {
                    origin: {
                        protocol: '',
                        hostname: '',
                        port: ''
                    }
                },
                rules: {
                    origin: {
                        protocol: [
                            { required: true, message: '协议必填', trigger: 'blur' },
                            { type: 'enum', enum: ['http', 'https'], message: '不在枚举范围', trigger: 'blur' }
                        ],
                        hostname: [
                            { required: true, message: '域名必填', trigger: 'blur' },
                            {
                                message: '域名不正确',
                                pattern: /^(?=^.{3,255}$)[*a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/
                            }
                        ],
                        port: [
                            { type: 'integer', required: true, message: '端口必填', trigger: 'blur' },
                            { type: 'integer', min: 1, max: 30000, message: '端口范围1~30000', trigger: 'blur' }
                        ]
                    }
                }
            };
        }
    };
    </script>
    
    
  3. 一行,为每个输入框进行校验,但是校验文本只显示一个

    image-20211217134508302

数组校验

  1. 普通

    image-20211202141650242

    <template>
        <el-form ref="form" :model="form" :rules="rules" label-width="100px">
            <el-form-item label="favoriteFood" prop="favoriteFood">
                <el-checkbox-group v-model="form.favoriteFood">
                    <el-checkbox label="apple">苹果</el-checkbox>
                    <el-checkbox label="banana">香蕉</el-checkbox>
                    <el-checkbox label="orange">橙子</el-checkbox>
                </el-checkbox-group>
            </el-form-item>
        </el-form>
    </template>
    
    <script lang="ts">
    export default {
        data() {
            const checkFavoriteFood = (rule, value, callback) => {
                console.warn('checkFavoriteFood', value);
                if (!value.includes('apple')) {
                    callback(new Error('必须有苹果'));
                } else {
                    callback();
                }
            };
            return {
                form: {
                    name: '',
                    age: '',
                    favoriteFood: []
                },
                rules: {
                    favoriteFood: [
                        { required: true, message: 'favoriteFood is required' },
                        // { type: 'array', message: '至少有选两项', min: 2 },
                        // { type: 'array', message: '只能选两项', len: 2 },
                        { type: 'array', message: '最多选两项', max: 2 },
                        { validator: checkFavoriteFood, trigger: ['blur', 'change'] }
                    ]
                }
            };
        }
    };
    </script>
    
  2. 多行

    image-20211202161912494

    <template>
        <el-form ref="form" :model="form" label-width="120px">
            <el-form-item
                v-for="(domain, index) in form.domains"
                :key="index"
                :label="'Domain' + index"
                :prop="`domains.${index}.value`"
                :rules="{
                    required: true,
                    message: 'domain can not be null',
                    trigger: 'blur'
                }"
            >
                <el-row :gutter="10">
                    <el-col :span="20">
                        <el-input v-model="domain.value"></el-input>
                    </el-col>
                    <el-col :span="4">
                        <el-button @click.prevent="removeDomain(index)" icon="el-icon-delete" type="danger">
                            Delete
                        </el-button>
                    </el-col>
                </el-row>
            </el-form-item>
            <el-form-item>
                <el-button @click="addDomain" icon="el-icon-plus">New domain</el-button>
            </el-form-item>
        </el-form>
    </template>
    
    <script lang="ts">
    export default {
        data() {
            return {
                form: {
                    domains: [
                        {
                            value: ''
                        }
                    ]
                }
            };
        },
        methods: {
            removeDomain(index) {
                this.form.domains.splice(index, 1);
                if (!this.form.domains.length) this.addDomain();
            },
            addDomain() {
                this.form.domains.push({
                    value: ''
                });
            }
        }
    };
    </script>
    <style scoped>
    .el-row + .el-row {
        margin-top: 10px;
    }
    </style>
    
    
    

    image-20211202164117273

    用slot只展示第一行label

    <template v-if="!index" v-slot:label>Domain</template>
    
  3. 数组中对象

    image-20211203172647069

    <template>
        <el-form ref="form" :model="form" label-width="120px" :rules="rules">
            <el-form-item v-for="(item, index) in form.domains" :key="index">
                <template v-if="!index" v-slot:label>地址</template>
                <el-row :gutter="10">
                    <el-col :span="7">
                        <el-form-item :prop="`domains.${index}.protocol`" :rules="rules.domains.protocol">
                            <el-input v-model="item.protocol" placeholder="协议"></el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="7">
                        <el-form-item :prop="`domains.${index}.hostname`" :rules="rules.domains.hostname">
                            <el-input v-model="item.hostname" placeholder="域名"></el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="7">
                        <el-form-item :prop="`domains.${index}.port`" :rules="rules.domains.port">
                            <el-input v-model.number="item.port" placeholder="端口"></el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="3">
                        <el-button @click.prevent="removeDomain(index)" icon="el-icon-delete" type="danger">
                            Delete
                        </el-button>
                    </el-col>
                </el-row>
            </el-form-item>
            <el-form-item>
                <el-button @click="addDomain" icon="el-icon-plus">New domain</el-button>
            </el-form-item>
        </el-form>
    </template>
    
    <script lang="ts">
    export default {
        data() {
            return {
                form: {
                    domains: [
                        {
                            protocol: '',
                            hostname: '',
                            port: ''
                        }
                    ]
                },
                rules: {
                    domains: {
                        protocol: [
                            { required: true, message: '协议必填', trigger: 'blur' },
                            { type: 'enum', enum: ['http', 'https'], message: '不在枚举范围', trigger: 'blur' }
                        ],
                        hostname: [
                            { required: true, message: '域名必填', trigger: 'blur' },
                            {
                                message: '域名不正确',
                                pattern: /^(?=^.{3,255}$)[*a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/
                            }
                        ],
                        port: [
                            { type: 'integer', required: true, message: '端口必填', trigger: 'blur' },
                            { type: 'integer', min: 1, max: 30000, message: '端口范围1~30000', trigger: 'blur' }
                        ]
                    }
                }
            };
        },
        methods: {
            removeDomain(index) {
                this.form.domains.splice(index, 1);
                if (!this.form.domains.length) this.addDomain();
            },
            addDomain() {
                this.form.domains.push({
                    protocol: '',
                    hostname: '',
                    port: ''
                });
            }
        }
    };
    </script>
    
    
    rules: {
                    domains: {
                        type: 'array',
                        required: true,
                        message: '至少有一个地址',
                        defaultField: {
                            type: 'object',
                            fields: {
                                protocol: [
                                    { required: true, message: '协议必填', trigger: 'blur' },
                                    { type: 'enum', enum: ['http', 'https'], message: '不在枚举范围', trigger: 'blur' }
                                ],
                                hostname: [
                                    { required: true, message: '域名必填', trigger: 'blur' },
                                    {
                                        message: '域名不正确',
                                        pattern:
                                            /^(?=^.{3,255}$)[*a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/
                                    }
                                ],
                                port: [
                                    { type: 'integer', required: true, message: '端口必填', trigger: 'blur' },
                                    { type: 'integer', min: 1, max: 30000, message: '端口范围1~30000', trigger: 'blur' }
                                ]
                            }
                        }
                    }
                }
    
  4. demo1

    image-20211217174751352

    需求:

    实现向对象中加key、value的操作。

    维护一个数组arr:[{key:'',value:''}] 对数组中的key进行单独校验 使其不能重复。

    思路:

    1. 维护一个keyList,当input-key的输入框focus时,我们拿到除当前输入框之外的其他key。用于当前input的输入框校验。

    2. 提交时,keyList要维护所有重复的key,用于每个input判断。

    3. 删除时,也要⚠️,当在input focus 后删除,

    如果遍历时绑定的:key='index' 那么删除时,由于索引位置的变化,其实导致了删除时,其位置后面的数据都变化了。

    由于keyList绑定的还是之前focus时的keyList,由于数据变化,再次走校验,导致校验不正常。

    Dec-20-2021 15-33-10

    • blur时清空keyList

    • 删除时重新校验,这里需要注意需要nextTick

      deleteLabel(index) {
        this.nodeLabelForm.labelList.splice(index, 1)
        // 校验时 keyList要存重复的key
        this.$nextTick(() => {
          // 不使用nextTick,会导致报错
          this.keyList = this.getRepeatElement()
          this.$refs.form.validate()
        })
      },
      

      image-20211220155017919

      报错原因:因为数据变化时,页面还没有发生变化,导致这一项还是需要校验的,但是数据已经没有了。所以需要$nextTick

    • 绑定唯一的key解决。

    完整代码

    <template>
        <div>
            <el-form ref="form" :model="nodeLabelForm" size="small" label-position="top">
                <el-form-item label="标签">
                    <el-row v-for="(item, index) in nodeLabelForm.labelList" :key="index" :gutter="10">
                        <div v-show="item.value !== null">
                            <el-col :span="11">
                                <el-form-item
                                    :prop="`labelList.${index}.key`"
                                    :rules="{
                                        trigger: ['change', 'blur'],
                                        validator: validateKey
                                    }"
                                >
                                    <el-input
                                        v-model.trim="item.key"
                                        clearable
                                        placeholder=""
                                        @focus="keyFocus(index)"
                                        @blur="keyBlur"
                                    />
                                </el-form-item> 
                            </el-col>
                            <el-col :span="11">
                                <el-form-item>
                                    <el-input v-model.trim="item.value" clearable placeholder="" />
                                </el-form-item>
                            </el-col>
                            <el-col :span="2">
                                <el-tooltip effect="light" content="删除" placement="top">
                                    <i class="el-icon-delete delete-icon" @click="deleteLabel(index)"></i>
                                </el-tooltip>
                            </el-col>
                        </div>
                    </el-row>
                    <el-row>
                        <el-button
                            type="primary"
                            icon="el-icon-plus"
                            :disabled="addLabelDisabled"
                            @click="addLabel"
                        >添加标签</el-button>
                    </el-row>
                </el-form-item>
            </el-form>
            <el-button @click="config.visible = false">取消</el-button>
            <el-button type="primary" :loading="loading" @click="confirm">确定</el-button>
        </div>
    </template>
    <script>
    
    export default {
        name: 'EditVolumeForm',
        data() {
            return {
                loading: false,
                nodeLabelForm: {
                    name: '',
                    labelList: [{ key: '', value: '' }]
                },
                keyList: []
            };
        },
        computed: {
            addLabelDisabled() {
                return this.nodeLabelForm.labelList.some(item => {
                    return !item.key
                })
            }
        },
    
        methods: {
            //  rules
            validateKey(rule, value, callback) {
                console.warn('校验', this.keyList);
                if (value === '') {
                    callback(new Error('无法添加空的 key'))
                } else if (this.keyList.includes(value)) {
                    callback(new Error('无法添加重复的 key'))
                } else {
                    callback()
                }
            },
            keyFocus(i) {
                // focus 时,找到非当前输入框的 其他key,用于校验
                this.keyList = this.nodeLabelForm.labelList.filter((item, index) => index !== i).map(item => item.key)
            },
            keyBlur() {
                this.keyList = []
            },
            // 标签
            addLabel() {
                this.nodeLabelForm.labelList.push({
                    key: '',
                    value: ''
                })
            },
            deleteLabel(index) {
                this.nodeLabelForm.labelList.splice(index, 1)
            },
            clearValidate() {
                if (this.$refs.form) {
                    this.$refs.form.clearValidate()
                }
            },
            getRepeatElement() {
                const allKeyList = this.nodeLabelForm.labelList.map(item => item.key)
                const noRepeat = []
                const repeat = []
                allKeyList.forEach(item => {
                    noRepeat.includes(item) ? repeat.push(item) : noRepeat.push(item)
                })
                return repeat
            },
            async confirm() {
                // 校验时 keyList要存重复的key
                this.keyList = this.getRepeatElement()
                this.$refs.form.validate((valid) => {
                    if (valid) {
                        alert('submit!')
                    } else {
                        console.log('error submit!!')
                        return false
                    }
                })
    
            }
        }
    };
    </script>
    <style lang="scss" scoped>
    .tips {
        color: rgba($color: #00102f, $alpha: 0.45);
    }
    .el-row + .el-row {
        margin-top: 10px;
    }
    .mb-20 {
        margin-bottom: 20px;
    }
    .accessModes {
        .el-radio--small.is-bordered {
            height: 50px;
        }
        span {
            display: block;
            margin-left: 20px;
        }
    }
    .storage {
        display: flex;
        .storage-slider {
            flex: 1;
        }
        .storage-unit {
            width: 20px;
            margin-left: 10px;
            vertical-align: middle;
        }
    }
    </style>
    
    

validate 方法

官方

this.$refs[formName].validate((valid) => {
        if (valid) {
          alert('submit!')
        } else {
          console.log('error submit!!')
          return false
        }
})

Async/await

await vaild = this.$refs[formName].validate().catch(err => {
        return err;
    });
// 将 element-ui 或 mtd 的 validate 变成一个始终是resolved状态的promise, 不用写try-catch或者回调函数的形式
export const pifyValidate = validateFn => {
    return new Promise(resolve => {
        validateFn(valid => {
            resolve(valid);
        });
    });
};

validateField方法

可用于分步校验

Async/await

// 将 element-ui 的 validateField 多个参数校验时,返回 校验成功true|失败false
// 使用方法:
// const vaild = await pifyValidateField(this.$refs[formName].validateField, ['name', 'region']);
export const pifyValidateField = async (validateFieldFn, args) => {
  const promiseList = args.map(item => {
    return new Promise(resolve => {
      validateFieldFn(item, errMsg => {
        errMsg ? resolve(errMsg) : resolve()
      })
    })
  })
  const msgArr = await Promise.all(promiseList)
  const errMsgArr = msgArr.filter(item => item !== undefined)
  return !errMsgArr.length
}

form中的v-if/v-show

涉及到v-if 的表单,注意要加key

当v-if值由false变true时,校验不触发,因为vue渲染时复用了 必须加key vue2 常见

Vue3 会自动加key 不需要手动加,支持<template> 绑定key ,官方link

分步骤表单用v-show

‼️入坑多次

rules 中的关键字说明

rules官方文档

Type
  • string: 字符串类型(默认值)
  • number: 数字类型
  • boolean:布尔类型
  • method: 函数类型
  • regexp:正则表达式
  • integer: 整型
  • float: 双精度浮点型数字
  • array: 数组类型
  • object: 对象类型
  • enum: 枚举值
  • date: 日期格式
  • url: 网址格式
  • hex: 16进制数字
  • email: 电子邮箱格式
  • any: 任意类型
required

必填字段,即非空验证。如上面实例中的的非空验证,以及邮箱前边的必填符号*,就是这个参数的功劳。

pattern

正则表达式

{ type : "string" , required: true , pattern : /^[a-z]+$/ }
min/max

判断数据大小范围,通常对数字大小范围做校验。对于字符串和数组类型,将根据长度进行比较。

{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
Len

len属性与min、max属性同时出现了,len属性有更高的优先级。

长度验证,如11位手机号码

{type: "number", required: true, len: 11}
enum

枚举值验证

{type: "enum", enum: ['admin', 'user', 'guest']}
whitespace

验证是否只有空格(如果没有该配置,则全空格的输入值也是有效的)。

{
 type: "string",
 message: '只存在空格',
 whitespace:true,
 trigger: ['change', 'blur']
}
transform

有时有必要在验证之前转换值,以强制或以某种方式对其进行清理。为此 transform ,向验证规则添加一个功能。在验证之前,先转换属性,然后将其重新分配给源对象,以更改该属性的值。

{
type: 'string',
required: true,
pattern: /^[a-z]+$/,
transform(value) {
return value.trim();
},
}

//transform:Number
fields

深层规则

object

{
 type: "object", required: true,
 fields: {
   street: {type: "string", required: true},
   city: {type: "string", required: true},
   zip: {type: "string", required: true, len: 8, message: "invalid zip"}
 }
}

array

{
 type: "array", required: true, len: 3,
 fields: {
   0: {type: "string", required: true},
   1: {type: "string", required: true},
   2: {type: "string", required: true}
 }
}

defaultField

{
 type: 'array',
 required: true,
 defaultField: { type: 'url' },
}
message

支持 字符串、html、vue-i18n

validator

可以为指定字段自定义验证函数——这就相当于把前边配置的东西用js按照以前的方式编写验证逻辑了。虽然麻烦点,但是能实现比较复杂的业务逻辑判断。

rule, value, callback, source, options

参考link

link

link

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐