refactor: date field UI supports configuration formatting (#2241)

* refactor: support format in datetime field

* refactor: support format config  in datetime field

* fix: datetime field support dateformat config

* fix: datetime field support dateformat config

* refactor: table column support date format config

* refactor: table column support date format config

* refactor: table column support date format config

* refactor: code improve

* refactor: code improve

* fix: merge bug

* fix: merge bug

* style: style improve

* Update index.tsx

* refactor: code improve

* refactor: code improve

* refactor: code improve

* refactor: code improve

* refactor: code improve

* refactor: code improve

* refactor: code improve

* refactor: locale improve

* refactor: locale improve

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
katherinehhh 2023-07-25 13:51:50 +08:00 committed by GitHub
parent df4e6de4dc
commit 137e3eb171
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 273 additions and 12 deletions

View File

@ -707,5 +707,6 @@ export default {
"Current form": "Current form", "Current form": "Current form",
"Current object":"Current object", "Current object":"Current object",
"Linkage with form fields":"Linkage with form fields", "Linkage with form fields":"Linkage with form fields",
"Allow add new, update and delete actions":"Allow add new, update and delete actions" "Allow add new, update and delete actions":"Allow add new, update and delete actions",
"Date display format":"Date display format"
}; };

View File

@ -618,5 +618,6 @@ export default {
"Current form":"現在のフォーム", "Current form":"現在のフォーム",
"Current object":"現在のオブジェクト", "Current object":"現在のオブジェクト",
"Linkage with form fields":"フォームデータから連動", "Linkage with form fields":"フォームデータから連動",
"Allow add new, update and delete actions":"削除変更操作の許可" "Allow add new, update and delete actions":"削除変更操作の許可",
"Date display format":"日付表示形式"
} }

View File

@ -792,5 +792,6 @@ export default {
"Copy into the form and continue to fill in": "复制到表单并继续填写", "Copy into the form and continue to fill in": "复制到表单并继续填写",
"Linkage with form fields":"从表单字段联动", "Linkage with form fields":"从表单字段联动",
"Failed to load plugin": "插件加载失败", "Failed to load plugin": "插件加载失败",
"Allow add new, update and delete actions":"允许增删改操作" "Allow add new, update and delete actions":"允许增删改操作",
"Date display format":"日期显示格式"
} }

View File

@ -1,7 +1,7 @@
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { ArrayCollapse, ArrayItems, FormLayout, FormItem as Item } from '@formily/antd-v5'; import { ArrayCollapse, ArrayItems, FormItem as Item, FormLayout } from '@formily/antd-v5';
import { Field } from '@formily/core'; import { Field } from '@formily/core';
import { ISchema, Schema, observer, useField, useFieldSchema } from '@formily/react'; import { ISchema, observer, Schema, useField, useFieldSchema } from '@formily/react';
import { dayjs } from '@nocobase/utils/client'; import { dayjs } from '@nocobase/utils/client';
import { Select } from 'antd'; import { Select } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
@ -20,9 +20,9 @@ import {
} from '../../../collection-manager'; } from '../../../collection-manager';
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields'; import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
import { GeneralSchemaItems } from '../../../schema-items/GeneralSchemaItems'; import { GeneralSchemaItems } from '../../../schema-items/GeneralSchemaItems';
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings'; import { GeneralSchemaDesigner, isPatternDisabled, isShowDefaultValue, SchemaSettings } from '../../../schema-settings';
import { VariableInput } from '../../../schema-settings/VariableInput/VariableInput';
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch'; import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
import { VariableInput } from '../../../schema-settings/VariableInput/VariableInput';
import { isVariable, parseVariables, useVariablesCtx } from '../../common/utils/uitls'; import { isVariable, parseVariables, useVariablesCtx } from '../../common/utils/uitls';
import { SchemaComponent } from '../../core'; import { SchemaComponent } from '../../core';
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks'; import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
@ -175,6 +175,8 @@ FormItem.Designer = function Designer() {
const isPickerMode = fieldSchema['x-component-props']?.mode === 'Picker'; const isPickerMode = fieldSchema['x-component-props']?.mode === 'Picker';
const showFieldMode = isAssociationField && fieldModeOptions && !isTableField; const showFieldMode = isAssociationField && fieldModeOptions && !isTableField;
const showModeSelect = showFieldMode && isPickerMode; const showModeSelect = showFieldMode && isPickerMode;
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField.interface);
return ( return (
<GeneralSchemaDesigner> <GeneralSchemaDesigner>
<GeneralSchemaItems /> <GeneralSchemaItems />
@ -840,6 +842,7 @@ FormItem.Designer = function Designer() {
}} }}
/> />
)} )}
{isDateField && <SchemaSettings.DataFormat fieldSchema={fieldSchema} />}
{collectionField && <SchemaSettings.Divider />} {collectionField && <SchemaSettings.Divider />}
<SchemaSettings.Remove <SchemaSettings.Remove
key="remove" key="remove"

