feat(课堂看板): 「这节课」那排题号后面写上几点布置的,之后改题不变
Deploy / deploy (push) Canceled after 0s

class_lesson 加 created_at(0034,老数据用 updated_at 回填),「改」只动 updated_at;
清掉再布置算重新开始。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-13 10:21:39 +08:00
1 parent 5c1f1d2bf8
commit 5c9d0ca501
7 files changed
+3887 -4

No files matched your search

@@ -0,0 +1,4 @@
ALTER TABLE "class_lesson" ADD COLUMN "created_at" timestamp with time zone DEFAULT now();--> statement-breakpoint
-- 加这一列之前布置的:只剩最后一次改的时刻,拿它当布置时刻
UPDATE "class_lesson" SET "created_at" = "updated_at";--> statement-breakpoint
ALTER TABLE "class_lesson" ALTER COLUMN "created_at" SET NOT NULL;
File diff suppressed because it is too large. Load diff
+7
View File
@@ -239,6 +239,13 @@
"when": 1791823809856,
"tag": "0033_drop_message",
"breakpoints": true
},
{
"idx": 34,
"version": "7",
"when": 1791858070886,
"tag": "0034_add_class_lesson_created_at",
"breakpoints": true
}
]
}
+11
View File
@@ -1433,6 +1433,17 @@ export const classLesson = pgTable(
*/
language: text().$type<LessonLanguage>(),
createdBy: integer("created_by").notNull(),
/**
* 这天第一次布置的时刻,之后「改」只动 updatedAt。清掉(删行)再布置算重新开始。
* 加这一列之前布置的用 updated_at 回填(0034)。带默认值是给共库的另一个站点:
* 机房那套还没升级时,它的旧代码插入不带这一列
*/
createdAt: timestamp("created_at", {
withTimezone: true,
mode: "string",
})
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", {
withTimezone: true,
mode: "string",
+20 -4
View File
@@ -185,14 +185,18 @@ async function visibleProblems(ids: number[]) {
return new Map(rows.map((row) => [row.id, row]))
}
/** 老师给这个班今天布置的题(problem.id,按输入顺序)和选的语言;没布置为 null */
/** 老师给这个班今天布置的题(problem.id,按输入顺序)、选的语言和布置时刻;没布置为 null */
async function lessonPlan(className: string, day: string) {
const [row] = await db
.select({ problemIds: schema.classLesson.problemIds, language: schema.classLesson.language })
.select({
problemIds: schema.classLesson.problemIds,
language: schema.classLesson.language,
createdAt: schema.classLesson.createdAt,
})
.from(schema.classLesson)
.where(and(eq(schema.classLesson.className, className), eq(schema.classLesson.day, day)))
.limit(1)
return row?.problemIds.length ? { problemIds: row.problemIds, language: row.language } : null
return row?.problemIds.length ? row : null
}
/**
@@ -217,6 +221,7 @@ async function classLessonProblems(className: string, lookbackDays: number) {
source: "teacher" as const,
day: today,
language: plan.language ?? null,
assignedAt: plan.createdAt,
problems: planned.flatMap((id) => {
const problem = problems.get(id)
if (!problem) return []
@@ -248,6 +253,7 @@ async function classLessonProblems(className: string, lookbackDays: number) {
day: picked.length ? latest : null,
// 推断出来的课不知道老师要的是哪种语言,不限
language: null,
assignedAt: null,
problems: picked.flatMap((row) => {
const problem = problems.get(row.problemId)
return problem
@@ -503,6 +509,7 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
day,
source: null,
language: null,
assignedAt: null,
lastLanguage: null,
problems: [],
students: [],
@@ -707,6 +714,7 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
day,
source: lesson.source,
language: lesson.language,
assignedAt: lesson.assignedAt,
lastLanguage: last?.language ?? null,
problems: lesson.problems.map((problem) => ({
problemId: problem.id,
@@ -848,7 +856,15 @@ classroomRoutes.put("/classroom/lesson", requireTeacher, async (c) => {
const now = new Date().toISOString()
await db
.insert(schema.classLesson)
.values({ className, day, problemIds, language, createdBy: user.id, updatedAt: now })
.values({
className,
day,
problemIds,
language,
createdBy: user.id,
createdAt: now,
updatedAt: now,
})
.onConflictDoUpdate({
target: [schema.classLesson.className, schema.classLesson.day],
set: { problemIds, language, createdBy: user.id, updatedAt: now },
+11
View File
@@ -485,6 +485,9 @@ function shortTitle(title: string, size: number) {
<span v-for="problem in board.problems" :key="problem.problemId" class="chip">
{{ problem.problemDisplayId }}
</span>
<span v-if="board.assignedAt" class="assigned">
{{ parseTime(board.assignedAt, "HH:mm") }} 布置
</span>
<n-button text class="edit" @click="startEdit">
<template #icon><Icon icon="ph:pencil-simple" /></template>
{{ board.source === "teacher" ? "改" : "布置" }}
@@ -854,6 +857,14 @@ function shortTitle(title: string, size: number) {
font-weight: 600;
}
.assigned {
margin-left: 4px;
font-size: 14px;
font-variant-numeric: tabular-nums;
color: v-bind("theme.textColor3");
white-space: nowrap;
}
.edit {
margin-left: 4px;
font-size: 14px;
+2
View File
@@ -151,6 +151,8 @@ export const classBoardSchema = z.object({
source: z.enum(["teacher", "inferred"]).nullable(),
/** 今天布置的语言(见 lessonLanguageSchema) */
language: lessonLanguageSchema,
/** 今天第一次布置的时刻,之后改题不变;推断出来的课为 null */
assignedAt: z.string().nullable(),
/** 这个班最近一次布置选的语言(含今天),给语言下拉当默认值;从没选过为 null */
lastLanguage: lessonLanguageSchema,
problems: z.array(classBoardProblemSchema),