refactor: 拆分SystemUserTab.tsx(588→311行)提取UserDetailDialog+CsvImportDialog+LdapImportDialog子组件

This commit is contained in:
attacker committed 2026-06-17 03:06:22 +08:00
1 parent 367232f2b3
commit 2b5cbfb992
4 files changed
+470 -315

No files matched your search

@@ -0,0 +1,146 @@
// CSV 批量导入用户弹窗(下载模板 + 文件选择 + 预览 + 导入结果) // Logan.Li
import { Button } from '@/components/ui/button';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Upload, Download, CheckCircle, XCircle } from 'lucide-react';
import { userApi } from '@/services/api';
interface CsvImportDialogProps {
importDialogOpen: 'csv' | 'ldap' | null;
setImportDialogOpen: (v: 'csv' | 'ldap' | null) => void;
importPreview: any[];
setImportPreview: (p: any[]) => void;
importing: boolean;
importResult: any;
setImportResult: (r: any) => void;
handleCsvUpload: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleBatchImport: () => void;
}
// CsvImportDialog 展示 CSV 导入流程:上传 → 预览 → 确认 → 结果 // Logan.Li
export function CsvImportDialog({
importDialogOpen,
setImportDialogOpen,
importPreview,
setImportPreview,
importing,
importResult,
setImportResult,
handleCsvUpload,
handleBatchImport,
}: CsvImportDialogProps) {
return (
<Dialog open={importDialogOpen === 'csv'} onOpenChange={(v) => !v && setImportDialogOpen(null)}>
<DialogContent className="bg-slate-900 border-slate-800 max-w-2xl">
<DialogHeader>
<DialogTitle className="text-slate-100 flex items-center gap-2">
<Upload className="h-5 w-5 text-blue-400" />
批量导入用户(CSV)
</DialogTitle>
<DialogDescription className="text-slate-500">
上传 CSV 文件批量创建用户。请先下载模板,按格式填写后上传。
</DialogDescription>
</DialogHeader>
{!importResult ? (
<>
<div className="space-y-3">
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300" onClick={() => userApi.downloadTemplate()}>
<Download className="h-4 w-4 mr-1.5" />
下载模板
</Button>
<label className="inline-flex items-center px-3 py-1.5 rounded-md text-sm bg-slate-800 border border-slate-700 text-slate-200 cursor-pointer hover:bg-slate-700 transition-colors">
<Upload className="h-4 w-4 mr-1.5" />
选择 CSV 文件
<input type="file" accept=".csv" className="hidden" onChange={handleCsvUpload} />
</label>
</div>
{importPreview.length > 0 && (
<div className="rounded-lg border border-slate-800 overflow-hidden">
<div className="px-3 py-2 bg-slate-800/50 text-xs text-slate-400">
预览:{importPreview.length} 条数据
</div>
<Table>
<TableHeader>
<TableRow className="border-slate-800">
<TableHead className="text-slate-400 text-xs">用户名</TableHead>
<TableHead className="text-slate-400 text-xs">昵称</TableHead>
<TableHead className="text-slate-400 text-xs">邮箱</TableHead>
<TableHead className="text-slate-400 text-xs">手机</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{importPreview.slice(0, 5).map((u, i) => (
<TableRow key={i} className="border-slate-800">
<TableCell className="text-slate-200 text-xs">{u.username}</TableCell>
<TableCell className="text-slate-300 text-xs">{u.nickname}</TableCell>
<TableCell className="text-slate-400 text-xs">{u.email}</TableCell>
<TableCell className="text-slate-400 text-xs">{u.phone}</TableCell>
</TableRow>
))}
{importPreview.length > 5 && (
<TableRow className="border-slate-800">
<TableCell colSpan={4} className="text-slate-500 text-xs text-center">
还有 {importPreview.length - 5} 条...
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" className="border-slate-700 text-slate-300" onClick={() => { setImportDialogOpen(null); setImportPreview([]); }}>
取消
</Button>
<Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={handleBatchImport} disabled={importing || importPreview.length === 0}>
{importing ? '导入中...' : `确认导入 (${importPreview.length})`}
</Button>
</DialogFooter>
</>
) : (
<div className="space-y-4 py-2">
<div className={`flex items-center gap-3 p-4 rounded-lg ${importResult.code === 200 ? 'bg-emerald-500/10' : 'bg-red-500/10'}`}>
{importResult.code === 200 ? (
<CheckCircle className="h-6 w-6 text-emerald-400 shrink-0" />
) : (
<XCircle className="h-6 w-6 text-red-400 shrink-0" />
)}
<span className="text-sm text-slate-200">{importResult.message}</span>
</div>
{importResult.errors?.length > 0 && (
<div className="max-h-32 overflow-auto rounded-lg bg-slate-800/50 p-3 space-y-1">
{importResult.errors.map((err: string, i: number) => (
<div key={i} className="text-xs text-red-400">{err}</div>
))}
</div>
)}
<DialogFooter>
<Button className="bg-blue-600 hover:bg-blue-700 text-white" onClick={() => { setImportDialogOpen(null); setImportPreview([]); setImportResult(null); }}>
完成
</Button>
</DialogFooter>
</div>
)}
</DialogContent>
</Dialog>
);
}