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,7 @@ import { isAdminOrAbove } from "@/lib/roles";
import { postTypeLabel } from "@/lib/postMeta";
import { pointReasonLabel } from "@/lib/points";
import { formatRelative } from "@/lib/format";
import { AdminPageHeader, AdminStatCard } from "@/components/admin";
import Forbidden from "../Forbidden";
export const metadata: Metadata = {
@@ -39,67 +40,50 @@ export default async function AdminEconomyPage() {
);
}
const cards = [
{
label: "用户余额合计",
value: fmt(stats.total_balance),
icon: <Coins size={18} />,
hint: "全站可用积分之和",
},
{
label: "今日签到",
value: fmt(stats.checkin_today),
icon: <Flame size={18} />,
hint: "今日已签到人数",
},
{
label: "托管中悬赏",
value: `${fmt(stats.open_bounties)} / ${fmt(stats.escrowed_points)}`,
icon: <Gift size={18} />,
hint: "未结算帖数 / 托管积分",
},
{
label: "流水条数",
value: fmt(stats.ledger_count),
icon: <Layers size={18} />,
hint: "历史入账出账记录",
},
];
const typeEntries = Object.entries(stats.posts_by_type || {}).sort(
(a, b) => b[1] - a[1]
);
return (
<div>
<div className="mb-6">
<h1 className="text-[22px] font-extrabold" style={{ color: "var(--ink)" }}>
积分与类型帖看板
</h1>
<p className="meta text-[13px] mt-1">
观察全站积分流通、悬赏托管与帖子类型分布
</p>
<AdminPageHeader
icon={Coins}
title="积分与类型帖看板"
description="观察全站积分流通、悬赏托管与帖子类型分布"
/>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-3 mb-5">
<AdminStatCard
label="用户余额合计"
value={fmt(stats.total_balance)}
hint="全站可用积分之和"
icon={<Coins size={16} />}
/>
<AdminStatCard
label="今日签到"
value={fmt(stats.checkin_today)}
hint="今日已签到人数"
icon={<Flame size={16} />}
color="var(--clay)"
soft="var(--clay-soft)"
/>
<AdminStatCard
label="托管中悬赏"
value={`${fmt(stats.open_bounties)} / ${fmt(stats.escrowed_points)}`}
hint="未结算帖数 / 托管积分"
icon={<Gift size={16} />}
color="var(--gold)"
soft="var(--gold-soft)"
/>
<AdminStatCard
label="流水条数"
value={fmt(stats.ledger_count)}
hint="历史入账出账记录"
icon={<Layers size={16} />}
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4 mb-6">
{cards.map((c) => (
<div key={c.label} className="panel p-5">
<div className="flex items-center gap-2 meta text-[12.5px]">
<span style={{ color: "var(--accent)" }}>{c.icon}</span>
{c.label}
</div>
<p
className="mt-3 text-[22px] font-extrabold tabular-nums leading-none"
style={{ color: "var(--ink)" }}
>
{c.value}
</p>
<p className="meta text-[12px] mt-2">{c.hint}</p>
</div>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<section className="panel overflow-hidden">
<div
className="px-5 py-3.5 font-medium text-[15px]"
@@ -111,11 +95,11 @@ export default async function AdminEconomyPage() {
<p className="meta text-center py-10">暂无数据</p>
) : (
<ul>
{typeEntries.map(([t, n]) => (
{typeEntries.map(([t, n], idx) => (
<li
key={t}
className="px-5 py-3 flex items-center justify-between"
style={{ borderTop: "1px solid var(--line)" }}
style={idx === 0 ? undefined : { borderTop: "1px solid var(--line)" }}
>
<span className="text-[14px]" style={{ color: "var(--ink)" }}>
{postTypeLabel(t)}
@@ -138,11 +122,11 @@ export default async function AdminEconomyPage() {
<p className="meta text-center py-10">暂无流水</p>
) : (
<ul>
{stats.recent_ledger.map((it) => (
{stats.recent_ledger.map((it, idx) => (
<li
key={it.id}
className="px-5 py-3 flex items-start justify-between gap-3"
style={{ borderTop: "1px solid var(--line)" }}
style={idx === 0 ? undefined : { borderTop: "1px solid var(--line)" }}
>
<div className="min-w-0">
<p className="text-[13.5px] truncate" style={{ color: "var(--ink)" }}>