UNPKG

@truenewx/tnxvue3

Version:

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

348 lines (340 loc) 17.3 kB
/** * 校验规则转换器,将服务端元数据中的校验规则转换为async-validator组件的规则。 * async-validator组件详见:https://github.com/yiminghe/async-validator */ import AsyncValidator, {RuleType, Rules} from 'async-validator'; import {FormItemRule} from 'element-plus'; import {Validator} from '../../../tnxcore/src/tnxcore.ts'; import {ApiModelPropertyMeta, ApiModelMeta} from '../../../tnxcore/src/api/meta.ts'; /** * async-validator组件支持的类型清单 */ const ruleTypes = ['string', 'number', 'boolean', 'method', 'regexp', 'integer', 'float', 'array', 'object', 'enum', 'date', 'url', 'hex', 'email', 'any']; function getRuleType(metaType: string): RuleType { if (ruleTypes.contains(metaType)) { return metaType as RuleType; } switch (metaType) { case 'decimal': return 'float'; case 'regex': return 'regexp'; } return ruleTypes[0] as RuleType; } export default class TnxElValidator extends Validator { getRule(validationName: string, validationValue: any, fieldMeta?: ApiModelPropertyMeta): FormItemRule | undefined { let rule: FormItemRule; let fieldCaption = ''; // 据目前观察,字段格式校验的错误消息均显示在字段旁,无需显示字段名称,未来如果出现不在字段旁显示的场景,再考虑扩展 // if (fieldMeta && fieldMeta.caption) { // fieldCaption = fieldMeta.caption; // } let _this = this; switch (validationName) { case 'required': case 'notNull': case 'notEmpty': case 'notBlank': if (validationValue === true) { rule = { required: true, validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { 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 = _this.getErrorMessage(validationName, fieldCaption); return callback(new Error(message)); } } return callback(); } } } break; case 'maxLength': rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { 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 = _this.getErrorMessage(validationName, fieldCaption, validationValue, fieldLength - validationValue); return callback(new Error(message)); } } return callback(); } }; break; case 'number': if (validationValue === true) { rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { if (fieldValue && typeof fieldValue === 'string') { if (!/^[0-9]+$/.test(fieldValue)) { let message = _this.getErrorMessage(validationName, fieldCaption); return callback(new Error(message)); } } return callback(); } }; } break; case 'notContainsHtmlChars': if (validationValue === true) { rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { 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 = _this.getErrorMessage('notContains', fieldCaption, s); return callback(new Error(message)); } } } return callback(); } }; } break; case 'notContains': if (validationValue) { rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { if (typeof fieldValue === 'string') { let limitedValues = validationValue.split(' '); for (let i = 0; i < limitedValues.length; i++) { if (fieldValue.indexOf(limitedValues[i]) >= 0) { let message = _this.getErrorMessage('notContains', fieldCaption, validationValue); return callback(new Error(message)); } } } return callback(); } }; } break; case 'rejectHtmlTags': if (validationValue === true) { rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { if (fieldValue) { if (/<[a-z]+[ ]*[/]?[ ]*>/gi.test(fieldValue)) { let message = _this.getErrorMessage(validationName, fieldCaption); return callback(new Error(message)); } } return callback(); } }; } break; case 'allowedHtmlTags': if (validationValue) { rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { 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 = _this.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: any, callback: (error?: (string | Error)) => void) { 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 = _this.getErrorMessage(validationName, fieldCaption, tags.join(', ')); return callback(new Error(message)); } } } } return callback(); } }; } break; case 'email': if (validationValue === true) { rule = { type: validationName, message: _this.getErrorMessage(validationName, fieldCaption), } } break; case 'idCardNo': rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { if (validationValue) { let message = _this.validateRegExp(validationName, fieldValue, fieldCaption); if (message) { return callback(new Error(message)); } } return callback(); } }; break; case 'url': rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { if (validationValue) { let message = _this.validateRegExp(validationName, fieldValue, fieldCaption); if (message) { return callback(new Error(message)); } } return callback(); } }; break; case 'opposableUrl': rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { if (validationValue) { let message = _this.validateRegExp(validationName, fieldValue, fieldCaption); if (message) { return callback(new Error(message)); } } return callback(); } }; break; case 'regex': rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { 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 = _this.getErrorMessage('regex', fieldCaption, ''); } return callback(new Error(message)); } } return callback(); } } break; case 'custom': if (typeof validationValue === 'function') { rule = { validator(r, fieldValue: any, callback: (error?: (string | Error)) => void) { 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; } getRules(meta: ApiModelMeta): Record<string, FormItemRule[]> { let rules: Record<string, FormItemRule[]> = {}; Object.keys(meta).forEach(fieldName => { let fieldMeta = meta[fieldName]; if (fieldMeta.validation) { let fieldRules: FormItemRule[] = []; Object.keys(fieldMeta.validation).forEach((validationName: string) => { let validationValue = fieldMeta.validation[validationName]; let rule = this.getRule(validationName, validationValue, fieldMeta); if (rule) { fieldRules.push(rule); } }); // 将可能包含的引用字段路径中的.替换为__,以符合规则名称规范 let ruleName = fieldName.replace('.', '__'); rules[ruleName] = fieldRules; } }); return rules; } /** * 校验字段 * @param rules 包含所有字段校验规则的对象,格式:{字段名: [规则数组]} * @param model 待校验的模型对象,包含待校验的字段 * @returns {Promise} then({errors,fields}=>{}) */ validate(rules: Rules, model: Record<string, any>): Promise<Record<string, any>> { return new AsyncValidator(rules).validate(model); } }