Преглед на файлове

perf: 优化mermaid类图及时序图导入

liaojiaxing преди 2 месеца
родител
ревизия
d43b6cfcec

+ 15 - 7
apps/designer/src/pages/flow/components/Config/GraphStyle.tsx

@@ -98,11 +98,11 @@ export default function GraphStyle() {
       const data = firstNode.getData();
       setFormModel({
         ...formModel,
-        x: position.x,
-        y: position.y,
-        width: size.width,
-        height: size.height,
-        rotation: firstNode.angle(),
+        x: parseInt(position.x + ''),
+        y: parseInt(position.y + ''),
+        width: parseInt(size.width + ''),
+        height: parseInt(size.height + ''),
+        rotation: parseInt(firstNode.angle() + ''),
         text: data.text,
         fill: data.fill,
         stroke: data.stroke,
@@ -128,8 +128,8 @@ export default function GraphStyle() {
             setFormModel((state) => {
               return {
                 ...state,
-                width: args.current.width,
-                height: args.current.height,
+                width: parseInt(args.current.width),
+                height: parseInt(args.current.height),
               };
             });
           }
@@ -143,6 +143,14 @@ export default function GraphStyle() {
               };
             });
           }
+          if(args.key === 'angle') {
+            setFormModel((state) => {
+              return {
+                ...state,
+                rotation: parseInt(args.current),
+              };
+            });
+          }
         });
       }
     }

+ 82 - 12
apps/designer/src/pages/flow/components/ToolBar/MermaidModal.tsx

@@ -17,7 +17,8 @@ import Stadium from "@/components/flowchart/terminator";
 import DoubleCircle from "@/components/er/multivaluedAttribute";
 import Circle from "@/components/flowchart/onPageReference";
 import Diamond from "@/components/flowchart/decision";
