公用的表单验证类

最近闲来无事,开发了个公用的表单验证类。

1、代码:

1)正则集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const regexps = {
    ruleRegex: /^(.+?)\[(.+)\]$/,
    numericRegex: /^[0-9]+$/,//数字
    integerRegex: /^[0-9]+$/,//数字
    decimalTwoRegex: /^[0-9]*\.[0-9]{2}$/,//两位小数
    decimalRegex: /^[0-9]*\.[0-9]+$/,//小数 不限制位数
    integerDecimalRegex: /^[0-9][0-9]*([.][0-9]{1,2})?$/,//正整数或1、2位小数
    emailRegex: /^[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])?)*$/,
    alphaRegex: /^[a-z]+$/i,//字母
    alphaNumericRegex: /^[a-z0-9]+$/i,//字母或数字
    alphaDashRegex: /^[a-z0-9_\-]+$/i,//字母、数字、下划线和破折号
    naturalNoZeroRegex: /^[1-9][0-9]*$/i,//自然数
    ipRegex: /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/,//IP地址
    base64Regex: /[^a-zA-Z0-9\/=]/i,//base64
    urlRegex: /^((http|https):\/\/(\w+:{0,1}\w*@)?(\S+)|)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,//url地址
    dateRegex: /\d{4}-\d{1,2}-\d{1,2}/,//日期或时间
    integer: /^[0-9]\d*$/,//包含0的正整数
    mobileRegex: /^1[0-9]\d{9}$/,//手机号码
    chineseRegex: /^[\u0391-\uFFE5]+$/,//汉字
    mail: /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/,//邮箱
    idcardRegex: [/^\d{17}(\d|x|X)$/, /^\d{15}$/],//身份证号
};
export default regexps;

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
const errorMessage = {
    required: '%s为必填项',
    matches: '%s与%s不一致',
    valid_mobile: '必须填写有效的%s',
    valid_email: '必须填写有效的%s',
    valid_emails: '填写的所有%s必须是有效的',
    min_length: '%s的最小长度必须是%s个字符',
    max_length: '%s的最大长度必须是%s个字符',
    exact_length: '%s的长度必须是%s个字符',
    greater_than: '%s必须大于%s',
    less_than: '%s必须小于%s',
    alpha: '%s必须全部为字母',
    alpha_numeric: '%s必须为字母和数字',
    alpha_dash: '%s只能填写字母、数字、下划线和破折号',
    numeric: '%s必须填写数字',
    integer: '%s必须填写整数',
    decimal: '%s必须填写小数',
    decimalTwoPoint: '%s必须填写两位小数',
    is_natural_no_zero: '%s只能填写大于0的数字',
    valid_ip: '%s必须是有效的IP',
    valid_base64: '%s必须是base64的字符串',
    valid_url: '%s只能是url地址',
    greater_than_date: '%s必须晚于%s的日期',
    less_than_date: '%s必须早于%s的日期',
    greater_than_or_equal_date: '%s不得早于%s',
    less_than_or_equal_date: '%s不得晚于%s'
};
export default errorMessage;

3)校验方法集合:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import moment from 'moment/moment';//格式化日期的方法
import regexps from './regexps';

function _getValidDate(date) {
    if (!date.match('today') && !date.match(regexps.dateRegex)) {
        return false;
    }
    if (!date.match('today')) {
        return moment(date);
    }
};

