feat(admin): 用户主页增加封禁与改角色快捷菜单

超管/站长可在他人主页就地管理账号;待审评论支持拒绝;角色弹窗前后台共用。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 06:29:06 +08:00
parent 2dd9cb82b3
commit b6e9966ba8
7 changed files with 709 additions and 346 deletions

View File

@@ -15,6 +15,7 @@ import Avatar from "@/components/Avatar";
import RoleBadge from "@/components/RoleBadge";
import { roleMeta } from "@/lib/roles";
import StartDMButton from "./StartDMButton";
import StaffUserMenu from "@/components/StaffUserMenu";
interface UserPageProps {
params: Promise<{ id: string }>;
@@ -50,9 +51,13 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
rawTab === "settings" ? "settings" :
rawTab === "security" ? "security" : "posts";
const cookie = authCookieHeader(await cookies());
const me = await getMeCached(cookie || undefined);
let data: UserProfile;
try {
data = await fetchUserProfile(id, page);
// 带 cookie:超管/站长才能拿到 banned 字段
data = await fetchUserProfile(id, page, cookie || undefined);
} catch {
return (
<div className="max-w-md mx-auto text-center py-24">
@@ -68,8 +73,6 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
}
// 判断是否本人(决定能否看到 settings / security tab)
const cookie = authCookieHeader(await cookies());
const me = await getMeCached(cookie || undefined);
const isOwner = me.user?.id === data.user.id;
const currentUser = me.user;
const effectiveTab: UserTab =
@@ -116,8 +119,8 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
</nav>
{/* ═══ Hero:石墨面板 + 淡靛几何装饰 + 身份信息 + 统计条 ═══ */}
<section className="panel relative mb-6 overflow-hidden">
{/* 装饰层:裁在面板圆角内 */}
<section className="panel relative mb-6">
{/* 装饰层单独裁圆角,避免 overflow-hidden 裁掉管理下拉菜单 */}
<div aria-hidden className="pointer-events-none absolute inset-0 overflow-hidden rounded-[inherit]">
<span
className="absolute -top-28 -right-24 w-[440px] h-[440px] rounded-full"
@@ -151,7 +154,17 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
</Link>
</div>
) : currentUser ? (
<div className="absolute top-3 right-3 sm:top-4 sm:right-4 z-20">
<div className="absolute top-3 right-3 sm:top-4 sm:right-4 z-20 flex items-center gap-2">
<StaffUserMenu
target={{
id: u.id,
username: u.username,
nickname: u.nickname,
role: u.role,
board_ids: u.board_ids,
banned: u.banned,
}}
/>
<StartDMButton userId={data.user.id} />
</div>
) : null}
@@ -186,6 +199,14 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
{u.role !== "user" && (
<RoleBadge role={u.role} boardIds={u.board_ids} />
)}
{u.banned === true && (
<span
className="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-semibold"
style={{ background: "var(--danger-soft)", color: "var(--danger)" }}
>
已封禁
</span>
)}
</div>
<p className="meta mt-1 flex items-center gap-1.5 text-[12.5px] flex-wrap">
<span>@{u.username}</span>