增加工具栏提示。

This commit is contained in:
dengqichen 2024-12-12 14:25:58 +08:00
parent 09489b3e11
commit f8b1019625
21 changed files with 650 additions and 1043 deletions

View File

@ -15,4 +15,7 @@
- 一次将所有修改应用于单个文件 - 一次将所有修改应用于单个文件
- 请勿修改不相关的文件 - 请勿修改不相关的文件
4. 代码注释:
- 尽量都编写代码注释,写明实现的方式
记住要始终考虑每个项目的背景和特定需求。 记住要始终考虑每个项目的背景和特定需求。

View File

@ -1,27 +1,21 @@
.node-panel { .node-panel {
height: 100%; height: 100%;
border-right: 1px solid #e8e8e8; border-right: 1px solid #e8e8e8;
background-color: #fff;
&-loading { &-tabs {
display: flex;
justify-content: center;
align-items: center;
height: 100%; height: 100%;
}
:global {
.ant-tabs {
height: 100%;
:global {
.ant-tabs-nav { .ant-tabs-nav {
margin: 0; margin: 0;
padding: 0 16px; padding: 0 12px;
background-color: #fafafa; background-color: #fafafa;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
.ant-tabs-tab { .ant-tabs-tab {
padding: 8px 16px !important; padding: 8px 12px;
margin: 0 !important; margin: 0 4px !important;
transition: all 0.3s; transition: all 0.3s;
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
@ -32,6 +26,7 @@
&.ant-tabs-tab-active { &.ant-tabs-tab-active {
background: #fff; background: #fff;
border-bottom-color: #fff;
.ant-tabs-tab-btn { .ant-tabs-tab-btn {
color: #1890ff; color: #1890ff;
@ -41,70 +36,70 @@
} }
} }
.ant-tabs-content-holder { .ant-tabs-content {
flex: 1; height: calc(100% - 44px);
padding: 12px;
overflow: auto; overflow: auto;
.ant-tabs-content {
height: calc(100% - 44px);
}
} }
} }
} }
}
.node-tabs { .node-panel-content {
height: 100%; display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 16px;
padding: 8px;
:global(.ant-tabs-content) { .node-item {
height: 100%; position: relative;
}
}
.node-panel-content {
padding: 8px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
height: 100%;
overflow-y: auto;
}
.node-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 12px; justify-content: center;
border: 1px solid #e8e8e8; min-height: 90px;
padding: 16px;
border-radius: 4px; border-radius: 4px;
cursor: move; cursor: move;
transition: all 0.3s; transition: all 0.3s;
background: #fff; user-select: none;
&:hover { &:hover {
border-color: #1890ff; box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); transform: translateY(-1px);
}
&:active {
transform: translateY(0);
} }
.node-icon { .node-icon {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px; margin-bottom: 8px;
color: #5F95FF; font-size: 24px;
line-height: 1;
.anticon { .anticon {
font-size: 32px; display: block;
} }
} }
.node-name { .node-name {
margin-bottom: 4px;
color: #262626;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: #333; text-align: center;
margin-bottom: 4px; line-height: 1.4;
} }
.node-desc { .node-desc {
color: #8c8c8c;
font-size: 12px; font-size: 12px;
color: #666;
text-align: center; text-align: center;
line-height: 1.4;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;

View File

@ -1,6 +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, NodeCategory } from '../../../../types'; import * as Icons from '@ant-design/icons';
import { NodeType, NodeCategory } from './types';
import { getNodeTypes } from '../../../../service'; import { getNodeTypes } from '../../../../service';
import './index.less'; import './index.less';
@ -17,9 +18,22 @@ const NodePanel: React.FC<NodePanelProps> = ({ onNodeDragStart }) => {
setLoading(true); setLoading(true);
try { try {
const response = await getNodeTypes({ enabled: true }); const response = await getNodeTypes({ enabled: true });
console.log('节点类型列表:', response); console.log('节点类型原始数据:', response);
if (response?.content) { if (response?.content) {
setNodeTypes(response.content); // 处理每个节点的样式
const processedTypes = response.content.map(node => ({
...node,
// 确保graphConfig中的style存在
style: node.graphConfig?.style || {
fill: '#ffffff',
stroke: '#1890ff',
strokeWidth: 2,
icon: 'api',
iconColor: '#1890ff'
}
}));
console.log('处理后的节点数据:', processedTypes);
setNodeTypes(processedTypes);
} }
} catch (error) { } catch (error) {
console.error('获取节点类型失败:', error); console.error('获取节点类型失败:', error);
@ -31,15 +45,64 @@ const NodePanel: React.FC<NodePanelProps> = ({ onNodeDragStart }) => {
fetchNodeTypes(); fetchNodeTypes();
}, []); }, []);
// 获取图标组件
const getIcon = (iconName: string) => {
if (!iconName) return null;
// 转换图标名称为大驼峰格式
const formatIconName = (name: string) => {
return name
.split('-')
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join('');
};
// 尝试多种图标命名方式
const iconVariants = [
formatIconName(iconName),
`${formatIconName(iconName)}Outlined`,
`${formatIconName(iconName)}Filled`,
`${formatIconName(iconName)}TwoTone`
];
console.log('尝试加载图标:', iconName, iconVariants);
for (const variant of iconVariants) {
const IconComponent = (Icons as any)[variant];
if (IconComponent) {
console.log('成功找到图标组件:', variant);
return React.createElement(IconComponent);
}
}
console.warn('未找到图标组件:', iconName, iconVariants);
return null;
};
// 按类别分组节点类型 // 按类别分组节点类型
const groupedNodeTypes = Array.isArray(nodeTypes) ? nodeTypes.reduce((acc, nodeType) => { const groupedNodeTypes = nodeTypes.reduce((acc, nodeType) => {
const category = nodeType.category; const category = nodeType.category || NodeCategory.TASK;
if (!acc[category]) { if (!acc[category]) {
acc[category] = []; acc[category] = [];
} }
acc[category].push(nodeType); acc[category].push(nodeType);
return acc; return acc;
}, {} as Record<string, NodeType[]>) : {}; }, {} as Record<string, NodeType[]>);
// 获取类别标签
const getCategoryLabel = (category: string): string => {
switch (category) {
case NodeCategory.EVENT:
return '事件节点';
case NodeCategory.TASK:
return '任务节点';
case NodeCategory.GATEWAY:
return '网关节点';
case NodeCategory.CONTAINER:
return '容器节点';
default:
return '其他节点';
}
};
// 将分组转换为 Tabs 项 // 将分组转换为 Tabs 项
const tabItems = Object.entries(groupedNodeTypes).map(([category, types]) => ({ const tabItems = Object.entries(groupedNodeTypes).map(([category, types]) => ({
@ -48,36 +111,41 @@ 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 graphConfig = typeof nodeType.graphConfig === 'string' // 从graphConfig中获取样式
? JSON.parse(nodeType.graphConfig) const style = nodeType.graphConfig?.style || nodeType.style || {};
: nodeType.graphConfig; console.log('节点样式:', nodeType.type, style);
const icon = getIcon(style.icon);
const nodeStyle = {
backgroundColor: style.fill || '#ffffff',
borderColor: style.stroke || '#d9d9d9',
borderWidth: `${style.strokeWidth || 1}px`,
borderStyle: 'solid'
};
console.log('应用的样式:', nodeType.type, nodeStyle);
return ( return (
<div <div
key={nodeType.type} key={nodeType.type}
className="node-card" className="node-item"
draggable draggable
onDragStart={(e) => { onDragStart={(e) => {
e.dataTransfer.setData('node-type', JSON.stringify({ e.dataTransfer.effectAllowed = 'move';
...nodeType,
graphConfig
}));
onNodeDragStart(nodeType); onNodeDragStart(nodeType);
}} }}
style={nodeStyle}
> >
<div className="node-icon"> {icon && (
{graphConfig.properties?.shape?.const === 'serviceTask' && ( <span
<i className="anticon"> className="node-icon"
<svg viewBox="0 0 1024 1024" width="32" height="32"> style={{ color: style.iconColor || '#1890ff' }}
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="#5F95FF"/> >
<path d="M512 196c-171.4 0-316 144.6-316 316s144.6 316 316 316 316-144.6 316-316-144.6-316-316-316zm0 552c-130.1 0-236-105.9-236-236s105.9-236 236-236 236 105.9 236 236-105.9 236-236 236z" fill="#5F95FF"/> {icon}
<path d="M512 392c-66.2 0-120 53.8-120 120s53.8 120 120 120 120-53.8 120-120-53.8-120-120-120z" fill="#5F95FF"/> </span>
</svg> )}
</i> <span className="node-name">{nodeType.name}</span>
)} <span className="node-desc">{nodeType.description}</span>
</div>
<div className="node-name">{nodeType.name}</div>
<div className="node-desc">{nodeType.description}</div>
</div> </div>
); );
})} })}
@ -85,33 +153,17 @@ const NodePanel: React.FC<NodePanelProps> = ({ onNodeDragStart }) => {
), ),
})); }));
if (loading) {
return (
<div className="node-panel-loading">
<Spin />
</div>
);
}
return ( return (
<div className="node-panel"> <div className="node-panel">
<Tabs <Spin spinning={loading}>
className="node-tabs" <Tabs
defaultActiveKey="TASK" defaultActiveKey={NodeCategory.EVENT}
items={tabItems} items={tabItems}
/> className="node-panel-tabs"
/>
</Spin>
</div> </div>
); );
}; };
// 获取类别标签
function getCategoryLabel(category: string): string {
const categoryMap: Record<string, string> = {
[NodeCategory.TASK]: '任务节点',
[NodeCategory.EVENT]: '事件节点',
[NodeCategory.GATEWAY]: '网关节点'
};
return categoryMap[category] || category;
}
export default NodePanel; export default NodePanel;