View File

@ -7,7 +7,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useFormBlockContext } from '../../../block-provider'; import { useFormBlockContext } from '../../../block-provider';
import { useCollection, useCollectionManager } from '../../../collection-manager'; import { useCollection, useCollectionManager } from '../../../collection-manager';
import { SchemaSettings, isPatternDisabled } from '../../../schema-settings'; import { isPatternDisabled, SchemaSettings } from '../../../schema-settings';
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks'; import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
import { useOperatorList } from '../filter/useOperators'; import { useOperatorList } from '../filter/useOperators';
import { isFileCollection } from './FormItem'; import { isFileCollection } from './FormItem';

View File

@ -1,13 +1,13 @@
import { ISchema, useField, useFieldSchema } from '@formily/react'; import { ISchema, useField, useFieldSchema } from '@formily/react';
import React from 'react';
import { set } from 'lodash'; import { set } from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useCollectionManager, useCollectionFilterOptions } from '../../../collection-manager'; import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled } from '../../../schema-settings'; import { GeneralSchemaDesigner, isPatternDisabled, SchemaSettings } from '../../../schema-settings';
import { useCompile, useDesignable } from '../../hooks'; import { useCompile, useDesignable } from '../../hooks';
import { useAssociationFieldContext } from '../association-field/hooks'; import { useAssociationFieldContext } from '../association-field/hooks';
import { FilterDynamicComponent } from './FilterDynamicComponent';
import { removeNullCondition } from '../filter'; import { removeNullCondition } from '../filter';
import { FilterDynamicComponent } from './FilterDynamicComponent';
const useLabelFields = (collectionName?: any) => { const useLabelFields = (collectionName?: any) => {
// 需要在组件顶层调用 // 需要在组件顶层调用
@ -44,6 +44,7 @@ export const TableColumnDesigner = (props) => {
const { currentMode, field: tableField } = useAssociationFieldContext(); const { currentMode, field: tableField } = useAssociationFieldContext();
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {}; const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
const dataSource = useCollectionFilterOptions(collectionField?.target); const dataSource = useCollectionFilterOptions(collectionField?.target);
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField.interface);
let readOnlyMode = 'editable'; let readOnlyMode = 'editable';
if (fieldSchema['x-disabled'] === true) { if (fieldSchema['x-disabled'] === true) {
readOnlyMode = 'readonly'; readOnlyMode = 'readonly';
@ -323,6 +324,8 @@ export const TableColumnDesigner = (props) => {
}} }}
/> />
)} )}
{isDateField && <SchemaSettings.DataFormat fieldSchema={fieldSchema} />}
<SchemaSettings.Divider /> <SchemaSettings.Divider />
<SchemaSettings.Remove <SchemaSettings.Remove
removeParentsIfNoChildren={!isSubTableColumn} removeParentsIfNoChildren={!isSubTableColumn}

View File

