feat: 表单项校验变更为顺序校验模式 (#6553)

* feat:
    表单项校验不再校验全部规则,改为顺序校验模式;
    自定义校验 addRule 增加灵活展示错误信息特性。

* doc: 表单项校验不再校验全部规则,改为顺序校验模式

* doc: 自定义校验 addRule 增加灵活展示错误信息特性

* test: 表单验证规则改为顺序校验,不再全部校验,测试用例变更

---------

Co-authored-by: jinye <jinye@baidu.com>
This commit is contained in:
lmaomaoz 2023-04-07 13:17:56 +08:00 committed by GitHub
parent 5b0171032a
commit d4c931cb85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 48 deletions

View File

@ -1087,7 +1087,7 @@ order: 1
}
```
同样也可以配置多个格式校验
同样也可以配置多个格式校验,按顺序进行校验,中途校验不通过就会终止
```schema: scope="body"
{
@ -1102,6 +1102,7 @@ order: 1
"isNumeric": true,
"minimum": 10
},
"validateOnChange": true,
"description": "请输入数字类型文本"
}
]

View File

@ -8,6 +8,8 @@ title: 扩展现有组件
如果默认的表单检测规则不满足需求,还可以通过代码的方式扩展。
### 普通用法
JSSDK 中的用法:
```javascript
@ -23,8 +25,10 @@ amisLib.addRule(
value === '天津' ||
value === '重庆'
) {
// return true 表示校验通过
return true;
}
// return false 表示校验不通过,会进行错误提示
return false;
},
// 出错时的报错信息
@ -46,6 +50,57 @@ amisLib.addRule(
import {addRule} from 'amis';
```
### 更加灵活的提示错误
> `2.9.1` 及以上版本
如果想在一个验证函数里根据不同情况提示不同的错误信息,需要返回固定格式的结果:
```js
{
error: true,
msg: '错误信息'
}
```
注意,当返回对象时,`error`必须为`true` 才会判定为错误:
具体用法如下:
```javascript
let amisLib = amisRequire('amis');
amisLib.addRule(
// 校验名
'isZXS',
// 校验函数values 是表单里所有表单项的值可用于做联合校验value 是当前表单项的值
(values, value) => {
if (value === '新加坡') {
// 校验不通过,提示:该地区不在国内
return {
error: true,
msg: '该地区不在国内'
};
}
if (
value === '北京' ||
value === '上海' ||
value === '天津' ||
value === '重庆'
) {
// return true 表示校验通过
return true;
}
// 校验不通过,提示:输入的不是直辖市
return {
error: true,
msg: '输入的不是直辖市'
};
}
);
```
## 同时支持多种类型编辑
在表单编辑中,每个 name 一般对应一种类型,如果这个 name 有多种类型,比如下面的例子中 id 的值有可能是字符串,也有可能是数字,但 type 只能设置为一种类型,这种情况如何处理?

View File

@ -99,7 +99,10 @@ export interface ValidateFn {
arg3?: any,
arg4?: any,
arg5?: any
): boolean;
): boolean | {
error: boolean;
msg?: string;
};
}
export const validations: {
@ -515,10 +518,14 @@ export function validate(
msg: string;
}> = [];
rules &&
Object.keys(rules).forEach(ruleName => {
if (rules) {
const ruleNames = Object.keys(rules);
const length = ruleNames.length;
for (let index = 0; index < length; index++) {
const ruleName = ruleNames[index];
if (!rules[ruleName] && rules[ruleName] !== 0) {
return;
continue;
} else if (typeof validations[ruleName] !== 'function') {
throw new Error('Validation `' + ruleName + '` not exists!');
}
@ -534,17 +541,31 @@ export function validate(
return item;
});
if (!fn(values, value, ...args)) {
let validateRes = fn(values, value, ...args);
// addRule 允许返回
// {error: true, msg: '错误提示'}
// 格式的信息来灵活展示错误
let fnResErrorMsg = '';
if (
typeof validateRes === 'object' &&
validateRes.error === true
) {
fnResErrorMsg = validateRes?.msg ?? '';
}
if (!validateRes || fnResErrorMsg) {
let msgRuleName = ruleName;
if (Array.isArray(value)) {
msgRuleName = `${ruleName}Array`;
}
errors.push({
return [{
rule: ruleName,
msg: filter(
__(
(messages && messages[ruleName]) ||
(messages && messages[ruleName]) ||
fnResErrorMsg ||
validateMessages[msgRuleName] ||
validateMessages[ruleName]
),
@ -552,9 +573,10 @@ export function validate(
...[''].concat(args)
}
)
});
}];
}
});
}
}
return errors;
}