View File

@ -0,0 +1,98 @@
import { BaseResponse } from '@/types/base/response';
import { NodeGraph } from '../../../types';
/**
*
*/
export interface NodeStyle {
fill: string; // 填充颜色
stroke: string; // 边框颜色
strokeWidth: number; // 边框宽度
icon: string; // 图标名称
iconColor: string; // 图标颜色
}
/**
*
*/
export enum NodeCategory {
EVENT = 'EVENT', // 事件节点
TASK = 'TASK', // 任务节点
GATEWAY = 'GATEWAY', // 网关节点
CONTAINER = 'CONTAINER' // 容器节点
}
/**
*
*/
export enum NodeTypeEnum {
START_EVENT = 'START_EVENT', // 开始节点
END_EVENT = 'END_EVENT', // 结束节点
USER_TASK = 'USER_TASK', // 用户任务
SERVICE_TASK = 'SERVICE_TASK', // 服务任务
SCRIPT_TASK = 'SCRIPT_TASK', // 脚本任务
EXCLUSIVE_GATEWAY = 'EXCLUSIVE_GATEWAY', // 排他网关
PARALLEL_GATEWAY = 'PARALLEL_GATEWAY', // 并行网关
SUB_PROCESS = 'SUB_PROCESS', // 子流程
CALL_ACTIVITY = 'CALL_ACTIVITY' // 调用活动
}
/**
*
*/
export interface NodeDetails {
description: string; // 详细描述
features: string[]; // 功能特性
scenarios: string[]; // 使用场景
}
/**
* JSON Schema属性
*/
export interface JsonSchemaProperty {
type: string;
title: string;
description: string;
format?: string;
default?: any;
enum?: string[];
enumNames?: string[];
}
/**
* Schema
*/
export interface NodeConfigSchema {
type: string;
properties: Record<string, JsonSchemaProperty>;
required: string[];
}
/**
*
*/
export interface NodeGraphConfig {
code: string; // 节点代码
name: string; // 节点名称
description: string; // 节点描述
details: NodeDetails; // 节点详情
configSchema: NodeConfigSchema; // 配置模式
uiSchema: NodeGraph; // UI渲染模式
}
/**
*
*/
export interface NodeType extends BaseResponse {
type: NodeTypeEnum; // 节点类型
name: string; // 节点名称
description: string; // 节点描述
category: NodeCategory; // 节点分类
flowableConfig: any; // 工作流引擎配置
graphConfig: NodeGraphConfig; // 图形配置
style: NodeStyle; // 节点样式
orderNum: number; // 排序号
enabled: boolean; // 是否启用
deleted: boolean; // 是否删除
extraData: any; // 扩展数据
}

View File

