增加工具栏提示。
This commit is contained in:
parent
09489b3e11
commit
f8b1019625
@ -15,4 +15,7 @@
|
|||||||
- 一次将所有修改应用于单个文件
|
- 一次将所有修改应用于单个文件
|
||||||
- 请勿修改不相关的文件
|
- 请勿修改不相关的文件
|
||||||
|
|
||||||
|
4. 代码注释:
|
||||||
|
- 尽量都编写代码注释,写明实现的方式
|
||||||
|
|
||||||
记住要始终考虑每个项目的背景和特定需求。
|
记住要始终考虑每个项目的背景和特定需求。
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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; // 扩展数据
|
||||||
|
}
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -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 }));
|
||||||
|
|||||||
@ -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 });
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export * from './base';
|
|
||||||
export * from './node';
|
|
||||||
export * from './edge';
|
|
||||||
export * from './workflow';
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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 });
|
|
||||||
};
|
|
||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
};
|
|
||||||
@ -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';
|
||||||
@ -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 {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user