@ -0,0 +1,105 @@
import { css } from '@emotion/css';
import moment from 'moment';
import { connect, mapProps } from '@formily/react';
import { useBoolean } from 'ahooks';
import { Input, Radio, Space } from 'antd';
import React, { useState } from 'react';
import { useToken } from '../../';
const date = moment();
const spaceCSS = css`
width: 100%;
& > .ant-space-item {
flex: 1;
}
`;
export const DateFormatCom = (props?) => {
const date = moment();
return (
<div style={{ display: 'inline-flex' }}>
<span>{props.format}</span>
<DateTimeFormatPreview content={date.format(props.format)} />
</div>
);
};
const DateTimeFormatPreview = ({ content }) => {
const { token } = useToken();
return (
<span
style={{
display: 'inline-block',
background: token.colorBgTextHover,
marginLeft: token.marginMD,
lineHeight: '1',
padding: token.paddingXXS,
borderRadius: token.borderRadiusOuter,
}}
>
{content}
</span>
);
};
const InternalExpiresRadio = (props) => {
const { onChange, defaultValue, formats, timeFormat } = props;
const [isCustom, { setFalse, setTrue }] = useBoolean(props.value && !formats.includes(props.value));
const targetValue = props.value && !formats.includes(props.value) ? props.value : defaultValue;
const [customFormatPreview, setCustomFormatPreview] = useState(targetValue ? date.format(targetValue) : null);
const onSelectChange = (v) => {
if (v.target.value === 'custom') {
setTrue();
onChange(targetValue);
} else {
setFalse();
onChange(v.target.value);
}
};
return (
<Space className={spaceCSS}>
<Radio.Group value={isCustom ? 'custom' : props.value} onChange={onSelectChange}>
<Space direction="vertical">
{props.options.map((v) => {
if (v.value === 'custom') {
return (
<Radio value={v.value}>
<Input
style={{ width: '150px' }}
defaultValue={targetValue}
onChange={(e) => {
if (
e.target.value &&
moment(timeFormat ? date.format() : date.toLocaleString(), e.target.value).isValid()
) {
setCustomFormatPreview(date.format(e.target.value));
} else {
setCustomFormatPreview(null);
}
if (isCustom) {
onChange(e.target.value);
}
}}
/>
<DateTimeFormatPreview content={customFormatPreview} />
</Radio>
);
}
return <Radio value={v.value}>{v.label}</Radio>;
})}
</Space>
</Radio.Group>
</Space>
);
};
const ExpiresRadio = connect(
InternalExpiresRadio,
mapProps({
dataSource: 'options',
}),
);
export { ExpiresRadio };

View File