@ -1,99 +0,0 @@
import { NodeConfig } from '../types';
export const NODE_CONFIG: Record<string, NodeConfig> = {
startEvent: {
size: { width: 80, height: 80 },
shape: 'circle',
theme: {
fill: '#f6ffed',
stroke: '#52c41a'
},
label: '开始节点',
extras: {
icon: {
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6bTE5MiAzMzZjMCA0LjQtMy42IDgtOCA4SDU0NHYxNTJjMCA0LjQtMy42IDgtOCA4aC00OGMtNC40IDAtOC0zLjYtOC04VjQwOEgzMjhjLTQuNCAwLTgtMy42LTgtOHYtNDhjMC00LjQgMy42LTggOC04aDE1MlYxOTJjMC00LjQgMy42LTggOC04aDQ4YzQuNCAwIDggMy42IDggOHYxNTJoMTUyYzQuNCAwIDggMy42IDggOHY0OHoiIGZpbGw9IiM1MmM0MWEiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
width: 32,
height: 32,
x: 24,
y: 8
}
}
},
endEvent: {
size: { width: 80, height: 80 },
shape: 'circle',
theme: {
fill: '#fff1f0',
stroke: '#ff4d4f'
},
label: '结束节点',
extras: {
icon: {
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDY0QzI2NC42IDY0IDY0IDI2NC42IDY0IDUxMnMyMDAuNiA0NDggNDQ4IDQ0OCA0NDgtMjAwLjYgNDQ4LTQ0OFM3NTkuNCA2NCA1MTIgNjR6bTE5MiAyODhjMCA0LjQtMy42IDgtOCA4SDMyOGMtNC40IDAtOC0zLjYtOC04di00OGMwLTQuNCAzLjYtOCA4LThoMzY4YzQuNCAwIDggMy42IDggOHY0OHoiIGZpbGw9IiNmZjRkNGYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
width: 32,
height: 32,
x: 24,
y: 8
}
}
},
userTask: {
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,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNODU4LjUgNzYzLjZjLTE4LjktMjYuMi00Ny45LTQxLjctNzkuOS00MS43SDI0Ni40Yy0zMiAwLTYxIDI1LjUtNzkuOSA0MS43LTE4LjkgMTYuMi0yOS45IDM3LjItMjkuOSA1OS42IDAgMjIuNCAyNi44IDQwLjYgNTkuOCA0MC42aDYzMi4yYzMzIDAgNTkuOC0xOC4yIDU5LjgtNDAuNiAwLTIyLjQtMTEtNDMuNC0yOS45LTU5LjZ6TTUxMiAyNTZjODguNCAwIDE2MCA3MS42IDE2MCAxNjBzLTcxLjYgMTYwLTE2MCAxNjAtMTYwLTcxLjYtMTYwLTE2MCA3MS42LTE2MCAxNjAtMTYweiIgZmlsbD0iI2ZhOGMxNiIgcC1pZD0iNDE2MiI+PC9wYXRoPjwvc3ZnPg==',
width: 32,
height: 32,
x: 8,
y: 24
}
}
},
shellTask: {
size: { width: 200, height: 80 },
shape: 'rect',
theme: {
fill: '#e6f7ff',
stroke: '#1890ff'
},
label: 'Shell脚本',
extras: {
rx: 4,
ry: 4,
icon: {
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NTM1NDY5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQxNjEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTYwIDI1NnY1MTJoNzA0VjI1NkgxNjB6IG02NDAgNDQ4SDE5MlYyODhoNjA4djQxNnpNMjI0IDQ4MGwxMjgtMTI4IDQ1LjMgNDUuMy04Mi43IDgyLjcgODIuNyA4Mi43TDM1MiA2MDhsLTEyOC0xMjh6IG0yMzEuMSAxOTBsLTQ1LjMtNDUuMyAxNTItMTUyIDQ1LjMgNDUuM2wtMTUyIDE1MnoiIGZpbGw9IiMxODkwZmYiIHAtaWQ9IjQxNjIiPjwvcGF0aD48L3N2Zz4=',
width: 32,
height: 32,
x: 8,
y: 24
}
}
},
exclusiveGateway: {
size: { width: 60, height: 60 },
shape: 'polygon',
theme: {
fill: '#f9f0ff',
stroke: '#722ed1'
},
label: '排他网关',
extras: {
refPoints: '0,30 30,0 60,30 30,60',
icon: {
'xlink:href': 'data:image/svg+xml;base64,PHN2ZyB0PSIxNzAzODQ4NzAzODY0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjY1NTUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNzgyLjcgNDQxLjRMNTQ1IDMwNC44YzAuMy0wLjMgMC40LTAuNyAwLjctMUw3ODIuNyA0NDEuNHogbTExOC45IDQzMi45TDIxNy43IDE1OC4xQzIwMi4xIDE0Mi41IDE3NiAxNDIuNSAxNjAuNCAxNTguMWMtMTUuNiAxNS42LTE1LjYgNDEuNyAwIDU3LjNsNjgzLjkgNzE2LjJjMTUuNiAxNS42IDQxLjcgMTUuNiA1Ny4zIDBzMTUuNi00MS43IDAtNTcuM3ogbS00MjYtMjQuNmMtMC4zIDAuMy0wLjQgMC43LTAuNyAxTDIzNy42IDU4Mi42YzAuMy0wLjMgMC40LTAuNyAwLjctMWwyMzcuMyAyNjguMXogbTIzNy4zLTI2Ny4xTDQ3NS42IDg1MC43YzAuMy0wLjMgMC40LTAuNyAwLjctMUw3MTMuNiA1ODIuNnoiIGZpbGw9IiM3MjJlZDEiIHAtaWQ9IjY1NTYiPjwvcGF0aD48L3N2Zz4=',
width: 32,
height: 32,
x: 14,
y: 14
}
}
}
};

View File

