feat: actions 2.0 (#7105)

* refactor: streamline AddNewActionModel and remove unused imports in TableModel

* feat: add LinkPopupActionModel and update PopupActionModel usage

* feat: add PopupRecordActionModel and update index export

* feat: remove LinkPopupActionModel

* refactor: update ViewActionModel to extend RecordActionModel and remove LinkPopupActionModel export

* fix: correct import path for ReactiveField in FormFieldModel

* fix: correct import path for ReactiveField in FormFieldModel

* refactor: update ActionModel to improve button rendering and props handling

* refactor: simplify AddNewActionModel by removing unused imports and streamlining flow registration

* refactor: streamline DeleteActionModel by removing unnecessary confirmation steps

* feat: enhance LinkActionModel with new navigation and parameter handling features

* feat: add Emotion CSS support to LinkActionModel

* refactor: update BulkDeleteActionModel to improve flow registration and event handling

* refactor: update AddNewActionModel and BulkDeleteActionModel to use title and icon props

* refactor: update RefreshActionModel to use title and icon props, and improve flow registration

* refactor: rename LinkActionModel to LinkRecordActionModel

* feat: add LinkGlobalActionModel and register flow for link handling

* feat: implement openLinkAction and integrate with LinkGlobalActionModel and LinkRecordActionModel

* feat: add CustomRequestRecordActionModel and update index export

* feat: add CustomRequestGlobalActionModel and update index export

* feat: add BulkEditActionModel and update index export
This commit is contained in:
Zeke Zhang 2025-06-20 09:54:03 +08:00 committed by GitHub
parent 1e28346b0e
commit 78d4b27690
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 570 additions and 147 deletions

View File

@ -0,0 +1,94 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { css } from '@emotion/css';
import { Variable } from '../../schema-component/antd/variable/Variable';
export const openLinkAction = {
title: '编辑链接',
uiSchema: {
url: {
title: 'URL',
'x-decorator': 'FormItem',
'x-component': Variable.TextArea,
description: 'Do not concatenate search params in the URL',
},
params: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: `Search parameters`,
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
style: {
flexWrap: 'nowrap',
maxWidth: '100%',
},
className: css`
& > .ant-space-item:first-child,
& > .ant-space-item:last-child {
flex-shrink: 0;
}
`,
},
properties: {
name: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: `{{t("Name")}}`,
},
},
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': Variable.TextArea,
'x-component-props': {
placeholder: `{{t("Value")}}`,
useTypedConstant: true,
changeOnSelect: true,
},
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: 'Add parameter',
'x-component': 'ArrayItems.Addition',
},
},
},
openInNewWindow: {
type: 'boolean',
'x-content': 'Open in new window',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
},
},
handler(ctx, params) {
ctx.globals.modal.confirm({
title: `TODO`,
content: JSON.stringify(params, null, 2),
});
},
};

View File

@ -8,63 +8,25 @@
*/ */
import { ButtonProps } from 'antd'; import { ButtonProps } from 'antd';
import React from 'react';
import { FlowPage } from '../../FlowPage';
import { GlobalActionModel } from '../base/ActionModel'; import { GlobalActionModel } from '../base/ActionModel';
import { openModeAction } from '../../actions/openModeAction';
export class AddNewActionModel extends GlobalActionModel { export class AddNewActionModel extends GlobalActionModel {
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
type: 'primary', type: 'primary',
children: 'Add new', title: 'Add new',
icon: 'PlusOutlined',
}; };
} }
AddNewActionModel.registerFlow({ AddNewActionModel.registerFlow({
sort: 200, sort: 200,
title: '事件', title: '点击事件',
key: 'event1', key: 'handleClick',
on: { on: {
eventName: 'click', eventName: 'click',
}, },
steps: { steps: {
step1: { open: openModeAction,
title: '弹窗配置',
uiSchema: {
width: {
type: 'number',
title: '宽度',
'x-decorator': 'FormItem',
'x-component': 'NumberPicker',
'x-component-props': {
placeholder: '请输入宽度',
},
},
},
defaultParams: {
width: 800,
},
handler(ctx, params) {
// eslint-disable-next-line prefer-const
let currentDrawer: any;
function DrawerContent() {
return (
<div>
<FlowPage
parentId={ctx.model.uid}
sharedContext={{ parentBlockModel: ctx.shared.currentBlockModel, currentDrawer }}
/>
</div>
);
}
currentDrawer = ctx.globals.drawer.open({
// title: '命令式 Drawer',
header: null,
width: params.width,
content: <DrawerContent />,
});
},
},
}, },
}); });

