feat(课堂看板): 看板接口给没做对的格子带上今天最后一次提交,按错法归堆、点开讲错误要用
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
754a9d7caf
commit
0a4f528c44
3 files changed
+62
-1
No files matched your search
@@ -9,6 +9,7 @@ import {
|
|||||||
type ClassActivityProblem,
|
type ClassActivityProblem,
|
||||||
type LastVisit,
|
type LastVisit,
|
||||||
type LessonLanguage,
|
type LessonLanguage,
|
||||||
|
type StatisticInfo,
|
||||||
FLOWCHART_PASS_GRADES,
|
FLOWCHART_PASS_GRADES,
|
||||||
} from "@oj2/contract"
|
} from "@oj2/contract"
|
||||||
import { and, asc, desc, eq, gte, inArray, isNotNull, isNull, lt, sql } from "drizzle-orm"
|
import { and, asc, desc, eq, gte, inArray, isNotNull, isNull, lt, sql } from "drizzle-orm"
|
||||||
@@ -558,7 +559,7 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
|
|||||||
// 「来了几次」代码和流程图合起来数:那天只画了流程图的也算来过,和 recentDays 同一个口径
|
// 「来了几次」代码和流程图合起来数:那天只画了流程图的也算来过,和 recentDays 同一个口径
|
||||||
const recentActivity = activityRows({ since: windowStart, users: userIds })
|
const recentActivity = activityRows({ since: windowStart, users: userIds })
|
||||||
const localDay = sql<string>`to_char(${localTime(recentActivity.createTime)}, 'YYYY-MM-DD')`
|
const localDay = sql<string>`to_char(${localTime(recentActivity.createTime)}, 'YYYY-MM-DD')`
|
||||||
const [cells, lastSubmits, lastDrawn, attendance, drawn] = await Promise.all([
|
const [cells, lastSubmits, lastDrawn, attendance, drawn, latests] = await Promise.all([
|
||||||
ids.length && userIds.length
|
ids.length && userIds.length
|
||||||
? db
|
? db
|
||||||
.select({
|
.select({
|
||||||
@@ -650,7 +651,42 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
|
|||||||
)
|
)
|
||||||
.groupBy(schema.flowchartSubmission.userId, schema.flowchartSubmission.problemId)
|
.groupBy(schema.flowchartSubmission.userId, schema.flowchartSubmission.problemId)
|
||||||
: [],
|
: [],
|
||||||
|
/**
|
||||||
|
* 每人每题今天最后一次代码提交:没做对的格子靠它点开讲错误、按错法归堆。
|
||||||
|
* 范围和 cells 一样(非比赛、布置的语言),只多一个「今天」
|
||||||
|
*/
|
||||||
|
ids.length && userIds.length
|
||||||
|
? db
|
||||||
|
.selectDistinctOn([schema.submission.userId, schema.submission.problemId], {
|
||||||
|
userId: schema.submission.userId,
|
||||||
|
problemId: schema.submission.problemId,
|
||||||
|
id: schema.submission.id,
|
||||||
|
result: schema.submission.result,
|
||||||
|
runtimeError: sql<
|
||||||
|
StatisticInfo["runtime_error"] | null
|
||||||
|
>`${schema.submission.statisticInfo}->'runtime_error'`,
|
||||||
|
sampleCheck: sql<
|
||||||
|
StatisticInfo["sample_check"] | null
|
||||||
|
>`${schema.submission.statisticInfo}->'sample_check'`,
|
||||||
|
})
|
||||||
|
.from(schema.submission)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
isNull(schema.submission.contestId),
|
||||||
|
inArray(schema.submission.userId, userIds),
|
||||||
|
inArray(schema.submission.problemId, ids),
|
||||||
|
gte(schema.submission.createTime, start),
|
||||||
|
lessonLanguageFilter(lesson.language),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.orderBy(
|
||||||
|
schema.submission.userId,
|
||||||
|
schema.submission.problemId,
|
||||||
|
desc(schema.submission.createTime),
|
||||||
|
)
|
||||||
|
: [],
|
||||||
])
|
])
|
||||||
|
const latestByKey = new Map(latests.map((row) => [`${row.userId}:${row.problemId}`, row]))
|
||||||
const cellByKey = new Map(cells.map((row) => [`${row.userId}:${row.problemId}`, row]))
|
const cellByKey = new Map(cells.map((row) => [`${row.userId}:${row.problemId}`, row]))
|
||||||
const drawnByKey = new Map(drawn.map((row) => [`${row.userId}:${row.problemId}`, row]))
|
const drawnByKey = new Map(drawn.map((row) => [`${row.userId}:${row.problemId}`, row]))
|
||||||
const lastByUser = new Map(lastSubmits.map((row) => [row.userId, row.lastAt]))
|
const lastByUser = new Map(lastSubmits.map((row) => [row.userId, row.lastAt]))
|
||||||
@@ -689,12 +725,21 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
|
|||||||
const cell = cellByKey.get(key)
|
const cell = cellByKey.get(key)
|
||||||
const flow = drawnByKey.get(key)
|
const flow = drawnByKey.get(key)
|
||||||
const acceptedAt = earliest(cell?.firstAcceptedAt, flow?.firstPassedAt)
|
const acceptedAt = earliest(cell?.firstAcceptedAt, flow?.firstPassedAt)
|
||||||
|
const latest = acceptedAt ? undefined : latestByKey.get(key)
|
||||||
return {
|
return {
|
||||||
status: !cell && !flow ? "none" : acceptedAt ? "accepted" : "tried",
|
status: !cell && !flow ? "none" : acceptedAt ? "accepted" : "tried",
|
||||||
attempts: (cell?.attempts ?? 0) + (flow?.attempts ?? 0),
|
attempts: (cell?.attempts ?? 0) + (flow?.attempts ?? 0),
|
||||||
codeAttempts: cell?.attempts ?? 0,
|
codeAttempts: cell?.attempts ?? 0,
|
||||||
flowchartAttempts: flow?.attempts ?? 0,
|
flowchartAttempts: flow?.attempts ?? 0,
|
||||||
acceptedAt,
|
acceptedAt,
|
||||||
|
latest: latest
|
||||||
|
? {
|
||||||
|
submissionId: latest.id,
|
||||||
|
result: latest.result,
|
||||||
|
runtimeError: latest.runtimeError ?? undefined,
|
||||||
|
sampleCheck: latest.sampleCheck ?? undefined,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
lastSubmitAt: lastByUser.get(student.userId) ?? null,
|
lastSubmitAt: lastByUser.get(student.userId) ?? null,
|
||||||
|
|||||||
File renamed without changes.
@@ -2,6 +2,7 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { judgeStatusSchema } from "./judge-status"
|
import { judgeStatusSchema } from "./judge-status"
|
||||||
import { lessonLanguageSchema as lessonLanguageValueSchema } from "./language"
|
import { lessonLanguageSchema as lessonLanguageValueSchema } from "./language"
|
||||||
|
import { statisticInfoSchema } from "./submission"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 「班里在做」:同班同一天有好几个人做的题,基本就是老师在课上点名的那几道。
|
* 「班里在做」:同班同一天有好几个人做的题,基本就是老师在课上点名的那几道。
|
||||||
@@ -105,6 +106,21 @@ export const classBoardCellSchema = z.object({
|
|||||||
flowchartAttempts: z.number().int(),
|
flowchartAttempts: z.number().int(),
|
||||||
/** 第一次通过的时刻;今天之前通过的,前端显示成「之前」 */
|
/** 第一次通过的时刻;今天之前通过的,前端显示成「之前」 */
|
||||||
acceptedAt: z.string().nullable(),
|
acceptedAt: z.string().nullable(),
|
||||||
|
/**
|
||||||
|
* 还没做对时,今天最后一次**代码**提交:点格子打开它讲错误,题卡片下面按错法归堆。
|
||||||
|
* 做对了、今天没交代码(只画了流程图)的为 null。
|
||||||
|
*
|
||||||
|
* 只带归类要用的那几样(runtime_error / sample_check 和提交里存的同一份),
|
||||||
|
* 不带代码和原始报错 —— 一班四五十人,真要看代码的时候按 submissionId 再拉
|
||||||
|
*/
|
||||||
|
latest: z
|
||||||
|
.object({
|
||||||
|
submissionId: z.string(),
|
||||||
|
result: judgeStatusSchema,
|
||||||
|
runtimeError: statisticInfoSchema.shape.runtime_error,
|
||||||
|
sampleCheck: statisticInfoSchema.shape.sample_check,
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const classBoardStudentSchema = z.object({
|
export const classBoardStudentSchema = z.object({
|
||||||
|
|||||||
Reference in new issue
Block a user