feat: support workflow bulk (#858)

Co-authored-by: sealday <sealday@gmail.com>
Reviewed-on: daoyoucloud/tachybase#858
This commit is contained in:
sealday 2024-04-30 16:27:50 +08:00
parent fda9853509
commit 46129499be
31 changed files with 889 additions and 84 deletions

View File

@ -53,5 +53,6 @@ export * from './variable';
export * from './custom-cascader'; export * from './custom-cascader';
export * from './unixTimestamp'; export * from './unixTimestamp';
export * from './nanoIDInput'; export * from './nanoIDInput';
export { PageHeader } from '@ant-design/pro-layout';
import './index.less'; import './index.less';

View File

@ -30,7 +30,7 @@ const approvals = {
const workflow = await context.db.getRepository('workflows').findOne({ const workflow = await context.db.getRepository('workflows').findOne({
filterByTk: workflowId, filterByTk: workflowId,
}); });
if (!(workflow == null ? void 0 : workflow.enabled)) { if (!workflow?.enabled) {
return context.throw(400, 'Current workflow not found or disabled, please refresh and try again'); return context.throw(400, 'Current workflow not found or disabled, please refresh and try again');
} }
if (status !== APPROVAL_STATUS.DRAFT) { if (status !== APPROVAL_STATUS.DRAFT) {

View File

@ -2,7 +2,7 @@
"name": "@hera/plugin-core", "name": "@hera/plugin-core",
"displayName": "Hera platform", "displayName": "Hera platform",
"version": "1.7.28", "version": "1.7.28",
"description": "Hera platform as nocobase plugin.", "description": "Hera platform",
"keywords": [ "keywords": [
"System management" "System management"
], ],
@ -11,13 +11,13 @@
"@ant-design/cssinjs": "^1.11.1", "@ant-design/cssinjs": "^1.11.1",
"@ant-design/icons": "~5.3.6", "@ant-design/icons": "~5.3.6",
"@ant-design/pro-layout": "^7.16.11", "@ant-design/pro-layout": "^7.16.11",
"@emotion/css": "^11.11.2", "@formily/antd-v5": "1.x",
"@react-pdf/renderer": "^3.3.2", "@react-pdf/renderer": "^3.3.2",
"@tachybase/components": "workspace:*", "@tachybase/components": "workspace:*",
"@tachybase/schema": "workspace:*", "@tachybase/schema": "workspace:*",
"@types/lodash": "~4.17.0",
"ahooks": "^3.7.2", "ahooks": "^3.7.2",
"antd": "5.16.1", "antd": "5.16.1",
"antd-style": "3.4.5",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.8", "dayjs": "^1.11.8",
@ -50,7 +50,9 @@
"@nocobase/plugin-acl": "workspace:*", "@nocobase/plugin-acl": "workspace:*",
"@nocobase/server": "workspace:*", "@nocobase/server": "workspace:*",
"@nocobase/test": "workspace:*", "@nocobase/test": "workspace:*",
"@nocobase/utils": "workspace:*" "@nocobase/utils": "workspace:*",
"@tachybase/plugin-workflow": "workspace:*",
"@tachybase/schema": "workspace:*"
}, },
"description.zh-CN": "提供标准赫拉平台能力", "description.zh-CN": "提供标准赫拉平台能力",
"displayName.zh-CN": "赫拉平台" "displayName.zh-CN": "赫拉平台"

View File

@ -1,4 +1,4 @@
import { css } from '@emotion/css'; import { css } from '@nocobase/client';
import { ArrayCollapse } from '@tachybase/components'; import { ArrayCollapse } from '@tachybase/components';
import { Form } from '@tachybase/schema'; import { Form } from '@tachybase/schema';
import { observer, useField, useFieldSchema } from '@tachybase/schema'; import { observer, useField, useFieldSchema } from '@tachybase/schema';

View File

@ -1,39 +1,12 @@
import React from 'react'; import React from 'react';
import { ActionInitializer, Application } from '@nocobase/client'; import { ActionInitializer } from '@nocobase/client';
import { Popover, Space, Button, Input } from 'antd'; import { Popover, Space, Button, Input } from 'antd';
import { useFieldSchema } from '@tachybase/schema'; import { useFieldSchema } from '@tachybase/schema';
import { useProps } from '@nocobase/client'; import { useProps } from '@nocobase/client';
import { ShareAltOutlined } from '@ant-design/icons'; import { ShareAltOutlined } from '@ant-design/icons';
import { lang, tval, useTranslation } from '../../locale'; import { tval, useTranslation } from '../../locale';
import { useOutboundActionProps } from './hooks/useOutboundActionProps';
export class OutboundActionHelper { export const OutboundLinkActionInitializer = (props) => {
constructor(private app: Application) {}
async load() {
this.app.addScopes({
useOutboundActionProps,
});
this.app.addComponents({
OutboundButton,
OutboundLinkActionInitializer,
});
const outboundItem = {
type: 'item',
name: 'enableActions.outbound',
title: lang('outbound'),
Component: 'OutboundLinkActionInitializer',
schema: {
'x-align': 'right',
},
};
this.app.schemaInitializerManager.addItem('table:configureActions', outboundItem.name, outboundItem);
this.app.schemaInitializerManager.addItem('details:configureActions', outboundItem.name, outboundItem);
this.app.schemaInitializerManager.addItem('kanban:configureActions', outboundItem.name, outboundItem);
}
}
export const OutboundLinkActionInitializer: React.FC = (props) => {
const schema = { const schema = {
title: tval('outbound'), title: tval('outbound'),
'x-action': 'outbound', 'x-action': 'outbound',

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { AdminProvider, RemoteCollectionManagerProvider, RemoteSchemaComponent } from '@nocobase/client'; import { AdminProvider, RemoteCollectionManagerProvider, RemoteSchemaComponent } from '@nocobase/client';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
export const OutboundPage: React.FC = () => { export const OutboundPage = () => {
const params = useParams(); const params = useParams();
return ( return (
<AdminProvider> <AdminProvider>

View File

@ -0,0 +1,33 @@
import { Plugin } from '@nocobase/client';
import { OutboundPage } from './OutboundPage';
import { OutboundButton, OutboundLinkActionInitializer } from './OutboundLinkActionInitializer';
import { lang } from '../../locale';
import { useOutboundActionProps } from './useOutboundActionProps';
export class PluginOutbound extends Plugin {
async load() {
this.app.router.add('outbound', {
path: '/r/:id',
Component: OutboundPage,
});
this.app.addScopes({
useOutboundActionProps,
});
this.app.addComponents({
OutboundButton,
OutboundLinkActionInitializer,
});
const outboundItem = {
type: 'item',
name: 'enableActions.outbound',
title: lang('outbound'),
Component: 'OutboundLinkActionInitializer',
schema: {
'x-align': 'right',
},
};
this.app.schemaInitializerManager.addItem('table:configureActions', outboundItem.name, outboundItem);
this.app.schemaInitializerManager.addItem('details:configureActions', outboundItem.name, outboundItem);
this.app.schemaInitializerManager.addItem('kanban:configureActions', outboundItem.name, outboundItem);
}
}

View File

@ -1,7 +1,7 @@
import { message } from 'antd'; import { message } from 'antd';
import { useFieldSchema } from '@tachybase/schema'; import { useFieldSchema } from '@tachybase/schema';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { useTranslation } from '../../../locale'; import { useTranslation } from '../../locale';
export const useOutboundActionProps = () => { export const useOutboundActionProps = () => {
let schema = useFieldSchema(); let schema = useFieldSchema();

View File

@ -0,0 +1,142 @@
import { useCompile, useCollectionManager_deprecated, useCollectionDataSource } from '@nocobase/client';
import { lang, tval } from '../../locale';
import {
Trigger,
CollectionBlockInitializer,
getCollectionFieldOptions,
FieldsSelect,
RadioWithTooltip,
CheckboxGroupWithTooltip,
} from '@tachybase/plugin-workflow/client';
const enum ACTION_TYPES {
CREATE = 'create',
UPDATE = 'update',
UPSERT = 'updateOrCreate',
DESTROY = 'destroy',
}
function useItems(item, options) {
const compile = useCompile();
const { getCollectionFields } = useCollectionManager_deprecated();
return [
{ label: lang('ID'), value: 'filterByTk' },
...(item.action !== ACTION_TYPES.DESTROY
? [
{
label: lang('Values submitted'),
value: 'values',
children: getCollectionFieldOptions({
...options,
appends: null,
depth: 3,
collection: item.collection,
compile,
getCollectionFields,
}),
},
]
: []),
];
}
export class ApiTrigger extends Trigger {
title = lang('API event');
description = lang('api trigger');
fieldset = {
collection: {
type: 'string',
title: tval('Collection'),
required: true,
'x-decorator': 'FormItem',
'x-component': 'DataSourceCollectionCascader',
'x-reactions': [{ target: 'changed', effects: ['onFieldValueChange'], fulfill: { state: { value: [] } } }],
},
global: {
type: 'boolean',
title: tval('Trigger mode'),
'x-decorator': 'FormItem',
'x-component': 'RadioWithTooltip',
'x-component-props': {
direction: 'vertical',
options: [
{
label: tval('Local mode, triggered before executing the actions bound to this workflow'),
value: false,
},
{
label: tval('Global mode, triggered before executing the following actions'),
value: true,
},
],
},
default: false,
},
actions: {
type: 'number',
title: tval('Select actions'),
'x-decorator': 'FormItem',
'x-component': 'CheckboxGroupWithTooltip',
'x-component-props': {
direction: 'vertical',
options: [
{ label: tval('Create record action'), value: ACTION_TYPES.CREATE },
{ label: tval('Update record action'), value: ACTION_TYPES.UPDATE },
{ label: tval('Delete record action'), value: ACTION_TYPES.DESTROY },
],
},
required: true,
'x-reactions': [
{ dependencies: ['collection', 'global'], fulfill: { state: { visible: '{{!!$deps[0] && !!$deps[1]}}' } } },
],
},
};
scope = { useCollectionDataSource };
components = {
FieldsSelect: FieldsSelect,
RadioWithTooltip: RadioWithTooltip,
CheckboxGroupWithTooltip: CheckboxGroupWithTooltip,
};
isActionTriggerable = (a, u) => {
const { global: m } = a;
return !m && !u.direct;
};
useVariables(config, options) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const compile = useCompile();
// eslint-disable-next-line react-hooks/rules-of-hooks
const { getCollectionFields } = useCollectionManager_deprecated();
const result = getCollectionFieldOptions({
appends: ['user'],
...options,
fields: [
{
collectionName: 'users',
name: 'user',
type: 'hasOne',
target: 'users',
uiSchema: { title: tval('User acted') },
},
],
compile,
getCollectionFields,
});
// eslint-disable-next-line react-hooks/rules-of-hooks
const parametersSchema = [{ label: lang('Parameters'), value: 'params', children: useItems(config, options) }];
return [...result, { label: lang('Role of user acted'), value: 'roleName' }, ...parametersSchema];
}
useInitializers(item) {
return item.collection
? {
name: 'triggerData',
type: 'item',
key: 'triggerData',
title: tval('Trigger data'),
Component: CollectionBlockInitializer,
collection: item.collection,
dataPath: '$context.params.values',
}
: null;
}
}

View File

@ -0,0 +1,104 @@
import {
ActionDesigner,
SchemaSettings,
SchemaSettingsItemType,
useDesignable,
useRequest,
useSchemaToolbar,
} from '@nocobase/client';
import { useFieldSchema } from '@tachybase/schema';
import { useTranslation } from 'react-i18next';
const schemaSettingsItems: SchemaSettingsItemType[] = [
{
name: 'editButton',
Component: ActionDesigner.ButtonEditor,
useComponentProps() {
const { buttonEditorProps } = useSchemaToolbar();
return buttonEditorProps;
},
},
{
name: 'Date',
type: 'select',
useComponentProps() {
const { dn } = useDesignable();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
return {
title: t('Data will be updated'),
options: [
{ label: t('Selected'), value: 'selected' },
{ label: t('All'), value: 'all' },
],
value: fieldSchema['x-action-settings']?.['updateMode'],
onChange: (value) => {
fieldSchema['x-action-settings']['updateMode'] = value;
dn.emit('patch', {
schema: {
'x-uid': fieldSchema['x-uid'],
'x-action-settings': fieldSchema['x-action-settings'],
},
});
},
};
},
},
{
name: 'Bind workflow',
type: 'select',
useComponentProps() {
const { dn } = useDesignable();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const { data } = useRequest<any>({
resource: 'workflows',
action: 'list',
params: {
filter: {
type: 'api',
},
},
});
// FIXME 不会实时生效
const options =
data?.data?.map((workflow) => ({
label: workflow.title,
value: workflow.id,
})) ?? [];
return {
title: t('Bind workflow'),
value: fieldSchema['x-action-settings'].bindWorkflow,
options: options.concat({
label: t('disabled'),
value: false,
}),
onChange(value) {
fieldSchema['x-action-settings'].bindWorkflow = value;
dn.emit('patch', {
schema: {
'x-uid': fieldSchema['x-uid'],
'x-action-settings': fieldSchema['x-action-settings'],
},
});
},
};
},
},
{
name: 'remove',
sort: 100,
Component: ActionDesigner.RemoveButton as any,
useComponentProps() {
const { removeButtonProps } = useSchemaToolbar();
return removeButtonProps;
},
},
];
const bulkWorkflowActionSettings = new SchemaSettings({
name: 'actionSettings:bulkWorkflow',
items: schemaSettingsItems,
});
export { bulkWorkflowActionSettings };

View File

@ -0,0 +1,30 @@
import { BlockInitializer, useSchemaInitializerItem } from '@nocobase/client';
import React from 'react';
import { tval } from '../../locale';
export const BulkWorkflowActionInitializer = () => {
const itemConfig = useSchemaInitializerItem();
const schema = {
type: 'void',
title: tval('Bulk workflow'),
'x-component': 'Action',
'x-use-component-props': 'useCustomizeBulkWorkflowActionProps',
'x-align': 'right',
'x-acl-action': 'update',
'x-decorator': 'ACLActionProvider',
'x-acl-action-props': {
skipScopeCheck: true,
},
'x-action': 'customize:bulkWorkflow',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:bulkWorkflow',
'x-action-settings': {
bindWorkflow: false,
updateMode: 'selected',
},
'x-component-props': {
icon: 'CarryOutOutlined',
},
};
return <BlockInitializer {...itemConfig} schema={schema} item={itemConfig} />;
};

View File

@ -0,0 +1,7 @@
import { BlockInitializer, useSchemaInitializerItem } from '@nocobase/client';
import React from 'react';
export const CustomizeActionInitializer = () => {
const itemConfig = useSchemaInitializerItem();
return <BlockInitializer {...itemConfig} item={itemConfig} />;
};

View File

@ -0,0 +1,37 @@
import { Plugin, useCollection } from '@nocobase/client';
import { tval } from '../../locale';
import { BulkWorkflowActionInitializer } from './BulkUpdateActionInitializer';
import { bulkWorkflowActionSettings } from './BulkUpdateAction.Settings';
import { CustomizeActionInitializer } from './CustomizeActionInitializer';
import { useCustomizeBulkWorkflowActionProps } from './utils';
import PluginWorkflow from '@tachybase/plugin-workflow/client';
import { ApiTrigger } from './ApiTrigger';
export class PluginWorkflowBulk extends Plugin {
async load() {
this.app.addComponents({ CustomizeActionInitializer });
this.app.addScopes({ useCustomizeBulkWorkflowActionProps });
this.app.schemaSettingsManager.add(bulkWorkflowActionSettings);
const initializerData = {
title: tval('Bulk workflow'),
Component: BulkWorkflowActionInitializer,
name: 'bulkWorkflow',
useVisible() {
const collection = useCollection();
return (
(collection.template !== 'view' || collection?.writableView) &&
collection.template !== 'file' &&
collection.template !== 'sql'
);
},
};
['table', 'details'].forEach((block) => {
this.app.schemaInitializerManager.addItem(block + ':configureActions', 'customize.bulkWorkflow', initializerData);
});
const plugin = this.app.pm.get(PluginWorkflow) as PluginWorkflow;
plugin.registerTrigger('api', ApiTrigger);
}
}

View File

@ -0,0 +1,102 @@
import { SchemaExpressionScopeContext, useField, useFieldSchema } from '@tachybase/schema';
import {
isVariable,
transformVariableValue,
useBlockRequestContext,
useCollection_deprecated,
useCompile,
useLocalVariables,
useRecord,
useRequest,
useTableBlockContext,
useVariables,
} from '@nocobase/client';
import { App } from 'antd';
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from '../../locale';
export const useCustomizeBulkWorkflowActionProps = () => {
const { field, resource, __parent, service } = useBlockRequestContext();
const expressionScope = useContext(SchemaExpressionScopeContext);
const actionSchema = useFieldSchema();
const tableBlockContext = useTableBlockContext();
const { rowKey } = tableBlockContext;
const selectedRecordKeys =
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
const navigate = useNavigate();
const compile = useCompile();
const { t } = useTranslation();
const actionField: any = useField();
const { modal } = App.useApp();
const variables = useVariables();
const record = useRecord();
const { name, getField } = useCollection_deprecated();
const localVariables = useLocalVariables();
const { run } = useRequest(
{
resource: 'workflows',
action: 'trigger',
},
{
manual: true,
},
);
return {
async onClick() {
const {
bindWorkflow = false,
assignedValues: originalAssignedValues = {},
updateMode,
} = actionSchema?.['x-action-settings'] ?? {};
actionField.data = field.data || {};
actionField.data.loading = true;
if (!bindWorkflow) {
return modal.info({
title: t('Not bind workflow!'),
});
}
const assignedValues = {};
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
const value = originalAssignedValues[key];
const collectionField = getField(key);
if (process.env.NODE_ENV !== 'production') {
if (!collectionField) {
throw new Error(`useCustomizeBulkUpdateActionProps: field "${key}" not found in collection "${name}"`);
}
}
if (isVariable(value)) {
const result = await variables?.parseVariable(value, localVariables);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
assignedValues[key] = value;
}
});
await Promise.all(waitList);
modal.confirm({
title: t('Bulk update', { ns: 'client' }),
content:
updateMode === 'selected'
? t('Update selected data?', { ns: 'client' })
: t('Update all data?', { ns: 'client' }),
async onOk() {
run({
filterByTk: bindWorkflow,
});
actionField.data.loading = false;
},
async onCancel() {
actionField.data.loading = false;
},
});
},
};
};

View File

@ -0,0 +1,145 @@
import { useCompile, useCollectionManager_deprecated, useCollectionDataSource } from '@nocobase/client';
import { lang, tval } from '../../locale';
import {
Trigger,
CollectionBlockInitializer,
getCollectionFieldOptions,
FieldsSelect,
RadioWithTooltip,
CheckboxGroupWithTooltip,
} from '@tachybase/plugin-workflow/client';
const enum ACTION_TYPES {
CREATE = 'create',
UPDATE = 'update',
UPSERT = 'updateOrCreate',
DESTROY = 'destroy',
}
function useItems(item, options) {
const compile = useCompile();
const { getCollectionFields } = useCollectionManager_deprecated();
return [
{ label: lang('ID'), value: 'filterByTk' },
...(item.action !== ACTION_TYPES.DESTROY
? [
{
label: lang('Values submitted'),
value: 'values',
children: getCollectionFieldOptions({
...options,
appends: null,
depth: 3,
collection: item.collection,
compile,
getCollectionFields,
}),
},
]
: []),
];
}
export class WorkflowTriggerInterceptor extends Trigger {
sync = true;
title = lang('Pre-action event');
description = lang(
'Triggered before the execution of a request initiated through an action button or API, such as before adding, updating, or deleting data. Suitable for data validation and logic judgment before action, and the request could be rejected by using the "End process" node.',
);
fieldset = {
collection: {
type: 'string',
title: tval('Collection'),
required: true,
'x-decorator': 'FormItem',
'x-component': 'DataSourceCollectionCascader',
'x-reactions': [{ target: 'changed', effects: ['onFieldValueChange'], fulfill: { state: { value: [] } } }],
},
global: {
type: 'boolean',
title: tval('Trigger mode'),
'x-decorator': 'FormItem',
'x-component': 'RadioWithTooltip',
'x-component-props': {
direction: 'vertical',
options: [
{
label: tval('Local mode, triggered before executing the actions bound to this workflow'),
value: false,
},
{
label: tval('Global mode, triggered before executing the following actions'),
value: true,
},
],
},
default: false,
},
actions: {
type: 'number',
title: tval('Select actions'),
'x-decorator': 'FormItem',
'x-component': 'CheckboxGroupWithTooltip',
'x-component-props': {
direction: 'vertical',
options: [
{ label: tval('Create record action'), value: ACTION_TYPES.CREATE },
{ label: tval('Update record action'), value: ACTION_TYPES.UPDATE },
{ label: tval('Delete record action'), value: ACTION_TYPES.DESTROY },
],
},
required: true,
'x-reactions': [
{ dependencies: ['collection', 'global'], fulfill: { state: { visible: '{{!!$deps[0] && !!$deps[1]}}' } } },
],
},
};
scope = { useCollectionDataSource };
components = {
FieldsSelect: FieldsSelect,
RadioWithTooltip: RadioWithTooltip,
CheckboxGroupWithTooltip: CheckboxGroupWithTooltip,
};
isActionTriggerable = (a, u) => {
const { global: m } = a;
return !m && !u.direct;
};
useVariables(config, options) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const compile = useCompile();
// eslint-disable-next-line react-hooks/rules-of-hooks
const { getCollectionFields } = useCollectionManager_deprecated();
const result = getCollectionFieldOptions({
appends: ['user'],
...options,
fields: [
{
collectionName: 'users',
name: 'user',
type: 'hasOne',
target: 'users',
uiSchema: { title: tval('User acted') },
},
],
compile,
getCollectionFields,
});
// eslint-disable-next-line react-hooks/rules-of-hooks
const parametersSchema = [{ label: lang('Parameters'), value: 'params', children: useItems(config, options) }];
return [...result, { label: lang('Role of user acted'), value: 'roleName' }, ...parametersSchema];
}
useInitializers(item) {
return item.collection
? {
name: 'triggerData',
type: 'item',
key: 'triggerData',
title: tval('Trigger data'),
Component: CollectionBlockInitializer,
collection: item.collection,
dataPath: '$context.params.values',
}
: null;
}
}

View File

@ -0,0 +1,10 @@
import { Plugin } from '@nocobase/client';
import PluginWorkflow from '@tachybase/plugin-workflow/client';
import { WorkflowTriggerInterceptor } from './WorkflowTriggerInterceptor';
export class PluginWorkflowInterceptor extends Plugin {
async load() {
const plugin = this.app.pm.get(PluginWorkflow) as PluginWorkflow;
plugin.registerTrigger('request-interception', WorkflowTriggerInterceptor);
}
}

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { Plugin, RemoteSchemaTemplateManagerProvider, EditTitleField, useCollection } from '@nocobase/client'; import { Plugin, EditTitleField } from '@nocobase/client';
import { remove } from 'lodash';
import { useFieldSchema } from '@tachybase/schema'; import { useFieldSchema } from '@tachybase/schema';
import { isValid } from '@tachybase/schema'; import { isValid } from '@tachybase/schema';
import { autorun } from '@tachybase/schema'; import { autorun } from '@tachybase/schema';
@ -16,7 +15,7 @@ import {
import { useCreateActionProps } from './schema-initializer/actions/hooks/useCreateActionProps'; import { useCreateActionProps } from './schema-initializer/actions/hooks/useCreateActionProps';
import { useGetCustomAssociatedComponents } from './hooks/useGetCustomAssociatedComponents'; import { useGetCustomAssociatedComponents } from './hooks/useGetCustomAssociatedComponents';
import { useGetCustomComponents } from './hooks/useGetCustomComponents'; import { useGetCustomComponents } from './hooks/useGetCustomComponents';
import { AdminLayout, DetailsPage, HomePage, OutboundPage, PageLayout } from './pages'; import { AdminLayout, DetailsPage, HomePage, PageLayout } from './pages';
import { PluginSettingsHelper } from './settings-manager-components'; import { PluginSettingsHelper } from './settings-manager-components';
import { import {
AssociatedFieldInterface, AssociatedFieldInterface,
@ -39,12 +38,7 @@ import {
ExcelFile, ExcelFile,
} from './components'; } from './components';
import { AutoComplete } from './schema-components'; import { AutoComplete } from './schema-components';
import { import { CreateSubmitActionInitializer, GroupBlockPlugin, SettingBlockInitializer } from './schema-initializer';
CreateSubmitActionInitializer,
GroupBlockPlugin,
OutboundActionHelper,
SettingBlockInitializer,
} from './schema-initializer';
import { import {
SheetBlock, SheetBlock,
SheetBlockInitializer, SheetBlockInitializer,
@ -67,8 +61,11 @@ import { PluginHeraVersion } from './features/hera-version';
import { PluginAssistant } from './features/assistant'; import { PluginAssistant } from './features/assistant';
import { TstzrangeFieldInterface } from './interfaces/TstzrangeFieldInterface'; import { TstzrangeFieldInterface } from './interfaces/TstzrangeFieldInterface';
import { PluginContextMenu } from './features/context-menu'; import { PluginContextMenu } from './features/context-menu';
import { PluginWorkflowBulk } from './features/workflow-bulk';
import { PluginWorkflowInterceptor } from './features/workflow-interceptor';
import { PluginPDF } from './features/pdf'; import { PluginPDF } from './features/pdf';
import { PluginExtendedFilterForm } from './features/extended-filter-form'; import { PluginExtendedFilterForm } from './features/extended-filter-form';
import { PluginOutbound } from './features/outbound';
export { usePDFViewerRef } from './schema-initializer'; export { usePDFViewerRef } from './schema-initializer';
export * from './components/custom-components/custom-components'; export * from './components/custom-components/custom-components';
@ -83,8 +80,11 @@ export class PluginCoreClient extends Plugin {
await this.app.pm.add(PluginHeraVersion); await this.app.pm.add(PluginHeraVersion);
await this.app.pm.add(PluginContextMenu); await this.app.pm.add(PluginContextMenu);
await this.app.pm.add(PluginAssistant); await this.app.pm.add(PluginAssistant);
await this.app.pm.add(PluginWorkflowBulk);
await this.app.pm.add(PluginWorkflowInterceptor);
await this.app.pm.add(PluginPDF); await this.app.pm.add(PluginPDF);
await this.app.pm.add(PluginExtendedFilterForm); await this.app.pm.add(PluginExtendedFilterForm);
await this.app.pm.add(PluginOutbound);
} }
async registerSettings() { async registerSettings() {
@ -162,10 +162,6 @@ export class PluginCoreClient extends Plugin {
} }
async registerRouters() { async registerRouters() {
this.app.router.add('outbound', {
path: '/r/:id',
element: <OutboundPage />,
});
this.app.router.remove('root'); this.app.router.remove('root');
this.app.router.add('home', { this.app.router.add('home', {
path: '/', path: '/',
@ -242,7 +238,6 @@ export class PluginCoreClient extends Plugin {
async load() { async load() {
this.locale = new Locale(this.app); this.locale = new Locale(this.app);
await new OutboundActionHelper(this.app).load();
await new PluginSettingsHelper(this.app).load(); await new PluginSettingsHelper(this.app).load();
await this.registerScopesAndComponents(); await this.registerScopesAndComponents();
await this.registerSettings(); await this.registerSettings();

View File

@ -1,4 +1,4 @@
import { css } from '@emotion/css'; import { css } from '@nocobase/client';
import { useSessionStorageState } from 'ahooks'; import { useSessionStorageState } from 'ahooks';
import { App, Layout } from 'antd'; import { App, Layout } from 'antd';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';

View File

@ -1,15 +1,8 @@
import React from 'react'; import React from 'react';
import { import { DataBlockProvider, RecordProvider, RemoteSchemaComponent, css, PageHeader } from '@nocobase/client';
CollectionRecordProvider,
DataBlockProvider,
RecordProvider,
RemoteSchemaComponent,
css,
} from '@nocobase/client';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { pathToRegexp } from 'path-to-regexp'; import { pathToRegexp } from 'path-to-regexp';
import { useHeadStyles } from './style'; import { useHeadStyles } from './style';
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
export const DetailsPage: React.FC = () => { export const DetailsPage: React.FC = () => {
const params = useParams<any>(); const params = useParams<any>();
@ -32,7 +25,7 @@ export const DetailsPage: React.FC = () => {
`} `}
> >
<div className={`${styles['.pageHeaderCss']}`}> <div className={`${styles['.pageHeaderCss']}`}>
<AntdPageHeader <PageHeader
ghost={false} ghost={false}
title={'详情页面'} title={'详情页面'}
onBack={() => { onBack={() => {

View File

@ -1,5 +1,5 @@
export * from './AdminLayout'; export * from './AdminLayout';
export * from './DetailsPage'; export * from './DetailsPage';
export * from './Home'; export * from './Home';
export * from './OutboundPage'; export * from '../features/outbound/OutboundPage';
export * from './PageLayout'; export * from './PageLayout';

View File

@ -1,4 +1,4 @@
import { createStyles } from 'antd-style'; import { createStyles } from '@nocobase/client';
export const useStyles = createStyles(({ css }) => ({ export const useStyles = createStyles(({ css }) => ({
home: css` home: css`

View File

@ -1,6 +1,6 @@
export * from './items/CustomFilterFormItemInitializer'; export * from './items/CustomFilterFormItemInitializer';
export * from './blocks/PDFVIewerBlockInitializer'; export * from './blocks/PDFVIewerBlockInitializer';
export * from './actions/OutboundLinkActionInitializer'; export * from '../features/outbound/OutboundLinkActionInitializer';
export * from './actions/CreateSubmitActionInitializer'; export * from './actions/CreateSubmitActionInitializer';
export * from './blocks/SettingBlockInitializer'; export * from './blocks/SettingBlockInitializer';
export * from './blocks/GroupBlockInitializer'; export * from './blocks/GroupBlockInitializer';

View File

@ -15,7 +15,6 @@ import {
useDesignable, useDesignable,
useFormBlockContext, useFormBlockContext,
useFormBlockType, useFormBlockType,
useLinkageCollectionFilterOptions,
useLocalVariables, useLocalVariables,
useRecord, useRecord,
useSchemaTemplateManager, useSchemaTemplateManager,
@ -24,7 +23,7 @@ import {
VariableScopeProvider, VariableScopeProvider,
} from '@nocobase/client'; } from '@nocobase/client';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from '../locale'; import { useTranslation } from '../locale';
import { FormFilterScope } from '../components/filter-form/FormFilterScope'; import { FormFilterScope } from '../components/filter-form/FormFilterScope';
import { useFieldComponents } from '../schema-initializer'; import { useFieldComponents } from '../schema-initializer';

View File

@ -12,10 +12,13 @@
"Configure": "Configure", "Configure": "Configure",
"Copy Failed": "Copy Failed", "Copy Failed": "Copy Failed",
"Copy embedded link": "Copy embedded link", "Copy embedded link": "Copy embedded link",
"Copy link": "Copy link",
"Copy successful": "Copy successful", "Copy successful": "Copy successful",
"Create a record": "Create a record",
"Custom filter": "Custom filter", "Custom filter": "Custom filter",
"Custom option label": "Custom option label", "Custom option label": "Custom option label",
"Delete department": "Delete department", "Delete department": "Delete department",
"Delete": "Delete",
"Department name": "Department name", "Department name": "Department name",
"Department": "Department", "Department": "Department",
"Departments management": "Departments management", "Departments management": "Departments management",
@ -37,6 +40,7 @@
"Owners": "Owners", "Owners": "Owners",
"Page style": "Page Style", "Page style": "Page Style",
"Pagination": "Pagination", "Pagination": "Pagination",
"Parameters": "Parameters",
"Pick a data entry for viewing and editing": "Pick a data entry for viewing and editing", "Pick a data entry for viewing and editing": "Pick a data entry for viewing and editing",
"Please select departments": "Please select departments", "Please select departments": "Please select departments",
"Please select members": "Please select members", "Please select members": "Please select members",
@ -45,6 +49,9 @@
"Remove departments": "Remove departments", "Remove departments": "Remove departments",
"Remove member": "Remove member", "Remove member": "Remove member",
"Remove members": "Remove members", "Remove members": "Remove members",
"Request inteception": "Request inteception",
"Request to intercept": "Request to intercept",
"Role of user acted": "Role of user acted",
"Roles management": "Roles management", "Roles management": "Roles management",
"Search for departments, users": "Search for departments, users", "Search for departments, users": "Search for departments, users",
"Search results": "Search results", "Search results": "Search results",
@ -56,15 +63,23 @@
"System setting": "System setting", "System setting": "System setting",
"The department has members, please remove them first": "The department has members, please remove them first", "The department has members, please remove them first": "The department has members, please remove them first",
"The department has sub-departments, please delete them first": "The department has sub-departments, please delete them first", "The department has sub-departments, please delete them first": "The department has sub-departments, please delete them first",
"The following selected requests all trigger interception on submission": "The following selected requests all trigger interception on submission",
"Third-party integration configuration": "Third-party integration configuration", "Third-party integration configuration": "Third-party integration configuration",
"This field is currently not supported for use in form blocks.": "This field is currently not supported for use in form blocks.", "This field is currently not supported for use in form blocks.": "This field is currently not supported for use in form blocks.",
"This month": "This month", "This month": "This month",
"This year": "This year", "This year": "This year",
"Trigger mode": "Trigger mode",
"Triggered before submitting a create, update, or delete request to a collection, so that data validation, logical judgment, etc., can be performed before executing the request, and then either approving the request or rejecting the request by using the \"End process\" node.": "Triggered before submitting a create, update, or delete request to a collection, so that data validation, logical judgment, etc., can be performed before executing the request, and then either approving the request or rejecting the request by using the \"End process\" node.",
"Triggered only when a form bound to this workflow is submitted": "Triggered only when a form bound to this workflow is submitted",
"Update a record": "Update a record",
"User acted": "User acted",
"Values submitted": "Values submitted",
"classical": "classical", "classical": "classical",
"component": "component", "component": "component",
"default": "default", "default": "default",
"error": "error", "error": "error",
"loading...": "loading...", "loading...": "loading...",
"outbound": "outbound",
"preview block": "preview block", "preview block": "preview block",
"tabs": "tabs", "tabs": "tabs",
"year": "year" "year": "year"

View File

@ -12,21 +12,26 @@
"Configure": "配置", "Configure": "配置",
"Copy Failed": "复制失败", "Copy Failed": "复制失败",
"Copy embedded link": "复制嵌入链接", "Copy embedded link": "复制嵌入链接",
"Copy link": "复制链接",
"Copy successful": "复制成功", "Copy successful": "复制成功",
"Create record action": "创建记录操作",
"Custom filter": "自定义筛选", "Custom filter": "自定义筛选",
"Custom option label": "自定义选项标签", "Custom option label": "自定义选项标签",
"Delete department": "删除部门", "Delete department": "删除部门",
"Delete record action": "删除记录操作",
"Department name": "部门名称", "Department name": "部门名称",
"Department": "部门", "Department": "部门",
"Departments management": "部门管理", "Departments management": "部门管理",
"Departments": "部门", "Departments": "部门",
"Edit department": "编辑部门", "Edit department": "编辑部门",
"Global mode, triggered before executing the following actions": "全局模式,以下操作执行前都触发",
"Group block": "汇总区块", "Group block": "汇总区块",
"Group": "汇总", "Group": "汇总",
"Hera integration": "赫拉集成", "Hera integration": "赫拉集成",
"HomePage Config": "主页配置", "HomePage Config": "主页配置",
"Last month": "上月", "Last month": "上月",
"Link manager": "链接管理器", "Link manager": "链接管理器",
"Local mode, triggered before executing the actions bound to this workflow": "局部模式,绑定该工作流的操作执行前触发",
"Main department": "主属部门", "Main department": "主属部门",
"Main": "主属部门", "Main": "主属部门",
"Mobile UI": "移动端页面", "Mobile UI": "移动端页面",
@ -37,17 +42,21 @@
"Owners": "负责人", "Owners": "负责人",
"Page style": "页面版本", "Page style": "页面版本",
"Pagination": "分页", "Pagination": "分页",
"Parameters": "参数",
"Pick a data entry for viewing and editing": "选择一条数据用于查看与编辑", "Pick a data entry for viewing and editing": "选择一条数据用于查看与编辑",
"Please select departments": "请选择部门", "Please select departments": "请选择部门",
"Please select members": "请选择成员", "Please select members": "请选择成员",
"Please select": "请选择", "Please select": "请选择",
"Pre-action event": "操作前事件",
"Remove department": "移除部门", "Remove department": "移除部门",
"Remove departments": "移除部门", "Remove departments": "移除部门",
"Remove member": "移除成员", "Remove member": "移除成员",
"Remove members": "移除成员", "Remove members": "移除成员",
"Role of user acted": "操作者角色",
"Roles management": "角色管理", "Roles management": "角色管理",
"Search for departments, users": "搜索部门、用户", "Search for departments, users": "搜索部门、用户",
"Search results": "搜索结果", "Search results": "搜索结果",
"Select actions": "选择操作",
"Select submit data type": "增量提交", "Select submit data type": "增量提交",
"Set as main department": "设置为主属部门", "Set as main department": "设置为主属部门",
"Signature input": "手写签名", "Signature input": "手写签名",
@ -60,11 +69,17 @@
"This field is currently not supported for use in form blocks.": "该字段目前不支持在表单区块中使用。", "This field is currently not supported for use in form blocks.": "该字段目前不支持在表单区块中使用。",
"This month": "本月", "This month": "本月",
"This year": "今年", "This year": "今年",
"Trigger mode": "触发模式",
"Triggered before the execution of a request initiated through an action button or API, such as before adding, updating, or deleting data. Suitable for data validation and logic judgment before action, and the request could be rejected by using the \"End process\" node.": "通过操作按钮或 API 发起请求并在执行前触发,比如新增、更新、删除数据之前。适用于在操作前进行数据验证、逻辑判断,并可通过“结束节点”来拦截请求。",
"Update record action": "更新记录操作",
"User acted": "操作者",
"Values submitted": "提交的值对象",
"classical": "经典版", "classical": "经典版",
"component": "组件", "component": "组件",
"default": "默认", "default": "默认",
"error": "出错了", "error": "出错了",
"loading...": "加载中...", "loading...": "加载中...",
"outbound": "外链",
"preview block": "预览区块", "preview block": "预览区块",
"tabs": "多标签", "tabs": "多标签",
"year": "年" "year": "年"

View File

@ -0,0 +1,7 @@
import { Trigger } from '@tachybase/plugin-workflow';
export class ApiTrigger extends Trigger {
static TYPE = 'api';
on() {}
off() {}
}

View File

@ -0,0 +1,114 @@
import PluginErrorHandler from '@nocobase/plugin-error-handler';
import { Trigger, EXECUTION_STATUS } from '@tachybase/plugin-workflow';
import { joinCollectionName } from '@nocobase/data-source-manager';
import type { Context, Next } from '@nocobase/actions';
import PluginWorkflow from '@tachybase/plugin-workflow';
class RequestInterceptionError extends Error {
status = 400;
messages = [];
constructor(message) {
super(message);
this.name = 'RequestInterceptionError';
}
}
export class RequestInterceptionTrigger extends Trigger {
static TYPE = 'request-interception';
sync = true;
middleware = async (context: Context, next: Next) => {
const {
resourceName,
actionName,
params: { filterByTk, filter, values, triggerWorkflows = '' },
} = context.action;
const dataSourceHeader = context.get('x-data-source');
const jointCollectionName = joinCollectionName(dataSourceHeader, resourceName);
const triggerWorkflowsMap = new Map();
const triggerWorkflowsArray = [];
for (const trigger of triggerWorkflows.split(',')) {
const [key, path] = trigger.split('!');
triggerWorkflowsMap.set(key, path);
triggerWorkflowsArray.push(key);
}
const workflows = Array.from(this.workflow.enabledCache.values()).filter(
(item) => item.type === RequestInterceptionTrigger.TYPE && item.config.collection === jointCollectionName,
);
const globalWorkflows = workflows
.filter((item) => {
return item.config.global && item.config.actions?.includes(actionName);
})
.sort((a, b) => a.id - b.id);
const localWorkflows = workflows
.filter((item) => !item.config.global)
.sort((a, b) => {
const aIndex = triggerWorkflowsArray.indexOf(a.key);
const bIndex = triggerWorkflowsArray.indexOf(b.key);
if (aIndex === -1 && bIndex === -1) {
return a.id - b.id;
}
if (aIndex === -1) {
return 1;
}
if (bIndex === -1) {
return -1;
}
return aIndex - bIndex;
});
for (const workflow of localWorkflows.concat(globalWorkflows)) {
if (!workflow.config.global && !triggerWorkflowsMap.has(workflow.key)) {
continue;
}
const processor = await this.workflow.trigger(
workflow,
{
user: context.state.currentUser,
roleName: context.state.currentRole,
params: {
filterByTk,
filter,
values,
},
},
{ httpContext: context },
);
if (!processor) {
return context.throw(500);
}
const { lastSavedJob, nodesMap } = processor;
const lastNode = nodesMap.get(lastSavedJob?.nodeId);
if (processor.execution.status === EXECUTION_STATUS.RESOLVED) {
if (lastNode?.type === 'end') {
return;
}
continue;
}
if (processor.execution.status < EXECUTION_STATUS.STARTED) {
if (lastNode?.type !== 'end') {
return context.throw(500, 'Workflow on your action failed, please contact the administrator');
}
const err = new RequestInterceptionError('Request is intercepted by workflow');
err.status = 400;
err.messages = context.state.messages;
return context.throw(err.status, err);
}
return context.throw(500, 'Workflow on your action hangs, please contact the administrator');
}
await next();
};
constructor(workflow: PluginWorkflow) {
super(workflow);
workflow.app.use(this.middleware, { after: 'dataSource' });
workflow.app.pm.get(PluginErrorHandler).errorHandler.register(
(err) => err instanceof RequestInterceptionError,
async (err, ctx, next) => {
ctx.body = {
errors: err.messages,
};
ctx.status = err.status;
},
);
}
on() {}
off() {}
}

View File

@ -0,0 +1,12 @@
import { Plugin } from '@nocobase/server';
import PluginWorkflow from '@tachybase/plugin-workflow';
import { RequestInterceptionTrigger } from './RequestInterceptionTrigger';
import { ApiTrigger } from './ApiTrigger';
export class PluginInterception extends Plugin {
async load() {
const workflowPlugin = this.app.pm.get(PluginWorkflow) as PluginWorkflow;
workflowPlugin.registerTrigger('request-interception', RequestInterceptionTrigger);
workflowPlugin.registerTrigger('api', ApiTrigger);
}
}

View File

@ -10,12 +10,15 @@ import './actions';
import { Container } from '@nocobase/utils'; import { Container } from '@nocobase/utils';
import { DepartmentsPlugin } from './features/departments'; import { DepartmentsPlugin } from './features/departments';
import TstzrangeField from './fields/tstzrange'; import TstzrangeField from './fields/tstzrange';
import { PluginInterception } from './features/interception';
export class PluginCoreServer extends Plugin { export class PluginCoreServer extends Plugin {
pluginDepartments: DepartmentsPlugin; pluginDepartments: DepartmentsPlugin;
pluginInterception: PluginInterception;
constructor(app: Application, options?: PluginOptions) { constructor(app: Application, options?: PluginOptions) {
super(app, options); super(app, options);
this.pluginDepartments = new DepartmentsPlugin(app, options); this.pluginDepartments = new DepartmentsPlugin(app, options);
this.pluginInterception = new PluginInterception(app, options);
} }
async afterAdd() { async afterAdd() {
this.db.registerFieldTypes({ this.db.registerFieldTypes({
@ -28,6 +31,7 @@ export class PluginCoreServer extends Plugin {
} }
async load() { async load() {
await this.pluginDepartments.load(); await this.pluginDepartments.load();
await this.pluginInterception.load();
Container.reset(); Container.reset();
try { try {
Container.set({ id: 'db', value: this.db }); Container.set({ id: 'db', value: this.db });
@ -47,7 +51,7 @@ export class PluginCoreServer extends Plugin {
} }
} }
async install(options?: InstallOptions) { async install(options?: InstallOptions) {
this.pluginDepartments.install(options); await this.pluginDepartments.install(options);
} }
async afterEnable() {} async afterEnable() {}
async afterDisable() {} async afterDisable() {}

View File

@ -2,6 +2,7 @@ import actions, { Context, utils } from '@nocobase/actions';
import { Op, Repository } from '@nocobase/database'; import { Op, Repository } from '@nocobase/database';
import Plugin from '../Plugin'; import Plugin from '../Plugin';
import { WorkflowModel } from '../types';
export async function update(context: Context, next) { export async function update(context: Context, next) {
const repository = utils.getRepositoryFromParams(context) as Repository; const repository = utils.getRepositoryFromParams(context) as Repository;
@ -160,6 +161,14 @@ export async function sync(context: Context, next) {
await next(); await next();
} }
export async function trigger(context: Context, next) { export async function trigger(ctx: Context) {
return next(); const plugin = ctx.app.getPlugin(Plugin) as Plugin;
const workflow = (await ctx.db.getRepository('workflows').findById(ctx.action.params.filterByTk)) as WorkflowModel;
plugin.trigger(
workflow,
{
data: {},
},
{ httpContext: ctx },
);
} }

View File

@ -1507,6 +1507,9 @@ importers:
'@nocobase/utils': '@nocobase/utils':
specifier: workspace:* specifier: workspace:*
version: link:../../../core/utils version: link:../../../core/utils
'@tachybase/plugin-workflow':
specifier: workspace:*
version: link:../../@tachybase/plugin-workflow
devDependencies: devDependencies:
'@ant-design/cssinjs': '@ant-design/cssinjs':
specifier: ^1.11.1 specifier: ^1.11.1
@ -1517,9 +1520,9 @@ importers:
'@ant-design/pro-layout': '@ant-design/pro-layout':
specifier: ^7.16.11 specifier: ^7.16.11
version: 7.17.16(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0) version: 7.17.16(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0)
'@emotion/css': '@formily/antd-v5':
specifier: ^11.11.2 specifier: 1.x
version: 11.11.2 version: 1.2.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)(typescript@4.9.5)
'@react-pdf/renderer': '@react-pdf/renderer':
specifier: ^3.3.2 specifier: ^3.3.2
version: 3.3.8(react@18.2.0) version: 3.3.8(react@18.2.0)
@ -1529,15 +1532,15 @@ importers:
'@tachybase/schema': '@tachybase/schema':
specifier: workspace:* specifier: workspace:*
version: link:../../../core/schema version: link:../../../core/schema
'@types/lodash':
specifier: ~4.17.0
version: 4.17.0
ahooks: ahooks:
specifier: ^3.7.2 specifier: ^3.7.2
version: 3.7.8(react@18.2.0) version: 3.7.8(react@18.2.0)
antd: antd:
specifier: 5.16.1 specifier: 5.16.1
version: 5.16.1(react-dom@18.2.0)(react@18.2.0) version: 5.16.1(react-dom@18.2.0)(react@18.2.0)
antd-style:
specifier: 3.4.5
version: 3.4.5(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0)
classnames: classnames:
specifier: ^2.3.1 specifier: ^2.3.1
version: 2.5.1 version: 2.5.1
@ -7511,6 +7514,19 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
tslib: 2.6.2 tslib: 2.6.2
/@dnd-kit/core@6.1.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@dnd-kit/accessibility': 3.1.0(react@18.2.0)
'@dnd-kit/utilities': 3.2.2(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
tslib: 2.6.2
dev: true
/@dnd-kit/modifiers@6.0.1(@dnd-kit/core@5.0.3)(react@18.2.0): /@dnd-kit/modifiers@6.0.1(@dnd-kit/core@5.0.3)(react@18.2.0):
resolution: {integrity: sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==} resolution: {integrity: sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==}
peerDependencies: peerDependencies:
@ -7535,6 +7551,18 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: false dev: false
/@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.1.0)(react@18.2.0):
resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==}
peerDependencies:
'@dnd-kit/core': ^6.0.7
react: '>=16.8.0'
dependencies:
'@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0)
'@dnd-kit/utilities': 3.2.2(react@18.2.0)
react: 18.2.0
tslib: 2.6.2
dev: true
/@dnd-kit/utilities@3.2.2(react@18.2.0): /@dnd-kit/utilities@3.2.2(react@18.2.0):
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
peerDependencies: peerDependencies:
@ -8754,6 +8782,40 @@ packages:
resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
dev: true dev: true
/@formily/antd-v5@1.2.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)(typescript@4.9.5):
resolution: {integrity: sha512-6K1fV7pwvy7j5KoO/5dEe5llosann7211I/bH/WJkLQtDSDK+XLFUQ4/lU7CdNpwHMrspzXS3lF82EM3hGyM8w==}
engines: {npm: '>=3.0.0'}
peerDependencies:
'@types/react': '>=16.8.0 || >=17.0.0'
'@types/react-dom': '>=16.8.0 || >=17.0.0'
antd: ^5.13.0
react: '>=16.8.0 || >=17.0.0'
react-dom: '>=16.8.0'
react-is: '>=16.8.0 || >=17.0.0'
dependencies:
'@ant-design/cssinjs': 1.20.0(react-dom@18.2.0)(react@18.2.0)
'@ant-design/icons': 5.3.6(react-dom@18.2.0)(react@18.2.0)
'@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0)
'@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.1.0)(react@18.2.0)
'@formily/core': 2.2.27
'@formily/grid': 2.2.27(typescript@4.9.5)
'@formily/json-schema': 2.2.27(typescript@4.9.5)
'@formily/react': 2.2.27(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)(typescript@4.9.5)
'@formily/reactive': 2.2.27
'@formily/reactive-react': 2.2.27(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)
'@formily/shared': 2.2.27
'@types/react': 18.2.79
'@types/react-dom': 18.2.25
antd: 5.16.1(react-dom@18.2.0)(react@18.2.0)
classnames: 2.5.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-is: 18.3.1
react-sticky-box: 1.0.2(react@18.2.0)
transitivePeerDependencies:
- typescript
dev: true
/@formily/core@2.2.27: /@formily/core@2.2.27:
resolution: {integrity: sha512-f8e4f/WlimOA7zN1ARmiRKMlIpsIH00XxT7NWAE7EV3DO2B6sq6gSq9GDc5JLwHqhiyJBAIPzUdfmIpRLwPxyQ==} resolution: {integrity: sha512-f8e4f/WlimOA7zN1ARmiRKMlIpsIH00XxT7NWAE7EV3DO2B6sq6gSq9GDc5JLwHqhiyJBAIPzUdfmIpRLwPxyQ==}
engines: {npm: '>=3.0.0'} engines: {npm: '>=3.0.0'}
@ -8771,7 +8833,6 @@ packages:
'@formily/reactive': 2.2.27 '@formily/reactive': 2.2.27
'@juggle/resize-observer': 3.4.0 '@juggle/resize-observer': 3.4.0
typescript: 4.9.5 typescript: 4.9.5
dev: false
/@formily/json-schema@2.2.27(typescript@4.9.5): /@formily/json-schema@2.2.27(typescript@4.9.5):
resolution: {integrity: sha512-YqtpPp9bmXO28iw10ZLT5KCAqipr2SwPrzfskUJ/Oa8O3xZi64+9TMVPylX73cBe49lVaOgFiQPNAljLudeveg==} resolution: {integrity: sha512-YqtpPp9bmXO28iw10ZLT5KCAqipr2SwPrzfskUJ/Oa8O3xZi64+9TMVPylX73cBe49lVaOgFiQPNAljLudeveg==}
@ -8783,7 +8844,6 @@ packages:
'@formily/reactive': 2.2.27 '@formily/reactive': 2.2.27
'@formily/shared': 2.2.27 '@formily/shared': 2.2.27
typescript: 4.9.5 typescript: 4.9.5
dev: false
/@formily/json-schema@2.2.27(typescript@5.4.5): /@formily/json-schema@2.2.27(typescript@5.4.5):
resolution: {integrity: sha512-YqtpPp9bmXO28iw10ZLT5KCAqipr2SwPrzfskUJ/Oa8O3xZi64+9TMVPylX73cBe49lVaOgFiQPNAljLudeveg==} resolution: {integrity: sha512-YqtpPp9bmXO28iw10ZLT5KCAqipr2SwPrzfskUJ/Oa8O3xZi64+9TMVPylX73cBe49lVaOgFiQPNAljLudeveg==}
@ -8830,7 +8890,6 @@ packages:
react-is: 18.3.1 react-is: 18.3.1
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
dev: false
/@formily/reactive-react@2.2.27(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0): /@formily/reactive-react@2.2.27(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0):
resolution: {integrity: sha512-LJefK/9g8MorhGexMiVF+FVWyMR/Vc4C63Djrj08XeXtTF0NUNUUKVBT1eZqmtdRHJSyDE7SSgUmpxcXP7QmQQ==} resolution: {integrity: sha512-LJefK/9g8MorhGexMiVF+FVWyMR/Vc4C63Djrj08XeXtTF0NUNUUKVBT1eZqmtdRHJSyDE7SSgUmpxcXP7QmQQ==}
@ -8854,7 +8913,6 @@ packages:
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
react-is: 18.3.1 react-is: 18.3.1
dev: false
/@formily/reactive@2.2.27: /@formily/reactive@2.2.27:
resolution: {integrity: sha512-ksdA4uIw3WMo0FmHcGsyqZgPwhujVgtXqxiWPSV+Tc2eEoJrInZhr15Q0IeTQ6glMh8izNsb19YK+PphBHwXGg==} resolution: {integrity: sha512-ksdA4uIw3WMo0FmHcGsyqZgPwhujVgtXqxiWPSV+Tc2eEoJrInZhr15Q0IeTQ6glMh8izNsb19YK+PphBHwXGg==}
@ -9105,7 +9163,6 @@ packages:
/@juggle/resize-observer@3.4.0: /@juggle/resize-observer@3.4.0:
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
dev: false
/@koa/cors@3.4.3: /@koa/cors@3.4.3:
resolution: {integrity: sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==} resolution: {integrity: sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==}
@ -13083,7 +13140,7 @@ packages:
'@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4) '@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4)
'@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4) '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4)
react-refresh: 0.14.0 react-refresh: 0.14.0
vite: 4.5.2(@types/node@20.12.2)(less@4.1.3) vite: 4.5.2(@types/node@20.12.7)(less@4.1.3)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -26145,7 +26202,6 @@ packages:
dependencies: dependencies:
react: 18.2.0 react: 18.2.0
resize-observer-polyfill: 1.5.1 resize-observer-polyfill: 1.5.1
dev: false
/react-to-print@2.14.15(react-dom@18.2.0)(react@18.2.0): /react-to-print@2.14.15(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-SKnwOzU2cJ8eaAkoJO7+gNhvfEDmm+Y34IdcHsjtHioUevUPhprqbVtvNJlZ2JkGJ8ExK2QNWM9pXECTDR5D8w==} resolution: {integrity: sha512-SKnwOzU2cJ8eaAkoJO7+gNhvfEDmm+Y34IdcHsjtHioUevUPhprqbVtvNJlZ2JkGJ8ExK2QNWM9pXECTDR5D8w==}
@ -29961,6 +30017,7 @@ packages:
rollup: 3.29.4 rollup: 3.29.4
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
dev: false
/vite@4.5.2(@types/node@20.12.7)(less@4.1.3): /vite@4.5.2(@types/node@20.12.7)(less@4.1.3):
resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==} resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==}
@ -29997,7 +30054,6 @@ packages:
rollup: 3.29.4 rollup: 3.29.4
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true
/vite@5.1.5(@types/node@20.12.7)(sass@1.75.0): /vite@5.1.5(@types/node@20.12.7)(sass@1.75.0):
resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==} resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}