mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
feat(plugin-workflow): add workflow task panel in toolbar (#5858)
* feat(plugin-workflow): add workflow task panel in toolbar * fix(plugin-workflow): fix type * fix(plugin-workflow): fix manual task list filter * fix(plugin-workflow): add tooltip for task button
This commit is contained in:
parent
631cea1df9
commit
56c03f3fef
@ -7,21 +7,45 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { ExtendCollectionsProvider, storePopupContext } from '@nocobase/client';
|
||||
import React, { FC } from 'react';
|
||||
import { ExtendCollectionsProvider, storePopupContext, useRequest } from '@nocobase/client';
|
||||
import React, { createContext, FC, useContext } from 'react';
|
||||
import { getWorkflowTodoViewActionSchema, nodeCollection, todoCollection, workflowCollection } from './WorkflowTodo';
|
||||
import { JOB_STATUS } from '@nocobase/plugin-workflow/client';
|
||||
|
||||
const collections = [nodeCollection, workflowCollection, todoCollection];
|
||||
|
||||
const ManualTaskCountRequestContext = createContext({});
|
||||
|
||||
/**
|
||||
* 1. 扩展几个工作流相关的 collection,防止在区块中因找不到 collection 而报错;
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const WorkflowManualProvider: FC = (props) => {
|
||||
return <ExtendCollectionsProvider collections={collections}>{props.children}</ExtendCollectionsProvider>;
|
||||
const request = useRequest<any>(
|
||||
{
|
||||
resource: 'users_jobs',
|
||||
action: 'countMine',
|
||||
params: {
|
||||
filter: {
|
||||
status: JOB_STATUS.PENDING,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ manual: true },
|
||||
);
|
||||
|
||||
return (
|
||||
<ExtendCollectionsProvider collections={collections}>
|
||||
<ManualTaskCountRequestContext.Provider value={request}>{props.children}</ManualTaskCountRequestContext.Provider>
|
||||
</ExtendCollectionsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useCountRequest() {
|
||||
return useContext(ManualTaskCountRequestContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. 将区块相关的按钮 Schema 缓存起来,这样就可以在弹窗中获取到 Schema,进而实现“弹窗 URL”的功能;
|
||||
*/
|
||||
|
@ -14,11 +14,13 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
css,
|
||||
useCollection,
|
||||
SchemaInitializerItem,
|
||||
useCollectionRecordData,
|
||||
useCompile,
|
||||
useOpenModeContext,
|
||||
usePlugin,
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
} from '@nocobase/client';
|
||||
|
||||
import {
|
||||
@ -44,6 +46,7 @@ import WorkflowPlugin, {
|
||||
import { NAMESPACE, useLang } from '../locale';
|
||||
import { FormBlockProvider } from './instruction/FormBlockProvider';
|
||||
import { ManualFormType, manualFormTypes } from './instruction/SchemaConfig';
|
||||
import { TableOutlined } from '@ant-design/icons';
|
||||
|
||||
export const nodeCollection = {
|
||||
title: `{{t("Task", { ns: "${NAMESPACE}" })}}`,
|
||||
@ -232,9 +235,94 @@ function UserJobStatusColumn(props) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
export const WorkflowTodo: React.FC & { Drawer: React.FC; Decorator: React.FC } = () => {
|
||||
const tableColumns = {
|
||||
workflow: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: null,
|
||||
},
|
||||
title: `{{t("Workflow", { ns: "workflow" })}}`,
|
||||
properties: {
|
||||
workflow: {
|
||||
'x-component': 'WorkflowColumn',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
node: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: null,
|
||||
},
|
||||
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
||||
properties: {
|
||||
node: {
|
||||
'x-component': 'NodeColumn',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: 100,
|
||||
},
|
||||
title: `{{t("Status", { ns: "workflow" })}}`,
|
||||
properties: {
|
||||
status: {
|
||||
type: 'number',
|
||||
'x-decorator': 'UserJobStatusColumn',
|
||||
'x-component': 'CollectionField',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: 140,
|
||||
},
|
||||
title: `{{t("Assignee", { ns: "${NAMESPACE}" })}}`,
|
||||
properties: {
|
||||
user: {
|
||||
'x-component': 'UserColumn',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: 160,
|
||||
},
|
||||
properties: {
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const WorkflowTodo: React.FC<{ columns?: string[] }> & {
|
||||
Initializer: React.FC;
|
||||
Drawer: React.FC;
|
||||
Decorator: React.FC;
|
||||
TaskBlock: React.FC;
|
||||
} = (props) => {
|
||||
const { columns = Object.keys(tableColumns) } = props;
|
||||
const { defaultOpenMode } = useOpenModeContext();
|
||||
const collection = useCollection();
|
||||
|
||||
return (
|
||||
<SchemaComponent
|
||||
@ -301,86 +389,13 @@ export const WorkflowTodo: React.FC & { Drawer: React.FC; Decorator: React.FC }
|
||||
},
|
||||
title: '{{t("Actions")}}',
|
||||
properties: {
|
||||
view: getWorkflowTodoViewActionSchema({ defaultOpenMode, collectionName: collection.name }),
|
||||
},
|
||||
},
|
||||
node: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: null,
|
||||
},
|
||||
title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`,
|
||||
properties: {
|
||||
node: {
|
||||
'x-component': 'NodeColumn',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
workflow: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: null,
|
||||
},
|
||||
title: `{{t("Workflow", { ns: "workflow" })}}`,
|
||||
properties: {
|
||||
workflow: {
|
||||
'x-component': 'WorkflowColumn',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: 100,
|
||||
},
|
||||
title: `{{t("Status", { ns: "workflow" })}}`,
|
||||
properties: {
|
||||
status: {
|
||||
type: 'number',
|
||||
'x-decorator': 'UserJobStatusColumn',
|
||||
'x-component': 'CollectionField',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: 140,
|
||||
},
|
||||
title: `{{t("Assignee", { ns: "${NAMESPACE}" })}}`,
|
||||
properties: {
|
||||
user: {
|
||||
'x-component': 'UserColumn',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
type: 'void',
|
||||
'x-decorator': 'TableV2.Column.Decorator',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-component-props': {
|
||||
width: 160,
|
||||
},
|
||||
properties: {
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
'x-component': 'CollectionField',
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
view: getWorkflowTodoViewActionSchema({ defaultOpenMode, collectionName: 'users_jobs' }),
|
||||
},
|
||||
},
|
||||
...columns.reduce((schema, key) => {
|
||||
schema[key] = tableColumns[key];
|
||||
return schema;
|
||||
}, {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -410,6 +425,7 @@ export function getWorkflowTodoViewActionSchema({ defaultOpenMode, collectionNam
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
'x-component': WorkflowTodo.Drawer,
|
||||
},
|
||||
},
|
||||
@ -671,7 +687,8 @@ function Drawer() {
|
||||
);
|
||||
}
|
||||
|
||||
function Decorator({ params = {}, children }) {
|
||||
function Decorator(props) {
|
||||
const { params = {}, children } = props;
|
||||
const blockProps = {
|
||||
collection: 'users_jobs',
|
||||
resource: 'users_jobs',
|
||||
@ -680,6 +697,9 @@ function Decorator({ params = {}, children }) {
|
||||
pageSize: 20,
|
||||
sort: ['-createdAt'],
|
||||
...params,
|
||||
filter: {
|
||||
...params.filter,
|
||||
},
|
||||
appends: ['user', 'node', 'workflow', 'execution.status'],
|
||||
except: ['node.config', 'workflow.config', 'workflow.options'],
|
||||
},
|
||||
@ -695,5 +715,79 @@ function Decorator({ params = {}, children }) {
|
||||
);
|
||||
}
|
||||
|
||||
function Initializer() {
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
const { insert } = useSchemaInitializer();
|
||||
return (
|
||||
<SchemaInitializerItem
|
||||
icon={<TableOutlined />}
|
||||
{...itemConfig}
|
||||
onClick={() => {
|
||||
insert({
|
||||
type: 'void',
|
||||
'x-decorator': 'WorkflowTodo.Decorator',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'CardItem',
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': 'blockSettings:table',
|
||||
properties: {
|
||||
todos: {
|
||||
type: 'void',
|
||||
'x-component': 'WorkflowTodo',
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
WorkflowTodo.Initializer = Initializer;
|
||||
WorkflowTodo.Drawer = Drawer;
|
||||
WorkflowTodo.Decorator = Decorator;
|
||||
WorkflowTodo.TaskBlock = TaskBlock;
|
||||
|
||||
function TaskBlock() {
|
||||
const { data: user } = useCurrentUserContext();
|
||||
return (
|
||||
<SchemaComponent
|
||||
components={{
|
||||
WorkflowTodo,
|
||||
}}
|
||||
schema={{
|
||||
name: 'todos',
|
||||
type: 'void',
|
||||
'x-decorator': 'WorkflowTodo.Decorator',
|
||||
'x-decorator-props': {
|
||||
params: {
|
||||
filter: {
|
||||
userId: user?.data?.id,
|
||||
},
|
||||
appends: [
|
||||
'node.id',
|
||||
'node.title',
|
||||
'job.id',
|
||||
'job.status',
|
||||
'job.result',
|
||||
'workflow.id',
|
||||
'workflow.title',
|
||||
'workflow.enabled',
|
||||
'execution.id',
|
||||
'execution.status',
|
||||
],
|
||||
},
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
todos: {
|
||||
type: 'void',
|
||||
'x-component': 'WorkflowTodo',
|
||||
'x-component-props': {
|
||||
columns: ['workflow', 'node', 'status', 'createdAt'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -13,9 +13,8 @@ import WorkflowPlugin from '@nocobase/plugin-workflow/client';
|
||||
import Manual from './instruction';
|
||||
|
||||
import { NAMESPACE } from '../locale';
|
||||
import { WorkflowManualProvider } from './WorkflowManualProvider';
|
||||
import { useCountRequest, WorkflowManualProvider } from './WorkflowManualProvider';
|
||||
import { WorkflowTodo } from './WorkflowTodo';
|
||||
import { WorkflowTodoBlockInitializer } from './WorkflowTodoBlockInitializer';
|
||||
import {
|
||||
addActionButton,
|
||||
addActionButton_deprecated,
|
||||
@ -33,10 +32,17 @@ export default class extends Plugin {
|
||||
async load() {
|
||||
this.addComponents();
|
||||
|
||||
// this.app.addProvider(Provider);
|
||||
this.app.addProvider(WorkflowManualProvider);
|
||||
|
||||
const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
|
||||
workflow.registerInstruction('manual', Manual);
|
||||
|
||||
workflow.registerTaskType('manual', {
|
||||
title: `{{t("My manual tasks", { ns: "${NAMESPACE}" })}}`,
|
||||
useCountRequest,
|
||||
component: WorkflowTodo.TaskBlock,
|
||||
});
|
||||
|
||||
this.app.schemaInitializerManager.add(addBlockButton_deprecated);
|
||||
this.app.schemaInitializerManager.add(addBlockButton);
|
||||
this.app.schemaInitializerManager.add(addActionButton_deprecated);
|
||||
@ -47,23 +53,20 @@ export default class extends Plugin {
|
||||
const blockInitializers = this.app.schemaInitializerManager.get('page:addBlock');
|
||||
blockInitializers.add('otherBlocks.workflowTodos', {
|
||||
title: `{{t("Workflow todos", { ns: "${NAMESPACE}" })}}`,
|
||||
Component: 'WorkflowTodoBlockInitializer',
|
||||
Component: 'WorkflowTodo.Initializer',
|
||||
icon: 'CheckSquareOutlined',
|
||||
});
|
||||
|
||||
this.app.schemaInitializerManager.addItem('mobile:addBlock', 'otherBlocks.workflowTodos', {
|
||||
title: `{{t("Workflow todos", { ns: "${NAMESPACE}" })}}`,
|
||||
Component: 'WorkflowTodoBlockInitializer',
|
||||
Component: 'WorkflowTodo.Initializer',
|
||||
icon: 'CheckSquareOutlined',
|
||||
});
|
||||
|
||||
this.app.addProvider(WorkflowManualProvider);
|
||||
}
|
||||
|
||||
addComponents() {
|
||||
this.app.addComponents({
|
||||
WorkflowTodo,
|
||||
WorkflowTodoBlockInitializer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -28,5 +28,6 @@
|
||||
"Workflow todos": "工作流待办",
|
||||
"Task node": "任务节点",
|
||||
"Unprocessed": "未处理",
|
||||
"Please check one of your update record form, and add at least one filter condition in form settings.": "请检查您的其中的更新数据表单,并在表单设置中至少添加一个筛选条件。"
|
||||
"Please check one of your update record form, and add at least one filter condition in form settings.": "请检查您的其中的更新数据表单,并在表单设置中至少添加一个筛选条件。",
|
||||
"My manual tasks": "我的人工任务"
|
||||
}
|
||||
|
@ -12,16 +12,13 @@ import actions from '@nocobase/actions';
|
||||
import { HandlerType } from '@nocobase/resourcer';
|
||||
import WorkflowPlugin, { JOB_STATUS } from '@nocobase/plugin-workflow';
|
||||
|
||||
import path from 'path';
|
||||
import { submit } from './actions';
|
||||
import * as jobActions from './actions';
|
||||
|
||||
import ManualInstruction from './ManualInstruction';
|
||||
|
||||
export default class extends Plugin {
|
||||
async load() {
|
||||
await this.importCollections(path.resolve(__dirname, 'collections'));
|
||||
|
||||
this.app.resource({
|
||||
this.app.resourceManager.define({
|
||||
name: 'users_jobs',
|
||||
actions: {
|
||||
list: {
|
||||
@ -40,11 +37,11 @@ export default class extends Plugin {
|
||||
},
|
||||
handler: actions.list as HandlerType,
|
||||
},
|
||||
submit,
|
||||
...jobActions,
|
||||
},
|
||||
});
|
||||
|
||||
this.app.acl.allow('users_jobs', ['list', 'get', 'submit'], 'loggedIn');
|
||||
this.app.acl.allow('users_jobs', ['list', 'get', 'submit', 'countMine'], 'loggedIn');
|
||||
|
||||
const workflowPlugin = this.app.pm.get(WorkflowPlugin) as WorkflowPlugin;
|
||||
workflowPlugin.registerInstruction('manual', ManualInstruction);
|
||||
|
@ -112,3 +112,27 @@ export async function submit(context: Context, next) {
|
||||
|
||||
plugin.resume(userJob.job);
|
||||
}
|
||||
|
||||
export async function countMine(context: Context, next) {
|
||||
const repository = utils.getRepositoryFromParams(context);
|
||||
const { currentUser } = context.state;
|
||||
|
||||
const count = await repository.count({
|
||||
filter: {
|
||||
$and: [
|
||||
{
|
||||
'workflow.enabled': true,
|
||||
},
|
||||
context.action.params.filter ?? {},
|
||||
{
|
||||
userId: currentUser.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
context,
|
||||
});
|
||||
|
||||
context.body = count;
|
||||
|
||||
await next();
|
||||
}
|
||||
|
@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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 React, { useEffect, useMemo } from 'react';
|
||||
import { Link, Outlet, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Button, Layout, Menu, Spin, Badge, theme, Tooltip } from 'antd';
|
||||
import { PageHeader } from '@ant-design/pro-layout';
|
||||
import { CheckCircleOutlined } from '@ant-design/icons';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import {
|
||||
css,
|
||||
PinnedPluginListProvider,
|
||||
SchemaComponentContext,
|
||||
SchemaComponentOptions,
|
||||
useCompile,
|
||||
usePlugin,
|
||||
} from '@nocobase/client';
|
||||
|
||||
import PluginWorkflowClient from '.';
|
||||
import { lang } from './locale';
|
||||
|
||||
const sideClass = css`
|
||||
height: calc(100vh - 46px);
|
||||
|
||||
.ant-layout-sider-children {
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface TaskTypeOptions {
|
||||
title: string;
|
||||
useCountRequest?: Function;
|
||||
component?: React.ComponentType;
|
||||
children?: TaskTypeOptions[];
|
||||
}
|
||||
|
||||
function MenuLink({ type }: any) {
|
||||
const workflowPlugin = usePlugin(PluginWorkflowClient);
|
||||
const compile = useCompile();
|
||||
const { title, useCountRequest } = workflowPlugin.taskTypes.get(type);
|
||||
const { data, loading, run } = useCountRequest?.() || { loading: false };
|
||||
useEffect(() => {
|
||||
run?.();
|
||||
}, [run]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/admin/workflow/tasks/${type}`}
|
||||
className={css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`}
|
||||
>
|
||||
<span>{compile(title)}</span>
|
||||
{loading ? <Spin /> : <Badge count={data?.data || 0} />}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function WorkflowTasks() {
|
||||
const workflowPlugin = usePlugin(PluginWorkflowClient);
|
||||
const navigate = useNavigate();
|
||||
const { taskType } = useParams();
|
||||
const compile = useCompile();
|
||||
const {
|
||||
token: { colorBgContainer },
|
||||
} = theme.useToken();
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
Array.from(workflowPlugin.taskTypes.getKeys()).map((key: string) => {
|
||||
return {
|
||||
key,
|
||||
label: <MenuLink type={key} />,
|
||||
};
|
||||
}),
|
||||
[workflowPlugin.taskTypes],
|
||||
);
|
||||
|
||||
const { title, component: Component } = useMemo<any>(
|
||||
() => workflowPlugin.taskTypes.get(taskType ?? items[0]?.key) ?? {},
|
||||
[items, taskType, workflowPlugin.taskTypes],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!taskType && items[0].key) {
|
||||
navigate(`/admin/workflow/tasks/${items[0].key}`, { replace: true });
|
||||
}
|
||||
}, [items, navigate, taskType]);
|
||||
|
||||
const key = taskType ?? items[0].key;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Sider className={sideClass} theme="light">
|
||||
<Menu mode="inline" selectedKeys={[key]} items={items} style={{ height: '100%' }} />
|
||||
</Layout.Sider>
|
||||
<Layout>
|
||||
<PageHeader
|
||||
className={classnames('pageHeaderCss', 'height0')}
|
||||
style={{ background: colorBgContainer, padding: '12px 24px 0 24px' }}
|
||||
title={compile(title)}
|
||||
/>
|
||||
<Layout.Content style={{ padding: '24px', minHeight: 280 }}>
|
||||
<SchemaComponentContext.Provider value={{ designable: false }}>
|
||||
{Component ? <Component /> : null}
|
||||
<Outlet />
|
||||
</SchemaComponentContext.Provider>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
function WorkflowTasksLink() {
|
||||
const workflowPlugin = usePlugin(PluginWorkflowClient);
|
||||
|
||||
const types = Array.from(workflowPlugin.taskTypes.getKeys());
|
||||
return types.length ? (
|
||||
<Tooltip title={lang('Workflow todos')}>
|
||||
<Button
|
||||
className={css`
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.anticon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 1em;
|
||||
}
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Link to="/admin/workflow/tasks">
|
||||
<CheckCircleOutlined />
|
||||
</Link>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export const TasksProvider = (props: any) => {
|
||||
return (
|
||||
<PinnedPluginListProvider
|
||||
items={{
|
||||
todo: { component: 'WorkflowTasksLink', pin: true, snippet: '*' },
|
||||
}}
|
||||
>
|
||||
<SchemaComponentOptions
|
||||
components={{
|
||||
WorkflowTasksLink,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SchemaComponentOptions>
|
||||
</PinnedPluginListProvider>
|
||||
);
|
||||
};
|
@ -11,7 +11,7 @@ import React from 'react';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { isValid } from '@formily/shared';
|
||||
|
||||
import { Plugin, useCompile, WorkflowConfig } from '@nocobase/client';
|
||||
import { PagePopups, Plugin, useCompile, WorkflowConfig } from '@nocobase/client';
|
||||
import { Registry } from '@nocobase/utils/client';
|
||||
|
||||
// import { ExecutionPage } from './ExecutionPage';
|
||||
@ -37,12 +37,15 @@ import { getWorkflowDetailPath, getWorkflowExecutionsPath } from './utils';
|
||||
import { lang, NAMESPACE } from './locale';
|
||||
import { customizeSubmitToWorkflowActionSettings } from './settings/customizeSubmitToWorkflowActionSettings';
|
||||
import { VariableOption } from './variable';
|
||||
import { WorkflowTasks, TasksProvider, TaskTypeOptions } from './WorkflowTasks';
|
||||
|
||||
export default class PluginWorkflowClient extends Plugin {
|
||||
triggers = new Registry<Trigger>();
|
||||
instructions = new Registry<Instruction>();
|
||||
systemVariables = new Registry<VariableOption>();
|
||||
|
||||
taskTypes = new Registry<TaskTypeOptions>();
|
||||
|
||||
useTriggersOptions = () => {
|
||||
const compile = useCompile();
|
||||
return Array.from(this.triggers.getEntities())
|
||||
@ -83,15 +86,29 @@ export default class PluginWorkflowClient extends Plugin {
|
||||
this.systemVariables.register(option.key, option);
|
||||
}
|
||||
|
||||
registerTaskType(key: string, option: TaskTypeOptions) {
|
||||
this.taskTypes.register(key, option);
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.app.router.add('admin.workflow.workflows.id', {
|
||||
this.router.add('admin.workflow.workflows.id', {
|
||||
path: getWorkflowDetailPath(':id'),
|
||||
element: <WorkflowPage />,
|
||||
Component: WorkflowPage,
|
||||
});
|
||||
|
||||
this.app.router.add('admin.workflow.executions.id', {
|
||||
this.router.add('admin.workflow.executions.id', {
|
||||
path: getWorkflowExecutionsPath(':id'),
|
||||
element: <ExecutionPage />,
|
||||
Component: ExecutionPage,
|
||||
});
|
||||
|
||||
this.router.add('admin.workflow.tasks', {
|
||||
path: '/admin/workflow/tasks/:taskType?',
|
||||
Component: WorkflowTasks,
|
||||
});
|
||||
|
||||
this.router.add('admin.workflow.tasks.popup', {
|
||||
path: '/admin/workflow/tasks/:taskType/popups/*',
|
||||
Component: PagePopups,
|
||||
});
|
||||
|
||||
this.app.pluginSettingsManager.add(NAMESPACE, {
|
||||
@ -101,6 +118,8 @@ export default class PluginWorkflowClient extends Plugin {
|
||||
aclSnippet: 'pm.workflow.workflows',
|
||||
});
|
||||
|
||||
this.app.use(TasksProvider);
|
||||
|
||||
this.app.schemaSettingsManager.add(customizeSubmitToWorkflowActionSettings);
|
||||
|
||||
this.app.schemaSettingsManager.addItem('actionSettings:delete', 'workflowConfig', {
|
||||
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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 { Instruction } from '.';
|
||||
import { NAMESPACE } from '../locale';
|
||||
import { WorkflowVariableInput } from '../variable';
|
||||
|
||||
export default class extends Instruction {
|
||||
title = `{{t("Assign output variable", { ns: "${NAMESPACE}" })}}`;
|
||||
type = 'output';
|
||||
group = 'control';
|
||||
description = `{{t("Assign variables for workflow output, which could be used in other workflows as result of subflow.", { ns: "${NAMESPACE}" })}}`;
|
||||
fieldset = {
|
||||
result: {
|
||||
type: 'object',
|
||||
title: `{{t("Value", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'WorkflowVariableInput',
|
||||
'x-component-props': {
|
||||
useTypedConstant: true,
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
components = {
|
||||
WorkflowVariableInput,
|
||||
};
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 { Instruction } from '.';
|
||||
import { NAMESPACE } from '../locale';
|
||||
import { JOB_STATUS } from '../constants';
|
||||
import { TriggerCollectionRecordSelect } from '../components/TriggerCollectionRecordSelect';
|
||||
import { Fieldset } from '../components/Fieldset';
|
||||
|
||||
export default class extends Instruction {
|
||||
title = `{{t("Call another workflow", { ns: "${NAMESPACE}" })}}`;
|
||||
type = 'subflow';
|
||||
group = 'control';
|
||||
description = `{{t("Run another workflow and use its output as variables.", { ns: "${NAMESPACE}" })}}`;
|
||||
fieldset = {
|
||||
workflow: {
|
||||
type: 'number',
|
||||
title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: `{{t("Post added", { ns: "${NAMESPACE}" })}}`, value: 1 },
|
||||
{ label: `{{t("Some other action", { ns: "${NAMESPACE}" })}}`, value: 2 },
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
context: {
|
||||
type: 'object',
|
||||
title: `{{t("Context", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Fieldset',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
title: `{{t("Trigger data", { ns: "${NAMESPACE}" })}}`,
|
||||
description: `{{t("Choose a record of the collection to trigger.", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'TriggerCollectionRecordSelect',
|
||||
default: null,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['workflow'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{workflow === 1}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
components = {
|
||||
TriggerCollectionRecordSelect,
|
||||
Fieldset,
|
||||
};
|
||||
}
|
@ -227,5 +227,7 @@
|
||||
"Add node": "添加节点",
|
||||
"Move all downstream nodes to": "将所有下游节点移至",
|
||||
"After end of branches": "分支结束后",
|
||||
"Inside of branch": "分支内"
|
||||
"Inside of branch": "分支内",
|
||||
|
||||
"Workflow todos": "流程待办"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user