const regexpUtil = {
    required: val => { return val !== null && val !== '' },

    //判断某个数据跟参照值是否相等 应用场景 二次确认密码
    matches: (val, validateVal)=> {
        if (!validateVal) return true;
        return val === validateVal;
    },

    valid_mobile: val => {
        return regexps.mobileRegex.test(val);
    },

    valid_email: val => {
        return regexps.emailRegex.test(val);
    },

    valid_emails: val => {
        let result = val.split(/\s*,\s*/g);
        for (var i = 0, resultLength = result.length; i < resultLength; i++) {
            if (!regexps.emailRegex.test(result[i])) {
                return false;
            }
        }
        return true;
    },

    valid_integerDecimal: val => {
        return regexps.integerDecimalRegex.test(val);
    },

    min_length: (val, length) => {
        return (val.length >= parseInt(length, 10));
    },

    max_length: (val, length) => {
        return (val.length <= parseInt(length, 10));
    },

    exact_length: (val, length) => {
        return (val.length === parseInt(length, 10));
    },

    greater_than: (val, param) => {
        if (!Number(val) || !Number(param)) {
            return false;
        }
        return (Number(val) > Number(param));
    },

    less_than: (val, param) => {
        if (!Number(val) || !Number(param)) {
            return false;
        }
        return (Number(val) < Number(param));
    },

    alpha: val => {
        return (regexps.alphaRegex.test(val));
    },

    alpha_numeric: val => {
        return (regexps.alphaNumericRegex.test(val));
    },

    alpha_dash: val => {
        return (regexps.alphaDashRegex.test(val));
    },

    numeric: val => {
        return (regexps.numericRegex.test(val));
    },

    integer: val => {
        return (regexps.integerRegex.test(val));
    },

    decimal: val => {
        return (regexps.decimalRegex.test(val));
    },

    decimalTwoPoint: val=> {
        return (regexps.decimalTwoRegex.test(val));
    },

    is_natural_no_zero: val => {
        return (regexps.naturalNoZeroRegex.test(val));
    },

    valid_ip: val => {
        return (regexps.ipRegex.test(val));
    },

    valid_base64: val => {
        return (regexps.base64Regex.test(val));
    },

    valid_url: val => {
        return (regexps.urlRegex.test(val));
    },

    greater_than_date: (val, date) => {
        let enteredDate = _getValidDate(val),
            validDate = _getValidDate(date);
        if (!validDate || !enteredDate) {
            return false;
        }
        return enteredDate > validDate;
    },

    less_than_date: (val, date) => {
        let enteredDate = _getValidDate(val),
            validDate = _getValidDate(date);
        if (!validDate || !enteredDate) {
            return false;
        }
        return enteredDate < validDate;
    },

    greater_than_or_equal_date: (val, date) => {
        let enteredDate = _getValidDate(val),
            validDate = _getValidDate(date);
        if (!validDate || !enteredDate) {
            return false;
        }
        return enteredDate >= validDate;
    },

    less_than_or_equal_date: (val, date) => {
        let enteredDate = _getValidDate(val),
            validDate = _getValidDate(date);
        if (!validDate || !enteredDate) {
            return false;
        }
        return enteredDate <= validDate;
    }
};

export default regexpUtil;

4)FormValidator类:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
 * validateRN.js 1.0.0
 * author tianyuan
 * date 2019-05-30
 */
import { SRNNative } from '@souche-f2e/srn-framework';
import errorMessage from './validate/errorMessage';
import regexps from './validate/regexps';
import regexpUtil from './validate/regexpUtil';
const {toast} = SRNNative;

class FormValidator {
    constructor(validateData, fields, callback) {
        this.callback = callback;
        this.errors = [];
        this.fields = fields || {};
        this.messages = {};
        this.handlers = {};
        this.conditionals = {};
        this.validateData = validateData || {};
    }

    _validateField(field) {
        let data = this.validateData;
        let rules = field.rules || [],//校验规则集合
            isRequired = rules.includes('required'),//是否是必填项
            isEmpty = (!data[field.paramKey] || data[field.paramKey] === '' || data[field.paramKey] === undefined);

        for (let i = 0, ruleLength = rules.length; i < ruleLength; i++) {
            let method = rules[i],
                param = null,
                failed = false,
                parts = regexps.ruleRegex.exec(method);//匹配matches[password] min_length[10]等方法

            if (!method) {continue;}

            if (!isRequired && isEmpty) {continue;}

            if (parts) {
                method = parts[1];
                param = parts[2];
            }

            if (method.charAt(0) === '!') {
                method = method.substring(1, method.length);
            }

            if (this._isFun(regexpUtil[method])) {
                let checkParam = param;
                if (method === 'matches' || (method.match('_date') && param !== 'today')) {
                    checkParam = data[param];
                }
                if (!regexpUtil[method].apply(this, [data[field.paramKey], checkParam])) {//匹配失败
                    failed = true;
                }
            } else if (this._isFun(this.handlers[method])) {
                if (this.handlers[method].apply(this, [data[field.paramKey], param, field]) === false) {
                    failed = true;
                }
            }

            if (failed) {
                let source = this.messages[field.paramKey + '.' + method] || this.messages[method] || errorMessage[method],
                    message = `${field.paramName}输入有误`;
                if (source) {
                    message = source.replace('%s', field.paramName);
                    if (param) {
                        message = this._formateMsg(param, method, message);
                    }
                }
                this.errors.push({
                    id: field.paramKey,
                    message: message
                });
            }
        }
    }

