"use client"; import { useMemo, useState } from "react"; type ManagedPost = { slug: string; title: string; createdAt: string; isPinned?: boolean; }; type ManagedUser = { id: string; username: string; displayName: string; role: "user" | "sponsor" | "admin"; dailyPostLimit: number; postCount: number; todayPostCount: number; posts: ManagedPost[]; }; const ROLE_OPTIONS: Array<{ value: ManagedUser["role"]; label: string }> = [ { value: "user", label: "普通" }, { value: "sponsor", label: "赞助" }, { value: "admin", label: "管理员" } ]; export function AdminUserManager({ initialUsers, currentUserId }: { initialUsers: ManagedUser[]; currentUserId: string; }) { const [users, setUsers] = useState(initialUsers); const [query, setQuery] = useState(""); const [savingId, setSavingId] = useState(null); const visibleUsers = useMemo(() => { const keyword = query.trim().toLowerCase(); if (!keyword) return users; return users.filter( (user) => user.username.toLowerCase().includes(keyword) || user.displayName.toLowerCase().includes(keyword) ); }, [query, users]); async function handleDeletePost(slug: string) { if (!window.confirm("确定要删除这条内容吗?")) return; const res = await fetch(`/api/posts/${slug}`, { method: "DELETE" }); const data = await res.json().catch(() => ({})); if (!res.ok) { alert(data.error || "删除失败"); return; } setUsers((prev) => prev.map((user) => { const target = user.posts.find((post) => post.slug === slug); if (!target) return user; return { ...user, postCount: Math.max(0, user.postCount - 1), todayPostCount: toShanghaiDateKey(target.createdAt) === toShanghaiDateKey(new Date().toISOString()) ? Math.max(0, user.todayPostCount - 1) : user.todayPostCount, posts: user.posts.filter((post) => post.slug !== slug) }; }) ); } async function handleTogglePin(userId: string, slug: string, isPinned: boolean) { const res = await fetch(`/api/posts/${slug}/pin`, { method: isPinned ? "DELETE" : "POST" }); const data = await res.json().catch(() => ({})); if (!res.ok) { alert(data.error || "置顶操作失败"); return; } setUsers((prev) => prev.map((user) => user.id !== userId ? user : { ...user, posts: [...user.posts] .map((post) => post.slug === slug ? { ...post, isPinned: Boolean(data.isPinned) } : post ) .sort((a, b) => { const pinnedDiff = Number(Boolean(b.isPinned)) - Number(Boolean(a.isPinned)); if (pinnedDiff !== 0) return pinnedDiff; return b.createdAt.localeCompare(a.createdAt); }) } ) ); } async function handleSaveUser( userId: string, payload: { dailyPostLimit: number; role: ManagedUser["role"] } ) { setSavingId(userId); try { const res = await fetch(`/api/admin/users/${userId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); const data = await res.json().catch(() => ({})); if (!res.ok) { alert(data.error || "保存失败"); return; } setUsers((prev) => prev.map((user) => user.id === userId ? { ...user, dailyPostLimit: data.dailyPostLimit ?? payload.dailyPostLimit, role: data.role ?? payload.role } : user ) ); } finally { setSavingId(null); } } async function handleDeleteUser(userId: string) { if (!window.confirm("确定要删除该用户及其全部内容吗?")) return; const res = await fetch(`/api/admin/users/${userId}`, { method: "DELETE" }); const data = await res.json().catch(() => ({})); if (!res.ok) { alert(data.error || "删除失败"); return; } setUsers((prev) => prev.filter((user) => user.id !== userId)); } return (

用户管理

可按用户名搜索,设置用户等级与每日发布额度,并删除用户或其指定内容。

setQuery(e.target.value)} placeholder="搜索用户名" className="w-44 rounded-full border border-slate-200 bg-white px-3 py-2 text-sm shadow-inner focus:border-brand-500 focus:outline-none" />
{visibleUsers.length === 0 ? (

没有匹配的用户。

) : ( visibleUsers.map((user) => ( )) )}
); } function toShanghaiDateKey(input: string) { const date = new Date(input); const shifted = new Date(date.getTime() + 8 * 60 * 60 * 1000); return shifted.toISOString().slice(0, 10); } function AdminUserCard({ user, currentUserId, saving, onDeletePost, onTogglePin, onDeleteUser, onSaveUser }: { user: ManagedUser; currentUserId: string; saving: boolean; onDeletePost: (slug: string) => Promise; onTogglePin: (userId: string, slug: string, isPinned: boolean) => Promise; onDeleteUser: (userId: string) => Promise; onSaveUser: ( userId: string, payload: { dailyPostLimit: number; role: ManagedUser["role"] } ) => Promise; }) { const [limit, setLimit] = useState(user.dailyPostLimit); const [role, setRole] = useState(user.role); return (

{user.displayName} (@{user.username})

角色:{ROLE_OPTIONS.find((item) => item.value === user.role)?.label || "普通"} | 总发布: {user.postCount} | 今日发布:{user.todayPostCount}

setLimit(Number(e.target.value))} className="w-24 rounded-full border border-slate-200 bg-white px-3 py-2 text-sm shadow-inner focus:border-brand-500 focus:outline-none" /> {user.id !== currentUserId ? ( ) : null}
{user.posts.length === 0 ? (

该用户暂无内容。

) : ( user.posts.map((post) => (
{post.isPinned ? ( 置顶 ) : null} {post.title}

{new Date(post.createdAt).toLocaleString("zh-CN", { hour12: false, timeZone: "Asia/Shanghai" })}

)) )}
); }