-import { useModel } from "umi";
+import { isMermaidFlowchart } from "@/utils";
+import mermaid from "mermaid";
 
 export enum VERTEX_TYPE {
   RECTANGLE = "rectangle", // 矩形
@@ -41,7 +42,6 @@ export default forwardRef(function MermaidModal(
 ) {
   const [open, setOpen] = useState(false);
   const [mermaidCode, setMermaidCode] = useState("");
-  const { graph } = useModel("graphModel");
   const resultRef = useRef<MermaidResult>();
 
   useImperativeHandle(ref, () => ({
@@ -65,7 +65,7 @@ export default forwardRef(function MermaidModal(
     const idMap: Record<string, string> = {};
     list.forEach((item) => {
       // 节点处理
-      if (item.type !== "arrow") {
+      if (item.type !== "arrow" && item.type !== "line") {
         switch (item.type) {
           case VERTEX_TYPE.ROUND: {
             comp = Round;
@@ -97,8 +97,8 @@ export default forwardRef(function MermaidModal(
           ...comp.node,
           id,
           position: {
-            x: item.x,
-            y: item.y,
+            x: parseInt(item.x),
+            y: parseInt(item.y),
           },
           width: parseInt(item.width),
           height: parseInt(item.height),
@@ -113,11 +113,12 @@ export default forwardRef(function MermaidModal(
         };
         idMap[item.id] = id;
         cells.push(node);
-      } else {
+      } 
+      if (item.type === "arrow") {
         // 连线处理
         const edge = {
-          source: { cell: idMap[item.start.id] },
-          target: { cell: idMap[item.end.id] },
+          source: { cell: idMap[item.start?.id] },
+          target: { cell: idMap[item.end?.id] },
           labels: item?.label
             ? [
                 {
@@ -137,6 +138,70 @@ export default forwardRef(function MermaidModal(
     return cells;
   };
 
+  // Fallback to Svg
+  const convertSvgToGraphImage = (svgContainer: HTMLDivElement) => {
+    // Extract SVG width and height
+    // TODO: make width and height change dynamically based on user's screen dimension
+    const svgEl = svgContainer.querySelector("svg");
+    if (!svgEl) {
+      throw new Error("SVG element not found");
+    }
+    const rect = svgEl.getBoundingClientRect();
+    const width = rect.width;
+    const height = rect.height;
+
+    // Set width and height explictly since in firefox it gets set to 0
+    // if the width and height are not expilcitly set
+    // eg in some cases like er Diagram, gnatt, width and height is set as 100%
+    // which sets the dimensions as 0 in firefox and thus the diagram isn't rendered
+    svgEl.setAttribute("width", `${width}`);
+    svgEl.setAttribute("height", `${height}`);
+
+    // Convert SVG to image
+    const mimeType = "image/svg+xml";
+    const decoded = unescape(encodeURIComponent(svgEl.outerHTML));
+    const base64 = btoa(decoded);
+    const dataURL = `data:image/svg+xml;base64,${base64}`;
+
+    return {
+      type: "graphImage",
+      mimeType,
+      dataURL,
+      width,
+      height,
+    };
+  };
+
+  const handleSvgToImage = (svg: string) => {
+    // Append Svg to DOM
+    const svgContainer = document.createElement("div");
+    svgContainer.setAttribute(
+      "style",
+      `opacity: 0; position: relative; z-index: -1;`
+    );
+    svgContainer.innerHTML = svg;
+    svgContainer.id = "mermaid-diagram";
+    document.body.appendChild(svgContainer);
+
+    const res = convertSvgToGraphImage(svgContainer);
+    svgContainer.remove();
+    
+    resultRef.current = {
+      type: "image",
+      data: res?.dataURL,
+      width: res.width,
+      height: res.height,
+    };
+    const img = new Image();
+    img.src = res?.dataURL;
+    img.onload = () => {
+      if (mermaidRef.current) {
+        mermaidRef.current.innerHTML = "";
+        mermaidRef.current.appendChild(img);
+      }
+    };
+  };
+
   /**
    * 绘制图标
    */
@@ -144,7 +209,7 @@ export default forwardRef(function MermaidModal(
     // 获取渲染后的 svg
     try {
       resultRef.current = undefined;
-      const { elements, files } = await parseMermaidToExcalidraw(mermaidCode);
+      const { elements, files } = await parseMermaidToExcalidraw(mermaidCode.replaceAll('"', "'"));
       console.log("parse elements:", elements, files);
       // 转换失败,加载图片
       if (files) {
@@ -163,11 +228,16 @@ export default forwardRef(function MermaidModal(
           }
         };
       } else {
+        // 类图与时序图处理
+        if(!isMermaidFlowchart(mermaidCode)) {
+          const { svg } = await mermaid.render("mermaid", mermaidCode);
+          handleSvgToImage(svg);
+          return;
+        }
         // 转换json
         if (mermaidRef.current) {
           mermaidRef.current.innerHTML = "";
           const cells = toX6Json(elements);
-          console.log("parse cells:", cells);
           resultRef.current = { type: "cell", data: cells };
           const graphInstance = new Graph({
             container: mermaidRef.current,
@@ -201,7 +271,7 @@ export default forwardRef(function MermaidModal(
   };
 
   useEffect(() => {
-    if (mermaidCode) {
+    if (mermaidCode.trim()) {
       drawDiagram();
     } else {
       if (mermaidRef.current) {
@@ -247,7 +317,7 @@ export default forwardRef(function MermaidModal(
         </i>
       </div>
       <div className="flex gap-24px">
-        <div className="left flex-1 rounded-4px h-500px border-solid border-gray-200">
+        <div className="left flex-1 rounded-4px h-500px border-solid border-gray-200 overflow-y-auto">
           <Input.TextArea
             autoSize={{ minRows: 10 }}
             placeholder="请输入Mermaid代码"

+ 2 - 0
apps/designer/src/pages/flow/components/ToolBar/index.tsx

@@ -188,6 +188,8 @@ export default function ToolBar() {
             menu: nodeMenu,
           },
         });
+        graph?.cleanSelection();
+        node && graph?.select(node.id);
       } else {
         graph?.cleanSelection();
         // cell数据

+ 7 - 0
apps/designer/src/utils/index.ts

@@ -93,3 +93,10 @@ export const getToken = () => {
 
   return token;
 };
+
+// Mermaid 流程图检测
+export function isMermaidFlowchart(code: string): boolean {
+  const flowchartRegex = /^\s*graph|flowchart\s+(TD|LR|RL|TB)\s*\n/i;
+  const nodeRegex = /(\w+)\[(.*?)\]|\{|\}/;
+  return flowchartRegex.test(code) && nodeRegex.test(code);
+}

+ 0 - 7
apps/designer/src/utils/mermaidToX6Json.ts

@@ -156,13 +156,6 @@ function convertToX6Format(nodes: MermaidNode[], edges: MermaidEdge[]): any[] {
   return [...x6Nodes, ...x6Edges];
 }
 
-// Mermaid 流程图检测
-function isMermaidFlowchart(code: string): boolean {
-  const flowchartRegex = /^\s*graph|flowchart\s+(TD|LR|RL|TB)\s*\n/i;
-  const nodeRegex = /(\w+)\[(.*?)\]|\{|\}/;
-  return flowchartRegex.test(code) && nodeRegex.test(code);
-}
-
 export function mermaidToX6Json(mermaidCode: string): { isFlowchart: boolean, raw?: string, cells?: any[] } {
   if (!isMermaidFlowchart(mermaidCode)) {
     return { isFlowchart: false, raw: mermaidCode };