From 9e41855610b96921d21320f4fb9944988773f8a8 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Thu, 27 Aug 2026 06:27:02 -0600 Subject: [PATCH] =?UTF-8?q?fix(=E6=B5=81=E7=A8=8B=E5=9B=BE):=20=E7=82=B9?= =?UTF-8?q?=E8=BF=9B=E5=8E=BB=E7=A9=BA=E7=99=BD=E7=9A=84=20tab=E3=80=81?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E3=80=8C=E6=B8=B2=E6=9F=93=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E3=80=8D=E8=BF=99=E4=B8=AA=E5=8F=AA=E8=BF=9B=E4=B8=8D=E5=87=BA?= =?UTF-8?q?=E7=9A=84=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 两个开关同时打开会做出一个空 tab 后端在 `allowFlowchart` 为真时把 `mermaidCode` 置成 null(不能把标准答案下发给 正要自己画图的学生,见 routes/problem.ts)。而前端 `tabOptions` 只看 `showFlowchart` 就把 "flowchart" 加进选项,面板那边却要求 `showFlowchart && mermaidCode` —— 两个开关都打开时,选项存在、面板不存在, URL 里带 `?tab=flowchart` 就会选中一个渲染不出任何东西的页签。 三个断点条件还各不相同(两处要求两者都有,第三处只看 showFlowchart,那处会拿 null 去渲染 ProblemFlowchart)。统一成一个 `canShowFlowchart`。 后台那边把「显示标准流程图」在允许提交流程图时置灰并说明原因,再补一个 watch 把存量数据里两个都开着的情况纠正掉 —— 它们本来就是互斥的。 ## 「渲染成功」只进不出 保存前的校验靠 `mermaidRenderSuccess`,而 MermaidEditor 只在成功时 emit、 这个 ref 也就只会从 false 变 true,永不复位。**先写对、再改坏,照样能存进库。** 改成上报渲染结果本身(`render-state`),并在 modelValue 一变就立刻打回 「未验证」,等防抖后的渲染真跑完再报结论 —— 只挂防抖那一支的话,改完 300ms 内 点保存读到的还是上一次的结论,刚改坏的代码会被当成校验通过。宁可让出题人多等 一下,也不能放脏数据进库。 实测:改动后 50ms 读到 false(此时保存会被拦),渲染完成后回到 true; 贴一段坏语法则一直是 false。 Co-Authored-By: Claude Opus 5 --- apps/web/src/admin/problem/detail.vue | 29 +++++++++++++++---- apps/web/src/oj/problem/detail.vue | 16 +++++++--- .../src/shared/components/MermaidEditor.vue | 11 +++++-- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/apps/web/src/admin/problem/detail.vue b/apps/web/src/admin/problem/detail.vue index d07645a..b2b9cce 100644 --- a/apps/web/src/admin/problem/detail.vue +++ b/apps/web/src/admin/problem/detail.vue @@ -173,6 +173,16 @@ const languageOptions = [ const isSQLProblem = computed(() => !!problem.value?.languages.includes("SQL")) // SQL 题联动:SQL 必须是唯一语言(后端强校验),不需要预制代码,自动初始化 sql_config +// 两个流程图开关是互斥的:allowFlowchart 为真时后端不会把 mermaidCode 下发给 +// 学生,showFlowchart 就成了一个点进去什么都没有的 tab。UI 上已经把开关置灰, +// 这里再把存量数据里两个都开着的情况纠正掉。 +watch( + () => problem.value?.allowFlowchart, + (allow) => { + if (allow) problem.value.showFlowchart = false + }, +) + watch( () => problem.value?.languages, (langs) => { @@ -310,9 +320,10 @@ function downloadTestcases() { download(`problems/${problem.value.id}/test-cases`) } -// Mermaid 渲染事件处理 -function onMermaidRenderSuccess() { - mermaidRenderSuccess.value = true +// Mermaid 渲染事件处理。这里必须原样接受 false —— +// 原来只在成功时置 true、永不复位,先写对再改坏就能把语法错误的代码存进库 +function onMermaidRenderState(ok: boolean) { + mermaidRenderSuccess.value = ok } // 题目是否有漏写的 @@ -864,7 +875,15 @@ watch( - + + + + 让学生自己画图时,标准流程图不会下发给学生,这个开关没有意义 + + @@ -872,7 +891,7 @@ watch( diff --git a/apps/web/src/oj/problem/detail.vue b/apps/web/src/oj/problem/detail.vue index c8a20eb..d0047ac 100644 --- a/apps/web/src/oj/problem/detail.vue +++ b/apps/web/src/oj/problem/detail.vue @@ -53,9 +53,17 @@ const { shouldShowProblem } = storeToRefs(screenModeStore) const { isMobile, isDesktop } = useBreakpoints() +// tab 选项和面板必须用同一个条件。后端在 allowFlowchart 为真时会把 mermaidCode +// 置成 null(不能把标准答案下发给正要自己画图的学生),只看 showFlowchart 的话, +// 两个开关同时打开就会做出一个「选项存在、面板不存在」的 tab —— URL 里带 +// ?tab=flowchart 会选中一个渲染不出任何东西的页签。 +const canShowFlowchart = computed( + () => !!problem.value?.showFlowchart && !!problem.value?.mermaidCode, +) + const tabOptions = computed(() => { const options: string[] = ["content"] - if (problem.value?.showFlowchart) { + if (canShowFlowchart.value) { options.push("flowchart") } @@ -159,7 +167,7 @@ watch( @@ -211,7 +219,7 @@ watch( @@ -251,7 +259,7 @@ watch( - + diff --git a/apps/web/src/shared/components/MermaidEditor.vue b/apps/web/src/shared/components/MermaidEditor.vue index 7c042a3..34e79b3 100644 --- a/apps/web/src/shared/components/MermaidEditor.vue +++ b/apps/web/src/shared/components/MermaidEditor.vue @@ -7,19 +7,26 @@ const mermaidContainer = useTemplateRef("mermaidContainer") const { renderFlowchart, renderError, renderSuccess } = useMermaid() +// 上报渲染结果而不是只上报成功:调用方拿它做保存前校验,只进不出的话, +// 「先写对再改坏」照样能存进库 const emit = defineEmits<{ - renderSuccess: [] + renderState: [ok: boolean] }>() const renderMermaid = async () => { await renderFlowchart(mermaidContainer.value, modelValue.value) - if (renderSuccess.value) emit("renderSuccess") + emit("renderState", renderSuccess.value) } onMounted(() => { nextTick(renderMermaid) }) +// 一改动就立刻把状态打回「未验证」,等防抖后的渲染真跑完再报结果。 +// 只挂防抖那一支的话,改完 300ms 内点保存,读到的还是上一次渲染的结论 —— +// 刚改坏的代码会被当成校验通过。宁可让用户多等一下,也不能放脏数据进库。 +watch(modelValue, () => emit("renderState", false)) + // 出题页是边敲边预览,不防抖的话每个字符都会触发一次完整的 mermaid 渲染, // 而中间态几乎全是语法错误 watchDebounced(modelValue, renderMermaid, { debounce: 300, maxWait: 1000 })