- 一张榜两个开关:本班 / 本年级 / 全服 × 这周 / 这学期 / 全部,默认本班 · 这学期。 原来最大那张全服 Top100 里,这学期在用的 25 级学生一个都进不去 - 做对一样多的不再按「提交少的在前」排,改成先做到的在前;名次旁边写比周一升降了几名 - 「你」那张卡:第几名、再做对几道上领奖台,要追的人早你几秒、追你的人晚你几秒 - 本年级 / 全服只列前面一段和你附近一段,中间折起来,点开再看 - 班级对抗按人均做对比,另有我和对手的名次走势、这周本班、每周冠军 - 老师不上榜,选班看;每行「⋯」能把怀疑抄代码的学生设成不计入排名(新增 user.rank_hidden_at, 首页周榜同样不算他),随时能恢复 - 去掉全服 Top10、日活两张图、在线人数和班级详情弹框里的统计词,以及它们背后的旧接口 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0cee4ef779
commit
1c6e2b9d82
31 files changed
+6633
-1392
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { paginatedSchema, sampleUserSchema } from "./common"
|
||||
import { sampleUserSchema } from "./common"
|
||||
import { userProfileSchema } from "./auth"
|
||||
|
||||
export const registerRequestSchema = z.object({
|
||||
@@ -37,27 +37,6 @@ export const rankProfileSchema = z.object({
|
||||
isOnline: z.boolean().nullable().default(null),
|
||||
})
|
||||
|
||||
/**
|
||||
* 榜单里「我」的位置。`rank` 是**全服名次**,与当前翻到第几页无关 ——
|
||||
* 前 100 名之外的学生也拿得到,页面靠它单独显示一行。
|
||||
*
|
||||
* 名次口径与列表的排序完全一致(AC 降序 → 提交数升序 → id 升序),
|
||||
* 所以「我的名次」和「我在表格里的行号」永远对得上;三个键都相同才算并列。
|
||||
*/
|
||||
export const myRankSchema = rankProfileSchema.extend({
|
||||
rank: z.number().int().positive(),
|
||||
})
|
||||
|
||||
export const userRankSchema = paginatedSchema(rankProfileSchema).extend({
|
||||
/** 未登录、或身份不入榜(教师/超管)时为 null */
|
||||
me: myRankSchema.nullable(),
|
||||
})
|
||||
|
||||
export const activityRankItemSchema = z.object({
|
||||
username: z.string(),
|
||||
count: z.number().int().nonnegative(),
|
||||
})
|
||||
|
||||
/**
|
||||
* 周榜的一行。`solvedCount` 是**本周首次 AC 的题目数** —— 不是「本周 AC 过的去重题数」。
|
||||
* 后者会把上周就做出来的题重交一次也算成本周成绩,等于给刷榜留了个口子;
|
||||
@@ -99,9 +78,6 @@ export type RegisterRequest = z.infer<typeof registerRequestSchema>
|
||||
export type UpdateProfileRequest = z.infer<typeof updateProfileRequestSchema>
|
||||
export type ProblemRank = z.infer<typeof problemRankSchema>
|
||||
export type RankProfile = z.infer<typeof rankProfileSchema>
|
||||
export type UserRank = z.infer<typeof userRankSchema>
|
||||
export type MyRank = z.infer<typeof myRankSchema>
|
||||
export type ActivityRankItem = z.infer<typeof activityRankItemSchema>
|
||||
export type WeeklyRankItem = z.infer<typeof weeklyRankItemSchema>
|
||||
export type WeeklyRank = z.infer<typeof weeklyRankSchema>
|
||||
export type Metrics = z.infer<typeof metricsSchema>
|
||||
|
||||
@@ -3,31 +3,6 @@ import { z } from "zod"
|
||||
import { judgeStatusSchema } from "./judge-status"
|
||||
import { lessonLanguageSchema as lessonLanguageValueSchema } from "./language"
|
||||
|
||||
export const classRankItemSchema = z.object({
|
||||
className: z.string(),
|
||||
userCount: z.number().int(),
|
||||
totalAc: z.number().int(),
|
||||
totalSubmission: z.number().int(),
|
||||
avgAc: z.number(),
|
||||
acRate: z.number(),
|
||||
rank: z.number().int(),
|
||||
})
|
||||
|
||||
export const classUserRankItemSchema = z.object({
|
||||
userId: z.number().int(),
|
||||
username: z.string(),
|
||||
acceptedNumber: z.number().int(),
|
||||
submissionNumber: z.number().int(),
|
||||
rank: z.number().int(),
|
||||
})
|
||||
|
||||
export const classUserRankSchema = z.object({
|
||||
className: z.string(),
|
||||
myRank: z.number().int(),
|
||||
total: z.number().int(),
|
||||
ranks: z.array(classUserRankItemSchema),
|
||||
})
|
||||
|
||||
export const classComparisonSchema = z.object({
|
||||
className: z.string(),
|
||||
userCount: z.number().int(),
|
||||
@@ -225,8 +200,6 @@ export const classLessonSchema = z.object({
|
||||
problems: z.array(z.object({ problemDisplayId: z.string(), title: z.string() })),
|
||||
})
|
||||
|
||||
export type ClassRankItem = z.infer<typeof classRankItemSchema>
|
||||
export type ClassUserRank = z.infer<typeof classUserRankSchema>
|
||||
export type ClassComparison = z.infer<typeof classComparisonSchema>
|
||||
export type ClassComparisonResponse = z.infer<typeof classComparisonResponseSchema>
|
||||
|
||||
@@ -240,5 +213,4 @@ export type ClassBoardStudent = z.infer<typeof classBoardStudentSchema>
|
||||
export type ClassBoardCell = z.infer<typeof classBoardCellSchema>
|
||||
export type ClassActivityProblem = z.infer<typeof classActivityProblemSchema>
|
||||
|
||||
export type ClassUserRankItem = z.infer<typeof classUserRankItemSchema>
|
||||
export type ClassComparisonRequest = z.infer<typeof classComparisonRequestSchema>
|
||||
@@ -12,6 +12,7 @@ export * from "./judge-status"
|
||||
export * from "./language"
|
||||
export * from "./problem"
|
||||
export * from "./problemset"
|
||||
export * from "./ranking"
|
||||
export * from "./roles"
|
||||
export * from "./site"
|
||||
export * from "./submission"
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { sampleUserSchema } from "./common"
|
||||
|
||||
/**
|
||||
* 排名页(设计稿「排名重设计」G1–G3)。用户要的是**激发竞争、一眼看出排名**,所以:
|
||||
*
|
||||
* - **同样多不并列**:做对多的在前,一样多的**先做到的在前**(`reachedAt` 早的在前)。
|
||||
* 课堂驱动下一个班十几个人同分是常态,原来按「提交少的在前」排等于罚多试的人;
|
||||
* 按先后排,名次差的是「这节课谁先做完」。
|
||||
* - 「做对」= 这道题**第一次**做对落在这段时间里(比赛里的提交不算),老题重交不算成绩。
|
||||
*/
|
||||
export const rankScopeSchema = z.enum(["class", "grade", "all"])
|
||||
export const rankPeriodSchema = z.enum(["week", "term", "all"])
|
||||
|
||||
export const rankRowSchema = z.object({
|
||||
rank: z.number().int().positive(),
|
||||
user: sampleUserSchema,
|
||||
/** 自己上传的头像;还是默认头像时为 null,前端画名字最后一个字 */
|
||||
avatar: z.string().nullable(),
|
||||
className: z.string().nullable(),
|
||||
solved: z.number().int().nonnegative(),
|
||||
/** 做到这个数的时刻(最后一道新做对的题);一道没做对时为 null */
|
||||
reachedAt: z.string().nullable(),
|
||||
/**
|
||||
* 比 `changeSince` 那一刻升了几名,正数是升。那时还一道没做对(不在榜上)时为 null ——
|
||||
* 从无到有不算「升了几名」,否则开学第一周人人都是 ↑30。
|
||||
*/
|
||||
change: z.number().int().nullable(),
|
||||
})
|
||||
|
||||
/** 我和对手每周的名次,给「我和对手的名次」那张走势图 */
|
||||
export const rankTrendSchema = z.object({
|
||||
/** 每个点的时刻(周一零点,也就是上周日晚上),最后一个是现在 */
|
||||
points: z.array(z.string()),
|
||||
series: z.array(
|
||||
z.object({
|
||||
userId: z.number().int(),
|
||||
/** 和 points 一一对应;那时还没上榜为 null */
|
||||
ranks: z.array(z.number().int().positive().nullable()),
|
||||
}),
|
||||
),
|
||||
})
|
||||
|
||||
export const rankBoardSchema = z.object({
|
||||
scope: rankScopeSchema,
|
||||
period: rankPeriodSchema,
|
||||
/** 本班 = 班号;本年级 = 年级(班号前两位,如 "25");全服 = null */
|
||||
className: z.string().nullable(),
|
||||
/** 这段时间从哪一刻算起;「全部」为 null */
|
||||
start: z.string().nullable(),
|
||||
/** `change` 拿哪一刻比:这周 = 今天零点,这学期 / 全部 = 本周一零点 */
|
||||
changeSince: z.string(),
|
||||
/** 榜上一共几个人。本班把一道没做对的也算上(全班名单),本年级 / 全服只算做对过的 */
|
||||
total: z.number().int().nonnegative(),
|
||||
/**
|
||||
* 本班、或者带了 `full=1` 时是全部;否则是前面一段 + 我附近一段(`complete = false`),
|
||||
* 中间折起来,按名次断开的地方就是折叠处
|
||||
*/
|
||||
rows: z.array(rankRowSchema),
|
||||
complete: z.boolean(),
|
||||
/** 我在这张榜上的那一行。没登录、老师、不计入排名、或者这段时间一道没做对时为 null */
|
||||
me: rankRowSchema.nullable(),
|
||||
/** 名次紧挨在我前面、后面的人 */
|
||||
ahead: rankRowSchema.nullable(),
|
||||
behind: rankRowSchema.nullable(),
|
||||
/** 只有「这学期」「全部」且有 me 时才有:这周清零,画走势没意义 */
|
||||
trend: rankTrendSchema.nullable(),
|
||||
/** 我被老师设成了不计入排名(学生自己看得到这句话,别人看不到他) */
|
||||
hidden: z.boolean(),
|
||||
/** 老师看本班时:这个班不计入排名的人。其余情况为空 */
|
||||
hiddenUsers: z.array(sampleUserSchema),
|
||||
})
|
||||
|
||||
/** 班级对抗:人均做对,按这学期算;`weekGain` 是这周人均涨了多少 */
|
||||
export const classBattleItemSchema = z.object({
|
||||
rank: z.number().int().positive(),
|
||||
className: z.string(),
|
||||
members: z.number().int().positive(),
|
||||
perCapita: z.number(),
|
||||
weekGain: z.number(),
|
||||
})
|
||||
|
||||
/** 本班每周冠军:最近几个已经结束的周,每周做对新题最多的人 */
|
||||
export const weeklyChampionSchema = z.object({
|
||||
weekStart: z.string(),
|
||||
user: sampleUserSchema,
|
||||
avatar: z.string().nullable(),
|
||||
solved: z.number().int().positive(),
|
||||
})
|
||||
|
||||
export const rankHiddenRequestSchema = z.object({
|
||||
hidden: z.boolean(),
|
||||
})
|
||||
|
||||
export type RankScope = z.infer<typeof rankScopeSchema>
|
||||
export type RankPeriod = z.infer<typeof rankPeriodSchema>
|
||||
export type RankRow = z.infer<typeof rankRowSchema>
|
||||
export type RankTrend = z.infer<typeof rankTrendSchema>
|
||||
export type RankBoard = z.infer<typeof rankBoardSchema>
|
||||
export type ClassBattleItem = z.infer<typeof classBattleItemSchema>
|
||||
export type WeeklyChampion = z.infer<typeof weeklyChampionSchema>
|
||||
export type RankHiddenRequest = z.infer<typeof rankHiddenRequestSchema>
|
||||
@@ -11,11 +11,6 @@ export const websiteConfigSchema = z.object({
|
||||
enableMaxkb: z.boolean(),
|
||||
})
|
||||
|
||||
/** 当前在线人数。只有聚合值 —— 「某某在不在线」是个人状态,不往匿名接口放 */
|
||||
export const onlineCountSchema = z.object({
|
||||
count: z.number().int().nonnegative(),
|
||||
})
|
||||
|
||||
/**
|
||||
* 一言。`from` 是出处(作品),`fromWho` 是谁说的,`type` 是数据集的分类名(动画、诗词……,界面不显示,
|
||||
* 只拿来判断出处要不要加书名号),
|
||||
@@ -31,4 +26,3 @@ export const quoteSchema = z.object({
|
||||
|
||||
export type WebsiteConfig = z.infer<typeof websiteConfigSchema>
|
||||
export type Quote = z.infer<typeof quoteSchema>
|
||||
export type OnlineCount = z.infer<typeof onlineCountSchema>
|
||||
Reference in new issue
Block a user