diff --git a/frontend/src/pages/UserManage.tsx b/frontend/src/pages/UserManage.tsx index 54bf779..298ea56 100644 --- a/frontend/src/pages/UserManage.tsx +++ b/frontend/src/pages/UserManage.tsx @@ -12,7 +12,7 @@ import { } from 'lucide-react'; import { toast } from 'sonner'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { apiClient } from '@/lib/api'; +import { apiClient, auth } from '@/lib/api'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; @@ -71,6 +71,47 @@ export default function UserManage() { const [ldapOpen, setLdapOpen] = useState(false); const [selected, setSelected] = useState>(new Set()); const [importResult, setImportResult] = useState(null); + // 批量删除 + const [batchIds, setBatchIds] = useState>(new Set()); + const [batchDeleting, setBatchDeleting] = useState(false); + const currentUsername = (() => { + try { + const token = auth.getToken() || ''; + const raw = atob(token.replace(/-/g, '+').replace(/_/g, '/')); + return raw.split('|')[0]; + } catch { return ''; } + })(); + + const toggleBatch = (id: number) => { + setBatchIds((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); else next.add(id); + return next; + }); + }; + + const batchDelete = async () => { + const targets = users.filter((u: any) => batchIds.has(u.id)); + if (targets.length === 0) return; + const preview = targets.slice(0, 5).map((u: any) => u.username).join('、') + (targets.length > 5 ? ` 等 ${targets.length} 个` : ''); + if (!window.confirm(`确定删除 ${preview} 吗?`)) return; + setBatchDeleting(true); + let ok = 0; + const failed: string[] = []; + for (const u of targets) { + try { + await apiClient.deleteUser(u.id); + ok++; + } catch (err) { + failed.push(`${u.username}(${(err as Error).message})`); + } + } + setBatchDeleting(false); + setBatchIds(new Set()); + invalidateUsers(); + if (failed.length === 0) toast.success(`已删除 ${ok} 个用户`); + else toast.warning(`删除完成:成功 ${ok},失败 ${failed.length}`, { description: failed.join(';') }); + }; const usersQuery = useQuery({ queryKey: ['users'], queryFn: apiClient.listUsers }); const { data: settings } = useQuery({ queryKey: ['settings'], queryFn: apiClient.getSettings }); @@ -80,6 +121,9 @@ export default function UserManage() { const users: any[] = usersQuery.data ?? []; + const selectableUsers = users.filter((u: any) => u.username !== currentUsername); + const allChecked = selectableUsers.length > 0 && selectableUsers.every((u: any) => batchIds.has(u.id)); + const invalidateUsers = () => queryClient.invalidateQueries({ queryKey: ['users'] }); // LDAP 预览列表(仅弹窗打开且未进入结果态时请求) @@ -255,6 +299,17 @@ export default function UserManage() { 新建用户 + {batchIds.size > 0 && ( + + )} @@ -280,6 +335,14 @@ export default function UserManage() { + {['用户名', '昵称', '邮箱', '角色', '来源', '创建时间', '操作'].map((h) => ( +
+ setBatchIds(e.target.checked ? new Set(selectableUsers.map((u: any) => u.id)) : new Set())} + title="全选(不含当前登录用户)" + /> + {h} @@ -294,8 +357,18 @@ export default function UserManage() { const isAdmin = u.role === 'admin'; return (
+ {u.username === currentUsername ? ( + + ) : ( + toggleBatch(u.id)} /> + )} + {u.username} + {u.username === currentUsername && ( + (当前登录) + )} {u.nickname || '—'} {u.email || '—'}