From 5867bbceed2b5548683cd20d63901120514683ee Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 29 Jun 2026 19:38:00 -0600 Subject: [PATCH] add new type of self-learning --- src/admin/api.ts | 5 +- .../tutorial/components/ExerciseManager.vue | 387 ++++++++++++++++-- src/oj/learn/components/ExerciseDebug.vue | 121 ++++++ src/oj/learn/components/ExerciseGroup.vue | 191 +++++++++ src/oj/learn/components/ExerciseMatch.vue | 192 +++++++++ src/oj/learn/components/ExercisePredict.vue | 92 +++++ src/oj/learn/components/ExerciseWidget.vue | 18 + .../learn/components/exercise-highlight.css | 50 +++ src/oj/learn/composables/useCodeHighlight.ts | 41 ++ src/utils/types.ts | 47 ++- 10 files changed, 1113 insertions(+), 31 deletions(-) create mode 100644 src/oj/learn/components/ExerciseDebug.vue create mode 100644 src/oj/learn/components/ExerciseGroup.vue create mode 100644 src/oj/learn/components/ExerciseMatch.vue create mode 100644 src/oj/learn/components/ExercisePredict.vue create mode 100644 src/oj/learn/components/exercise-highlight.css create mode 100644 src/oj/learn/composables/useCodeHighlight.ts diff --git a/src/admin/api.ts b/src/admin/api.ts index eddffd1..ebb2d94 100644 --- a/src/admin/api.ts +++ b/src/admin/api.ts @@ -8,6 +8,7 @@ import type { BlankProblem, Contest, Exercise, + ExerciseType, Server, TestcaseUploadedReturns, Tutorial, @@ -272,7 +273,7 @@ export async function getAdminExercises(tutorialId: number) { export async function createExercise(data: { tutorial_id: number - type: "mcq" | "sort" | "fill" + type: ExerciseType data: object order: number }) { @@ -282,7 +283,7 @@ export async function createExercise(data: { export async function updateExercise(data: { id: number - type: "mcq" | "sort" | "fill" + type: ExerciseType data: object order: number }) { diff --git a/src/admin/tutorial/components/ExerciseManager.vue b/src/admin/tutorial/components/ExerciseManager.vue index 01a4e79..6d87712 100644 --- a/src/admin/tutorial/components/ExerciseManager.vue +++ b/src/admin/tutorial/components/ExerciseManager.vue @@ -1,9 +1,14 @@ @@ -208,6 +400,10 @@ function typeTagType(type: string): "success" | "info" | "warning" { 选择题 代码排序 代码填空 + 连线匹配 + 输出预测 + 代码找错 + 归类分组 @@ -251,7 +447,7 @@ function typeTagType(type: string): "success" | "info" | "warning" { @click=" () => { mcqOptions.splice(i, 1) - mcqAnswer.value = mcqAnswer.value + mcqAnswer = mcqAnswer .filter((a) => a !== i) .map((a) => (a > i ? a - 1 : a)) } @@ -287,7 +483,7 @@ function typeTagType(type: string): "success" | "info" | "warning" { - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ i + 1 }}. {{ line }} + + + + + + + + + + + + + + + + + + + + diff --git a/src/oj/learn/components/ExerciseDebug.vue b/src/oj/learn/components/ExerciseDebug.vue new file mode 100644 index 0000000..ff6dd7e --- /dev/null +++ b/src/oj/learn/components/ExerciseDebug.vue @@ -0,0 +1,121 @@ + + + + + + 练一练 · 代码找错 + + + + {{ data.question }} + + + 点击你认为有错误的代码行(可多选)。 + + + + + + {{ idx + 1 }} + + + + + + + + {{ data.explanation }} + + + + + 提交 + 重置 + + + diff --git a/src/oj/learn/components/ExerciseGroup.vue b/src/oj/learn/components/ExerciseGroup.vue new file mode 100644 index 0000000..6d53357 --- /dev/null +++ b/src/oj/learn/components/ExerciseGroup.vue @@ -0,0 +1,191 @@ + + + + + + 练一练 · 归类分组 + + + + {{ data.question }} + + + 把下面的项目拖到对应的分组里,可在分组间拖动调整。 + + + + + (已全部归类) + + + {{ data.items[i] }} + + + + + + + {{ bucket }} + + + + {{ data.items[i] }} + + + + + + + + + + 提交 + + 重置 + + + diff --git a/src/oj/learn/components/ExerciseMatch.vue b/src/oj/learn/components/ExerciseMatch.vue new file mode 100644 index 0000000..df9da90 --- /dev/null +++ b/src/oj/learn/components/ExerciseMatch.vue @@ -0,0 +1,192 @@ + + + + + + 练一练 · 连线匹配 + + + + {{ data.question }} + + + 先点左边一项,再点右边一项即可连线;点击已连线的项可取消。 + + + + + + + {{ item }} + + + + + + + {{ data.right[rightIdx] }} + + + + + + + + + 提交 + + 重置 + + + diff --git a/src/oj/learn/components/ExercisePredict.vue b/src/oj/learn/components/ExercisePredict.vue new file mode 100644 index 0000000..30af5f2 --- /dev/null +++ b/src/oj/learn/components/ExercisePredict.vue @@ -0,0 +1,92 @@ + + + + + + 练一练 · 输出预测 + + + + {{ data.question }} + + + + + 这段代码会输出什么? + + + + + + + 提交 + + 重置 + + + diff --git a/src/oj/learn/components/ExerciseWidget.vue b/src/oj/learn/components/ExerciseWidget.vue index b7fbfc8..6c88419 100644 --- a/src/oj/learn/components/ExerciseWidget.vue +++ b/src/oj/learn/components/ExerciseWidget.vue @@ -4,6 +4,12 @@ import { Exercise } from "utils/types" const ExerciseMcq = defineAsyncComponent(() => import("./ExerciseMcq.vue")) const ExerciseSort = defineAsyncComponent(() => import("./ExerciseSort.vue")) const ExerciseFill = defineAsyncComponent(() => import("./ExerciseFill.vue")) +const ExerciseMatch = defineAsyncComponent(() => import("./ExerciseMatch.vue")) +const ExercisePredict = defineAsyncComponent( + () => import("./ExercisePredict.vue"), +) +const ExerciseDebug = defineAsyncComponent(() => import("./ExerciseDebug.vue")) +const ExerciseGroup = defineAsyncComponent(() => import("./ExerciseGroup.vue")) defineProps<{ exercise: Exercise; lang?: string }>() @@ -20,4 +26,16 @@ defineProps<{ exercise: Exercise; lang?: string }>() :exercise="exercise" :lang="lang" /> + + + + diff --git a/src/oj/learn/components/exercise-highlight.css b/src/oj/learn/components/exercise-highlight.css new file mode 100644 index 0000000..5493423 --- /dev/null +++ b/src/oj/learn/components/exercise-highlight.css @@ -0,0 +1,50 @@ +/* 练一练代码高亮配色(明 / 暗),由涉及代码高亮的题型组件统一引入 */ +.hljs-keyword, +.hljs-operator, +.hljs-selector-tag { + color: #d73a49; +} +.hljs-string, +.hljs-regexp, +.hljs-template-literal { + color: #032f62; +} +.hljs-comment, +.hljs-quote { + color: #6a737d; + font-style: italic; +} +.hljs-number, +.hljs-literal { + color: #005cc5; +} +.hljs-built_in, +.hljs-title.function_, +.hljs-class .hljs-title { + color: #6f42c1; +} + +.dark .hljs-keyword, +.dark .hljs-operator, +.dark .hljs-selector-tag { + color: #c678dd; +} +.dark .hljs-string, +.dark .hljs-regexp, +.dark .hljs-template-literal { + color: #98c379; +} +.dark .hljs-comment, +.dark .hljs-quote { + color: #7f848e; + font-style: italic; +} +.dark .hljs-number, +.dark .hljs-literal { + color: #e5c07b; +} +.dark .hljs-built_in, +.dark .hljs-title.function_, +.dark .hljs-class .hljs-title { + color: #61afef; +} diff --git a/src/oj/learn/composables/useCodeHighlight.ts b/src/oj/learn/composables/useCodeHighlight.ts new file mode 100644 index 0000000..3ac8261 --- /dev/null +++ b/src/oj/learn/composables/useCodeHighlight.ts @@ -0,0 +1,41 @@ +import hljs from "highlight.js/lib/core" +import python from "highlight.js/lib/languages/python" +import c from "highlight.js/lib/languages/c" + +hljs.registerLanguage("python", python) +hljs.registerLanguage("c", c) + +export function escapeHtml(text: string): string { + return text.replace(/&/g, "&").replace(//g, ">") +} + +function normalizeLang(lang?: string): "python" | "c" | null { + return lang === "python" ? "python" : lang === "c" ? "c" : null +} + +// 把整段代码高亮为 HTML;不支持的语言或异常时回退为转义文本 +export function highlight(code: string, lang?: string): string { + const language = normalizeLang(lang) + if (language) { + try { + return hljs.highlight(code, { language }).value + } catch { + // fall through + } + } + return escapeHtml(code) +} + +// 按行高亮:整体高亮后再按行切分,保证跨行 token 着色正确,返回逐行 HTML 数组 +export function highlightLines(lines: string[], lang?: string): string[] { + const language = normalizeLang(lang) + if (language) { + try { + const html = hljs.highlight(lines.join("\n"), { language }).value + return html.split("\n") + } catch { + // fall through + } + } + return lines.map((line) => escapeHtml(line)) +} diff --git a/src/utils/types.ts b/src/utils/types.ts index 4364873..34d2b3e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -627,10 +627,53 @@ export interface ExerciseFillData { code: string } +export interface ExerciseMatchData { + question: string + left: string[] + right: string[] + answer: number[] +} + +export interface ExercisePredictData { + question: string + code: string + answer: string[] +} + +export interface ExerciseDebugData { + question: string + lines: string[] + answer: number[] + explanation?: string +} + +export interface ExerciseGroupData { + question: string + buckets: string[] + items: string[] + answer: number[] +} + +export type ExerciseType = + | "mcq" + | "sort" + | "fill" + | "match" + | "predict" + | "debug" + | "group" + export interface Exercise { id: number - type: "mcq" | "sort" | "fill" - data: ExerciseMcqData | ExerciseSortData | ExerciseFillData + type: ExerciseType + data: + | ExerciseMcqData + | ExerciseSortData + | ExerciseFillData + | ExerciseMatchData + | ExercisePredictData + | ExerciseDebugData + | ExerciseGroupData order: number }
+ {{ data.question }} +
+ 点击你认为有错误的代码行(可多选)。 +
+ 把下面的项目拖到对应的分组里,可在分组间拖动调整。 +
+ {{ bucket }} +
+ 先点左边一项,再点右边一项即可连线;点击已连线的项可取消。 +
这段代码会输出什么?