"use client"; import { useState, useEffect, useCallback } from "react"; import { useAuth } from "@/hooks/use-auth"; import { listUsersAPI, createUserAPI, disableUserAPI, resetPasswordAPI, getDataStatsAPI, dataResetAPI, type UserItem, type DataStats, } from "@/lib/api"; export default function UsersPage() { const { user: currentUser } = useAuth(); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); // Create user dialog state const [showCreate, setShowCreate] = useState(false); const [newUsername, setNewUsername] = useState(""); const [newRole, setNewRole] = useState("user"); const [createLoading, setCreateLoading] = useState(false); const [createError, setCreateError] = useState(""); const [createdResult, setCreatedResult] = useState<{ username: string; password: string } | null>(null); // Reset password result const [resetResult, setResetResult] = useState<{ username: string; password: string } | null>(null); // Copy feedback const [copied, setCopied] = useState(false); // Data reset state const [dataStats, setDataStats] = useState(null); const [resetMode, setResetMode] = useState<"all" | "recommendations" | "date_range" | "low_score">("low_score"); const [beforeDate, setBeforeDate] = useState(""); const [resetLoading, setResetLoading] = useState(false); const [resetResultMsg, setResetResultMsg] = useState(null); const [confirmReset, setConfirmReset] = useState(false); function copyCredential(username: string, password: string) { const text = `用户名:${username}\n密码:${password}`; navigator.clipboard.writeText(text).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); }); } const fetchUsers = useCallback(async () => { try { const data = await listUsersAPI(); setUsers(data); } catch { setError("加载用户列表失败"); } finally { setLoading(false); } }, []); const fetchStats = useCallback(async () => { try { const stats = await getDataStatsAPI(); setDataStats(stats); } catch { // silently fail } }, []); useEffect(() => { if (currentUser?.role === "admin") { fetchUsers(); fetchStats(); } }, [currentUser, fetchUsers, fetchStats]); // Non-admin: show nothing (AuthGuard + route should prevent this) if (currentUser?.role !== "admin") { return (

需要管理员权限

); } async function handleCreate(e: React.FormEvent) { e.preventDefault(); setCreateError(""); if (!newUsername.trim()) { setCreateError("请输入用户名"); return; } setCreateLoading(true); try { const result = await createUserAPI(newUsername.trim(), newRole); setCreatedResult({ username: result.username, password: result.password }); setNewUsername(""); setNewRole("user"); fetchUsers(); } catch (err) { setCreateError(err instanceof Error ? err.message : "创建失败"); } finally { setCreateLoading(false); } } async function handleDisable(userId: number) { try { await disableUserAPI(userId); fetchUsers(); } catch (err) { alert(err instanceof Error ? err.message : "操作失败"); } } async function handleResetPassword(userId: number) { try { const result = await resetPasswordAPI(userId); setResetResult({ username: result.username, password: result.password }); fetchUsers(); } catch (err) { alert(err instanceof Error ? err.message : "操作失败"); } } async function handleDataReset() { setConfirmReset(false); setResetLoading(true); setResetResultMsg(null); try { const result = await dataResetAPI( resetMode, resetMode === "date_range" ? beforeDate : undefined, resetMode === "low_score" ? 60 : undefined, ); const parts = Object.entries(result.deleted) .filter(([, v]) => v > 0) .map(([k, v]) => `${k}: ${v}条`); setResetResultMsg(parts.length > 0 ? `已删除: ${parts.join(", ")}` : "没有需要删除的数据"); fetchStats(); } catch (err) { setResetResultMsg(err instanceof Error ? err.message : "重置失败"); } finally { setResetLoading(false); } } return (
{/* Header */}

用户管理

创建和管理系统用户

{/* Error */} {error && (

{error}

)} {/* Data Reset Section */} {dataStats && (

数据统计 & 重置

{/* Stats */}
推荐记录
{dataStats.recommendations}
跟踪数据
{dataStats.tracking}
低分记录
{dataStats.low_score_count}
板块热度
{dataStats.sector_heat}
市场温度
{dataStats.market_temperature}
日期范围
{dataStats.earliest_date || "-"} ~ {dataStats.latest_date || "-"}
{/* Reset mode selection */}
{[ { key: "low_score", label: "清理低分 (<60)", desc: "删除评分低于60的推荐" }, { key: "date_range", label: "按日期清除", desc: "删除指定日期之前的数据" }, { key: "recommendations", label: "清除推荐", desc: "删除推荐和跟踪,保留板块温度" }, { key: "all", label: "全部重置", desc: "清除所有业务数据" }, ].map(({ key, label }) => ( ))}
{/* Date range input */} {resetMode === "date_range" && (
setBeforeDate(e.target.value)} className="w-full sm:w-auto bg-surface-2 border border-border-default rounded-lg px-3 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-amber-500/30" />
)} {/* Reset result */} {resetResultMsg && (
{resetResultMsg}
)} {/* Confirm + Execute */} {confirmReset ? (

{resetMode === "all" ? "确认清除所有数据?此操作不可撤销!" : resetMode === "recommendations" ? "确认清除推荐和跟踪数据?" : resetMode === "date_range" ? `确认清除 ${beforeDate} 之前的数据?` : "确认删除评分<60的推荐?"}

) : ( )}
)} {/* User list */} {loading ? (
{[1, 2, 3].map((i) => (
))}
) : (
{users.map((u) => (
{/* Avatar */}
{u.username.charAt(0).toUpperCase()}
{u.username} {u.role} {!u.is_active && ( 已禁用 )}
{u.created_at && (

创建于 {new Date(u.created_at).toLocaleDateString("zh-CN")}

)}
{/* Actions */} {u.id !== currentUser!.id && (
{u.is_active ? ( ) : ( 已禁用 )}
)}
))} {users.length === 0 && (

暂无用户

)}
)} {/* Create User Dialog */} {showCreate && (
setShowCreate(false)} />
{createdResult ? (

用户创建成功

用户名 {createdResult.username}
密码 {createdResult.password}

请妥善保管密码,此密码仅显示一次

) : ( <>

创建新用户

setNewUsername(e.target.value)} className="w-full bg-surface-2 border border-border-default rounded-xl px-4 py-2.5 text-sm text-text-primary focus:outline-none focus:ring-1 focus:ring-amber-500/30 placeholder-text-muted/40" /> {createError &&

{createError}

}
)}
)} {/* Reset Password Result Dialog */} {resetResult && (
setResetResult(null)} />

密码已重置

用户名 {resetResult.username}
新密码 {resetResult.password}

请妥善保管新密码,此密码仅显示一次

)}
); }