import { useEffect, useState, useCallback } from 'react'; import { adminApi } from '../lib/api'; import type { AdminUser, AdminUserDetail, Team } from '../types'; import { showToast } from '../components/Toast'; import { ConfirmModal } from '../components/ConfirmModal'; import { Select } from '../components/Select'; import styles from './UsersPage.module.css'; export function UsersPage() { const [users, setUsers] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [teamFilter, setTeamFilter] = useState(''); const [teams, setTeams] = useState([]); const [loading, setLoading] = useState(true); const pageSize = 20; // Quota edit modal const [editUser, setEditUser] = useState(null); const [editDaily, setEditDaily] = useState(''); const [editMonthly, setEditMonthly] = useState(''); // User detail drawer const [detailUser, setDetailUser] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); // Confirm toggle const [confirmUser, setConfirmUser] = useState(null); // Create user modal const [createOpen, setCreateOpen] = useState(false); const [newUsername, setNewUsername] = useState(''); const [newEmail, setNewEmail] = useState(''); const [newPassword, setNewPassword] = useState(''); const [newDaily, setNewDaily] = useState('600'); const [newMonthly, setNewMonthly] = useState('6000'); const [newIsStaff, setNewIsStaff] = useState(false); const [createError, setCreateError] = useState(''); // Load teams for filter dropdown useEffect(() => { adminApi.getTeams().then(({ data }) => setTeams(data.results)).catch(() => {}); }, []); const fetchUsers = useCallback(async () => { setLoading(true); try { const { data } = await adminApi.getUsers({ page, page_size: pageSize, search, status: statusFilter, team_id: teamFilter ? Number(teamFilter) : undefined, }); setUsers(data.results); setTotal(data.total); } catch { showToast('加载用户列表失败'); } finally { setLoading(false); } }, [page, search, statusFilter, teamFilter]); useEffect(() => { fetchUsers(); }, [fetchUsers]); const handleSearch = () => { setPage(1); fetchUsers(); }; const handleToggleStatus = async (user: AdminUser) => { try { await adminApi.updateUserStatus(user.id, !user.is_active); showToast(user.is_active ? '已禁用用户' : '已启用用户'); fetchUsers(); } catch { showToast('操作失败'); } }; const openEditModal = (user: AdminUser) => { setEditUser(user); setEditDaily(String(user.daily_seconds_limit)); setEditMonthly(String(user.monthly_seconds_limit)); }; const handleSaveQuota = async () => { if (!editUser) return; try { await adminApi.updateUserQuota(editUser.id, Number(editDaily), Number(editMonthly)); showToast('配额已更新'); setEditUser(null); fetchUsers(); } catch { showToast('更新失败'); } }; const openDrawer = async (userId: number) => { try { const { data } = await adminApi.getUserDetail(userId); setDetailUser(data); setDrawerOpen(true); } catch { showToast('加载用户详情失败'); } }; const resetCreateForm = () => { setNewUsername(''); setNewEmail(''); setNewPassword(''); setNewDaily('600'); setNewMonthly('6000'); setNewIsStaff(false); setCreateError(''); }; const handleCreateUser = async () => { setCreateError(''); if (!newUsername.trim()) { setCreateError('请输入用户名'); return; } if (!newEmail.trim()) { setCreateError('请输入邮箱'); return; } if (newPassword.length < 6) { setCreateError('密码至少6位'); return; } try { await adminApi.createUser({ username: newUsername.trim(), email: newEmail.trim(), password: newPassword, daily_seconds_limit: Number(newDaily), monthly_seconds_limit: Number(newMonthly), is_staff: newIsStaff, }); showToast('用户创建成功'); setCreateOpen(false); resetCreateForm(); fetchUsers(); } catch (err: any) { const msg = err.response?.data?.error || err.response?.data?.username?.[0] || '创建失败'; setCreateError(msg); } }; const totalPages = Math.ceil(total / pageSize); return (

用户管理

setSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} /> { setTeamFilter(v); setPage(1); }} placeholder="全部团队" options={[{ label: '全部团队', value: '' }, ...teams.map((t) => ({ label: t.name, value: String(t.id) }))]} />
{loading ? ( Array.from({ length: 5 }).map((_, i) => ( {Array.from({ length: 10 }).map((_, j) => ( ))} )) ) : users.length === 0 ? ( ) : ( users.map((u) => ( )) )}
用户名 团队 邮箱 注册时间 状态 日限额(秒) 月限额(秒) 今日消费(秒) 本月消费(秒) 操作
暂无数据
{u.team_name || '-'} {u.email} {new Date(u.date_joined).toLocaleDateString('zh-CN')} {u.is_active ? '启用' : '禁用'} {u.daily_seconds_limit === -1 ? '不限' : u.daily_seconds_limit.toLocaleString() + 's'} {u.monthly_seconds_limit === -1 ? '不限' : u.monthly_seconds_limit.toLocaleString() + 's'} {u.seconds_today.toLocaleString()}s {u.seconds_this_month.toLocaleString()}s
{totalPages > 1 && (
共 {total} 条
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { let p: number; if (totalPages <= 5) p = i + 1; else if (page <= 3) p = i + 1; else if (page >= totalPages - 2) p = totalPages - 4 + i; else p = page - 2 + i; return ( ); })}
)} { if (confirmUser) { handleToggleStatus(confirmUser); setConfirmUser(null); } }} onCancel={() => setConfirmUser(null)} /> {/* Quota Edit Modal */} {editUser && (
{ if (e.target === e.currentTarget) setEditUser(null); }}>

编辑配额 — {editUser.username}

setEditDaily(e.target.value)} />
setEditMonthly(e.target.value)} />
)} {/* Create User Modal */} {createOpen && (
{ if (e.target === e.currentTarget) setCreateOpen(false); }}>

新增用户

setNewUsername(e.target.value)} placeholder="请输入用户名" />
setNewEmail(e.target.value)} placeholder="请输入邮箱" />
setNewPassword(e.target.value)} placeholder="至少6位" />
setNewDaily(e.target.value)} />
setNewMonthly(e.target.value)} />
{createError &&
{createError}
}
)} {/* User Detail Drawer */} {drawerOpen && detailUser && (
setDrawerOpen(false)}>
e.stopPropagation()}>

用户详情

用户名 {detailUser.username}
邮箱 {detailUser.email}
状态 {detailUser.is_active ? '启用' : '禁用'}
注册时间 {new Date(detailUser.date_joined).toLocaleString('zh-CN')}
日限额/今日消费 {detailUser.seconds_today.toLocaleString()}s / {detailUser.daily_seconds_limit === -1 ? '不限' : detailUser.daily_seconds_limit.toLocaleString() + 's'}
月限额/本月消费 {detailUser.seconds_this_month.toLocaleString()}s / {detailUser.monthly_seconds_limit === -1 ? '不限' : detailUser.monthly_seconds_limit.toLocaleString() + 's'}
累计消费 {detailUser.seconds_total.toLocaleString()}s

近期消费记录

{detailUser.recent_records.length === 0 ? (
暂无记录
) : ( detailUser.recent_records.map((r) => (
{new Date(r.created_at).toLocaleString('zh-CN')}
{r.seconds_consumed.toLocaleString()}s {r.mode === 'universal' ? '全能参考' : '首尾帧'} { { queued: '排队中', processing: '生成中', completed: '已完成', failed: '失败' }[r.status] }
{r.prompt &&
{r.prompt.slice(0, 60)}{r.prompt.length > 60 ? '...' : ''}
}
)) )}
)}
); }