yyd-flow-designer 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/adapters/designerConfig.tsx +43 -0
- package/src/adapters/http.ts +23 -2
- package/src/adapters/router.tsx +1 -2
- package/src/api/flowAction.ts +36 -0
- package/src/api/flowDefine.ts +7 -10
- package/src/api/flowDraft.ts +6 -11
- package/src/designer/flow/Nodes/ServiceTask.tsx +4 -0
- package/src/designer/flow/actions/properties/BpmnProperties.tsx +33 -8
- package/src/designer/flow/actions/properties/components/ParamIOEditors.tsx +2 -2
- package/src/designer/flow/components/ExecuteTestFlowPreview.tsx +3 -115
- package/src/designer/flow/components/ExecuteTestModal.tsx +26 -25
- package/src/designer/flow/utils/validateFlowGraph.ts +10 -0
- package/src/designer/index.tsx +229 -140
- package/src/designer/service/index.ts +7 -12
- package/src/designer/style.less +1 -0
- package/src/index.tsx +35 -13
- package/src/viewer/FlowViewer.tsx +152 -0
- package/src/designer/flow/.DS_Store +0 -0
- package/src/designer/flow/actions/.DS_Store +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
/** 画布图内容,与草稿 content / 实例 FlowDefineJson 同形 */
|
|
4
|
+
export type FlowGraphContent = {
|
|
5
|
+
nodes?: any[];
|
|
6
|
+
edges?: any[];
|
|
7
|
+
timestamp?: string;
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type FlowDesignerFeatures = {
|
|
12
|
+
/** 发布版本,默认:定义模式 true,受控模式 false */
|
|
13
|
+
publish?: boolean;
|
|
14
|
+
/** 版本管理,默认:定义模式 true,受控模式 false */
|
|
15
|
+
versionManager?: boolean;
|
|
16
|
+
/** 调用测试,默认 true */
|
|
17
|
+
executeTest?: boolean;
|
|
18
|
+
/** 导入/导出,默认 true */
|
|
19
|
+
importExport?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type DesignerConfig = {
|
|
23
|
+
/** 受控:传入则不再拉草稿;用于编辑实例 FlowDefineJson */
|
|
24
|
+
value?: FlowGraphContent | null;
|
|
25
|
+
/** 受控保存:传入则保存时调此回调,不写 FlowDraft */
|
|
26
|
+
onSave?: (content: FlowGraphContent) => void | Promise<void>;
|
|
27
|
+
title?: string;
|
|
28
|
+
features?: FlowDesignerFeatures;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const DesignerConfigCtx = createContext<DesignerConfig>({});
|
|
32
|
+
|
|
33
|
+
export const DesignerConfigProvider = DesignerConfigCtx.Provider;
|
|
34
|
+
|
|
35
|
+
export function useDesignerConfig(): DesignerConfig {
|
|
36
|
+
return useContext(DesignerConfigCtx);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 是否受控模式(编辑实例图 / 宿主自管存取) */
|
|
40
|
+
export function useIsControlledDesigner(): boolean {
|
|
41
|
+
const { value, onSave } = useDesignerConfig();
|
|
42
|
+
return value !== undefined || typeof onSave === 'function';
|
|
43
|
+
}
|
package/src/adapters/http.ts
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
|
-
/** HTTP —
|
|
1
|
+
/** HTTP — 由宿主注入 baseURL / 管理入口 / headers;鉴权头与宿主一致用 token */
|
|
2
|
+
|
|
2
3
|
let baseURL = '';
|
|
4
|
+
/** 设计器管理单一入口,默认联调路径;宿主可改成自己的 Controller */
|
|
5
|
+
let adminPath = '/yydflowweb';
|
|
3
6
|
let getHeaders: () => Record<string, string> = () => ({});
|
|
4
7
|
|
|
5
|
-
export function configureHttp(options: {
|
|
8
|
+
export function configureHttp(options: {
|
|
9
|
+
baseURL?: string;
|
|
10
|
+
/** 管理 action 入口,默认 /yydflowweb */
|
|
11
|
+
adminPath?: string;
|
|
12
|
+
/**
|
|
13
|
+
* 请求头。宿主应对齐登录体系:
|
|
14
|
+
* `() => ({ token: getUserToken() })`
|
|
15
|
+
*/
|
|
16
|
+
getHeaders?: () => Record<string, string>;
|
|
17
|
+
}) {
|
|
6
18
|
if (options.baseURL != null) baseURL = options.baseURL.replace(/\/$/, '');
|
|
19
|
+
if (options.adminPath != null) {
|
|
20
|
+
const p = options.adminPath.trim() || '/yydflowweb';
|
|
21
|
+
adminPath = p.startsWith('/') || p.startsWith('http') ? p.replace(/\/$/, '') : `/${p.replace(/\/$/, '')}`;
|
|
22
|
+
}
|
|
7
23
|
if (options.getHeaders) getHeaders = options.getHeaders;
|
|
8
24
|
}
|
|
9
25
|
|
|
@@ -47,3 +63,8 @@ export const httpGet = <T = any>(url: string, params?: Record<string, any>) => {
|
|
|
47
63
|
};
|
|
48
64
|
|
|
49
65
|
export const httpPost = <T = any>(url: string, data?: any) => request<T>('POST', url, data);
|
|
66
|
+
|
|
67
|
+
/** POST { action, ...data } 到管理入口 */
|
|
68
|
+
export function flowAction<T = any>(action: string, data?: Record<string, any>) {
|
|
69
|
+
return httpPost<T>(adminPath, { action, ...(data || {}) });
|
|
70
|
+
}
|
package/src/adapters/router.tsx
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { flowAction } from '../adapters/http';
|
|
2
|
+
|
|
3
|
+
/** 设计器管理 action(与后端 FlowAdminActions 对齐) */
|
|
4
|
+
export const Actions = {
|
|
5
|
+
projectAdd: 'project.add',
|
|
6
|
+
projectUpdate: 'project.update',
|
|
7
|
+
projectDelete: 'project.delete',
|
|
8
|
+
projectGet: 'project.get',
|
|
9
|
+
projectList: 'project.list',
|
|
10
|
+
|
|
11
|
+
tagAdd: 'tag.add',
|
|
12
|
+
tagUpdate: 'tag.update',
|
|
13
|
+
tagDelete: 'tag.delete',
|
|
14
|
+
tagGet: 'tag.get',
|
|
15
|
+
tagList: 'tag.list',
|
|
16
|
+
defineSetTags: 'define.setTags',
|
|
17
|
+
|
|
18
|
+
defineAdd: 'define.add',
|
|
19
|
+
defineUpdate: 'define.update',
|
|
20
|
+
defineDelete: 'define.delete',
|
|
21
|
+
defineGet: 'define.get',
|
|
22
|
+
defineList: 'define.list',
|
|
23
|
+
defineCheckCode: 'define.checkCode',
|
|
24
|
+
|
|
25
|
+
draftSave: 'draft.save',
|
|
26
|
+
draftGet: 'draft.get',
|
|
27
|
+
draftGetByProcessId: 'draft.getByProcessId',
|
|
28
|
+
draftList: 'draft.list',
|
|
29
|
+
|
|
30
|
+
versionAdd: 'version.add',
|
|
31
|
+
versionRestore: 'version.restore',
|
|
32
|
+
versionGet: 'version.get',
|
|
33
|
+
versionList: 'version.list',
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
export { flowAction };
|
package/src/api/flowDefine.ts
CHANGED
|
@@ -1,30 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/** 与 lowcode 前端一致的路径前缀;boot 可配 context 或反代 */
|
|
4
|
-
const P = '/web';
|
|
1
|
+
import { Actions, flowAction } from './flowAction';
|
|
5
2
|
|
|
6
3
|
async function getEntity(params: any) {
|
|
7
|
-
return
|
|
4
|
+
return flowAction(Actions.defineGet, { processId: params.processId });
|
|
8
5
|
}
|
|
9
6
|
|
|
10
7
|
async function getList(params: any) {
|
|
11
|
-
return
|
|
8
|
+
return flowAction(Actions.defineList, params);
|
|
12
9
|
}
|
|
13
10
|
|
|
14
11
|
async function add(params: any) {
|
|
15
|
-
return
|
|
12
|
+
return flowAction(Actions.defineAdd, params);
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
async function update(params: any) {
|
|
19
|
-
return
|
|
16
|
+
return flowAction(Actions.defineUpdate, params);
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
async function del(params: any) {
|
|
23
|
-
return
|
|
20
|
+
return flowAction(Actions.defineDelete, params);
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
async function checkProcessCodeExists(params: any) {
|
|
27
|
-
return
|
|
24
|
+
return flowAction(Actions.defineCheckCode, params);
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
export default { getEntity, getList, add, update, del, checkProcessCodeExists };
|
package/src/api/flowDraft.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Actions, flowAction } from './flowAction';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
export const FlowDraftSaveReq = (params: any) => httpPost(`${P}/flowDraft/save`, params);
|
|
3
|
+
export const FlowDraftSaveReq = (params: any) => flowAction(Actions.draftSave, params);
|
|
6
4
|
export const FlowDraftGetReq = (params: any) =>
|
|
7
|
-
|
|
8
|
-
export const FlowVersionHistoryAddReq = (params: any) =>
|
|
9
|
-
|
|
10
|
-
export const
|
|
11
|
-
httpPost(`${P}/flowVersionHistory/getList`, params);
|
|
12
|
-
export const FlowVersionHistoryRestoreReq = (params: any) =>
|
|
13
|
-
httpPost(`${P}/flowVersionHistory/restore`, params);
|
|
5
|
+
flowAction(Actions.draftGetByProcessId, { processId: params?.processId });
|
|
6
|
+
export const FlowVersionHistoryAddReq = (params: any) => flowAction(Actions.versionAdd, params);
|
|
7
|
+
export const FlowVersionHistoryGetListReq = (params: any) => flowAction(Actions.versionList, params);
|
|
8
|
+
export const FlowVersionHistoryRestoreReq = (params: any) => flowAction(Actions.versionRestore, params);
|
|
@@ -4,8 +4,8 @@ import AssigneeRulesEditor from './components/AssigneeRulesEditor';
|
|
|
4
4
|
import TimeoutSettingEditor from './components/TimeoutSettingEditor';
|
|
5
5
|
import GatewayBranchesEditor from './components/GatewayBranchesEditor';
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
InputsEditor,
|
|
8
|
+
OutputsEditor,
|
|
9
9
|
ProcessParamsEditor,
|
|
10
10
|
} from './components/ParamIOEditors';
|
|
11
11
|
|
|
@@ -81,8 +81,8 @@ const TaskIoProperty = memo(({ nodeInfo, updateNodes }: PropertyProps) => {
|
|
|
81
81
|
const content = nodeInfo?.data?.nodeConfig?.content ?? {};
|
|
82
82
|
return (
|
|
83
83
|
<Form layout="vertical">
|
|
84
|
-
<
|
|
85
|
-
<
|
|
84
|
+
<InputsEditor nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
85
|
+
<OutputsEditor nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
86
86
|
<DescriptionField content={content} nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
87
87
|
</Form>
|
|
88
88
|
);
|
|
@@ -92,22 +92,47 @@ export const UserTaskProperty = memo(({ nodeInfo, updateNodes }: PropertyProps)
|
|
|
92
92
|
const content = nodeInfo?.data?.nodeConfig?.content ?? {};
|
|
93
93
|
return (
|
|
94
94
|
<Form layout="vertical">
|
|
95
|
-
<
|
|
95
|
+
<InputsEditor nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
96
96
|
<AssigneeRulesEditor nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
97
97
|
<TimeoutSettingEditor nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
98
|
-
<
|
|
98
|
+
<OutputsEditor nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
99
99
|
<DescriptionField content={content} nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
100
100
|
</Form>
|
|
101
101
|
);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
export const ServiceTaskProperty =
|
|
104
|
+
export const ServiceTaskProperty = memo(({ nodeInfo, updateNodes }: PropertyProps) => {
|
|
105
|
+
const content = nodeInfo?.data?.nodeConfig?.content ?? {};
|
|
106
|
+
return (
|
|
107
|
+
<Form layout="vertical">
|
|
108
|
+
<InputsEditor nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
109
|
+
<Form.Item
|
|
110
|
+
label="Handler"
|
|
111
|
+
extra="对应宿主 @Component("名称") 的 Bean 名;留空则不调 Java SPI"
|
|
112
|
+
>
|
|
113
|
+
<Input
|
|
114
|
+
placeholder="如 notifySms"
|
|
115
|
+
value={content.handler ?? ''}
|
|
116
|
+
onChange={(e) => updateNodeContent(nodeInfo, updateNodes, { handler: e.target.value })}
|
|
117
|
+
/>
|
|
118
|
+
</Form.Item>
|
|
119
|
+
<AssigneeRulesEditor
|
|
120
|
+
nodeInfo={nodeInfo}
|
|
121
|
+
updateNodes={updateNodes}
|
|
122
|
+
label="接收人"
|
|
123
|
+
extra="给 handler 用(通知/抄送等),不产生待办。规则与用户任务相同:按 scope 对宿主用户体系。"
|
|
124
|
+
/>
|
|
125
|
+
<OutputsEditor nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
126
|
+
<DescriptionField content={content} nodeInfo={nodeInfo} updateNodes={updateNodes} />
|
|
127
|
+
</Form>
|
|
128
|
+
);
|
|
129
|
+
});
|
|
105
130
|
|
|
106
131
|
export const ExclusiveGatewayProperty = memo(({ nodeInfo, updateNodes }: PropertyProps) => {
|
|
107
132
|
const content = nodeInfo?.data?.nodeConfig?.content ?? {};
|
|
108
133
|
return (
|
|
109
134
|
<Form layout="vertical">
|
|
110
|
-
<
|
|
135
|
+
<InputsEditor nodeInfo={nodeInfo} updateNodes={updateNodes} title="判断参数" />
|
|
111
136
|
<GatewayBranchesEditor
|
|
112
137
|
nodeInfo={nodeInfo}
|
|
113
138
|
updateNodes={updateNodes}
|
|
@@ -454,7 +454,7 @@ export const ProcessParamsEditor = memo(({ nodeInfo, updateNodes }: {
|
|
|
454
454
|
);
|
|
455
455
|
});
|
|
456
456
|
|
|
457
|
-
export const
|
|
457
|
+
export const InputsEditor = memo(({ nodeInfo, updateNodes, title = '输入参数' }: {
|
|
458
458
|
nodeInfo: any;
|
|
459
459
|
updateNodes: (nodes: any[]) => void;
|
|
460
460
|
title?: string;
|
|
@@ -472,7 +472,7 @@ export const InputParamsEditor = memo(({ nodeInfo, updateNodes, title = '输入
|
|
|
472
472
|
);
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
-
export const
|
|
475
|
+
export const OutputsEditor = memo(({ nodeInfo, updateNodes, showNotifyWebhook = true }: {
|
|
476
476
|
nodeInfo: any;
|
|
477
477
|
updateNodes: (nodes: any[]) => void;
|
|
478
478
|
showNotifyWebhook?: boolean;
|
|
@@ -1,115 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ReactFlow,
|
|
5
|
-
ReactFlowProvider,
|
|
6
|
-
} from '@xyflow/react';
|
|
7
|
-
import { memo, useMemo } from 'react';
|
|
8
|
-
import { PopoverProvider } from '../../../shared/flow/PopoverContext';
|
|
9
|
-
import edgeTypes from '../Edges';
|
|
10
|
-
import defaultEdgeOptions from '../Edges/defaultEdgeOptions';
|
|
11
|
-
import nodeTypes from '../Nodes';
|
|
12
|
-
import { normalizeFlowNodes } from '../utils/nodeIdentity';
|
|
13
|
-
import {
|
|
14
|
-
applyTrailToEdges,
|
|
15
|
-
applyTrailToNodes,
|
|
16
|
-
collectCodes,
|
|
17
|
-
type TrailOverlay,
|
|
18
|
-
} from '../utils/trailOverlay';
|
|
19
|
-
|
|
20
|
-
export interface ExecuteTestFlowPreviewProps {
|
|
21
|
-
nodes: any[];
|
|
22
|
-
edges: any[];
|
|
23
|
-
passedNodes?: any[];
|
|
24
|
-
parallelTokens?: any[];
|
|
25
|
-
currentNodeCode?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 测试面板内的流程图:原样抄设计器节点/连线/布局,只叠加轨迹高亮。
|
|
30
|
-
*/
|
|
31
|
-
const ExecuteTestFlowInner = ({
|
|
32
|
-
nodes,
|
|
33
|
-
edges,
|
|
34
|
-
passedNodes = [],
|
|
35
|
-
parallelTokens = [],
|
|
36
|
-
currentNodeCode = '',
|
|
37
|
-
}: ExecuteTestFlowPreviewProps) => {
|
|
38
|
-
const trailOverlay: TrailOverlay = useMemo(() => {
|
|
39
|
-
const passedCodes = collectCodes(passedNodes, ['nodeCode', 'NodeCode']);
|
|
40
|
-
const waitingCodes = collectCodes(parallelTokens, ['nodeCode', 'NodeCode']);
|
|
41
|
-
const currentCode = currentNodeCode?.trim() || '';
|
|
42
|
-
if (!passedCodes.length && !waitingCodes.length && !currentCode) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
return { passedCodes, waitingCodes, currentCode };
|
|
46
|
-
}, [passedNodes, parallelTokens, currentNodeCode]);
|
|
47
|
-
|
|
48
|
-
const { previewNodes, previewEdges } = useMemo(() => {
|
|
49
|
-
const baseNodes = normalizeFlowNodes(nodes as any[]);
|
|
50
|
-
const baseEdges = edges ?? [];
|
|
51
|
-
return {
|
|
52
|
-
previewNodes: applyTrailToNodes(baseNodes, trailOverlay).map((n) => ({
|
|
53
|
-
...n,
|
|
54
|
-
selected: false,
|
|
55
|
-
draggable: false,
|
|
56
|
-
selectable: false,
|
|
57
|
-
connectable: false,
|
|
58
|
-
})),
|
|
59
|
-
previewEdges: applyTrailToEdges(baseEdges as any[], baseNodes, trailOverlay).map((e) => ({
|
|
60
|
-
...e,
|
|
61
|
-
selected: false,
|
|
62
|
-
})),
|
|
63
|
-
};
|
|
64
|
-
}, [nodes, edges, trailOverlay]);
|
|
65
|
-
|
|
66
|
-
if (!previewNodes.length) {
|
|
67
|
-
return <div className="execute-test-flow-preview__empty">画布暂无节点</div>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<ReactFlow
|
|
72
|
-
nodes={previewNodes}
|
|
73
|
-
edges={previewEdges}
|
|
74
|
-
nodeTypes={nodeTypes}
|
|
75
|
-
edgeTypes={edgeTypes}
|
|
76
|
-
defaultEdgeOptions={defaultEdgeOptions}
|
|
77
|
-
fitView
|
|
78
|
-
fitViewOptions={{ padding: 0.2, maxZoom: 1 }}
|
|
79
|
-
nodesDraggable={false}
|
|
80
|
-
nodesConnectable={false}
|
|
81
|
-
elementsSelectable={false}
|
|
82
|
-
panOnDrag
|
|
83
|
-
zoomOnScroll
|
|
84
|
-
preventScrolling={false}
|
|
85
|
-
proOptions={{ hideAttribution: true }}
|
|
86
|
-
minZoom={0.15}
|
|
87
|
-
maxZoom={1.5}
|
|
88
|
-
className="bpmn-design-flow"
|
|
89
|
-
>
|
|
90
|
-
<Background gap={14} size={1.2} color="rgba(0,0,0,0.05)" />
|
|
91
|
-
<Controls showInteractive={false} position="bottom-right" />
|
|
92
|
-
</ReactFlow>
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const ExecuteTestFlowPreview = (props: ExecuteTestFlowPreviewProps) => (
|
|
97
|
-
<div className="execute-test-flow-preview">
|
|
98
|
-
<div className="execute-test-flow-preview__legend">
|
|
99
|
-
<span><i className="dot passed" />已走过</span>
|
|
100
|
-
<span><i className="dot waiting" />挂起待办</span>
|
|
101
|
-
<span><i className="dot current" />当前续跑</span>
|
|
102
|
-
<span><i className="dot idle" />未到达</span>
|
|
103
|
-
</div>
|
|
104
|
-
{/* bpmn-design-page:复用设计器节点皮肤(Modal 挂到 body 时否则丢样式) */}
|
|
105
|
-
<div className="execute-test-flow-preview__canvas bpmn-design-page">
|
|
106
|
-
<ReactFlowProvider>
|
|
107
|
-
<PopoverProvider>
|
|
108
|
-
<ExecuteTestFlowInner {...props} />
|
|
109
|
-
</PopoverProvider>
|
|
110
|
-
</ReactFlowProvider>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
export default memo(ExecuteTestFlowPreview);
|
|
1
|
+
/** @deprecated 请用包导出的 FlowViewer;测试弹窗内部仍走此别名 */
|
|
2
|
+
export { default } from '../../../viewer/FlowViewer';
|
|
3
|
+
export type { FlowViewerProps as ExecuteTestFlowPreviewProps } from '../../../viewer/FlowViewer';
|
|
@@ -2,7 +2,7 @@ import { PlayCircleOutlined } from '@ant-design/icons';
|
|
|
2
2
|
import { Button, Collapse, Form, Input, Modal, Space, Typography, message } from 'antd';
|
|
3
3
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { httpPost } from '../../../adapters/http';
|
|
5
|
-
import
|
|
5
|
+
import FlowViewer from '../../../viewer/FlowViewer';
|
|
6
6
|
import { showFlowIssues, validateFlowGraph } from '../utils/validateFlowGraph';
|
|
7
7
|
|
|
8
8
|
export interface ExecuteTestModalProps {
|
|
@@ -41,9 +41,9 @@ const safeParseArray = (text: string | undefined): any[] => {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
/** 从 execute 结果回填表单(整包
|
|
44
|
+
/** 从 execute 结果回填表单(整包 instanceState;续跑目标优先用本步 nextNodes,否则用仍挂起的 parallelTokens) */
|
|
45
45
|
const applyResultToForm = (form: ReturnType<typeof Form.useForm>[0], r: any) => {
|
|
46
|
-
const st = r?.
|
|
46
|
+
const st = r?.instanceState ?? r?.InstanceState ?? {};
|
|
47
47
|
const nextNodes = r?.nextNodes ?? r?.NextNodes ?? [];
|
|
48
48
|
const tokens = st.parallelTokens ?? st.ParallelTokens ?? [];
|
|
49
49
|
const stillWaiting = (Array.isArray(tokens) ? tokens : []).filter(
|
|
@@ -111,7 +111,7 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
111
111
|
currentNodeCode: '',
|
|
112
112
|
resumeTokenId: '',
|
|
113
113
|
operatorId: 'u1001',
|
|
114
|
-
|
|
114
|
+
inputs: '{\n "orderId": "A001"\n}',
|
|
115
115
|
instanceId: '',
|
|
116
116
|
startUserId: 'u1001',
|
|
117
117
|
startOrgId: '',
|
|
@@ -147,18 +147,18 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
147
147
|
|
|
148
148
|
if (instanceId && (!Array.isArray(passedNodes) || passedNodes.length === 0)) {
|
|
149
149
|
setAdvancedKeys(['advanced']);
|
|
150
|
-
message.error('续跑时 passedNodes 不能为空:请先执行一次后点「用
|
|
150
|
+
message.error('续跑时 passedNodes 不能为空:请先执行一次后点「用 instanceState 回填」,或清空 instanceId 作为新实例');
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
153
|
if (instanceId && values.resumeTokenId?.trim()
|
|
154
154
|
&& (!Array.isArray(parallelTokens) || parallelTokens.length === 0)) {
|
|
155
155
|
setAdvancedKeys(['advanced']);
|
|
156
|
-
message.error('续跑时 parallelTokens 不能为空:请点「用
|
|
156
|
+
message.error('续跑时 parallelTokens 不能为空:请点「用 instanceState 回填」整包带回');
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const payload = {
|
|
161
|
-
|
|
161
|
+
flowDefineJson: {
|
|
162
162
|
nodes: flowData.nodes,
|
|
163
163
|
edges: flowData.edges ?? [],
|
|
164
164
|
},
|
|
@@ -166,9 +166,9 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
166
166
|
currentNodeCode: values.currentNodeCode?.trim() || '',
|
|
167
167
|
resumeTokenId: values.resumeTokenId?.trim() || undefined,
|
|
168
168
|
operatorId: values.operatorId?.trim() || undefined,
|
|
169
|
-
|
|
169
|
+
inputs: tryParseJson(values.inputs, 'inputs') ?? {},
|
|
170
170
|
},
|
|
171
|
-
|
|
171
|
+
instanceState: {
|
|
172
172
|
instanceId: instanceId || null,
|
|
173
173
|
startUserId: values.startUserId?.trim() || undefined,
|
|
174
174
|
startOrgId: values.startOrgId?.trim() || undefined,
|
|
@@ -179,7 +179,7 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
179
179
|
};
|
|
180
180
|
|
|
181
181
|
setLoading(true);
|
|
182
|
-
const res = await httpPost<any>('/
|
|
182
|
+
const res = await httpPost<any>('/yydflow/execute', payload);
|
|
183
183
|
setLastResult(res);
|
|
184
184
|
setResultText(pretty(res));
|
|
185
185
|
|
|
@@ -188,11 +188,11 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
188
188
|
} else {
|
|
189
189
|
const info = applyResultToForm(form, res);
|
|
190
190
|
if (info.newTodos > 0) {
|
|
191
|
-
message.success(`执行完成;本步新待办 ${info.newTodos} 条,已回填
|
|
191
|
+
message.success(`执行完成;本步新待办 ${info.newTodos} 条,已回填 instanceState`);
|
|
192
192
|
} else if (info.stillWaiting > 0) {
|
|
193
193
|
message.success(`执行完成;仍有 ${info.stillWaiting} 条挂起,已回填`);
|
|
194
194
|
} else {
|
|
195
|
-
message.success('执行完成;已自动回填
|
|
195
|
+
message.success('执行完成;已自动回填 instanceState');
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
} catch (e: any) {
|
|
@@ -210,7 +210,7 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
212
|
if (lastResult.status === 'error' || lastResult.Status === 'error') {
|
|
213
|
-
message.warning('错误结果没有可续跑的
|
|
213
|
+
message.warning('错误结果没有可续跑的 instanceState');
|
|
214
214
|
return;
|
|
215
215
|
}
|
|
216
216
|
const info = applyResultToForm(form, lastResult);
|
|
@@ -219,7 +219,7 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
219
219
|
? `已回填;本步新待办 ${info.newTodos} 条`
|
|
220
220
|
: info.stillWaiting > 0
|
|
221
221
|
? `已回填;无新待办,仍有 ${info.stillWaiting} 条挂起可续跑`
|
|
222
|
-
: '已回填完整
|
|
222
|
+
: '已回填完整 instanceState'
|
|
223
223
|
);
|
|
224
224
|
}, [form, lastResult]);
|
|
225
225
|
|
|
@@ -242,7 +242,7 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
242
242
|
关闭
|
|
243
243
|
</Button>,
|
|
244
244
|
<Button key="fill" disabled={!lastResult} onClick={fillBackFromResult}>
|
|
245
|
-
用
|
|
245
|
+
用 instanceState 回填
|
|
246
246
|
</Button>,
|
|
247
247
|
<Button key="run" type="primary" loading={loading} icon={<PlayCircleOutlined />} onClick={handleExecute}>
|
|
248
248
|
执行
|
|
@@ -257,7 +257,7 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
257
257
|
请求参数
|
|
258
258
|
</Typography.Text>
|
|
259
259
|
<Typography.Paragraph type="secondary" className="!mb-3 !mt-1 text-xs">
|
|
260
|
-
首次执行请清空 instanceId;成功后自动回填
|
|
260
|
+
首次执行请清空 instanceId;成功后自动回填 instanceState。
|
|
261
261
|
</Typography.Paragraph>
|
|
262
262
|
|
|
263
263
|
<Typography.Text type="secondary">command</Typography.Text>
|
|
@@ -274,9 +274,9 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
274
274
|
<Input placeholder="留空则引擎生成" allowClear />
|
|
275
275
|
</Form.Item>
|
|
276
276
|
<Form.Item
|
|
277
|
-
label="
|
|
278
|
-
name="
|
|
279
|
-
rules={[{ required: true, message: '请填写
|
|
277
|
+
label="inputs(仅业务 JSON)"
|
|
278
|
+
name="inputs"
|
|
279
|
+
rules={[{ required: true, message: '请填写 inputs JSON' }]}
|
|
280
280
|
>
|
|
281
281
|
<Input.TextArea rows={5} className="font-mono text-xs" />
|
|
282
282
|
</Form.Item>
|
|
@@ -297,7 +297,7 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
297
297
|
items={[
|
|
298
298
|
{
|
|
299
299
|
key: 'advanced',
|
|
300
|
-
label: '高级 ·
|
|
300
|
+
label: '高级 · instanceState 原始 JSON',
|
|
301
301
|
forceRender: true,
|
|
302
302
|
children: (
|
|
303
303
|
<>
|
|
@@ -326,11 +326,12 @@ const ExecuteTestModal = ({ open, onClose, getFlowData }: ExecuteTestModalProps)
|
|
|
326
326
|
流程图与设计器一致;执行后按轨迹高亮已过 / 挂起 / 当前节点。
|
|
327
327
|
</Typography.Paragraph>
|
|
328
328
|
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
329
|
+
<FlowViewer
|
|
330
|
+
flowDefineJson={flowSnapshot}
|
|
331
|
+
instanceState={{
|
|
332
|
+
passedNodes: trailPassed,
|
|
333
|
+
parallelTokens: trailTokens,
|
|
334
|
+
}}
|
|
334
335
|
currentNodeCode={watchedCurrent}
|
|
335
336
|
/>
|
|
336
337
|
|
|
@@ -136,6 +136,16 @@ export const validateFlowGraph = (nodes: Node[] = [], edges: Edge[] = []): FlowI
|
|
|
136
136
|
issues.push({ level: 'warning', message: `${label}:未配置审批人` });
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
if (type === 'ServiceTask') {
|
|
141
|
+
const handler = String(contentOf(node).handler ?? '').trim();
|
|
142
|
+
const rules = contentOf(node).assignee?.rules;
|
|
143
|
+
if (!handler) {
|
|
144
|
+
issues.push({ level: 'warning', message: `${label}:未指定 handler,不会调用 Java SPI` });
|
|
145
|
+
} else if (!Array.isArray(rules) || rules.length === 0) {
|
|
146
|
+
issues.push({ level: 'warning', message: `${label}:已指定 handler 但未配置接收人` });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
139
149
|
});
|
|
140
150
|
|
|
141
151
|
if (starts.length === 1) {
|