import { useState, useEffect, useCallback } from 'react' import type { User, Role, PaginatedResponse } from '@/types' import { usersApi, type UserQueryParams, type UserDetail as UserDetailType } from '@/api' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Badge } from '@/components/ui/badge' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { PaginationNavigator } from '@/components/ui/pagination' import UserDetail from '@/components/UserDetail' import UserRoleDialog from '@/components/UserRoleDialog' import { Search, RefreshCw, Eye, UserPlus, Ban, Unlock, Loader2 } from 'lucide-react' // 状态颜色映射 const statusColors: Record = { active: 'bg-green-500', banned: 'bg-red-500', deleted: 'bg-gray-500', } // 状态文本映射 const statusText: Record = { active: '正常', banned: '已封禁', deleted: '已删除', } // 角色颜色映射 const roleColors: Record = { admin: 'bg-red-500', moderator: 'bg-orange-500', user: 'bg-blue-500', super_admin: 'bg-purple-500', } export default function Users() { // 状态 const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) const [total, setTotal] = useState(0) const [page, setPage] = useState(1) const pageSize = 10 const [keyword, setKeyword] = useState('') const [statusFilter, setStatusFilter] = useState('all') // 详情对话框 const [detailOpen, setDetailOpen] = useState(false) const [selectedUser, setSelectedUser] = useState(null) const [detailLoading, setDetailLoading] = useState(false) // 角色对话框 const [roleDialogOpen, setRoleDialogOpen] = useState(false) const [roleDialogUser, setRoleDialogUser] = useState(null) const [allRoles, setAllRoles] = useState([]) // 加载用户列表 const loadUsers = useCallback(async () => { setLoading(true) try { const params: UserQueryParams = { page, page_size: pageSize, keyword: keyword || undefined, status: statusFilter !== 'all' ? statusFilter : undefined, } const response = await usersApi.getUsers(params) const data = response.data.data as PaginatedResponse setUsers(data.list || []) setTotal(data.total || 0) } catch (error) { console.error('Failed to load users:', error) setUsers([]) setTotal(0) } finally { setLoading(false) } }, [page, pageSize, keyword, statusFilter]) // 加载所有角色 const loadAllRoles = useCallback(async () => { try { const { rolesApi } = await import('@/api') const response = await rolesApi.getRoles() const roles = response.data.data || [] setAllRoles(roles.map(r => ({ id: r.name, // 使用 name 作为 id name: r.name, description: r.description, created_at: '', // 后端列表接口不返回这些字段 updated_at: '', }))) } catch (error) { console.error('Failed to load roles:', error) } }, []) // 初始加载 useEffect(() => { loadUsers() }, [loadUsers]) // 加载角色列表(仅一次) useEffect(() => { loadAllRoles() }, [loadAllRoles]) // 查看用户详情 const handleViewDetail = async (user: User) => { setDetailLoading(true) setDetailOpen(true) try { const response = await usersApi.getUserById(user.id) setSelectedUser(response.data.data) } catch (error) { console.error('Failed to load user detail:', error) setSelectedUser(null) } finally { setDetailLoading(false) } } // 打开角色分配对话框 const handleOpenRoleDialog = (user: User) => { setRoleDialogUser(user) setRoleDialogOpen(true) } // 分配角色 const handleAssignRole = async (userId: string, role: string) => { await usersApi.assignRole(userId, role) // 刷新用户列表 loadUsers() // 如果详情对话框打开,也刷新详情 if (selectedUser?.id === userId) { const response = await usersApi.getUserById(userId) setSelectedUser(response.data.data) } } // 移除角色 const handleRemoveRole = async (userId: string, role: string) => { await usersApi.removeRole(userId, role) // 刷新用户列表 loadUsers() // 如果详情对话框打开,也刷新详情 if (selectedUser?.id === userId) { const response = await usersApi.getUserById(userId) setSelectedUser(response.data.data) } } // 切换用户状态 const handleToggleStatus = async (user: User) => { const newStatus = user.status === 'active' ? 'banned' : 'active' try { await usersApi.updateUserStatus(user.id, newStatus) // 刷新用户列表 loadUsers() // 如果详情对话框打开,也刷新详情 if (selectedUser?.id === user.id) { const response = await usersApi.getUserById(user.id) setSelectedUser(response.data.data) } } catch (error) { console.error('Failed to update user status:', error) } } // 搜索 const handleSearch = () => { setPage(1) loadUsers() } // 刷新 const handleRefresh = () => { loadUsers() } // 分页变化 const handlePageChange = (newPage: number) => { setPage(newPage) } // 计算总页数 const totalPages = Math.ceil(total / pageSize) // 获取头像fallback文字 const getInitials = (name: string) => { return name.slice(0, 2).toUpperCase() } // 格式化日期 const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', }) } return (

用户管理

管理系统用户账户

{/* 筛选栏 */} 筛选条件 根据条件筛选用户
setKeyword(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} className="max-w-sm" />
{/* 用户表格 */} 用户列表 共 {total} 个用户 ID 头像 用户名 邮箱 状态 角色 注册时间 操作 {loading ? (

加载中...

) : users.length === 0 ? ( 暂无数据 ) : ( users.map((user) => ( handleViewDetail(user)} > {user.id} {getInitials(user.nickname || user.username)}

{user.nickname || user.username}

@{user.username}

{user.email} {statusText[user.status] || user.status}
{user.roles?.slice(0, 2).map((role: Role) => ( {role.name} ))} {user.roles && user.roles.length > 2 && ( +{user.roles.length - 2} )}
{formatDate(user.created_at)} e.stopPropagation()}>
{user.status === 'active' ? ( ) : user.status === 'banned' ? ( ) : null}
)) )}
{/* 分页 */} {totalPages > 1 && (
)}
{/* 用户详情对话框 */} 用户详情 查看用户详细信息 {detailLoading ? (
) : selectedUser ? ( { setDetailOpen(false) setRoleDialogUser({ id: selectedUser.id, username: selectedUser.username, nickname: selectedUser.nickname, email: selectedUser.email, avatar: selectedUser.avatar, status: selectedUser.status, roles: selectedUser.roles, created_at: selectedUser.created_at, updated_at: selectedUser.updated_at, }) setRoleDialogOpen(true) }} onToggleStatus={() => handleToggleStatus(selectedUser)} /> ) : (

加载失败

)}
{/* 角色分配对话框 */} {roleDialogUser && ( )}
) }