@ -3,8 +3,8 @@ import {useNavigate, useParams} from 'react-router-dom';
import {Button, Card, Layout, message, Space, Spin, Drawer, Form, Dropdown} from 'antd'; import {Button, Card, Layout, message, Space, Spin, Drawer, Form, Dropdown} from 'antd';
import {ArrowLeftOutlined, SaveOutlined, DeleteOutlined, CopyOutlined, SettingOutlined, ClearOutlined, FullscreenOutlined} from '@ant-design/icons'; import {ArrowLeftOutlined, SaveOutlined, DeleteOutlined, CopyOutlined, SettingOutlined, ClearOutlined, FullscreenOutlined} from '@ant-design/icons';
import {getDefinition, updateDefinition, getNodeTypes} from '../../service'; import {getDefinition, updateDefinition, getNodeTypes} from '../../service';
import {WorkflowStatus} from '../../../Workflow/types'; import {WorkflowDefinition, WorkflowStatus} from '../../../Workflow/types';
import {Graph, Node, Cell, Edge, Shape} from '@antv/x6'; import {Graph, Node, Cell, Edge} from '@antv/x6';
import '@antv/x6-react-shape'; import '@antv/x6-react-shape';
import './index.module.less'; import './index.module.less';
import NodePanel from './components/NodePanel'; import NodePanel from './components/NodePanel';
@ -12,11 +12,9 @@ import NodeConfig from './components/NodeConfig';
import Toolbar from './components/Toolbar'; import Toolbar from './components/Toolbar';
import EdgeConfig from './components/EdgeConfig'; import EdgeConfig from './components/EdgeConfig';
import { validateFlow, hasCycle } from './validate'; import { validateFlow, hasCycle } from './validate';
import { generateNodeStyle, generatePorts, calculateNodePosition, calculateCanvasPosition, getNodeShape, getNodeSize } from './utils/nodeUtils'; import { generateNodeStyle, generatePorts, calculateCanvasPosition, getNodeShape, getNodeSize } from './utils/nodeUtils';
import { NODE_CONFIG } from './configs/nodeConfig';
import { isWorkflowError } from './utils/errors';
import { initGraph } from './utils/graphUtils'; import { initGraph } from './utils/graphUtils';
import { NodeType, NodeData, EdgeData, WorkflowDefinition, WorkflowGraph } from './types'; import { NodeType, NodeData, WorkflowGraph } from './types';
const {Sider, Content} = Layout; const {Sider, Content} = Layout;
@ -105,16 +103,17 @@ 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.type === data.type); console.log(data)
if (nodeType) { // const nodeType = nodeTypes.find(type => type.type === data.type);
setCurrentNodeType(nodeType); // if (nodeType) {
const formValues = { // setCurrentNodeType(nodeType);
name: data.name || nodeType.name, // const formValues = {
description: data.description, // name: data.name || nodeType.name,
...data.config // description: data.description,
}; // ...data.config
form.setFieldsValue(formValues); // };
} // form.setFieldsValue(formValues);
// }
setConfigVisible(true); setConfigVisible(true);
} }
setContextMenu(prev => ({ ...prev, visible: false })); setContextMenu(prev => ({ ...prev, visible: false }));

View File

@ -1,61 +0,0 @@
import request from '@/utils/request';
import { NodeCategory } from '../../types';
export interface NodeExecutor {
code: string;
name: string;
description: string;
configSchema: string;
defaultConfig?: string;
}
export interface JsonSchemaProperty {
type: string;
title: string;
description?: string;
minLength?: number;
maxLength?: number;
minimum?: number;
maximum?: number;
default?: any;
format?: string;
pattern?: string;
enum?: string[];
enumNames?: string[];
}
export interface JsonSchema {
type: string;
properties: Record<string, JsonSchemaProperty>;
required?: string[];
}
export interface NodeType {
id: number;
code: string;
name: string;
category: NodeCategory;
description: string;
enabled: boolean;
icon: string;
color: string;
executors: NodeExecutor[];
configSchema: string;
defaultConfig: string;
createTime: string;
updateTime: string;
version: number;
deleted: boolean;
createBy: string;
updateBy: string;
extraData: any;
}
export interface NodeTypeQuery {
enabled?: boolean;
category?: NodeCategory;
}
// 获取节点类型列表
export const getNodeTypes = (params?: NodeTypeQuery) =>
request.get<NodeType[]>('/api/v1/node-types', { params });

View File

@ -1,21 +1,45 @@
/**
*
*/
export interface Position { export interface Position {
x: number; x: number;
y: number; y: number;
} }
/**
*
*/
export interface Size { export interface Size {
width: number; width: number;
height: number; height: number;
} }
/**
*
*/
export enum NodeType {
START = 'startEvent',
END = 'endEvent',
USER_TASK = 'userTask',
SERVICE_TASK = 'serviceTask',
SCRIPT_TASK = 'scriptTask',
EXCLUSIVE_GATEWAY = 'exclusiveGateway',
PARALLEL_GATEWAY = 'parallelGateway',
SUBPROCESS = 'subProcess',
CALL_ACTIVITY = 'callActivity'
}
/**
*
*/
export interface NodeConfig { export interface NodeConfig {
size: Size; size?: Size;
shape: 'circle' | 'rect' | 'polygon'; shape?: 'circle' | 'rect' | 'polygon';
theme: { theme?: {
fill: string; fill: string;
stroke: string; stroke: string;
}; };
label: string; label?: string;
extras?: { extras?: {
rx?: number; rx?: number;
ry?: number; ry?: number;
@ -27,46 +51,94 @@ export interface NodeConfig {
y: number; y: number;
}; };
}; };
assignee?: string;
candidateUsers?: string[];
candidateGroups?: string[];
dueDate?: string;
priority?: number;
formKey?: string;
executor?: string;
retryTimes?: number;
retryInterval?: number;
script?: string;
timeout?: number;
workingDirectory?: string;
environment?: string;
successExitCode?: string;
[key: string]: any;
} }
export interface NodeType {
id?: number; /**
code: string; *
*/
export interface WorkflowNode {
id: string;
type: NodeType;
name: string; name: string;
category: string; position: Position;
color?: string; size: Size;
icon?: string; config?: NodeConfig;
enabled?: boolean; properties?: Record<string, any>;
description?: string;
config: NodeConfig;
} }
/**
*
*/
export interface WorkflowEdge {
id: string;
source: string;
target: string;
sourcePortId?: string;
targetPortId?: string;
name?: string;
condition?: string;
description?: string;
priority?: number;
config?: {
condition?: string;
expression?: string;
type?: 'sequence' | 'message' | 'association';
[key: string]: any;
};
properties?: Record<string, any>;
}
/**
*
*/
export interface WorkflowGraph {
nodes: WorkflowNode[];
edges: WorkflowEdge[];
properties?: WorkflowProperties;
}
/**
*
*/
export interface WorkflowProperties {
name: string;
key?: string;
description?: string;
version?: number;
category?: string;
}
/**
*
*/
export interface NodeData { export interface NodeData {
type: string; type: string;
name?: string; name?: string;
description?: string; description?: string;
config: { config?: NodeConfig;
executor?: string;
retryTimes?: number;
retryInterval?: number;
script?: string;
timeout?: number;
workingDirectory?: string;
environment?: string;
successExitCode?: string;
[key: string]: any;
};
} }
/**
*
*/
export interface EdgeData { export interface EdgeData {
condition?: string; condition?: string;
description?: string; description?: string;
priority?: number; priority?: number;
} }
export interface WorkflowDefinition {
id: number;
name: string;
description?: string;
bpmnJson: string;
}

