增加工具栏提示。
This commit is contained in:
parent
a5793f5367
commit
e37175099a
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Tabs, Spin } from 'antd';
|
import { Tabs, Spin } from 'antd';
|
||||||
import { NodeType, getNodeTypes } from '../../service';
|
import { NodeType, NodeCategory } from '../../../../types';
|
||||||
import { NODE_CONFIG } from '../../configs/nodeConfig';
|
import { getNodeTypes } from '../../../../service';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
interface NodePanelProps {
|
interface NodePanelProps {
|
||||||
@ -17,10 +17,8 @@ const NodePanel: React.FC<NodePanelProps> = ({ onNodeDragStart }) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await getNodeTypes({ enabled: true });
|
const response = await getNodeTypes({ enabled: true });
|
||||||
if (response) {
|
if (response?.content) {
|
||||||
// 只保留有配置的节点类型
|
setNodeTypes(response.content);
|
||||||
const filteredTypes = response.filter(type => NODE_CONFIG[type.code]);
|
|
||||||
setNodeTypes(filteredTypes);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取节点类型失败:', error);
|
console.error('获取节点类型失败:', error);
|
||||||
@ -49,39 +47,35 @@ const NodePanel: React.FC<NodePanelProps> = ({ onNodeDragStart }) => {
|
|||||||
children: (
|
children: (
|
||||||
<div className="node-panel-content">
|
<div className="node-panel-content">
|
||||||
{types.map((nodeType) => {
|
{types.map((nodeType) => {
|
||||||
const config = NODE_CONFIG[nodeType.code];
|
const graphConfig = JSON.parse(nodeType.graphConfig || '{}');
|
||||||
if (!config) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={nodeType.code}
|
key={nodeType.type}
|
||||||
className="node-card"
|
className="node-card"
|
||||||
draggable
|
draggable
|
||||||
onDragStart={(e) => {
|
onDragStart={(e) => {
|
||||||
e.dataTransfer.setData('node-type', JSON.stringify({
|
e.dataTransfer.setData('node-type', JSON.stringify({
|
||||||
...nodeType,
|
...nodeType,
|
||||||
config: config // 添加节点配置
|
graphConfig
|
||||||
}));
|
}));
|
||||||
onNodeDragStart(nodeType);
|
onNodeDragStart(nodeType);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
borderColor: config.theme.stroke,
|
borderColor: graphConfig.attrs?.body?.stroke || '#5F95FF',
|
||||||
backgroundColor: config.theme.fill
|
backgroundColor: graphConfig.attrs?.body?.fill || '#fff'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="node-icon"
|
className="node-icon"
|
||||||
style={{ color: config.theme.stroke }}
|
style={{ color: graphConfig.attrs?.body?.stroke || '#5F95FF' }}
|
||||||
>
|
>
|
||||||
{config.extras?.icon ? (
|
{graphConfig.attrs?.icon?.xlinkHref && (
|
||||||
<img
|
<img
|
||||||
src={config.extras.icon['xlink:href']}
|
src={graphConfig.attrs.icon.xlinkHref}
|
||||||
width={32}
|
width={32}
|
||||||
height={32}
|
height={32}
|
||||||
alt={nodeType.name}
|
alt={nodeType.name}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<i className={nodeType.icon} />
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="node-name">{nodeType.name}</div>
|
<div className="node-name">{nodeType.name}</div>
|
||||||
@ -104,7 +98,7 @@ const NodePanel: React.FC<NodePanelProps> = ({ onNodeDragStart }) => {
|
|||||||
<div className="node-panel">
|
<div className="node-panel">
|
||||||
<Tabs
|
<Tabs
|
||||||
className="node-tabs"
|
className="node-tabs"
|
||||||
defaultActiveKey="BASIC"
|
defaultActiveKey="EVENT"
|
||||||
items={tabItems}
|
items={tabItems}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -114,7 +108,6 @@ const NodePanel: React.FC<NodePanelProps> = ({ onNodeDragStart }) => {
|
|||||||
// 获取类别标签
|
// 获取类别标签
|
||||||
const getCategoryLabel = (category: string) => {
|
const getCategoryLabel = (category: string) => {
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
BASIC: '基础节点',
|
|
||||||
TASK: '任务节点',
|
TASK: '任务节点',
|
||||||
EVENT: '事件节点',
|
EVENT: '事件节点',
|
||||||
GATEWAY: '网关节点',
|
GATEWAY: '网关节点',
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { NodeConfig } from '../types';
|
import { NodeConfig } from '../types';
|
||||||
|
|
||||||
export const NODE_CONFIG: Record<string, NodeConfig> = {
|
export const NODE_CONFIG: Record<string, NodeConfig> = {
|
||||||
START: {
|
startEvent: {
|
||||||
size: { width: 80, height: 80 },
|
size: { width: 80, height: 80 },
|
||||||
shape: 'circle',
|
shape: 'circle',
|
||||||
theme: {
|
theme: {
|
||||||
fill: '#f6ffed',
|
fill: '#f6ffed',
|
||||||
stroke: '#52c41a'
|
stroke: '#52c41a'
|
||||||
},
|
},
|
||||||
label: '开始',
|
label: '开始节点',
|
||||||
extras: {
|
extras: {
|
||||||
icon: {
|
icon: {
|
||||||
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6bTE5MiAzMzZjMCA0LjQtMy42IDgtOCA4SDU0NHYxNTJjMCA0LjQtMy42IDgtOCA4aC00OGMtNC40IDAtOC0zLjYtOC04VjQwOEgzMjhjLTQuNCAwLTgtMy42LTgtOHYtNDhjMC00LjQgMy42LTggOC04aDE1MlYxOTJjMC00LjQgMy42LTggOC04aDQ4YzQuNCAwIDggMy42IDggOHYxNTJoMTUyYzQuNCAwIDggMy42IDggOHY0OHoiIGZpbGw9IiM1MmM0MWEiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
|
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6bTE5MiAzMzZjMCA0LjQtMy42IDgtOCA4SDU0NHYxNTJjMCA0LjQtMy42IDgtOCA4aC00OGMtNC40IDAtOC0zLjYtOC04VjQwOEgzMjhjLTQuNCAwLTgtMy42LTgtOHYtNDhjMC00LjQgMy42LTggOC04aDE1MlYxOTJjMC00LjQgMy42LTggOC04aDQ4YzQuNCAwIDggMy42IDggOHYxNTJoMTUyYzQuNCAwIDggMy42IDggOHY0OHoiIGZpbGw9IiM1MmM0MWEiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
|
||||||
@ -19,14 +19,14 @@ export const NODE_CONFIG: Record<string, NodeConfig> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
END: {
|
endEvent: {
|
||||||
size: { width: 80, height: 80 },
|
size: { width: 80, height: 80 },
|
||||||
shape: 'circle',
|
shape: 'circle',
|
||||||
theme: {
|
theme: {
|
||||||
fill: '#fff1f0',
|
fill: '#fff1f0',
|
||||||
stroke: '#ff4d4f'
|
stroke: '#ff4d4f'
|
||||||
},
|
},
|
||||||
label: '结束',
|
label: '结束节点',
|
||||||
extras: {
|
extras: {
|
||||||
icon: {
|
icon: {
|
||||||
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6bTE5MiAyODhjMCA0LjQtMy42IDgtOCA4SDMyOGMtNC40IDAtOC0zLjYtOC04di00OGMwLTQuNCAzLjYtOCA4LThoMzY4YzQuNCAwIDggMy42IDggOHY0OHoiIGZpbGw9IiNmZjRkNGYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
|
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6bTE5MiAyODhjMCA0LjQtMy42IDgtOCA4SDMyOGMtNC40IDAtOC0zLjYtOC04di00OGMwLTQuNCAzLjYtOCA4LThoMzY4YzQuNCAwIDggMy42IDggOHY0OHoiIGZpbGw9IiNmZjRkNGYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
|
||||||
@ -37,7 +37,7 @@ export const NODE_CONFIG: Record<string, NodeConfig> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SCRIPT: {
|
shellTask: {
|
||||||
size: { width: 200, height: 80 },
|
size: { width: 200, height: 80 },
|
||||||
shape: 'rect',
|
shape: 'rect',
|
||||||
theme: {
|
theme: {
|
||||||
@ -57,42 +57,22 @@ export const NODE_CONFIG: Record<string, NodeConfig> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TIMER: {
|
exclusiveGateway: {
|
||||||
size: { width: 200, height: 80 },
|
|
||||||
shape: 'rect',
|
|
||||||
theme: {
|
|
||||||
fill: '#fff7e6',
|
|
||||||
stroke: '#fa8c16'
|
|
||||||
},
|
|
||||||
label: '定时器',
|
|
||||||
extras: {
|
|
||||||
rx: 4,
|
|
||||||
ry: 4,
|
|
||||||
icon: {
|
|
||||||
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NjE2NTM2IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjUzNTgiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6IG0wIDgyMGMtMjA1LjQgMC0zNzItMTY2LjYtMzcyLTM3MnMxNjYuNi0zNzIgMzcyLTM3MiAzNzIgMTY2LjYgMzcyIDM3Mi0xNjYuNiAzNzItMzcyIDM3MnogbS00NC44LTM3Mi4zaDIzNS45djUzLjhIMzk4LjdWMjk3LjZoNjguNXYyMTQuMXoiIGZpbGw9IiNmYThjMTYiIHAtaWQ9IjUzNTkiPjwvcGF0aD48L3N2Zz4=',
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
x: 8,
|
|
||||||
y: 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GATEWAY: {
|
|
||||||
size: { width: 60, height: 60 },
|
size: { width: 60, height: 60 },
|
||||||
shape: 'polygon',
|
shape: 'polygon',
|
||||||
theme: {
|
theme: {
|
||||||
fill: '#f9f0ff',
|
fill: '#f9f0ff',
|
||||||
stroke: '#722ed1'
|
stroke: '#722ed1'
|
||||||
},
|
},
|
||||||
label: '网关',
|
label: '排他网关',
|
||||||
extras: {
|
extras: {
|
||||||
refPoints: '0,30 30,0 60,30 30,60',
|
refPoints: '0,30 30,0 60,30 30,60',
|
||||||
icon: {
|
icon: {
|
||||||
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NzAzODY0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjY1NTUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNzgyLjcgNDQxLjRMNTQ1IDMwNC44YzAuMy0wLjMgMC40LTAuNyAwLjctMUw3ODIuNyA0NDEuNHogbTExOC45IDQzMi45TDIxNy43IDE1OC4xQzIwMi4xIDE0Mi41IDE3NiAxNDIuNSAxNjAuNCAxNTguMWMtMTUuNiAxNS42LTE1LjYgNDEuNyAwIDU3LjNsNjgzLjkgNzE2LjJjMTUuNiAxNS42IDQxLjcgMTUuNiA1Ny4zIDBzMTUuNi00MS43IDAtNTcuM3ogbS00MjYtMjQuNmMtMC4zIDAuMy0wLjQgMC43LTAuNyAxTDIzNy42IDU4Mi42YzAuMy0wLjMgMC40LTAuNyAwLjctMWwyMzcuMyAyNjguMXogbTIzNy4zLTI2Ny4xTDQ3NS42IDg1MC43YzAuMy0wLjMgMC40LTAuNyAwLjctMUw3MTIuOSA1ODIuNnogbS00NzUtMjY3LjFsMjM3LjMgMjY4LjFjLTAuMyAwLjMtMC40IDAuNy0wLjcgMUwyMzcuOSAzMTUuNXoiIGZpbGw9IiM3MjJlZDEiIHAtaWQ9IjY1NTYiPjwvcGF0aD48L3N2Zz4=',
|
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NzAzODY0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjY1NTUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNzgyLjcgNDQxLjRMNTQ1IDMwNC44YzAuMy0wLjMgMC40LTAuNyAwLjctMUw3ODIuNyA0NDEuNHogbTExOC45IDQzMi45TDIxNy43IDE1OC4xQzIwMi4xIDE0Mi41IDE3NiAxNDIuNSAxNjAuNCAxNTguMWMtMTUuNiAxNS42LTE1LjYgNDEuNyAwIDU3LjNsNjgzLjkgNzE2LjJjMTUuNiAxNS42IDQxLjcgMTUuNiA1Ny4zIDBzMTUuNi00MS43IDAtNTcuM3ogbS00MjYtMjQuNmMtMC4zIDAuMy0wLjQgMC43LTAuNyAxTDIzNy42IDU4Mi42YzAuMy0wLjMgMC40LTAuNyAwLjctMWwyMzcuMyAyNjguMXogbTIzNy4zLTI2Ny4xTDQ3NS42IDg1MC43YzAuMy0wLjMgMC40LTAuNyAwLjctMUw3MTMuNiA1ODIuNnoiIGZpbGw9IiM3MjJlZDEiIHAtaWQ9IjY1NTYiPjwvcGF0aD48L3N2Zz4=',
|
||||||
width: 24,
|
width: 32,
|
||||||
height: 24,
|
height: 32,
|
||||||
x: 18,
|
x: 14,
|
||||||
y: 6
|
y: 14
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,7 +105,7 @@ const FlowDesigner: React.FC = () => {
|
|||||||
if (contextMenu.cell && contextMenu.cell.isNode()) {
|
if (contextMenu.cell && contextMenu.cell.isNode()) {
|
||||||
setCurrentNode(contextMenu.cell);
|
setCurrentNode(contextMenu.cell);
|
||||||
const data = contextMenu.cell.getData() as NodeData;
|
const data = contextMenu.cell.getData() as NodeData;
|
||||||
const nodeType = nodeTypes.find(type => type.code === data.type);
|
const nodeType = nodeTypes.find(type => type.type === data.type);
|
||||||
if (nodeType) {
|
if (nodeType) {
|
||||||
setCurrentNodeType(nodeType);
|
setCurrentNodeType(nodeType);
|
||||||
const formValues = {
|
const formValues = {
|
||||||
@ -198,7 +198,7 @@ const FlowDesigner: React.FC = () => {
|
|||||||
console.log('Node clicked, data:', data);
|
console.log('Node clicked, data:', data);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const nodeType = nodeTypes.find(type => type.code === data.type);
|
const nodeType = nodeTypes.find(type => type.type === data.type);
|
||||||
if (nodeType) {
|
if (nodeType) {
|
||||||
setCurrentNodeType(nodeType);
|
setCurrentNodeType(nodeType);
|
||||||
const formValues = {
|
const formValues = {
|
||||||
@ -273,13 +273,13 @@ const FlowDesigner: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 获取节点配置
|
// 获取节点配置
|
||||||
const position = calculateNodePosition(nodeType.code, dropPosition);
|
const position = calculateNodePosition(nodeType.type, dropPosition);
|
||||||
const nodeStyle = generateNodeStyle(nodeType.code);
|
const nodeStyle = generateNodeStyle(nodeType.type);
|
||||||
const ports = generatePorts(nodeType.code);
|
const ports = generatePorts(nodeType.type);
|
||||||
const config = NODE_CONFIG[nodeType.code];
|
const config = NODE_CONFIG[nodeType.type];
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error(`未找到节点类型 ${nodeType.code} 的配置`);
|
throw new Error(`未找到节点类型 ${nodeType.type} 的配置`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建节点
|
// 创建节点
|
||||||
@ -288,8 +288,8 @@ const FlowDesigner: React.FC = () => {
|
|||||||
...nodeStyle,
|
...nodeStyle,
|
||||||
ports,
|
ports,
|
||||||
data: {
|
data: {
|
||||||
type: nodeType.code,
|
type: nodeType.type,
|
||||||
name: config.label, // 使用配置中的中文名称
|
name: nodeType.name,
|
||||||
config: {} as any,
|
config: {} as any,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -299,7 +299,7 @@ const FlowDesigner: React.FC = () => {
|
|||||||
graphRef.current.select(node);
|
graphRef.current.select(node);
|
||||||
setCurrentNode(node);
|
setCurrentNode(node);
|
||||||
setCurrentNodeType(nodeType);
|
setCurrentNodeType(nodeType);
|
||||||
form.setFieldsValue({ name: config.label }); // 使用配置中的中文名称
|
form.setFieldsValue({ name: nodeType.name });
|
||||||
setConfigVisible(true);
|
setConfigVisible(true);
|
||||||
|
|
||||||
message.success('节点创建成功');
|
message.success('节点创建成功');
|
||||||
|
|||||||
@ -1,159 +0,0 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
|
||||||
import {useNavigate, useParams} from 'react-router-dom';
|
|
||||||
import {Button, Card, Form, Input, message, Space, Spin} from 'antd';
|
|
||||||
import {getDefinition, updateDefinition} from '../../service';
|
|
||||||
import {UpdateWorkflowDefinitionRequest, WorkflowDefinition, WorkflowStatus} from '../../types';
|
|
||||||
import {ArrowLeftOutlined} from '@ant-design/icons';
|
|
||||||
import {WorkflowConfigUtils} from '../../../Workflow/utils';
|
|
||||||
|
|
||||||
const WorkflowDefinitionEdit: React.FC = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const {id} = useParams<{ id: string }>();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [detail, setDetail] = useState<WorkflowDefinition>();
|
|
||||||
|
|
||||||
// 获取详情
|
|
||||||
const fetchDetail = async () => {
|
|
||||||
if (!id) return;
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await getDefinition(parseInt(id));
|
|
||||||
if (response) {
|
|
||||||
setDetail(response);
|
|
||||||
// 设置表单初始值
|
|
||||||
form.setFieldsValue({
|
|
||||||
code: response.code,
|
|
||||||
name: response.name,
|
|
||||||
description: response.description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 首次加载
|
|
||||||
useEffect(() => {
|
|
||||||
fetchDetail();
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
// 处理保存
|
|
||||||
const handleSave = async (values: any) => {
|
|
||||||
if (!id || !detail) return;
|
|
||||||
try {
|
|
||||||
// 保留原有配置和状态
|
|
||||||
const data: UpdateWorkflowDefinitionRequest = {
|
|
||||||
...values,
|
|
||||||
status: detail.status,
|
|
||||||
version: detail.version,
|
|
||||||
enabled: detail.enabled,
|
|
||||||
nodeConfig: JSON.stringify(WorkflowConfigUtils.parseNodeConfig(detail.nodeConfig)),
|
|
||||||
transitionConfig: JSON.stringify(WorkflowConfigUtils.parseTransitionConfig(detail.transitionConfig)),
|
|
||||||
formDefinition: JSON.stringify(WorkflowConfigUtils.parseFormDefinition(detail.formDefinition)),
|
|
||||||
graphDefinition: JSON.stringify(WorkflowConfigUtils.parseGraphDefinition(detail.graphDefinition))
|
|
||||||
};
|
|
||||||
await updateDefinition(parseInt(id), data);
|
|
||||||
message.success('保存成功');
|
|
||||||
navigate('/workflow/definition');
|
|
||||||
} catch (error) {
|
|
||||||
// 错误已在请求拦截器中处理
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理取消
|
|
||||||
const handleCancel = () => {
|
|
||||||
navigate('/workflow/definition');
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div style={{textAlign: 'center', padding: 100}}>
|
|
||||||
<Spin size="large"/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
title="编辑流程定义"
|
|
||||||
extra={
|
|
||||||
<Button icon={<ArrowLeftOutlined/>} onClick={handleCancel}>
|
|
||||||
返回
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div style={{maxWidth: 800, margin: '0 auto'}}>
|
|
||||||
{detail?.status !== WorkflowStatus.DRAFT && (
|
|
||||||
<div style={{marginBottom: 24, padding: '12px 24px', backgroundColor: '#fff2f0', border: '1px solid #ffccc7', borderRadius: 4, color: '#ff4d4f'}}>
|
|
||||||
注意:非草稿状态的流程定义不能修改!
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
labelCol={{span: 6}}
|
|
||||||
wrapperCol={{span: 18}}
|
|
||||||
onFinish={handleSave}
|
|
||||||
layout="horizontal"
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
name="code"
|
|
||||||
label="流程编码"
|
|
||||||
rules={[
|
|
||||||
{required: true, message: '请输入流程编码'},
|
|
||||||
{pattern: /^[A-Z_]+$/, message: '编码只能包含大写字母和下划线'},
|
|
||||||
]}
|
|
||||||
extra="编码只能包含大写字母和下划线,例如:DEPLOY_WORKFLOW"
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
placeholder="请输入流程编码"
|
|
||||||
disabled={detail?.status !== WorkflowStatus.DRAFT}
|
|
||||||
allowClear
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="name"
|
|
||||||
label="流程名称"
|
|
||||||
rules={[{required: true, message: '请输入流程名称'}]}
|
|
||||||
extra="给流程起一个简单易懂的名称"
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
placeholder="请输入流程名称"
|
|
||||||
disabled={detail?.status !== WorkflowStatus.DRAFT}
|
|
||||||
allowClear
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="description"
|
|
||||||
label="流程描述"
|
|
||||||
extra="详细描述流程的用途、步骤和注意事项"
|
|
||||||
>
|
|
||||||
<Input.TextArea
|
|
||||||
placeholder="请输入流程描述"
|
|
||||||
disabled={detail?.status !== WorkflowStatus.DRAFT}
|
|
||||||
allowClear
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item wrapperCol={{offset: 6, span: 18}}>
|
|
||||||
<Space size="large">
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
disabled={detail?.status !== WorkflowStatus.DRAFT}
|
|
||||||
>
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleCancel}>取消</Button>
|
|
||||||
</Space>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WorkflowDefinitionEdit;
|
|
||||||
@ -3,6 +3,7 @@ import {Button, Card, Form, Input, message, Modal, Select, Space, Table, Tag} fr
|
|||||||
import {useNavigate} from 'react-router-dom';
|
import {useNavigate} from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
createDefinition,
|
createDefinition,
|
||||||
|
updateDefinition,
|
||||||
deleteDefinition,
|
deleteDefinition,
|
||||||
disableDefinition,
|
disableDefinition,
|
||||||
enableDefinition,
|
enableDefinition,
|
||||||
@ -27,6 +28,7 @@ const WorkflowDefinitionList: React.FC = () => {
|
|||||||
const [current, setCurrent] = useState(1);
|
const [current, setCurrent] = useState(1);
|
||||||
const [size, setSize] = useState(10);
|
const [size, setSize] = useState(10);
|
||||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||||
|
const [editingRecord, setEditingRecord] = useState<WorkflowDefinition | null>(null);
|
||||||
|
|
||||||
// 获取列表数据
|
// 获取列表数据
|
||||||
const fetchList = async (page = current, pageSize = size) => {
|
const fetchList = async (page = current, pageSize = size) => {
|
||||||
@ -72,33 +74,44 @@ const WorkflowDefinitionList: React.FC = () => {
|
|||||||
fetchList(1);
|
fetchList(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理创建
|
// 处理创建或更新
|
||||||
const handleCreate = async (values: WorkflowDefinitionBase) => {
|
const handleSubmit = async (values: WorkflowDefinitionBase) => {
|
||||||
try {
|
try {
|
||||||
// 设置系统字段的默认值
|
if (editingRecord) {
|
||||||
const data = {
|
// 更新流程
|
||||||
...values,
|
const updateData = {
|
||||||
status: WorkflowStatus.DRAFT,
|
...values,
|
||||||
version: 1,
|
status: editingRecord.status,
|
||||||
enabled: true,
|
flowVersion: editingRecord.flowVersion
|
||||||
nodeConfig: JSON.stringify({
|
};
|
||||||
nodes: []
|
await updateDefinition(editingRecord.id, updateData);
|
||||||
}),
|
message.success('更新成功');
|
||||||
transitionConfig: JSON.stringify({
|
} else {
|
||||||
transitions: []
|
// 创建流程
|
||||||
}),
|
const data = {
|
||||||
formDefinition: JSON.stringify({}),
|
...values,
|
||||||
graphDefinition: JSON.stringify({})
|
status: WorkflowStatus.DRAFT,
|
||||||
};
|
flowVersion: 1
|
||||||
await createDefinition(data);
|
};
|
||||||
message.success('创建成功');
|
await createDefinition(data);
|
||||||
|
message.success('创建成功');
|
||||||
|
}
|
||||||
setCreateModalVisible(false);
|
setCreateModalVisible(false);
|
||||||
|
setEditingRecord(null);
|
||||||
|
form.resetFields();
|
||||||
fetchList();
|
fetchList();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 错误已在请求拦截器中处理
|
// 错误已在请求拦截器中处理
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理弹窗关闭
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setCreateModalVisible(false);
|
||||||
|
setEditingRecord(null);
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
// 处理删除
|
// 处理删除
|
||||||
const handleDelete = (record: WorkflowDefinition) => {
|
const handleDelete = (record: WorkflowDefinition) => {
|
||||||
confirm({
|
confirm({
|
||||||
@ -143,6 +156,17 @@ const WorkflowDefinitionList: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理编辑
|
||||||
|
const handleEdit = (record: WorkflowDefinition) => {
|
||||||
|
setEditingRecord(record);
|
||||||
|
form.setFieldsValue({
|
||||||
|
key: record.key,
|
||||||
|
name: record.name,
|
||||||
|
description: record.description
|
||||||
|
});
|
||||||
|
setCreateModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
// 表格列定义
|
// 表格列定义
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -224,13 +248,14 @@ const WorkflowDefinitionList: React.FC = () => {
|
|||||||
width: 380,
|
width: 380,
|
||||||
render: (_: any, record: WorkflowDefinition) => (
|
render: (_: any, record: WorkflowDefinition) => (
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
{record.status === WorkflowStatus.DRAFT && (
|
||||||
type="link"
|
<Button
|
||||||
icon={<EditOutlined/>}
|
type="link"
|
||||||
onClick={() => navigate(`/workflow/definition/edit/${record.id}`)}
|
onClick={() => handleEdit(record)}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
onClick={() => navigate(`/workflow/definition/designer/${record.id}`)}
|
onClick={() => navigate(`/workflow/definition/designer/${record.id}`)}
|
||||||
@ -329,44 +354,51 @@ const WorkflowDefinitionList: React.FC = () => {
|
|||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 创建表单弹窗 */}
|
{/* 创建/编辑表单弹窗 */}
|
||||||
<Modal
|
<Modal
|
||||||
title="新建流程"
|
title={editingRecord ? "编辑流程" : "新建流程"}
|
||||||
open={createModalVisible}
|
open={createModalVisible}
|
||||||
onCancel={() => setCreateModalVisible(false)}
|
onCancel={handleModalClose}
|
||||||
footer={null}
|
footer={null}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
labelCol={{span: 4}}
|
form={form}
|
||||||
wrapperCol={{span: 20}}
|
labelCol={{span: 6}}
|
||||||
onFinish={handleCreate}
|
wrapperCol={{span: 18}}
|
||||||
|
onFinish={handleSubmit}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="code"
|
name="key"
|
||||||
label="编码"
|
label="业务标识"
|
||||||
rules={[
|
rules={[
|
||||||
{required: true, message: '请输入流程编码'},
|
{required: true, message: '请输入业务标识'},
|
||||||
{pattern: /^[A-Z_]+$/, message: '编码只能包含大写字母和下划线'},
|
{pattern: /^[A-Z_]+$/, message: '标识只能包含大写字母和下划线'},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入流程编码"/>
|
<Input
|
||||||
|
placeholder="请输入业务标识"
|
||||||
|
disabled={!!editingRecord}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="name"
|
||||||
label="名称"
|
label="流程名称"
|
||||||
rules={[{required: true, message: '请输入流程名称'}]}
|
rules={[{required: true, message: '请输入流程名称'}]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入流<EFBFBD><EFBFBD><EFBFBD>名称"/>
|
<Input placeholder="请输入流程名称"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="description" label="描述">
|
<Form.Item
|
||||||
|
name="description"
|
||||||
|
label="流程描述"
|
||||||
|
>
|
||||||
<Input.TextArea placeholder="请输入流程描述"/>
|
<Input.TextArea placeholder="请输入流程描述"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item wrapperCol={{offset: 4, span: 20}}>
|
<Form.Item wrapperCol={{offset: 6, span: 18}}>
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
确定
|
确定
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setCreateModalVisible(false)}>
|
<Button onClick={handleModalClose}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
const WORKFLOW_DEFINITION_URL = '/api/v1/workflow/definition';
|
const WORKFLOW_DEFINITION_URL = '/api/v1/workflow/definition';
|
||||||
const NODE_TYPE_URL = '/api/v1/node-types';
|
const NODE_DEFINITION_URL = '/api/v1/workflow/node-definition';
|
||||||
|
|
||||||
// 创建工作流定义
|
// 创建工作流定义
|
||||||
export const createDefinition = (data: CreateWorkflowDefinitionRequest) =>
|
export const createDefinition = (data: CreateWorkflowDefinitionRequest) =>
|
||||||
@ -44,13 +44,13 @@ export const disableDefinition = (id: number) =>
|
|||||||
export const enableDefinition = (id: number) =>
|
export const enableDefinition = (id: number) =>
|
||||||
request.post<WorkflowDefinition>(`${WORKFLOW_DEFINITION_URL}/${id}/enable`);
|
request.post<WorkflowDefinition>(`${WORKFLOW_DEFINITION_URL}/${id}/enable`);
|
||||||
|
|
||||||
// 创建新版本
|
// <EFBFBD><EFBFBD><EFBFBD>建新版本
|
||||||
export const createVersion = (id: number) =>
|
export const createVersion = (id: number) =>
|
||||||
request.post<WorkflowDefinition>(`${WORKFLOW_DEFINITION_URL}/${id}/versions`);
|
request.post<WorkflowDefinition>(`${WORKFLOW_DEFINITION_URL}/${id}/versions`);
|
||||||
|
|
||||||
// 节点类型相关接口
|
// 获取节点类型列表
|
||||||
export const getNodeTypes = (params?: NodeTypeQuery) =>
|
export const getNodeTypes = (params?: NodeTypeQuery) =>
|
||||||
request.get<NodeType[]>(NODE_TYPE_URL, { params });
|
request.get<Page<NodeType>>(`${NODE_DEFINITION_URL}/page`, { params });
|
||||||
|
|
||||||
export const getNodeTypeExecutors = (code: string) =>
|
export const getNodeTypeExecutors = (code: string) =>
|
||||||
request.get<NodeType>(`${NODE_TYPE_URL}/${code}/executors`);
|
request.get<NodeType>(`${NODE_DEFINITION_URL}/${code}/executors`);
|
||||||
@ -32,18 +32,22 @@ export interface NodeExecutor {
|
|||||||
// 节点类型
|
// 节点类型
|
||||||
export interface NodeType {
|
export interface NodeType {
|
||||||
id: number;
|
id: number;
|
||||||
code: string;
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
category: NodeCategory;
|
|
||||||
description: string;
|
description: string;
|
||||||
|
category: NodeCategory;
|
||||||
|
flowableConfig: string; // JSON string
|
||||||
|
graphConfig: string; // JSON string
|
||||||
|
formConfig: string; // JSON string
|
||||||
|
orderNum: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
icon: string;
|
|
||||||
color: string;
|
|
||||||
executors: NodeExecutor[];
|
|
||||||
configSchema: string; // JSON Schema
|
|
||||||
defaultConfig: string;
|
|
||||||
createTime: string;
|
createTime: string;
|
||||||
updateTime: string;
|
updateTime: string;
|
||||||
|
createBy: string;
|
||||||
|
updateBy: string;
|
||||||
|
version: number;
|
||||||
|
deleted: boolean;
|
||||||
|
extraData: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 工作流定义查询参数
|
// 工作流定义查询参数
|
||||||
@ -77,15 +81,16 @@ export interface TransitionConfig {
|
|||||||
|
|
||||||
// 工作流定义基本信息
|
// 工作流定义基本信息
|
||||||
export interface WorkflowDefinitionBase {
|
export interface WorkflowDefinitionBase {
|
||||||
code: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
key: string;
|
||||||
|
flowVersion?: number;
|
||||||
|
status?: WorkflowStatus;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 工作流定义
|
// 工作流定义
|
||||||
export interface WorkflowDefinition extends WorkflowDefinitionBase {
|
export interface WorkflowDefinition extends WorkflowDefinitionBase {
|
||||||
id: number;
|
id: number;
|
||||||
version: number;
|
|
||||||
status: WorkflowStatus;
|
status: WorkflowStatus;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
nodeConfig: string; // JSON string of NodeConfig
|
nodeConfig: string; // JSON string of NodeConfig
|
||||||
@ -94,17 +99,13 @@ export interface WorkflowDefinition extends WorkflowDefinitionBase {
|
|||||||
graphDefinition: string; // JSON string
|
graphDefinition: string; // JSON string
|
||||||
createTime: string;
|
createTime: string;
|
||||||
updateTime: string;
|
updateTime: string;
|
||||||
|
createBy: string;
|
||||||
|
updateBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建工作流定义请求
|
// 创建工作流定义请求
|
||||||
export interface CreateWorkflowDefinitionRequest extends WorkflowDefinitionBase {
|
export interface CreateWorkflowDefinitionRequest extends WorkflowDefinitionBase {
|
||||||
status: WorkflowStatus;
|
status: WorkflowStatus;
|
||||||
version: number;
|
|
||||||
enabled: boolean;
|
|
||||||
nodeConfig: string; // JSON string of NodeConfig
|
|
||||||
transitionConfig: string; // JSON string of TransitionConfig
|
|
||||||
formDefinition: string; // JSON string
|
|
||||||
graphDefinition: string; // JSON string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新工作流定义请求
|
// 更新工作流定义请求
|
||||||
|
|||||||
@ -33,7 +33,6 @@ const Department = lazy(() => import('../pages/System/Department'));
|
|||||||
const External = lazy(() => import('../pages/System/External'));
|
const External = lazy(() => import('../pages/System/External'));
|
||||||
const X6Test = lazy(() => import('../pages/X6Test'));
|
const X6Test = lazy(() => import('../pages/X6Test'));
|
||||||
const WorkflowDefinition = lazy(() => import('../pages/Workflow/Definition'));
|
const WorkflowDefinition = lazy(() => import('../pages/Workflow/Definition'));
|
||||||
const WorkflowDefinitionEdit = lazy(() => import('../pages/Workflow/Definition/Edit'));
|
|
||||||
const WorkflowInstance = lazy(() => import('../pages/Workflow/Instance'));
|
const WorkflowInstance = lazy(() => import('../pages/Workflow/Instance'));
|
||||||
const WorkflowMonitor = lazy(() => import('../pages/Workflow/Monitor'));
|
const WorkflowMonitor = lazy(() => import('../pages/Workflow/Monitor'));
|
||||||
const FlowDesigner = lazy(() => import('../pages/Workflow/Definition/Designer'));
|
const FlowDesigner = lazy(() => import('../pages/Workflow/Definition/Designer'));
|
||||||
@ -132,14 +131,6 @@ const router = createBrowserRouter([
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'edit/:id?',
|
|
||||||
element: (
|
|
||||||
<Suspense fallback={<LoadingComponent/>}>
|
|
||||||
<WorkflowDefinitionEdit/>
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'designer/:id?',
|
path: 'designer/:id?',
|
||||||
element: (
|
element: (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user