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:
parent
df4e6de4dc
commit
137e3eb171
@ -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"
|
||||||
};
|
};
|
||||||
|
@ -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":"日付表示形式"
|
||||||
}
|
}
|
||||||
|
@ -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":"日期显示格式"
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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';
|
||||||
|
@ -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}
|
||||||
|
@ -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 };
|
@ -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 (
|
||||||
|
Loading…
Reference in New Issue
Block a user