test1 / app /workflows /create /NodeLibrary.tsx
daios007's picture
init
9eb1c55
'use client';
import { useState } from 'react';
export default function NodeLibrary() {
const [activeCategory, setActiveCategory] = useState('trigger');
const [searchTerm, setSearchTerm] = useState('');
const nodeCategories = [
{ id: 'trigger', name: '触发器', icon: 'ri-play-line' },
{ id: 'action', name: '动作', icon: 'ri-tools-line' },
{ id: 'condition', name: '条件', icon: 'ri-question-line' },
{ id: 'data', name: '数据', icon: 'ri-database-line' },
{ id: 'ai', name: 'AI', icon: 'ri-brain-line' },
{ id: 'integration', name: '集成', icon: 'ri-links-line' }
];
const nodes = {
trigger: [
{ id: 'schedule', name: '定时触发', icon: '⏰', desc: '按时间表执行' },
{ id: 'webhook', name: 'Webhook', icon: '🔗', desc: 'HTTP请求触发' },
{ id: 'file-upload', name: '文件上传', icon: '📁', desc: '文件上传时触发' },
{ id: 'email', name: '邮件触发', icon: '📧', desc: '收到邮件时触发' },
{ id: 'database', name: '数据库变更', icon: '🗄️', desc: '数据变更时触发' }
],
action: [
{ id: 'http-request', name: 'HTTP请求', icon: '🌐', desc: '发送HTTP请求' },
{ id: 'send-email', name: '发送邮件', icon: '📮', desc: '发送电子邮件' },
{ id: 'file-operation', name: '文件操作', icon: '📄', desc: '文件增删改查' },
{ id: 'notification', name: '消息通知', icon: '🔔', desc: '发送通知消息' },
{ id: 'data-transform', name: '数据转换', icon: '🔄', desc: '转换数据格式' }
],
condition: [
{ id: 'if-else', name: '条件判断', icon: '🤔', desc: '根据条件分支' },
{ id: 'filter', name: '数据过滤', icon: '🔍', desc: '过滤数据项' },
{ id: 'loop', name: '循环处理', icon: '🔁', desc: '循环执行操作' },
{ id: 'delay', name: '延时等待', icon: '⏱️', desc: '延时一段时间' },
{ id: 'merge', name: '数据合并', icon: '🔀', desc: '合并多个数据源' }
],
data: [
{ id: 'mysql', name: 'MySQL', icon: '🐬', desc: 'MySQL数据库' },
{ id: 'mongodb', name: 'MongoDB', icon: '🍃', desc: 'MongoDB数据库' },
{ id: 'redis', name: 'Redis', icon: '📦', desc: 'Redis缓存' },
{ id: 'csv', name: 'CSV文件', icon: '📊', desc: 'CSV格式数据' },
{ id: 'json', name: 'JSON数据', icon: '📋', desc: 'JSON格式数据' }
],
ai: [
{ id: 'text-generation', name: '文本生成', icon: '✍️', desc: 'AI文本生成' },
{ id: 'text-analysis', name: '文本分析', icon: '🔎', desc: '文本内容分析' },
{ id: 'translation', name: '文本翻译', icon: '🌍', desc: '多语言翻译' },
{ id: 'sentiment', name: '情感分析', icon: '😊', desc: '情感倾向分析' },
{ id: 'ocr', name: '图片识别', icon: '👁️', desc: 'OCR文字识别' }
],
integration: [
{ id: 'slack', name: 'Slack', icon: '💬', desc: 'Slack消息推送' },
{ id: 'wechat', name: '微信', icon: '💚', desc: '微信消息推送' },
{ id: 'dingtalk', name: '钉钉', icon: '📱', desc: '钉钉消息推送' },
{ id: 'github', name: 'GitHub', icon: '🐙', desc: 'GitHub集成' },
{ id: 'google-sheets', name: 'Google Sheets', icon: '📈', desc: 'Google表格' }
]
};
const handleDragStart = (e: React.DragEvent, node: any) => {
e.dataTransfer.setData('application/json', JSON.stringify({
type: 'node',
nodeType: node.id,
...node
}));
};
const filteredNodes = searchTerm
? nodes[activeCategory as keyof typeof nodes]?.filter(node =>
node.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
node.desc.toLowerCase().includes(searchTerm.toLowerCase())
)
: nodes[activeCategory as keyof typeof nodes];
return (
<div className="h-full flex flex-col">
{/* 搜索框 */}
<div className="p-4 border-b border-gray-200">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<div className="w-4 h-4 flex items-center justify-center">
<i className="ri-search-line text-gray-400"></i>
</div>
</div>
<input
type="text"
placeholder="搜索节点..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-4 py-2 w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
/>
</div>
</div>
{/* 分类标签 */}
<div className="p-4 border-b border-gray-200">
<div className="grid grid-cols-2 gap-2">
{nodeCategories.map((category) => (
<button
key={category.id}
onClick={() => setActiveCategory(category.id)}
className={`p-2 rounded-lg text-sm font-medium transition-colors cursor-pointer ${
activeCategory === category.id
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:bg-gray-100'
}`}
>
<div className="flex items-center space-x-2">
<div className="w-4 h-4 flex items-center justify-center">
<i className={category.icon}></i>
</div>
<span>{category.name}</span>
</div>
</button>
))}
</div>
</div>
{/* 节点列表 */}
<div className="flex-1 overflow-y-auto p-4">
<div className="space-y-2">
{filteredNodes?.map((node) => (
<div
key={node.id}
draggable
onDragStart={(e) => handleDragStart(e, node)}
className="p-3 bg-white border border-gray-200 rounded-lg hover:shadow-md transition-all cursor-move"
>
<div className="flex items-start space-x-3">
<div className="w-8 h-8 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0">
<span className="text-sm">{node.icon}</span>
</div>
<div className="flex-1 min-w-0">
<h4 className="font-medium text-gray-900 text-sm">{node.name}</h4>
<p className="text-xs text-gray-500 mt-1 line-clamp-2">{node.desc}</p>
</div>
</div>
</div>
))}
</div>
</div>
{/* 模板快捷入口 */}
<div className="p-4 border-t border-gray-200">
<button className="w-full p-3 bg-blue-50 text-blue-700 rounded-lg hover:bg-blue-100 transition-colors cursor-pointer">
<div className="flex items-center justify-center space-x-2">
<div className="w-4 h-4 flex items-center justify-center">
<i className="ri-template-line"></i>
</div>
<span className="text-sm font-medium">使用模板</span>
</div>
</button>
</div>
</div>
);
}