feat: add custom action (#396)
* feat: add custom operation * feat: add custom operation * fix: fix cancel button hide popup * refactor: save part schema of ActionModal * fix: can not refresh ui * fix: save part schema * fix: revert code * fix: fix pop up * fix: fix pop up * feat: add form custom * fix: acl allow plugins:getPinned * fix: add pop up * feat: fix show callback * fix: update data * refactor: reviert code * fix: update text * feat: finish table custom operation * feat: add form custom operation * fix: refresh update date * refactor: remove default button design item * fix: refresh form values * fix: remove default edit action design item * fix: fix save form values logic error * refactor: refactor code * fix: format schema * fix: table and form update will change all data * fix: insert schema non refresh * fix: fix remote load schema * fix: fix non request while uid unchange * fix: locale change * fix: change x-action to customizePopup * fix: support custom action modal tip content * refactor: refactor custom operation * fix: remove form.setValues * fix: remove stopPropagation * feat: improve translation * feat: popup Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
53482c5b22
commit
4cc162d1c1
@ -1,9 +1,11 @@
|
||||
import { useField, useFieldSchema, useForm } from '@formily/react';
|
||||
import { Modal } from 'antd';
|
||||
import { message, Modal } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useCollection } from '../../collection-manager';
|
||||
import { useRecord } from '../../record-provider';
|
||||
import { useActionContext, useCompile } from '../../schema-component';
|
||||
import { useCurrentUserContext } from '../../user';
|
||||
import { useBlockRequestContext, useFilterByTk } from '../BlockProvider';
|
||||
import { useDetailsBlockContext } from '../DetailsBlockProvider';
|
||||
import { TableFieldResource } from '../TableFieldProvider';
|
||||
@ -65,8 +67,8 @@ export const useCreateActionProps = () => {
|
||||
return {
|
||||
async onClick() {
|
||||
const fieldNames = fields.map((field) => field.name);
|
||||
const skipValidator = actionSchema?.['x-action-settings']?.skipValidator;
|
||||
const overwriteValues = actionSchema?.['x-action-settings']?.overwriteValues;
|
||||
const { assignedValues, onSuccess, overwriteValues, skipValidator } = actionSchema?.['x-action-settings'] ?? {};
|
||||
|
||||
if (!skipValidator) {
|
||||
await form.submit();
|
||||
}
|
||||
@ -107,15 +109,16 @@ export const useCreateActionProps = () => {
|
||||
values: {
|
||||
...values,
|
||||
...overwriteValues,
|
||||
...assignedValues,
|
||||
},
|
||||
});
|
||||
actionField.data.loading = false;
|
||||
__parent?.service?.refresh?.();
|
||||
setVisible?.(false);
|
||||
const onSuccess = actionSchema?.['x-action-settings']?.onSuccess;
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
Modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
@ -129,6 +132,56 @@ export const useCreateActionProps = () => {
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useCustomizeUpdateActionProps = () => {
|
||||
const { resource, __parent, service } = useBlockRequestContext();
|
||||
const filterByTk = useFilterByTk();
|
||||
const actionSchema = useFieldSchema();
|
||||
const currentRecord = useRecord();
|
||||
const ctx = useCurrentUserContext();
|
||||
const history = useHistory();
|
||||
const compile = useCompile();
|
||||
const form = useForm();
|
||||
|
||||
return {
|
||||
async onClick() {
|
||||
const { assignedValues, onSuccess, skipValidator } = actionSchema?.['x-action-settings'] ?? {};
|
||||
if (skipValidator === false) {
|
||||
await form.submit();
|
||||
}
|
||||
await resource.update({
|
||||
filterByTk,
|
||||
values: { ...assignedValues },
|
||||
});
|
||||
service?.refresh?.();
|
||||
if (!(resource instanceof TableFieldResource)) {
|
||||
__parent?.service?.refresh?.();
|
||||
}
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
Modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||
if (isURL(onSuccess.redirectTo)) {
|
||||
window.location.href = onSuccess.redirectTo;
|
||||
} else {
|
||||
history.push(onSuccess.redirectTo);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -145,8 +198,8 @@ export const useUpdateActionProps = () => {
|
||||
const actionField = useField();
|
||||
return {
|
||||
async onClick() {
|
||||
const skipValidator = actionSchema?.['x-action-settings']?.skipValidator;
|
||||
const overwriteValues = actionSchema?.['x-action-settings']?.overwriteValues;
|
||||
const { assignedValues, onSuccess, overwriteValues, skipValidator } = actionSchema?.['x-action-settings'] ?? {};
|
||||
|
||||
if (!skipValidator) {
|
||||
await form.submit();
|
||||
}
|
||||
@ -196,6 +249,7 @@ export const useUpdateActionProps = () => {
|
||||
values: {
|
||||
...values,
|
||||
...overwriteValues,
|
||||
...assignedValues,
|
||||
},
|
||||
});
|
||||
actionField.data.loading = false;
|
||||
@ -204,8 +258,10 @@ export const useUpdateActionProps = () => {
|
||||
__parent?.__parent?.service?.refresh?.();
|
||||
}
|
||||
setVisible?.(false);
|
||||
const onSuccess = actionSchema?.['x-action-settings']?.onSuccess;
|
||||
if (onSuccess?.successMessage) {
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
Modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
@ -219,6 +275,8 @@ export const useUpdateActionProps = () => {
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import { useCollectionField } from './hooks';
|
||||
// TODO: 初步适配
|
||||
const InternalField: React.FC = (props) => {
|
||||
const field = useField<Field>();
|
||||
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { name, interface: interfaceType, uiSchema } = useCollectionField();
|
||||
const component = useComponent(uiSchema?.['x-component']);
|
||||
|
@ -288,4 +288,15 @@ export default {
|
||||
|
||||
'Unsaved changes': 'Unsaved changes',
|
||||
'Are you sure you don\'t want to save?': 'Are you sure you don\'t want to save?',
|
||||
|
||||
'Popup': 'Popup',
|
||||
'Trigger workflow': 'Trigger workflow',
|
||||
'Request API': 'Request API',
|
||||
'Assign field values': 'Assign field values',
|
||||
'Popup close method': 'Popup close method',
|
||||
'Automatic close': 'Automatic close',
|
||||
'Manually close': 'Manually close',
|
||||
'After successful update': 'After successful update',
|
||||
'Save record': 'Save record',
|
||||
'After successful save': 'After successful save',
|
||||
}
|
||||
|
@ -365,7 +365,7 @@ export default {
|
||||
'Tab name': '标签名称',
|
||||
'Current record blocks': '当前数据区块',
|
||||
|
||||
'Pop-up message': '弹窗提示消息',
|
||||
'Popup message': '弹窗提示消息',
|
||||
|
||||
'Delete role': '删除角色',
|
||||
|
||||
@ -487,4 +487,22 @@ export default {
|
||||
'Unsaved changes': '未保存修改',
|
||||
'Are you sure you don\'t want to save?': '你确定不保存修改吗?',
|
||||
'Dragging': '拖拽中',
|
||||
|
||||
'Popup': '打开弹窗',
|
||||
'Trigger workflow': '触发工作流',
|
||||
'Request API': '请求API',
|
||||
'Assign field values': '字段赋值',
|
||||
'Constant value': '静态值',
|
||||
'Dynamic value': '动态值',
|
||||
'Current user': '当前用户',
|
||||
'Current record': '当前记录',
|
||||
'Popup close method': '弹窗关闭方式',
|
||||
'Automatic close': '自动关闭',
|
||||
'Manually close': '手动关闭',
|
||||
'After successful update': '更新成功后',
|
||||
'Save record': '保存数据',
|
||||
'Updated successfully': '更新成功',
|
||||
'After successful save': '保存成功后',
|
||||
'After clicking the custom button, the following field values will be assigned according to the following form.': '点击当前自定义按钮时,以下字段值将按照以下表单赋值。',
|
||||
'After clicking the custom button, The following fields of the current record will be saved according to the following form.': '点击当前自定义按钮时,当前数据以下字段将按照以下表单保存。',
|
||||
}
|
||||
|
@ -1,17 +1,61 @@
|
||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { isValid } from '@formily/shared';
|
||||
import { uid } from '@nocobase/utils';
|
||||
import { Menu } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDesignable } from '../..';
|
||||
import { useActionContext, useCompile, useDesignable } from '../..';
|
||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||
|
||||
const MenuGroup = (props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const actionType = fieldSchema['x-action'] || '';
|
||||
const { t } = useTranslation();
|
||||
const actionTitles = {
|
||||
'customize:popup': t('Popup'),
|
||||
'customize:update': t('Update record'),
|
||||
'customize:save': t('Save record'),
|
||||
};
|
||||
if (!['customize:popup', 'customize:update', 'customize:save'].includes(actionType)) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
return <Menu.ItemGroup title={`${t('Customize')} > ${actionTitles[actionType]}`}>{props.children}</Menu.ItemGroup>;
|
||||
};
|
||||
|
||||
export const ActionDesigner = (props) => {
|
||||
const { modalTip, ...restProps } = props;
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const isPopupAction = ['create', 'update', 'view'].includes(fieldSchema['x-action'] || '');
|
||||
const compile = useCompile();
|
||||
const isPopupAction = ['create', 'update', 'view', 'customize:popup'].includes(fieldSchema['x-action'] || '');
|
||||
const context = useActionContext();
|
||||
const [initialSchema, setInitialSchema] = useState<ISchema>();
|
||||
const actionType = fieldSchema['x-action'] || '';
|
||||
|
||||
useEffect(() => {
|
||||
const schemaUid = uid();
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
'x-uid': schemaUid,
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'CustomFormItemInitializers',
|
||||
};
|
||||
setInitialSchema(schema);
|
||||
}, [field.address]);
|
||||
|
||||
const tips = {
|
||||
'customize:update': t(
|
||||
'After clicking the custom button, The following fields of the current record will be saved according to the following form.',
|
||||
),
|
||||
'customize:save': t(
|
||||
'After clicking the custom button, The following fields of the current record will be saved according to the following form.',
|
||||
),
|
||||
};
|
||||
return (
|
||||
<GeneralSchemaDesigner {...props} disableInitializer>
|
||||
<GeneralSchemaDesigner {...restProps} disableInitializer>
|
||||
<MenuGroup>
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Edit button')}
|
||||
schema={
|
||||
@ -79,7 +123,27 @@ export const ActionDesigner = (props) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{fieldSchema?.['x-action-settings'] && (
|
||||
|
||||
{isValid(fieldSchema?.['x-action-settings']?.assignedValues) && (
|
||||
<SchemaSettings.ActionModalItem
|
||||
title={t('Assign field values')}
|
||||
initialSchema={initialSchema}
|
||||
initialValues={fieldSchema?.['x-action-settings']?.assignedValues}
|
||||
modalTip={tips[actionType]}
|
||||
uid={fieldSchema?.['x-action-settings']?.schemaUid}
|
||||
onSubmit={(assignedValues) => {
|
||||
fieldSchema['x-action-settings']['assignedValues'] = assignedValues;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-action-settings': fieldSchema['x-action-settings'],
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isValid(fieldSchema?.['x-action-settings']?.skipValidator) && (
|
||||
<SchemaSettings.SwitchItem
|
||||
title={t('Skip required validation')}
|
||||
checked={!!fieldSchema?.['x-action-settings']?.skipValidator}
|
||||
@ -96,7 +160,7 @@ export const ActionDesigner = (props) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{fieldSchema?.['x-action-settings'] && (
|
||||
{isValid(fieldSchema?.['x-action-settings']?.overwriteValues) && (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Form values')}
|
||||
schema={
|
||||
@ -129,22 +193,37 @@ export const ActionDesigner = (props) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{fieldSchema?.['x-action-settings'] && (
|
||||
{isValid(fieldSchema?.['x-action-settings']?.['onSuccess']) && (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('After successful submission')}
|
||||
title={
|
||||
compile(fieldSchema?.['x-action-settings']?.['onSuccess']?.['title']) ?? t('After successful submission')
|
||||
}
|
||||
initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('After successful submission'),
|
||||
title: {
|
||||
'customize:save': t('After successful save'),
|
||||
'customize:update': t('After successful update'),
|
||||
}[actionType],
|
||||
properties: {
|
||||
successMessage: {
|
||||
// default: t('Submitted successfully!'),
|
||||
title: t('Pop-up message'),
|
||||
title: t('Popup message'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {},
|
||||
},
|
||||
manualClose: {
|
||||
title: t('Popup close method'),
|
||||
default: false,
|
||||
enum: [
|
||||
{ label: t('Automatic close'), value: false },
|
||||
{ label: t('Manually close'), value: true },
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {},
|
||||
},
|
||||
redirecting: {
|
||||
title: t('Then'),
|
||||
default: false,
|
||||
@ -191,9 +270,10 @@ export const ActionDesigner = (props) => {
|
||||
return s['x-component'] === 'Space' || s['x-component'].endsWith('ActionBar');
|
||||
}}
|
||||
confirm={{
|
||||
title: t('Delete action')
|
||||
title: t('Delete action'),
|
||||
}}
|
||||
/>
|
||||
</MenuGroup>
|
||||
</GeneralSchemaDesigner>
|
||||
);
|
||||
};
|
||||
|
@ -25,11 +25,11 @@ export const useActionContext = () => {
|
||||
content: t("Are you sure you don't want to save?"),
|
||||
async onOk() {
|
||||
ctx.setFormValueChanged(false);
|
||||
ctx.setVisible(false);
|
||||
ctx.setVisible?.(false);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
ctx.setVisible(false);
|
||||
ctx?.setVisible?.(false);
|
||||
}
|
||||
} else {
|
||||
ctx?.setVisible?.(visible);
|
||||
|
@ -46,7 +46,7 @@ export const MarkdownVoid: any = observer((props: any) => {
|
||||
const schema = useFieldSchema();
|
||||
const { dn } = useDesignable();
|
||||
const { onSave, onCancel } = props;
|
||||
return field.editable ? (
|
||||
return field?.editable ? (
|
||||
<MarkdownEditor
|
||||
{...props}
|
||||
defaultValue={content}
|
||||
|
@ -2,6 +2,7 @@ import { isPlainObj } from '@formily/shared';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { SchemaComponentOptions } from '../schema-component';
|
||||
import * as globals from './buttons';
|
||||
import * as initializerComponents from './components';
|
||||
import * as items from './items';
|
||||
import { SchemaInitializer } from './SchemaInitializer';
|
||||
|
||||
@ -49,7 +50,9 @@ export const SchemaInitializerProvider: React.FC<SchemaInitializerProviderProps>
|
||||
const { initializers, components, children } = props;
|
||||
return (
|
||||
<SchemaInitializerContext.Provider value={{ ...globals, ...initializers }}>
|
||||
<SchemaComponentOptions components={{ ...items, ...components }}>{children}</SchemaComponentOptions>
|
||||
<SchemaComponentOptions components={{ ...items, ...components, ...initializerComponents }}>
|
||||
{children}
|
||||
</SchemaComponentOptions>
|
||||
</SchemaInitializerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaInitializer } from '../SchemaInitializer';
|
||||
import { gridRowColWrap, useCustomFormItemInitializerFields } from '../utils';
|
||||
|
||||
// 表单里配置字段
|
||||
export const CustomFormItemInitializers = (props: any) => {
|
||||
const { t } = useTranslation();
|
||||
const { insertPosition, component } = props;
|
||||
return (
|
||||
<SchemaInitializer.Button
|
||||
wrap={gridRowColWrap}
|
||||
icon={'SettingOutlined'}
|
||||
items={[
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: t('Configure fields'),
|
||||
children: useCustomFormItemInitializerFields(),
|
||||
},
|
||||
]}
|
||||
insertPosition={insertPosition}
|
||||
component={component}
|
||||
title={component ? null : t('Configure fields')}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,4 +1,3 @@
|
||||
|
||||
// 表单的操作配置
|
||||
export const FormActionInitializers = {
|
||||
title: '{{t("Configure actions")}}',
|
||||
@ -13,12 +12,7 @@ export const FormActionInitializers = {
|
||||
title: '{{t("Submit")}}',
|
||||
component: 'CreateSubmitActionInitializer',
|
||||
schema: {
|
||||
'x-action-settings': {
|
||||
initialValues: {},
|
||||
onSuccess: {
|
||||
successMessage: '{{t("Submitted successfully")}}',
|
||||
},
|
||||
},
|
||||
'x-action-settings': {},
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -32,16 +26,74 @@ export const FormActionInitializers = {
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Save action")}}',
|
||||
title: '{{t("Popup")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Save") }}',
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-action': 'customize:popup',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'RecordBlockInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Save record")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Save record") }}',
|
||||
'x-action': 'customize:save',
|
||||
'x-component': 'Action',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-designer-props': {
|
||||
modalTip:
|
||||
'{{ t("When the button is clicked, the following fields will be assigned and saved together with the fields in the form. If there are overlapping fields, the value here will overwrite the value in the form.") }}',
|
||||
},
|
||||
'x-action-settings': {
|
||||
initialValues: {},
|
||||
assignedValues: {},
|
||||
skipValidator: false,
|
||||
onSuccess: {
|
||||
successMessage: '{{t("Submitted successfully")}}',
|
||||
manualClose: true,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Saved successfully")}}',
|
||||
},
|
||||
},
|
||||
'x-component-props': {
|
||||
@ -67,9 +119,7 @@ export const CreateFormActionInitializers = {
|
||||
title: '{{t("Submit")}}',
|
||||
component: 'CreateSubmitActionInitializer',
|
||||
schema: {
|
||||
'x-action-settings': {
|
||||
overwriteValues: {},
|
||||
},
|
||||
'x-action-settings': {},
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -83,14 +133,75 @@ export const CreateFormActionInitializers = {
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Save action")}}',
|
||||
title: '{{t("Popup")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Save") }}',
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-action': 'customize:popup',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'RecordBlockInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Save record")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Save record") }}',
|
||||
'x-action': 'customize:save',
|
||||
'x-component': 'Action',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-designer-props': {
|
||||
modalTip:
|
||||
'{{ t("When the button is clicked, the following fields will be assigned and saved together with the fields in the form. If there are overlapping fields, the value here will overwrite the value in the form.") }}',
|
||||
},
|
||||
'x-action-settings': {
|
||||
overwriteValues: {},
|
||||
assignedValues: {},
|
||||
skipValidator: false,
|
||||
onSuccess: {
|
||||
manualClose: true,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Saved successfully")}}',
|
||||
},
|
||||
},
|
||||
'x-component-props': {
|
||||
useProps: '{{ useCreateActionProps }}',
|
||||
@ -115,9 +226,7 @@ export const UpdateFormActionInitializers = {
|
||||
title: '{{t("Submit")}}',
|
||||
component: 'UpdateSubmitActionInitializer',
|
||||
schema: {
|
||||
'x-action-settings': {
|
||||
overwriteValues: {},
|
||||
},
|
||||
'x-action-settings': {},
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -131,14 +240,75 @@ export const UpdateFormActionInitializers = {
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Save action")}}',
|
||||
title: '{{t("Popup")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-action': 'customize:popup',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'RecordBlockInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Save record")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Save") }}',
|
||||
'x-component': 'Action',
|
||||
'x-action': 'customize:save',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-designer-props': {
|
||||
modalTip:
|
||||
'{{ t("When the button is clicked, the following fields will be assigned and saved together with the fields in the form. If there are overlapping fields, the value here will overwrite the value in the form.") }}',
|
||||
},
|
||||
'x-action-settings': {
|
||||
overwriteValues: {},
|
||||
assignedValues: {},
|
||||
skipValidator: false,
|
||||
onSuccess: {
|
||||
manualClose: true,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Saved successfully")}}',
|
||||
},
|
||||
},
|
||||
'x-component-props': {
|
||||
useProps: '{{ useUpdateActionProps }}',
|
||||
|
@ -33,5 +33,87 @@ export const ReadPrettyFormActionInitializers = {
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'subMenu',
|
||||
title: '{{t("Customize")}}',
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Popup")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-action': 'customize:popup',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'RecordBlockInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Update record")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Update record") }}',
|
||||
'x-component': 'Action',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-action': 'customize:update',
|
||||
'x-action-settings': {
|
||||
assignedValues: {},
|
||||
onSuccess: {
|
||||
manualClose: true,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Updated successfully")}}',
|
||||
},
|
||||
},
|
||||
'x-component-props': {
|
||||
useProps: '{{ useCustomizeUpdateActionProps }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -70,6 +70,87 @@ export const TableActionColumnInitializers = (props: any) => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'subMenu',
|
||||
title: '{{t("Customize")}}',
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Popup")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-action': 'customize:popup',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'RecordBlockInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: '{{t("Update record")}}',
|
||||
component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Update record") }}',
|
||||
'x-component': 'Action.Link',
|
||||
'x-action': 'customize:update',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-action-settings': {
|
||||
assignedValues: {},
|
||||
onSuccess: {
|
||||
manualClose: true,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Updated successfully")}}',
|
||||
},
|
||||
},
|
||||
'x-component-props': {
|
||||
useProps: '{{ useCustomizeUpdateActionProps }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
component={<MenuOutlined style={{ cursor: 'pointer' }} />}
|
||||
/>
|
||||
|
@ -1,6 +1,7 @@
|
||||
export * from './BlockInitializers';
|
||||
export * from './CalendarActionInitializers';
|
||||
export * from './CreateFormBlockInitializers';
|
||||
export * from './CustomFormItemInitializers';
|
||||
export * from './DetailsActionInitializers';
|
||||
export * from './FormActionInitializers';
|
||||
export * from './FormItemInitializers';
|
||||
@ -15,4 +16,3 @@ export * from './TableActionInitializers';
|
||||
export * from './TableColumnInitializers';
|
||||
export * from './TableSelectorInitializers';
|
||||
export * from './TabPaneInitializers';
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
import { Field } from '@formily/core';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CollectionField, useCollection } from '../../../collection-manager';
|
||||
import { useCompile, useFilterOptions } from '../../../schema-component';
|
||||
|
||||
export const AssignedField = (props: any) => {
|
||||
const { t } = useTranslation();
|
||||
const compile = useCompile();
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const [type, setType] = useState<string>('constantValue');
|
||||
const [value, setValue] = useState(field?.value?.value ?? '');
|
||||
const [options, setOptions] = useState<any[]>([]);
|
||||
const { getField } = useCollection();
|
||||
const collectionField = getField(fieldSchema.name);
|
||||
const { uiSchema } = collectionField;
|
||||
const currentUser = useFilterOptions('users');
|
||||
const currentRecord = useFilterOptions(collectionField.collectionName);
|
||||
|
||||
useEffect(() => {
|
||||
const opt = [
|
||||
{
|
||||
name: 'currentUser',
|
||||
title: t('Current user'),
|
||||
children: [...currentUser],
|
||||
},
|
||||
{
|
||||
name: 'currentRecord',
|
||||
title: t('Current record'),
|
||||
children: [...currentRecord],
|
||||
},
|
||||
];
|
||||
setOptions(compile(opt));
|
||||
}, []);
|
||||
|
||||
const valueChangeHandler = (val) => {
|
||||
setValue(val);
|
||||
};
|
||||
|
||||
return <CollectionField {...props} value={field.value} onChange={valueChangeHandler} />;
|
||||
|
||||
// return (
|
||||
// <Space>
|
||||
// <Select defaultValue={type} value={type} style={{ width: 120 }} onChange={typeChangeHandler}>
|
||||
// <Select.Option value="constantValue">{t('Constant value')}</Select.Option>
|
||||
// <Select.Option value="dynamicValue">{t('Dynamic value')}</Select.Option>
|
||||
// </Select>
|
||||
|
||||
// {type === 'constantValue' ? (
|
||||
// <CollectionField {...props} onChange={valueChangeHandler} />
|
||||
// ) : (
|
||||
// <Cascader
|
||||
// fieldNames={{
|
||||
// label: 'title',
|
||||
// value: 'name',
|
||||
// children: 'children',
|
||||
// }}
|
||||
// style={{
|
||||
// width: 150,
|
||||
// }}
|
||||
// options={options}
|
||||
// onChange={valueChangeHandler}
|
||||
// defaultValue={value}
|
||||
// />
|
||||
// )}
|
||||
// </Space>
|
||||
// );
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './AssignedField';
|
@ -0,0 +1 @@
|
||||
export * from './assigned-field';
|
@ -20,7 +20,7 @@ import {
|
||||
createTableSelectorSchema,
|
||||
useCollectionDataSourceItems,
|
||||
useCurrentSchema,
|
||||
useRecordCollectionDataSourceItems
|
||||
useRecordCollectionDataSourceItems,
|
||||
} from '../utils';
|
||||
|
||||
// Block
|
||||
|
@ -39,6 +39,19 @@ export const removeGridFormItem = (schema, cb) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useRemoveGridFormItem = () => {
|
||||
const form = useForm();
|
||||
return (schema, cb) => {
|
||||
cb(schema, {
|
||||
removeParentsIfNoChildren: true,
|
||||
breakRemoveOn: {
|
||||
'x-component': 'Grid',
|
||||
},
|
||||
});
|
||||
delete form.values?.[schema.name];
|
||||
};
|
||||
};
|
||||
|
||||
export const findTableColumn = (schema: Schema, key: string, action: string, deepth: number = 0) => {
|
||||
return schema.reduceProperties((buf, s) => {
|
||||
if (s[key] === action) {
|
||||
@ -92,6 +105,7 @@ export const useFormItemInitializerFields = (options?: any) => {
|
||||
const { getInterface } = useCollectionManager();
|
||||
const form = useForm();
|
||||
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||
|
||||
return fields
|
||||
?.filter((field) => field?.interface)
|
||||
?.map((field) => {
|
||||
@ -119,6 +133,40 @@ export const useFormItemInitializerFields = (options?: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useCustomFormItemInitializerFields = (options?: any) => {
|
||||
const { name, fields } = useCollection();
|
||||
const { getInterface } = useCollectionManager();
|
||||
const form = useForm();
|
||||
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||
const remove = useRemoveGridFormItem();
|
||||
return fields
|
||||
?.filter((field) => {
|
||||
return field?.interface && !field?.uiSchema?.['x-read-pretty'];
|
||||
})
|
||||
?.map((field) => {
|
||||
const interfaceConfig = getInterface(field.interface);
|
||||
const schema = {
|
||||
type: 'string',
|
||||
name: field.name,
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
'x-designer': 'FormItem.Designer',
|
||||
'x-component': 'AssignedField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': `${name}.${field.name}`,
|
||||
};
|
||||
return {
|
||||
type: 'item',
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
component: 'CollectionFieldInitializer',
|
||||
remove: remove,
|
||||
schemaInitialize: (s) => {
|
||||
interfaceConfig?.schemaInitialize?.(s, { field, block, readPretty });
|
||||
},
|
||||
schema,
|
||||
} as SchemaInitializerItemOptions;
|
||||
});
|
||||
};
|
||||
|
||||
const findSchema = (schema: Schema, key: string, action: string) => {
|
||||
return schema.reduceProperties((buf, s) => {
|
||||
if (s[key] === action) {
|
||||
|
@ -1,25 +1,28 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { FormDialog, FormItem, FormLayout, Input } from '@formily/antd';
|
||||
import { GeneralField } from '@formily/core';
|
||||
import { ISchema, Schema, SchemaOptionsContext } from '@formily/react';
|
||||
import { createForm, GeneralField } from '@formily/core';
|
||||
import { ISchema, Schema, SchemaOptionsContext, useFieldSchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { Dropdown, Menu, MenuItemProps, Modal, Select, Switch } from 'antd';
|
||||
import { Alert, Button, Dropdown, Menu, MenuItemProps, Modal, Select, Space, Switch } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ActionContext,
|
||||
createDesignable,
|
||||
Designable,
|
||||
FormProvider,
|
||||
RemoteSchemaComponent,
|
||||
SchemaComponent,
|
||||
SchemaComponentOptions,
|
||||
useActionContext,
|
||||
useAPIClient,
|
||||
useCollection
|
||||
useCollection,
|
||||
useCompile
|
||||
} from '..';
|
||||
import { useSchemaTemplateManager } from '../schema-templates';
|
||||
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
|
||||
|
||||
interface SchemaSettingsProps {
|
||||
title?: any;
|
||||
dn?: Designable;
|
||||
@ -462,6 +465,83 @@ SchemaSettings.PopupItem = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
SchemaSettings.ActionModalItem = React.memo((props: any) => {
|
||||
const { title, onSubmit, initialValues, initialSchema, modalTip, ...others } = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [schemaUid, setSchemaUid] = useState<string>(props.uid);
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const ctx = useContext(SchemaSettingsContext);
|
||||
const { dn } = useSchemaSettings();
|
||||
const compile = useCompile();
|
||||
const api = useAPIClient();
|
||||
const form = useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
initialValues: cloneDeep(initialValues),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const cancelHandler = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
const submitHandler = () => {
|
||||
onSubmit?.(cloneDeep(form.values));
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const openAssignedFieldValueHandler = async () => {
|
||||
if (!schemaUid && initialSchema['x-uid']) {
|
||||
fieldSchema['x-action-settings'].schemaUid = initialSchema['x-uid'];
|
||||
dn.emit('patch', { schema: fieldSchema });
|
||||
await api.resource('uiSchemas').insert({ values: initialSchema });
|
||||
setSchemaUid(initialSchema['x-uid']);
|
||||
}
|
||||
|
||||
ctx.setVisible(false);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<SchemaSettings.Item {...others} onClick={openAssignedFieldValueHandler}>
|
||||
{props.children || props.title}
|
||||
</SchemaSettings.Item>
|
||||
|
||||
<Modal
|
||||
width={'50%'}
|
||||
title={compile(title)}
|
||||
{...others}
|
||||
destroyOnClose
|
||||
visible={visible}
|
||||
onCancel={cancelHandler}
|
||||
footer={
|
||||
<Space>
|
||||
<Button onClick={cancelHandler}>{t('Cancel')}</Button>
|
||||
<Button type="primary" onClick={submitHandler}>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<FormProvider form={form}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
{modalTip && <Alert message={modalTip} />}
|
||||
{modalTip && <br />}
|
||||
{visible && <RemoteSchemaComponent noForm uid={schemaUid} />}
|
||||
</FormLayout>
|
||||
</FormProvider>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SchemaSettings.ModalItem = (props) => {
|
||||
const { hidden, title, components, scope, effects, schema, onSubmit, initialValues, ...others } = props;
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
|
@ -202,7 +202,7 @@ SettingsForm.Drawer = () => {
|
||||
style={{ width: 200 }}
|
||||
onClick={async () => {
|
||||
setDropdownVisible(false);
|
||||
const values = await FormDrawer('Pop-up form', () => {
|
||||
const values = await FormDrawer('Popup form', () => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components, FormItem }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
|
Loading…
Reference in New Issue
Block a user