需求
之前的表单代码看了下,写的比较冗长,于是去万能的Github找点轮子,发现了这个:
GitHub - aweiu/element-ui-verify: 如果你受够了饿了么ElementUI原生的校验方式,那就来试试它吧!一款更懂你的校验插件
但是用起来发现不支持最新的vue,因为里面基于mixin混入。改了一会没耐性了,这么简单个功能,自己卷个轮子算了,索性自己改,但是要有以下几点支持:
1)支持label提示,例如必输的信息,如果前面label为用户的话,输入框提示为“用户昵称不为空”,如果是最小长度检测的话,需要提示“用户昵称最少4字符”。而用户昵称的校验器我只需要设置“用户昵称”这个变量,提示信息自动拼接
2)支持验证器组合,例如同时非空,且为数值格式。
3)支持自定义提示信息,不要自己构造方法,最好都参数化解决
思路
既然都Typescript了,我们就采用对象的方式来解决问题。问题1的方式,只需要在构造器里添加label信息即可。
问题2的方式,写过后端的都知道,有个很舒服的方式叫做 链式调用,OK,把多行代码压缩为一行个的方式就它了。
问题3,给个可选参数就好,这个是es5的好工具,结合typescript的默认值操作很方便的解决
问题4,有些代码直接照搬了github,其它未测试的部分理论上应该没问题,有BUG再回来改😉
效果
以修改密码的业务为例,这样个界面
改造前
原来代码:
/** 修改密码窗体模块 */
<template>
<cc-window
ref="windowDom"
v-bind="$attrs"
title="修改当前用户密码"
width="400px"
:append-to-body="true"
@submit="onSubmit"
>
<el-form
ref="editFormDom"
:model="form"
auto-complete="off"
:label-width="100"
class="h-rmargin40"
:rules="formRules"
>
<el-form-item prop="userPsw" label="输入密码">
<el-input type="password" v-model="form.userPsw" maxlength="20" show-password />
</el-form-item>
<el-form-item class="x-fillitem" prop="reUserPsw" label="确认密码">
<el-input type="password" v-model="form.reUserPsw" maxlength="20" show-password />
</el-form-item>
</el-form>
</cc-window>
</template>
校验部分
const formRules = reactive<{
userPsw: any[]
reUserPsw: any[]
}>({
userPsw: [
{ required: true, message: '请输入密码', trigger: ['change', 'blur'] },
{
pattern: /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]+\S{5,20}$/,
message: '需同时含有字母和数字,长度在6-20之间',
trigger: ['change', 'blur']
}
],
reUserPsw: [
{ required: true, message: '请重新输入密码', trigger: ['change', 'blur'] },
{
validator: (rule: any, value: any, callback: object) => {
return value === form.userPsw
},
message: '两次输入的密码不相等',
trigger: ['change', 'blur']
}
]
})
改造后
/** 修改密码窗体模块 */
<template>
<cc-window
ref="windowDom"
v-bind="$attrs"
title="修改当前用户密码"
width="400px"
:append-to-body="true"
@submit="onSubmit"
>
<el-form ref="editFormDom" :model="form" auto-complete="off" :label-width="100" class="h-rmargin40">
<el-form-item
prop="userPsw"
label="输入密码"
:rules="
utils
.validate('密码')
.required()
.pattern(/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]+\S{5,20}$/, '需同时含有字母和数字,长度在6-20之间')
"
>
<el-input type="password" v-model="form.userPsw" maxlength="20" show-password />
</el-form-item>
<el-form-item
class="x-fillitem"
prop="reUserPsw"
label="确认密码"
:rules="utils.validate('密码').compare(form.userPsw)"
>
<el-input type="password" v-model="form.reUserPsw" maxlength="20" show-password />
</el-form-item>
</el-form>
</cc-window>
</template>
可见简短了很多,方便使用还避免了出错
代码实现
代码实现也不复杂,写一个模块即可解决:
/**
* 链式校验模块,支持常用校验方式
*
* 使用列:
* import {validate} from '@/commons/utils/Validator'
*
* //建立一个非空4字符以上的昵称校验,未输入提示“请输入昵称”,过短提示“昵称长度最小为 4 字符”
* validate('昵称').required().minLength(4)
*
* 注意除了非空校验外,其它校验都是为空可以通过的,除非显示的指定了非空校验required
*
* @author Jim 24/05/09
*/
export interface IValidator extends Array<any> {
required: (error?: string) => IValidator // 必须输入
gt: (value: number, error?: string) => IValidator // 必须大于
gte: (value: number, error?: string) => IValidator // 必须大于等于
lt: (value: number, error?: string) => IValidator // 必须小于于
lte: (value: number, error?: string) => IValidator // 必须小于等于
minLength: (size: number, error?: string) => IValidator // 不能小于X字符
maxLength: (size: number, error?: string) => IValidator // 不能超过X字符(用于textArea校验)
precision: (digitSize: number, error?: string) => IValidator // 校验数字最大小数位(精度限制)
number: () => IValidator // 校验是否为数字
int: () => IValidator // 校验是否为整数
phone: () => IValidator // 校验是否为手机号(随着号段的增加,未来可能需要更新)
email: () => IValidator // 校验是否为电子邮件地址
verifyCode: () => IValidator // 校验6位数字验证码
pattern: (regex: RegExp, error?: string) => IValidator // 校验正则格式
compare: (otherVal: string, error?: string) => Validator // 校验两次输入一致
}
export class Validator extends Array implements IValidator {
private labelText?: string = undefined
constructor(label?: string) {
super(0)
this.labelText = label
return this
}
required(error?: string): Validator {
this.push({
required: true,
message: error ?? (this.labelText ? `请输入${this.labelText}` : '输入不能为空'),
trigger: ['change', 'blur']
})
return this
}
gt(value: number, error?: string): Validator {
this.push({
validator: (rule: any, val: any, callback: (error?: Error) => void) => {
return !val || val > value
},
message: error ?? `${this.labelText ?? '输入数值'}应大于 ${value}`,
trigger: ['change', 'blur']
})
return this
}
gte(value: number, error?: string): Validator {
this.push({
type: 'number',
min: value,
transform: (v: string) => Number(v),
message: error ?? `${this.labelText ?? '输入数值'}不能小于 ${value}`,
trigger: ['change', 'blur']
})
return this
}
lt(value: number, error?: string): Validator {
this.push({
validator: (rule: any, val: any, callback: (error?: Error) => void) => {
return !val || val < value
},
message: error ?? `${this.labelText ?? '输入数值'}应小于 ${value}`,
trigger: ['change', 'blur']
})
return this
}
lte(value: number, error?: string): Validator {
this.push({
type: 'number',
max: value,
transform: (v: string) => Number(v),
message: error ?? `${this.labelText ?? '输入数值'}不能大于 ${value}`,
trigger: ['change', 'blur']
})
return this
}
minLength(size: number, error?: string): Validator {
this.push({
validator: (rule: any, val: any, callback: (error?: Error) => void) => {
return (val?.length || 0) >= size
},
message: error ?? `${this.labelText ?? '内容'}长度最小为 ${size} 字符`,
trigger: ['change', 'blur']
})
return this
}
maxLength(size: number, error?: string): Validator {
this.push({
validator: (rule: any, val: any, callback: (error?: Error) => void) => {
return (val?.length || 0) <= size
},
message: error ?? `${this.labelText ?? '内容'}长度最多为 ${size} 字符`,
trigger: ['change', 'blur']
})
return this
}
precision(digitSize: number, error?: string): Validator {
this.push({
validator: (rule: any, val: any, callback: (error?: Error) => void) => {
const decimal = val.toString().split('.')[1]
return !!decimal && decimal.length < digitSize
},
message: error ?? `${this.labelText ?? ''}小数部分不能超过 ${digitSize} 位`,
trigger: ['change', 'blur']
})
return this
}
number(): Validator {
return this.pattern(/^([-+])?\d+(\.\d+)?$/, `${this.labelText ?? ''}请输入数字`)
}
int(): Validator {
this.push({
type: 'integer',
transform: (v: string) => Number(v),
message: '请输入整数',
trigger: ['change', 'blur']
})
return this
}
phone(): Validator {
return this.pattern(/^1\d{10}$/, '请输入正确手机号码')
}
email(): Validator {
this.push({
type: 'email',
message: '邮箱格式不正确',
trigger: ['change', 'blur']
})
return this
}
verifyCode(): Validator {
return this.pattern(/^([-+])?\d+(\.\d+)?$/, '请输入验证码')
}
compare(otherVal: string, error?: string): Validator {
this.push({
validator: (rule: any, val: any, callback: (error?: Error) => void) => {
return !val || val === otherVal
},
message: error ?? `${this.labelText ?? ''}两次输入不一致`,
trigger: ['change', 'blur']
})
return this
}
pattern(regex: RegExp, error?: string) {
this.push({
validator: (rule: any, val: any, callback: (error?: Error) => void) => {
return !val || regex.test(val)
},
message: error ?? '请输入正确格式',
trigger: ['change', 'blur']
})
return this
}
}
export const validate: (label?: string) => IValidator = (label) => {
return new Validator(label)
}
不到200行搞掂
小遗憾
与element-ui-verify相比,唯一有点小遗憾的这种方式还是基于配置,无法自动获取到label的信息了,不然定义完label后,还得重复写一次,精简的不够极致。而且也无法响应校验的配置变化,算一点小遗憾了,以后有空再继续改造
--------------------- [ 2024/06/05 新增 ] ---------------------
今天改造个vue2项目,也需要校验,偷个懒,让GPT翻译成了javascript,能用
/* eslint-disable no-unused-vars */
/**
* 链式校验模块,支持常用校验方式
*
* 使用列:
* import Validator from '@/commons/utils/validator'
*
* //建立一个非空4字符以上的昵称校验,未输入提示“请输入昵称”,过短提示“昵称长度最小为 4 字符”
* new Validator('昵称').required().minLength(4)
*
* 注意除了非空校验外,其它校验都是为空可以通过的,除非显示的指定了非空校验required
*
* @author Jim 24/06/05
*/
export default class Validator extends Array {
constructor(label) {
super();
this.labelText = label;
return this;
}
required(error) {
this.push({
required: true,
message: error ?? (this.labelText ? `请输入${this.labelText}` : '输入不能为空'),
trigger: ['change', 'blur']
});
return this;
}
gt(value, error) {
this.push({
validator: (rule, val, callback) => {
return !val || val > value;
},
message: error ?? `${this.labelText ?? '输入数值'}应大于 ${value}`,
trigger: ['change', 'blur']
});
return this;
}
gte(value, error) {
this.push({
type: 'number',
min: value,
transform: (v) => Number(v),
message: error ?? `${this.labelText ?? '输入数值'}不能小于 ${value}`,
trigger: ['change', 'blur']
});
return this;
}
lt(value, error) {
this.push({
validator: (rule, val, callback) => {
return !val || val < value;
},
message: error ?? `${this.labelText ?? '输入数值'}应小于 ${value}`,
trigger: ['change', 'blur']
});
return this;
}
lte(value, error) {
this.push({
type: 'number',
max: value,
transform: (v) => Number(v),
message: error ?? `${this.labelText ?? '输入数值'}不能大于 ${value}`,
trigger: ['change', 'blur']
});
return this;
}
minLength(size, error) {
this.push({
validator: (rule, val, callback) => {
return (val?.length || 0) >= size;
},
message: error ?? `${this.labelText ?? '内容'}长度最小为 ${size} 字符`,
trigger: ['change', 'blur']
});
return this;
}
maxLength(size, error) {
this.push({
validator: (rule, val, callback) => {
return (val?.length || 0) <= size;
},
message: error ?? `${this.labelText ?? '内容'}长度最多为 ${size} 字符`,
trigger: ['change', 'blur']
});
return this;
}
precision(digitSize, error) {
this.push({
validator: (rule, val, callback) => {
const decimal = val.toString().split('.')[1];
return !!decimal && decimal.length < digitSize;
},
message: error ?? `${this.labelText ?? ''}小数部分不能超过 ${digitSize} 位`,
trigger: ['change', 'blur']
});
return this;
}
number() {
return this.pattern(/^([-+])?\d+(\.\d+)?$/, `${this.labelText ?? ''}请输入数字`);
}
int() {
this.push({
type: 'integer',
transform: (v) => Number(v),
message: '请输入整数',
trigger: ['change', 'blur']
});
return this;
}
phone() {
return this.pattern(/^1\d{10}$/, '请输入正确手机号码');
}
email() {
this.push({
type: 'email',
message: '邮箱格式不正确',
trigger: ['change', 'blur']
});
return this;
}
verifyCode() {
return this.pattern(/^([-+])?\d+(\.\d+)?$/, '请输入验证码');
}
compare(otherVal, error) {
this.push({
validator: (rule, val, callback) => {
return !val || val === otherVal;
},
message: error ?? `${this.labelText ?? ''}两次输入不一致`,
trigger: ['change', 'blur']
});
return this;
}
pattern(regex, error) {
this.push({
validator: (rule, val, callback) => {
return !val || regex.test(val);
},
message: error ?? '请输入正确格式',
trigger: ['change', 'blur']
});
return this;
}
}
const validate = (label) => {
return new Validator(label);
}
export { validate };
关键代码:
<el-form-item
label="批次名称"
prop="batchName"
:rules="new Validator('批次名称').required()"
>
<el-input
v-model="form.batchName"
placeholder="方便管理的名称,领用人/领用日期等"
style="width: 300px;"
maxlength="32"
/>
</el-form-item>
....
import Validator from '@/utils/validator'
export default {
data() {
return {
Validator
}
},
....