fix(client): improve datepicker component, date with time zone, gmt support

This commit is contained in:
chenos 2022-06-16 22:50:09 +08:00
parent 7a9bab9bab
commit 1c03fbb853
9 changed files with 206 additions and 82 deletions

View File

@ -23,6 +23,14 @@ export const datetime: IField = {
properties: {
...defaultProps,
...dateTimeProps,
'uiSchema.x-component-props.gmt': {
type: 'boolean',
title: '{{t("GMT")}}',
'x-component': 'Checkbox',
'x-content': '{{t("Use the same time zone (GMT) for all users")}}',
'x-decorator': 'FormItem',
default: false,
},
},
filterable: {
operators: operators.datetime,

View File

@ -540,4 +540,5 @@ export default {
"Field value changes": "Field value changes",
"One to one (has one)": "One to one (has one)",
"One to one (belongs to)": "One to one (belongs to)",
"Use the same time zone (GMT) for all users": "Use the same time zone (GMT) for all users",
}

View File

@ -595,5 +595,6 @@ export default {
"Select Field": "选择字段",
"Field value changes": "变更记录",
"One to one (has one)": "一对一has one",
"One to one (belongs to)": "一对一belongs to"
"One to one (belongs to)": "一对一belongs to",
"Use the same time zone (GMT) for all users": "所有用户使用同一时区 (格林尼治标准时间)",
}

View File

@ -1,4 +1,4 @@
import { formatMomentValue, usePrefixCls } from '@formily/antd/lib/__builtins__';
import { usePrefixCls } from '@formily/antd/lib/__builtins__';
import { isArr } from '@formily/shared';
import type {
DatePickerProps as AntdDatePickerProps,
@ -7,7 +7,7 @@ import type {
import cls from 'classnames';
import moment from 'moment';
import React from 'react';
import { getDefaultFormat } from './util';
import { getDefaultFormat, str2moment } from './util';
type Composed = {
DatePicker: React.FC<AntdDatePickerProps>;
@ -22,8 +22,9 @@ ReadPretty.DatePicker = (props: any) => {
}
const prefixCls = usePrefixCls('description-date-picker', props);
const getLabels = () => {
const d = moment(props.value);
const labels = formatMomentValue(d.isValid() ? d : null, getDefaultFormat(props), props.placeholder);
const format = getDefaultFormat(props) as string;
const m = str2moment(props.value, props);
const labels = moment.isMoment(m) ? m.format(format) : '';
return isArr(labels) ? labels.join('~') : labels;
};
return <div className={cls(prefixCls, props.className)}>{getLabels()}</div>;
@ -31,8 +32,13 @@ ReadPretty.DatePicker = (props: any) => {
ReadPretty.DateRangePicker = (props: any) => {
const prefixCls = usePrefixCls('description-text', props);
const format = getDefaultFormat(props);
const getLabels = () => {
const labels = formatMomentValue(props.value, props.format, props.placeholder);
const m = str2moment(props.value, props);
if (!m) {
return '';
}
const labels = m.map(m => m.format(format));
return isArr(labels) ? labels.join('~') : labels;
};
return (

View File

@ -0,0 +1,79 @@
import moment from 'moment';
import { moment2str, str2moment } from '../util';
describe('str2moment', () => {
describe('string value', () => {
test('gmt date', async () => {
const m = str2moment('2022-06-21T00:00:00.000Z', { gmt: true });
expect(m.format('YYYY-MM-DD HH:mm:ss')).toBe('2022-06-21 00:00:00');
});
test('local date', async () => {
const m = str2moment('2022-06-21T00:00:00.000Z');
expect(m.toISOString()).toBe('2022-06-21T00:00:00.000Z');
});
test('value is null', async () => {
const m = str2moment(null);
expect(m).toBeNull();
});
test('picker is month', async () => {
const m = str2moment('2022-06-01T00:00:00.000Z', { picker: 'month' });
expect(m.format('YYYY-MM-DD HH:mm:ss')).toBe('2022-06-01 00:00:00');
});
});
describe('array value', () => {
test('gmt date', async () => {
const arr = str2moment(['2022-06-21T00:00:00.000Z', '2022-06-21T00:00:00.000Z'], { gmt: true });
for (const m of arr) {
expect(m.format('YYYY-MM-DD HH:mm:ss')).toBe('2022-06-21 00:00:00');
}
});
test('local date', async () => {
const arr = str2moment(['2022-06-21T00:00:00.000Z', '2022-06-21T00:00:00.000Z']);
for (const m of arr) {
expect(m.toISOString()).toBe('2022-06-21T00:00:00.000Z');
}
});
});
});
describe('moment2str', () => {
test('gmt date', () => {
const m = moment('2022-06-21 10:10:00');
const str = moment2str(m, { showTime: true, gmt: true });
expect(str).toBe('2022-06-21T10:10:00.000Z');
});
test('gmt date only', () => {
const m = moment('2022-06-21 10:10:00');
const str = moment2str(m);
expect(str).toBe('2022-06-21T00:00:00.000Z');
});
test('with time', () => {
const m = moment('2022-06-21 10:10:00');
const str = moment2str(m, { showTime: true });
expect(str).toBe(m.toISOString());
});
test('picker is year', () => {
const m = moment('2022-06-21 10:10:00');
const str = moment2str(m, { picker: 'year' });
expect(str).toBe('2022-01-01T00:00:00.000Z');
});
test('picker is month', () => {
const m = moment('2022-06-21 10:10:00');
const str = moment2str(m, { picker: 'month' });
expect(str).toBe('2022-06-01T00:00:00.000Z');
});
test('value is null', async () => {
const m = moment2str(null);
expect(m).toBeNull();
});
});

View File

@ -17,6 +17,7 @@ const schema = {
dateFormat: 'YYYY/MM/DD',
showTime: true,
},
// default: '2022-11-22',
'x-reactions': {
target: '*(read1,read2)',
fulfill: {

View File

@ -1,5 +1,5 @@
/**
* title: DatePicker (with timezone)
* title: DatePicker (GMT)
*/
import { FormItem } from '@formily/antd';
import { DatePicker, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
@ -16,7 +16,7 @@ const schema = {
'x-component-props': {
dateFormat: 'YYYY/MM/DD',
showTime: true,
withTz: true,
gmt: true,
},
default: '2022-06-04T15:00:00.000Z',
'x-reactions': {
@ -36,7 +36,8 @@ const schema = {
'x-component': 'DatePicker',
'x-component-props': {
dateFormat: 'YYYY/MM/DD',
// showTime: true,
showTime: true,
gmt: true,
},
},
read2: {

View File

@ -9,11 +9,11 @@ group:
## Examples
### DatePicker
### Basic
<code src="./demos/demo1.tsx" />
### DatePicker (with timezone)
### DatePicker (GMT)
<code src="./demos/demo3.tsx" />

View File

@ -1,7 +1,102 @@
import { isArr, isEmpty, isFn } from '@formily/shared';
import type { DatePickerProps } from 'antd/lib/date-picker';
import moment from 'moment';
const toGmt = (value: moment.Moment | moment.Moment[]) => {
if (!value) {
return value;
}
if (Array.isArray(value)) {
return value.map((val) => `${val.format('YYYY-MM-DD')}T${val.format('HH:mm:ss.SSS')}Z`);
}
if (moment.isMoment(value)) {
return `${value.format('YYYY-MM-DD')}T${value.format('HH:mm:ss.SSS')}Z`;
}
};
const toStringByPicker = (value, picker) => {
if (picker === 'year') {
return value.format('YYYY') + '-01-01T00:00:00.000Z';
}
if (picker === 'month') {
return value.format('YYYY-MM') + '-01T00:00:00.000Z';
}
if (picker === 'quarter') {
return value.format('YYYY-MM') + '-01T00:00:00.000Z';
}
if (picker === 'week') {
return value.format('YYYY-MM-DD') + 'T00:00:00.000Z';
}
return value.format('YYYY-MM-DD') + 'T00:00:00.000Z';
};
const toGmtByPicker = (value: moment.Moment | moment.Moment[], picker?: any) => {
if (!value) {
return value;
}
if (Array.isArray(value)) {
return value.map((val) => toStringByPicker(val, picker));
}
if (moment.isMoment(value)) {
return toStringByPicker(value, picker);
}
};
const toLocal = (value: moment.Moment | moment.Moment[]) => {
if (!value) {
return value;
}
if (Array.isArray(value)) {
return value.map((val) => val.toISOString());
}
if (moment.isMoment(value)) {
return value.toISOString();
}
};
export interface Str2momentOptions {
gmt?: boolean;
picker?: 'year' | 'month' | 'week' | 'quarter';
}
const toMoment = (val: any, options?: Str2momentOptions) => {
if (moment.isMoment(val)) {
return val;
}
const { gmt, picker } = options;
if (gmt || picker) {
val = val.replace('T', ' ').replace('Z', '');
return moment(val);
}
return moment(val);
};
export const str2moment = (value?: string | string[], options: Str2momentOptions = {}): any => {
return Array.isArray(value)
? value.map((val) => {
return toMoment(val, options);
})
: value
? toMoment(value, options)
: value;
};
export interface Moment2strOptions {
showTime?: boolean;
gmt?: boolean;
picker?: 'year' | 'month' | 'week' | 'quarter';
}
export const moment2str = (value?: moment.Moment | moment.Moment[], options: Moment2strOptions = {}) => {
const { showTime, gmt, picker } = options;
if (!value) {
return value;
}
if (showTime) {
return gmt ? toGmt(value) : toLocal(value);
}
return toGmtByPicker(value, picker);
};
export const getDefaultFormat = (props: DatePickerProps & { dateFormat: string; timeFormat: string }) => {
if (props.format) {
return props.format;
@ -24,70 +119,6 @@ export const getDefaultFormat = (props: DatePickerProps & { dateFormat: string;
return props['showTime'] ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD';
};
const toMoment = (val, format) => {
if (moment.isMoment(val)) {
return val;
}
if (typeof val === 'string' && val.includes('T')) {
return moment(val);
}
return moment(val, format);
};
export const momentable = (value: any, format?: string) => {
return Array.isArray(value)
? value.map((val) => {
return toMoment(val, format);
})
: value
? toMoment(value, format)
: value;
};
export const formatMomentValue = (value: any, format: any, placeholder?: string): string | string[] => {
const formatDate = (date: any, format: any, i = 0) => {
if (!date) return placeholder;
if (isArr(format)) {
const _format = format[i];
if (isFn(_format)) {
return _format(date);
}
if (isEmpty(_format)) {
return date;
}
return moment(date).format(_format);
} else {
if (isFn(format)) {
return format(date);
}
if (isEmpty(format)) {
return date;
}
return moment(date).format(format);
}
};
if (isArr(value)) {
return value.map((val, index) => {
return formatDate(val, format, index);
});
} else {
return value ? formatDate(value, format) : value || placeholder;
}
};
const momentToString = (value: moment.Moment | moment.Moment[]) => {
if (!value) {
return value;
}
if (Array.isArray(value)) {
return value.map(val => val.toISOString());
}
if (moment.isMoment(value)) {
return value.toISOString();
}
}
export const mapDateFormat = function () {
return (props: any) => {
const format = getDefaultFormat(props) as any;
@ -95,14 +126,10 @@ export const mapDateFormat = function () {
return {
...props,
format: format,
value: momentable(props.value, format === 'YYYY-wo' ? 'YYYY-w' : format),
value: str2moment(props.value, props),
onChange: (value: moment.Moment | moment.Moment[]) => {
if (onChange) {
if (props.withTz) {
onChange(momentToString(value));
} else {
onChange(formatMomentValue(value, format));
}
onChange(moment2str(value, props));
}
},
};