View File

@ -143,35 +143,36 @@ describe('InputGroup with validationConfig', () => {
expect(errorDom?.childElementCount).toStrictEqual(2);
});
test('InputGroup with validationConfig: delimiter', async () => {
const delimiter = '@@';
const {container} = setup({validationConfig: {delimiter}});
const child1 = container.querySelector(
'input[name=child1]'
) as HTMLInputElement;
const child2 = container.querySelector(
'input[name=child2]'
) as HTMLInputElement;
fireEvent.change(child1, {target: {value: 'amis'}});
await wait(500);
// v2.9.1 改为顺序校验后,不会同时输出多条校验错误消息,所以也不会用到分隔符
// test('InputGroup with validationConfig: delimiter', async () => {
// const delimiter = '@@';
// const {container} = setup({validationConfig: {delimiter}});
// const child1 = container.querySelector(
// 'input[name=child1]'
// ) as HTMLInputElement;
// const child2 = container.querySelector(
// 'input[name=child2]'
// ) as HTMLInputElement;
// fireEvent.change(child1, {target: {value: 'amis'}});
// await wait(500);
const submitBtn = screen.getByRole('button', {name: 'Submit'});
fireEvent.click(submitBtn);
await wait(500);
// const submitBtn = screen.getByRole('button', {name: 'Submit'});
// fireEvent.click(submitBtn);
// await wait(500);
screen.debug(container);
// screen.debug(container);
expect(
container.querySelector('*[class*="InputGroup-validation--full"]')
).toBeInTheDocument();
// expect(
// container.querySelector('*[class*="InputGroup-validation--full"]')
// ).toBeInTheDocument();
const errorDom = container.querySelector('*[class*="Form-feedback"]');
expect(errorDom?.childElementCount).toStrictEqual(1);
// const errorDom = container.querySelector('*[class*="Form-feedback"]');
// expect(errorDom?.childElementCount).toStrictEqual(1);
const child1ErrorText = errorDom?.childNodes[0]
? errorDom.childNodes[0].textContent
: '';
// const child1ErrorText = errorDom?.childNodes[0]
// ? errorDom.childNodes[0].textContent
// : '';
expect(child1ErrorText).toEqual(expect.stringMatching(delimiter));
});
// expect(child1ErrorText).toEqual(expect.stringMatching(delimiter));
// });
});

View File

@ -1,4 +1,4 @@
import {validate, str2rules} from '../../src';
import {validate, str2rules, addRule} from '../../src';
test('validation:isRequired valid', () => {
expect(
@ -718,10 +718,6 @@ test('validation:multipleRules invalid', () => {
{
rule: 'isUrl',
msg: 'validate.isUrl'
},
{
rule: 'isInt',
msg: 'validate.isInt'
}
]);
});
@ -831,10 +827,6 @@ test('validation:multipleMatchRegexp invalid', () => {
{
rule: 'matchRegexp1',
msg: '请输入abc开头的好么'
},
{
rule: 'matchRegexp2',
msg: '请输入123结尾的好么'
}
]);
});
@ -874,10 +866,6 @@ test('validation:multipleMatchRegexp:noSlash invalid', () => {
{
rule: 'matchRegexp1',
msg: '请输入abc开头的好么'
},
{
rule: 'matchRegexp2',
msg: '请输入123结尾的好么'
}
]);
});