refactor(题目): 比赛和题单改用题库那个编辑器——题单里的求助终于接得上,一键换中文标点也能用了
比赛和题单原来用的是另一个精简版编辑器(BasicEditor),没有协作绑定、也没有 错误标记扩展:题单里学生点「求助」,老师接单后编辑器绑不上;比赛和题单里 「把中文标点都换成英文」点了没反应、出错行也不标红。三种入口统一成 ProblemEditor, 删掉 BasicEditor。流程图仍然只在题库入口出现(语言列表那道闸没动)。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0daa74651d
commit
a8b83ba762
3 files changed
+9
-81
No files matched your search
@@ -1,71 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { storeToRefs } from "pinia"
|
|
||||||
import { useCodeStore } from "oj/store/code"
|
|
||||||
import { useProblemStore } from "oj/store/problem"
|
|
||||||
import { SOURCES } from "utils/constants"
|
|
||||||
import CodeEditor from "shared/components/CodeEditor.vue"
|
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
|
||||||
import storage from "utils/storage"
|
|
||||||
import type { LANGUAGE } from "utils/types"
|
|
||||||
import { beginEditTrace, editTraceExtensions } from "oj/problem/utils/editTrace"
|
|
||||||
import EditorToolbar from "./EditorToolbar.vue"
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const codeStore = useCodeStore()
|
|
||||||
const problemStore = useProblemStore()
|
|
||||||
const { problem } = storeToRefs(problemStore)
|
|
||||||
|
|
||||||
const { isDesktop } = useBreakpoints()
|
|
||||||
|
|
||||||
const contestID = route.params.contestID || null
|
|
||||||
const storageKey = computed(
|
|
||||||
() => `problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
const editorHeight = computed(() =>
|
|
||||||
isDesktop.value ? "calc(100vh - 133px)" : "calc(100vh - 172px)",
|
|
||||||
)
|
|
||||||
|
|
||||||
function loadCode() {
|
|
||||||
codeStore.code.language = problemStore.supportedLanguage(codeStore.code.language)
|
|
||||||
const savedCode = storage.get(storageKey.value)
|
|
||||||
codeStore.setCode(
|
|
||||||
savedCode ||
|
|
||||||
problem.value!.template[codeStore.code.language] ||
|
|
||||||
SOURCES[codeStore.code.language],
|
|
||||||
)
|
|
||||||
beginEditTrace(`problem_${problem.value!._id}_contest_${contestID}`, codeStore.code.value.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(loadCode)
|
|
||||||
|
|
||||||
// 换题时组件复用、不重新挂载,和 ProblemEditor 一样得自己重新载入
|
|
||||||
watch(() => problem.value?._id, loadCode)
|
|
||||||
|
|
||||||
const changeCode = (v: string) => {
|
|
||||||
storage.set(storageKey.value, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeLanguage = (v: LANGUAGE) => {
|
|
||||||
const savedCode = storage.get(storageKey.value)
|
|
||||||
codeStore.setCode(
|
|
||||||
savedCode && storageKey.value.split("_").pop() === v
|
|
||||||
? savedCode
|
|
||||||
: problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-flex vertical>
|
|
||||||
<EditorToolbar :storage-key="storageKey" @change-language="changeLanguage" />
|
|
||||||
<CodeEditor
|
|
||||||
v-model:value="codeStore.code.value"
|
|
||||||
:language="codeStore.code.language"
|
|
||||||
:height="editorHeight"
|
|
||||||
:extra-extensions="editTraceExtensions"
|
|
||||||
@update:model-value="changeCode"
|
|
||||||
/>
|
|
||||||
</n-flex>
|
|
||||||
</template>
|
|
||||||
@@ -8,11 +8,12 @@ import { useScreenModeStore } from "shared/store/screenMode"
|
|||||||
import { useMyFlowchartStore } from "shared/store/myFlowchart"
|
import { useMyFlowchartStore } from "shared/store/myFlowchart"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
|
|
||||||
// 抽成具名 loader,便于进页面时与接口并行预取编辑器 chunk
|
// 抽成具名 loader,便于进页面时与接口并行预取编辑器 chunk。
|
||||||
|
// 题库、比赛、题单三种入口用的是同一个编辑器:原来比赛和题单用的是另一个精简版,
|
||||||
|
// 没有协作、也没有错误标记,于是题单里「求助」老师接不上、「把中文标点都换成英文」
|
||||||
|
// 点了没反应(见 docs/specs/2026-09-28-problem-page-redesign-design.md 第 10 节)
|
||||||
const loadProblemEditor = () => import("./components/ProblemEditor.vue")
|
const loadProblemEditor = () => import("./components/ProblemEditor.vue")
|
||||||
const loadContestEditor = () => import("./components/BasicEditor.vue")
|
|
||||||
const ProblemEditor = defineAsyncComponent(loadProblemEditor)
|
const ProblemEditor = defineAsyncComponent(loadProblemEditor)
|
||||||
const BasicEditor = defineAsyncComponent(loadContestEditor)
|
|
||||||
const EditorForTest = defineAsyncComponent(() => import("./components/EditorForTest.vue"))
|
const EditorForTest = defineAsyncComponent(() => import("./components/EditorForTest.vue"))
|
||||||
const ProblemContent = defineAsyncComponent(() => import("./components/ProblemContent.vue"))
|
const ProblemContent = defineAsyncComponent(() => import("./components/ProblemContent.vue"))
|
||||||
const ProblemInfo = defineAsyncComponent(() => import("./components/ProblemInfo.vue"))
|
const ProblemInfo = defineAsyncComponent(() => import("./components/ProblemInfo.vue"))
|
||||||
@@ -71,8 +72,6 @@ const tabOptions = computed(() => {
|
|||||||
|
|
||||||
const currentTab = ref("content")
|
const currentTab = ref("content")
|
||||||
|
|
||||||
const isPublicProblemRoute = computed(() => route.name === "problem")
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => route.query.tab, () => tabOptions.value],
|
[() => route.query.tab, () => tabOptions.value],
|
||||||
([rawTab]) => {
|
([rawTab]) => {
|
||||||
@@ -103,7 +102,7 @@ async function init() {
|
|||||||
myFlowchartStore.hide()
|
myFlowchartStore.hide()
|
||||||
// 并行预取右侧编辑器 chunk(CodeMirror ~370K+),
|
// 并行预取右侧编辑器 chunk(CodeMirror ~370K+),
|
||||||
// 避免等 getProblem 返回后才串行下载,编辑器才迟迟出现
|
// 避免等 getProblem 返回后才串行下载,编辑器才迟迟出现
|
||||||
;(isPublicProblemRoute.value ? loadProblemEditor : loadContestEditor)()
|
loadProblemEditor()
|
||||||
try {
|
try {
|
||||||
const res = await getProblem(problemID, contestID)
|
const res = await getProblem(problemID, contestID)
|
||||||
problem.value = res
|
problem.value = res
|
||||||
@@ -188,7 +187,7 @@ watch(
|
|||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</template>
|
</template>
|
||||||
<template #2>
|
<template #2>
|
||||||
<component :is="isPublicProblemRoute ? ProblemEditor : BasicEditor" />
|
<ProblemEditor />
|
||||||
</template>
|
</template>
|
||||||
</n-split>
|
</n-split>
|
||||||
|
|
||||||
@@ -232,7 +231,7 @@ watch(
|
|||||||
<ProblemFlowchart />
|
<ProblemFlowchart />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="editor" tab="代码">
|
<n-tab-pane name="editor" tab="代码">
|
||||||
<component :is="isPublicProblemRoute ? ProblemEditor : BasicEditor" />
|
<ProblemEditor />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="info" tab="统计" :disabled="!!problemSetId">
|
<n-tab-pane name="info" tab="统计" :disabled="!!problemSetId">
|
||||||
<ProblemInfo />
|
<ProblemInfo />
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import type { SubmissionTrace } from "@oj2/contract"
|
|||||||
* 编辑过程信号的采集,随提交一起报给后端(落进 `submission_trace`)。
|
* 编辑过程信号的采集,随提交一起报给后端(落进 `submission_trace`)。
|
||||||
* 字段含义见契约的 `submissionTraceSchema`。**只数字符数和次数,不留任何按键内容。**
|
* 字段含义见契约的 `submissionTraceSchema`。**只数字符数和次数,不留任何按键内容。**
|
||||||
*
|
*
|
||||||
* 是模块单例而不是 Pinia store:编辑器(ProblemEditor / BasicEditor 里)和
|
* 是模块单例而不是 Pinia store:编辑器(ProblemEditor 里)和
|
||||||
* 提交按钮(Form → SubmitCode)是兄弟组件,得共用一份计数;而 CodeMirror 的
|
* 提交按钮(EditorToolbar → SubmitCode)是兄弟组件,得共用一份计数;而 CodeMirror 的
|
||||||
* 扩展对象一旦经过 store 就会被包成响应式代理,facet 靠身份比较,代理过的扩展
|
* 扩展对象一旦经过 store 就会被包成响应式代理,facet 靠身份比较,代理过的扩展
|
||||||
* 直接失效。计数本身也不需要响应式。
|
* 直接失效。计数本身也不需要响应式。
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in new issue
Block a user