File size: 7,145 Bytes
9eb1c55 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
'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>
);
} |