feat(课堂): 布置这节课的题时可以选用什么语言做,学生用别的语言交对不算这份作业做完
class_lesson 加一列 language(0024,可空:以前布置的、按提交推断出来的课都不限语言)。 布置时有题不支持选的语言就整个拒掉并点名是哪几道;看板的格子、学生首页的做完状态、 提交列表「这节课」都只认这个语言的代码提交,流程图照旧算。看板多给 lastLanguage 当默认值。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e3be3cd62c
commit
a18e129f4c
6 files changed
+4218
-19
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "class_lesson" ADD COLUMN "language" text;
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -169,6 +169,13 @@
|
|||||||
"when": 1790519287984,
|
"when": 1790519287984,
|
||||||
"tag": "0023_add_class_lesson",
|
"tag": "0023_add_class_lesson",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 24,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1790732755406,
|
||||||
|
"tag": "0024_add_class_lesson_language",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -50,6 +50,7 @@ import type {
|
|||||||
ProblemSetDifficulty,
|
ProblemSetDifficulty,
|
||||||
ProblemSetStatus,
|
ProblemSetStatus,
|
||||||
ReactionKey,
|
ReactionKey,
|
||||||
|
RunnableLanguage,
|
||||||
SqlConfig,
|
SqlConfig,
|
||||||
SqlDisplay,
|
SqlDisplay,
|
||||||
TagCategory,
|
TagCategory,
|
||||||
@@ -1477,6 +1478,11 @@ export const classLesson = pgTable(
|
|||||||
className: text("class_name").notNull(),
|
className: text("class_name").notNull(),
|
||||||
day: text().notNull(),
|
day: text().notNull(),
|
||||||
problemIds: jsonb("problem_ids").notNull().$type<number[]>(),
|
problemIds: jsonb("problem_ids").notNull().$type<number[]>(),
|
||||||
|
/**
|
||||||
|
* 这份作业用什么语言做(Python / C / C++),老师布置时选。可空:加这一列之前布置的
|
||||||
|
* 没有它。写入侧在 PUT /classroom/lesson 按 runnableLanguageSchema 校验过
|
||||||
|
*/
|
||||||
|
language: text().$type<RunnableLanguage>(),
|
||||||
createdBy: integer("created_by").notNull(),
|
createdBy: integer("created_by").notNull(),
|
||||||
updatedAt: timestamp("updated_at", {
|
updatedAt: timestamp("updated_at", {
|
||||||
withTimezone: true,
|
withTimezone: true,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
type ClassRankItem,
|
type ClassRankItem,
|
||||||
type ClassUserRank,
|
type ClassUserRank,
|
||||||
type LastVisit,
|
type LastVisit,
|
||||||
|
type RunnableLanguage,
|
||||||
FLOWCHART_PASS_GRADES,
|
FLOWCHART_PASS_GRADES,
|
||||||
} from "@oj2/contract"
|
} from "@oj2/contract"
|
||||||
import {
|
import {
|
||||||
@@ -224,8 +225,10 @@ function activityRows(options: {
|
|||||||
since: string
|
since: string
|
||||||
users?: number[] | ReturnType<typeof classMemberIds>
|
users?: number[] | ReturnType<typeof classMemberIds>
|
||||||
problemIds?: number[]
|
problemIds?: number[]
|
||||||
|
/** 老师布置时选了语言:代码只数这个语言交的,流程图不受影响 */
|
||||||
|
language?: RunnableLanguage | null
|
||||||
}) {
|
}) {
|
||||||
const { since, users, problemIds } = options
|
const { since, users, problemIds, language } = options
|
||||||
const code = db
|
const code = db
|
||||||
.select({
|
.select({
|
||||||
userId: schema.submission.userId,
|
userId: schema.submission.userId,
|
||||||
@@ -240,6 +243,7 @@ function activityRows(options: {
|
|||||||
gte(schema.submission.createTime, since),
|
gte(schema.submission.createTime, since),
|
||||||
users ? inArray(schema.submission.userId, users) : undefined,
|
users ? inArray(schema.submission.userId, users) : undefined,
|
||||||
problemIds ? inArray(schema.submission.problemId, problemIds) : undefined,
|
problemIds ? inArray(schema.submission.problemId, problemIds) : undefined,
|
||||||
|
language ? eq(schema.submission.language, language) : undefined,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const flowchart = db
|
const flowchart = db
|
||||||
@@ -268,12 +272,13 @@ function activityRows(options: {
|
|||||||
async function classDayGroups(
|
async function classDayGroups(
|
||||||
className: string,
|
className: string,
|
||||||
since: string,
|
since: string,
|
||||||
options: { minUsers?: number; problemIds?: number[] } = {},
|
options: { minUsers?: number; problemIds?: number[]; language?: RunnableLanguage | null } = {},
|
||||||
): Promise<ClassDayGroup[]> {
|
): Promise<ClassDayGroup[]> {
|
||||||
const activity = activityRows({
|
const activity = activityRows({
|
||||||
since,
|
since,
|
||||||
users: classMemberIds(className),
|
users: classMemberIds(className),
|
||||||
problemIds: options.problemIds,
|
problemIds: options.problemIds,
|
||||||
|
language: options.language,
|
||||||
})
|
})
|
||||||
const day = sql<string>`to_char(${localTime(activity.createTime)}, 'YYYY-MM-DD')`
|
const day = sql<string>`to_char(${localTime(activity.createTime)}, 'YYYY-MM-DD')`
|
||||||
const userCount = sql<number>`count(distinct ${activity.userId})::int`
|
const userCount = sql<number>`count(distinct ${activity.userId})::int`
|
||||||
@@ -310,14 +315,14 @@ async function visibleProblems(ids: number[]) {
|
|||||||
return new Map(rows.map((row) => [row.id, row]))
|
return new Map(rows.map((row) => [row.id, row]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 老师给这个班今天布置的题(problem.id,按输入顺序);没布置为 null */
|
/** 老师给这个班今天布置的题(problem.id,按输入顺序)和选的语言;没布置为 null */
|
||||||
async function lessonProblemIds(className: string, day: string) {
|
async function lessonPlan(className: string, day: string) {
|
||||||
const [row] = await db
|
const [row] = await db
|
||||||
.select({ problemIds: schema.classLesson.problemIds })
|
.select({ problemIds: schema.classLesson.problemIds, language: schema.classLesson.language })
|
||||||
.from(schema.classLesson)
|
.from(schema.classLesson)
|
||||||
.where(and(eq(schema.classLesson.className, className), eq(schema.classLesson.day, day)))
|
.where(and(eq(schema.classLesson.className, className), eq(schema.classLesson.day, day)))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
return row?.problemIds.length ? row.problemIds : null
|
return row?.problemIds.length ? { problemIds: row.problemIds, language: row.language } : null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -330,16 +335,18 @@ async function lessonProblemIds(className: string, day: string) {
|
|||||||
*/
|
*/
|
||||||
async function classLessonProblems(className: string, lookbackDays: number) {
|
async function classLessonProblems(className: string, lookbackDays: number) {
|
||||||
const today = calendarDay()
|
const today = calendarDay()
|
||||||
const planned = await lessonProblemIds(className, today)
|
const plan = await lessonPlan(className, today)
|
||||||
if (planned) {
|
if (plan) {
|
||||||
|
const planned = plan.problemIds
|
||||||
const [problems, groups] = await Promise.all([
|
const [problems, groups] = await Promise.all([
|
||||||
visibleProblems(planned),
|
visibleProblems(planned),
|
||||||
classDayGroups(className, dayStart(), { problemIds: planned }),
|
classDayGroups(className, dayStart(), { problemIds: planned, language: plan.language }),
|
||||||
])
|
])
|
||||||
const groupById = new Map(groups.map((row) => [row.problemId, row]))
|
const groupById = new Map(groups.map((row) => [row.problemId, row]))
|
||||||
return {
|
return {
|
||||||
source: "teacher" as const,
|
source: "teacher" as const,
|
||||||
day: today,
|
day: today,
|
||||||
|
language: plan.language ?? null,
|
||||||
problems: planned.flatMap((id) => {
|
problems: planned.flatMap((id) => {
|
||||||
const problem = problems.get(id)
|
const problem = problems.get(id)
|
||||||
if (!problem) return []
|
if (!problem) return []
|
||||||
@@ -369,6 +376,8 @@ async function classLessonProblems(className: string, lookbackDays: number) {
|
|||||||
return {
|
return {
|
||||||
source: picked.length ? ("inferred" as const) : null,
|
source: picked.length ? ("inferred" as const) : null,
|
||||||
day: picked.length ? latest : null,
|
day: picked.length ? latest : null,
|
||||||
|
// 推断出来的课不知道老师要的是哪种语言,不限
|
||||||
|
language: null,
|
||||||
problems: picked.flatMap((row) => {
|
problems: picked.flatMap((row) => {
|
||||||
const problem = problems.get(row.problemId)
|
const problem = problems.get(row.problemId)
|
||||||
return problem
|
return problem
|
||||||
@@ -502,6 +511,7 @@ classroomRoutes.get("/me/class-activity", requireAuth, async (c) => {
|
|||||||
className: null,
|
className: null,
|
||||||
day: null,
|
day: null,
|
||||||
source: null,
|
source: null,
|
||||||
|
language: null,
|
||||||
problems: [],
|
problems: [],
|
||||||
} satisfies ClassActivity)
|
} satisfies ClassActivity)
|
||||||
}
|
}
|
||||||
@@ -522,6 +532,8 @@ classroomRoutes.get("/me/class-activity", requireAuth, async (c) => {
|
|||||||
eq(schema.submission.userId, user.id),
|
eq(schema.submission.userId, user.id),
|
||||||
inArray(schema.submission.problemId, ids),
|
inArray(schema.submission.problemId, ids),
|
||||||
isNull(schema.submission.contestId),
|
isNull(schema.submission.contestId),
|
||||||
|
// 布置的是 C:以前用 Python 做对过不算这份作业做完
|
||||||
|
lesson.language ? eq(schema.submission.language, lesson.language) : undefined,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.groupBy(schema.submission.problemId)
|
.groupBy(schema.submission.problemId)
|
||||||
@@ -554,6 +566,7 @@ classroomRoutes.get("/me/class-activity", requireAuth, async (c) => {
|
|||||||
className: user.className,
|
className: user.className,
|
||||||
day: lesson.day,
|
day: lesson.day,
|
||||||
source: lesson.source,
|
source: lesson.source,
|
||||||
|
language: lesson.language,
|
||||||
problems: lesson.problems.map((problem) => {
|
problems: lesson.problems.map((problem) => {
|
||||||
const accepted = mineById.get(problem.id)
|
const accepted = mineById.get(problem.id)
|
||||||
return {
|
return {
|
||||||
@@ -619,14 +632,27 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
|
|||||||
className: null,
|
className: null,
|
||||||
day,
|
day,
|
||||||
source: null,
|
source: null,
|
||||||
|
language: null,
|
||||||
|
lastLanguage: null,
|
||||||
problems: [],
|
problems: [],
|
||||||
students: [],
|
students: [],
|
||||||
recentLessons: 0,
|
recentLessons: 0,
|
||||||
} satisfies ClassBoard)
|
} satisfies ClassBoard)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 推断只看今天(回看 1 天):看板是给这节课用的,昨天的题不该冒出来
|
// 推断只看今天(回看 1 天):看板是给这节课用的,昨天的题不该冒出来。
|
||||||
const lesson = await classLessonProblems(className, 1)
|
// lastLanguage 给语言下拉当默认值:这个班上一次布置选的什么(一个班一般一学期只学一种)
|
||||||
|
const [lesson, [last]] = await Promise.all([
|
||||||
|
classLessonProblems(className, 1),
|
||||||
|
db
|
||||||
|
.select({ language: schema.classLesson.language })
|
||||||
|
.from(schema.classLesson)
|
||||||
|
.where(
|
||||||
|
and(eq(schema.classLesson.className, className), isNotNull(schema.classLesson.language)),
|
||||||
|
)
|
||||||
|
.orderBy(desc(schema.classLesson.day))
|
||||||
|
.limit(1),
|
||||||
|
])
|
||||||
const ids = lesson.problems.map((problem) => problem.id)
|
const ids = lesson.problems.map((problem) => problem.id)
|
||||||
const start = dayStart()
|
const start = dayStart()
|
||||||
|
|
||||||
@@ -680,6 +706,7 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
|
|||||||
isNull(schema.submission.contestId),
|
isNull(schema.submission.contestId),
|
||||||
inArray(schema.submission.userId, userIds),
|
inArray(schema.submission.userId, userIds),
|
||||||
inArray(schema.submission.problemId, ids),
|
inArray(schema.submission.problemId, ids),
|
||||||
|
lesson.language ? eq(schema.submission.language, lesson.language) : undefined,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.groupBy(schema.submission.userId, schema.submission.problemId)
|
.groupBy(schema.submission.userId, schema.submission.problemId)
|
||||||
@@ -774,6 +801,8 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
|
|||||||
className,
|
className,
|
||||||
day,
|
day,
|
||||||
source: lesson.source,
|
source: lesson.source,
|
||||||
|
language: lesson.language,
|
||||||
|
lastLanguage: last?.language ?? null,
|
||||||
problems: lesson.problems.map((problem) => ({
|
problems: lesson.problems.map((problem) => ({
|
||||||
problemId: problem.id,
|
problemId: problem.id,
|
||||||
problemDisplayId: problem.displayId,
|
problemDisplayId: problem.displayId,
|
||||||
@@ -815,12 +844,18 @@ classroomRoutes.get("/classroom/board", requireTeacher, async (c) => {
|
|||||||
classroomRoutes.get("/classroom/lesson", requireTeacher, async (c) => {
|
classroomRoutes.get("/classroom/lesson", requireTeacher, async (c) => {
|
||||||
const className = c.req.query("className")?.trim() || (await suggestActiveClass())
|
const className = c.req.query("className")?.trim() || (await suggestActiveClass())
|
||||||
if (!className)
|
if (!className)
|
||||||
return success(c, { className: null, source: null, problems: [] } satisfies ClassLesson)
|
return success(c, {
|
||||||
|
className: null,
|
||||||
|
source: null,
|
||||||
|
language: null,
|
||||||
|
problems: [],
|
||||||
|
} satisfies ClassLesson)
|
||||||
// 和看板一样只回看今天:「这节课」不该冒出昨天的题
|
// 和看板一样只回看今天:「这节课」不该冒出昨天的题
|
||||||
const lesson = await classLessonProblems(className, 1)
|
const lesson = await classLessonProblems(className, 1)
|
||||||
return success(c, {
|
return success(c, {
|
||||||
className,
|
className,
|
||||||
source: lesson.source,
|
source: lesson.source,
|
||||||
|
language: lesson.language,
|
||||||
problems: lesson.problems.map((problem) => ({
|
problems: lesson.problems.map((problem) => ({
|
||||||
problemDisplayId: problem.displayId,
|
problemDisplayId: problem.displayId,
|
||||||
title: problem.title,
|
title: problem.title,
|
||||||
@@ -834,10 +869,11 @@ classroomRoutes.get("/classroom/lesson", requireTeacher, async (c) => {
|
|||||||
* 学生那边退回推断。
|
* 学生那边退回推断。
|
||||||
*/
|
*/
|
||||||
classroomRoutes.put("/classroom/lesson", requireTeacher, async (c) => {
|
classroomRoutes.put("/classroom/lesson", requireTeacher, async (c) => {
|
||||||
const parsed = await parseBody(c, classLessonRequestSchema, "题号格式不对")
|
const parsed = await parseBody(c, classLessonRequestSchema, "题号或语言不对")
|
||||||
if (!parsed.success) return parsed.response
|
if (!parsed.success) return parsed.response
|
||||||
const user = c.get("user")!
|
const user = c.get("user")!
|
||||||
const { className, problemDisplayIds } = parsed.data
|
const { className, problemDisplayIds } = parsed.data
|
||||||
|
const language = parsed.data.language ?? null
|
||||||
const day = calendarDay()
|
const day = calendarDay()
|
||||||
|
|
||||||
const [exists] = await db
|
const [exists] = await db
|
||||||
@@ -856,7 +892,11 @@ classroomRoutes.put("/classroom/lesson", requireTeacher, async (c) => {
|
|||||||
|
|
||||||
const wanted = [...new Set(problemDisplayIds.map((id) => id.toLowerCase()))]
|
const wanted = [...new Set(problemDisplayIds.map((id) => id.toLowerCase()))]
|
||||||
const found = await db
|
const found = await db
|
||||||
.select({ id: schema.problem.id, displayId: schema.problem.displayId })
|
.select({
|
||||||
|
id: schema.problem.id,
|
||||||
|
displayId: schema.problem.displayId,
|
||||||
|
languages: schema.problem.languages,
|
||||||
|
})
|
||||||
.from(schema.problem)
|
.from(schema.problem)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
@@ -871,14 +911,29 @@ classroomRoutes.put("/classroom/lesson", requireTeacher, async (c) => {
|
|||||||
return failure(c, 400, "problem-not-found", `这些题号不存在或没有公开:${missing.join("、")}`)
|
return failure(c, 400, "problem-not-found", `这些题号不存在或没有公开:${missing.join("、")}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 选了语言就得每道都能用它交,不然学生点开才发现交不了(提交接口会拒:language-not-allowed)
|
||||||
|
if (language) {
|
||||||
|
const unsupported = found
|
||||||
|
.filter((row) => !row.languages.includes(language))
|
||||||
|
.map((row) => row.displayId)
|
||||||
|
if (unsupported.length) {
|
||||||
|
return failure(
|
||||||
|
c,
|
||||||
|
400,
|
||||||
|
"language-not-allowed",
|
||||||
|
`这些题不能用 ${language} 交:${unsupported.join("、")}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const problemIds = wanted.map((id) => idByDisplay.get(id)!)
|
const problemIds = wanted.map((id) => idByDisplay.get(id)!)
|
||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
await db
|
await db
|
||||||
.insert(schema.classLesson)
|
.insert(schema.classLesson)
|
||||||
.values({ className, day, problemIds, createdBy: user.id, updatedAt: now })
|
.values({ className, day, problemIds, language, createdBy: user.id, updatedAt: now })
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: [schema.classLesson.className, schema.classLesson.day],
|
target: [schema.classLesson.className, schema.classLesson.day],
|
||||||
set: { problemIds, createdBy: user.id, updatedAt: now },
|
set: { problemIds, language, createdBy: user.id, updatedAt: now },
|
||||||
})
|
})
|
||||||
return success(c, null)
|
return success(c, null)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { judgeStatusSchema } from "./judge-status"
|
import { judgeStatusSchema } from "./judge-status"
|
||||||
|
import { runnableLanguageSchema } from "./language"
|
||||||
|
|
||||||
export const classRankItemSchema = z.object({
|
export const classRankItemSchema = z.object({
|
||||||
className: z.string(),
|
className: z.string(),
|
||||||
@@ -76,15 +77,25 @@ export const classActivityProblemSchema = z.object({
|
|||||||
userCount: z.number().int(),
|
userCount: z.number().int(),
|
||||||
/** 其中那天通过了的人数 */
|
/** 其中那天通过了的人数 */
|
||||||
acceptedCount: z.number().int(),
|
acceptedCount: z.number().int(),
|
||||||
/** 我自己在题库里(不含比赛)做这道题的状态,不限那一天 */
|
/**
|
||||||
|
* 我自己在题库里(不含比赛)做这道题的状态,不限那一天。老师布置时选了语言的,
|
||||||
|
* 代码只认用那个语言交的(流程图照旧算)
|
||||||
|
*/
|
||||||
myStatus: z.enum(["accepted", "tried", "none"]),
|
myStatus: z.enum(["accepted", "tried", "none"]),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 老师布置这节课时选的语言(Python / C / C++)。null = 没选:推断出来的课、加这一项之前
|
||||||
|
* 布置的课。选了之后这份作业「做完」只认这个语言交对的,学生打开这几道题编辑器默认就是它
|
||||||
|
*/
|
||||||
|
const lessonLanguageSchema = runnableLanguageSchema.nullable()
|
||||||
|
|
||||||
export const classActivitySchema = z.object({
|
export const classActivitySchema = z.object({
|
||||||
className: z.string().nullable(),
|
className: z.string().nullable(),
|
||||||
day: z.string().nullable(),
|
day: z.string().nullable(),
|
||||||
/** teacher = 老师在课堂看板里布置的;inferred = 从同班提交记录推断的 */
|
/** teacher = 老师在课堂看板里布置的;inferred = 从同班提交记录推断的 */
|
||||||
source: z.enum(["teacher", "inferred"]).nullable(),
|
source: z.enum(["teacher", "inferred"]).nullable(),
|
||||||
|
language: lessonLanguageSchema,
|
||||||
problems: z.array(classActivityProblemSchema),
|
problems: z.array(classActivityProblemSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -122,10 +133,14 @@ export const lastVisitSchema = z.object({
|
|||||||
.nullable(),
|
.nullable(),
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 课堂看板里老师布置这节课的题:按输入顺序给展示题号,最多 20 道,空数组 = 清掉 */
|
/**
|
||||||
|
* 课堂看板里老师布置这节课的题:按输入顺序给展示题号,最多 20 道,空数组 = 清掉。
|
||||||
|
* `language` 是这份作业用什么语言做;不传当 null(上线时还开着的旧看板页面不带它)
|
||||||
|
*/
|
||||||
export const classLessonRequestSchema = z.object({
|
export const classLessonRequestSchema = z.object({
|
||||||
className: z.string().trim().min(1),
|
className: z.string().trim().min(1),
|
||||||
problemDisplayIds: z.array(z.string().trim().min(1)).max(20),
|
problemDisplayIds: z.array(z.string().trim().min(1)).max(20),
|
||||||
|
language: lessonLanguageSchema.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const classBoardProblemSchema = z.object({
|
export const classBoardProblemSchema = z.object({
|
||||||
@@ -135,7 +150,10 @@ export const classBoardProblemSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const classBoardCellSchema = z.object({
|
export const classBoardCellSchema = z.object({
|
||||||
/** accepted 看的是题库里(不含比赛)有没有通过过,不限今天 —— 以前做过的也算做完 */
|
/**
|
||||||
|
* accepted 看的是题库里(不含比赛)有没有通过过,不限今天 —— 以前做过的也算做完。
|
||||||
|
* 布置时选了语言的,代码的次数和通过都只数那个语言(流程图照旧)
|
||||||
|
*/
|
||||||
status: z.enum(["accepted", "tried", "none"]),
|
status: z.enum(["accepted", "tried", "none"]),
|
||||||
/** 今天在这道题上交了几次(代码 + 流程图) */
|
/** 今天在这道题上交了几次(代码 + 流程图) */
|
||||||
attempts: z.number().int(),
|
attempts: z.number().int(),
|
||||||
@@ -175,6 +193,10 @@ export const classBoardSchema = z.object({
|
|||||||
className: z.string().nullable(),
|
className: z.string().nullable(),
|
||||||
day: z.string(),
|
day: z.string(),
|
||||||
source: z.enum(["teacher", "inferred"]).nullable(),
|
source: z.enum(["teacher", "inferred"]).nullable(),
|
||||||
|
/** 今天布置的语言(见 lessonLanguageSchema) */
|
||||||
|
language: lessonLanguageSchema,
|
||||||
|
/** 这个班最近一次布置选的语言(含今天),给语言下拉当默认值;从没选过为 null */
|
||||||
|
lastLanguage: lessonLanguageSchema,
|
||||||
problems: z.array(classBoardProblemSchema),
|
problems: z.array(classBoardProblemSchema),
|
||||||
students: z.array(classBoardStudentSchema),
|
students: z.array(classBoardStudentSchema),
|
||||||
/**
|
/**
|
||||||
@@ -192,6 +214,8 @@ export const classBoardSchema = z.object({
|
|||||||
export const classLessonSchema = z.object({
|
export const classLessonSchema = z.object({
|
||||||
className: z.string().nullable(),
|
className: z.string().nullable(),
|
||||||
source: z.enum(["teacher", "inferred"]).nullable(),
|
source: z.enum(["teacher", "inferred"]).nullable(),
|
||||||
|
/** 选了语言时,列表那边连语言一起筛 */
|
||||||
|
language: lessonLanguageSchema,
|
||||||
problems: z.array(z.object({ problemDisplayId: z.string(), title: z.string() })),
|
problems: z.array(z.object({ problemDisplayId: z.string(), title: z.string() })),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user