diff --git a/client/src/components/task/TaskStatsModal.vue b/client/src/components/task/TaskStatsModal.vue index 24a2802..e63c0ff 100644 --- a/client/src/components/task/TaskStatsModal.vue +++ b/client/src/components/task/TaskStatsModal.vue @@ -3,452 +3,526 @@ :show="show" @update:show="$emit('update:show', $event)" preset="card" - title="提交统计" - style="max-width: 660px" + style="width: 90vw; max-width: 760px" :bordered="false" > -
- - + + - -
+
还没有打过分的作品
+ + + + + + + + diff --git a/client/src/pages/ChallengeDetail.vue b/client/src/pages/ChallengeDetail.vue index e90318c..48bb7b3 100644 --- a/client/src/pages/ChallengeDetail.vue +++ b/client/src/pages/ChallengeDetail.vue @@ -25,7 +25,7 @@ - 本题统计 + 课堂总结 - + { else dist.count_4_plus++ } - // 标记统计:该任务所有提交(不按学生过滤) - const flagStats = { red: 0, blue: 0, green: 0, yellow: 0 } - for (const s of subs) { - if (s.flag && s.flag in flagStats) flagStats[s.flag as keyof typeof flagStats]++ + // 高分作品:每人分数最高的一份(同分取较早的),取前五 + const best = new Map() + for (const s of studentSubs) { + const cur = best.get(s.user_id) + if (s.score > 0 && (!cur || s.score > cur.score || (s.score === cur.score && s.created < cur.created))) + best.set(s.user_id, s) } - - const topViewed = classname - ? all( - `SELECT s.id, s.view_count, u.username, u.classname ${SUBMISSION_FROM} - WHERE s.task_id = $id AND u.role = 'normal' AND u.classname = $classname - ORDER BY s.view_count DESC LIMIT 5`, - { id: taskId, classname }, - ) - : all( - `SELECT s.id, s.view_count, u.username, u.classname ${SUBMISSION_FROM} - WHERE s.task_id = $id AND u.role = 'normal' - ORDER BY s.view_count DESC LIMIT 5`, - { id: taskId }, - ) + const studentById = new Map(students.map((s) => [s.id, s])) + const topScored = [...best.values()] + .sort((a, b) => b.score - a.score || (a.created < b.created ? -1 : 1)) + .slice(0, 5) + .map((s) => ({ ...tag(studentById.get(s.user_id)!), score: s.score, submission_id: uuidOut(s.id) })) const unrated = students.filter((s) => submittedIds.has(s.id) && !ratedIds.has(s.id)) return c.json({ @@ -477,14 +470,9 @@ router.get("/stats/:task_id", (c) => { unsubmitted_users: students.filter((s) => !submittedIds.has(s.id)).map(tag), unrated_users: unrated.map(tag), submission_count_distribution: dist, - flag_stats: flagStats, + present_count: studentSubs.filter((s) => s.flag === "present").length, classes, - top_viewed: topViewed.map((s) => ({ - username: s.username, - classname: s.classname, - view_count: s.view_count, - submission_id: uuidOut(s.id), - })), + top_scored: topScored, }) }) diff --git a/server/tests/api.test.ts b/server/tests/api.test.ts index 7f6300d..d150648 100644 --- a/server/tests/api.test.ts +++ b/server/tests/api.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, test } from "bun:test" -import { get, all } from "../src/db" +import { get, all, run } from "../src/db" import { app } from "../src/app" import { config } from "../src/config" import { mkdtemp, readFile, readdir, rm } from "node:fs/promises" @@ -609,3 +609,40 @@ describe("同学作品与批改", () => { expect((await teacher("PUT", `/api/submission/${uuid(latest)}/flag`, { flag: "present" })).json.flag).toBe("present") }) }) + +describe("课堂总结", () => { + test("按班级统计,高分作品每人只取最高的一份", async () => { + makeUser("adm", "admin") + const task = makeTask("挑战", "challenge", 1) + const a = makeUser("weba1小明", "normal", "a1") + const b = makeUser("weba1小红", "normal", "a1") + makeUser("weba1小刚", "normal", "a1") + const other = makeUser("weba2小李", "normal", "a2") + makeSubmission(a, task, 3, "2026-01-01 00:00:00.000000") + const aBest = makeSubmission(a, task, 4.5, "2026-01-02 00:00:00.000000") + makeSubmission(a, task, 4, "2026-01-03 00:00:00.000000") + const bSub = makeSubmission(b, task, 0) + run(`UPDATE submission_submission SET flag = 'present' WHERE id = $id`, { id: bSub }) + makeSubmission(other, task, 5) + + const adm = await client("adm") + const res = await adm("GET", `/api/submission/stats/${task}?classname=a1`) + expect(res.status).toBe(200) + expect(res.json).toMatchObject({ + submitted_count: 2, + unsubmitted_count: 1, + average_score: 4, + unrated_count: 2, + present_count: 1, + classes: ["a1", "a2"], + submission_count_distribution: { count_1: 1, count_2: 0, count_3: 1, count_4_plus: 0 }, + }) + expect(res.json.unsubmitted_users).toEqual([{ username: "weba1小刚", classname: "a1" }]) + expect(res.json.top_scored).toEqual([ + { username: "weba1小明", classname: "a1", score: 4.5, submission_id: uuid(aBest) }, + ]) + + const all = await adm("GET", `/api/submission/stats/${task}`) + expect(all.json.top_scored.map((t: any) => t.username)).toEqual(["weba2小李", "weba1小明"]) + }) +})