'use client'; import { useEffect, useState, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { useAuth } from '@/components/admin/auth-context'; import { adminApi } from '@/lib/admin-api'; import { toast } from '@/components/ui/sonner'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Plus, Edit, Trash2, Search, UserCog, Shield, Mail, Calendar } from 'lucide-react'; interface UserData { id: string; username: string; nickname: string; email: string; phone: string; status: number; role: string; roles: Array<{ code: string; name: string }>; createdAt: string; updatedAt: string; } interface RoleData { code: string; name: string; builtin: boolean; } const ROLE_LABELS: Record = { super_admin: '超级管理员', content_admin: '内容管理员', content_editor: '内容编辑', reviewer: '审核员', readonly: '只读用户', }; const ROLE_COLORS: Record = { super_admin: 'bg-error-bg text-error-text', content_admin: 'bg-info-bg text-info-text', content_editor: 'bg-success-bg text-success-text', reviewer: 'bg-warning-bg text-warning-text', readonly: 'bg-bg-hover text-text-secondary', }; export default function UsersPage() { const { user, loading: authLoading } = useAuth(); const router = useRouter(); const [users, setUsers] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [roles, setRoles] = useState([]); const pageSize = 20; // Create/Edit dialog state const [dialogOpen, setDialogOpen] = useState(false); const [editingUser, setEditingUser] = useState(null); const [formData, setFormData] = useState({ username: '', password: '', nickname: '', email: '', phone: '', roleCodes: [] as string[], }); const [saving, setSaving] = useState(false); // Delete dialog state const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [pendingDeleteId, setPendingDeleteId] = useState(null); const fetchUsers = useCallback(async () => { setLoading(true); setError(''); try { const query = new URLSearchParams(); query.set('page', String(page)); query.set('pageSize', String(pageSize)); if (search) query.set('search', search); const data = await adminApi.request<{ users: UserData[]; total: number; page: number; pageSize: number; totalPages: number; }>(`/api/admin/users?${query.toString()}`); setUsers(data.users || []); setTotal(data.total || 0); } catch (err) { setError(err instanceof Error ? err.message : '加载失败'); } finally { setLoading(false); } }, [page, search]); const fetchRoles = useCallback(async () => { try { const data = await adminApi.getRoles(); setRoles(data.roles || []); } catch { // Roles fetch is secondary } }, []); useEffect(() => { if (!authLoading && !user) { router.push('/admin/login'); return; } if (user) { fetchUsers(); fetchRoles(); } }, [user, authLoading, router, fetchUsers, fetchRoles]); const openCreateDialog = () => { setEditingUser(null); setFormData({ username: '', password: '', nickname: '', email: '', phone: '', roleCodes: [] }); setDialogOpen(true); }; const openEditDialog = (u: UserData) => { setEditingUser(u); setFormData({ username: u.username, password: '', nickname: u.nickname, email: u.email, phone: u.phone, roleCodes: u.roles.map((r) => r.code), }); setDialogOpen(true); }; const handleSave = async () => { if (editingUser) { // Update if (!formData.nickname && !formData.email) { toast.error('请填写昵称或邮箱'); return; } setSaving(true); try { const body: Record = { nickname: formData.nickname, email: formData.email, phone: formData.phone, roleCodes: formData.roleCodes, }; if (formData.password) body.password = formData.password; await adminApi.request(`/api/admin/users?id=${editingUser.id}`, { method: 'PUT', body: JSON.stringify(body), }); toast.success('用户更新成功'); setDialogOpen(false); fetchUsers(); } catch (err) { toast.error(err instanceof Error ? err.message : '更新失败'); } finally { setSaving(false); } } else { // Create if (!formData.username || !formData.password) { toast.error('用户名和密码必填'); return; } if (formData.username.length < 3) { toast.error('用户名长度不能少于 3 位'); return; } if (formData.password.length < 6) { toast.error('密码长度不能少于 6 位'); return; } setSaving(true); try { await adminApi.request('/api/admin/users', { method: 'POST', body: JSON.stringify(formData), }); toast.success('用户创建成功'); setDialogOpen(false); fetchUsers(); } catch (err) { toast.error(err instanceof Error ? err.message : '创建失败'); } finally { setSaving(false); } } }; const handleDelete = async () => { if (!pendingDeleteId) return; try { await adminApi.request(`/api/admin/users?id=${pendingDeleteId}`, { method: 'DELETE' }); toast.success('用户已删除'); setDeleteDialogOpen(false); setPendingDeleteId(null); fetchUsers(); } catch (err) { toast.error(err instanceof Error ? err.message : '删除失败'); } }; const toggleRole = (code: string) => { setFormData((prev) => ({ ...prev, roleCodes: prev.roleCodes.includes(code) ? prev.roleCodes.filter((r) => r !== code) : [...prev.roleCodes, code], })); }; const totalPages = Math.ceil(total / pageSize); if (authLoading || (!user && typeof window !== 'undefined')) { return (
); } return (

用户管理

管理系统用户与角色分配

{/* Search */}
{ setSearch(e.target.value); setPage(1); }} className="w-full pl-10 pr-4 py-2 border border-border-primary rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-primary" />
{error && (
{error}
)} {/* Users table */}
{loading ? ( ) : users.length === 0 ? ( ) : ( users.map((u) => ( )) )}
用户名 昵称 邮箱 角色 状态 创建时间 操作
加载中...
暂无用户数据
{u.username}
{u.nickname || '-'} {u.email ? ( {u.email} ) : ( - )}
{u.roles.map((r) => ( {r.name || ROLE_LABELS[r.code] || r.code} ))} {u.roles.length === 0 && ( 无角色 )}
{u.status === 1 ? '启用' : '禁用'}
{new Date(u.createdAt).toLocaleDateString('zh-CN')}
{/* Pagination */} {totalPages > 1 && (
共 {total} 条,第 {page}/{totalPages} 页
)}
{/* Create/Edit Dialog */} {dialogOpen && (
setDialogOpen(false)} />

{editingUser ? '编辑用户' : '创建用户'}

setFormData((p) => ({ ...p, username: e.target.value }))} disabled={!!editingUser} className="w-full px-3 py-2 border border-border-primary rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-primary disabled:bg-bg-hover disabled:text-text-muted" placeholder="3-32 位字母/数字/下划线" />
setFormData((p) => ({ ...p, password: e.target.value }))} className="w-full px-3 py-2 border border-border-primary rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-primary" placeholder={editingUser ? '留空保持原密码' : '至少 6 位'} />
setFormData((p) => ({ ...p, nickname: e.target.value }))} className="w-full px-3 py-2 border border-border-primary rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-primary" />
setFormData((p) => ({ ...p, email: e.target.value }))} className="w-full px-3 py-2 border border-border-primary rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-primary" />
setFormData((p) => ({ ...p, phone: e.target.value }))} className="w-full px-3 py-2 border border-border-primary rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-border-primary" />
{roles.map((r) => ( ))} {roles.length === 0 && ( 加载角色中... )}
)} {/* Delete confirmation */} 确认删除 删除后该用户将无法登录系统,此操作不可撤销。 取消 确认删除
); }