UNPKG

@truenewx/tnxvue2

Version:

互联网技术解决方案:Vue2扩展支持

373 lines (365 loc) 16.9 kB
// tnxvue-validator.js /** * 校验规则转换器,将服务端元数据中的校验规则转换为async-validator组件的规则。 * async-validator组件详见:https://github.com/yiminghe/async-validator */ import validator from '@truenewx/tnxcore/src/tnxcore-validator'; import AsyncValidator from 'async-validator'; const regExps = { number: /^-?([1-9]\d{0,2}((,?\d{3})*|\d*)(\.\d*)?|0?\.\d*|0)$/, integer: /^(-?[1-9]\d{0,2}(,?\d{3}))|0*$/, email: /^[a-zA-Z0-9_\-]([a-zA-Z0-9_\-\.]{0,62})@[a-zA-Z0-9_\-]([a-zA-Z0-9_\-\.]{0,62})$/, idCardNo: /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/, url: /^https?:\/\/[A-Za-z0-9]+(\.?[A-Za-z0-9_-]+)*(:[0-9]+)?(\/\S*)?$/, opposableUrl: /^(https?:\/)?\/[A-Za-z0-9]+(\.?[A-Za-z0-9_-]+)*(:[0-9]+)?(\/\S*)?$/, } function validateRegExp(regExpName, fieldValue, fieldCaption) { if (fieldValue) { let regExp = regExps[regExpName]; if (regExp && !regExp.test(fieldValue)) { return validator.getErrorMessage(regExpName, fieldCaption); } } return undefined; } /** * async-validator组件支持的类型清单 */ const ruleTypes = ['string', 'number', 'boolean', 'method', 'regexp', 'integer', 'float', 'array', 'object', 'enum', 'date', 'url', 'hex', 'email', 'any']; function getRuleType(metaType) { if (ruleTypes.contains(metaType)) { return metaType; } switch (metaType) { case 'decimal': return 'float'; case 'regex': return 'regexp'; } return ruleTypes[0]; } function getRule(validationName, validationValue, fieldMeta) { let rule = undefined; let fieldCaption = ''; // 据目前观察,字段格式校验的错误消息均显示在字段旁,无需显示字段名称,未来如果出现不在字段旁显示的场景,再考虑扩展 // if (fieldMeta && fieldMeta.caption) { // fieldCaption = fieldMeta.caption; // } switch (validationName) { case 'required': case 'notNull': case 'notEmpty': case 'notBlank': if (validationValue === true) { rule = { required: true, validator(r, fieldValue, callback, source, options) { if (validationValue) { let blank = fieldValue === undefined || fieldValue === null; if (!blank) { if (Array.isArray(fieldValue)) { blank = fieldValue.length === 0; // 数组长度为0视为空 } else if (typeof fieldValue === 'string') { blank = fieldValue.trim().length === 0; // 字符串去掉两端空格后长度为0视为空 } else if (typeof fieldValue === 'number') { blank = isNaN(fieldValue); // 非法的数字视为空 } else if (typeof fieldValue === 'object') { // 对象非日期,且没有字段的视为空 blank = !(fieldValue instanceof Date) && Object.keys(fieldValue).length === 0; if (!blank) { // 可永久的日期对象,非永久且日期值为空时视为空 if (typeof fieldValue.permanent === 'boolean') { blank = !fieldValue.permanent && !fieldValue.value; } } } } if (blank) { let message = validator.getErrorMessage(validationName, fieldCaption); return callback(new Error(message)); } } return callback(); } } } break; case 'maxLength': rule = { validator(r, fieldValue, callback, source, options) { if (typeof validationValue === 'number' && typeof fieldValue === 'string') { // 回车符计入长度 let enterLength = fieldValue.indexOf('\n') < 0 ? 0 : fieldValue.match(/\n/g).length; let fieldLength = fieldValue.length + enterLength; if (fieldLength > validationValue) { let message = validator.getErrorMessage(validationName, fieldCaption, validationValue, fieldLength - validationValue); return callback(new Error(message)); } } return callback(); } }; break; case 'number': if (validationValue === true) { rule = { validator(r, fieldValue, callback, source, options) { if (fieldValue && typeof fieldValue === 'string') { if (!/^[0-9]+$/.test(fieldValue)) { let message = validator.getErrorMessage(validationName, fieldCaption); return callback(new Error(message)); } } return callback(); } }; } break; case 'notContainsHtmlChars': if (validationValue === true) { rule = { validator(r, fieldValue, callback, source, options) { if (typeof fieldValue === 'string') { let limitedValues = ['<', '>', '\'', '"', '\\']; for (let i = 0; i < limitedValues.length; i++) { if (fieldValue.indexOf(limitedValues[i]) >= 0) { let s = limitedValues.join(' '); let message = validator.getErrorMessage('notContains', fieldCaption, s); return callback(new Error(message)); } } } return callback(); } }; } break; case 'notContains': if (validationValue) { rule = { validator(r, fieldValue, callback, source, options) { if (typeof fieldValue === 'string') { let limitedValues = validationValue.split(' '); for (let i = 0; i < limitedValues.length; i++) { if (fieldValue.indexOf(limitedValues[i]) >= 0) { let message = validator.getErrorMessage('notContains', fieldCaption, validationValue); return callback(new Error(message)); } } } return callback(); } }; } break; case 'rejectHtmlTags': if (validationValue === true) { rule = { validator(r, fieldValue, callback, source, options) { if (fieldValue) { if (/<[a-z]+[ ]*[/]?[ ]*>/gi.test(fieldValue)) { let message = validator.getErrorMessage(validationName, fieldCaption); return callback(new Error(message)); } } return callback(); } }; } break; case 'allowedHtmlTags': if (validationValue) { rule = { validator(r, fieldValue, callback, source, options) { if (typeof fieldValue === 'string') { let tags = validationValue.toLowerCase().split(','); if (tags.length) { fieldValue = fieldValue.toLowerCase(); let leftIndex = fieldValue.indexOf('<'); let rightIndex = leftIndex >= 0 ? fieldValue.indexOf('>', leftIndex) : -1; while (leftIndex >= 0 && rightIndex >= 0) { let sub = fieldValue.substring(leftIndex + 1, rightIndex); // <>中间的部分 let spaceIndex = sub.indexOf(' '); let tag = spaceIndex >= 0 ? sub.substring(0, spaceIndex) : sub; if (tag.startsWith('/')) { // 标签结束处 tag = tag.substring(1); } if (!tags.contains(tag.toLowerCase())) { let message = validator.getErrorMessage(validationName, fieldCaption, tags.join(', ')); return callback(new Error(message)); // 存在不允许的标签,则报错 } leftIndex = fieldValue.indexOf('<', rightIndex); rightIndex = leftIndex >= 0 ? fieldValue.indexOf('>', leftIndex) : -1; } } } return callback(); } }; } break; case 'forbiddenHtmlTags': if (validationValue) { rule = { validator(r, fieldValue, callback, source, options) { if (fieldValue) { let tags = validationValue.toLowerCase().split(','); if (tags.length) { fieldValue = fieldValue.toLowerCase(); for (let tag of tags) { if (fieldValue.contains('<' + tag + '>') || fieldValue.contains('<' + tag + ' ')) { let message = validator.getErrorMessage(validationName, fieldCaption, tags.join(', ')); return callback(new Error(message)); } } } } return callback(); } }; } break; case 'email': if (validationValue === true) { rule = { type: validationName, message: validator.getErrorMessage(validationName, fieldCaption), } } break; case 'idCardNo': rule = { validator(r, fieldValue, callback, source, options) { if (validationValue) { let message = validateRegExp(validationName, fieldValue, fieldCaption); if (message) { return callback(new Error(message)); } } return callback(); } }; break; case 'url': rule = { validator(r, fieldValue, callback, source, options) { if (validationValue) { let message = validateRegExp(validationName, fieldValue, fieldCaption); if (message) { return callback(new Error(message)); } } return callback(); } }; break; case 'opposableUrl': rule = { validator(r, fieldValue, callback, source, options) { if (validationValue) { let message = validateRegExp(validationName, fieldValue, fieldCaption); if (message) { return callback(new Error(message)); } } return callback(); } }; break; case 'regex': rule = { validator(r, fieldValue, callback, source, options) { if (fieldValue) { let pattern = validationValue.pattern; // 服务端正则表达式无需以^$作为首尾,客户端需确保以^$作为首尾 if (!pattern.startsWith('^')) { pattern = '^' + pattern; } if (!pattern.endsWith('$')) { pattern += '$'; } let regexp = new RegExp(pattern, 'gi'); if (!regexp.test(fieldValue)) { let message = validationValue.message; if (message) { message = fieldCaption + message; } else { message = validator.getErrorMessage('regex', fieldCaption, ''); } return callback(new Error(message)); } } return callback(); } } break; case 'custom': if (typeof validationValue === 'function') { rule = { validator(r, fieldValue, callback, source, options) { let message = validationValue(fieldValue); if (message) { return callback(new Error(message)); } return callback(); } } } break; } if (rule) { let metaType = 'text'; if (fieldMeta && fieldMeta.type) { metaType = fieldMeta.type.toLowerCase(); } rule.type = rule.type || getRuleType(metaType); rule.trigger = metaType === 'option' ? 'change' : 'blur'; } return rule; } /** * 从服务端元数据中构建完整的规则集 * @param meta * @returns {{}} */ export function getRules(meta) { let rules = {}; Object.keys(meta).forEach(fieldName => { let fieldMeta = meta[fieldName]; if (fieldMeta.validation) { let fieldRules = []; Object.keys(fieldMeta.validation).forEach(validationName => { let validationValue = fieldMeta.validation[validationName]; let rule = getRule(validationName, validationValue, fieldMeta); if (rule) { fieldRules.push(rule); } }); // 将可能包含的引用字段路径中的.替换为__,以符合async-validator规则名称的规范 let ruleName = fieldName.replace('.', '__'); rules[ruleName] = fieldRules; } }); return rules; } export default { validateRegExp, getRule, getRules, getErrorMessage: validator.getErrorMessage, /** * 校验字段 * @param rules 包含所有字段校验规则的对象,格式:{字段名: [规则数组]} * @param model 待校验的模型对象,包含待校验的字段 * @param callback 校验结果回调,参数为:errors, fields * @returns {Promise} */ validate: function (rules, model, callback) { return new AsyncValidator(rules).validate(model, callback); }, }