feat: FormItem格式校验支持日期时间规则 (#5241)

This commit is contained in:
RUNZE LU 2022-08-26 13:31:29 +08:00 committed by GitHub
parent e7c909b277
commit eb28d2fba7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1213 additions and 131 deletions

View File

@ -678,7 +678,7 @@ order: 1
### 字符串形式(不推荐)
也可以配置字符串形式来指定,如下例,输入不合法的值,点击提交会报错并显示报错信息
也可以配置字符串形式来指定,如下例,输入不合法的值,点击提交会报错并显示报错信息。(注意日期时间类的校验规则不支持字符串形式)
```schema: scope="body"
{
@ -765,7 +765,7 @@ amis 会有默认的报错信息,如果你想自定义校验信息,配置`va
}
```
默认的校验信息如下,可以直接配置文字,也可用多语言中的 key。参考https://github.com/baidu/amis/blob/master/src/locale/zh-CN.ts#L175-L201
默认的校验信息如下,可以直接配置文字,也可用多语言中的 key。参考https://github.com/baidu/amis/blob/master/packages/amis-ui/src/locale/zh-CN.ts#L250
```js
{
@ -794,7 +794,20 @@ amis 会有默认的报错信息,如果你想自定义校验信息,配置`va
isPhoneNumber: 'validate.isPhoneNumber',
isTelNumber: 'validate.isTelNumber',
isZipcode: 'validate.isZipcode',
isId: 'validate.isId'
isId: 'validate.isId',
/* 日期时间相关校验规则 2.2.0 及以上版本生效 */
isDateTimeSame: 'validate.isDateTimeSame',
isDateTimeBefore: 'validate.isDateTimeBefore',
isDateTimeAfter: 'validate.isDateTimeAfter',
isDateTimeSameOrBefore: 'validate.isDateTimeSameOrBefore',
isDateTimeSameOrAfter: 'validate.isDateTimeSameOrAfter',
isDateTimeBetween: 'validate.isDateTimeBetween',
isTimeSame: 'validate.isTimeSame',
isTimeBefore: 'validate.isTimeBefore',
isTimeAfter: 'validate.isTimeAfter',
isTimeSameOrBefore: 'validate.isTimeSameOrBefore',
isTimeSameOrAfter: 'validate.isTimeSameOrAfter',
isTimeBetween: 'validate.isTimeBetween'
}
```
@ -804,31 +817,45 @@ amis 会有默认的报错信息,如果你想自定义校验信息,配置`va
### 支持的格式校验
- `isEmail` 必须是 Email。
- `isUrl` 必须是 Url。
- `isNumeric` 必须是 数值。
- `isAlpha` 必须是 字母。
- `isAlphanumeric` 必须是 字母或者数字。
- `isInt` 必须是 整形。
- `isFloat` 必须是 浮点形。
- `isLength:length` 是否长度正好等于设定值。
- `minLength:length` 最小长度。
- `maxLength:length` 最大长度。
- `maximum:number` 最大值。
- `minimum:number` 最小值。
- `equals:xxx` 当前值必须完全等于 xxx。
- `equalsField:xxx` 当前值必须与 xxx 变量值一致。
- `isJson` 是否是合法的 Json 字符串。
- `isUrlPath` 是 url 路径。
- `isPhoneNumber` 是否为合法的手机号码
- `isTelNumber` 是否为合法的电话号码
- `isZipcode` 是否为邮编号码
- `isId` 是否为身份证号码,没做校验
- `matchRegexp:/foo/` 必须命中某个正则。
- `matchRegexp1:/foo/` 必须命中某个正则。
- `matchRegexp2:/foo/` 必须命中某个正则。
- `matchRegexp3:/foo/` 必须命中某个正则。
- `matchRegexp4:/foo/` 必须命中某个正则。
| 规则名称 | 说明 | 定义 | 版本 |
| ------------------------ | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------- |
| `isEmail` | 必须是 Email。 | `(value: any) => boolean` | |
| `isUrl` | 必须是 Url。 | `(value: any) => boolean` | |
| `isNumeric` | 必须是 数值。 | `(value: any) => boolean` | |
| `isAlpha` | 必须是 字母。 | `(value: any) => boolean` | |
| `isAlphanumeric` | 必须是 字母或者数字。 | `(value: any) => boolean` | |
| `isInt` | 必须是 整形。 | `(value: any) => boolean` | |
| `isFloat` | 必须是 浮点形。 | `(value: any) => boolean` | |
| `isLength:length` | 是否长度正好等于设定值。 | `(value: any) => boolean` | |
| `minLength:length` | 最小长度。 | `(value: any, length: number) => boolean` | |
| `maxLength:length` | 最大长度。 | `(value: any, length: number) => boolean` | |
| `maximum:number` | 最大值。 | `(value: any, maximum: number) => boolean` | |
| `minimum:number` | 最小值。 | `(value: any, minimum:number) => boolean` | |
| `equals:xxx` | 当前值必须完全等于 xxx。 | `(value: any, targetValue: any) => boolean` | |
| `equalsField:xxx` | 当前值必须与 xxx 变量值一致。 | `(value: any, field: string) => boolean` | |
| `isJson` | 是否是合法的 Json 字符串。 | `(value: any) => boolean` | |
| `isUrlPath` | 是 url 路径。 | `(value: any) => boolean` | |
| `isPhoneNumber` | 是否为合法的手机号码 | `(value: any) => boolean` | |
| `isTelNumber` | 是否为合法的电话号码 | `(value: any) => boolean` | |
| `isZipcode` | 是否为邮编号码 | `(value: any) => boolean` | |
| `isId` | 是否为身份证号码,没做校验 | `(value: any) => boolean` | |
| `matchRegexp:/foo/` | 必须命中某个正则。 | `(value: any, regexp: string \| RegExp) => boolean` | |
| `matchRegexp1:/foo/` | 必须命中某个正则。 | `(value: any, regexp: string \| RegExp) => boolean` | |
| `matchRegexp2:/foo/` | 必须命中某个正则。 | `(value: any, regexp: string \| RegExp) => boolean` | |
| `matchRegexp3:/foo/` | 必须命中某个正则。 | `(value: any, regexp: string \| RegExp) => boolean` | |
| `matchRegexp4:/foo/` | 必须命中某个正则。 | `(value: any, regexp: string \| RegExp) => boolean` | |
| `isDateTimeSame` | 日期和目标日期相同,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetDate: any, granularity?: string) => boolean` | `2.2.0` |
| `isDateTimeBefore` | 日期早于目标日期,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetDate: any, granularity?: string) => boolean` | `2.2.0` |
| `isDateTimeAfter` | 日期晚于目标日期,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetDate: any, granularity?: string) => boolean` | `2.2.0` |
| `isDateTimeSameOrBefore` | 日期早于目标日期或和目标日期相同,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetDate: any, granularity?: string) => boolean` | `2.2.0` |
| `isDateTimeSameOrAfter` | 日期晚于目标日期或和目标日期相同,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetDate: any, granularity?: string) => boolean` | `2.2.0` |
| `isDateTimeBetween` | 日期处于目标日期范围,支持指定粒度和区间的开闭形式,默认到毫秒 `millisecond`,左右开区间`'()'` | `(value: any, lhs: any, rhs: any, granularity?: string, inclusivity?: '()' \| '[)' \| '(]' \| '[]') => boolean` | `2.2.0` |
| `isTimeSame` | 时间和目标时间相同,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetTime: any, granularity?: string) => boolean` | `2.2.0` |
| `isTimeBefore` | 时间早于目标时间,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetTime: any, granularity?: string) => boolean` | `2.2.0` |
| `isTimeAfter` | 时间晚于目标时间,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetTime: any, granularity?: string) => boolean` | `2.2.0` |
| `isTimeSameOrBefore` | 时间早于目标时间或和目标时间相同,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetTime: any, granularity?: string) => boolean` | `2.2.0` |
| `isTimeSameOrAfter` | 时间晚于目标时间或和目标时间相同,支持指定粒度,默认到毫秒 `millisecond` | `(value: any, targetTime: any, granularity?: string) => boolean` | `2.2.0` |
| `isTimeBetween` | 时间处于目标时间范围,支持指定粒度和区间的开闭形式,默认到毫秒 `millisecond`,左右开区间`'()'` | `(value: any, lhs: any, rhs: any, granularity?: string, inclusivity?: '()' \| '[)' \| '(]' \| '[]') => boolean` | `2.2.0` |
#### 验证只允许 http 协议的 url 地址