    _formateMsg(param, method, message) {
        let str = param;
        //先判断method是否是matches 或者是匹配日期的方法
        if (method === 'matches' || (method.match('_date') && param !== 'today')) {
            str = this._getParamName(param);
        }
        //判断是否是today
        if (param === 'today') {
            str = '今天';
        }
        return message.replace('%s', str);
    }

    _getParamName(val) {
        if (!val) {return '';}
        for (let i = 0; i < this.fields.length; i++) {
            if (this.fields[i].paramKey === val) {
                return this.fields[i].paramName;
            }
        }
        return '';
    }

    _isFun(obj) {
        return Object.prototype.toString.call(obj) === '[object Function]';
    }

    _isStr(val) {
        return Object.prototype.toString.call(val) === '[object String]';
    }

    validateForm() {
        if (!this.fields) {return;}
        let field;
        Object.keys(this.fields).forEach(val => {
            field = this.fields[val];
            field.paramKey = val;
            if (field.depends && this._isFun(field.depends)) {
                if (field.depends.call(this, field)) {
                    this._validateField(field);
                }
            } else if (field.depends && this._isStr(field.depends) && this.conditionals[field.depends]) {
                if (this.conditionals[field.depends].call(this,field)) {
                    this._validateField(field);
                }
            } else {
                this._validateField(field);
            }
        });
        if (this.errors.length) {
            toast(this.errors[0].message);
        } else {
            if (this._isFun(this.callback)) {
                this.callback();
            }
        }
    }

    setMessage(rule, message) {
        this.messages[rule] = message;
    }

    registerCallback(name, handler) {
        if (name && this._isStr(name) && handler && this._isFun(handler)) {
            this.handlers[name] = handler;
        }
    }

    registerConditional(name, conditional) {
        if (name && this._isStr(name) && conditional && this._isFun(conditional)) {
            this.conditionals[name] = conditional;
        }
    }
}

export default FormValidator;

2、使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import FormValidator from '../../utils/validateRN';

const vdata = {
    'name': '12',
    'phone': '12121232121'
};
const validator = new FormValidator(vdata, {
    'name': {
        paramName: '姓名',
        rules: ['min_length[10]']
    },
    'phone': {
        paramName: '手机号码',
        rules: ['required', 'valid_mobile']
    }
}, () => {
    //这里是验证成功的回调 可以写相应的业务逻辑
    console.log('验证成功');
});
validator.validateForm();//必须执行此方法,才会进行数据校验

3、参数描述

1)实例参数:

名称(形参) 说明 类型
validateData 要校验的提交给后端的数据 Object
fields 表示校验规则,整体数据结构如下:paramKey:
{
paramName: ‘xx’,
depends: ‘xx’,

depends: ()=>{
return Math.random() > 0.5;
},
rules: [‘required’, ‘greater_than_or_equal_date[startDate]’]
}
Array [Object]
callback 校验成功的回调 Function

2)fields的详细说明:

名称 说明 类型 是否必传
paramKey 一般是校验数据{key:value}的key值,在这里必须唯一 String
paramName paramKey的汉字描述 String
depends 代表要进行校验的条件,返回一个布尔值,false的情况不进行校验
可以直接传递一个方法,根据方法返回的值来确定是否进行rules中所有规则的校验;
也可以传递一个字符串,但前提是必须要执行registerConditional方法进行条件注册
String or Function
rules 代表要校验的规则集合,下文有表格详细说明。如果没有传递,则不会对validateData相应的字段进行校验 Array [String]

4、可用规则集合

