polish(admin): 抽出后台控件包并统一各页布局与交互

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 19:03:21 +08:00
parent cfc6c1403b
commit f73d2a89e8
23 changed files with 1720 additions and 1412 deletions

View File

@@ -9,6 +9,8 @@ import {
MessageSquareText,
FileText,
Users,
LayoutDashboard,
Coins,
} from "lucide-react";
import {
fetchOverview,
@@ -19,6 +21,7 @@ import { authCookieHeader } from "@/lib/cookies";
import { getMeCached } from "@/lib/serverData";
import { canModerateAny, isSuperOrOwner, isAdminOrAbove } from "@/lib/roles";
import Avatar from "@/components/Avatar";
import { AdminPageHeader, AdminStatCard } from "@/components/admin";
import { formatRelative } from "@/lib/format";
export const metadata: Metadata = {
@@ -53,7 +56,7 @@ function PanelHead({ title, extra }: { title: string; extra?: string }) {
);
}
/** 待办工作区:待审帖子 / 评论数量,整块进入审核页 */
/** 待办:待审帖子 / 评论,整块进入审核页 */
function PendingWorkPanel({ counts }: { counts: PendingCounts }) {
const total = counts.posts + counts.comments;
const idle = total === 0;
@@ -70,7 +73,7 @@ function PendingWorkPanel({ counts }: { counts: PendingCounts }) {
background: idle ? "var(--tint)" : "var(--ok-soft)",
color: idle ? "var(--ink-3)" : "var(--ok)",
}}
aria-hidden="true"
aria-hidden
>
<ClipboardCheck size={20} />
</span>
@@ -81,30 +84,26 @@ function PendingWorkPanel({ counts }: { counts: PendingCounts }) {
</p>
{idle ? (
<span
className="text-[11.5px] px-2 py-0.5 rounded-full"
style={{ background: "var(--tint)", color: "var(--ink-3)" }}
className="admin-status is-muted"
>
暂无待审
</span>
) : (
<span
className="text-[11.5px] px-2 py-0.5 rounded-full tabular-nums"
style={{ background: "var(--ok-soft)", color: "var(--ok)" }}
>
<span className="admin-status is-ok tabular-nums">
{fmt(total)} 项待处理
</span>
)}
</div>
<div className="mt-3 flex flex-wrap gap-3 sm:gap-5">
<span className="inline-flex items-center gap-1.5 text-[13px]" style={{ color: "var(--ink-2)" }}>
<FileText size={14} style={{ color: "var(--ink-3)" }} aria-hidden="true" />
<FileText size={14} style={{ color: "var(--ink-3)" }} aria-hidden />
待审帖子
<strong className="tabular-nums font-extrabold" style={{ color: "var(--ink)" }}>
{fmt(counts.posts)}
</strong>
</span>
<span className="inline-flex items-center gap-1.5 text-[13px]" style={{ color: "var(--ink-2)" }}>
<MessageSquareText size={14} style={{ color: "var(--ink-3)" }} aria-hidden="true" />
<MessageSquareText size={14} style={{ color: "var(--ink-3)" }} aria-hidden />
待审评论
<strong className="tabular-nums font-extrabold" style={{ color: "var(--ink)" }}>
{fmt(counts.comments)}
@@ -152,31 +151,35 @@ export default async function AdminDashboardPage() {
return (
<div>
<div className="mb-5">
<h1
className="text-[22px] sm:text-[26px] font-extrabold tracking-tight"
style={{ color: "var(--ink)" }}
>
{greeting(now.getHours())},{me.user?.nickname}
</h1>
<p className="meta mt-1.5 text-[12.5px]">{dateLabel} · 站点管理</p>
</div>
<AdminPageHeader
icon={LayoutDashboard}
title={`${greeting(now.getHours())},${me.user?.nickname ?? ""}`}
description={`${dateLabel} · 站点管理`}
/>
{canMod && pendingCounts && <PendingWorkPanel counts={pendingCounts} />}
{canEconomy && (
<Link
href="/admin/economy"
className="panel mb-4 px-4 sm:px-5 py-4 flex items-center justify-between gap-3 transition-colors hover:bg-[var(--panel-2)]"
>
<div>
<p className="font-medium text-[15px]" style={{ color: "var(--ink)" }}>
积分与类型帖看板
</p>
<p className="meta text-[12.5px] mt-0.5">余额合计、悬赏托管、类型分布与最近流水</p>
</div>
<ChevronRight size={18} style={{ color: "var(--ink-3)" }} />
</Link>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-4">
<Link href="/admin/economy" className="block hover-lift rounded-[var(--r)]">
<AdminStatCard
label="积分看板"
value="查看"
hint="余额合计、悬赏托管与流水"
icon={<Coins size={16} />}
color="var(--gold)"
soft="var(--gold-soft)"
/>
</Link>
{stats && (
<AdminStatCard
label="注册成员"
value={fmt(stats.users)}
hint="全站用户总量"
icon={<Users size={16} />}
/>
)}
</div>
)}
<div className="grid lg:grid-cols-2 gap-4 items-start">
@@ -201,16 +204,10 @@ export default async function AdminDashboardPage() {
size={34}
/>
<span className="min-w-0 flex-1 leading-tight">
<span
className="block text-[13.5px] truncate"
style={{ color: "var(--ink)" }}
>
<span className="block text-[13.5px] truncate" style={{ color: "var(--ink)" }}>
{u.nickname}
</span>
<span
className="block text-[11.5px] truncate"
style={{ color: "var(--ink-3)" }}
>
<span className="block text-[11.5px] truncate" style={{ color: "var(--ink-3)" }}>
@{u.username}
</span>
</span>
@@ -218,7 +215,7 @@ export default async function AdminDashboardPage() {
className="shrink-0 inline-flex items-center gap-1 text-[11.5px]"
style={{ color: "var(--ink-3)" }}
>
<UserPlus size={12} aria-hidden="true" />
<UserPlus size={12} aria-hidden />
{formatRelative(u.created_at)}加入
</span>
</Link>
@@ -233,9 +230,9 @@ export default async function AdminDashboardPage() {
color: "var(--accent)",
}}
>
<Users size={14} aria-hidden="true" />
<Users size={14} aria-hidden />
前往用户权限
<ChevronRight size={14} aria-hidden="true" />
<ChevronRight size={14} aria-hidden />
</Link>
)}
</div>
@@ -255,16 +252,13 @@ export default async function AdminDashboardPage() {
<span
className="w-8 h-8 rounded-lg flex items-center justify-center shrink-0"
style={{ background: "var(--panel-2)", color: "var(--accent)" }}
aria-hidden="true"
aria-hidden
>
<LayoutGrid size={14} />
</span>
<span className="min-w-0 flex-1">
<span className="flex items-center justify-between gap-2">
<span
className="text-[13.5px] truncate"
style={{ color: "var(--ink)" }}
>
<span className="text-[13.5px] truncate" style={{ color: "var(--ink)" }}>
{b.name}
</span>
<span
@@ -277,7 +271,7 @@ export default async function AdminDashboardPage() {
<span
className="block mt-1.5 h-1 rounded-full overflow-hidden"
style={{ background: "var(--tint)" }}
aria-hidden="true"
aria-hidden
>
<span
className="block h-full rounded-full"