View File

@ -3,107 +3,398 @@ export default {
toolbar: "<a target='_blank' href='/docs/renderers/Form/FormItem'>文档</a>",
body: [
{
type: 'form',
autoFocus: false,
messages: {
validateFailed: '请仔细检查表单规则,部分表单项没通过验证'
},
title: '表单',
actions: [
type: 'grid',
columns: [
{
type: 'submit',
label: '提交'
}
],
api: '/api/mock2/form/saveFormFailed?waitSeconds=2',
mode: 'horizontal',
body: [
{
type: 'input-text',
name: 'test',
label: '必填',
required: true
body: [
{
type: 'form',
autoFocus: false,
messages: {
validateFailed: '请仔细检查表单规则,部分表单项没通过验证'
},
title: '表单',
actions: [
{
type: 'submit',
label: '提交'
}
],
api: '/api/mock2/form/saveFormFailed?waitSeconds=2',
mode: 'horizontal',
body: [
{
type: 'input-text',
name: 'test',
label: '必填',
required: true
},
{
type: 'divider'
},
{
name: 'test1',
type: 'input-email',
label: 'Email'
},
{
type: 'divider'
},
{
name: 'url',
type: 'input-url',
label: 'URL'
},
{
type: 'divider'
},
{
name: 'num',
type: 'input-text',
label: '数字',
validations: 'isNumeric'
},
{
type: 'divider'
},
{
name: 'alpha',
type: 'input-text',
label: '字母或数字',
validations: 'isAlphanumeric'
},
{
type: 'divider'
},
{
name: 'int',
type: 'input-text',
label: '整形',
validations: 'isInt'
},
{
type: 'divider'
},
{
name: 'minLength',
type: 'input-text',
label: '长度限制',
validations: 'minLength:2,maxLength:10'
},
{
type: 'divider'
},
{
name: 'min',
type: 'input-text',
label: '数值限制',
validations: 'maximum:10,minimum:2'
},
{
type: 'divider'
},
{
name: 'reg',
type: 'input-text',
label: '正则',
validations: 'matchRegexp:/^abc/',
validationErrors: {
matchRegexp: '请输入abc开头的好么?'
}
},
{
type: 'divider'
},
{
name: 'test2',
type: 'input-text',
label: '服务端验证'
}
]
}
]
},
{
type: 'divider'
body: [
{
type: 'form',
name: 'form2',
api: '/api/mock2/form/saveForm',
debug: true,
debugConfig: {
levelExpand: 3
},
title: '<b>日期时间相关校验规则</b>',
data: {
datetime1: '2022-09-10 00:00:00',
datetime2: '2022-10-01 00:00:00',
datetime3: '2022-10-01 00:00:00',
datetime4: '2022-10-01 00:00:01',
datetime5: '2022-09-30 23:59:59',
datetime6: '2022-09-30 23:59:59'
},
body: [
{
type: 'input-datetime',
label: 'isDateTimeSame',
name: 'datetime1',
format: 'YYYY-MM-DD HH:mm:ss',
validations: {
isDateTimeSame: '2022-10-01 00:00:00'
}
},
{
type: 'divider'
},
{
type: 'input-datetime',
label: 'isDateTimeBefore',
name: 'datetime2',
format: 'YYYY-MM-DD HH:mm:ss',
validations: {
isDateTimeBefore: '2022-10-01 00:00:00'
}
},
{
type: 'divider'
},
{
type: 'input-datetime',
label: 'isDateTimeAfter',
name: 'datetime3',
format: 'YYYY-MM-DD HH:mm:ss',
validations: {
isDateTimeAfter: '2022-10-01 00:00:00'
}
},
{
type: 'divider'
},
{
type: 'input-datetime',
label: 'isDateTimeSameOrBefore',
name: 'datetime4',
format: 'YYYY-MM-DD HH:mm:ss',
validations: {
isDateTimeSameOrBefore: '2022-10-01 00:00:00'
}
},
{
type: 'divider'
},
{
type: 'input-datetime',
label: 'isDateTimeSameOrAfter',
name: 'datetime5',
format: 'YYYY-MM-DD HH:mm:ss',
validations: {
isDateTimeSameOrAfter: '2022-10-01 00:00:00'
}
},
{
type: 'divider'
},
{
type: 'input-datetime',
label: 'isDateTimeBetween',
name: 'datetime6',
format: 'YYYY-MM-DD HH:mm:ss',
validations: {
isDateTimeBetween: [
'2022-10-01 00:00:00',
'2022-10-03 00:00:00',
'second',
'[]'
]
},
validationErrors: {
isDateTimeBetween:
'选择的日期有误,日期必须在 $1 $2 范围内'
}
}
]
},
{
type: 'form',
name: 'form3',
api: '/api/mock2/form/saveForm',
debug: true,
debugConfig: {
levelExpand: 3
},
title: '<b>日期时间相关校验规则(带变量)</b>',
body: [
{
type: 'input-datetime',
label: '开始日期时间',
name: 'startTime',
inputFormat: 'YYYY-MM-DD HH:mm:ss',
format: 'YYYY-MM-DD HH:mm:ss',
required: true,
validations: {
isDateTimeSameOrBefore: '${endTime}'
},
validationErrors: {
isDateTimeSameOrBefore:
'当前日期值不合法,开始时间不能大于结束时间'
}
},
{
type: 'input-datetime',
label: '结束日期时间',
name: 'endTime',
inputFormat: 'YYYY-MM-DD HH:mm:ss',
format: 'YYYY-MM-DD HH:mm:ss',
required: true
}
]
}
]
},
{
name: 'test1',
type: 'input-email',
label: 'Email'
},
{
type: 'divider'
},
{
name: 'url',
type: 'input-url',
label: 'URL'
},
{
type: 'divider'
},
{
name: 'num',
type: 'input-text',
label: '数字',
validations: 'isNumeric'
},
{
type: 'divider'
},
{
name: 'alpha',
type: 'input-text',
label: '字母或数字',
validations: 'isAlphanumeric'
},
{
type: 'divider'
},
{
name: 'int',
type: 'input-text',
label: '整形',
validations: 'isInt'
},
{
type: 'divider'
},
{
name: 'minLength',
type: 'input-text',
label: '长度限制',
validations: 'minLength:2,maxLength:10'
},
{
type: 'divider'
},
{
name: 'min',
type: 'input-text',
label: '数值限制',
validations: 'maximum:10,minimum:2'
},
{
type: 'divider'
},
{
name: 'reg',
type: 'input-text',
label: '正则',
validations: 'matchRegexp:/^abc/',
validationErrors: {
matchRegexp: '请输入abc开头的好么?'
}
},
{
type: 'divider'
},
{
name: 'test2',
type: 'input-text',
label: '服务端验证'
body: [
{
type: 'form',
name: 'form4',
api: '/api/mock2/form/saveForm',
debug: true,
debugConfig: {
levelExpand: 3
},
title: '<b>时间相关校验规则</b>',
data: {
time1: '16:00:00',
time2: '16:00:00',
time3: '16:00:00',
time4: '16:00:01',
time5: '16:00:01',
time6: '15:00:01'
},
body: [
{
type: 'input-time',
label: 'isTimeSame',
name: 'time1',
format: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
validations: {
isTimeSame: '16:00:01'
}
},
{
type: 'divider'
},
{
type: 'input-time',
label: 'isTimeBefore',
name: 'time2',
format: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
validations: {
isTimeBefore: '15:00:00'
}
},
{
type: 'divider'
},
{
type: 'input-time',
label: 'isTimeAfter',
name: 'time3',
format: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
validations: {
isTimeAfter: '16:00:30'
}
},
{
type: 'divider'
},
{
type: 'input-time',
label: 'isTimeSameOrBefore',
name: 'time4',
format: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
validations: {
isTimeSameOrBefore: '16:00:00'
}
},
{
type: 'divider'
},
{
type: 'input-time',
label: 'isTimeSameOrAfter',
name: 'time5',
format: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
validations: {
isTimeSameOrAfter: '16:30:00'
}
},
{
type: 'divider'
},
{
type: 'input-time',
label: 'isTimeBetween',
name: 'time6',
format: 'HH:mm:ss',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
validations: {
isTimeBetween: ['03:00:00', '15:00:00', 'second', '[]']
},
validationErrors: {
isTimeBetween: '选择的时间有误,时间必须在 $1 $2 范围内'
}
}
]
},
{
type: 'form',
name: 'form5',
api: '/api/mock2/form/saveForm',
debug: true,
debugConfig: {
levelExpand: 3
},
title: '<b>时间相关校验规则(带变量)</b>',
body: [
{
type: 'input-time',
label: '开始时间',
name: 'startTime',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
format: 'HH:mm:ss',
validations: {
isRequired: true,
isTimeSameOrBefore: '${endTime}'
},
validationErrors: {
isTimeSameOrBefore:
'当前时间值不合法,开始时间不能大于结束时间 $1'
}
},
{
type: 'input-time',
label: '结束时间',
name: 'endTime',
timeFormat: 'HH:mm:ss',
inputFormat: 'HH:mm:ss',
format: 'HH:mm:ss',
required: true
}
]
}
]
}
]
}

View File

@ -174,7 +174,18 @@ export interface FormBaseControl extends BaseSchemaWithoutType {
maximum?: string;
minLength?: string;
minimum?: string;
isDateTimeSame?: string;
isDateTimeBefore?: string;
isDateTimeAfter?: string;
isDateTimeSameOrBefore?: string;
isDateTimeSameOrAfter?: string;
isDateTimeBetween?: string;
isTimeSame?: string;
isTimeBefore?: string;
isTimeAfter?: string;
isTimeSameOrBefore?: string;
isTimeSameOrAfter?: string;
isTimeBetween?: string;
[propName: string]: any;
};
@ -276,6 +287,78 @@ export interface FormBaseControl extends BaseSchemaWithoutType {
*/
minimum?: number;
/**
*
* @version 2.2.0
*/
isDateTimeSame?: string | string[];
/**
*
* @version 2.2.0
*/
isDateTimeBefore?: string | string[];
/**
*
* @version 2.2.0
*/
isDateTimeAfter?: string | string[];
/**
*
* @version 2.2.0
*/
isDateTimeSameOrBefore?: string | string[];
/**
*
* @version 2.2.0
*/
isDateTimeSameOrAfter?: string | string[];
/**
* ,
* @version 2.2.0
*/
isDateTimeBetween?: string | string[];
/**
*
* @version 2.2.0
*/
isTimeSame?: string | string[];
/**
*
* @version 2.2.0
*/
isTimeBefore?: string | string[];
/**
*
* @version 2.2.0
*/
isTimeAfter?: string | string[];
/**
*
* @version 2.2.0
*/
isTimeSameOrBefore?: string | string[];
/**
*
* @version 2.2.0
*/
isTimeSameOrAfter?: string | string[];
/**
* ,
* @version 2.2.0
*/
isTimeBetween?: string | string[];
[propName: string]: any;
};

View File

@ -1,5 +1,6 @@
import {types, getEnv, flow, isAlive, Instance} from 'mobx-state-tree';
import debounce from 'lodash/debounce';
import toPairs from 'lodash/toPairs';
import {ServiceStore} from './service';
import type {IFormItemStore} from './formItem';
import {Api, ApiObject, fetchOptions, Payload} from '../types';
@ -13,12 +14,14 @@ import {
difference,
isEmpty,
mapObject,
keyToPath
keyToPath,
isObject
} from '../utils/helper';
import isEqual from 'lodash/isEqual';
import flatten from 'lodash/flatten';
import find from 'lodash/find';
import {filter} from '../utils/tpl';
import {isPureVariable} from '../utils/tpl-builtin';
import {normalizeApiResponseData} from '../utils/api';
export const FormStore = ServiceStore.named('FormStore')
@ -543,6 +546,21 @@ export const FormStore = ServiceStore.named('FormStore')
// 先清除组合校验的错误
item.clearError('rules');
/* 日期类校验存在表单项联动的情况,需要在提交前重置校验状态,避免变量更新后联动校验结果未更新 */
if (
item.validated &&
isObject(item.rules) &&
toPairs(item.rules)
.filter(([key, value]) => /^is(Date)?Time/.test(key))
.some(([key, value]) =>
Array.isArray(value)
? value.some(item => isPureVariable(item))
: isPureVariable(value)
)
) {
item.resetValidationStatus();
}
// 验证过,或者是 unique 的表单项或者强制验证或者有远端校验api
if (
!item.validated ||

View File

@ -286,7 +286,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
rules = {
...rules,
isRequired: self.required
isRequired: self.required || rules?.isRequired
};
// todo 这个弄个配置由渲染器自己来决定
@ -1174,6 +1174,11 @@ export const FormItemStore = StoreNode.named('FormItemStore')
!keepErrors && clearError();
}
function resetValidationStatus(tag?: string) {
self.validated = false;
clearError();
}
function openDialog(
schema: any,
data: any,
@ -1232,6 +1237,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
setSubStore,
getSubStore,
reset,
resetValidationStatus,
openDialog,
closeDialog,
changeTmpValue,

View File

@ -1,6 +1,8 @@
import {createObject} from './helper';
import moment from 'moment';
import {filter} from './tpl';
import {isPureVariable, resolveVariableAndFilter} from './tpl-builtin';
import type {MomentInput, unitOfTime, MomentFormatSpecification} from 'moment';
const isExisty = (value: any) => value !== null && value !== undefined;
const isEmpty = (value: any) => value === '';
const makeRegexp = (reg: string | RegExp) => {
@ -94,7 +96,9 @@ export interface ValidateFn {
value: any,
arg1?: any,
arg2?: any,
arg3?: any
arg3?: any,
arg4?: any,
arg5?: any
): boolean;
}
@ -282,6 +286,146 @@ export const validations: {
},
matchRegexp9: function (values, value, regexp) {
return validations.matchRegexp(values, value, regexp);
},
/** ============================ 日期时间相关 ============================= */
isDateTimeSame: (
values,
value: MomentInput,
targetDate: MomentInput,
granularity?: unitOfTime.StartOf
) => {
return moment(value).isSame(moment(targetDate), granularity);
},
isDateTimeBefore: (
values,
value: MomentInput,
targetDate: MomentInput,
granularity?: unitOfTime.StartOf
) => {
return moment(value).isBefore(moment(targetDate), granularity);
},
isDateTimeAfter: (
values,
value: MomentInput,
targetDate: MomentInput,
granularity?: unitOfTime.StartOf
) => {
return moment(value).isAfter(moment(targetDate), granularity);
},
isDateTimeSameOrBefore: (
values,
value: MomentInput,
targetDate: MomentInput,
granularity?: unitOfTime.StartOf
) => {
return moment(value).isSameOrBefore(moment(targetDate), granularity);
},
isDateTimeSameOrAfter: (
values,
value: MomentInput,
targetDate: MomentInput,
granularity?: unitOfTime.StartOf
) => {
return moment(value).isSameOrAfter(moment(targetDate), granularity);
},
isDateTimeBetween: (
values,
value: MomentInput,
lhs: MomentInput,
rhs: MomentInput,
granularity?: unitOfTime.StartOf,
inclusivity?: '()' | '[)' | '(]' | '[]'
) => {
return moment(value).isBetween(
moment(lhs),
moment(rhs),
granularity,
inclusivity
);
},
/** ============================ 时间相关 ============================= */
isTimeSame: (
values,
value: MomentInput,
targetTime: MomentInput,
granularity?: unitOfTime.StartOf,
format?: MomentFormatSpecification
) => {
// 直接使用时间构造的moment object是不合法的所以需要额外指定一下格式
format = format ?? 'hh:mm:ss';
return moment(value, format).isSame(
moment(targetTime, format),
granularity
);
},
isTimeBefore: (
values,
value: MomentInput,
targetTime: MomentInput,
granularity?: unitOfTime.StartOf,
format?: MomentFormatSpecification
) => {
format = format ?? 'hh:mm:ss';
return moment(value, format).isBefore(
moment(targetTime, format),
granularity
);
},
isTimeAfter: (
values,
value: MomentInput,
targetTime: MomentInput,
granularity?: unitOfTime.StartOf,
format?: MomentFormatSpecification
) => {
format = format ?? 'hh:mm:ss';
return moment(value, format).isAfter(
moment(targetTime, format),
granularity
);
},
isTimeSameOrBefore: (
values,
value: MomentInput,
targetTime: MomentInput,
granularity?: unitOfTime.StartOf,
format?: MomentFormatSpecification
) => {
format = format ?? 'hh:mm:ss';
return moment(value, format).isSameOrBefore(
moment(targetTime, format),
granularity
);
},
isTimeSameOrAfter: (
values,
value: MomentInput,
targetTime: MomentInput,
granularity?: unitOfTime.StartOf,
format?: MomentFormatSpecification
) => {
format = format ?? 'hh:mm:ss';
return moment(value, format).isSameOrAfter(
moment(targetTime, format),
granularity
);
},
isTimeBetween: (
values,
value: MomentInput,
lhs: MomentInput,
rhs: MomentInput,
granularity?: unitOfTime.StartOf,
inclusivity?: '()' | '[)' | '(]' | '[]',
format?: MomentFormatSpecification
) => {
format = format ?? 'hh:mm:ss';
return moment(value, format).isBetween(
moment(lhs, format),
moment(rhs, format),
granularity,
inclusivity
);
}
};
@ -322,7 +466,19 @@ export const validateMessages: {
isPhoneNumber: 'validate.isPhoneNumber',
isTelNumber: 'validate.isTelNumber',
isZipcode: 'validate.isZipcode',
isId: 'validate.isId'
isId: 'validate.isId',
isDateTimeSame: 'validate.isDateTimeSame',
isDateTimeBefore: 'validate.isDateTimeBefore',
isDateTimeAfter: 'validate.isDateTimeAfter',
isDateTimeSameOrBefore: 'validate.isDateTimeSameOrBefore',
isDateTimeSameOrAfter: 'validate.isDateTimeSameOrAfter',
isDateTimeBetween: 'validate.isDateTimeBetween',
isTimeSame: 'validate.isTimeSame',
isTimeBefore: 'validate.isTimeBefore',
isTimeAfter: 'validate.isTimeAfter',
isTimeSameOrBefore: 'validate.isTimeSameOrBefore',
isTimeSameOrAfter: 'validate.isTimeSameOrAfter',
isTimeBetween: 'validate.isTimeBetween'
};
export function validate(

View File

@ -712,7 +712,11 @@ export class CustomTimeView extends React.Component<
});
inputs.length && inputs.pop();
const quickLists = [<a onClick={this.selectNowTime}>{__('TimeNow')}</a>];
const quickLists = [
<a key="select-now" onClick={this.selectNowTime}>
{__('TimeNow')}
</a>
];
return (
<>
<div className={cx(timeRangeHeader ? 'TimeRangeHeaderWrapper' : null)}>

View File

@ -281,6 +281,30 @@ register('de-DE', {
'validate.minimum': 'Der Eingabewert ist kleiner als der Mindestwert von $1.',
'validate.minLength': 'Geben Sie weitere Zeichen ein, mindestens $1.',
'validate.notEmptyString': 'Geben Sie nicht nur Leerzeichen ein.',
'validate.isDateTimeSame':
'Der aktuelle Datumswert ist ungültig, bitte geben Sie denselben Datumswert wie $1 ein',
'validate.isDateTimeBefore':
'Der aktuelle Datumswert ist ungültig, bitte geben Sie einen Datumswert vor $1 ein',
'validate.isDateTimeAfter':
'Der aktuelle Datumswert ist ungültig, bitte geben Sie nach $1 einen Datumswert ein',
'validate.isDateTimeSameOrBefore':
'Der aktuelle Datumswert ist ungültig. Bitte geben Sie einen Datumswert ein, der gleich oder älter als $1 ist',
'validate.isDateTimeSameOrAfter':
'Der aktuelle Datumswert ist ungültig. Bitte geben Sie einen Datumswert ein, der gleich oder nach $1 ist',
'validate.isDateTimeBetween':
'Der aktuelle Datumswert ist ungültig, bitte geben Sie einen Datumswert zwischen $1 und $2 ein',
'validate.isTimeSame':
'Der aktuelle Zeitwert ist ungültig, bitte geben Sie denselben Zeitwert wie 1 $ ein',
'validate.isTimeBefore':
'Der aktuelle Zeitwert ist ungültig, bitte geben Sie einen Zeitwert vor $1 ein',
'validate.isTimeAfter':
'Der aktuelle Zeitwert ist ungültig, bitte geben Sie nach $1 einen Zeitwert ein',
'validate.isTimeSameOrBefore':
'Der aktuelle Zeitwert ist ungültig. Bitte geben Sie einen Zeitwert ein, der gleich oder älter als $1 ist',
'validate.isTimeSameOrAfter':
'Der aktuelle Zeitwert ist ungültig. Bitte geben Sie einen Zeitwert ein, der gleich oder nach $1 ist',
'validate.isTimeBetween':
'Der aktuelle Zeitwert ist ungültig, bitte geben Sie einen Zeitwert zwischen $1 und $2 ein',
'validateFailed': 'Fehler bei der Überprüfung',
'Wizard.configError': 'Konfigurationsfehler',
'Wizard.finish': 'Ende',

View File

@ -271,6 +271,30 @@ register('en-US', {
'validate.minimum': 'The input value is lower than the minimum value of $1',
'validate.minLength': 'Please enter more, at least $1 characters.',
'validate.notEmptyString': 'Please do not enter all blank characters',
'validate.isDateTimeSame':
'The current date value is invalid, please enter the same date value as $1',
'validate.isDateTimeBefore':
'The current date value is invalid, please enter a date value before $1',
'validate.isDateTimeAfter':
'The current date value is invalid, please enter a date value after $1',
'validate.isDateTimeSameOrBefore':
'The current date value is invalid, please enter a date value that is the same as or before $1',
'validate.isDateTimeSameOrAfter':
'The current date value is invalid, please enter a date value that is the same as or after $1',
'validate.isDateTimeBetween':
'The current date value is invalid, please enter a date value between $1 and $2',
'validate.isTimeSame':
'The current time value is invalid, please enter the same time value as $1',
'validate.isTimeBefore':
'The current time value is invalid, please enter a time value before $1',
'validate.isTimeAfter':
'The current time value is invalid, please enter a time value after $1',
'validate.isTimeSameOrBefore':
'The current time value is invalid, please enter a time value that is the same as or before $1',
'validate.isTimeSameOrAfter':
'The current time value is invalid, please enter a time value that is the same as or after $1',
'validate.isTimeBetween':
'The current time value is invalid, please enter a time value between $1 and $2',
'validateFailed': 'Validate failed',
'Wizard.configError': 'Config error',
'Wizard.finish': 'Finish',

View File

@ -273,6 +273,23 @@ register('zh-CN', {
'validate.minimum': '当前输入值低于最小值 $1',
'validate.minLength': '请输入更多的内容,至少输入 $1 个字符。',
'validate.notEmptyString': '请不要全输入空白字符',
'validate.isDateTimeSame': '当前日期值不合法,请输入和 $1 相同的日期值',
'validate.isDateTimeBefore': '当前日期值不合法,请输入 $1 之前的日期值',
'validate.isDateTimeAfter': '当前日期值不合法,请输入 $1 之后的日期值',
'validate.isDateTimeSameOrBefore':
'当前日期值不合法,请输入和 $1 相同或之前的日期值',
'validate.isDateTimeSameOrAfter':
'当前日期值不合法,请输入和 $1 相同或之后的日期值',
'validate.isDateTimeBetween':
'当前日期值不合法,请输入 $1 和 $2 之间的日期值',
'validate.isTimeSame': '当前时间值不合法,请输入和 $1 相同的时间值',
'validate.isTimeBefore': '当前时间值不合法,请输入 $1 之前的时间值',
'validate.isTimeAfter': '当前时间值不合法,请输入 $1 之后的时间值',
'validate.isTimeSameOrBefore':
'当前时间值不合法,请输入和 $1 相同或之前的时间值',
'validate.isTimeSameOrAfter':
'当前时间值不合法,请输入和 $1 相同或之后的时间值',
'validate.isTimeBetween': '当前时间值不合法,请输入 $1 和 $2 之间的时间值',
'validateFailed': '表单验证失败',
'Wizard.configError': '配置错误',
'Wizard.finish': '完成',

View File

@ -907,3 +907,435 @@ test('validation:multiplestr2rules:noSlash', () => {
matchRegexp2: ['123$']
});
});
/** ============================ 日期时间相关 ============================= */
describe('validation:DateTime', () => {
describe('validation: isDateTimeSame', () => {
const targetDate = '2022-10-01 00:00:00';
test('validation: isDateTimeSame:valid', () => {
expect(
validate(
targetDate,
{},
{
isDateTimeSame: targetDate
}
)
).toMatchObject([]);
});
test('validation: isDateTimeSame:inValid', () => {
expect(
validate(
'2022-10-01 12:00:00',
{},
{
isDateTimeSame: targetDate
}
)
).toMatchObject([
{msg: 'validate.isDateTimeSame', rule: 'isDateTimeSame'}
]);
});
});
describe('validation: isDateTimeBefore', () => {
const targetDate = '2022-10-01 00:00:00';
test('validation: isDateTimeBefore:valid', () => {
expect(
validate(
'2022-08-25 10:00:00',
{},
{
isDateTimeBefore: targetDate
}
)
).toMatchObject([]);
});
test('validation: isDateTimeBefore:inValid', () => {
expect(
validate(
'2022-10-01 00:00:30',
{},
{
isDateTimeBefore: targetDate
}
)
).toMatchObject([
{msg: 'validate.isDateTimeBefore', rule: 'isDateTimeBefore'}
]);
});
});
describe('validation: isDateTimeAfter', () => {
const targetDate = '2022-10-01 00:00:00';
test('validation: isDateTimeAfter:valid', () => {
expect(
validate(
'2022-10-01 00:00:01',
{},
{
isDateTimeAfter: targetDate
}
)
).toMatchObject([]);
});
test('validation: isDateTimeAfter:inValid', () => {
expect(
validate(
'2022-09-30 23:59:59',
{},
{
isDateTimeAfter: targetDate
}
)
).toMatchObject([
{msg: 'validate.isDateTimeAfter', rule: 'isDateTimeAfter'}
]);
});
});
describe('validation: isDateTimeSameOrBefore', () => {
const targetDate = '2022-10-01 00:00:00';
test('validation: isDateTimeSameOrBefore:same valid', () => {
expect(
validate(
targetDate,
{},
{
isDateTimeSameOrBefore: targetDate
}
)
).toMatchObject([]);
});
test('validation: isDateTimeSameOrBefore:before valid', () => {
expect(
validate(
'2022-09-30 23:59:59',
{},
{
isDateTimeSameOrBefore: targetDate
}
)
).toMatchObject([]);
});
test('validation: isDateTimeSameOrBefore:inValid', () => {
expect(
validate(
'2022-10-01 00:00:10',
{},
{
isDateTimeSameOrBefore: targetDate
}
)
).toMatchObject([
{msg: 'validate.isDateTimeSameOrBefore', rule: 'isDateTimeSameOrBefore'}
]);
});
});
describe('validation: isDateTimeSameOrAfter', () => {
const targetDate = '2022-10-01 00:00:00';
test('validation: isDateTimeSameOrAfter:same valid', () => {
expect(
validate(
targetDate,
{},
{
isDateTimeSameOrAfter: targetDate
}
)
).toMatchObject([]);
});
test('validation: isDateTimeSameOrAfter:after valid', () => {
expect(
validate(
'2022-10-01 00:10:00',
{},
{
isDateTimeSameOrAfter: targetDate
}
)
).toMatchObject([]);
});
test('validation: isDateTimeSameOrAfter:inValid', () => {
expect(
validate(
'2022-09-30 23:59:59',
{},
{
isDateTimeSameOrAfter: targetDate
}
)
).toMatchObject([
{msg: 'validate.isDateTimeSameOrAfter', rule: 'isDateTimeSameOrAfter'}
]);
});
});
describe('validation: isDateTimeBetween', () => {
const lhs = '2022-10-01 00:00:00';
const rhs = '2022-10-03 00:00:00';
test('validation: isDateTimeBetween:default valid', () => {
expect(
validate(
'2022-10-01 00:00:01',
{},
{
isDateTimeBetween: [lhs, rhs]
}
)
).toMatchObject([]);
});
test('validation: isDateTimeBetween:inclusivity with () endpoints of the interval', () => {
expect(
validate(
'2022-10-01 00:00:00',
{},
{
isDateTimeBetween: [lhs, rhs, 'millisecond', '()']
}
)
).toMatchObject([
{msg: 'validate.isDateTimeBetween', rule: 'isDateTimeBetween'}
]);
});
test('validation: isDateTimeBetween:inclusivity with [] endpoints of the interval', () => {
expect(
validate(
'2022-10-01 00:00:00',
{},
{
isDateTimeBetween: [lhs, rhs, 'millisecond', '[]']
}
)
).toMatchObject([]);
});
});
});
/** ============================ 时间相关 ============================= */
describe('validation:Time', () => {
describe('validation: isTimeSame', () => {
const targetTime = '00:00:00';
test('validation: isTimeSame:valid', () => {
expect(
validate(
targetTime,
{},
{
isTimeSame: targetTime
}
)
).toMatchObject([]);
});
test('validation: isTimeSame:inValid', () => {
expect(
validate(
'12:00:00',
{},
{
isTimeSame: targetTime
}
)
).toMatchObject([{msg: 'validate.isTimeSame', rule: 'isTimeSame'}]);
});
});
describe('validation: isTimeBefore', () => {
const targetTime = '15:00:00';
test('validation: isTimeBefore:valid', () => {
expect(
validate(
'10:00:00',
{},
{
isTimeBefore: targetTime
}
)
).toMatchObject([]);
});
test('validation: isTimeBefore:inValid', () => {
expect(
validate(
'15:00:30',
{},
{
isTimeBefore: targetTime
}
)
).toMatchObject([{msg: 'validate.isTimeBefore', rule: 'isTimeBefore'}]);
});
});
describe('validation: isTimeAfter', () => {
const targetTime = '15:00:00';
test('validation: isTimeAfter:valid', () => {
expect(
validate(
'15:30:01',
{},
{
isTimeAfter: targetTime
}
)
).toMatchObject([]);
});
test('validation: isTimeAfter:inValid', () => {
expect(
validate(
'12:40:01',
{},
{
isTimeAfter: targetTime
}
)
).toMatchObject([{msg: 'validate.isTimeAfter', rule: 'isTimeAfter'}]);
});
});
describe('validation: isTimeSameOrBefore', () => {
const targetTime = '12:00:00';
test('validation: isTimeSameOrBefore:same valid', () => {
expect(
validate(
targetTime,
{},
{
isTimeSameOrBefore: targetTime
}
)
).toMatchObject([]);
});
test('validation: isTimeSameOrBefore:before valid', () => {
expect(
validate(
'11:59:59',
{},
{
isTimeSameOrBefore: targetTime
}
)
).toMatchObject([]);
});
test('validation: isTimeSameOrBefore:inValid', () => {
expect(
validate(
'12:00:01',
{},
{
isTimeSameOrBefore: targetTime
}
)
).toMatchObject([
{msg: 'validate.isTimeSameOrBefore', rule: 'isTimeSameOrBefore'}
]);
});
});
describe('validation: isTimeSameOrAfter', () => {
const targetTime = '12:00:00';
test('validation: isTimeSameOrAfter:same valid', () => {
expect(
validate(
targetTime,
{},
{
isTimeSameOrAfter: targetTime
}
)
).toMatchObject([]);
});
test('validation: isTimeSameOrAfter:after valid', () => {
expect(
validate(
'23:00:00',
{},
{
isTimeSameOrAfter: targetTime
}
)
).toMatchObject([]);
});
test('validation: isTimeSameOrAfter:inValid', () => {
expect(
validate(
'08:00:00',
{},
{
isTimeSameOrAfter: targetTime
}
)
).toMatchObject([
{msg: 'validate.isTimeSameOrAfter', rule: 'isTimeSameOrAfter'}
]);
});
});
describe('validation: isTimeBetween', () => {
const lhs = '06:00:00';
const rhs = '18:00:00';
test('validation: isTimeBetween:default valid', () => {
expect(
validate(
'12:00:00',
{},
{
isTimeBetween: [lhs, rhs]
}
)
).toMatchObject([]);
});
test('validation: isTimeBetween:inclusivity with () endpoints of the interval', () => {
expect(
validate(
'06:00:00',
{},
{
isTimeBetween: [lhs, rhs, 'millisecond', '()']
}
)
).toMatchObject([{msg: 'validate.isTimeBetween', rule: 'isTimeBetween'}]);
});
test('validation: isTimeBetween:inclusivity with [] endpoints of the interval', () => {
expect(
validate(
'18:00:00',
{},
{
isTimeBetween: [lhs, rhs, 'millisecond', '[]']
}
)
).toMatchObject([]);
});
});
});