View File

@ -10,20 +10,25 @@
import { MultiRecordResource } from '@nocobase/flow-engine'; import { MultiRecordResource } from '@nocobase/flow-engine';
import { ButtonProps } from 'antd'; import { ButtonProps } from 'antd';
import { GlobalActionModel } from '../base/ActionModel'; import { GlobalActionModel } from '../base/ActionModel';
import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction';
import { refreshOnCompleteAction } from '../../actions/refreshOnCompleteAction';
export class BulkDeleteActionModel extends GlobalActionModel { export class BulkDeleteActionModel extends GlobalActionModel {
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
children: 'Delete', title: 'Delete',
icon: 'DeleteOutlined',
}; };
} }
BulkDeleteActionModel.registerFlow({ BulkDeleteActionModel.registerFlow({
key: 'event1', key: 'handleClick',
title: '点击事件',
on: { on: {
eventName: 'click', eventName: 'click',
}, },
steps: { steps: {
step1: { secondaryConfirmationAction,
delete: {
async handler(ctx, params) { async handler(ctx, params) {
if (!ctx.shared?.currentBlockModel?.resource) { if (!ctx.shared?.currentBlockModel?.resource) {
ctx.globals.message.error('No resource selected for deletion.'); ctx.globals.message.error('No resource selected for deletion.');
@ -38,5 +43,6 @@ BulkDeleteActionModel.registerFlow({
ctx.globals.message.success('Selected records deleted successfully.'); ctx.globals.message.success('Selected records deleted successfully.');
}, },
}, },
refreshOnCompleteAction,
}, },
}); });

View File

@ -0,0 +1,63 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { MultiRecordResource } from '@nocobase/flow-engine';
import { ButtonProps } from 'antd';
import { GlobalActionModel } from '../base/ActionModel';
import { openModeAction } from '../../actions/openModeAction';
export class BulkEditActionModel extends GlobalActionModel {
defaultProps: ButtonProps = {
title: 'Bulk edit',
icon: 'EditOutlined',
};
}
BulkEditActionModel.registerFlow({
key: 'handleClick',
title: '点击事件',
on: {
eventName: 'click',
},
steps: {
openModeAction,
bulkEdit: {
title: '更新的数据',
uiSchema: {
updateMode: {
'x-component': 'Radio.Group',
'x-component-props': {
options: [
{ label: '更新选中行', value: 'selected' },
{ label: '更新所有行', value: 'all' },
],
},
},
},
defaultParams(ctx) {
return {
updateMode: 'selected',
};
},
async handler(ctx, params) {
if (!ctx.shared?.currentBlockModel?.resource) {
ctx.globals.message.error('No resource selected for bulk edit.');
return;
}
const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource;
if (resource.getSelectedRows().length === 0) {
ctx.globals.message.warning('No records selected for bulk edit.');
return;
}
await resource.destroySelectedRows();
ctx.globals.message.success('Successfully.');
},
},
},
});

View File

@ -15,9 +15,8 @@ import { refreshOnCompleteAction } from '../../actions/refreshOnCompleteAction';
import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction'; import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction';
import { GlobalActionModel } from '../base/ActionModel'; import { GlobalActionModel } from '../base/ActionModel';
export class CustomRequestActionModel extends GlobalActionModel { export class CustomRequestGlobalActionModel extends GlobalActionModel {
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
type: 'link',
title: 'Custom request', title: 'Custom request',
}; };
} }
@ -35,7 +34,7 @@ const useVariableProps = () => {
}; };
}; };
CustomRequestActionModel.registerFlow({ CustomRequestGlobalActionModel.registerFlow({
key: 'handleClick', key: 'handleClick',
title: '点击事件', title: '点击事件',
on: { on: {

View File

@ -0,0 +1,304 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import type { ButtonProps } from 'antd/es/button';
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
import { BlocksSelector } from '../../../schema-component/antd/action/Action.Designer';
import { useAfterSuccessOptions } from '../../../schema-component/antd/action/hooks/useGetAfterSuccessVariablesOptions';
import { refreshOnCompleteAction } from '../../actions/refreshOnCompleteAction';
import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction';
import { RecordActionModel } from '../base/ActionModel';
export class CustomRequestRecordActionModel extends RecordActionModel {
defaultProps: ButtonProps = {
type: 'link',
title: 'Custom request',
};
}
const fieldNames = {
value: 'value',
label: 'label',
};
const useVariableProps = () => {
const environmentVariables = useGlobalVariable('$env');
const scope = useAfterSuccessOptions();
return {
scope: [environmentVariables, ...scope].filter(Boolean),
fieldNames,
};
};
CustomRequestRecordActionModel.registerFlow({
key: 'handleClick',
title: '点击事件',
on: {
eventName: 'click',
},
steps: {
secondaryConfirmation: secondaryConfirmationAction,
request: {
title: '请求设置',
uiSchema: {
method: {
type: 'string',
required: true,
title: 'HTTP method',
'x-decorator-props': {
tooltip:
'When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data',
},
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
showSearch: false,
allowClear: false,
className: 'auto-width',
},
enum: [
{ label: 'GET', value: 'GET' },
{ label: 'POST', value: 'POST' },
{ label: 'PUT', value: 'PUT' },
{ label: 'PATCH', value: 'PATCH' },
{ label: 'DELETE', value: 'DELETE' },
],
default: 'POST',
},
url: {
type: 'string',
required: true,
title: 'URL',
'x-decorator': 'FormItem',
'x-component': 'Variable.TextArea',
'x-use-component-props': useVariableProps,
'x-component-props': {
placeholder: 'https://www.nocobase.com',
},
},
headers: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: 'Headers',
description: '"Content-Type" only support "application/json", and no need to specify',
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
name: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'Name',
},
},
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Variable.TextArea',
'x-use-component-props': useVariableProps,
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: 'Add request header',
'x-component': 'ArrayItems.Addition',
},
},
},
params: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: 'Parameters',
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
name: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: 'Name',
},
},
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Variable.TextArea',
'x-use-component-props': useVariableProps,
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: 'Add parameter',
'x-component': 'ArrayItems.Addition',
},
},
},
data: {
type: 'string',
title: 'Body',
'x-decorator': 'FormItem',
'x-decorator-props': {},
'x-component': 'Variable.JSON',
'x-component-props': {
scope: '{{useCustomRequestVariableOptions}}',
fieldNames: {
value: 'name',
label: 'title',
},
changeOnSelect: true,
autoSize: {
minRows: 10,
},
placeholder: 'Input request data',
},
description: 'Only support standard JSON data',
},
timeout: {
type: 'number',
title: 'Timeout config',
'x-decorator': 'FormItem',
'x-decorator-props': {},
'x-component': 'InputNumber',
'x-component-props': {
addonAfter: 'ms',
min: 1,
step: 1000,
defaultValue: 5000,
},
},
responseType: {
type: 'string',
title: 'Response type',
'x-decorator': 'FormItem',
'x-decorator-props': {},
'x-component': 'Select',
default: 'json',
enum: [
{ value: 'json', label: 'JSON' },
{ value: 'stream', label: 'Stream' },
],
},
},
async handler(ctx, params) {
ctx.globals.modal({
title: 'TODO: Custom request action handler',
});
},
},
afterSuccess: {
title: '提交成功后',
uiSchema: {
successMessage: {
title: 'Popup message',
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-component-props': {},
},
manualClose: {
title: 'Message popup close method',
enum: [
{ label: 'Automatic close', value: false },
{ label: 'Manually close', value: true },
],
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {},
},
redirecting: {
title: 'Then',
'x-hidden': true,
enum: [
{ label: 'Stay on current page', value: false },
{ label: 'Redirect to', value: true },
],
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {},
'x-reactions': {
target: 'redirectTo',
fulfill: {
state: {
visible: '{{!!$self.value}}',
},
},
},
},
actionAfterSuccess: {
title: 'Action after successful submission',
enum: [
{ label: 'Stay on the current popup or page', value: 'stay' },
{ label: 'Return to the previous popup or page', value: 'previous' },
{ label: 'Redirect to', value: 'redirect' },
],
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {},
'x-reactions': {
target: 'redirectTo',
fulfill: {
state: {
visible: "{{$self.value==='redirect'}}",
},
},
},
},
redirectTo: {
title: 'Link',
'x-decorator': 'FormItem',
'x-component': 'Variable.TextArea',
// eslint-disable-next-line react-hooks/rules-of-hooks
'x-use-component-props': () => useVariableProps(),
},
blocksToRefresh: {
type: 'array',
title: 'Refresh data blocks',
'x-decorator': 'FormItem',
'x-use-decorator-props': () => {
return {
tooltip: 'After successful submission, the selected data blocks will be automatically refreshed.',
};
},
'x-component': BlocksSelector,
// 'x-hidden': isInBlockTemplateConfigPage, // 模板配置页面暂不支持该配置
},
},
handler(ctx, params) {},
},
refresh: refreshOnCompleteAction,
},
});

