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: {
|
||||
...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,
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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": "所有用户使用同一时区 (格林尼治标准时间)",
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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',
|
||||
showTime: true,
|
||||
},
|
||||
// default: '2022-11-22',
|
||||
'x-reactions': {
|
||||
target: '*(read1,read2)',
|
||||
fulfill: {
|
||||
|
@ -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: {
|
||||
|
@ -9,11 +9,11 @@ group:
|
||||
|
||||
## Examples
|
||||
|
||||
### DatePicker
|
||||
### Basic
|
||||
|
||||
<code src="./demos/demo1.tsx" />
|
||||
|
||||
### DatePicker (with timezone)
|
||||
### DatePicker (GMT)
|
||||
|
||||
<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 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));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user