增加工具栏提示。

This commit is contained in:
dengqichen 2024-12-11 13:03:12 +08:00
parent a5793f5367
commit e37175099a
8 changed files with 130 additions and 292 deletions

View File

@ -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: '网关节点',

View File

@ -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
} }
} }
} }

View File

@ -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('节点创建成功');

View File

@ -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;

View File

@ -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>

View File

@ -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`);

View File

@ -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
} }
// 更新工作流定义请求 // 更新工作流定义请求

View File

@ -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: (