feat: 新增排行榜模块

- 新增 leaderboard 服务与 handler、前端排行榜页面
- 路由与首页入口适配
This commit is contained in:
2026-09-25 18:28:12 +08:00
parent 4ae57d9392
commit cab8230803
9 changed files with 1112 additions and 2 deletions

View File

@@ -759,6 +759,79 @@ export async function fetchBoardSidebar(
return res.json();
}
/** 榜单周期:本周 | 本月 | 总榜 */
export type LeaderboardPeriod = "week" | "month" | "all";
/** 榜单维度:全部(综合)| 发帖最多 | 回复最多 | 获赞最多 | 最佳答案 */
export type LeaderboardMetric = "all" | "post" | "comment" | "like" | "answer";
/** 排行榜单条(领奖台/列表通用;level 由后端按累计积分计算) */
export interface LeaderboardEntry {
rank: number;
id: number;
username: string;
nickname: string;
avatar: string;
total_points: number;
points: number;
level: number;
post_count: number;
comment_count: number;
like_count: number;
answer_count: number;
score: number;
}
/** /api/leaderboard 响应 */
export interface LeaderboardResponse {
period: LeaderboardPeriod;
metric: LeaderboardMetric;
entries: LeaderboardEntry[];
}
/** 我的排名(rank=0 未上榜;change 正=上升负=下降,has_change=false 表示无上一周期对比) */
export interface LeaderboardMe {
period: LeaderboardPeriod;
metric: LeaderboardMetric;
rank: number;
total: number;
score: number;
change: number;
has_change: boolean;
}
/** SSR 排行榜;失败时抛错,由页面降级为空榜 */
export async function fetchLeaderboard(
period: LeaderboardPeriod,
metric: LeaderboardMetric,
cookieHeader?: string
): Promise<LeaderboardResponse> {
const res = await fetch(
`${API_BASE}/api/leaderboard?period=${period}&metric=${metric}`,
ssrInit(cookieHeaders(cookieHeader))
);
if (!res.ok) throw new Error("获取排行榜失败");
return res.json();
}
/** SSR 我的排名;游客/失败返回 null(不抛错) */
export async function fetchMyRank(
period: LeaderboardPeriod,
metric: LeaderboardMetric,
cookieHeader?: string
): Promise<LeaderboardMe | null> {
try {
const res = await fetch(
`${API_BASE}/api/leaderboard/me?period=${period}&metric=${metric}`,
ssrInit(cookieHeaders(cookieHeader))
);
if (!res.ok) return null;
const data = (await res.json()) as { me: LeaderboardMe | null };
return data.me ?? null;
} catch {
return null;
}
}
export async function fetchBoards(): Promise<{ boards: BoardWithCount[] }> {
const res = await fetch(`${API_BASE}/api/boards`, ssrInit());
if (!res.ok) throw new Error("获取板块失败");