文章目录
form
rules 绑定
-
绑定到
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中的值关联校验。
-
绑定到
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中的值关联校验。
对象校验
-
一行
缺点:校验不通过时,所有输入框变红。
<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>
-
每个输入框单独校验
<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>
-
一行,为每个输入框进行校验,但是校验文本只显示一个
数组校验
-
普通
<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>
-
多行
<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>
用slot只展示第一行label
<template v-if="!index" v-slot:label>Domain</template>
-
数组中对象
<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' } ] } } } }
-
demo1
需求:
实现向对象中加key、value的操作。
维护一个数组
arr:[{key:'',value:''}]
对数组中的key进行单独校验 使其不能重复。思路:
-
维护一个keyList,当input-key的输入框focus时,我们拿到除当前输入框之外的其他key。用于当前input的输入框校验。
-
提交时,keyList要维护所有重复的key,用于每个input判断。
-
删除时,也要⚠️,当在input focus 后删除,
如果遍历时绑定的
:key='index'
那么删除时,由于索引位置的变化,其实导致了删除时,其位置后面的数据都变化了。由于keyList绑定的还是之前focus时的keyList,由于数据变化,再次走校验,导致校验不正常。
-
blur时清空keyList
-
删除时重新校验,这里需要注意需要nextTick
deleteLabel(index) { this.nodeLabelForm.labelList.splice(index, 1) // 校验时 keyList要存重复的key this.$nextTick(() => { // 不使用nextTick,会导致报错 this.keyList = this.getRepeatElement() this.$refs.form.validate() }) },
报错原因:因为数据变化时,页面还没有发生变化,导致这一项还是需要校验的,但是数据已经没有了。所以需要
$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 中的关键字说明
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