feat: Minecraft theme overhaul, fix points bug, add history

这个提交包含在:
X
2026-02-15 09:41:54 -08:00
父节点 37266bb846
当前提交 ef6d71ef54
修改 28 个文件,包含 1821 行新增1053 行删除

查看文件

@@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { apiFetch } from "@/lib/api";
import { useI18nText } from "@/lib/i18n";
import { useUiPreferences } from "@/components/ui-preference-provider";
type Row = {
user_id: number;
@@ -14,6 +15,8 @@ type Row = {
export default function LeaderboardPage() {
const { tx } = useI18nText();
const { theme } = useUiPreferences();
const isMc = theme === "minecraft";
const [items, setItems] = useState<Row[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
@@ -34,22 +37,52 @@ export default function LeaderboardPage() {
void load();
}, []);
const getRankColor = (index: number) => {
if (!isMc) return "";
switch (index) {
case 0: return "text-[color:var(--mc-gold)] drop-shadow-sm"; // Gold
case 1: return "text-zinc-300"; // Iron
case 2: return "text-orange-700"; // Copper
default: return "text-zinc-400";
}
};
const getRankIcon = (index: number) => {
if (!isMc) return `#${index + 1}`;
switch (index) {
case 0: return "🏆";
case 1: return "🥈";
case 2: return "🥉";
default: return `#${index + 1}`;
}
};
return (
<main className="mx-auto max-w-4xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl">
{tx("全站排行榜", "Global Leaderboard")}
<main className="mx-auto max-w-4xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8 font-mono">
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
{isMc ? (
<span className="flex items-center gap-2">
<span>🏰</span>
{tx("名人堂", "Hall of Fame")}
</span>
) : (
tx("全站排行榜", "Global Leaderboard")
)}
</h1>
{loading && <p className="mt-3 text-sm text-zinc-500">{tx("加载中...", "Loading...")}</p>}
{loading && <p className="mt-3 text-sm text-zinc-500">{tx("正在读取卷轴...", "Reading scrolls...")}</p>}
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
<div className="mt-4 rounded-xl border bg-white">
<div className={`mt-4 rounded-xl border ${isMc ? "border-[3px] border-black bg-[color:var(--mc-deep-slate)] shadow-[4px_4px_0_rgba(0,0,0,0.5)] text-white" : "bg-white border-zinc-200"}`}>
<div className="divide-y md:hidden">
{items.map((row, i) => (
<article key={row.user_id} className="space-y-1 p-3 text-sm">
<p className="font-medium">
#{i + 1} · {row.username}
</p>
<p className="text-xs text-zinc-600">Rating: {row.rating}</p>
<article key={row.user_id} className={`space-y-1 p-3 text-sm ${isMc ? "border-zinc-700" : ""}`}>
<div className="flex items-center justify-between">
<p className={`font-medium ${getRankColor(i)}`}>
<span className="mr-2 text-lg">{getRankIcon(i)}</span>
{row.username}
</p>
<span className="text-[color:var(--mc-emerald)] font-bold">{row.rating}</span>
</div>
<p className="text-xs text-zinc-500">
{tx("注册时间:", "Registered: ")}
{new Date(row.created_at * 1000).toLocaleString()}
@@ -58,28 +91,28 @@ export default function LeaderboardPage() {
))}
{!loading && items.length === 0 && (
<p className="px-3 py-5 text-center text-sm text-zinc-500">
{tx("暂无排行数据", "No ranking data yet")}
{tx("暂无数据", "No legends yet")}
</p>
)}
</div>
<div className="hidden overflow-x-auto md:block">
<table className="min-w-full text-sm">
<thead className="bg-zinc-100 text-left">
<thead className={`${isMc ? "bg-black/30 text-zinc-300" : "bg-zinc-100 text-left"}`}>
<tr>
<th className="px-3 py-2">{tx("排名", "Rank")}</th>
<th className="px-3 py-2">{tx("用户", "User")}</th>
<th className="px-3 py-2">Rating</th>
<th className="px-3 py-2">{tx("注册时间", "Registered At")}</th>
<th className="px-3 py-2 text-left">{tx("排名", "Rank")}</th>
<th className="px-3 py-2 text-left">{tx("用户", "User")}</th>
<th className="px-3 py-2 text-left">Rating</th>
<th className="px-3 py-2 text-left">{tx("注册时间", "Registered At")}</th>
</tr>
</thead>
<tbody>
<tbody className={isMc ? "divide-y divide-zinc-700" : ""}>
{items.map((row, i) => (
<tr key={row.user_id} className="border-t">
<td className="px-3 py-2">{i + 1}</td>
<td className="px-3 py-2">{row.username}</td>
<td className="px-3 py-2">{row.rating}</td>
<td className="px-3 py-2">
<tr key={row.user_id} className={isMc ? "hover:bg-white/5 transition-colors" : "border-t"}>
<td className={`px-3 py-2 font-bold ${getRankColor(i)}`}>{getRankIcon(i)}</td>
<td className={`px-3 py-2 font-medium ${getRankColor(i)}`}>{row.username}</td>
<td className="px-3 py-2 text-[color:var(--mc-emerald)]">{row.rating}</td>
<td className="px-3 py-2 text-zinc-500">
{new Date(row.created_at * 1000).toLocaleString()}
</td>
</tr>
@@ -87,7 +120,7 @@ export default function LeaderboardPage() {
{!loading && items.length === 0 && (
<tr>
<td className="px-3 py-5 text-center text-zinc-500" colSpan={4}>
{tx("暂无排行数据", "No ranking data yet")}
{tx("暂无数据", "No legends yet")}
</td>
</tr>
)}