View File

@ -7,54 +7,28 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import { MultiRecordResource } from '@nocobase/flow-engine'; import type { ButtonProps } from 'antd/es/button';
import type { ButtonProps } from 'antd';
import { RecordActionModel } from '../base/ActionModel'; import { RecordActionModel } from '../base/ActionModel';
import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction';
import { MultiRecordResource } from '@nocobase/flow-engine';
import { refreshOnCompleteAction } from '../../actions/refreshOnCompleteAction';
export class DeleteActionModel extends RecordActionModel { export class DeleteActionModel extends RecordActionModel {
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
children: 'Delete',
type: 'link', type: 'link',
title: 'Delete',
}; };
} }
DeleteActionModel.registerFlow({ DeleteActionModel.registerFlow({
key: 'event1', key: 'handleClick',
title: '点击事件',
on: { on: {
eventName: 'click', eventName: 'click',
}, },
steps: { steps: {
confirm: { secondaryConfirmation: secondaryConfirmationAction,
uiSchema: { delete: {
title: {
type: 'string',
title: 'Confirm title',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
content: {
type: 'string',
title: 'Confirm content',
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
},
},
defaultParams: {
title: 'Confirm Deletion',
content: 'Are you sure you want to delete this record?',
},
async handler(ctx, params) {
const confirmed = await ctx.globals.modal.confirm({
title: params.title,
content: params.content,
});
if (!confirmed) {
ctx.globals.message.info('Deletion cancelled.');
return ctx.exit();
}
},
},
step1: {
async handler(ctx, params) { async handler(ctx, params) {
if (!ctx.shared?.currentBlockModel?.resource) { if (!ctx.shared?.currentBlockModel?.resource) {
ctx.globals.message.error('No resource selected for deletion.'); ctx.globals.message.error('No resource selected for deletion.');
@ -69,5 +43,6 @@ DeleteActionModel.registerFlow({
ctx.globals.message.success('Record deleted successfully.'); ctx.globals.message.success('Record deleted successfully.');
}, },
}, },
refresh: refreshOnCompleteAction,
}, },
}); });

View File

@ -0,0 +1,29 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import type { ButtonProps } from 'antd';
import { GlobalActionModel } from '../base/ActionModel';
import { openLinkAction } from '../../actions/openLinkAction';
export class LinkGlobalActionModel extends GlobalActionModel {
defaultProps: ButtonProps = {
title: 'Link',
};
}
LinkGlobalActionModel.registerFlow({
key: 'handleClick',
title: '点击事件',
on: {
eventName: 'click',
},
steps: {
navigate: openLinkAction,
},
});

View File

@ -9,22 +9,22 @@
import type { ButtonProps } from 'antd'; import type { ButtonProps } from 'antd';
import { RecordActionModel } from '../base/ActionModel'; import { RecordActionModel } from '../base/ActionModel';
import { openLinkAction } from '../../actions/openLinkAction';
export class LinkActionModel extends RecordActionModel { export class LinkRecordActionModel extends RecordActionModel {
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
type: 'link', type: 'link',
children: 'Link', children: 'Link',
}; };
} }
LinkActionModel.registerFlow({ LinkRecordActionModel.registerFlow({
key: 'event1', key: 'handleClick',
title: '点击事件',
on: { on: {
eventName: 'click', eventName: 'click',
}, },
steps: { steps: {
step1: { navigate: openLinkAction,
handler(ctx, params) {},
},
}, },
}); });

View File

@ -11,14 +11,13 @@ import type { ButtonProps } from 'antd/es/button';
import { openModeAction } from '../../actions/openModeAction'; import { openModeAction } from '../../actions/openModeAction';
import { RecordActionModel } from '../base/ActionModel'; import { RecordActionModel } from '../base/ActionModel';
export class PopupActionModel extends RecordActionModel { export class PopupRecordActionModel extends RecordActionModel {
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
type: 'link',
title: 'Popup', title: 'Popup',
}; };
} }
PopupActionModel.registerFlow({ PopupRecordActionModel.registerFlow({
key: 'handleClick', key: 'handleClick',
title: '点击事件', title: '点击事件',
on: { on: {

View File

@ -9,20 +9,24 @@
import { ButtonProps } from 'antd'; import { ButtonProps } from 'antd';
import { GlobalActionModel } from '../base/ActionModel'; import { GlobalActionModel } from '../base/ActionModel';
import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction';
export class RefreshActionModel extends GlobalActionModel { export class RefreshActionModel extends GlobalActionModel {
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
children: 'Refresh', title: 'Refresh',
icon: 'ReloadOutlined',
}; };
} }
RefreshActionModel.registerFlow({ RefreshActionModel.registerFlow({
key: 'event1', key: 'handleClick',
title: '点击事件',
on: { on: {
eventName: 'click', eventName: 'click',
}, },
steps: { steps: {
step1: { secondaryConfirmationAction,
refresh: {
async handler(ctx, params) { async handler(ctx, params) {
if (!ctx.shared?.currentBlockModel?.resource) { if (!ctx.shared?.currentBlockModel?.resource) {
ctx.globals.message.error('No resource selected for refresh.'); ctx.globals.message.error('No resource selected for refresh.');

View File

@ -7,50 +7,24 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import { ButtonProps } from 'antd'; import type { ButtonProps } from 'antd/es/button';
import React from 'react';
import { FlowPage } from '../../FlowPage';
import { RecordActionModel } from '../base/ActionModel'; import { RecordActionModel } from '../base/ActionModel';
import { openModeAction } from '../../actions/openModeAction';
export class ViewActionModel extends RecordActionModel { export class ViewActionModel extends RecordActionModel {
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
children: 'View',
type: 'link', type: 'link',
title: 'View',
}; };
} }
ViewActionModel.registerFlow({ ViewActionModel.registerFlow({
key: 'event1', key: 'handleClick',
title: '点击事件',
on: { on: {
eventName: 'click', eventName: 'click',
}, },
steps: { steps: {
step1: { open: openModeAction,
handler(ctx, params) {
// eslint-disable-next-line prefer-const
let currentDrawer: any;
function DrawerContent() {
return (
<div>
<FlowPage
parentId={ctx.model.uid}
sharedContext={{
currentDrawer,
parentRecord: ctx.extra.currentRecord,
parentBlockModel: ctx.shared.currentBlockModel,
}}
/>
</div>
);
}
currentDrawer = ctx.globals.drawer.open({
// title: '命令式 Drawer',
width: 800,
content: <DrawerContent />,
});
},
},
}, },
}); });

View File

@ -9,13 +9,16 @@
export * from './AddNewActionModel'; export * from './AddNewActionModel';
export * from './BulkDeleteActionModel'; export * from './BulkDeleteActionModel';
export * from './CustomRequestActionModel'; export * from './BulkEditActionModel';
export * from './CustomRequestRecordActionModel';
export * from './CustomRequestGlobalActionModel';
export * from './DeleteActionModel'; export * from './DeleteActionModel';
export * from './DuplicateActionModel'; export * from './DuplicateActionModel';
export * from './EditActionModel'; export * from './EditActionModel';
export * from './FilterActionModel'; export * from './FilterActionModel';
export * from './LinkActionModel'; export * from './LinkRecordActionModel';
export * from './PopupActionModel'; export * from './LinkGlobalActionModel';
export * from './PopupRecordActionModel';
export * from './RefreshActionModel'; export * from './RefreshActionModel';
export * from './UpdateRecordActionModel'; export * from './UpdateRecordActionModel';
export * from './ViewActionModel'; export * from './ViewActionModel';

View File

@ -11,44 +11,56 @@ import { FlowModel } from '@nocobase/flow-engine';
import { Button } from 'antd'; import { Button } from 'antd';
import type { ButtonProps } from 'antd/es/button'; import type { ButtonProps } from 'antd/es/button';
import React from 'react'; import React from 'react';
import IconPicker from '../../../schema-component/antd/icon-picker/IconPicker';
import { Icon } from '../../../icon/Icon';
export class ActionModel extends FlowModel { export class ActionModel extends FlowModel {
declare props: ButtonProps;
defaultProps: ButtonProps = { defaultProps: ButtonProps = {
type: 'default', type: 'default',
children: 'Action', title: 'Action',
}; };
render() { render() {
return <Button {...this.defaultProps} {...this.props} />; const props = { ...this.defaultProps, ...this.props };
const icon = <Icon type={props.icon as any} />;
return (
<Button {...props} icon={icon}>
{props.children || props.title}
</Button>
);
} }
} }
ActionModel.registerFlow({ ActionModel.registerFlow({
key: 'default', key: 'default',
title: '通用配置',
auto: true, auto: true,
title: '基础',
sort: 100,
steps: { steps: {
step1: { buttonProps: {
title: '编辑按钮', title: '编辑按钮',
uiSchema: { uiSchema: {
children: { title: {
type: 'string',
title: '标题',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
'x-component-props': { title: 'Button title',
placeholder: '请输入标题', },
}, icon: {
'x-decorator': 'FormItem',
'x-component': IconPicker,
title: 'Button icon',
}, },
}, },
defaultParams(ctx) { defaultParams(ctx) {
return { return {
type: 'default', title: ctx.model.defaultProps.title,
...ctx.model.defaultProps, icon: ctx.model.defaultProps.icon,
}; };
}, },
handler(ctx, params) { handler(ctx, params) {
ctx.model.setProps('children', params.children); ctx.model.setProps(params);
ctx.model.setProps('onClick', (event) => { ctx.model.setProps('onClick', (event) => {
ctx.model.dispatchEvent('click', { ctx.model.dispatchEvent('click', {
...ctx.extra, ...ctx.extra,

View File

@ -11,7 +11,6 @@ import { SettingOutlined } from '@ant-design/icons';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { import {
AddActionButton, AddActionButton,
AddActionModel,
AddFieldButton, AddFieldButton,
Collection, Collection,
FlowModelRenderer, FlowModelRenderer,