如果你受够了饿了么 ElementUI 原生的校验方式,那就来试试它吧! 项目地址

前言

饿了么 ElementUI 虽好,但表单校验的体验不够理想

如果说产品开发要讲究用户体验,那插件开发也要讲究开发体验,而好的开发体验,要靠好的 api 设计来保障

本人专注校验插件开发 30 年,有祖传的校验插件 api 设计(玩笑)。主要是参考了之前写的 vue-verify-pop 的 api,并加以完善,取其精华,去其糟粕,揉和日月之精华。。。

本插件只是对 ElementUI 原本的校验方式做了一层封装,核心的校验器仍然是 async-validator,非侵入式,完全不会影响你继续使用 ElementUI 的原生校验

注意:element 在 2.5.x 之后,对于使用了 v-model.numberel-input 将无法输入小数。
基于上面这个情况以及数字校验业务的综合考虑,自 1.2.0 后,本插件数字相关的校验不再要求强数字类型,也就是说 “数字字符串” 也可以通过校验了(你无须再使用 v-model.number)。
当然了,你还是可以根据业务需要继续使用 v-model.number,不过需要将 el-input 的 type 设置成 number 才能输入小数,坏处是如果输入了非 “数字字符串”,会在输入框有值的情况下触发校验插件的空提示。

安装

1
npm install element-ui-verify

使用

环境

vue版本:^2.3.0
element-ui版本:>=1.1.1
webpack模块环境

一,安装

1
2
3
4
5
6
7
import Vue from 'vue'
import elementUI from 'element-ui'
import elementUIVerify from 'element-ui-verify'

// 这里注意必须得先use elementUI
Vue.use(elementUI)
Vue.use(elementUIVerify)

二,在 el-form-item 上配置校验规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<el-form label-width="100px" :model="model">
<el-form-item label="年龄" prop="age" verify int :gt="0">
<el-input v-model="model.age"></el-input>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
model: {
age: '',
},
}
},
}
</script>

ok,你已经完成了一个内容为大于 0 的整数校验!(欢迎对比官方版的相似例子)

默认支持的校验规则

  • length: 校验文本长度
  • minLength: 校验文本最短长度
  • gt: 校验数字要大于某数
  • gte: 校验数字要大于等于某数
  • lt: 校验数字要小于某数
  • lte: 校验数字要小于等于某数
  • maxDecimalLength: 校验数字最大小数位
  • number: 校验是否为数字
  • int: 校验是否为整数
  • phone: 校验是否为手机号(随着号段的增加,未来可能需要更新)
  • email: 校验是否为电子邮件地址
  • verifyCode: 校验是否为 6 位数字验证码

配置

示例:

1
2
3
4
Vue.use(elementUIVerify, {
errorMessageTemplate: yourErrorMessageTemplate,
fieldChange: 'clear',
})

errorMessageTemplate

错误提示模板。默认值:默认模板

如要使用自定义模板,模板内容要覆盖 默认模板 中定义的所有字段,否则 getErrorMessage 在获取不到值的时候会抛异常

fieldChange

当绑定字段变化时,插件的默认行为。默认值:’verify’

注意:在输入框失去焦点时会始终触发校验

verify

当绑定字段变化时会实时触发校验

clear

当绑定字段变化时只清空校验结果,不触发校验

重要选项说明

verify

若要使用本插件,verify 选项是必须的,换句话说,如果没有配置该选项,那么你仍然可以正常使用 ElementUI 原生的校验

该选项还可以接收一个函数值,用于 自定义校验方法

canBeEmpty

插件默认开启输入内容不为空校验,如果开启该选项,一旦该输入项为空则会忽略该输入项之后所有的校验

该选项一般用于如下情况,比如邀请码这种一般可以为空,不为空又需要校验的输入项

1
2
3
4
5
6
7
8
<!--当邀请码不为空时才校验长度是否等于6-->
<el-form-item
prop="invitationCode"
verify
can-be-empty
:length="6"
error-message="邀请码不正确"
></el-form-item>

space

插件执行空检测时默认忽略空格,也就是说某个输入框中如果只输入了空格是过不了空检测的,除非设置该选项

1
<el-form-item prop="test" verify space></el-form-item>

emptyMessage

