涉及的技术:mixin,directive
1、创建验证规则文件 validator.js
export default{
message: {
required: "这是必填字段",
email: "请输入有效的电子邮件地址",
url: "请输入有效的网址",
date: "请输入有效的日期",
dateISO: "请输入有效的日期 (YYYY-MM-DD)",
dateYM: "请输入正确的日期(YYYY-MM)",
datetime: "请输入正确的日期(YYYY-MM-DD HH:mm:ss)",
number: "请输入有效的数字",
digits: "只能输入整数",
maxlength: "最多可以输入 {0} 个字符",
minlength: "最少要输入 {0} 个字符",
rangelength: "请输入长度在 {0} 到 {1} 之间的字符串",
range: "请输入范围在 {0} 到 {1} 之间的数值",
percent: "请输入范围在 {0} 到 {1} 之间的数值",
max: "请输入不大于 {0} 的数值",
min: "请输入不小于 {0} 的数值",
decimal: "请精确到小数点后 {0} 位",
mindecimal: "请至少精确到小数点后 {0} 位",
maxdecimal: "请最多精确到小数点后 {0} 位",
rangedecimal: "请精确到小数点后 {0} 至 {1} 位",
IDCard: "请输入合法的身份证号码",
phone: "请输入合法的手机号码",
password: "密码必须符合以下要求:长度为8~16位,至少包含一个大写字母、一个小写字母、一个数字、以及一个特殊符号",
email2: "请输入有效的电子邮件地址",
number_0: "请输入非零的有效数字",
digits_0: "请输入非零的整数",
English_0: "请输入姓名拼音(小写)",
space: "不能输入空格"
},
methods: {
//必填
required: function (value, element, param) {
return value.length > 0;
},
//邮箱
email: function (value, element) {
if (value == null || this.trim(value) == "") return true;
return /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(value);
},
//网址
url: function (value, element) {
if (value == null || this.trim(value) == "") return true;
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value);
},
//日期
date: function (value, element) {
if (value == null || this.trim(value) == "") return true;
return !/Invalid|NaN/.test(new Date(value).toString());
},
//日期(ISO)
dateISO: function (value, element) {
if (value == null || this.trim(value) == "") return true;
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value);
},
dateYM: function (value, element) {
if (value == null || this.trim(value) == "") return true;
return /^\d{4}[\/\-]?(0[1-9]|1[012])$/.test(value);
},
datetime: function (value, element) {
if (value == null || this.trim(value) == "") return true;
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])\s([01][0-9]|2[0-4]):([0-5][0-9]):([0-5][0-9])$/.test(value);
},
//有效的数字
number: function (value, element) {
if (value == null || this.trim(value) == "") return true;
return /^-?\d+(?:\.\d+)?$/.test(value);
},
//数字
digits: function (value, element) {
if (value == null || this.trim(value) == "") return true;
return /^-?\d+$/.test(value);
},
//字符串至少n个字符
minlength: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
return value.length >= param[0];
},
//字符串最多n个字符
maxlength: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
return value.length <= param[0];
},
//字符串长度的范围
rangelength: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
return (value.length >= param[0] && value.length <= param[1]);
},
//数字大于n
min: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
return value >= param[0];
},
//数字小于n
max: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
return value <= param[0];
},
//数字范围n-m
range: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
return (value * 1 >= param[0] * 1 && value * 1 <= param[1] * 1);
},
//百分数
percent: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
if (value.substr(value.length - 1) === "%") {
value = value.substr(0, value.length - 1);
}
return (value >= param[0] && value <= param[1]);
},
//小数位数n位
decimal: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
var rex = new RegExp("^-?\\d+(.\\d{" + param[0] + "," + param[0] + "})$");
return rex.test(value);
},
//小数位数至少n位
mindecimal: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
var rex = new RegExp("^-?\\d+(.\\d{" + param[0] + ",})$");
return rex.test(value);
},
//小数位数最多n位
maxdecimal: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
var rex = new RegExp("^-?\\d+(.\\d{1," + param[0] + "})?$");
return rex.test(value);
},
//小数位数范围n-m位
rangedecimal: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
var rex = new RegExp("^-?\\d+(.\\d{" + param[0] + "," + param[1] + "})$");
return rex.test(value);
},
//身份证号码
IDCard: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
var rex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X)$)/;
return rex.test(value);
},
//手机号码
phone: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
var rex = /^1[345789]\d{9}$/;
return rex.test(value);
},
//手机号码(前面可能含有86)
phone86: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
var rex = /^(86)?1[345789]\d{9}$/;
return rex.test(value);
},
//密码
password: function (value, element, param) {
if (value == null || this.trim(value) == "") return true;
var rex = /^(?=.*\d+)(?=.*[a-z]+)(?=.*[A-Z]+)(?=.*[^A-Za-z0-9\s]+)\S{8,16}$/;
return rex.test(value);
},
//邮箱
email2: function (value, element) {
if (value == null || this.trim(value) == "") return true;
var rex = /^(([a-zA-Z0-9])*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}){1,2}$/;
return rex.test(value);
},
//有效的数字(非0)
number_0: function (value, element) {
if (value == null || this.trim(value) == "") return true;
if (this.trim(value) == "0") return false;
return /^-?\d+(?:\.\d+)?$/.test(value);
},
//数字(非0)
digits_0: function (value, element) {
if (value == null || this.trim(value) == "") return true;
if (this.trim(value) == "0") return false;
return /^-?\d+$/.test(value);
},
//英文字母(小写)
English_0: function (value, element) {
if (value == null) return true;
var rex = /^[a-z]{1,100}]?$/;
return rex.test(value);
},
//非空格
space: function (value, element) {
if (value == null) return true;
var rex = /^\S+$/;
return rex.test(value);
},
trim(value) {
return value.replace(/(^\s*)|(\s*$)/g, "");
}
}
}
2、创建存储验证信息的类 validateMsg.js
import {pluginInstance} from "./plugin";
class ErrorBag {
constructor(id) {
this.id = id;
this.errors = [];
this.fileds = [];
}
setErrors(name, msg,el) {
let errorIndex = this.errors.findIndex(x => x.name === name);
if (errorIndex > -1 && errorIndex < this.errors.length) {
this.errors[errorIndex].msg = msg;
} else {
let error = {
name: name,
msg: msg,
};
this.errors.push(error);
}
if (this.fileds.indexOf(name) < 0) this.fileds.push(name);
}
deleteErrors(name) {
let index = this.errors.findIndex(item => item.name === name);
if (index > -1 && index < this.errors.length) {
this.errors.splice(index, 1);
}
index = this.fileds.findIndex(item => item === name);
if (index > -1 && index < this.fileds.length) {
this.fileds.splice(index, 1);
}
}
clear() {
this.errors = [];
this.fileds = [];
}
get(name) {
let error = this.errors.filter(item => item.name === name);
return error && error.length?error[0].msg : null;
}
has(name) {
if(this.errors){
let error = this.errors.filter(item => item.name === name);
return error && error.length;
}
return false;
}
all(){
return this.errors;
}
hasAny(){
return this.errors.length
}
}
/*
* 验证信息
* */
export class validateMsg {
constructor(id) {
this.id=id;
this.errors = new ErrorBag(id);
this._validateRules = {};
this._validateMsgs = {};
this._validatePositions = {};
}
setErrors(name, msg,position) {
this.errors.setErrors(name, msg,position);
}
removeErrorMsg(name) {
this.errors.deleteErrors(name);
}
clearMsg(id) {
if(this.id===id){
this.errors.clear();
}
}
checkAll(checklist,options){
return pluginInstance.checkAll(checklist,options,this);
}
clear(){
this.errors.clear();
}
}
3、创建组件基础类(即mixin)mixin.js
const isBuiltInComponent = (vnode) => {
if (!vnode) {
return false;
}
const tag = vnode.componentOptions.tag;
return /^(keep-alive|transition|transition-group)$/.test(tag);
};
import {validateMsg} from './validateMsg.js'
export default {
beforeCreate() {
if (isBuiltInComponent(this.$vnode)) {
return;
}
if (!this.$validator) {
this.$validator = new validateMsg(this._uid);
}
if (this.$validator) {
const Vue = this.$options._base;
//定义errors为响应式属性
Vue.util.defineReactive(this.$validator, 'errors', this.$validator.errors);
}
if (!this.$options.computed) {
this.$options.computed = {};
}
this.$options.computed['errors'] = function errorBagGetter() {
return this.$validator.errors;
};
},
//清空所有验证信息
beforeDestroy() {
if(this.$validator){
let id=this._uid;
this.$validator.clearMsg(id);
}
}
}
4、创建对外调用接口
/**
* 验证规则
*/
import validator from './validator.js'
import mixin from './mixin.js'
const isObject = (obj) => obj !== null && obj && typeof obj === 'object' && !Array.isArray(obj);
const isString = (obj) => obj !== null && obj && typeof obj === 'string';
let Vue = null;
/*
* valedateUtil 实例
* */
export let pluginInstance;
class validateUtil {
static install(_vue, options) {
if (Vue && _vue === Vue) {
return;
}
Vue = _vue;
pluginInstance = new validateUtil();
Vue.mixin(mixin)
Vue.directive('validate', {
//指令第一次绑定到元素时调用
bind: function (el, binding, vnode, oldnode) {
pluginInstance.bindfunc(el, binding, vnode, oldnode)
},
})
}
static get instance() {
return pluginInstance;
}
bindfunc(el, binding, vnode, oldnode) {
let vm = vnode.context;
let validate = this.createVM(vm);
this.addValidateRules(el, binding, vnode, oldnode,validate);
let vaType = el.getAttribute("validate-type");
//v-validate:name.change='rules'
if (!vaType && binding.modifiers){
let types=Object.keys(binding.modifiers)
types&&types.length&&(vaType=types[0]);
}
switch (vaType) {
case "change":
this.change(el, binding, vnode, oldnode, validate);
break;
case "input":
this.oninput(el, binding, vnode, oldnode,validate);
break;
default:
// this.change(el, binding, vnode, oldnode, validate);
break;
}
}
//将验证规则放置 _validateRules
addValidateRules(el, binding, vnode, oldnode,validate) {
let name = el.getAttribute("validate-name");
let dataRules = el.getAttribute("data-rules");
//v-validate:name='rules'
if (!name && binding.arg) name = binding.arg;
if (!dataRules && binding.value) dataRules = binding.value;
let rules, ruleName, ruleMap ={},msgMap={};
if (isObject(dataRules)) {
ruleMap = dataRules.rules;
msgMap= dataRules.msg
} else if (isString(dataRules)) {
rules = dataRules.split(" ");
for (let item of rules) {
if(!item) continue;
let ruleArr = item.split("|");
let ruleParams = [];
for (let i = 0; i < ruleArr.length; i++) {
if (i === 0) {
ruleName = ruleArr[i];
}
else {
try {
ruleParams.push(ruleArr[i] * 1);
} catch (e) {
console.error(e);
}
}
}
ruleMap[ruleName]=ruleParams;
let itemtip=el.getAttribute(`validate-tips-${ruleName}`);
itemtip&&itemtip.length&&(msgMap[ruleName]=itemtip);
}
}
validate._validateRules[name] = ruleMap;
validate._validateMsgs[name] = msgMap;
//存储每个验证元素(貌似没啥用了)
validate._validatePositions[name]=el;
}
change(el, binding, vnode, oldnode, validate) {
el.addEventListener('change', () => {
this.check(el, binding, validate);
})
}
oninput(el, binding, vnode, oldnode,validate) {
el.addEventListener('input', () => {
this.check(el, binding, validate);
})
}
check(el, binding, validate) {
let name = el.getAttribute("validate-name");
if (!name && binding.arg) name = binding.arg;
let rules = validate._validateRules[name],msgMap=validate._validateMsgs[name];
let checkobj={
name:name,
rules:rules,
msg:msgMap,
value:el.value,
el:el,
}
this.checkItem(checkobj,validate);
}
checkItem(checkobj,validate){
let name=checkobj.name,rules=checkobj.rules,msgMap=checkobj.msg,value=checkobj.value,el=checkobj.el;
let ruleName, ruleParams,ruleResult = true;
!rules&&(rules=validate._validateRules[name]);
!msgMap&&(msgMap=validate._validateMsgs[name]);
!el&&(el=validate._validatePositions[name]);
for (let key of Object.keys(rules)) {
ruleName = key;
ruleParams = rules[key];
if (ruleName in validator.methods && ruleName in validator.message) {
let _result = validator.methods[ruleName](value, el, ruleParams);
ruleResult = _result;
if (!_result) {
let msg=validator.message[ruleName];
if(msgMap&&msgMap[ruleName]){
msg=msgMap[ruleName];
}
let errmsg = this.msgFormat(msg, ruleParams);
validate.setErrors(name, errmsg,el);
break;
}
}
}
//全部验证通过,清空错误信息
if (ruleResult) {
validate.removeErrorMsg(name);
}
return ruleResult;
}
msgFormat(msg, param) {
if (param !== undefined && param.constructor === Array) {
param.forEach(function (value, index) {
msg = msg.replace(new RegExp("\\{" + index + "\\}", "g"), function () {
return value;
});
});
}
return msg;
}
// 挂在vue实例上面$validate
createVM(vm) {
if (!vm.$validator) {
let validate = new validateMsg();
vm.$validator = validate;
}
return vm.$validator;
}
checkAll(ckecklist,options,validate){
let result=true;
if(!ckecklist||!ckecklist.length){
ckecklist=[];
for(let key of Object.keys(validate._validateRules)){
let checkItem={
name:key,
rules:validate._validateRules[key],
msg:validate._validateMsgs[key],
value:validate._validatePositions[key].value,
el:validate._validatePositions[key],
};
ckecklist.push(checkItem);
}
}
if(ckecklist&&ckecklist.length){
for(let checkitem of ckecklist){
//是否仅验证加了 v-validate 的元素
if(options&&options.strict){
if(!validate._validatePositions[checkitem.name])
continue;
}
let itemresult=this.checkItem(checkitem,validate);
result&&(result=itemresult);
}
}
return result;
}
}
export default validateUtil;
5、使用
5.1、引入组件
import validate from './src/plugin'
Vue.use(validate);
5.2、使用
{{errors.get('test4').msg}}
{{errors.get('test3').msg}}
{{errors.get('test2').msg}}
{{errors.get('test1').msg}}
接口说明
参数名 | 参数说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
v-validate | 验证自定义命令 | - | - | - |
data-rules | 验证规则 | String/object | 具体格式见:data-rules说明 | - |
validate-name | 验证组件名称 | String | 必填 | - |
validate-type | 验证类型 | String | change/input/空 | - |
简写说明:v-validate:[验证名称].[验证方式]=[验证规则]
参数名 | 参数说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
验证名称 | 同上述validate-name | - | - | - |
验证方式 | 同上述validate-type | - | - | - |
验证规则 | 同上述data-rules | String/object | 必填 | - |
data-rules说明:string/object
String
:不同的验证规则之间用空格分割,验证的条件值用| 分割,例如:required range|1|100
Object
:
{
//验证规则
rules: {
required: true,
rangelength: [6, 9]
},
//提示信息
msg: {
required: "请输入信息",
rangelength: "长度范围{0}到{1}"
}
}
errors 说明
方法名 | 参数说明 | 返回值 |
---|---|---|
errors.has('验证名称') | 该验证是否通过 | true/false |
errors.get('验证名称') | 获取验证错误信息 | String |
errors.hasAny() | 是否存在验证失败信息 | true/false |
errors.all() | 所有验证失败的信息 | 数组 |
vue的核心是数据驱动,所以该插件没有添加任何提示样式,可根据自身需求设置验证提示组件,添加消息提示组件也很方便的
github地址:https://github.com/zh-huan/validate