Files
jiang13-bbs/frontend/app/admin/economy/page.tsx
freefire 5507552ca5 polish(frontend): 侧栏与徽章可读性,智能悬停提示
降权全站字重;优化右栏公告/数据块;类型状态徽章用固定浅色区分;TipHost 仅在文案不一致、非文字或省略时显示。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 03:54:14 +08:00

177 lines
5.7 KiB
TypeScript

import type { Metadata } from "next";
import Link from "next/link";
import { cookies } from "next/headers";
import { Coins, Flame, Gift, Layers } from "lucide-react";
import { fetchAdminEconomyStats } from "@/lib/api";
import { authCookieHeader } from "@/lib/cookies";
import { getMeCached } from "@/lib/serverData";
import { isAdminOrAbove } from "@/lib/roles";
import { postTypeLabel } from "@/lib/postMeta";
import { pointReasonLabel } from "@/lib/points";
import { formatRelative } from "@/lib/format";
import Forbidden from "../Forbidden";
export const metadata: Metadata = {
title: "积分看板",
};
const fmt = (n: number) => n.toLocaleString("zh-CN");
export default async function AdminEconomyPage() {
const cookie = authCookieHeader(await cookies());
const me = await getMeCached(cookie || undefined);
if (!me.user || !isAdminOrAbove(me.user.role)) {
return <Forbidden text="积分看板仅管理员及以上可查看。" />;
}
let stats: Awaited<ReturnType<typeof fetchAdminEconomyStats>> | null = null;
try {
stats = await fetchAdminEconomyStats(cookie);
} catch {
stats = null;
}
if (!stats) {
return (
<div className="panel p-8 text-center">
<p className="meta">暂时无法加载积分数据</p>
</div>
);
}
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>
</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">
<section className="panel overflow-hidden">
<div
className="px-5 py-3.5 font-medium text-[15px]"
style={{ borderBottom: "1px solid var(--line)", color: "var(--ink)" }}
>
帖子类型分布
</div>
{typeEntries.length === 0 ? (
<p className="meta text-center py-10">暂无数据</p>
) : (
<ul>
{typeEntries.map(([t, n]) => (
<li
key={t}
className="px-5 py-3 flex items-center justify-between"
style={{ borderTop: "1px solid var(--line)" }}
>
<span className="text-[14px]" style={{ color: "var(--ink)" }}>
{postTypeLabel(t)}
</span>
<span className="tabular-nums meta">{fmt(n)}</span>
</li>
))}
</ul>
)}
</section>
<section className="panel overflow-hidden">
<div
className="px-5 py-3.5 font-medium text-[15px]"
style={{ borderBottom: "1px solid var(--line)", color: "var(--ink)" }}
>
最近流水
</div>
{(stats.recent_ledger || []).length === 0 ? (
<p className="meta text-center py-10">暂无流水</p>
) : (
<ul>
{stats.recent_ledger.map((it) => (
<li
key={it.id}
className="px-5 py-3 flex items-start justify-between gap-3"
style={{ borderTop: "1px solid var(--line)" }}
>
<div className="min-w-0">
<p className="text-[13.5px] truncate" style={{ color: "var(--ink)" }}>
{pointReasonLabel(it.reason)}
{it.note ? ` · ${it.note}` : ""}
</p>
<p className="meta text-[12px] mt-0.5">{formatRelative(it.created_at)}</p>
</div>
<span
className="tabular-nums font-medium shrink-0"
style={{ color: it.delta >= 0 ? "var(--accent)" : "var(--danger)" }}
>
{it.delta >= 0 ? `+${it.delta}` : it.delta}
</span>
</li>
))}
</ul>
)}
</section>
</div>
<p className="meta text-[12.5px] mt-5">
用户可在{" "}
<Link href="/points" className="hover:text-[var(--accent)]">
/points
</Link>{" "}
查看个人流水。
</p>
</div>
);
}