空检测错误提示

1
<el-form-item prop="head" verify empty-message="请上传头像"></el-form-item>

errorMessage

用于自定义校验不通过提示(空检测和自定义校验方法的错误提示不受该值影响)

1
2
3
4
5
6
<el-form-item
prop="numberProp"
verify
number
error-message="请输入正确的数字"
></el-form-item>

alias

懒人的福音,用于复用错误提示,默认值:”该输入项”。使用场景:

假设你的空检测错误提示模板为:

1
{empty: '{alias}不能为空'}

表单内容为:

1
2
3
<el-form-item prop="unknown" verify></el-form-item>
<el-form-item alias="姓名" prop="name" verify></el-form-item>
<el-form-item label="地址" prop="address" verify></el-form-item>
  • unknown输入框为空时,会提示”该输入项不能为空”(alias 值默认为”该输入项”)
  • 姓名输入框为空时,会提示”姓名不能为空”(显式设置了 alias 值时,提示内容自然会以该值去替换模板)
  • 地址输入框为空时,会提示”地址不能为空”(大部分 el-form-item 都需要设置一个 label 项,而 label 项往往就代表该输入项的 alias,因此插件会取该值直接作为 alias,很贴心有木有)

fieldChange

参见全局 fieldChange 配置

watch

监听其他变量,触发自身校验

一个常见例子:密码一致性校验,pass1 的变化会触发 pass2 的校验(欢迎对比官方版的相似例子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<el-form :model="model">
<el-form-item label="密码" prop="pass1" verify>
<el-input v-model="model.pass1"></el-input>
</el-form-item>
<el-form-item
label="确认密码"
prop="pass2"
:verify="verifyPassword"
:watch="model.pass1"
>
<el-input v-model="model.pass2"></el-input>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
model: {
pass1: '',
pass2: '',
},
}
},
methods: {
verifyPassword(rule, val, callback) {
if (val !== this.model.pass1) callback(Error('两次输入密码不一致!'))
else callback()
},
},
}
</script>

再举一个不适合使用该选项的场景,比如下面这个最少最多人数的例子,最少人数变化要触发最多人数的校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<el-form :model="model">
<el-form-item label="最少参与人数" prop="minNumber" verify int :gt="0">
<el-input v-model="model.minNumber"></el-input>
</el-form-item>
<el-form-item
label="最多参与人数"
prop="maxNumber"
verify
int
:gt="model.minNumber"
:watch="minNumber"
>
<el-input v-model="model.maxNumber"></el-input>
</el-form-item>
</el-form>
</template>

其实 watch 选项在这里完全没必要,因为插件会响应所有校验参数的变化来触发自身校验,最多参与人数的gt值引用了model.minNumber,一旦model.minNumber变化,就会触发校验

如果你再 watch 一下,反而会多触发一次。所以这样即可:

1
2
3
4
5
6
7
<el-form-item
label="最多参与人数"
prop="maxNumber"
verify
int
:gt="model.minNumber"
></el-form-item>

注意事项

  • 所有选项调用不能有大写字母,用中划线分隔,同 vue props 属性设置规则
  • length minLength gt gte lt lte maxDecimalLength 等需要接收数值的选项,该值须为数字(:length="1")
  • verify canBeEmpty space number int 等无须接收值的选项一旦设置了,可以通过赋值为 undefined 来取消

规则简写

number,int,phone 等无须设定值的选项可以直接:

1
2
3
<el-form-item prop="prop" verify number></el-form-item>
<!--不用这么写-->
<el-form-item prop="prop" verify :number="true"></el-form-item>

gt gte lt lte maxDecimalLength 等数字规则无须再写 number 规则

1
2
3
4
<!--该输入项内容必须为不大于10的数字-->
<el-form-item prop="prop" verify :lte="10"></el-form-item>
<!--不用这么写-->
<el-form-item prop="prop" verify number :lte="10"></el-form-item>

全局 API

addRule (name: string | VerifyRulePropOptions, getter: RuleGetter): RuleGetter

用于 自定义校验规则

name: 规则名称

本插件的校验选项基于 Vue 组件的 Prop,故该参数可以是一个类似 VueProp 的配置项:

1
2
3
4
5
6
7
{
name: 'length',
type: Number,
validator (v) {
return v > 0
}
}

