feat: Minecraft theme overhaul, fix points bug, add history
这个提交包含在:
@@ -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>
|
||||
)}
|
||||
|
||||
在新工单中引用
屏蔽一个用户