File size: 3,072 Bytes
a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 d0c18f0 a43ac26 | 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 | /**
* 插件卡片组件
*
* 使用 console 语义 token,与 PluginDetailPage 等页面保持一致
*/
import { Link } from 'react-router-dom';
import { Play, Pause, LayoutGrid } from 'lucide-react';
import { PluginListItem, PluginStatus } from '@typings/plugin';
import { StatusPill } from '@components/console';
import { Button } from '@components/ui/button';
interface PluginCardProps {
plugin: PluginListItem;
onEnable: (name: string) => void;
onDisable: (name: string) => void;
}
// 状态到 StatusPill 变体的映射
const statusVariantMap: Record<PluginStatus, 'enabled' | 'disabled' | 'error' | 'warning' | 'incomplete'> = {
enabled: 'enabled',
disabled: 'disabled',
error: 'error',
dependency_error: 'error',
incomplete: 'incomplete',
discovered: 'warning',
loaded: 'warning',
installed: 'disabled',
installing: 'warning',
uninstalling: 'warning',
};
export function PluginCard({ plugin, onEnable, onDisable }: PluginCardProps) {
const statusVariant = statusVariantMap[plugin.status] || 'disabled';
const hasUiPage = plugin.ui_entry?.type === 'static';
return (
<div className="py-3 px-4 bg-console-surface-raised rounded-console-md border border-console-border">
{/* 头部:名称 + 状态 */}
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<h3 className="font-semibold text-console-text-primary truncate">
{plugin.metadata.name}
</h3>
<div className="flex items-center gap-1.5 mt-1 text-xs text-console-text-secondary">
<span>v{plugin.metadata.version}</span>
<span className="text-console-text-muted">•</span>
<span>{plugin.metadata.author}</span>
</div>
</div>
<StatusPill status={statusVariant} />
</div>
{/* 描述 */}
<p className="text-sm text-console-text-secondary mt-3 line-clamp-2">
{plugin.metadata.description}
</p>
{plugin.errors.length > 0 && (
<p className="text-sm text-console-status-error mt-2">
{plugin.errors[0].message}
</p>
)}
{/* 操作按钮 */}
<div className="flex items-center gap-2 mt-3 pt-3 border-t border-console-border">
{plugin.status === 'disabled' && (
<Button size="sm" onClick={() => onEnable(plugin.metadata.name)} className="h-7 text-xs">
<Play className="h-3 w-3 mr-1" />
启用
</Button>
)}
{plugin.status === 'enabled' && (
<Button size="sm" variant="outline" onClick={() => onDisable(plugin.metadata.name)} className="h-7 text-xs">
<Pause className="h-3 w-3 mr-1" />
禁用
</Button>
)}
{hasUiPage && (
<Link to={`/tool/${plugin.metadata.name}`}>
<Button size="sm" variant="outline" className="h-7 text-xs">
<LayoutGrid className="h-3 w-3 mr-1" />
访问页面
</Button>
</Link>
)}
</div>
</div>
);
}
|