feat(比赛): 比赛页一页看题目和自己的名次;排名竞争感做足、结束有领奖台;老师考试中看全班、考完看成绩;新建比赛一屏做完
Deploy / deploy (push) Canceled after 0s

- 学生比赛页(设计稿 B):左边题目带几人做对、谁最先做对;右边我第几名、5 分钟里升了几名、再做对 1 道能到第几名、我前后的人
- 期中、期末考试中只有自己的进度,写明「排名考完公布」,题目页的排名入口也藏起来
- 排名页:按班筛、名次变化、最先做对的深绿格、自己那行钉住;结束后前三名领奖台 + 我的成绩卡;排名曲线和学生的「导出数据」去掉
- 老师的「全班情况」:卡在一道题、15 分钟没交的放最上面,没进来的人对上看板的请假;考完变「成绩」,点格子看代码、标看过了(代替后台审核页),导出成绩
- 比赛列表:进行中的一大块「接着比」,已结束的写我第几名,前三带奖杯
- 新建比赛:默认 10 分钟后开始,时长点按钮,搜题号把题加进来自动编号、拖动排顺序

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-09 20:58:59 +08:00
1 parent b94e028ff4
commit 151bbdcb50
19 files changed
+3768 -743

No files matched your search

+14 -9
View File
@@ -4,6 +4,8 @@ import {
type Contest,
type ContestAccess,
type ContestList,
type ContestClassView,
type ContestScoreboard,
type ActivityRankItem,
type FormatCodeResponse,
type Metrics,
@@ -52,7 +54,6 @@ import { toProblemRow } from "oj/transforms"
import type {
Announcement,
AnnouncementListItem,
ContestRank,
Profile,
Message,
SubmissionListItem,
@@ -303,9 +304,14 @@ export function getContestList(query: {
limit: number
keyword: string
status: string
/** 练习 / 期中 / 期末,或「考试」= 期中和期末 */
tag: string
/** 只看我交过题的 */
joined?: boolean
}) {
return api.get<ContestList>("contests", { params: query })
return api.get<ContestList>("contests", {
params: { ...query, joined: query.joined ? "1" : undefined },
})
}
export function getContest(id: string) {
@@ -328,13 +334,12 @@ export async function getContestProblems(contestID: string) {
return res.map(toProblemRow)
}
export function getContestRank(contestID: string, query: { limit: number; offset: number }) {
// submissionInfo 在契约里是 Record<string, unknown>(JSONB 原文),
// 前端在这里收窄成 ContestSubmissionInfo,见 utils/types 的 ContestRank
return api.get<{ results: ContestRank[]; total: number }>(
`contests/${encodeURIComponent(contestID)}/rank`,
{ params: query },
)
export function getContestScoreboard(contestID: string) {
return api.get<ContestScoreboard>(`contests/${encodeURIComponent(contestID)}/scoreboard`)
}
export function getContestClassView(contestID: string) {
return api.get<ContestClassView>(`contests/${encodeURIComponent(contestID)}/class-view`)
}
export function uploadAvatar(file: File) {