@truenewx/tnxvue3
Version:
互联网技术解决方案:Vue3扩展支持
348 lines (340 loc) • 17.3 kB
text/typescript
/**
* 校验规则转换器,将服务端元数据中的校验规则转换为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);
}
}