View File

@ -1,63 +0,0 @@
import { Graph, Node, Edge } from '@antv/x6';
// 基础实体类型
export interface BaseEntity {
id: number;
createTime?: string;
createBy?: string;
updateTime?: string;
updateBy?: string;
version: number;
deleted: boolean;
extraData?: any;
}
// 位置类型
export interface Position {
x: number;
y: number;
}
// 尺寸类型
export interface Size {
width: number;
height: number;
}
// 工作流状态
export enum WorkflowStatus {
DRAFT = 'DRAFT', // 草稿
PUBLISHED = 'PUBLISHED', // 已发布
DISABLED = 'DISABLED' // 已禁用
}
// 节点类别
export enum NodeCategory {
EVENT = 'EVENT', // 事件节点
TASK = 'TASK', // 任务节点
GATEWAY = 'GATEWAY' // 网关节点
}
// 图形属性
export interface GraphAttrs {
body?: {
fill?: string;
stroke?: string;
strokeWidth?: number;
rx?: number;
ry?: number;
};
label?: {
text?: string;
fill?: string;
fontSize?: number;
fontWeight?: string;
};
image?: {
'xlink:href'?: string;
width?: number;
height?: number;
x?: number;
y?: number;
};
}

View File

@ -1,42 +0,0 @@
// 连线数据
export interface EdgeData {
id: string;
source: string;
target: string;
name?: string;
config: {
type: 'sequenceFlow';
condition?: string;
defaultFlow?: boolean;
};
properties: Record<string, any>;
}
// 连线验证错误
export interface EdgeValidationError {
edgeId: string;
property: string;
message: string;
}
// 连线样式
export interface EdgeStyle {
line: {
stroke: string;
strokeWidth: number;
targetMarker?: {
name: string;
size: number;
};
sourceMarker?: {
name: string;
size: number;
};
};
label?: {
text: string;
fill: string;
fontSize: number;
fontWeight?: string;
};
}

View File

@ -1,97 +0,0 @@
/**
*
*/
export interface WorkflowGraph {
nodes: WorkflowNode[];
edges: WorkflowEdge[];
properties?: WorkflowProperties;
}
/**
*
*/
export interface WorkflowProperties {
name: string;
key?: string;
description?: string;
version?: number;
category?: string;
}
/**
*
*/
export interface WorkflowNode {
id: string;
type: NodeType;
name: string;
position: Position;
config?: NodeConfig;
properties?: Record<string, any>;
size?: {
width: number;
height: number;
};
}
/**
*
*/
export enum NodeType {
START = 'start',
END = 'end',
USER_TASK = 'userTask',
SERVICE_TASK = 'serviceTask',
SCRIPT_TASK = 'scriptTask',
EXCLUSIVE_GATEWAY = 'exclusiveGateway',
PARALLEL_GATEWAY = 'parallelGateway',
SUBPROCESS = 'subProcess',
CALL_ACTIVITY = 'callActivity'
}
/**
*
*/
export interface WorkflowEdge {
id: string;
source: string;
target: string;
name?: string;
config?: EdgeConfig;
properties?: Record<string, any>;
}
/**
*
*/
export interface EdgeConfig {
condition?: string;
expression?: string;
type?: 'sequence' | 'message' | 'association';
}
/**
*
*/
export interface NodeConfig {
type?: string;
implementation?: string;
fields?: Record<string, any>;
assignee?: string;
candidateUsers?: string[];
candidateGroups?: string[];
dueDate?: string;
priority?: number;
formKey?: string;
skipExpression?: string;
isAsync?: boolean;
exclusive?: boolean;
}
/**
*
*/
export interface Position {
x: number;
y: number;
}

View File

@ -1,4 +0,0 @@
export * from './base';
export * from './node';
export * from './edge';
export * from './workflow';

View File

@ -1,90 +0,0 @@
import { BaseEntity, NodeCategory, Position, Size, GraphAttrs } from './base';
// 端口组配置
export interface PortGroup {
position: string;
attrs: {
circle: {
r: number;
magnet: boolean;
stroke?: string;
strokeWidth?: number;
fill?: string;
};
};
}
// 图形配置
export interface GraphConfig {
shape: 'circle' | 'rect' | 'diamond';
width: number;
height: number;
ports: {
groups: Record<string, PortGroup>;
};
attrs: GraphAttrs;
}
// 表单属性
export interface FormProperty {
name: string;
label: string;
type: string;
required?: boolean;
default?: any;
options?: Array<{
label: string;
value: any;
}>;
}
// 表单配置
export interface FormConfig {
properties: FormProperty[];
}
// 节点类型定义
export interface NodeType extends BaseEntity {
type: string;
name: string;
description: string;
category: NodeCategory;
flowableConfig: Record<string, any>;
graphConfig: GraphConfig;
formConfig: FormConfig;
orderNum: number;
enabled: boolean;
}
// 节点数据
export interface NodeData {
id: string;
type: string;
name: string;
position: Position;
size: Size;
config: {
type: string;
implementation?: string;
fields?: Record<string, {
string?: string;
expression?: string;
}>;
// Shell任务特有配置
script?: string;
workDir?: string;
// 用户任务特有配置
assignee?: string;
candidateUsers?: string;
candidateGroups?: string;
dueDate?: string;
};
properties: Record<string, any>;
}
// 节点验证错误
export interface NodeValidationError {
nodeId: string;
property: string;
message: string;
}

View File

