From 2b5cbfb99239e816c9f446924ba68d551e39cfeb Mon Sep 17 00:00:00 2001 From: attacker Date: Wed, 17 Jun 2026 03:06:22 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=8B=86=E5=88=86SystemUserTab.tsx?= =?UTF-8?q?(588=E2=86=92311=E8=A1=8C)=E6=8F=90=E5=8F=96UserDetailDialog+Cs?= =?UTF-8?q?vImportDialog+LdapImportDialog=E5=AD=90=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/system/CsvImportDialog.tsx | 146 ++++++++ .../components/system/LdapImportDialog.tsx | 182 +++++++++ .../src/components/system/SystemUserTab.tsx | 353 ++---------------- .../components/system/UserDetailDialog.tsx | 104 ++++++ 4 files changed, 470 insertions(+), 315 deletions(-) create mode 100644 frontend/src/components/system/CsvImportDialog.tsx create mode 100644 frontend/src/components/system/LdapImportDialog.tsx create mode 100644 frontend/src/components/system/UserDetailDialog.tsx diff --git a/frontend/src/components/system/CsvImportDialog.tsx b/frontend/src/components/system/CsvImportDialog.tsx new file mode 100644 index 0000000..76f0379 --- /dev/null +++ b/frontend/src/components/system/CsvImportDialog.tsx @@ -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) => void; + handleBatchImport: () => void; +} + +// CsvImportDialog 展示 CSV 导入流程:上传 → 预览 → 确认 → 结果 // Logan.Li +export function CsvImportDialog({ + importDialogOpen, + setImportDialogOpen, + importPreview, + setImportPreview, + importing, + importResult, + setImportResult, + handleCsvUpload, + handleBatchImport, +}: CsvImportDialogProps) { + return ( + !v && setImportDialogOpen(null)}> + + + + + 批量导入用户(CSV) + + + 上传 CSV 文件批量创建用户。请先下载模板,按格式填写后上传。 + + + + {!importResult ? ( + <> +
+
+ + +
+ + {importPreview.length > 0 && ( +
+
+ 预览:{importPreview.length} 条数据 +
+ + + + 用户名 + 昵称 + 邮箱 + 手机 + + + + {importPreview.slice(0, 5).map((u, i) => ( + + {u.username} + {u.nickname} + {u.email} + {u.phone} + + ))} + {importPreview.length > 5 && ( + + + 还有 {importPreview.length - 5} 条... + + + )} + +
+
+ )} +
+ + + + + + + ) : ( +
+
+ {importResult.code === 200 ? ( + + ) : ( + + )} + {importResult.message} +
+ {importResult.errors?.length > 0 && ( +
+ {importResult.errors.map((err: string, i: number) => ( +
{err}
+ ))} +
+ )} + + + +
+ )} +
+
+ ); +} diff --git a/frontend/src/components/system/LdapImportDialog.tsx b/frontend/src/components/system/LdapImportDialog.tsx new file mode 100644 index 0000000..d53c996 --- /dev/null +++ b/frontend/src/components/system/LdapImportDialog.tsx @@ -0,0 +1,182 @@ +// LDAP 用户导入弹窗(OU 选择 + 用户列表多选 + 导入结果) // 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 { Network, Loader2, RefreshCw, CheckCircle, XCircle } from 'lucide-react'; + +interface LdapImportDialogProps { + importDialogOpen: 'csv' | 'ldap' | null; + setImportDialogOpen: (v: 'csv' | 'ldap' | null) => void; + ldapUsers: any[]; + setLdapUsers: (u: any[]) => void; + ldapSelected: Set; + setLdapSelected: (s: Set) => void; + ldapLoading: boolean; + ldapImportOUs: any[]; + ldapImportOUDN: string; + setLdapImportOUDN: (v: string) => void; + loadLdapUsers: (ouDn?: string) => void; + handleLdapImport: () => void; + importing: boolean; + importResult: any; + setImportResult: (r: any) => void; +} + +// LdapImportDialog 展示 LDAP 用户检索和多选导入流程 // Logan.Li +export function LdapImportDialog({ + importDialogOpen, + setImportDialogOpen, + ldapUsers, + setLdapUsers, + ldapSelected, + setLdapSelected, + ldapLoading, + ldapImportOUs, + ldapImportOUDN, + setLdapImportOUDN, + loadLdapUsers, + handleLdapImport, + importing, + importResult, + setImportResult, +}: LdapImportDialogProps) { + return ( + !v && setImportDialogOpen(null)}> + + + + + 从 LDAP 导入用户 + + + 从 LDAP 服务器检索用户列表,选择需要导入的用户 + + + + {!importResult ? ( + <> +
+
+
+ 选择 OU + +
+ +
+ +

