- 界面上的「课堂看板」「课堂作业」统一叫「课堂任务」,顶栏、老师首页、后台菜单一起改 - 前端 oj/classroom → oj/task、oj/class → oj/pk,两个名字不再只差几个字母却毫不相干;旧地址不留跳转 - 后端 routes/classroom.ts 拆开:课堂任务的接口进 routes/task.ts(/task/board|lesson|today), 首页的「上次来」「班里在做」进 routes/home.ts;共用的取题口径挪进 services/lesson.ts - 契约 classroom.ts 同样拆成 task.ts / home.ts,ClassBoard* 改叫 TaskBoard* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
4.6 KiB
TypeScript
85 lines
4.6 KiB
TypeScript
import { z } from 'zod'
|
||
|
||
/**
|
||
* 判题沙箱认得的语言。**这是全仓唯一的语言集合定义处。**
|
||
*
|
||
* 键名必须与 `apps/api/src/judge/languages.ts` 的 `languageConfigs` 一致 ——
|
||
* 沙箱的编译/运行命令按这些键查表,查不到就是 `Unsupported judge language`。
|
||
* 后端那边是 `Record<string, …>`(判题机要按名字取配置,不能窄化成联合),
|
||
* 所以这个联合是**手写**的,改 languages.ts 时两边一起改。
|
||
*
|
||
* **这里列的是历史上出现过的值,不等于现在能提交的语言。** 现在判题沙箱只认
|
||
* `C` / `C++` / `Python`(`languages.ts` 里就这三块),前端的语言复选框也只给这三个
|
||
* 加 SQL。`Java`(44 条) / `Golang`(15) / `JavaScript`(3) 是历史值:判题机没有它们的
|
||
* 编译配置、镜像里也没有工具链了,但生产库里有这些提交记录,提交列表要能渲染出来,
|
||
* 所以键必须留着。
|
||
*
|
||
* Java / JavaScript / Golang 是 2026-09 随判题镜像升级一起下掉的,理由见
|
||
* `apps/api/src/judge/languages.ts` 顶部。
|
||
*
|
||
* `Python2` / `Python3` 这两个旧值**已经不在这里了** —— 0019 迁移把库里的
|
||
* 104530 条提交、937 道题、1235 个用户的成就指标全并成了 `Python`。
|
||
*/
|
||
export const judgeLanguageSchema = z.enum(['C', 'C++', 'Python', 'Java', 'JavaScript', 'Golang'])
|
||
|
||
/**
|
||
* 题目可以挂的语言 = 沙箱语言 + 两种非沙箱题型。
|
||
*
|
||
* - `SQL` 走 `judge/sql/` 那条独立链路(子进程 + sql.js),不经过沙箱;
|
||
* - `Flowchart` 走 AI 评分(flowchart/run.ts),也不经过沙箱。
|
||
*
|
||
* **两者都是真实可选、真实有历史数据的**,不是预留值:生产库 961 道题里有
|
||
* 9 道 SQL 题、124191 条提交里有 91 条 SQL。前端原来手抄的语言联合漏了 SQL,
|
||
* 于是那 91 条提交的 `language` 在类型上是 `undefined` —— 这就是两份真相
|
||
* 各自演进的代价,现在并成一份。
|
||
*/
|
||
export const problemLanguageSchema = z.enum([...judgeLanguageSchema.options, 'SQL', 'Flowchart'])
|
||
|
||
/**
|
||
* 判题沙箱**现在**跑得了的语言,就是 `apps/api/src/judge/languages.ts` 里那三块。
|
||
* 试运行(`POST /trial-runs`)只收这几个:历史语言交上来只会在判题机那边报系统错误。
|
||
*/
|
||
export const runnableLanguageSchema = judgeLanguageSchema.extract(['C', 'C++', 'Python'])
|
||
|
||
/**
|
||
* 老师布置任务能选的语言:判题机跑得了的三种,加 SQL(数据库课一整节都是 SQL 题)。
|
||
* SQL 题只收 SQL,所以 C / Python 的作业里夹 SQL 题也行 —— 那几道不受作业语言限制
|
||
*/
|
||
export const lessonLanguageSchema = problemLanguageSchema.extract(['Python', 'C', 'C++', 'SQL'])
|
||
|
||
/**
|
||
* 这节课做什么,老师布置时选(2026-11 加):
|
||
* - `code`:写代码,「做完」只认代码交对(加这一项之前布置的课都是它);
|
||
* - `flowchart`:画流程图,只认画到 A / S,不选语言(language 为 null);
|
||
* - `both`:先画再写,一道题画到 A / S **并且**代码交对才算做完。学生画一道写一道、
|
||
* 全画完再写都行,所以是一节课里的两步,不是两节课。
|
||
*
|
||
* 画流程图的两种只能布置打开了「让学生自己画」的题(problem.allowFlowchart)。
|
||
* 推断出来的课没有它(null):代码交对或流程图画到 A / S 都算做完,和加这一项之前一样
|
||
*/
|
||
export const lessonModeSchema = z.enum(['code', 'flowchart', 'both'])
|
||
export type LessonMode = z.infer<typeof lessonModeSchema>
|
||
export const LESSON_MODE_LABEL: Record<LessonMode, string> = { code: '写代码', flowchart: '画流程图', both: '先画再写' }
|
||
/** 这种课要不要画、要不要写 */
|
||
export function lessonNeeds(mode: LessonMode) {
|
||
return { draw: mode !== 'code', code: mode !== 'flowchart' }
|
||
}
|
||
|
||
/**
|
||
* SQL 题:可选语言里有 SQL(后端保存时强校验 SQL 只能单独出现)。题型只认这一条,
|
||
* 前后端一律走它,别再各自看 sqlConfig 或 languages
|
||
*/
|
||
export function isSqlProblem(problem: { languages: readonly string[] }) {
|
||
return problem.languages.includes('SQL')
|
||
}
|
||
|
||
export type JudgeLanguage = z.infer<typeof judgeLanguageSchema>
|
||
export type LessonLanguage = z.infer<typeof lessonLanguageSchema>
|
||
export type RunnableLanguage = z.infer<typeof runnableLanguageSchema>
|
||
export type ProblemLanguage = z.infer<typeof problemLanguageSchema>
|
||
|
||
/** 认语言名(地址栏、localStorage 里来的);认不出来返回 null,由调用方决定怎么兜底。 */
|
||
export function parseLanguage(value: unknown): ProblemLanguage | null {
|
||
return problemLanguageSchema.safeParse(value).data ?? null
|
||
}
|