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 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"
|
||||
"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 object":"現在のオブジェクト",
|
||||
"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": "复制到表单并继续填写",
|
||||
"Linkage with form fields":"从表单字段联动",
|
||||
"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 { 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 { ISchema, Schema, observer, useField, useFieldSchema } from '@formily/react';
|
||||
import { ISchema, observer, Schema, useField, useFieldSchema } from '@formily/react';
|
||||
import { dayjs } from '@nocobase/utils/client';
|
||||
import { Select } from 'antd';
|
||||
import _ from 'lodash';
|
||||
@ -20,9 +20,9 @@ import {
|
||||
} from '../../../collection-manager';
|
||||
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
|
||||
import { GeneralSchemaItems } from '../../../schema-items/GeneralSchemaItems';
|
||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
|
||||
import { VariableInput } from '../../../schema-settings/VariableInput/VariableInput';
|
||||
import { GeneralSchemaDesigner, isPatternDisabled, isShowDefaultValue, SchemaSettings } from '../../../schema-settings';
|
||||
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
|
||||
import { VariableInput } from '../../../schema-settings/VariableInput/VariableInput';
|
||||
import { isVariable, parseVariables, useVariablesCtx } from '../../common/utils/uitls';
|
||||
import { SchemaComponent } from '../../core';
|
||||
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
|
||||
@ -175,6 +175,8 @@ FormItem.Designer = function Designer() {
|
||||
const isPickerMode = fieldSchema['x-component-props']?.mode === 'Picker';
|
||||
const showFieldMode = isAssociationField && fieldModeOptions && !isTableField;
|
||||
const showModeSelect = showFieldMode && isPickerMode;
|
||||
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField.interface);
|
||||
|
||||
return (
|
||||
<GeneralSchemaDesigner>
|
||||
<GeneralSchemaItems />
|
||||
@ -840,6 +842,7 @@ FormItem.Designer = function Designer() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isDateField && <SchemaSettings.DataFormat fieldSchema={fieldSchema} />}
|
||||
{collectionField && <SchemaSettings.Divider />}
|
||||
<SchemaSettings.Remove
|
||||
key="remove"
|
||||
|
@ -7,7 +7,7 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFormBlockContext } from '../../../block-provider';
|
||||
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 { useOperatorList } from '../filter/useOperators';
|
||||
import { isFileCollection } from './FormItem';
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { set } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCollectionManager, useCollectionFilterOptions } from '../../../collection-manager';
|
||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled } from '../../../schema-settings';
|
||||
import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
|
||||
import { GeneralSchemaDesigner, isPatternDisabled, SchemaSettings } from '../../../schema-settings';
|
||||
import { useCompile, useDesignable } from '../../hooks';
|
||||
import { useAssociationFieldContext } from '../association-field/hooks';
|
||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
||||
import { removeNullCondition } from '../filter';
|
||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
||||
|
||||
const useLabelFields = (collectionName?: any) => {
|
||||
// 需要在组件顶层调用
|
||||
@ -44,6 +44,7 @@ export const TableColumnDesigner = (props) => {
|
||||
const { currentMode, field: tableField } = useAssociationFieldContext();
|
||||
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
|
||||
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
||||
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField.interface);
|
||||
let readOnlyMode = 'editable';
|
||||
if (fieldSchema['x-disabled'] === true) {
|
||||
readOnlyMode = 'readonly';
|
||||
@ -323,6 +324,8 @@ export const TableColumnDesigner = (props) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isDateField && <SchemaSettings.DataFormat fieldSchema={fieldSchema} />}
|
||||
|
||||
<SchemaSettings.Divider />
|
||||
<SchemaSettings.Remove
|
||||
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 { Field, GeneralField, createForm } from '@formily/core';
|
||||
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 { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
||||
import { FormDataTemplates } from './DataTemplates';
|
||||
import { DateFormatCom, ExpiresRadio } from './DateFormat/ExpiresRadio';
|
||||
import { EnableChildCollections } from './EnableChildCollections';
|
||||
import { ChildDynamicComponent } from './EnableChildCollections/DynamicComponent';
|
||||
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) => {
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user