@ -1,44 +0,0 @@
import { BaseEntity, WorkflowStatus } from './base';
import { NodeData } from './node';
import { EdgeData } from './edge';
// 图形数据
export interface GraphData {
nodes: NodeData[];
edges: EdgeData[];
properties: Record<string, any>;
}
// 表单配置
export interface WorkflowFormConfig {
formItems: Array<any>; // 根据实际表单配置类型定义
}
// 工作流定义
export interface WorkflowDefinition extends BaseEntity {
name: string;
key: string;
flowVersion: number;
description?: string;
status: WorkflowStatus;
bpmnXml: string;
graph: GraphData;
formConfig: WorkflowFormConfig;
}
// 工作流验证错误
export interface WorkflowValidationError {
type: 'node' | 'edge' | 'workflow';
id?: string;
message: string;
}
// 工作流查询参数
export interface WorkflowDefinitionQuery {
keyword?: string;
status?: WorkflowStatus;
startTime?: string;
endTime?: string;
pageSize?: number;
current?: number;
}

View File

@ -1,71 +0,0 @@
import { Graph } from '@antv/x6';
import { NodeData } from '../types';
export interface GraphJsonCell {
id: string;
shape: string;
data?: {
label: string;
serviceTask?: {
type: string;
implementation: string;
fields: any;
};
};
position?: {
x: number;
y: number;
};
source?: string;
target?: string;
}
export interface GraphJson {
cells: GraphJsonCell[];
}
export const loadGraphData = (graph: Graph, graphJson: GraphJson) => {
// 清空现有图形
graph.clearCells();
// 先添加所有节点
const nodes = graphJson.cells.filter(cell => cell.shape !== 'edge');
nodes.forEach(node => {
graph.addNode({
id: node.id,
shape: node.shape,
x: node.position?.x || 0,
y: node.position?.y || 0,
label: node.data?.label || '',
data: {
...node.data,
type: node.shape,
},
});
});
// 再添加所有边
const edges = graphJson.cells.filter(cell => cell.shape === 'edge');
edges.forEach(edge => {
graph.addEdge({
id: edge.id,
source: edge.source,
target: edge.target,
label: edge.data?.label || '',
attrs: {
line: {
stroke: '#5F95FF',
strokeWidth: 1,
targetMarker: {
name: 'classic',
size: 8,
},
},
},
});
});
// 自动布局
graph.centerContent();
graph.zoomToFit({ padding: 20 });
};

View File

@ -1,6 +1,7 @@
import { Graph } from '@antv/x6'; import { Graph } from '@antv/x6';
import { NodeType } from './types';
interface ValidationResult { export interface ValidationResult {
valid: boolean; valid: boolean;
errors: string[]; errors: string[];
} }
@ -14,8 +15,15 @@ export const validateFlow = (graph: Graph): ValidationResult => {
const nodes = graph.getNodes(); const nodes = graph.getNodes();
const edges = graph.getEdges(); const edges = graph.getEdges();
// 验证是否有节点
if (nodes.length === 0) {
result.errors.push('流程必须包含至少一个节点');
result.valid = false;
return result;
}
// 验证开始节点 // 验证开始节点
const startNodes = nodes.filter(node => node.getData()?.type === 'startEvent'); const startNodes = nodes.filter(node => node.getData()?.type === NodeType.START);
if (startNodes.length === 0) { if (startNodes.length === 0) {
result.errors.push('流程必须包含一个开始节点'); result.errors.push('流程必须包含一个开始节点');
result.valid = false; result.valid = false;
@ -25,10 +33,13 @@ export const validateFlow = (graph: Graph): ValidationResult => {
} }
// 验证结束节点 // 验证结束节点
const endNodes = nodes.filter(node => node.getData()?.type === 'endEvent'); const endNodes = nodes.filter(node => node.getData()?.type === NodeType.END);
if (endNodes.length === 0) { if (endNodes.length === 0) {
result.errors.push('流程必须包含至少一个结束节点'); result.errors.push('流程必须包含至少一个结束节点');
result.valid = false; result.valid = false;
} else if (endNodes.length > 1) {
result.errors.push('流程只能包含一个结束节点');
result.valid = false;
} }
// 验证孤立节点 // 验证孤立节点
@ -39,17 +50,17 @@ export const validateFlow = (graph: Graph): ValidationResult => {
const nodeType = nodeData?.type; const nodeType = nodeData?.type;
const nodeName = nodeData?.name || '未命名'; const nodeName = nodeData?.name || '未命名';
if (nodeType === 'startEvent' && incomingEdges.length > 0) { if (nodeType === NodeType.START && incomingEdges.length > 0) {
result.errors.push('开始节点不能有入边'); result.errors.push('开始节点不能有入边');
result.valid = false; result.valid = false;
} }
if (nodeType === 'endEvent' && outgoingEdges.length > 0) { if (nodeType === NodeType.END && outgoingEdges.length > 0) {
result.errors.push('结束节点不能有出边'); result.errors.push('结束节点不能有出边');
result.valid = false; result.valid = false;
} }
if (nodeType !== 'startEvent' && nodeType !== 'endEvent' && if (nodeType !== NodeType.START && nodeType !== NodeType.END &&
(incomingEdges.length === 0 || outgoingEdges.length === 0)) { (incomingEdges.length === 0 || outgoingEdges.length === 0)) {
result.errors.push(`节点 "${nodeName}" 未完全连接`); result.errors.push(`节点 "${nodeName}" 未完全连接`);
result.valid = false; result.valid = false;
@ -57,28 +68,51 @@ export const validateFlow = (graph: Graph): ValidationResult => {
}); });
// 验证网关配对 // 验证网关配对
const validateGatewayPairs = () => { const validateGateways = () => {
const gatewayNodes = nodes.filter(node => node.getData()?.type === 'exclusiveGateway'); const exclusiveGateways = nodes.filter(
gatewayNodes.forEach(gateway => { node => node.getData()?.type === NodeType.EXCLUSIVE_GATEWAY
);
const parallelGateways = nodes.filter(
node => node.getData()?.type === NodeType.PARALLEL_GATEWAY
);
// 验证排他网关
exclusiveGateways.forEach(gateway => {
const outgoingEdges = graph.getOutgoingEdges(gateway) || []; const outgoingEdges = graph.getOutgoingEdges(gateway) || [];
const incomingEdges = graph.getIncomingEdges(gateway) || []; const incomingEdges = graph.getIncomingEdges(gateway) || [];
const gatewayData = gateway.getData(); const gatewayData = gateway.getData();
const gatewayName = gatewayData?.name || '未命名'; const gatewayName = gatewayData?.name || '未命名';
// 排他网关必须至少有一个出口 if (outgoingEdges.length < 2) {
if (outgoingEdges.length < 1) { result.errors.push(`排他网关 "${gatewayName}" 必须至少有两个出口`);
result.errors.push(`排他网关 "${gatewayName}" 必须至少有一个出口`);
result.valid = false; result.valid = false;
} }
// 排他网关必须有入口
if (incomingEdges.length < 1) { if (incomingEdges.length < 1) {
result.errors.push(`排他网关 "${gatewayName}" 必须至少有一个入口`); result.errors.push(`排他网关 "${gatewayName}" 必须至少有一个入口`);
result.valid = false; result.valid = false;
} }
}); });
// 验证并行网关
parallelGateways.forEach(gateway => {
const outgoingEdges = graph.getOutgoingEdges(gateway) || [];
const incomingEdges = graph.getIncomingEdges(gateway) || [];
const gatewayData = gateway.getData();
const gatewayName = gatewayData?.name || '未命名';
if (outgoingEdges.length < 2) {
result.errors.push(`并行网关 "${gatewayName}" 必须至少有两个出口`);
result.valid = false;
}
if (incomingEdges.length < 1) {
result.errors.push(`并行网关 "${gatewayName}" 必须至少有一个入口`);
result.valid = false;
}
});
}; };
validateGatewayPairs(); validateGateways();
return result; return result;
}; };

