feat(排名): 排名页重做成领奖台加全班赛道,一眼看出自己第几、该追谁、谁在追自己
Deploy / deploy (push) Canceled after 0s

- 一张榜两个开关:本班 / 本年级 / 全服 × 这周 / 这学期 / 全部,默认本班 · 这学期。
  原来最大那张全服 Top100 里,这学期在用的 25 级学生一个都进不去
- 做对一样多的不再按「提交少的在前」排,改成先做到的在前;名次旁边写比周一升降了几名
- 「你」那张卡:第几名、再做对几道上领奖台,要追的人早你几秒、追你的人晚你几秒
- 本年级 / 全服只列前面一段和你附近一段,中间折起来,点开再看
- 班级对抗按人均做对比,另有我和对手的名次走势、这周本班、每周冠军
- 老师不上榜,选班看;每行「⋯」能把怀疑抄代码的学生设成不计入排名(新增 user.rank_hidden_at,
  首页周榜同样不算他),随时能恢复
- 去掉全服 Top10、日活两张图、在线人数和班级详情弹框里的统计词,以及它们背后的旧接口

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-10 07:26:15 +08:00
1 parent 0cee4ef779
commit 1c6e2b9d82
31 files changed
+6633 -1392

No files matched your search

+34 -27
View File
@@ -6,7 +6,6 @@ import {
type ContestList,
type ContestClassView,
type ContestScoreboard,
type ActivityRankItem,
type FormatCodeResponse,
type Metrics,
type TutorialSummary,
@@ -16,9 +15,11 @@ import {
type ClassLesson,
type KnowledgeMap,
type ClassComparisonResponse,
type ClassRankItem,
type ClassUserRank,
type UserRank,
type ClassBattleItem,
type RankBoard,
type RankPeriod,
type RankScope,
type WeeklyChampion,
type WeeklyRank,
type ProblemRank,
type CreateSubmissionResponse,
@@ -64,7 +65,6 @@ import type {
Submission,
SubmissionListPayload,
SubmitCodePayload,
OnlineCount,
Tutorial,
TutorialProgress,
} from "utils/types"
@@ -77,11 +77,6 @@ import type {
*/
const loadDetailSchemas = () => import("./detailSchemas")
/** 当前在线人数。只有聚合数字,「谁在线」在榜单接口里、且只对老师下发 */
export function getOnlineCount() {
return api.get<OnlineCount>("site/online")
}
export async function getProblemList(
offset = 0,
limit = 10,
@@ -227,19 +222,41 @@ export function getSubmissionStatisticsGrid(
}
/**
* 全服榜单。上限(100 名)由服务端定,调用方只管翻页 ——
* 「全服 Top10」就是这个榜的第一页,取 limit=10 即可,不需要另一个上限参数。
* 排名页的榜:本班 / 本年级 / 全服 × 这周 / 这学期 / 全部。学生看自己的班和年级,
* 老师用 `className` 选班(不给就是最近上课的班)。`full` 把本年级 / 全服中间折起来的那段也要回来
*/
export function getRank(offset: number, limit: number) {
return api.get<UserRank>("rankings/users", { params: { offset, limit } })
export function getRankBoard(
scope: RankScope,
period: RankPeriod,
options: { className?: string; full?: boolean } = {},
) {
return api.get<RankBoard>("rankings/board", {
params: {
scope,
period,
className: options.className || undefined,
full: options.full ? 1 : undefined,
},
})
}
export function getActivityRank(start: string) {
return api.get<ActivityRankItem[]>("rankings/activity", {
params: { start },
/** 班级对抗:全服各班这学期人均做对,外加这周人均涨了多少 */
export function getClassBattle() {
return api.get<ClassBattleItem[]>("rankings/classes")
}
/** 本班最近几周的每周冠军 */
export function getWeeklyChampions(className?: string) {
return api.get<WeeklyChampion[]>("rankings/champions", {
params: { className: className || undefined },
})
}
/** 老师把学生设成不计入排名 / 恢复 */
export function setRankHidden(userId: number, hidden: boolean) {
return api.put<null>(`rankings/hidden/${userId}`, { hidden })
}
/**
* 本周进步榜。`scope` 只有两个取值,服务端认不出的一律当 global ——
* 班级榜要求调用者有班级,教师/超管拿到的是 400,所以别在没班级时切过去。
@@ -281,16 +298,6 @@ export function getLastVisit() {
return api.get<LastVisit>("me/last-visit")
}
export function getClassRank(grade?: number | null) {
return api.get<ClassRankItem[]>("rankings/classes", { params: { grade } })
}
export function getUserClassRank(scope?: "all" | "window", offset?: number, limit?: number) {
return api.get<ClassUserRank>("me/class-rank", {
params: { scope, offset, limit },
})
}
export function getClassPK(classNames: string[], startTime?: string, endTime?: string) {
return api.post<ClassComparisonResponse>("classes/comparison", {
classNames,