From 591c7bcab91d1fcb1729404b71563d029cc69af2 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Tue, 13 Oct 2026 14:07:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=90=8E=E5=8F=B0=E6=AF=94=E8=B5=9B):=20?= =?UTF-8?q?=E6=AF=94=E8=B5=9B=E5=88=97=E8=A1=A8=E5=B8=A6=E4=B8=8A=E6=AF=8F?= =?UTF-8?q?=E5=9C=BA=E5=87=A0=E9=81=93=E9=A2=98=E3=80=81=E5=87=A0=E4=BA=BA?= =?UTF-8?q?=E5=8F=82=E5=8A=A0=EF=BC=8C=E6=8C=89=E5=BC=80=E5=A7=8B=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=8E=92=EF=BC=8C=E5=A4=8D=E5=88=B6=E5=87=BA=E6=9D=A5?= =?UTF-8?q?=E7=9A=84=E6=97=A7=E6=AF=94=E8=B5=9B=E4=B8=8D=E5=86=8D=E8=B7=91?= =?UTF-8?q?=E5=88=B0=E6=9C=80=E5=89=8D=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- apps/api/src/routes/admin/contest.ts | 33 +++++++++++++++++++++++++--- packages/contract/src/admin.ts | 9 +++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/apps/api/src/routes/admin/contest.ts b/apps/api/src/routes/admin/contest.ts index b945afe..0204d83 100644 --- a/apps/api/src/routes/admin/contest.ts +++ b/apps/api/src/routes/admin/contest.ts @@ -5,7 +5,7 @@ import { type AdminContest, type AdminContestList, } from "@oj2/contract" -import { and, count, desc, eq, ilike, inArray } from "drizzle-orm" +import { and, count, countDistinct, desc, eq, ilike, inArray } from "drizzle-orm" import { Hono } from "hono" import { requireTeacher, type AppEnv } from "../../auth/middleware" @@ -93,12 +93,39 @@ adminContestRoutes.get("/contests", requireTeacher, async (c) => { .innerJoin(schema.user, eq(schema.contest.createdById, schema.user.id)) .leftJoin(schema.userProfile, eq(schema.userProfile.userId, schema.user.id)) .where(where) - .orderBy(desc(schema.contest.createTime)) + // 按开始时间:原来按创建时间,「复制一份」出来的旧比赛会排到最前面 + .orderBy(desc(schema.contest.startTime), desc(schema.contest.id)) .limit(limit) .offset(offset), ]) + const ids = rows.map((row) => row.contest.id) + const [problemCounts, participantCounts] = ids.length + ? await Promise.all([ + db + .select({ contestId: schema.problem.contestId, value: count() }) + .from(schema.problem) + .where(inArray(schema.problem.contestId, ids)) + .groupBy(schema.problem.contestId), + db + .select({ + contestId: schema.submission.contestId, + value: countDistinct(schema.submission.userId), + }) + .from(schema.submission) + .where(inArray(schema.submission.contestId, ids)) + .groupBy(schema.submission.contestId), + ]) + : [[], []] + const problemsOf = new Map(problemCounts.map((row) => [row.contestId, row.value])) + const peopleOf = new Map(participantCounts.map((row) => [row.contestId, row.value])) return success(c, { - results: await Promise.all(rows.map(serialize)), + results: await Promise.all( + rows.map(async (row) => ({ + ...(await serialize(row)), + problemCount: problemsOf.get(row.contest.id) ?? 0, + participantCount: peopleOf.get(row.contest.id) ?? 0, + })), + ), total: totalRows[0]?.value ?? 0, } satisfies AdminContestList) }) diff --git a/packages/contract/src/admin.ts b/packages/contract/src/admin.ts index ed0ec21..d049465 100644 --- a/packages/contract/src/admin.ts +++ b/packages/contract/src/admin.ts @@ -394,7 +394,13 @@ export const adminContestSchema = z.object({ contestType: z.enum(["Public", "Password Protected"]), }) -export const adminContestListSchema = paginatedSchema(adminContestSchema) +/** 后台比赛列表的一行:比赛本身,加几道题、几人参加(交过题的人数) */ +export const adminContestListItemSchema = adminContestSchema.extend({ + problemCount: z.number().int(), + participantCount: z.number().int(), +}) + +export const adminContestListSchema = paginatedSchema(adminContestListItemSchema) export const createContestRequestSchema = z.object({ title: z.string().trim().min(1).max(128), @@ -1008,6 +1014,7 @@ export type CreateProblemRequest = z.infer export type AdminContest = z.infer export type AdminContestList = z.infer +export type AdminContestListItem = z.infer export type AdminUser = z.infer export type AdminUserList = z.infer export type CheckUsernamesResponse = z.infer