189 lines
6.2 KiB
TypeScript
189 lines
6.2 KiB
TypeScript
import React from 'react';
|
||
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;
|
||
}
|
||
|
||
const NodePanel: React.FC<NodePanelProps> = ({ className = '' }) => {
|
||
// 按分类分组节点
|
||
const nodesByCategory = NODE_DEFINITIONS.reduce((acc, node) => {
|
||
if (!acc[node.category]) {
|
||
acc[node.category] = [];
|
||
}
|
||
acc[node.category].push(node);
|
||
return acc;
|
||
}, {} as Record<NodeCategory, WorkflowNodeDefinition[]>);
|
||
|
||
// 拖拽开始处理
|
||
const handleDragStart = (event: React.DragEvent, nodeDefinition: WorkflowNodeDefinition) => {
|
||
event.dataTransfer.setData('application/reactflow', JSON.stringify({
|
||
nodeType: nodeDefinition.nodeType,
|
||
nodeDefinition
|
||
}));
|
||
event.dataTransfer.effectAllowed = 'move';
|
||
};
|
||
|
||
// 渲染节点项
|
||
const renderNodeItem = (nodeDefinition: WorkflowNodeDefinition) => (
|
||
<div
|
||
key={nodeDefinition.nodeCode}
|
||
draggable
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '8px',
|
||
padding: '8px 12px',
|
||
borderRadius: '6px',
|
||
border: '1px solid #e5e7eb',
|
||
background: 'white',
|
||
cursor: 'grab',
|
||
transition: 'all 0.2s ease-in-out',
|
||
marginBottom: '6px'
|
||
}}
|
||
onMouseEnter={(e) => {
|
||
e.currentTarget.style.background = '#f9fafb';
|
||
e.currentTarget.style.borderColor = nodeDefinition.uiConfig.style.fill;
|
||
e.currentTarget.style.transform = 'translateX(2px)';
|
||
}}
|
||
onMouseLeave={(e) => {
|
||
e.currentTarget.style.background = 'white';
|
||
e.currentTarget.style.borderColor = '#e5e7eb';
|
||
e.currentTarget.style.transform = 'translateX(0)';
|
||
}}
|
||
onDragStart={(e) => {
|
||
e.currentTarget.style.cursor = 'grabbing';
|
||
handleDragStart(e, nodeDefinition);
|
||
}}
|
||
onDragEnd={(e) => {
|
||
e.currentTarget.style.cursor = 'grab';
|
||
}}
|
||
>
|
||
<div style={{
|
||
fontSize: '16px',
|
||
width: '20px',
|
||
textAlign: 'center',
|
||
color: nodeDefinition.uiConfig.style.fill
|
||
}}>
|
||
{getNodeIcon(nodeDefinition.uiConfig.style.icon)}
|
||
</div>
|
||
<div>
|
||
<div style={{
|
||
fontSize: '13px',
|
||
fontWeight: '500',
|
||
color: '#374151',
|
||
lineHeight: '1.2'
|
||
}}>
|
||
{nodeDefinition.nodeName}
|
||
</div>
|
||
<div style={{
|
||
fontSize: '11px',
|
||
color: '#6b7280',
|
||
lineHeight: '1.2',
|
||
marginTop: '2px'
|
||
}}>
|
||
{nodeDefinition.description}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
// 分类标题映射
|
||
const categoryTitles = {
|
||
[NodeCategory.EVENT]: '🎯 事件节点',
|
||
[NodeCategory.TASK]: '📋 任务节点',
|
||
[NodeCategory.GATEWAY]: '🔀 网关节点',
|
||
[NodeCategory.CONTAINER]: '📦 容器节点'
|
||
};
|
||
|
||
return (
|
||
<div className={`node-panel ${className}`} style={{
|
||
width: '260px',
|
||
height: '100%',
|
||
background: '#f8fafc',
|
||
borderRight: '1px solid #e5e7eb',
|
||
overflow: 'hidden',
|
||
display: 'flex',
|
||
flexDirection: 'column'
|
||
}}>
|
||
<div style={{
|
||
padding: '16px',
|
||
background: 'white',
|
||
borderBottom: '1px solid #e5e7eb'
|
||
}}>
|
||
<h3 style={{
|
||
margin: 0,
|
||
fontSize: '14px',
|
||
fontWeight: '600',
|
||
color: '#374151'
|
||
}}>
|
||
节点面板
|
||
</h3>
|
||
<p style={{
|
||
margin: '4px 0 0 0',
|
||
fontSize: '12px',
|
||
color: '#6b7280'
|
||
}}>
|
||
拖拽节点到画布创建工作流
|
||
</p>
|
||
</div>
|
||
|
||
<div style={{
|
||
flex: '1',
|
||
overflow: 'auto',
|
||
padding: '12px'
|
||
}}>
|
||
{Object.entries(nodesByCategory).map(([category, nodes]) => (
|
||
<div key={category} style={{ marginBottom: '16px' }}>
|
||
<div style={{
|
||
fontSize: '12px',
|
||
fontWeight: '600',
|
||
color: '#4b5563',
|
||
marginBottom: '8px',
|
||
padding: '4px 0',
|
||
borderBottom: '1px solid #e5e7eb'
|
||
}}>
|
||
{categoryTitles[category as NodeCategory]}
|
||
</div>
|
||
{nodes.map(renderNodeItem)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 使用提示 */}
|
||
<div style={{
|
||
padding: '12px',
|
||
background: '#f1f5f9',
|
||
borderTop: '1px solid #e5e7eb',
|
||
fontSize: '11px',
|
||
color: '#64748b',
|
||
lineHeight: '1.4'
|
||
}}>
|
||
💡 提示:
|
||
<br />• 拖拽节点到画布创建
|
||
<br />• 双击节点进行配置
|
||
<br />• 连接节点创建流程
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default NodePanel;
|