From 2553c6ddfe5fe3e406334288969d4bc536259345 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Sat, 10 Oct 2026 09:52:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=92=E5=90=8D):=20=E4=B8=AA=E6=80=A7?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E6=8C=82=E5=88=B0=E6=8E=92=E5=90=8D=E4=B8=8A?= =?UTF-8?q?=EF=BC=8C=E9=A2=86=E5=A5=96=E5=8F=B0=E4=B8=8A=E6=AF=8F=E4=BA=BA?= =?UTF-8?q?=E4=B8=80=E5=8F=A5=E3=80=81=E3=80=8C=E4=BD=A0=E3=80=8D=E5=8D=A1?= =?UTF-8?q?=E9=87=8C=E5=B0=B1=E5=9C=B0=E6=94=B9=EF=BC=9B=E5=85=A8=E6=9C=8D?= =?UTF-8?q?=E6=8E=92=E5=90=8D=E5=8F=AA=E5=88=97=E5=88=B0=E7=AC=AC=20100=20?= =?UTF-8?q?=E5=90=8D=EF=BC=8C=E5=B1=95=E5=BC=80=E4=B8=8D=E5=86=8D=E5=8D=A1?= =?UTF-8?q?=E6=AD=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 领奖台:头像旁边一个签名气泡(手机在头像上面),只露两行,长的指上去看全文 - 「你」卡:自己的签名带「改」,就地改不用跳设置页;没写的提示「写一句个性签名」;前一名、后一名名字下面带他们的签名 - 赛道:桌面有签名的名字后面一个引号,指上去看;手机直接写在名字后面 - 个性签名以后限 30 个字(老的长签名不动,榜上只露两行) - 老师在名单「⋯」或台上气泡的「×」能清空不合适的签名(DELETE /rankings/mood/:userId) - 全服名单最多列到第 100 名:原来展开会一次摊开一千五百多人把页面卡死 Co-Authored-By: Claude Opus 5.5 --- apps/api/src/routes/ranking.ts | 40 ++- apps/api/src/services/ranking.ts | 3 + apps/web/src/oj/api.ts | 7 +- apps/web/src/oj/rank/components/RankMe.vue | 229 ++++++++++++++++- .../web/src/oj/rank/components/RankPodium.vue | 232 +++++++++++++++--- apps/web/src/oj/rank/components/RankTrack.vue | 72 +++++- apps/web/src/oj/rank/list.vue | 42 +++- apps/web/src/oj/user/setting.vue | 14 +- packages/contract/src/account.ts | 6 +- packages/contract/src/ranking.ts | 12 + 10 files changed, 588 insertions(+), 69 deletions(-) diff --git a/apps/api/src/routes/ranking.ts b/apps/api/src/routes/ranking.ts index 5ff324d..09bef46 100644 --- a/apps/api/src/routes/ranking.ts +++ b/apps/api/src/routes/ranking.ts @@ -46,6 +46,8 @@ const WINDOW_BEFORE = 5 const WINDOW_AFTER = 7 /** 没有「我」(老师、没上榜)时榜面给多少 */ const BOARD_HEAD_NO_ME = 30 +/** 全服名单最多列到第几名,展开也到此为止(一千五百多人全摊开页面会卡死) */ +const ALL_CAP = 100 /** 走势图往回看几周 */ const TREND_WEEKS = 4 /** 每周冠军列几周 */ @@ -58,6 +60,7 @@ function toRow(standing: Standing, change: number | null): RankRow { user: sampleUser(entrant, null), avatar: entrant.avatar, className: entrant.className, + mood: entrant.mood, solved: standing.solved, reachedAt: standing.reachedAt, change, @@ -187,11 +190,13 @@ rankingRoutes.get("/rankings/board", optionalAuth, async (c) => { const meIndex = user ? now.findIndex((row) => row.entrant.id === user.id && row.solved > 0) : -1 const me = meIndex >= 0 ? now[meIndex] : undefined + const cap = scope.data === "all" ? ALL_CAP : null + const listable = cap ? now.slice(0, cap) : now const full = includeZero || c.req.query("full") === "1" - let picked = now + let picked = listable if (!full) { const head = me ? BOARD_HEAD : BOARD_HEAD_NO_ME - picked = now.filter( + picked = listable.filter( (_, index) => index < head || (me && index >= meIndex - WINDOW_BEFORE && index <= meIndex + WINDOW_AFTER), ) @@ -230,7 +235,8 @@ rankingRoutes.get("/rankings/board", optionalAuth, async (c) => { changeSince, total: now.length, rows: picked.map((row) => rowOf(row)!), - complete: picked.length === now.length, + complete: picked.length === listable.length, + cap, me: rowOf(me), ahead: rowOf(meIndex > 0 ? now[meIndex - 1] : undefined), behind: rowOf(meIndex >= 0 ? now[meIndex + 1] : undefined), @@ -340,3 +346,31 @@ rankingRoutes.put("/rankings/hidden/:userId", requireTeacher, async (c) => { if (!updated.length) return failure(c, 404, "user-not-found", "没有这个学生") return success(c, null) }) + +/** + * 老师清空一个学生的个性签名。签名在排名上挂出来以后更显眼,拿同学开涮、留电话号码的 + * 得有人能拿掉(用户 2026-10-10 定的)。只清这一句,他之后还能重新写。 + */ +rankingRoutes.delete("/rankings/mood/:userId", requireTeacher, async (c) => { + const userId = Number(c.req.param("userId")) + if (!Number.isInteger(userId)) return failure(c, 400, "invalid-user", "用户不对") + + const updated = await db + .update(schema.userProfile) + .set({ mood: null }) + .where( + and( + eq(schema.userProfile.userId, userId), + inArray( + schema.userProfile.userId, + db + .select({ id: schema.user.id }) + .from(schema.user) + .where(inArray(schema.user.adminType, [...STUDENT_ROLES])), + ), + ), + ) + .returning({ id: schema.userProfile.userId }) + if (!updated.length) return failure(c, 404, "user-not-found", "没有这个学生") + return success(c, null) +}) diff --git a/apps/api/src/services/ranking.ts b/apps/api/src/services/ranking.ts index 270627f..1dfe9a7 100644 --- a/apps/api/src/services/ranking.ts +++ b/apps/api/src/services/ranking.ts @@ -51,6 +51,7 @@ export interface Entrant { username: string className: string | null avatar: string | null + mood: string | null /** 每道题第一次做对的时刻,升序;毫秒数用来比较,原文留着给出参 */ times: number[] stamps: string[] @@ -90,6 +91,7 @@ export async function loadEntrants(where: SQL, since: string | null): Promise time), stamps: pairs.map(([, stamp]) => stamp), } diff --git a/apps/web/src/oj/api.ts b/apps/web/src/oj/api.ts index df610b5..41bdba2 100644 --- a/apps/web/src/oj/api.ts +++ b/apps/web/src/oj/api.ts @@ -264,6 +264,11 @@ export function setRankHidden(userId: number, hidden: boolean) { return api.put(`rankings/hidden/${userId}`, { hidden }) } +/** 老师清空一个学生的个性签名 */ +export function clearMood(userId: number) { + return api.delete(`rankings/mood/${userId}`) +} + /** * 本周进步榜。`scope` 只有两个取值,服务端认不出的一律当 global —— * 班级榜要求调用者有班级,教师/超管拿到的是 400,所以别在没班级时切过去。 @@ -363,7 +368,7 @@ export function uploadAvatar(file: File) { }) } -export function updateProfile(data: { realName: string; mood: string }) { +export function updateProfile(data: { realName?: string; mood?: string }) { return api.put("me/profile", data) } diff --git a/apps/web/src/oj/rank/components/RankMe.vue b/apps/web/src/oj/rank/components/RankMe.vue index df23ba0..2f26836 100644 --- a/apps/web/src/oj/rank/components/RankMe.vue +++ b/apps/web/src/oj/rank/components/RankMe.vue @@ -1,6 +1,8 @@
排名没取到,刷新一下试试
@@ -452,6 +485,7 @@ async function restore(userId: number) { :label="`${scopeLabel(board.scope)} · ${periodLabel(board.period)}`" :minis="minis" :compact="!isDesktop" + @mood-saved="onMoodSaved" />
你现在不计入排名 diff --git a/apps/web/src/oj/user/setting.vue b/apps/web/src/oj/user/setting.vue index 94ac54c..f0fcdeb 100644 --- a/apps/web/src/oj/user/setting.vue +++ b/apps/web/src/oj/user/setting.vue @@ -1,4 +1,5 @@