规则名称 规则描述 是否传参 举例
required 若要校验的值为空,返回false required
matches 若要校验的值与期望的值不相等,返回false matches[password]
password代表的是validateData中的某个字段
valid_mobile 若要校验的值不是手机号码,返回false valid_mobile
valid_email 若要校验的值不是电子邮箱地址,返回false valid_email
valid_integerDecimal 若要校验的值不是整数、1位或2位小数,返回false valid_integerDecimal
alpha 若要校验的值不是英文字母(不区分大小写)组成的,返回false alpha
alpha_numeric 若要校验的值不是由字母或数字组成的,返回false alpha_numeric
alpha_dash 若要校验的值存在字母、数字、下划线和破折号之外的字符,返回false alpha_dash
numeric 若要校验的值不是整数,返回false
提示语是“请填写数字”
numeric
integer 同上
提示语是“请填写整数”
integer
decimal 若要校验的值不是小数(不限制位数),返回false decimal
decimalTwoPoint 若要校验的值不是2位小数,返回false decimalTwoPoint
is_natural_no_zero 若要校验的值不是非0自然数,返回false is_natural_no_zero
valid_ip 若要校验的值不是ip地址,返回false valid_ip
valid_base64 若要校验的值不是base64位格式数据,返回false valid_base64
valid_url 若要校验的值不是url地址,返回false valid_url
min_length 若要校验的值的长度比某个数字小,返回false min_length[20]
只能与数字比较
max_length 若要校验的值的长度比某个数字大,返回false max_length[200]
只能与数字比较
exact_length 若要校验的值的长度不等于某个数字,返回false exact_length[200]
只能与数字比较
greater_than 若要校验的值的长度不大于某个值(整数和小数均可),返回false greater_than[3.14]
只能与数字比较
less_than 若要校验的值的长度不小于某个值(整数和小数均可),返回false less_than[3.14]
只能与数字比较
greater_than_date 若要校验的值不晚于某个日期,返回false greater_than_date[startDate]
startDate代表的是validateData中的某个字段
也可以直接传递“today”代表与当前日期相比较
less_than_date 若要校验的值不早于某个日期,返回false less_than_date[startDate]
startDate代表的是validateData中的某个字段
也可以直接传递“today”代表与当前日期相比较
greater_than_or_equal_date 若要校验的值晚于某个日期,返回false greater_than_or_equal_date[startDate]
startDate代表的是validateData中的某个字段
也可以直接传递“today”代表与当前日期相比较
less_than_or_equal_date 若要校验的值早于某个日期,返回false less_than_or_equal_date[startDate]
startDate代表的是validateData中的某个字段
也可以直接传递“today”代表与当前日期相比较

5、扩充方法:

FormValidator类除了validateForm,还对外提供了三个方法:

1)setMassage: 自定义某规则的错误提示语。

2)registerCallback: 自定义某个规则。

示例:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const vdata = {
    'price': '2',
    'startDate': '2019-06-01',
    'endDate': '2019-05-31',
    'ip': '172.17.55.223',
};
const validator = new FormValidator(vdata, {
    'price': {
        paramName: '价格',
        rules: ['required', 'valid_price']
    },
    'startDate': {
        paramName: '开始日期',
        rules: ['required']
    },
    'endDate': {
        paramName: '结束日期',
        rules: ['required', 'greater_than_or_equal_date[startDate]']
    },
    'ip': {
        paramName: 'ip地址',
        rules: ['required', 'validate_ip[192.168]'],
    }
}, () => {
    console.log('验证成功');
});

//自定义的规则可以包括一个参数也可以不写参数;
//提示语中一般选择用一个%s占位,并且用paramName代替;
//第二个%s用来给规则中传的参数占位,也可以不用%s占位;
validator.setMessage('greater_than_or_equal_date', '填写的%s必须晚于%s');
validator.registerCallback('valid_price', (val, param, field)=>{
    //val代表要校验的值
    //param代表校验时传的参数
    //field代表fields中的某个键值对
    if (/^[0-9]*\.[0-9]{1}$/.test(vdata.price)) {//匹配价格为1位小数
        return false;
    }
    return true;
});
//自定义的规则若包括参数,则参数必须写确定的值;如:'validate_ip[192.168]'
validator.setMessage('validate_ip', '填写的%s必须是%s网段');
validator.registerCallback('validate_ip', (val, param, field)=>{
    if (val.match(param)) {//匹配该网段的ip
        return true;
    }
    return false;
});
validator.validateForm();

3)registerConditional: 注册某个是否进行校验的条件,与depends配合使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const vdata = {
    'remark': '',
};
const validator = new FormValidator(vdata, {
    'remark': {
        paramName: '备注',
        depends: 'checkRemark',
        rules: ['required']
    }
}, () => {
    console.log('验证成功');
});
//当满足某个条件时 才会按照rules中的所有规则校验某个数据
validator.registerConditional('checkRemark', (field)=>{
    return Math.random() > 0.5;
});
validator.validateForm();

或者写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const vdata = {
    'remark': '',
};
const validator = new FormValidator(vdata, {
    'remark': {
        paramName: '备注',
        depends: ()=>{
            return Math.random() > 0.5;
        },
        rules: ['required']
    }
}, () => {
    console.log('验证成功');
});
validator.validateForm();

参考文档:

validate.js