Просмотр исходного кода

Merge pull request #2476 from visuddhinanda/development

:bug: fix(dashboard-v6): Mermaid 组件适配异步 render,失败时显示错误提示
visuddhinanda 2 недель назад
Родитель
Сommit
959407d028

+ 82 - 9
dashboard-v6/src/components/general/Mermaid.tsx

@@ -1,20 +1,93 @@
+import { Alert } from "antd";
 import lodash from "lodash";
 import mermaid from "mermaid";
+import { useEffect, useMemo, useRef, useState } from "react";
+import { useIntl } from "react-intl";
 
 interface IWidget {
   text?: string;
 }
+
+type RenderState =
+  | { status: "loading" }
+  | { status: "ready"; svg: string }
+  | { status: "error"; message: string };
+
 const MermaidWidget = ({ text }: IWidget) => {
-  const id = lodash.times(20, () => lodash.random(35).toString(36)).join("");
-  const graph = mermaid.render(`g-${id}`, text ? text : "");
-
-  return (
-    <div
-      dangerouslySetInnerHTML={{
-        __html: graph,
-      }}
-    />
+  const intl = useIntl();
+  const id = useMemo(
+    () => lodash.times(20, () => lodash.random(35).toString(36)).join(""),
+    []
+  );
+  const containerRef = useRef<HTMLDivElement>(null);
+  const bindFunctionsRef = useRef<((element: Element) => void) | undefined>(
+    undefined
   );
+  const [state, setState] = useState<RenderState>({ status: "loading" });
+
+  useEffect(() => {
+    let cancelled = false;
+    bindFunctionsRef.current = undefined;
+
+    // mermaid v10+ 的 render 是异步的,返回 Promise<{svg, bindFunctions}>
+    mermaid
+      .render(`g-${id}`, text ?? "")
+      .then(({ svg, bindFunctions }) => {
+        if (cancelled) {
+          return;
+        }
+        bindFunctionsRef.current = bindFunctions;
+        setState({ status: "ready", svg });
+      })
+      .catch((error: unknown) => {
+        if (cancelled) {
+          return;
+        }
+        const message = error instanceof Error ? error.message : String(error);
+        console.error("[mermaid] render failed", error);
+        setState({ status: "error", message });
+      });
+
+    return () => {
+      cancelled = true;
+    };
+  }, [id, text]);
+
+  // svg 插入 DOM 之后再绑定交互回调(点击、链接等)
+  useEffect(() => {
+    if (
+      state.status === "ready" &&
+      bindFunctionsRef.current &&
+      containerRef.current
+    ) {
+      bindFunctionsRef.current(containerRef.current);
+    }
+  }, [state]);
+
+  if (state.status === "error") {
+    return (
+      <Alert
+        type="error"
+        showIcon
+        title={intl.formatMessage({
+          id: "labels.error.render-failed",
+          defaultMessage: "图形渲染失败",
+        })}
+        description={state.message}
+      />
+    );
+  }
+
+  if (state.status === "ready") {
+    return (
+      <div
+        ref={containerRef}
+        dangerouslySetInnerHTML={{ __html: state.svg }}
+      />
+    );
+  }
+
+  return null;
 };
 
 export default MermaidWidget;

+ 1 - 0
dashboard-v6/src/locales/en-US/label.ts

@@ -60,6 +60,7 @@ const items = {
   "labels.error.404": "没有找到指定的资源",
   "labels.error.429": "请求过于频繁",
   "labels.error.500": "Server Internal Error",
+  "labels.error.render-failed": "Diagram failed to render",
   "labels.task": "task",
   "labels.task.hall": "task hall",
   "labels.task.mine": "my task",

+ 1 - 0
dashboard-v6/src/locales/zh-Hans/label.ts

@@ -64,6 +64,7 @@ const items = {
   "labels.error.404": "没有找到指定的资源",
   "labels.error.429": "请求过于频繁",
   "labels.error.500": "服务器内部错误",
+  "labels.error.render-failed": "图形渲染失败",
   "labels.task": "任务",
   "labels.task.hall": "任务大厅",
   "labels.task.mine": "我的任务",

+ 1 - 0
dashboard-v6/src/locales/zh-Hant/label.ts

@@ -64,6 +64,7 @@ const items = {
   "labels.error.404": "沒有找到指定的資源",
   "labels.error.429": "請求過於頻繁",
   "labels.error.500": "伺服器內部錯誤",
+  "labels.error.render-failed": "圖形渲染失敗",
   "labels.task": "任務",
   "labels.task.hall": "任務大廳",
   "labels.task.mine": "我的任務",