@ -1,3 +1,4 @@
import { css } from '@emotion/css';
import { ArrayCollapse, ArrayItems, FormItem, FormLayout, Input } from '@formily/antd-v5'; import { ArrayCollapse, ArrayItems, FormItem, FormLayout, Input } from '@formily/antd-v5';
import { Field, GeneralField, createForm } from '@formily/core'; import { Field, GeneralField, createForm } from '@formily/core';
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react'; import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react';
@ -63,6 +64,7 @@ import { getTargetKey } from '../schema-component/antd/association-filter/utilts
import { useSchemaTemplateManager } from '../schema-templates'; import { useSchemaTemplateManager } from '../schema-templates';
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate'; import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
import { FormDataTemplates } from './DataTemplates'; import { FormDataTemplates } from './DataTemplates';
import { DateFormatCom, ExpiresRadio } from './DateFormat/ExpiresRadio';
import { EnableChildCollections } from './EnableChildCollections'; import { EnableChildCollections } from './EnableChildCollections';
import { ChildDynamicComponent } from './EnableChildCollections/DynamicComponent'; import { ChildDynamicComponent } from './EnableChildCollections/DynamicComponent';
import { FormLinkageRules } from './LinkageRules'; import { FormLinkageRules } from './LinkageRules';
@ -1267,6 +1269,151 @@ SchemaSettings.EnableChildCollections = function EnableChildCollectionsItem(prop
); );
}; };
SchemaSettings.DataFormat = function DateFormatConfig(props: { fieldSchema: Schema }) {
const { fieldSchema } = props;
const field = useField();
const form = useForm();
const { dn } = useDesignable();
const { t } = useTranslation();
const { getCollectionJoinField } = useCollectionManager();
const collectionField = getCollectionJoinField(fieldSchema?.['x-collection-field']) || {};
const isShowTime = fieldSchema?.['x-component-props']?.showTime;
const dateFormatDefaultValue =
fieldSchema?.['x-component-props']?.dateFormat ||
collectionField?.uiSchema?.['x-component-props']?.dateFormat ||
'YYYY-MM-DD';
const timeFormatDefaultValue =
fieldSchema?.['x-component-props']?.timeFormat || collectionField?.uiSchema?.['x-component-props']?.timeFormat;
return (
<SchemaSettings.ModalItem
title={t('Date display format')}
schema={
{
type: 'object',
properties: {
dateFormat: {
type: 'string',
title: '{{t("Date format")}}',
'x-component': ExpiresRadio,
'x-decorator': 'FormItem',
'x-decorator-props': {},
'x-component-props': {
className: css`
.ant-radio-wrapper {
display: flex;
margin: 5px 0px;
}
`,
defaultValue: 'dddd',
formats: ['MMMMM Do YYYY', 'YYYY-MM-DD', 'MM/DD/YY', 'YYYY/MM/DD', 'DD/MM/YYYY'],
},
default: dateFormatDefaultValue,
enum: [
{
label: DateFormatCom({ format: 'MMMMM Do YYYY' }),
value: 'MMMMM Do YYYY',
},
{
label: DateFormatCom({ format: 'YYYY-MM-DD' }),
value: 'YYYY-MM-DD',
},
{
label: DateFormatCom({ format: 'MM/DD/YY' }),
value: 'MM/DD/YY',
},
{
label: DateFormatCom({ format: 'YYYY/MM/DD' }),
value: 'YYYY/MM/DD',
},
{
label: DateFormatCom({ format: 'DD/MM/YYYY' }),
value: 'DD/MM/YYYY',
},
{
label: 'custom',
value: 'custom',
},
],
},
showTime: {
default:
isShowTime === undefined ? collectionField?.uiSchema?.['x-component-props']?.showTime : isShowTime,
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-content': '{{t("Show time")}}',
'x-reactions': [
`{{(field) => {
field.query('.timeFormat').take(f => {
f.display = field.value ? 'visible' : 'none';
});
}}}`,
],
},
timeFormat: {
type: 'string',
title: '{{t("Time format")}}',
'x-component': ExpiresRadio,
'x-decorator': 'FormItem',
'x-decorator-props': {
className: css`
margin-bottom: 0px;
`,
},
'x-component-props': {
className: css`
color: red;
.ant-radio-wrapper {
display: flex;
margin: 5px 0px;
}
`,
defaultValue: 'h:mm a',
formats: ['hh:mm:ss a', 'HH:mm:ss'],
timeFormat: true,
},
default: timeFormatDefaultValue,
enum: [
{
label: DateFormatCom({ format: 'hh:mm:ss a' }),
value: 'hh:mm:ss a',
},
{
label: DateFormatCom({ format: 'HH:mm:ss' }),
value: 'HH:mm:ss',
},
{
label: 'custom',
value: 'custom',
},
],
},
},
} as ISchema
}
onSubmit={(data) => {
const schema = {
['x-uid']: fieldSchema['x-uid'],
};
schema['x-component-props'] = fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props'] = {
...(fieldSchema['x-component-props'] || {}),
...data,
};
schema['x-component-props'] = fieldSchema['x-component-props'];
field.componentProps = fieldSchema['x-component-props'];
field.query(`.*.${fieldSchema.name}`).forEach((f) => {
f.componentProps = fieldSchema['x-component-props'];
});
dn.emit('patch', {
schema,
});
dn.refresh();
}}
/>
);
};
// 是否显示默认值配置项 // 是否显示默认值配置项
export const isShowDefaultValue = (collectionField: CollectionFieldOptions, getInterface) => { export const isShowDefaultValue = (collectionField: CollectionFieldOptions, getInterface) => {
return ( return (