View File

@ -1,17 +0,0 @@
import { NodeCategory } from './types';
// 节点分类配置
export const NODE_CATEGORY_CONFIG = {
[NodeCategory.TASK]: {
icon: 'CodeOutlined',
label: '任务节点',
},
[NodeCategory.EVENT]: {
icon: 'ThunderboltOutlined',
label: '事件节点',
},
[NodeCategory.GATEWAY]: {
icon: 'ForkOutlined',
label: '网关节点',
},
} as const;

View File

@ -15,7 +15,7 @@ import {
WorkflowStatus, WorkflowStatus,
WorkflowDefinitionBase WorkflowDefinitionBase
} from '../types'; } from '../types';
import {DeleteOutlined, EditOutlined, PlusOutlined} from '@ant-design/icons'; import {DeleteOutlined, PlusOutlined} from '@ant-design/icons';
const {confirm} = Modal; const {confirm} = Modal;

View File

@ -1,129 +0,0 @@
import { NodeCategory, NodeType } from '../types';
// Mock 节点类型数据
const mockNodeTypes: NodeType[] = [
{
id: 1,
code: 'start',
name: '开始节点',
color: '#67C23A',
icon: 'PlayCircleOutlined',
category: NodeCategory.EVENT,
description: '流程的开始节点',
enabled: true,
executors: [],
configSchema: '{}',
defaultConfig: '{}',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
},
{
id: 2,
code: 'end',
name: '结束节点',
color: '#F56C6C',
icon: 'StopOutlined',
category: NodeCategory.EVENT,
description: '流程的结束节点',
enabled: true,
executors: [],
configSchema: '{}',
defaultConfig: '{}',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
},
{
id: 3,
code: 'approval',
name: '审批节点',
color: '#409EFF',
icon: 'AuditOutlined',
category: NodeCategory.TASK,
description: '人工审批节点',
enabled: true,
executors: [],
configSchema: '{}',
defaultConfig: '{}',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
},
{
id: 4,
code: 'script',
name: '脚本节点',
color: '#909399',
icon: 'CodeOutlined',
category: NodeCategory.TASK,
description: '执行自定义脚本',
enabled: true,
executors: [],
configSchema: '{}',
defaultConfig: '{}',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
},
{
id: 5,
code: 'webhook',
name: 'Webhook',
color: '#9C27B0',
icon: 'ApiOutlined',
category: NodeCategory.TASK,
description: '调用外部 Webhook',
enabled: true,
executors: [],
configSchema: '{}',
defaultConfig: '{}',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
},
{
id: 6,
code: 'exclusive',
name: '排他网关',
color: '#FF9800',
icon: 'ForkOutlined',
category: NodeCategory.GATEWAY,
description: '根据条件选择一条分支执行',
enabled: true,
executors: [],
configSchema: '{}',
defaultConfig: '{}',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
},
{
id: 7,
code: 'parallel',
name: '并行网关',
color: '#795548',
icon: 'PartitionOutlined',
category: NodeCategory.GATEWAY,
description: '同时执行所有分支',
enabled: true,
executors: [],
configSchema: '{}',
defaultConfig: '{}',
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
}
];
interface GetNodeTypesParams {
enabled?: boolean;
}
// 获取节点类型列表
export const getNodeTypes = async (params?: GetNodeTypesParams): Promise<NodeType[]> => {
// 模拟 API 延迟
await new Promise(resolve => setTimeout(resolve, 500));
let result = [...mockNodeTypes];
// 根据启用状态筛选
if (params?.enabled !== undefined) {
result = result.filter(node => node.enabled === params.enabled);
}
return result;
};

View File

