| '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> |
| ); |
| } |