/* eslint-disable @typescript-eslint/no-explicit-any */ import { useState, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Badge } from '@/components/ui/badge'; import { CheckCircle, XCircle, Users, Shield, Settings } from 'lucide-react'; import { userApi } from '@/api'; import type { Role } from '@/types'; import { SystemUserTab } from './SystemUserTab'; import { SystemRolesTab } from './SystemRolesTab'; import { SystemSettingsTab } from './SystemSettingsTab'; import { ResetPasswordDialog } from './ResetPasswordDialog'; import { useLdapManagement } from './useLdapManagement'; import type { UserFormData } from './UserFormDialog'; // 后端用户数据转换为前端兼容格式 function mapUser( u: any) { return { ...u, status: u.status === 1 || u.status === 'active' ? ('active' as const) : ('inactive' as const), roles: Array.isArray(u.roles) ? u.roles : u.role ? [u.role] : [], lastLoginAt: u.last_login ?? u.lastLoginAt, createdAt: u.created_at ?? u.createdAt, authSource: u.auth_source ?? u.authSource ?? 'local', }; } const emptyForm: UserFormData = { username: '', nickname: '', email: '', phone: '', role: '', password: '', status: 1 as number, }; export function SystemManager() { const confirm = useConfirm(); const location = useLocation(); // 侧边栏菜单路径映射到 Tab // Logan.Li const pathToTab: Record = { '/users': 'users', '/roles': 'roles', '/settings': 'settings', }; const [activeTab, setActiveTab] = useState(pathToTab[location.pathname] || 'users'); // URL 变化时同步 Tab(侧边栏菜单点击触发) // Logan.Li useEffect(() => { const tab = pathToTab[location.pathname]; if (tab && tab !== activeTab) { setActiveTab(tab); } }, [location.pathname]); const [searchQuery, setSearchQuery] = useState(''); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [selectedUser, setSelectedUser] = useState(null); const [resetPasswordUser, setResetPasswordUser] = useState(null); const [selectedRole, setSelectedRole] = useState(null); // 创建/编辑用户弹窗 const [dialogOpen, setDialogOpen] = useState(false); const [editingUser, setEditingUser] = useState(null); const [saving, setSaving] = useState(false); const [form, setForm] = useState({ ...emptyForm }); // CSV 批量导入弹窗 const [importDialogOpen, setImportDialogOpen] = useState<'csv' | 'ldap' | null>(null); const [importPreview, setImportPreview] = useState([]); const [importResult, setImportResult] = useState(null); const loadUsers = async () => { setLoading(true); try { const res = await userApi.getUsers({ page: 1, page_size: 100 }); const rawUsers = res?.list ?? []; setUsers(rawUsers.map(mapUser)); } catch (error) { console.error('Failed to fetch users:', error); setUsers([]); } finally { setLoading(false); } }; // LDAP 状态全部由 hook 管理(在 loadUsers 之后调用避免 TDZ) // Logan.Li const ldap = useLdapManagement(loadUsers); useEffect(() => { loadUsers(); ldap.loadLdapConfig(); }, []); // LDAP 导入 Dialog 打开时加载 OU 和用户 useEffect(() => { if (importDialogOpen === 'ldap') { ldap.loadImportDialogData(); } }, [importDialogOpen]); const filteredUsers = users.filter( (u) => (u.username || '').toLowerCase().includes(searchQuery.toLowerCase()) || (u.nickname || '').toLowerCase().includes(searchQuery.toLowerCase()) || (u.email || '').toLowerCase().includes(searchQuery.toLowerCase()) ); const getUserStatusBadge = (status: 'active' | 'inactive') => { return status === 'active' ? ( 正常 ) : ( 禁用 ); }; const openCreate = () => { setEditingUser(null); setForm({ ...emptyForm }); setDialogOpen(true); }; const openEdit = ( user: any) => { setEditingUser(user); setForm({ username: user.username || '', nickname: user.nickname || '', email: user.email || '', phone: user.phone || '', role: Array.isArray(user.roles) ? user.roles[0] || '' : user.role || '', password: '', status: user.status === 'active' ? 1 : 0, }); setDialogOpen(true); }; const handleSubmit = async () => { setSaving(true); try { const payload: Record = { username: form.username, nickname: form.nickname, email: form.email, phone: form.phone, role: form.role, status: form.status, }; if (form.password) { payload.password = form.password; } if (editingUser) { await userApi.updateUser(editingUser.id, payload); } else { await userApi.createUser(payload); } setDialogOpen(false); await loadUsers(); } catch (error) { console.error('Failed to save user:', error); } finally { setSaving(false); } }; const handleDelete = async ( user: any) => { const ok = await confirm({ title: '删除用户', description: `确认删除用户「${user.nickname || user.username}」吗?`, confirmText: '删除', variant: 'destructive' }); if (!ok) return; try { await userApi.deleteUser(user.id); await loadUsers(); } catch (error) { console.error('Failed to delete user:', error); } }; // CSV 文件解析 const handleCsvUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (ev) => { const text = ev.target?.result as string; const lines = text.split('\n').filter((l) => l.trim() && !l.startsWith('\xEF\xBB\xBF')); if (lines.length < 2) return; // Find column indices from header const headers = lines[0].split(',').map((h) => h.trim().toLowerCase()); const idxUsername = headers.findIndex((h) => h.includes('用户名') || h.includes('username')); const idxNickname = headers.findIndex((h) => h.includes('昵称') || h.includes('name') || h.includes('显示名')); const idxEmail = headers.findIndex((h) => h.includes('邮箱') || h.includes('email') || h.includes('mail')); const idxPhone = headers.findIndex((h) => h.includes('手机') || h.includes('phone')); const idxRole = headers.findIndex((h) => h.includes('角色') || h.includes('role')); const idxPassword = headers.findIndex((h) => h.includes('密码') || h.includes('password')); const parsed: Record[] = []; for (let i = 1; i < lines.length; i++) { const cols = lines[i].split(',').map((c) => c.trim()); parsed.push({ username: idxUsername >= 0 ? cols[idxUsername] : '', nickname: idxNickname >= 0 ? cols[idxNickname] : '', email: idxEmail >= 0 ? cols[idxEmail] : '', phone: idxPhone >= 0 ? cols[idxPhone] : '', role: idxRole >= 0 ? cols[idxRole] : '', password: idxPassword >= 0 ? cols[idxPassword] : '', }); } setImportPreview(parsed); setImportResult(null); }; reader.readAsText(file, 'UTF-8'); }; // 执行 CSV 批量导入 const handleBatchImport = async () => { ldap.setImporting(true); try { const res = await userApi.batchImport({ users: importPreview, skip_existing: true }); setImportResult(res); await loadUsers(); } catch (error) { setImportResult({ code: 500, message: error?.message || '导入失败' }); } finally { ldap.setImporting(false); } }; return (
{/* Page Header */}

系统管理

用户、权限与系统配置

{/* Tabs */} 用户管理 角色权限 系统设置 { setSelectedUser(null); setResetPasswordUser(u); }} importDialogOpen={importDialogOpen} setImportDialogOpen={setImportDialogOpen} importPreview={importPreview} setImportPreview={setImportPreview} importing={ldap.importing} importResult={importResult} setImportResult={setImportResult} handleCsvUpload={handleCsvUpload} handleBatchImport={handleBatchImport} ldapUsers={ldap.ldapUsers} ldapSelected={ldap.ldapSelected} setLdapSelected={ldap.setLdapSelected} ldapLoading={ldap.ldapLoading} ldapImportOUs={ldap.ldapImportOUs} ldapImportOUDN={ldap.ldapImportOUDN} setLdapImportOUDN={ldap.setLdapImportOUDN} loadLdapUsers={ldap.loadLdapUsers} handleLdapImport={ldap.handleLdapImport} setLdapUsers={ldap.setLdapUsers} /> setResetPasswordUser(null)} />
); }