这么做的好处是可以对规则接收值本身也做一些限制,提前规避一些不必要的调用错误,比如:

1
<el-form-item prop="prop" verify length="哈哈哈"></el-form-item>

上述这种情况,就很有可能使你的校验文本长度规则出现异常。当然了,如果你对调用者比较相信或者是那种无须接收值的规则,也可以省事点直接这样:

1
2
import elementUIVerify from 'element-ui-verify'
elementUIVerify.addRule('length', (length) => SomeRule)

getter: asyncValidatorRule 获取方法

该回调可以返回单条规则或规则数组,具体规则需要参见 async-validator

1
2
3
4
5
6
7
// 单条
() => ({ type: 'number', message: '请输入数字' })
// 多条
(gte) => [
{ type: 'number', message: '请输入数字' },
{ type: 'number', min: gte, message: `该数字不能小于${gte}` },
]

如果对上述示例中多条规则重复定义了type有疑问的同学,可以看 这里

getRule (name: string): RuleGetter

用于获取校验规则。一般是和 自定义校验规则 搭配使用,方便已有规则的复用

返回内容即为 addRule 传入的getter参数

getErrorMessage (name: string, templateData?: any): string

用于从通用错误提示模板中获取错误提示。一般是和 自定义校验规则 搭配使用

假设错误提示模板为

1
2
3
4
5
6
7
8
{
// 没有Macro
empty: '该输入项内容不能为空',
// Macro正好等于RuleName
length: '该输入项内容长度需要等于{length}',
// Macro不等于RuleName
maxDecimalLength: '该输入项最多接受{MDL}位小数'
}

调用示例:

1
2
3
4
5
6
7
import elementUIVerify from 'element-ui-verify'
// 返回:"该输入项内容不能为空"
elementUIVerify.getErrorMessage('empty')
// 返回:"该输入项内容长度需要等于10"
elementUIVerify.getErrorMessage('length', 10)
// 返回:"该输入项最多接受2位小数"
elementUIVerify.getErrorMessage('maxDecimalLength', { MDL: 2 })

自定义校验方法

如果自带的校验规则满足不了你的需求,可以在校验规则中插入你自己的 校验方法

自定义校验方法在校验规则都通过后才会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
<el-form :model="model">
<el-form-item
label="卡号"
prop="cardNumber"
:verify="verifyCardNumber"
:length="6"
>
<el-input v-model="model.cardNumber"></el-input>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
model: {
cardNumber: '',
},
}
},
methods: {
// callback形式
// 校验是否是010,011开头的卡号
verifyCardNumber(rule, val, callback) {
// rule: 这个参数很恶心,不经常用到还要放在第一位,不过为了保持async-validator的风格,还是留着它了
// val: 待校验值
// callback: 校验结果回调 具体可以点击上文的"校验方法"链接查看
if (!['010', '011'].includes(val.substr(0, 3)))
callback(Error('错误的卡号'))
else callback()
},
},
}
</script>

自定义校验规则

和自定义校验方法的区别是这个适用于全局,等于增加插件自带的校验规则

前言已经说过,本插件的核心校验器来自 async-validator,故校验规则需要你先参考它的 文档

示例 1:新增一个校验是否为 10 位整数的规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import elementUIVerify from 'element-ui-verify'
// 通过getRule方法复用已有的int规则
const intRuleGetter = elementUIVerify.getRule('int')
elementUIVerify.addRule('int10', () => [
// 首先校验是否为整数
intRuleGetter(),
// 再校验是否为10位
{
validator(rule, val, callback) {
if ((val + '').length !== 10) {
// 尽量将出错提示定义在错误模板中(假设为{int10: '该输入项为10位整数'})
const errorMessage = elementUIVerify.getErrorMessage('int10')
callback(Error(errorMessage))
} else callback()
},
},
])

这样就完成了一个简单的规则拓展,你就可以在任何地方像使用默认规则那样来调用你的自定义规则,如下:

1
<el-form-item prop="prop" verify int10></el-form-item>

