1
This commit is contained in:
parent
aacc7437cc
commit
c9b5ee9e95
@ -1,155 +0,0 @@
|
||||
-- =====================================================
|
||||
-- 修复 workflow_node_definition 表的初始化数据
|
||||
-- =====================================================
|
||||
|
||||
-- 删除注释,添加正确的节点定义数据
|
||||
INSERT INTO workflow_node_definition (
|
||||
node_type, node_code, node_name, description, category,
|
||||
ui_variables, panel_variables_schema, local_variables_schema,
|
||||
form_variables_schema, enabled,
|
||||
create_time, create_by, update_time, update_by, version, deleted
|
||||
) VALUES
|
||||
-- 开始节点
|
||||
(
|
||||
'START_EVENT', 'START_EVENT', '开始节点', '工作流的起点', 'EVENT',
|
||||
'{
|
||||
"shape": "circle",
|
||||
"size": {"width": 40, "height": 40},
|
||||
"style": {
|
||||
"fill": "#e8f7ff",
|
||||
"stroke": "#1890ff",
|
||||
"strokeWidth": 2,
|
||||
"icon": "play-circle",
|
||||
"iconColor": "#1890ff"
|
||||
},
|
||||
"ports": {
|
||||
"groups": {
|
||||
"out": {
|
||||
"position": "right",
|
||||
"attrs": {
|
||||
"circle": {"r": 4, "fill": "#ffffff", "stroke": "#1890ff"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
'{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {"type": "string", "title": "节点Code"},
|
||||
"name": {"type": "string", "title": "节点名称"},
|
||||
"description": {"type": "string", "title": "节点描述"}
|
||||
},
|
||||
"required": ["code", "name"]
|
||||
}',
|
||||
'{}',
|
||||
'{"formItems": []}',
|
||||
1,
|
||||
NOW(), 'system', NOW(), 'system', 1, 0
|
||||
),
|
||||
-- 结束节点
|
||||
(
|
||||
'END_EVENT', 'END_EVENT', '结束节点', '工作流的终点', 'EVENT',
|
||||
'{
|
||||
"shape": "circle",
|
||||
"size": {"width": 40, "height": 40},
|
||||
"style": {
|
||||
"fill": "#fff1f0",
|
||||
"stroke": "#ff4d4f",
|
||||
"strokeWidth": 2,
|
||||
"icon": "stop",
|
||||
"iconColor": "#ff4d4f"
|
||||
},
|
||||
"ports": {
|
||||
"groups": {
|
||||
"in": {
|
||||
"position": "left",
|
||||
"attrs": {
|
||||
"circle": {"r": 4, "fill": "#ffffff", "stroke": "#1890ff"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
'{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {"type": "string", "title": "节点Code"},
|
||||
"name": {"type": "string", "title": "节点名称"},
|
||||
"description": {"type": "string", "title": "节点描述"}
|
||||
},
|
||||
"required": ["code", "name"]
|
||||
}',
|
||||
'{}',
|
||||
'{"formItems": []}',
|
||||
1,
|
||||
NOW(), 'system', NOW(), 'system', 1, 0
|
||||
),
|
||||
-- 脚本任务节点
|
||||
(
|
||||
'SCRIPT_TASK', 'SCRIPT_TASK', '脚本任务', '脚本执行任务', 'TASK',
|
||||
'{
|
||||
"shape": "rect",
|
||||
"size": {"width": 120, "height": 60},
|
||||
"style": {
|
||||
"fill": "#ffffff",
|
||||
"stroke": "#1890ff",
|
||||
"strokeWidth": 2,
|
||||
"icon": "code",
|
||||
"iconColor": "#1890ff"
|
||||
},
|
||||
"ports": {
|
||||
"groups": {
|
||||
"in": {
|
||||
"position": "left",
|
||||
"attrs": {
|
||||
"circle": {"r": 4, "fill": "#ffffff", "stroke": "#1890ff"}
|
||||
}
|
||||
},
|
||||
"out": {
|
||||
"position": "right",
|
||||
"attrs": {
|
||||
"circle": {"r": 4, "fill": "#ffffff", "stroke": "#1890ff"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
'{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {"type": "string", "title": "节点Code"},
|
||||
"name": {"type": "string", "title": "节点名称"},
|
||||
"description": {"type": "string", "title": "节点描述"},
|
||||
"script": {
|
||||
"type": "string",
|
||||
"title": "脚本内容",
|
||||
"format": "textarea"
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"title": "脚本语言",
|
||||
"default": "shell",
|
||||
"enum": ["shell", "python", "javascript"],
|
||||
"enumNames": ["Shell脚本", "Python脚本", "JavaScript脚本"]
|
||||
},
|
||||
"workingDirectory": {
|
||||
"type": "string",
|
||||
"title": "工作目录",
|
||||
"default": "/tmp"
|
||||
},
|
||||
"delegate": {
|
||||
"type": "string",
|
||||
"title": "执行委派者",
|
||||
"default": "${shellTaskDelegate}"
|
||||
}
|
||||
},
|
||||
"required": ["code", "name", "script", "language", "delegate"]
|
||||
}',
|
||||
'{
|
||||
"environment": {"type": "object", "additionalProperties": {"type": "string"}}
|
||||
}',
|
||||
'{"formItems": []}',
|
||||
1,
|
||||
NOW(), 'system', NOW(), 'system', 1, 0
|
||||
);
|
||||
@ -25,7 +25,7 @@ alwaysApply: true
|
||||
## 上下文与设置
|
||||
<a id="上下文与设置"></a>
|
||||
|
||||
你是超智能AI编程助手,集成在Windsurf IDE中(一个基于VS Code的AI增强IDE)。由于你的先进能力,你经常过于热衷于在未经明确请求的情况下实现更改,这可能导致代码逻辑破坏。为防止这种情况,你必须严格遵循本协议。
|
||||
你是超智能AI编程助手,集成在Cursor IDE中(一个基于VS Code的AI增强IDE)。由于你的先进能力,你经常过于热衷于在未经明确请求的情况下实现更改,这可能导致代码逻辑破坏。为防止这种情况,你必须严格遵循本协议。
|
||||
|
||||
**语言设置**:除非用户另有指示,所有常规交互响应应使用中文。然而,模式声明(如[MODE: RESEARCH])和特定格式化输出(如代码块、检查清单等)应保持英文以确保格式一致性。
|
||||
|
||||
|
||||
@ -3,21 +3,6 @@ import { NodeCategory } from '../types';
|
||||
import { NODE_DEFINITIONS } from '../nodes';
|
||||
import type { WorkflowNodeDefinition } from '../nodes/types';
|
||||
|
||||
// 图标映射函数
|
||||
const getNodeIcon = (iconName: string): string => {
|
||||
const iconMap: Record<string, string> = {
|
||||
'play-circle': '▶️',
|
||||
'stop-circle': '⏹️',
|
||||
'user': '👤',
|
||||
'api': '⚙️',
|
||||
'code': '📜',
|
||||
'build': '🚀',
|
||||
'jenkins': '🔨',
|
||||
'gateway': '💎'
|
||||
};
|
||||
return iconMap[iconName] || '📋';
|
||||
};
|
||||
|
||||
interface NodePanelProps {
|
||||
className?: string;
|
||||
}
|
||||
@ -60,7 +45,7 @@ const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = '#f9fafb';
|
||||
e.currentTarget.style.borderColor = nodeDefinition.uiConfig.style.fill;
|
||||
e.currentTarget.style.borderColor = nodeDefinition.renderConfig.theme.primary;
|
||||
e.currentTarget.style.transform = 'translateX(2px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
@ -80,9 +65,9 @@ const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||||
fontSize: '16px',
|
||||
width: '20px',
|
||||
textAlign: 'center',
|
||||
color: nodeDefinition.uiConfig.style.fill
|
||||
color: nodeDefinition.renderConfig.theme.primary
|
||||
}}>
|
||||
{getNodeIcon(nodeDefinition.uiConfig.style.icon)}
|
||||
{nodeDefinition.renderConfig.icon.content}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{
|
||||
|
||||
@ -45,8 +45,8 @@ export const useWorkflowLoad = () => {
|
||||
label: node.nodeName,
|
||||
nodeType: node.nodeType as any,
|
||||
category: getNodeCategory(node.nodeType) as any,
|
||||
icon: nodeDefinition?.uiConfig.style.icon || getNodeIcon(node.nodeType),
|
||||
color: nodeDefinition?.uiConfig.style.fill || getNodeColor(node.nodeType),
|
||||
icon: nodeDefinition?.renderConfig.icon.content || getNodeIcon(node.nodeType),
|
||||
color: nodeDefinition?.renderConfig.theme.primary || getNodeColor(node.nodeType),
|
||||
configs: node.configs || {},
|
||||
inputMapping: node.inputMapping || {},
|
||||
outputMapping: node.outputMapping || {},
|
||||
|
||||
@ -358,8 +358,8 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
label: nodeDefinition.nodeName,
|
||||
nodeType: nodeDefinition.nodeType,
|
||||
category: nodeDefinition.category,
|
||||
icon: nodeDefinition.uiConfig.style.icon,
|
||||
color: nodeDefinition.uiConfig.style.fill,
|
||||
icon: nodeDefinition.renderConfig.icon.content,
|
||||
color: nodeDefinition.renderConfig.theme.primary,
|
||||
// 保存原始节点定义引用,用于配置
|
||||
nodeDefinition,
|
||||
// 初始化配置数据
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||||
import { BaseNodeDefinition, NodeType, NodeCategory } from './types';
|
||||
import type { FlowNodeData } from '../types';
|
||||
|
||||
/**
|
||||
* 结束事件节点定义(元数据)
|
||||
* 结束事件节点定义(纯配置)
|
||||
* 使用 BaseNode 组件渲染,无需自定义渲染代码
|
||||
*/
|
||||
export const EndEventNodeDefinition: BaseNodeDefinition = {
|
||||
nodeCode: "END_EVENT",
|
||||
@ -13,14 +11,29 @@ export const EndEventNodeDefinition: BaseNodeDefinition = {
|
||||
category: NodeCategory.EVENT,
|
||||
description: "工作流的结束节点",
|
||||
|
||||
uiConfig: {
|
||||
size: { width: 80, height: 50 },
|
||||
style: {
|
||||
fill: '#ff4d4f',
|
||||
stroke: '#cf1322',
|
||||
strokeWidth: 2,
|
||||
icon: 'stop-circle',
|
||||
iconColor: '#fff'
|
||||
// 渲染配置(配置驱动)
|
||||
renderConfig: {
|
||||
shape: 'circle',
|
||||
size: { width: 100, height: 60 },
|
||||
icon: {
|
||||
type: 'emoji',
|
||||
content: '⏹️',
|
||||
size: 40
|
||||
},
|
||||
theme: {
|
||||
primary: '#ef4444',
|
||||
secondary: '#f87171',
|
||||
selectedBorder: '#3b82f6',
|
||||
hoverBorder: '#ef4444',
|
||||
gradient: ['#fef2f2', '#fee2e2']
|
||||
},
|
||||
handles: {
|
||||
input: true, // 结束节点有输入
|
||||
output: false // 无输出
|
||||
},
|
||||
features: {
|
||||
showBadge: false, // 结束节点不显示徽章
|
||||
showHoverMenu: false // 结束节点不显示菜单
|
||||
}
|
||||
},
|
||||
|
||||
@ -52,89 +65,4 @@ export const EndEventNodeDefinition: BaseNodeDefinition = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 结束事件节点渲染组件
|
||||
*/
|
||||
const EndEventNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||||
const nodeData = data as FlowNodeData;
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '50%',
|
||||
border: `3px solid ${selected ? '#3b82f6' : '#ef4444'}`,
|
||||
background: selected
|
||||
? 'linear-gradient(135deg, #fee2e2 0%, #fecaca 100%)'
|
||||
: 'linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%)',
|
||||
minWidth: '70px',
|
||||
minHeight: '70px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
boxShadow: selected
|
||||
? '0 8px 16px rgba(59, 130, 246, 0.25), 0 2px 4px rgba(59, 130, 246, 0.15)'
|
||||
: isHovered
|
||||
? '0 6px 12px rgba(239, 68, 68, 0.2), 0 2px 4px rgba(239, 68, 68, 0.1)'
|
||||
: '0 2px 8px rgba(0,0,0,0.08)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transform: isHovered ? 'scale(1.05)' : 'scale(1)',
|
||||
}}
|
||||
>
|
||||
{/* 图标 */}
|
||||
<div style={{
|
||||
fontSize: '26px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
filter: selected ? 'brightness(1.1)' : 'none',
|
||||
transition: 'filter 0.2s ease'
|
||||
}}>
|
||||
⏹️
|
||||
</div>
|
||||
|
||||
{/* 输入连接点 */}
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
style={{
|
||||
background: '#ef4444',
|
||||
border: '3px solid white',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 节点标签 */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '-30px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
fontSize: '13px',
|
||||
color: '#1f2937',
|
||||
fontWeight: '600',
|
||||
whiteSpace: 'nowrap',
|
||||
background: 'white',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,0.1)',
|
||||
border: '1px solid #e5e7eb',
|
||||
}}
|
||||
>
|
||||
{nodeData.label || '结束'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(EndEventNode);
|
||||
|
||||
// ✅ 不再需要单独的渲染组件,使用 BaseNode 即可
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||||
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from './types';
|
||||
import type { FlowNodeData } from '../types';
|
||||
|
||||
/**
|
||||
* Jenkins构建节点定义(元数据)
|
||||
* Jenkins构建节点定义(纯配置)
|
||||
* 使用 BaseNode 组件渲染,无需自定义渲染代码
|
||||
*/
|
||||
export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
||||
nodeCode: "JENKINS_BUILD",
|
||||
@ -13,14 +11,29 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
||||
category: NodeCategory.TASK,
|
||||
description: "通过Jenkins执行构建任务",
|
||||
|
||||
uiConfig: {
|
||||
// 渲染配置(配置驱动)
|
||||
renderConfig: {
|
||||
shape: 'rounded-rect',
|
||||
size: { width: 160, height: 80 },
|
||||
style: {
|
||||
fill: '#52c41a',
|
||||
stroke: '#389e08',
|
||||
strokeWidth: 2,
|
||||
icon: 'jenkins',
|
||||
iconColor: '#fff'
|
||||
icon: {
|
||||
type: 'emoji',
|
||||
content: '🔨',
|
||||
size: 40
|
||||
},
|
||||
theme: {
|
||||
primary: '#52c41a',
|
||||
secondary: '#73d13d',
|
||||
selectedBorder: '#3b82f6',
|
||||
hoverBorder: '#73d13d',
|
||||
gradient: ['#ffffff', '#f6ffed']
|
||||
},
|
||||
handles: {
|
||||
input: true,
|
||||
output: true
|
||||
},
|
||||
features: {
|
||||
showBadge: true,
|
||||
showHoverMenu: true
|
||||
}
|
||||
},
|
||||
|
||||
@ -122,16 +135,12 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
||||
type: "object",
|
||||
title: "构建参数",
|
||||
description: "传递给Jenkins Job的构建参数",
|
||||
properties: {},
|
||||
additionalProperties: true,
|
||||
default: {}
|
||||
},
|
||||
environmentVariables: {
|
||||
type: "object",
|
||||
title: "环境变量",
|
||||
description: "构建时的环境变量",
|
||||
properties: {},
|
||||
additionalProperties: true,
|
||||
default: {}
|
||||
},
|
||||
commitId: {
|
||||
@ -203,12 +212,6 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
||||
type: "object",
|
||||
title: "测试结果",
|
||||
description: "构建中的测试结果统计",
|
||||
properties: {
|
||||
totalCount: { type: "number", title: "总测试数", default: 0 },
|
||||
passCount: { type: "number", title: "通过数", default: 0 },
|
||||
failCount: { type: "number", title: "失败数", default: 0 },
|
||||
skipCount: { type: "number", title: "跳过数", default: 0 }
|
||||
},
|
||||
default: {
|
||||
totalCount: 0,
|
||||
passCount: 0,
|
||||
@ -221,167 +224,4 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Jenkins构建节点渲染组件
|
||||
*/
|
||||
const JenkinsBuildNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||||
const nodeData = data as FlowNodeData;
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{
|
||||
padding: '16px 20px',
|
||||
borderRadius: '12px',
|
||||
border: `2px solid ${selected ? '#3b82f6' : isHovered ? '#73d13d' : '#e5e7eb'}`,
|
||||
background: selected
|
||||
? 'linear-gradient(135deg, #ffffff 0%, #f0f9ff 100%)'
|
||||
: 'linear-gradient(135deg, #ffffff 0%, #f6ffed 100%)',
|
||||
minWidth: '160px',
|
||||
minHeight: '80px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
boxShadow: selected
|
||||
? '0 8px 16px rgba(59, 130, 246, 0.2), 0 2px 8px rgba(59, 130, 246, 0.1)'
|
||||
: isHovered
|
||||
? '0 6px 16px rgba(82, 196, 26, 0.15), 0 2px 6px rgba(82, 196, 26, 0.08)'
|
||||
: '0 2px 8px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transform: isHovered ? 'translateY(-2px)' : 'translateY(0)',
|
||||
}}
|
||||
>
|
||||
{/* 输入连接点 */}
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
style={{
|
||||
background: '#52c41a',
|
||||
border: '3px solid white',
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
|
||||
transition: 'all 0.2s ease',
|
||||
transform: isHovered ? 'scale(1.2)' : 'scale(1)',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 节点内容 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
{/* 图标背景 */}
|
||||
<div style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '10px',
|
||||
background: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 4px 8px rgba(82, 196, 26, 0.2)',
|
||||
transition: 'transform 0.2s ease',
|
||||
transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
||||
}}>
|
||||
<span style={{ fontSize: '22px' }}>🔨</span>
|
||||
</div>
|
||||
|
||||
{/* 标签 */}
|
||||
<div style={{
|
||||
fontSize: '14px',
|
||||
color: '#1f2937',
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
lineHeight: '1.2'
|
||||
}}>
|
||||
{nodeData.label || 'Jenkins构建'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 输出连接点 */}
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
style={{
|
||||
background: '#52c41a',
|
||||
border: '3px solid white',
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
|
||||
transition: 'all 0.2s ease',
|
||||
transform: isHovered ? 'scale(1.2)' : 'scale(1)',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 配置指示器 */}
|
||||
{nodeData.configs && Object.keys(nodeData.configs).length > 3 && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '50%',
|
||||
background: 'linear-gradient(135deg, #10b981 0%, #34d399 100%)',
|
||||
border: '2px solid white',
|
||||
fontSize: '10px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
boxShadow: '0 2px 6px rgba(16, 185, 129, 0.3)',
|
||||
}}
|
||||
>
|
||||
✓
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hover操作菜单 */}
|
||||
{isHovered && !selected && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-12px',
|
||||
right: '8px',
|
||||
display: 'flex',
|
||||
gap: '4px',
|
||||
opacity: isHovered ? 1 : 0,
|
||||
transition: 'opacity 0.2s ease',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '6px',
|
||||
background: 'white',
|
||||
border: '1px solid #e5e7eb',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
title="复制"
|
||||
>
|
||||
📋
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(JenkinsBuildNode);
|
||||
|
||||
// ✅ 不再需要单独的渲染组件,使用 BaseNode 即可
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||||
import { BaseNodeDefinition, NodeType, NodeCategory } from './types';
|
||||
import type { FlowNodeData } from '../types';
|
||||
|
||||
/**
|
||||
* 开始事件节点定义(元数据)
|
||||
* 开始事件节点定义(纯配置)
|
||||
* 使用 BaseNode 组件渲染,无需自定义渲染代码
|
||||
*/
|
||||
export const StartEventNodeDefinition: BaseNodeDefinition = {
|
||||
nodeCode: "START_EVENT",
|
||||
@ -13,14 +11,29 @@ export const StartEventNodeDefinition: BaseNodeDefinition = {
|
||||
category: NodeCategory.EVENT,
|
||||
description: "工作流的起始节点",
|
||||
|
||||
uiConfig: {
|
||||
size: { width: 80, height: 50 },
|
||||
style: {
|
||||
fill: '#52c41a',
|
||||
stroke: '#389e08',
|
||||
strokeWidth: 2,
|
||||
icon: 'play-circle',
|
||||
iconColor: '#fff'
|
||||
// 渲染配置(配置驱动)
|
||||
renderConfig: {
|
||||
shape: 'circle',
|
||||
size: { width: 100, height: 60 },
|
||||
icon: {
|
||||
type: 'emoji',
|
||||
content: '▶️',
|
||||
size: 40
|
||||
},
|
||||
theme: {
|
||||
primary: '#10b981',
|
||||
secondary: '#34d399',
|
||||
selectedBorder: '#3b82f6',
|
||||
hoverBorder: '#10b981',
|
||||
gradient: ['#ecfdf5', '#d1fae5']
|
||||
},
|
||||
handles: {
|
||||
input: false, // 开始节点无输入
|
||||
output: true // 有输出
|
||||
},
|
||||
features: {
|
||||
showBadge: false, // 开始节点不显示徽章
|
||||
showHoverMenu: false // 开始节点不显示菜单
|
||||
}
|
||||
},
|
||||
|
||||
@ -52,89 +65,4 @@ export const StartEventNodeDefinition: BaseNodeDefinition = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 开始事件节点渲染组件
|
||||
*/
|
||||
const StartEventNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||||
const nodeData = data as FlowNodeData;
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '50%',
|
||||
border: `3px solid ${selected ? '#3b82f6' : '#10b981'}`,
|
||||
background: selected
|
||||
? 'linear-gradient(135deg, #d0fce8 0%, #a7f3d0 100%)'
|
||||
: 'linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%)',
|
||||
minWidth: '70px',
|
||||
minHeight: '70px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
boxShadow: selected
|
||||
? '0 8px 16px rgba(59, 130, 246, 0.25), 0 2px 4px rgba(59, 130, 246, 0.15)'
|
||||
: isHovered
|
||||
? '0 6px 12px rgba(16, 185, 129, 0.2), 0 2px 4px rgba(16, 185, 129, 0.1)'
|
||||
: '0 2px 8px rgba(0,0,0,0.08)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transform: isHovered ? 'scale(1.05)' : 'scale(1)',
|
||||
}}
|
||||
>
|
||||
{/* 图标 */}
|
||||
<div style={{
|
||||
fontSize: '26px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
filter: selected ? 'brightness(1.1)' : 'none',
|
||||
transition: 'filter 0.2s ease'
|
||||
}}>
|
||||
▶️
|
||||
</div>
|
||||
|
||||
{/* 输出连接点 */}
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
style={{
|
||||
background: '#10b981',
|
||||
border: '3px solid white',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 节点标签 */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '-30px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
fontSize: '13px',
|
||||
color: '#1f2937',
|
||||
fontWeight: '600',
|
||||
whiteSpace: 'nowrap',
|
||||
background: 'white',
|
||||
padding: '4px 10px',
|
||||
borderRadius: '6px',
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,0.1)',
|
||||
border: '1px solid #e5e7eb',
|
||||
}}
|
||||
>
|
||||
{nodeData.label || '开始'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(StartEventNode);
|
||||
|
||||
// ✅ 不再需要单独的渲染组件,使用 BaseNode 即可
|
||||
|
||||
244
frontend/src/pages/Workflow/Design/nodes/components/BaseNode.tsx
Normal file
244
frontend/src/pages/Workflow/Design/nodes/components/BaseNode.tsx
Normal file
@ -0,0 +1,244 @@
|
||||
import React, { useState, CSSProperties } from 'react';
|
||||
import { Handle, Position, NodeProps } from '@xyflow/react';
|
||||
import type { FlowNodeData } from '../../types';
|
||||
|
||||
/**
|
||||
* 通用节点组件 - 配置驱动渲染
|
||||
* 所有节点共用这一个组件,通过 renderConfig 配置不同样式
|
||||
*/
|
||||
const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
|
||||
const nodeData = data as FlowNodeData;
|
||||
const definition = nodeData?.nodeDefinition;
|
||||
|
||||
// 如果没有 definition,显示错误提示
|
||||
if (!definition) {
|
||||
return <div>节点未配置定义</div>;
|
||||
}
|
||||
|
||||
const config = definition.renderConfig;
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
// 🎨 计算容器样式
|
||||
const getContainerStyle = (): CSSProperties => {
|
||||
const { shape, size, theme } = config;
|
||||
|
||||
const baseStyle: CSSProperties = {
|
||||
minWidth: `${size.width}px`,
|
||||
minHeight: `${size.height}px`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
};
|
||||
|
||||
// 根据形状设置样式
|
||||
switch (shape) {
|
||||
case 'circle':
|
||||
baseStyle.borderRadius = '50%';
|
||||
baseStyle.padding = '12px';
|
||||
break;
|
||||
case 'rounded-rect':
|
||||
baseStyle.borderRadius = '12px';
|
||||
baseStyle.padding = '16px 20px';
|
||||
break;
|
||||
case 'rect':
|
||||
baseStyle.borderRadius = '4px';
|
||||
baseStyle.padding = '16px 20px';
|
||||
break;
|
||||
case 'diamond':
|
||||
baseStyle.transform = 'rotate(45deg)';
|
||||
baseStyle.padding = '16px';
|
||||
break;
|
||||
}
|
||||
|
||||
// 边框和背景
|
||||
const borderColor = selected
|
||||
? theme.selectedBorder
|
||||
: isHovered
|
||||
? theme.hoverBorder
|
||||
: '#e5e7eb';
|
||||
|
||||
baseStyle.border = `${selected ? '3px' : '2px'} solid ${borderColor}`;
|
||||
baseStyle.background = selected
|
||||
? `linear-gradient(135deg, ${theme.gradient[0]} 0%, #f0f9ff 100%)`
|
||||
: `linear-gradient(135deg, ${theme.gradient[0]} 0%, ${theme.gradient[1]} 100%)`;
|
||||
|
||||
// 阴影
|
||||
baseStyle.boxShadow = selected
|
||||
? `0 8px 16px ${theme.selectedBorder}33, 0 2px 8px ${theme.selectedBorder}22`
|
||||
: isHovered
|
||||
? `0 6px 16px ${theme.hoverBorder}26, 0 2px 6px ${theme.hoverBorder}14`
|
||||
: '0 2px 8px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)';
|
||||
|
||||
// Hover 效果
|
||||
if (isHovered) {
|
||||
baseStyle.transform = 'translateY(-2px)';
|
||||
}
|
||||
|
||||
return baseStyle;
|
||||
};
|
||||
|
||||
// 🔌 计算连接点样式
|
||||
const getHandleStyle = (): CSSProperties => {
|
||||
return {
|
||||
background: config.theme.primary,
|
||||
border: '3px solid white',
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
|
||||
transition: 'all 0.2s ease',
|
||||
transform: isHovered ? 'scale(1.2)' : 'scale(1)',
|
||||
};
|
||||
};
|
||||
|
||||
// 🎭 渲染图标
|
||||
const renderIcon = () => {
|
||||
const { icon, theme } = config;
|
||||
|
||||
if (icon.type === 'emoji') {
|
||||
return (
|
||||
<div style={{
|
||||
width: `${icon.size || 40}px`,
|
||||
height: `${icon.size || 40}px`,
|
||||
borderRadius: '10px',
|
||||
background: `linear-gradient(135deg, ${theme.primary} 0%, ${theme.secondary} 100%)`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: `0 4px 8px ${theme.primary}33`,
|
||||
transition: 'transform 0.2s ease',
|
||||
transform: isHovered ? 'scale(1.1)' : 'scale(1)',
|
||||
}}>
|
||||
<span style={{ fontSize: `${(icon.size || 40) * 0.55}px` }}>
|
||||
{icon.content}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: 支持自定义组件图标
|
||||
return <span>{icon.content}</span>;
|
||||
};
|
||||
|
||||
// 🎖️ 渲染配置徽章
|
||||
const renderBadge = () => {
|
||||
if (!config.features.showBadge) return null;
|
||||
if (!nodeData.configs || Object.keys(nodeData.configs).length <= 3) return null;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '-8px',
|
||||
right: '-8px',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '50%',
|
||||
background: `linear-gradient(135deg, ${config.theme.primary} 0%, ${config.theme.secondary} 100%)`,
|
||||
border: '2px solid white',
|
||||
fontSize: '10px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
boxShadow: `0 2px 6px ${config.theme.primary}4D`,
|
||||
}}>
|
||||
✓
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 📋 渲染 Hover 菜单
|
||||
const renderHoverMenu = () => {
|
||||
if (!config.features.showHoverMenu) return null;
|
||||
if (!isHovered || selected) return null;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '-12px',
|
||||
right: '8px',
|
||||
display: 'flex',
|
||||
gap: '4px',
|
||||
opacity: isHovered ? 1 : 0,
|
||||
transition: 'opacity 0.2s ease',
|
||||
}}>
|
||||
<div style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '6px',
|
||||
background: 'white',
|
||||
border: '1px solid #e5e7eb',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
||||
fontSize: '12px',
|
||||
}} title="复制">
|
||||
📋
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={getContainerStyle()}
|
||||
>
|
||||
{/* 🔌 输入连接点 */}
|
||||
{config.handles.input && (
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
style={getHandleStyle()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 📦 节点内容 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
{/* 🎭 图标 */}
|
||||
{renderIcon()}
|
||||
|
||||
{/* 🏷️ 标签 */}
|
||||
<div style={{
|
||||
fontSize: '14px',
|
||||
color: '#1f2937',
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
lineHeight: '1.2'
|
||||
}}>
|
||||
{nodeData.label || definition.nodeName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 🔌 输出连接点 */}
|
||||
{config.handles.output && (
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
style={getHandleStyle()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 🎖️ 配置徽章 */}
|
||||
{renderBadge()}
|
||||
|
||||
{/* 📋 Hover 菜单 */}
|
||||
{renderHoverMenu()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(BaseNode);
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* 节点定义统一导出
|
||||
* 每个节点文件同时导出:
|
||||
* 1. xxxDefinition - 节点元数据配置(给配置表单用)
|
||||
* 2. default export - 节点渲染组件(给画布用)
|
||||
* 所有节点使用统一的 BaseNode 组件渲染
|
||||
* 通过 renderConfig 配置不同的样式和行为
|
||||
*/
|
||||
|
||||
import StartEventNode, { StartEventNodeDefinition } from './StartEventNode';
|
||||
import EndEventNode, { EndEventNodeDefinition } from './EndEventNode';
|
||||
import JenkinsBuildNode, { JenkinsBuildNodeDefinition } from './JenkinsBuildNode';
|
||||
import BaseNode from './components/BaseNode';
|
||||
import { StartEventNodeDefinition } from './StartEventNode';
|
||||
import { EndEventNodeDefinition } from './EndEventNode';
|
||||
import { JenkinsBuildNodeDefinition } from './JenkinsBuildNode';
|
||||
import type { WorkflowNodeDefinition } from './types';
|
||||
|
||||
/**
|
||||
@ -21,21 +21,20 @@ export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
|
||||
|
||||
/**
|
||||
* React Flow 节点类型映射(给 FlowCanvas 使用)
|
||||
* ✨ 所有节点都使用 BaseNode 组件,通过 renderConfig 配置渲染
|
||||
*/
|
||||
export const nodeTypes = {
|
||||
START_EVENT: StartEventNode,
|
||||
END_EVENT: EndEventNode,
|
||||
JENKINS_BUILD: JenkinsBuildNode,
|
||||
START_EVENT: BaseNode,
|
||||
END_EVENT: BaseNode,
|
||||
JENKINS_BUILD: BaseNode,
|
||||
};
|
||||
|
||||
/**
|
||||
* 单独导出(按需导入)
|
||||
*/
|
||||
export {
|
||||
// 组件
|
||||
StartEventNode,
|
||||
EndEventNode,
|
||||
JenkinsBuildNode,
|
||||
// 通用组件
|
||||
BaseNode,
|
||||
|
||||
// 定义
|
||||
StartEventNodeDefinition,
|
||||
|
||||
@ -47,30 +47,53 @@ export const getNodeCategory = (nodeType: NodeType | string): NodeCategory => {
|
||||
import type { ISchema } from '@formily/react';
|
||||
export type JSONSchema = ISchema;
|
||||
|
||||
// UI 配置
|
||||
// ========== 渲染配置(配置驱动节点渲染) ==========
|
||||
|
||||
// 节点尺寸
|
||||
export interface NodeSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface NodeStyle {
|
||||
fill: string;
|
||||
icon: string;
|
||||
stroke: string;
|
||||
iconColor: string;
|
||||
strokeWidth: number;
|
||||
iconSize?: number;
|
||||
borderRadius?: string;
|
||||
boxShadow?: string;
|
||||
transition?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: string;
|
||||
fontFamily?: string;
|
||||
// 节点形状
|
||||
export type NodeShape = 'circle' | 'rounded-rect' | 'rect' | 'diamond';
|
||||
|
||||
// 图标配置
|
||||
export interface IconConfig {
|
||||
type: 'emoji' | 'component'; // emoji 或 React 组件
|
||||
content: string; // emoji: "🔨", component: "JenkinsIcon"
|
||||
size?: number; // 图标大小(默认 24)
|
||||
}
|
||||
|
||||
export interface UIConfig {
|
||||
size: NodeSize;
|
||||
style: NodeStyle;
|
||||
// 颜色主题配置
|
||||
export interface ThemeConfig {
|
||||
primary: string; // 主色
|
||||
secondary: string; // 次要色
|
||||
selectedBorder: string; // 选中边框色
|
||||
hoverBorder: string; // Hover 边框色
|
||||
gradient: [string, string]; // 背景渐变 [起始, 结束]
|
||||
}
|
||||
|
||||
// 连接点配置
|
||||
export interface HandlesConfig {
|
||||
input: boolean; // 是否显示输入连接点
|
||||
output: boolean; // 是否显示输出连接点
|
||||
}
|
||||
|
||||
// 功能开关配置
|
||||
export interface FeaturesConfig {
|
||||
showBadge: boolean; // 是否显示配置徽章
|
||||
showHoverMenu: boolean; // 是否显示 Hover 菜单
|
||||
}
|
||||
|
||||
// 完整的渲染配置
|
||||
export interface RenderConfig {
|
||||
shape: NodeShape; // 节点形状
|
||||
size: NodeSize; // 节点尺寸
|
||||
icon: IconConfig; // 图标配置
|
||||
theme: ThemeConfig; // 颜色主题
|
||||
handles: HandlesConfig; // 连接点配置
|
||||
features: FeaturesConfig; // 功能开关
|
||||
}
|
||||
|
||||
// 基础节点定义(只有基本配置)
|
||||
@ -80,8 +103,8 @@ export interface BaseNodeDefinition {
|
||||
nodeType: NodeType;
|
||||
category: NodeCategory;
|
||||
description: string;
|
||||
uiConfig: UIConfig;
|
||||
configSchema: JSONSchema; // 基本配置Schema(包含基本信息+节点配置)
|
||||
renderConfig: RenderConfig; // ✨ 渲染配置(配置驱动)
|
||||
configSchema: JSONSchema; // 基本配置Schema(包含基本信息+节点配置)
|
||||
}
|
||||
|
||||
// 可配置节点定义(有3个TAB:基本配置、输入、输出)
|
||||
@ -108,7 +131,6 @@ export interface NodeInstanceData {
|
||||
|
||||
// UI位置信息
|
||||
position: { x: number; y: number };
|
||||
uiConfig: UIConfig;
|
||||
}
|
||||
|
||||
// 判断是否为可配置节点
|
||||
|
||||
Loading…
Reference in New Issue
Block a user