fix(client): improve datepicker component, date with time zone, gmt support
This commit is contained in:
parent
7a9bab9bab
commit
1c03fbb853
@ -23,6 +23,14 @@ export const datetime: IField = {
|
|||||||
properties: {
|
properties: {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
...dateTimeProps,
|
...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: {
|
filterable: {
|
||||||
operators: operators.datetime,
|
operators: operators.datetime,
|
||||||
|
@ -540,4 +540,5 @@ export default {
|
|||||||
"Field value changes": "Field value changes",
|
"Field value changes": "Field value changes",
|
||||||
"One to one (has one)": "One to one (has one)",
|
"One to one (has one)": "One to one (has one)",
|
||||||
"One to one (belongs to)": "One to one (belongs to)",
|
"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",
|
||||||
}
|
}
|
||||||
|
@ -595,5 +595,6 @@ export default {
|
|||||||
"Select Field": "选择字段",
|
"Select Field": "选择字段",
|
||||||
"Field value changes": "变更记录",
|
"Field value changes": "变更记录",
|
||||||
"One to one (has one)": "一对一(has one)",
|
"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": "所有用户使用同一时区 (格林尼治标准时间)",
|
||||||
}
|
}
|
||||||
|
@ -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 { isArr } from '@formily/shared';
|
||||||
import type {
|
import type {
|
||||||
DatePickerProps as AntdDatePickerProps,
|
DatePickerProps as AntdDatePickerProps,
|
||||||
@ -7,7 +7,7 @@ import type {
|
|||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getDefaultFormat } from './util';
|
import { getDefaultFormat, str2moment } from './util';
|
||||||
|
|
||||||
type Composed = {
|
type Composed = {
|
||||||
DatePicker: React.FC<AntdDatePickerProps>;
|
DatePicker: React.FC<AntdDatePickerProps>;
|
||||||
@ -22,8 +22,9 @@ ReadPretty.DatePicker = (props: any) => {
|
|||||||
}
|
}
|
||||||
const prefixCls = usePrefixCls('description-date-picker', props);
|
const prefixCls = usePrefixCls('description-date-picker', props);
|
||||||
const getLabels = () => {
|
const getLabels = () => {
|
||||||
const d = moment(props.value);
|
const format = getDefaultFormat(props) as string;
|
||||||
const labels = formatMomentValue(d.isValid() ? d : null, getDefaultFormat(props), props.placeholder);
|
const m = str2moment(props.value, props);
|
||||||
|
const labels = moment.isMoment(m) ? m.format(format) : '';
|
||||||
return isArr(labels) ? labels.join('~') : labels;
|
return isArr(labels) ? labels.join('~') : labels;
|
||||||
};
|
};
|
||||||
return <div className={cls(prefixCls, props.className)}>{getLabels()}</div>;
|
return <div className={cls(prefixCls, props.className)}>{getLabels()}</div>;
|
||||||
@ -31,8 +32,13 @@ ReadPretty.DatePicker = (props: any) => {
|
|||||||
|
|
||||||
ReadPretty.DateRangePicker = (props: any) => {
|
ReadPretty.DateRangePicker = (props: any) => {
|
||||||
const prefixCls = usePrefixCls('description-text', props);
|
const prefixCls = usePrefixCls('description-text', props);
|
||||||
|
const format = getDefaultFormat(props);
|
||||||
const getLabels = () => {
|
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 isArr(labels) ? labels.join('~') : labels;
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -17,6 +17,7 @@ const schema = {
|
|||||||
dateFormat: 'YYYY/MM/DD',
|
dateFormat: 'YYYY/MM/DD',
|
||||||
showTime: true,
|
showTime: true,
|
||||||
},
|
},
|
||||||
|
// default: '2022-11-22',
|
||||||
'x-reactions': {
|
'x-reactions': {
|
||||||
target: '*(read1,read2)',
|
target: '*(read1,read2)',
|
||||||
fulfill: {
|
fulfill: {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* title: DatePicker (with timezone)
|
* title: DatePicker (GMT)
|
||||||
*/
|
*/
|
||||||
import { FormItem } from '@formily/antd';
|
import { FormItem } from '@formily/antd';
|
||||||
import { DatePicker, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
import { DatePicker, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
@ -16,7 +16,7 @@ const schema = {
|
|||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
dateFormat: 'YYYY/MM/DD',
|
dateFormat: 'YYYY/MM/DD',
|
||||||
showTime: true,
|
showTime: true,
|
||||||
withTz: true,
|
gmt: true,
|
||||||
},
|
},
|
||||||
default: '2022-06-04T15:00:00.000Z',
|
default: '2022-06-04T15:00:00.000Z',
|
||||||
'x-reactions': {
|
'x-reactions': {
|
||||||
@ -36,7 +36,8 @@ const schema = {
|
|||||||
'x-component': 'DatePicker',
|
'x-component': 'DatePicker',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
dateFormat: 'YYYY/MM/DD',
|
dateFormat: 'YYYY/MM/DD',
|
||||||
// showTime: true,
|
showTime: true,
|
||||||
|
gmt: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
read2: {
|
read2: {
|
||||||
|
@ -9,11 +9,11 @@ group:
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### DatePicker
|
### Basic
|
||||||
|
|
||||||
<code src="./demos/demo1.tsx" />
|
<code src="./demos/demo1.tsx" />
|
||||||
|
|
||||||
### DatePicker (with timezone)
|
### DatePicker (GMT)
|
||||||
|
|
||||||
<code src="./demos/demo3.tsx" />
|
<code src="./demos/demo3.tsx" />
|
||||||
|
|
||||||
|
@ -1,7 +1,102 @@
|
|||||||
import { isArr, isEmpty, isFn } from '@formily/shared';
|
|
||||||
import type { DatePickerProps } from 'antd/lib/date-picker';
|
import type { DatePickerProps } from 'antd/lib/date-picker';
|
||||||
import moment from 'moment';
|
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 }) => {
|
export const getDefaultFormat = (props: DatePickerProps & { dateFormat: string; timeFormat: string }) => {
|
||||||
if (props.format) {
|
if (props.format) {
|
||||||
return 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';
|
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 () {
|
export const mapDateFormat = function () {
|
||||||
return (props: any) => {
|
return (props: any) => {
|
||||||
const format = getDefaultFormat(props) as any;
|
const format = getDefaultFormat(props) as any;
|
||||||
@ -95,14 +126,10 @@ export const mapDateFormat = function () {
|
|||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
format: format,
|
format: format,
|
||||||
value: momentable(props.value, format === 'YYYY-wo' ? 'YYYY-w' : format),
|
value: str2moment(props.value, props),
|
||||||
onChange: (value: moment.Moment | moment.Moment[]) => {
|
onChange: (value: moment.Moment | moment.Moment[]) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
if (props.withTz) {
|
onChange(moment2str(value, props));
|
||||||
onChange(momentToString(value));
|
|
||||||
} else {
|
|
||||||
onChange(formatMomentValue(value, format));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user