默认是这个班上一次布置选的语言。学生那边首页、题号下拉、题目页课堂条都写上「C语言」; 打开这节课的题时编辑器自动切过去,每道题只切一次,学生自己再换就按他的。提交列表的 「这节课」连语言一起筛。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a18e129f4c
commit
773d15b033
7 files changed
+93
-14
No files matched your search
@@ -41,6 +41,7 @@ import {
|
|||||||
type FlowchartStatistics,
|
type FlowchartStatistics,
|
||||||
type SubmissionStatistics,
|
type SubmissionStatistics,
|
||||||
type SubmissionLessons,
|
type SubmissionLessons,
|
||||||
|
type RunnableLanguage,
|
||||||
type SubmissionStatisticsGrid,
|
type SubmissionStatisticsGrid,
|
||||||
type TodaySubmissionStatistics,
|
type TodaySubmissionStatistics,
|
||||||
} from "@oj2/contract"
|
} from "@oj2/contract"
|
||||||
@@ -242,8 +243,12 @@ export function getClassLesson(className?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 给这个班布置今天的题,空数组 = 清掉 */
|
/** 给这个班布置今天的题,空数组 = 清掉 */
|
||||||
export function setClassLesson(className: string, problemDisplayIds: string[]) {
|
export function setClassLesson(
|
||||||
return api.put<null>("classroom/lesson", { className, problemDisplayIds })
|
className: string,
|
||||||
|
problemDisplayIds: string[],
|
||||||
|
language: RunnableLanguage | null,
|
||||||
|
) {
|
||||||
|
return api.put<null>("classroom/lesson", { className, problemDisplayIds, language })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 我的知识点地图(只有自己的,不能查别人) */
|
/** 我的知识点地图(只有自己的,不能查别人) */
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { getClassBoard, setClassLesson } from "oj/api"
|
|||||||
import { useHiddenStudents } from "shared/composables/hiddenStudents"
|
import { useHiddenStudents } from "shared/composables/hiddenStudents"
|
||||||
import { useCollabStore } from "shared/store/collab"
|
import { useCollabStore } from "shared/store/collab"
|
||||||
import { useConfigStore } from "shared/store/config"
|
import { useConfigStore } from "shared/store/config"
|
||||||
|
import type { RunnableLanguage } from "@oj2/contract"
|
||||||
import { errorMessage } from "utils/api"
|
import { errorMessage } from "utils/api"
|
||||||
|
import { LANGUAGE_SHOW_VALUE } from "utils/constants"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import type { ClassBoard, ClassBoardCell, ClassBoardStudent } from "utils/types"
|
import type { ClassBoard, ClassBoardCell, ClassBoardStudent } from "utils/types"
|
||||||
|
|
||||||
@@ -34,6 +36,16 @@ const updatedAt = ref<Date | null>(null)
|
|||||||
const projector = ref(false)
|
const projector = ref(false)
|
||||||
|
|
||||||
const lessonInput = ref("")
|
const lessonInput = ref("")
|
||||||
|
/**
|
||||||
|
* 这份作业用什么语言做,跟着这一次布置走(用户定的:不是给班级定死的设置)。默认是这个班
|
||||||
|
* 上一次布置时选的;选了之后看板和学生那边的「做完」都只认这个语言交对的
|
||||||
|
*/
|
||||||
|
const lessonLanguage = ref<RunnableLanguage>("Python")
|
||||||
|
const LESSON_LANGUAGES: RunnableLanguage[] = ["Python", "C", "C++"]
|
||||||
|
const languageOptions = LESSON_LANGUAGES.map((value) => ({
|
||||||
|
label: LANGUAGE_SHOW_VALUE[value],
|
||||||
|
value,
|
||||||
|
}))
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
|
||||||
function classLabel(name: string) {
|
function classLabel(name: string) {
|
||||||
@@ -65,7 +77,7 @@ async function load() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 换了班要把输入框换成那个班今天布置的题
|
// 换了班要把输入框换成那个班今天布置的题、布置时选的语言(15 秒一次的刷新不动它们)
|
||||||
watch(
|
watch(
|
||||||
() => [board.value?.className, board.value?.source] as const,
|
() => [board.value?.className, board.value?.source] as const,
|
||||||
() => {
|
() => {
|
||||||
@@ -73,6 +85,7 @@ watch(
|
|||||||
board.value?.source === "teacher"
|
board.value?.source === "teacher"
|
||||||
? board.value.problems.map((problem) => problem.problemDisplayId).join(" ")
|
? board.value.problems.map((problem) => problem.problemDisplayId).join(" ")
|
||||||
: ""
|
: ""
|
||||||
|
lessonLanguage.value = board.value?.language ?? board.value?.lastLanguage ?? "Python"
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,8 +99,12 @@ async function saveLesson() {
|
|||||||
const ids = lessonInput.value.split(/[\s,,、;;]+/).filter(Boolean)
|
const ids = lessonInput.value.split(/[\s,,、;;]+/).filter(Boolean)
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await setClassLesson(className.value, ids)
|
await setClassLesson(className.value, ids, ids.length ? lessonLanguage.value : null)
|
||||||
message.success(ids.length ? "已布置,学生首页会显示这几道题" : "已清掉,学生那边改回自动推断")
|
message.success(
|
||||||
|
ids.length
|
||||||
|
? `已布置(${LANGUAGE_SHOW_VALUE[lessonLanguage.value]}),学生首页会显示这几道题`
|
||||||
|
: "已清掉,学生那边改回自动推断",
|
||||||
|
)
|
||||||
await load()
|
await load()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(errorMessage(error))
|
message.error(errorMessage(error))
|
||||||
@@ -241,7 +258,12 @@ function submissionsHref(
|
|||||||
username: student.username,
|
username: student.username,
|
||||||
exactUsername: "1",
|
exactUsername: "1",
|
||||||
today: "1",
|
today: "1",
|
||||||
...(flowchart ? { language: "Flowchart" } : {}),
|
// 布置时选了语言的,代码那边只看这个语言交的(和格子里数的一致)
|
||||||
|
...(flowchart
|
||||||
|
? { language: "Flowchart" }
|
||||||
|
: board.value?.language
|
||||||
|
? { language: board.value.language }
|
||||||
|
: {}),
|
||||||
...(problemDisplayId ? { problem: problemDisplayId } : {}),
|
...(problemDisplayId ? { problem: problemDisplayId } : {}),
|
||||||
},
|
},
|
||||||
}).href
|
}).href
|
||||||
@@ -293,6 +315,12 @@ function submissionsHref(
|
|||||||
<n-card v-if="!projector" size="small" class="section">
|
<n-card v-if="!projector" size="small" class="section">
|
||||||
<n-flex align="center" :wrap="false">
|
<n-flex align="center" :wrap="false">
|
||||||
<n-text strong style="flex-shrink: 0">这节课的题</n-text>
|
<n-text strong style="flex-shrink: 0">这节课的题</n-text>
|
||||||
|
<n-select
|
||||||
|
v-model:value="lessonLanguage"
|
||||||
|
:options="languageOptions"
|
||||||
|
aria-label="用什么语言做"
|
||||||
|
style="width: 110px; flex-shrink: 0"
|
||||||
|
/>
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="lessonInput"
|
v-model:value="lessonInput"
|
||||||
placeholder="输入题号,用空格隔开,比如 8019 8020 8021"
|
placeholder="输入题号,用空格隔开,比如 8019 8020 8021"
|
||||||
@@ -303,13 +331,15 @@ function submissionsHref(
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
<n-text depth="3" class="meta">
|
<n-text depth="3" class="meta">
|
||||||
<template v-if="board.source === 'teacher'">
|
<template v-if="board.source === 'teacher'">
|
||||||
学生首页的「班里在做」显示的就是这几道。清空再点布置就改回自动推断。
|
学生首页的「班里在做」显示的就是这几道<template v-if="board.language"
|
||||||
|
>,用 {{ LANGUAGE_SHOW_VALUE[board.language] }} 交对才算做完</template
|
||||||
|
>。清空再点布置就改回自动推断。
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="board.source === 'inferred'">
|
<template v-else-if="board.source === 'inferred'">
|
||||||
还没布置。下面这几道是按今天的提交记录推断的(同班 5 人以上做过的题)。
|
还没布置。下面这几道是按今天的提交记录推断的(同班 5 人以上做过的题)。
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
布置之后,学生打开首页就能看到这几道题,没听清题号的也能找到。
|
布置之后,学生打开首页就能看到这几道题,没听清题号的也能找到;打开题目时编辑器默认就是左边选的语言。
|
||||||
</template>
|
</template>
|
||||||
</n-text>
|
</n-text>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { useBreakpoints } from "shared/composables/breakpoints"
|
|||||||
import { useProblemJump } from "shared/composables/problemJump"
|
import { useProblemJump } from "shared/composables/problemJump"
|
||||||
import { pickHeadline, type Headline } from "oj/user/knowledge"
|
import { pickHeadline, type Headline } from "oj/user/knowledge"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import { ContestStatus, CONTEST_STATUS, JUDGE_STATUS } from "utils/constants"
|
import { ContestStatus, CONTEST_STATUS, JUDGE_STATUS, LANGUAGE_SHOW_VALUE } from "utils/constants"
|
||||||
import { duration, parseTime, zonedParts, zonedYear } from "utils/functions"
|
import { duration, parseTime, zonedParts, zonedYear } from "utils/functions"
|
||||||
import type {
|
import type {
|
||||||
AnnouncementListItem,
|
AnnouncementListItem,
|
||||||
@@ -227,7 +227,10 @@ const classActivityTitle = computed(() => {
|
|||||||
const day = classActivity.value?.day
|
const day = classActivity.value?.day
|
||||||
if (!day) return ""
|
if (!day) return ""
|
||||||
// 老师在课堂看板布置的,说成「老师布置的」,比「班里在做」更让人知道该做这几道
|
// 老师在课堂看板布置的,说成「老师布置的」,比「班里在做」更让人知道该做这几道
|
||||||
if (classActivity.value?.source === "teacher") return "今天老师布置的题"
|
if (classActivity.value?.source === "teacher") {
|
||||||
|
const language = classActivity.value.language
|
||||||
|
return `今天老师布置的题${language ? ` · 用${LANGUAGE_SHOW_VALUE[language]}做` : ""}`
|
||||||
|
}
|
||||||
if (classToday.value) return "今天班里在做"
|
if (classToday.value) return "今天班里在做"
|
||||||
const [, month, date] = day.split("-").map(Number)
|
const [, month, date] = day.split("-").map(Number)
|
||||||
return `${month}月${date}日班里做了`
|
return `${month}月${date}日班里做了`
|
||||||
|
|||||||
@@ -232,7 +232,10 @@ onSolvedHere(() => {
|
|||||||
<template>
|
<template>
|
||||||
<!-- 课堂 -->
|
<!-- 课堂 -->
|
||||||
<div v-if="lesson" class="context-bar lesson">
|
<div v-if="lesson" class="context-bar lesson">
|
||||||
<span class="lead lesson-lead">这节课 做完 {{ lesson.done }}/{{ lesson.total }}</span>
|
<span class="lead lesson-lead"
|
||||||
|
>这节课<template v-if="lessonStore.languageText"> · {{ lessonStore.languageText }}</template>
|
||||||
|
做完 {{ lesson.done }}/{{ lesson.total }}</span
|
||||||
|
>
|
||||||
<!-- 手机上放不下一排题号:只说是第几道,箭头做大(设计稿「手机:题目 / 代码 / 结果」) -->
|
<!-- 手机上放不下一排题号:只说是第几道,箭头做大(设计稿「手机:题目 / 代码 / 结果」) -->
|
||||||
<span v-if="isMobile" class="meta">这是第 {{ lesson.position }} 道</span>
|
<span v-if="isMobile" class="meta">这是第 {{ lesson.position }} 道</span>
|
||||||
<ProblemChips v-else :items="lesson.chips" :current="currentId" variant="lesson" />
|
<ProblemChips v-else :items="lesson.chips" :current="currentId" variant="lesson" />
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useCollabHere, useTeacherCollab } from "../composables/teacherCollab"
|
|||||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||||
import CollabBar from "./CollabBar.vue"
|
import CollabBar from "./CollabBar.vue"
|
||||||
import { useFlowchartStore } from "oj/store/flowchart"
|
import { useFlowchartStore } from "oj/store/flowchart"
|
||||||
|
import { useLessonStore } from "oj/store/lesson"
|
||||||
|
|
||||||
const FlowchartEditor = defineAsyncComponent(
|
const FlowchartEditor = defineAsyncComponent(
|
||||||
() => import("shared/components/FlowchartEditor/index.vue"),
|
() => import("shared/components/FlowchartEditor/index.vue"),
|
||||||
@@ -151,6 +152,30 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 老师在课堂看板布置这节课时选了语言:打开这节课的题,编辑器默认就用它。学 C 的班,
|
||||||
|
* 编辑器停在默认的 Python(或者上一个用这台电脑的人留下的偏好)的话,学生交上去才发现
|
||||||
|
* 不算数。课堂条只在题库入口出现,这里同一个范围。
|
||||||
|
*
|
||||||
|
* 每道题只自动换一次:学生自己再切回别的语言,就按他的来。课堂状态可能比题面晚到
|
||||||
|
* (课堂条进页面才去拉),所以两边都等到了再判断;画着流程图时不动他。
|
||||||
|
*/
|
||||||
|
const lessonStore = useLessonStore()
|
||||||
|
let lessonLanguageFor: string | null = null
|
||||||
|
watch(
|
||||||
|
() => [problem.value?._id, lessonStore.activity] as const,
|
||||||
|
([id, activity]) => {
|
||||||
|
if (!id || !activity || lessonLanguageFor === id) return
|
||||||
|
lessonLanguageFor = id
|
||||||
|
const language = activity.language
|
||||||
|
if (!language || ctx.value.entry !== "problem" || teacherCollab.value) return
|
||||||
|
if (!lessonStore.includes(id) || !problemStore.codeLanguages.includes(language)) return
|
||||||
|
if (codeStore.code.language === language || codeStore.code.language === "Flowchart") return
|
||||||
|
problemStore.switchLanguage(language)
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 画着流程图时忽然不让画了:工具栏的「写代码 / 画流程图」和语言下拉都跟着 canDraw 藏起来,
|
* 画着流程图时忽然不让画了:工具栏的「写代码 / 画流程图」和语言下拉都跟着 canDraw 藏起来,
|
||||||
* 编辑器要是还停在画布上,学生就回不去写代码了。拖窄到手机宽度时页面会换一套布局、编辑器
|
* 编辑器要是还停在画布上,学生就回不去写代码了。拖窄到手机宽度时页面会换一套布局、编辑器
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { getClassActivity } from "oj/api"
|
import { getClassActivity } from "oj/api"
|
||||||
|
import { LANGUAGE_SHOW_VALUE } from "utils/constants"
|
||||||
import type { ClassActivity, ClassActivityProblem } from "utils/types"
|
import type { ClassActivity, ClassActivityProblem } from "utils/types"
|
||||||
|
|
||||||
/** 同一节课里来回换题不用每次都重拉;过了这么久再进题目页就重拉一次 */
|
/** 同一节课里来回换题不用每次都重拉;过了这么久再进题目页就重拉一次 */
|
||||||
@@ -74,10 +75,18 @@ export const useLessonStore = defineStore("lesson", () => {
|
|||||||
return { total: list.length, next: rest[0] ?? null, remaining: rest.length }
|
return { total: list.length, next: rest[0] ?? null, remaining: rest.length }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 老师布置的叫「老师布置的题」,推断出来的叫「这节课的题」 */
|
/** 老师布置时选的语言,给界面写「C语言」;没选(推断出来的课、以前布置的)为空 */
|
||||||
|
const languageText = computed(() => {
|
||||||
|
const language = activity.value?.language
|
||||||
|
return language ? LANGUAGE_SHOW_VALUE[language] : ""
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 老师布置的叫「老师布置的题」(选了语言带上语言),推断出来的叫「这节课的题」 */
|
||||||
const label = computed(() =>
|
const label = computed(() =>
|
||||||
activity.value?.source === "teacher" ? "老师布置的题" : "这节课的题",
|
activity.value?.source === "teacher"
|
||||||
|
? `老师布置的题${languageText.value ? ` · ${languageText.value}` : ""}`
|
||||||
|
: "这节课的题",
|
||||||
)
|
)
|
||||||
|
|
||||||
return { activity, label, load, markAccepted, includes, nextAfter }
|
return { activity, label, languageText, load, markAccepted, includes, nextAfter }
|
||||||
})
|
})
|
||||||
@@ -508,6 +508,8 @@ async function toggleLesson() {
|
|||||||
if (lessonActive.value) {
|
if (lessonActive.value) {
|
||||||
query.problem = ""
|
query.problem = ""
|
||||||
query.today = "0"
|
query.today = "0"
|
||||||
|
// 「这节课」顺带筛上的语言一起放掉;老师自己另选的别动
|
||||||
|
if (lesson.value?.language && query.language === lesson.value.language) query.language = ""
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lessonLoading.value = true
|
lessonLoading.value = true
|
||||||
@@ -525,6 +527,8 @@ async function toggleLesson() {
|
|||||||
lesson.value = res
|
lesson.value = res
|
||||||
query.problem = lessonIds.value
|
query.problem = lessonIds.value
|
||||||
query.today = "1"
|
query.today = "1"
|
||||||
|
// 老师布置时选了语言:看板上只数这个语言交的,列表也只列它(流程图那边不动)
|
||||||
|
if (res.language && !flowMode.value) query.language = res.language
|
||||||
} finally {
|
} finally {
|
||||||
lessonLoading.value = false
|
lessonLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user