示例 2:新增一个校验是否为任意位整数的规则,与上面不同的是该规则需要接收一个规则参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import elementUIVerify from 'element-ui-verify'
const intRuleGetter = elementUIVerify.getRule('int')
// 这里最好不要再直接传入'intLength'了,而是一个类似VueProp的配置项,来对规则参数稍作限制
elementUIVerify.addRule({ name: 'intLength', type: Number }, (intLength) => [
intRuleGetter(),
// 校验整数长度是否符合规则
{
validator(rule, val, callback) {
if ((val + '').length !== intLength) {
// 假设出错模板为{intLength: '该输入项为{intLength}位整数'}
const errorMessage = elementUIVerify.getErrorMessage(
'intLength',
intLength,
)
callback(Error(errorMessage))
} else callback()
},
},
])

调用:

1
<el-form-item prop="prop" verify :int-length="10"></el-form-item>

看到这里,相信类似拓展一个支持正则校验的规则这种对你来说肯定不在话下了。更多示例你还可以直接翻看本插件 源码 中默认规则的添加

插件的默认校验不通过提示模版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
empty: '请补充该项内容',
length: '请输入{length}位字符',
minLength: '输入内容至少{minLength}位',
number: '请输入数字',
int: '请输入整数',
lt: '输入数字应小于{lt}',
lte: '输入数字不能大于{lte}',
gt: '输入数字应大于{gt}',
gte: '输入数字不能小于{gte}',
maxDecimalLength: '该输入项最多接受{maxDecimalLength}位小数',
phone: '请输入正确的手机号',
email: '请输入正确的邮箱',
verifyCode: '请输入正确的验证码'
}

常见问题

为什么不把 prop 配置项干掉?每次都要写校验规则都要写它好烦!

我也烦。但本插件是基于 ElementUI 的,所以很多地方会受到原始校验机制的限制,还要尽可能不对它产生影响。比如这个 prop 选项,如果把它干掉,会影响到 el-form 的校验,因为 ElementUI 以 prop 作为 uid 来存储校验队列

如何切换校验类型?比如某个输入框可能输入手机号也可能输入邮箱

1
2
<el-form-item prop="prop" verify phone v-if="isPhone"></el-form-item>
<el-form-item prop="prop" verify email v-else></el-form-item>

在规则变化不多的情况下也可以这样(该种方式切换类型时会立即触发校验)

1
2
3
4
5
6
<el-form-item
prop="prop"
verify
:phone="isPhone ? true : undefined"
:email="isPhone ? undefined : true"
></el-form-item>

其他

在写gte规则的时候,我首先引用了 async-validator 的数字类型规则,然后再引用了它的 Range 规则
而它的 Range 规则同时支持字符串和数字,所以针对数字的校验需要显式再设置一遍type: number,这虽然会导致gte规则可能会校验两遍类型是否为数字,不过性能影响不大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 这里的min显然是要校验数字的
[
{type: 'number'},
{min: 3}
]
// 然而你必须得写成
[
{type: 'number'},
{type: 'number', min: 3}
]
// 当然了你也可以通过写自定义校验函数来规避此问题,不过此问题没这个必要
[
{type: 'number'},
{
validator (rule, val, callback) {
if (val < 3) callback(Error('不能小于3'))
}
}
]

也不能算是 async-validator 的缺点吧,基于它当前的规则约定如果做规则推断的话收益不大,还会增加校验规则的不确定性,给调用者造成困惑。不过只针对本需求的话可以考虑把文本和数字的 range 规则给分开

这里可能你会问那为什么不直接用单条规则?

1
{type: 'number', min: 3, errorMessage: 'xxxx'}

如果这么写了,输入非数字的错误提示会是原始错误提示模板中的内容。为了不破坏 ElementUI 的原生校验(修改原始错误提示模板)和最大化复用 async-validator 内置的校验,只得通过拆分规则来让校验错误提示更具体

之后你在自定义校验规则的时候,如遇类似问题也可考虑通过规则拆分来让错误提示更具体

后话

由于本插件的校验选项是基于 VueProp,有如下缺点:

  • 随着 ElementUI 的更新,未来有可能会和它的某些新增选项产生冲突
  • 难以做到按照校验规则的书写顺序来执行校验,目前的大顺序是:空检测 > 通用规则 > 自定义校验方法。不过该条的影响不是很大,现有规则下的大部分场景不需要考虑规则顺序

 评论