+ {ldapUsers.length > 0 ? `找到 ${ldapUsers.length} 个用户` : '选择 OU 或点击刷新获取用户列表'} +

+ + {ldapUsers.length > 0 && ( +
+ + + + + 0} + onChange={(e) => { + if (e.target.checked) { + setLdapSelected(new Set(ldapUsers.map((u) => u.dn))); + } else { + setLdapSelected(new Set()); + } + }} + /> + + 用户名 + 显示名 + 邮箱 + DN + + + + {ldapUsers.map((u) => ( + + + { + const next = new Set(ldapSelected); + if (e.target.checked) next.add(u.dn); + else next.delete(u.dn); + setLdapSelected(next); + }} + /> + + {u.uid} + {u.cn} + {u.mail} + {u.dn} + + ))} + +
+
+ )} +
+ + + + + + + ) : ( +
+
+ {importResult.code === 200 ? ( + + ) : ( + + )} + {importResult.message} +
+ + + +
+ )} +
+
+ ); +} diff --git a/frontend/src/components/system/SystemUserTab.tsx b/frontend/src/components/system/SystemUserTab.tsx index ab73a68..a17ed47 100644 --- a/frontend/src/components/system/SystemUserTab.tsx +++ b/frontend/src/components/system/SystemUserTab.tsx @@ -10,14 +10,6 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; import { Search, Plus, @@ -25,19 +17,14 @@ import { Network, Mail, Phone, - CheckCircle, - XCircle, Eye, Edit, Trash2, - Download, - Key, - Loader2, - RefreshCw, - User as UserIcon, } from 'lucide-react'; -import { userApi } from '@/services/api'; import { UserFormDialog, type UserFormData } from './UserFormDialog'; +import { UserDetailDialog } from './UserDetailDialog'; +import { CsvImportDialog } from './CsvImportDialog'; +import { LdapImportDialog } from './LdapImportDialog'; interface SystemUserTabProps { // Search @@ -272,83 +259,14 @@ export function SystemUserTab({ - {/* User Detail Dialog */} - setSelectedUser(null)}> - - - - - {selectedUser?.nickname} - - - @{selectedUser?.username} · {selectedUser?.email} - - + {/* 用户详情 / 创建编辑 / CSV 导入 / LDAP 导入 弹窗 — 均由独立子组件承载 */} + -
-
-
-

用户名

-

{selectedUser?.username}

-
-
-

昵称

-

{selectedUser?.nickname}

-
-
-

邮箱

-

{selectedUser?.email}

-
-
-

手机号

-

{selectedUser?.phone || '--'}

-
-
-

状态

-

{selectedUser && getUserStatusBadge(selectedUser.status)}

-
-
-

创建时间

-

- {selectedUser && selectedUser.createdAt ? new Date(selectedUser.createdAt).toLocaleDateString() : '--'} -

-
-
- -
-

角色

-
- {selectedUser?.roles.map((role: string) => ( - - {role} - - ))} -
-
-
- - - - - -
-
- - {/* Create / Edit User Dialog */} - {/* CSV 批量导入 Dialog */} - !v && setImportDialogOpen(null)}> - - - - - 批量导入用户(CSV) - - - 上传 CSV 文件批量创建用户。请先下载模板,按格式填写后上传。 - - + - {!importResult ? ( - <> -
-
- - -
- - {importPreview.length > 0 && ( -
-
- 预览:{importPreview.length} 条数据 -
- - - - 用户名 - 昵称 - 邮箱 - 手机 - - - - {importPreview.slice(0, 5).map((u, i) => ( - - {u.username} - {u.nickname} - {u.email} - {u.phone} - - ))} - {importPreview.length > 5 && ( - - - 还有 {importPreview.length - 5} 条... - - - )} - -
-
- )} -
- - - - - - - ) : ( -
-
- {importResult.code === 200 ? ( - - ) : ( - - )} - {importResult.message} -
- {importResult.errors?.length > 0 && ( -
- {importResult.errors.map((err: string, i: number) => ( -
{err}
- ))} -
- )} - - - -
- )} -
-
- - {/* LDAP 导入 Dialog */} - !v && setImportDialogOpen(null)}> - - - - - 从 LDAP 导入用户 - - - 从 LDAP 服务器检索用户列表,选择需要导入的用户 - - - - {!importResult ? ( - <> -
-
-
- 选择 OU - -
- -
- -

- {ldapUsers.length > 0 ? `找到 ${ldapUsers.length} 个用户` : '选择 OU 或点击刷新获取用户列表'} -

- - {ldapUsers.length > 0 && ( -
- - - - - 0} - onChange={(e) => { - if (e.target.checked) { - setLdapSelected(new Set(ldapUsers.map((u) => u.dn))); - } else { - setLdapSelected(new Set()); - } - }} - /> - - 用户名 - 显示名 - 邮箱 - DN - - - - {ldapUsers.map((u) => ( - - - { - const next = new Set(ldapSelected); - if (e.target.checked) next.add(u.dn); - else next.delete(u.dn); - setLdapSelected(next); - }} - /> - - {u.uid} - {u.cn} - {u.mail} - {u.dn} - - ))} - -
-
- )} -
- - - - - - - ) : ( -
-
- {importResult.code === 200 ? ( - - ) : ( - - )} - {importResult.message} -
- - - -
- )} -
-
+ ); } diff --git a/frontend/src/components/system/UserDetailDialog.tsx b/frontend/src/components/system/UserDetailDialog.tsx new file mode 100644 index 0000000..7c71042 --- /dev/null +++ b/frontend/src/components/system/UserDetailDialog.tsx @@ -0,0 +1,104 @@ +// 用户详情弹窗(只读信息 + 角色徽章 + 编辑入口) // Logan.Li +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Key, Edit, User as UserIcon } from 'lucide-react'; + +interface UserDetailDialogProps { + selectedUser: any | null; + setSelectedUser: (u: any | null) => void; + getUserStatusBadge: (status: 'active' | 'inactive') => React.ReactNode; + openEdit: (user: any) => void; +} + +// UserDetailDialog 仅展示选中用户的基本信息和角色列表 // Logan.Li +export function UserDetailDialog({ + selectedUser, + setSelectedUser, + getUserStatusBadge, + openEdit, +}: UserDetailDialogProps) { + return ( + setSelectedUser(null)}> + + + + + {selectedUser?.nickname} + + + @{selectedUser?.username} · {selectedUser?.email} + + + +
+
+
+

用户名

+

{selectedUser?.username}

+
+
+

昵称

+

{selectedUser?.nickname}

+
+
+

邮箱

+

{selectedUser?.email}

+
+
+

手机号

+

{selectedUser?.phone || '--'}

+
+
+

状态

+

{selectedUser && getUserStatusBadge(selectedUser.status)}

+
+
+

创建时间

+

+ {selectedUser && selectedUser.createdAt ? new Date(selectedUser.createdAt).toLocaleDateString() : '--'} +

+
+
+ +
+

角色

+
+ {selectedUser?.roles.map((role: string) => ( + + {role} + + ))} +
+
+
+ + + + + +
+
+ ); +}