feat(首页): 新接口给老师首页列出今天在上课的每个班——这节课的题、几人交过、全做对几人、最后一次交题的时刻

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-13 14:53:38 +08:00
1 parent 46b043999b
commit c8c38553ec
2 files changed
+126

No files matched your search

+95
View File
@@ -3,6 +3,7 @@ import {
STUDENT_ROLES,
classLessonRequestSchema,
type ClassActivity,
type ClassToday,
type ClassBoard,
type ClassBoardStudent,
type ClassLesson,
@@ -762,6 +763,100 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
} satisfies ClassBoard)
})
/**
* 老师首页「今天的课」:今天在上课的每个班一行。在上课 = 同班 ≥5 人今天一起交过题(和「班里在做」
* 同一个门槛),或者老师今天在看板上布置过题(刚布置、还没人交也要列出来)。每个班的题和人数
* 和看板同一套取法(classLessonProblems),「全做对」看这几道题做没做对过,不限今天
*/
classroomRoutes.get("/classroom/today", requireTeacher, async (c) => {
const start = dayStart()
const today = calendarDay()
const activity = activityRows({ since: start })
const users = sql<number>`count(distinct ${activity.userId})::int`
const lastAt = sql<string>`max(${activity.createTime})`
const [active, lessons] = await Promise.all([
db
.select({ className: schema.user.className, users, lastAt })
.from(activity)
.innerJoin(schema.user, eq(schema.user.id, activity.userId))
.where(
and(
isNotNull(schema.user.className),
eq(schema.user.isDisabled, false),
inArray(schema.user.adminType, [...STUDENT_ROLES]),
),
)
.groupBy(schema.user.className),
db
.select({ className: schema.classLesson.className })
.from(schema.classLesson)
.where(eq(schema.classLesson.day, today)),
])
const activeByClass = new Map(
active
.filter((row) => row.className && row.users >= CLASS_ACTIVITY_MIN_USERS)
.map((row) => [row.className!, row]),
)
const names = [...new Set([...activeByClass.keys(), ...lessons.map((row) => row.className)])]
const classes = await Promise.all(
names.map(async (className) => {
const [lesson, [roster]] = await Promise.all([
classLessonProblems(className, 1),
db
.select({ value: sql<number>`count(*)::int` })
.from(schema.user)
.where(
and(
eq(schema.user.className, className),
eq(schema.user.isDisabled, false),
inArray(schema.user.adminType, [...STUDENT_ROLES]),
),
),
])
const ids = lesson.problems.map((problem) => problem.id)
let allDoneCount = 0
if (ids.length) {
// 做没做对不限今天(和看板的格子一样:以前做对的也算做完),语言跟着布置走
const done = activityRows({
since: new Date(0).toISOString(),
users: classMemberIds(className),
problemIds: ids,
language: lesson.language,
})
const rows = await db
.select({
userId: done.userId,
solved: sql<number>`count(distinct ${done.problemId})::int`,
})
.from(done)
.where(sql`${done.solved}`)
.groupBy(done.userId)
allDoneCount = rows.filter((row) => row.solved >= ids.length).length
}
const today = activeByClass.get(className)
return {
className,
source: lesson.source,
assignedAt: lesson.assignedAt,
problems: lesson.problems.map((problem) => ({
problemDisplayId: problem.displayId,
title: problem.title,
userCount: problem.userCount,
acceptedCount: problem.acceptedCount,
})),
activeCount: today?.users ?? 0,
rosterCount: roster?.value ?? 0,
allDoneCount,
lastSubmitAt: today?.lastAt ? new Date(today.lastAt).toISOString() : null,
}
}),
)
// 正在交的在前,只布置了还没人交的放最后
classes.sort((a, b) => (b.lastSubmitAt ?? "").localeCompare(a.lastSubmitAt ?? ""))
return success(c, { classes } satisfies ClassToday)
})
/**
* 提交列表「这节课」按钮:这个班这节课是哪几道题,取法同课堂看板(老师布置的优先,
* 否则按今天同班提交推断)。不传班级就猜最近两小时在交题的班
+31
View File
@@ -185,7 +185,38 @@ export const classLessonSchema = z.object({
problems: z.array(z.object({ problemDisplayId: z.string(), title: z.string() })),
})
/**
* 老师首页「今天的课」:今天在上课的每个班一行(同班 ≥5 人一起交题,或者老师在看板上布置过)。
* 题目的人数是今天的(和看板同一套取法),「全做对」看的是这几道题有没有做对过,不限今天
*/
export const classTodaySchema = z.object({
classes: z.array(
z.object({
className: z.string(),
source: z.enum(["teacher", "inferred"]).nullable(),
/** 老师布置的时刻;推断出来的为 null */
assignedAt: z.string().nullable(),
problems: z.array(
z.object({
problemDisplayId: z.string(),
title: z.string(),
userCount: z.number().int(),
acceptedCount: z.number().int(),
}),
),
/** 今天交过题的人数 / 全班人数 */
activeCount: z.number().int(),
rosterCount: z.number().int(),
/** 这节课的题全做对了的人数 */
allDoneCount: z.number().int(),
/** 今天最后一次有人交题;还没人交(只布置了)为 null */
lastSubmitAt: z.string().nullable(),
}),
),
})
export type ClassActivity = z.infer<typeof classActivitySchema>
export type ClassToday = z.infer<typeof classTodaySchema>
export type LastVisit = z.infer<typeof lastVisitSchema>
export type ClassLessonRequest = z.infer<typeof classLessonRequestSchema>
export type ClassBoard = z.infer<typeof classBoardSchema>