diff --git a/apps/web/src/oj/problem/components/MyFlowchartTab.vue b/apps/web/src/oj/problem/components/MyFlowchartTab.vue
index 10987f4..f2389c9 100644
--- a/apps/web/src/oj/problem/components/MyFlowchartTab.vue
+++ b/apps/web/src/oj/problem/components/MyFlowchartTab.vue
@@ -22,7 +22,11 @@ watch(
{{ renderError }}
-
+
diff --git a/apps/web/src/oj/problem/components/ProblemFlowchart.vue b/apps/web/src/oj/problem/components/ProblemFlowchart.vue
index c61f7a8..6ab9451 100644
--- a/apps/web/src/oj/problem/components/ProblemFlowchart.vue
+++ b/apps/web/src/oj/problem/components/ProblemFlowchart.vue
@@ -27,7 +27,9 @@ watch(() => problem.value?.mermaidCode, renderProblemFlowchart)
{{ renderError }}
-
+
+
diff --git a/apps/web/src/oj/problem/components/SubmitFlowchart.vue b/apps/web/src/oj/problem/components/SubmitFlowchart.vue
index a816897..6bbcda4 100644
--- a/apps/web/src/oj/problem/components/SubmitFlowchart.vue
+++ b/apps/web/src/oj/problem/components/SubmitFlowchart.vue
@@ -314,26 +314,40 @@ async function getSubmission(submissionPage = 0) {
}
}
+// 请求失败时 rendering 必须复位:拦截器对普通业务错误是静默 reject 的,
+// 少了这个 finally,弹框就会永远停在转圈状态,也没有任何提示
async function updatePage(val: number) {
page.value = val
rendering.value = true
- await getSubmission(val)
- // 等待 DOM 更新
- await nextTick()
- await renderFlowchart(mermaidContainer.value, myMermaidCode.value)
- rendering.value = false
+ try {
+ await getSubmission(val)
+ // 等待 DOM 更新
+ await nextTick()
+ await renderFlowchart(mermaidContainer.value, myMermaidCode.value)
+ } catch (error) {
+ message.error("加载这次提交失败,请稍后重试")
+ console.error("加载流程图提交失败:", error)
+ } finally {
+ rendering.value = false
+ }
}
// ==================== 模态框相关函数 ====================
async function openDetailModal() {
showDetailModal.value = true
rendering.value = true
- await getSubmission()
- page.value = submissionCount.value
- // 等待 DOM 更新,确保弹框已经渲染
- await nextTick()
- await renderFlowchart(mermaidContainer.value, myMermaidCode.value)
- rendering.value = false
+ try {
+ await getSubmission()
+ page.value = submissionCount.value
+ // 等待 DOM 更新,确保弹框已经渲染
+ await nextTick()
+ await renderFlowchart(mermaidContainer.value, myMermaidCode.value)
+ } catch (error) {
+ message.error("加载评分详情失败,请稍后重试")
+ console.error("加载流程图评分详情失败:", error)
+ } finally {
+ rendering.value = false
+ }
}
function closeModal() {
@@ -342,14 +356,18 @@ function closeModal() {
function loadToEditor() {
if (myFlowchartZippedStr.value) {
- const str = atou(myFlowchartZippedStr.value)
- const json = JSON.parse(str)
- const processedData = {
- nodes: json.nodes || [],
- edges: json.edges || [],
- }
- if (flowchartEditorRef?.value) {
- flowchartEditorRef.value.setFlowchartData(processedData)
+ // 老提交的压缩数据可能是坏的(格式换过、存了一半),
+ // 不兜住的话 atou/JSON.parse 直接抛,按钮点了毫无反应
+ try {
+ const json = JSON.parse(atou(myFlowchartZippedStr.value))
+ flowchartEditorRef?.value?.setFlowchartData({
+ nodes: json.nodes || [],
+ edges: json.edges || [],
+ })
+ } catch (error) {
+ message.error("这份流程图数据已损坏,无法加载到编辑器")
+ console.error("解析流程图数据失败:", error)
+ return
}
}
closeModal()
@@ -432,7 +450,11 @@ onUnmounted(() => {
{{ renderError }}
-
+
diff --git a/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue
index 6e242c9..ed90cbc 100644
--- a/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue
+++ b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue
@@ -20,8 +20,9 @@
{{ renderError }}
-
+
diff --git a/apps/web/src/shared/components/MermaidEditor.vue b/apps/web/src/shared/components/MermaidEditor.vue
index 238e5ac..7c042a3 100644
--- a/apps/web/src/shared/components/MermaidEditor.vue
+++ b/apps/web/src/shared/components/MermaidEditor.vue
@@ -20,7 +20,9 @@ onMounted(() => {
nextTick(renderMermaid)
})
-watch(modelValue, renderMermaid)
+// 出题页是边敲边预览,不防抖的话每个字符都会触发一次完整的 mermaid 渲染,
+// 而中间态几乎全是语法错误
+watchDebounced(modelValue, renderMermaid, { debounce: 300, maxWait: 1000 })
const clearCode = () => {
modelValue.value = ""
diff --git a/apps/web/src/shared/composables/useMermaid.ts b/apps/web/src/shared/composables/useMermaid.ts
index 0925627..942c462 100644
--- a/apps/web/src/shared/composables/useMermaid.ts
+++ b/apps/web/src/shared/composables/useMermaid.ts
@@ -256,25 +256,41 @@ function getChromeVersion(): number {
return match ? parseInt(match[1]) : Infinity
}
-let mermaidInstance: any = null
+let mermaidPromise: Promise | null = null
let mermaidIsLegacy = false
-async function loadMermaid() {
- if (!mermaidInstance) {
- if (getChromeVersion() < 94) {
- mermaidInstance = (await import("mermaid-legacy")).default
- mermaidIsLegacy = true
- } else {
- mermaidInstance = (await import("mermaid")).default
- }
- mermaidInstance.initialize({
- startOnLoad: false,
- securityLevel: "strict",
- theme: "base",
- themeVariables: mermaidThemeVariables,
+function loadMermaid(): Promise {
+ // 缓存 Promise 而不是实例:同一屏里两个组件一起挂载时,缓存实例会让两边都
+ // 落进 if (!mermaidInstance),import 两次、initialize 两次
+ if (!mermaidPromise) {
+ mermaidPromise = (async () => {
+ let instance: any
+ if (getChromeVersion() < 94) {
+ instance = (await import("mermaid-legacy")).default
+ mermaidIsLegacy = true
+ } else {
+ instance = (await import("mermaid")).default
+ }
+ instance.initialize({
+ startOnLoad: false,
+ securityLevel: "strict",
+ theme: "base",
+ // 解析失败时 mermaid 默认会先把一张「错误图」画进挂在 document.body 上的
+ // 临时容器,再抛异常,而清理临时容器的那行在 throw 之后,永远执行不到;
+ // 每次 render 用的又是新的随机 id,旧的也清不掉。于是每失败一次就往
+ // body 里留一个 div#dmermaid-xxx —— 出题页每敲一个字符渲染一次,一段
+ // 代码写下来能堆几十个。打开这个开关后 mermaid 会先清理再抛。
+ suppressErrorRendering: true,
+ themeVariables: mermaidThemeVariables,
+ })
+ return instance
+ })().catch((error) => {
+ // 失败的 Promise 不能留在缓存里,否则后面每次渲染都直接复用这个失败结果
+ mermaidPromise = null
+ throw error
})
}
- return mermaidInstance
+ return mermaidPromise
}
export function useMermaid() {