@ -1,15 +1,209 @@
export enum NodeCategory { import { BaseResponse } from '@/types/base/response';
TASK = 'TASK',
EVENT = 'EVENT', /**
GATEWAY = 'GATEWAY' *
*/
interface NodeGraphStyle {
fill: string;
stroke: string;
icon?: string;
iconColor?: string;
strokeWidth?: number;
} }
export interface NodeType { /**
code: string; *
name: string; */
color: string; interface PortGroup {
icon: string; position: 'left' | 'right';
category: NodeCategory; attrs: {
description?: string; circle: {
enabled?: boolean; r: number;
fill: string;
stroke: string;
};
};
} }
/**
*
*/
interface NodePorts {
groups: {
in?: PortGroup;
out?: PortGroup;
};
types: ('in' | 'out')[];
}
/**
*
*/
interface NodeGraph {
shape: 'circle' | 'rectangle';
size: {
width: number;
height: number;
};
style: NodeGraphStyle;
ports: NodePorts;
}
/**
*
*/
interface NodeConfig {
name: string;
description?: string;
script?: string;
language?: string;
[key: string]: any;
}
/**
*
*/
interface WorkflowNode {
id: string;
code: string;
type: string;
name: string;
graph: NodeGraph;
config: NodeConfig;
}
/**
*
*/
interface EdgeConfig {
type: 'sequence';
[key: string]: any;
}
/**
*
*/
interface WorkflowEdge {
id: string;
from: string;
to: string;
name: string;
config: EdgeConfig;
properties?: Record<string, any>;
}
/**
*
*/
export enum WorkflowStatus {
DRAFT = 'DRAFT', // 草稿
PUBLISHED = 'PUBLISHED', // 已发布
DISABLED = 'DISABLED' // 已禁用
}
/**
*
*/
export interface WorkflowGraph {
nodes: WorkflowNode[];
edges: WorkflowEdge[];
}
/**
*
*/
export interface WorkflowDefinitionBase {
name: string; // 工作流名称
key: string; // 工作流标识
flowVersion?: number; // 流程版本
description?: string; // 描述信息
}
/**
*
*/
export interface CreateWorkflowDefinitionRequest extends WorkflowDefinitionBase {
status: WorkflowStatus; // 工作流状态
}
/**
*
*/
export interface UpdateWorkflowDefinitionRequest extends WorkflowDefinitionBase {
}
/**
*
*/
export interface FormConfig {
formItems: any[]; // 表单项配置
}
/**
*
*/
export interface WorkflowDefinition extends BaseResponse {
name: string; // 工作流名称
key: string; // 工作流标识
flowVersion: number; // 流程版本
bpmnXml: string | null; // BPMN XML定义
graph: WorkflowGraph; // 图形定义
formConfig: FormConfig; // 表单配置
status: WorkflowStatus; // 工作流状态
description: string; // 描述信息
deleted: boolean; // 是否删除
extraData: any; // 扩展数据
}
/**
*
*/
export const WorkflowConfigUtils = {
/**
*
*/
parseNodeConfig: (config: string): Record<string, any> => {
try {
return JSON.parse(config);
} catch {
return {nodes: []};
}
},
/**
*
*/
parseTransitionConfig: (config: string): Record<string, any> => {
try {
return JSON.parse(config);
} catch {
return {transitions: []};
}
},
/**
*
*/
parseFormDefinition: (config: string): Record<string, any> => {
try {
return JSON.parse(config);
} catch {
return {};
}
},
/**
*
*/
parseGraphDefinition: (config: string): Record<string, any> => {
try {
return JSON.parse(config);
} catch {
return {};
}
}
};
// 导出NodePanel中的所有类型
export * from './Designer/components/NodePanel/types';

View File

@ -1,152 +1,29 @@
import {Page} from '../../types/base'; import { Page } from '@/types/base';
import { BaseQuery } from '@/types/base/query';
import type { WorkflowDefinition, WorkflowStatus } from './Definition/types';
// 工作流状态枚举 /**
export enum WorkflowStatus { *
DRAFT = 'DRAFT', // 草稿 */
PUBLISHED = 'PUBLISHED', // 已发布 export interface WorkflowDefinitionQuery extends BaseQuery {
DISABLED = 'DISABLED' // 已禁用 keyword?: string; // 关键字搜索
status?: WorkflowStatus; // 工作流状态
enabled?: boolean; // 是否启用
} }
// 节点类型分类枚举 /**
export enum NodeCategory { *
TASK = 'TASK', // 任务节点 */
EVENT = 'EVENT', // 事件节点
GATEWAY = 'GATEWAY' // 网关节点
}
// 节点类型查询参数
export interface NodeTypeQuery {
enabled?: boolean;
category?: NodeCategory;
}
// 节点执行器
export interface NodeExecutor {
code: string;
name: string;
description: string;
configSchema: string; // JSON Schema
defaultConfig?: string;
}
// 节点类型
export interface NodeType {
id: number;
type: string;
name: string;
description: string;
category: NodeCategory;
flowableConfig: string; // JSON string
graphConfig: string; // JSON string
formConfig: string; // JSON string
orderNum: number;
enabled: boolean;
createTime: string;
updateTime: string;
createBy: string;
updateBy: string;
version: number;
deleted: boolean;
extraData: any;
}
// 工作流定义查询参数
export interface WorkflowDefinitionQuery {
page?: number;
size?: number;
sort?: string[];
keyword?: string;
status?: WorkflowStatus;
enabled?: boolean;
}
// 节点配置
export interface NodeConfig {
nodes: {
id: string;
type: string;
name: string;
config: Record<string, any>;
}[];
}
// 流转配置
export interface TransitionConfig {
transitions: {
from: string;
to: string;
condition: string;
}[];
}
// 工作流定义基本信息
export interface WorkflowDefinitionBase {
name: string;
key: string;
flowVersion?: number;
status?: WorkflowStatus;
description?: string;
}
// 工作流定义
export interface WorkflowDefinition extends WorkflowDefinitionBase {
id: number;
status: WorkflowStatus;
enabled: boolean;
nodeConfig: string; // JSON string of NodeConfig
transitionConfig: string; // JSON string of TransitionConfig
formDefinition: string; // JSON string
graphDefinition: string; // JSON string
createTime: string;
updateTime: string;
createBy: string;
updateBy: string;
}
// 创建工作流定义请求
export interface CreateWorkflowDefinitionRequest extends WorkflowDefinitionBase {
status: WorkflowStatus;
}
// 更新工作流定义请求
export interface UpdateWorkflowDefinitionRequest extends WorkflowDefinitionBase {
nodeConfig?: string; // JSON string of NodeConfig
transitionConfig?: string; // JSON string of TransitionConfig
formDefinition?: string; // JSON string
graphDefinition?: string; // JSON string
}
// 分页响应
export type WorkflowDefinitionPage = Page<WorkflowDefinition>; export type WorkflowDefinitionPage = Page<WorkflowDefinition>;
// 工作流配置的解析和序列化工具 /**
export const WorkflowConfigUtils = { *
parseNodeConfig: (config: string): NodeConfig => { */
try { export interface NodeTypeQuery extends BaseQuery {
return JSON.parse(config); enabled?: boolean; // 是否启用
} catch { category?: string; // 节点类别
return { nodes: [] }; }
}
}, // 重新导出工作流相关类型
parseTransitionConfig: (config: string): TransitionConfig => { export type { WorkflowDefinition } from './Definition/types';
try { export { WorkflowStatus } from './Definition/types';
return JSON.parse(config);
} catch {
return { transitions: [] };
}
},
parseFormDefinition: (config: string): Record<string, any> => {
try {
return JSON.parse(config);
} catch {
return {};
}
},
parseGraphDefinition: (config: string): Record<string, any> => {
try {
return JSON.parse(config);
} catch {
return {};
}
}
};