Browse Source

perf: 优化数据保存结构,添加与默认数据对比差异方法

liaojiaxing 3 months ago
parent
commit
ad687f9006

+ 1 - 1
apps/designer/src/config/data.ts

@@ -168,7 +168,7 @@ export const getDefaultDataByTheme = (themeKey: string) => {
         color: nodeStyle?.textColor || defaultData.text.color
       }
     }),
-    edgeDefaultData: merge(cloneDeep(defaultData), {
+    edgeDefaultData: merge(cloneDeep(BaseEdge), {
       attrs: {
         line: {
           stroke: edgeStyle?.strokeColor,

+ 98 - 78
apps/designer/src/events/flowEvent.ts

@@ -1,68 +1,43 @@
 import { Graph, Node, EventArgs, Cell } from "@antv/x6";
-import { BatchAddFlowchartElement, BatchEditFlowchartElement, BatchDeleteFlowchartElement } from "@/api/systemDesigner";
-import { getDefaultDataByTheme } from "@/config/data";
-import { differenceWith, toPairs, isEqual } from "lodash-es";
-
-// 对比查找默认数据修改的部分
-export const getDifferences = (source: any, target: any) => {
-  const differences = differenceWith(
-      toPairs(source),
-      toPairs(target),
-      isEqual
-  );
-
-  return differences;
-};
-
-// 获取基础数据
-export const getBasicData = (cellMeta: Cell.Properties) => {
-  const projectInfo = sessionStorage.getItem("system-design-project");
-  if(!projectInfo) {
-    return cellMeta; 
-  }
-  // 先根据主题获取通用数据,然后返回差异部分
-  const themeKey = JSON.parse(projectInfo)?.graph?.theme || '1';
-  const defaultData = getDefaultDataByTheme(themeKey);
-  if(cellMeta.shape === "edge") {
-    return getDifferences(cellMeta, defaultData.nodeDefaultData);
-  } else {
-    return getDifferences(cellMeta.data || {}, defaultData.nodeDefaultData);
-  }
-};
-
+import {
+  BatchAddFlowchartElement,
+  BatchEditFlowchartElement,
+  BatchDeleteFlowchartElement,
+} from "@/api/systemDesigner";
+import { getBasicData } from "@/utils/diffrence";
 export const handleGraphEvent = (graph: Graph) => {
   // 边开始拖拽点
   const sourceArrowhead = {
     name: "source-arrowhead",
     args: {
       attrs: {
-        d: 'M -5,-5 5,-5 5,5 -5,5 Z',
-        fill: '#fff',
-        stroke: '#239edd',
-        'stroke-width': 1,
+        d: "M -5,-5 5,-5 5,5 -5,5 Z",
+        fill: "#fff",
+        stroke: "#239edd",
+        "stroke-width": 1,
       },
-    }
-  }
+    },
+  };
   // 边结束拖拽点
   const targetArrowhead = {
     name: "target-arrowhead",
     args: {
       attrs: {
-        d: 'M -5,-5 5,-5 5,5 -5,5 Z',
-        fill: '#fff',
-        stroke: '#239edd',
-        'stroke-width': 1,
+        d: "M -5,-5 5,-5 5,5 -5,5 Z",
+        fill: "#fff",
+        stroke: "#239edd",
+        "stroke-width": 1,
       },
-    }
-  }
+    },
+  };
   // 边选中
   graph.on("edge:selected", (args) => {
-    args.edge.addTools(['edge-editor', sourceArrowhead, targetArrowhead]);
+    args.edge.addTools(["edge-editor", sourceArrowhead, targetArrowhead]);
   });
   // 边取消选中
   graph.on("edge:unselected", (args) => {
     setTimeout(() => {
-      args.edge.removeTools(['edge-editor', sourceArrowhead, targetArrowhead]);
+      args.edge.removeTools(["edge-editor", sourceArrowhead, targetArrowhead]);
     }, 100);
   });
 
@@ -113,7 +88,7 @@ export const handleGraphEvent = (graph: Graph) => {
 
   // 处理添加节点事件 noCreate为不用创建节点
   graph.on("node:added", ({ node }) => {
-    if(node.getData()?.noCreate) {
+    if (node.getData()?.noCreate) {
       setTimeout(() => {
         graph.removeNode(node.id);
       }, 50);
@@ -121,20 +96,64 @@ export const handleGraphEvent = (graph: Graph) => {
   });
 };
 
+const getCellData = (cellData: Cell.Properties) => {
+  const cellBasicData = getBasicData(cellData);
+  const {
+    id,
+    zIndex,
+    graphId,
+    parent,
+    position,
+    shape,
+    size,
+    view,
+    source,
+    target,
+    ports,
+  } = cellData;
+
+  const { attrs, data, router } = cellBasicData;
+  return cellData.shape === "edge"
+    ? // 边
+      {
+        zIndex,
+        source,
+        target,
+        id,
+        graphId,
+        data,
+        attrs,
+        router,
+        shape,
+      }
+    : // 节点
+      {
+        id,
+        graphId,
+        parent,
+        position,
+        shape,
+        size,
+        view,
+        zIndex,
+        source,
+        target,
+        ports: ports?.items ? { items: ports.items } : undefined,
+        data: cellBasicData,
+      };
+};
+
 export const handleGraphApiEvent = (graph: Graph) => {
   let timer2: any;
   let map2: Record<string, any> = {};
   graph.on("cell:added", (args) => {
-    if(args.cell?.data?.isPage) return;
+    if (args.cell?.data?.isPage) return;
     const data = args.cell.toJSON();
-    const cellBasicData = getBasicData(data);
-    console.log('新增数据:', cellBasicData)
     const setData = () => {
       const id = args.cell.id;
-      delete data.tools;
-      map2[id] = data;
-    }
-    if(timer2) {
+      map2[id] = getCellData(data);
+    };
+    if (timer2) {
       setData();
       return;
     }
@@ -142,13 +161,15 @@ export const handleGraphApiEvent = (graph: Graph) => {
     timer2 = setTimeout(() => {
       const graphId = sessionStorage.getItem("projectId");
       const list = Object.values(map2);
-      if(graphId && list.length > 0) {
-        BatchAddFlowchartElement(list.map(item => {
-          return {
-            ...item,
-            graphId
-          }
-        }));
+      if (graphId && list.length > 0) {
+        BatchAddFlowchartElement(
+          list.map((item) => {
+            return {
+              ...item,
+              graphId,
+            };
+          })
+        );
       }
       timer2 = null;
       map2 = {};
@@ -159,16 +180,13 @@ export const handleGraphApiEvent = (graph: Graph) => {
   let timer1: any;
   let map: Record<string, any> = {};
   graph.on("cell:change:*", (args: EventArgs["cell:change:*"]) => {
-    if(args.cell?.data?.isPage || args.key === 'tools' || timer2) return;
+    if (args.cell?.data?.isPage || args.key === "tools" || timer2) return;
     const setData = () => {
       const id = args.cell.id;
       const data = args.cell.toJSON();
-      delete data.tools;
-      map[id] = data;
-      const cellBasicData = getBasicData(data);
-      console.log('修改数据:', cellBasicData)
-    }
-    if(timer1) {
+      map[id] = getCellData(data);
+    };
+    if (timer1) {
       setData();
       return;
     }
@@ -176,32 +194,34 @@ export const handleGraphApiEvent = (graph: Graph) => {
     timer1 = setTimeout(() => {
       const graphId = sessionStorage.getItem("projectId");
       const list = Object.values(map);
-      if(graphId && list.length > 0) {
-        BatchEditFlowchartElement(list.map(item => {
-          return {
-            ...item,
-            graphId
-          }
-        }));
+      if (graphId && list.length > 0) {
+        BatchEditFlowchartElement(
+          list.map((item) => {
+            return {
+              ...item,
+              graphId,
+            };
+          })
+        );
       }
       timer1 = null;
       map = {};
     }, 1000);
   });
 
-  let timer: any
+  let timer: any;
   const ids: string[] = [];
   graph.on("cell:removed", (args) => {
     // 加入防抖 如果连续多个删除调用批量删除
-    if(timer) {
+    if (timer) {
       ids.push(args.cell.id);
       return;
     }
     ids.push(args.cell.id);
     timer = setTimeout(() => {
       timer = null;
-      if(ids.length > 0) {
-        BatchDeleteFlowchartElement({ids});
+      if (ids.length > 0) {
+        BatchDeleteFlowchartElement({ ids });
       }
       ids.splice(0, ids.length);
     }, 500);

+ 5 - 0
apps/designer/src/models/appModel.ts

@@ -108,6 +108,11 @@ export default function appModel() {
     })
   }
 
+  // 主题风格存入session其他地方使用
+  useEffect(() => {
+    sessionStorage.setItem("flow-theme-key", pageState.theme);
+  }, [pageState.theme]);
+
   useEffect(() => {
     const graphRoot = document.querySelector(
       "#graph-container"

+ 37 - 76
apps/designer/src/models/flowchartModel.ts

@@ -11,12 +11,11 @@ import { Export } from "@antv/x6-plugin-export";
 import { useModel } from "umi";
 import "@/components/PageContainer";
 import { handleGraphEvent, handleGraphApiEvent } from "@/events/flowEvent";
-import { pageMenu, nodeMenu, edgeMenu } from "@/utils/contentMenu";
+import { pageMenu, nodeMenu } from "@/utils/contentMenu";
 import { bindKeys } from "@/utils/fastKey";
 import { handleSetEdgeStyle } from "@/utils";
 import { getDefaultDataByTheme } from "@/config/data";
-import { merge } from "lodash-es";
-import { getCompMap } from "@/components/flowchartNode";
+import { getFlowNodeMetaByBasic } from "@/utils/flow";
 export default function flowchartModel() {
   const [graph, setGraph] = useState<Graph>();
   const [dnd, setDnd] = useState<Dnd>();
@@ -34,11 +33,12 @@ export default function flowchartModel() {
 
   useEffect(() => {
     // 用于恢复历史后画布更新
-    if(updateKey) {
+    if (updateKey) {
       graphRef.current?.off("cell:added");
       graphRef.current?.off("cell:change:*");
       graphRef.current?.off("cell:removed");
-      pageNodeRef?.current && graphRef.current?.resetCells([pageNodeRef?.current])
+      pageNodeRef?.current &&
+        graphRef.current?.resetCells([pageNodeRef?.current]);
     }
   }, [updateKey]);
   const addPageNode = () => {
@@ -65,52 +65,13 @@ export default function flowchartModel() {
     return pageNodeRef.current;
   };
 
+  // 初始化元素数据
   const initCells = (cells: Cell.Properties[], theme = "1") => {
     if (graphRef.current) {
       const defaultDataByTheme = getDefaultDataByTheme(theme);
-      // 添加节点
-      const list = (cells || []).map((item) => {
-        if (item.shape !== "edge") {
-          const nodeMeta = getCompMap()[item.shape as string] || {};
-          const newNode = {
-            ...item,
-            data: JSON.parse(item?.data || "{}"),
-            ports: JSON.parse(item?.ports  || "{}"),
-            size: JSON.parse(item?.size || "{}"),
-            position: JSON.parse(item?.position || "{}"),
-            tools: [{
-              name: "contextmenu",
-              args: {
-                menu: nodeMenu,
-              },
-            }]
-          };
-          const result = merge(nodeMeta, { data: defaultDataByTheme.nodeDefaultData }, newNode);
-          console.log(defaultDataByTheme.nodeDefaultData);
-          return result
-        }
-        if (item.shape === "edge") {
-          const newEdge = {
-            ...item,
-            data: JSON.parse(item?.data || "{}"),
-            attrs: JSON.parse(item?.attrs as unknown as string || "{}"),
-            source: JSON.parse(item?.source),
-            target: JSON.parse(item?.target),
-            ...(item?.connector ? {connector: JSON.parse(item.connector)} : {}),
-            ...(item?.labels ? {labels: JSON.parse(item.labels)} : {}),
-            router: item?.router ? 'manhattan' : '',
-            tools: [
-              {
-                name: "contextmenu",
-                args: {
-                  menu: edgeMenu,
-                },
-              },
-            ]
-          };
-          return merge(defaultDataByTheme.edgeDefaultData, newEdge);
-        }
-      });
+      // 添加节点,可能节点数据不完整,需要合并默认数据
+      const list = (cells || []).map((item) => getFlowNodeMetaByBasic(item, defaultDataByTheme));
+      console.log(list);
       graphRef.current.fromJSON({ cells: list as any });
       addPageNode();
       handleGraphApiEvent(graphRef.current);
@@ -138,7 +99,7 @@ export default function flowchartModel() {
   // 修改跨线展示
   useEffect(() => {
     const edges = graphRef.current?.getEdges();
-    (edges || []).forEach(edge => {
+    (edges || []).forEach((edge) => {
       handleSetEdgeStyle(edge, edge.data?.connectorType, pageState.jumpOver);
     });
   }, [pageState.jumpOver]);
@@ -236,32 +197,32 @@ export default function flowchartModel() {
   };
 
   /**组件库拖拽生成 */
-  const startDrag: any = useCallback((
-    e: React.MouseEvent<HTMLDivElement, MouseEvent>,
-    node: Node.Metadata
-  ) => {
-    if (!dndRef.current || !graphRef.current) return;
-    // 查找对应主题的默认数据
-    const defaultDataByTheme = getDefaultDataByTheme(pageState.theme || "1");
-
-    // 往画布添加节点
-    const n = graphRef.current.createNode({
-      ...node,
-      data: {
-        ...defaultDataByTheme.nodeDefaultData,
-        ...node.data
-      },
-      ports: node?.ports || defaultDataByTheme.ports
-    });
-    // 右键菜单
-    n.addTools({
-      name: "contextmenu",
-      args: {
-        menu: nodeMenu,
-      },
-    });
-    dndRef.current.start(n, e.nativeEvent as any);
-  }, [pageState.theme]);
+  const startDrag: any = useCallback(
+    (e: React.MouseEvent<HTMLDivElement, MouseEvent>, node: Node.Metadata) => {
+      if (!dndRef.current || !graphRef.current) return;
+      // 查找对应主题的默认数据
+      const defaultDataByTheme = getDefaultDataByTheme(pageState.theme || "1");
+
+      // 往画布添加节点
+      const n = graphRef.current.createNode({
+        ...node,
+        data: {
+          ...defaultDataByTheme.nodeDefaultData,
+          ...node.data,
+        },
+        ports: node?.ports || defaultDataByTheme.ports,
+      });
+      // 右键菜单
+      n.addTools({
+        name: "contextmenu",
+        args: {
+          menu: nodeMenu,
+        },
+      });
+      dndRef.current.start(n, e.nativeEvent as any);
+    },
+    [pageState.theme]
+  );
 
   // 撤销
   const onUndo = () => {
@@ -286,6 +247,6 @@ export default function flowchartModel() {
     onRedo,
     initCells,
     updateKey,
-    setUpdateKey
+    setUpdateKey,
   };
 }

+ 59 - 0
apps/designer/src/utils/diffrence.ts

@@ -0,0 +1,59 @@
+import { getDefaultDataByTheme } from "@/config/data";
+import { isEqual, isArray } from "lodash-es";
+import { Cell } from "@antv/x6";
+
+/**
+ * 获取两个对象之间的差异
+ * 该函数通过递归比较两个对象,找出它们之间的差异部分,并返回这些差异组成的对象
+ * 主要用于在两个较为复杂的对象结构中找到变化的部分,常用于数据同步、变更检测等场景
+ * 
+ * @param newData 新的数据对象,与旧数据对象进行比较以找出变化
+ * @param oldData 旧的数据对象,作为比较的基础
+ * @returns 返回一个对象,包含所有变化的属性和它们的新值
+ */
+export const getDifferences = <T extends Record<string, any> | any[]>(newData: T, oldData: T): Partial<T> => {
+  // 处理基础类型、null和undefined
+  if (typeof newData !== 'object' || newData === null) {
+    return !isEqual(newData, oldData) ? newData : {} as Partial<T>;
+  }
+
+  const differences: Partial<T> = {};
+
+  // 统一的比较处理函数
+  const compareValue = (value: any, oldValue: any, key: string | number) => {
+    if (typeof value === 'object' && value !== null) {
+      const differenceVal = getDifferences(value, oldValue || {});
+      if (!isEqual(differenceVal, {})) {
+        differences[key] = differenceVal;
+      }
+    } else if (!isEqual(value, oldValue)) {
+      differences[key] = value;
+    }
+  };
+
+  if (Array.isArray(newData)) {
+    newData.forEach((item, index) => {
+      compareValue(item, oldData?.[index], index);
+    });
+  } else {
+    Object.entries(newData).forEach(([key, value]) => {
+      compareValue(value, oldData?.[key], key);
+    });
+  }
+
+  return differences;
+};
+
+// 获取flow节点基础数据
+export const getBasicData = (cellMeta: Cell.Properties) => {
+  const themeKey = sessionStorage.getItem("flow-theme-key") || '1';
+
+  // 先根据主题获取通用数据,然后返回差异部分
+  const defaultData = getDefaultDataByTheme(themeKey);
+  if(cellMeta.shape === "edge") {
+    console.log(cellMeta, defaultData.edgeDefaultData)
+    return getDifferences(cellMeta, defaultData.edgeDefaultData);
+  } else {
+    return getDifferences(cellMeta.data || {}, defaultData.nodeDefaultData);
+  }
+};

+ 62 - 0
apps/designer/src/utils/flow.ts

@@ -0,0 +1,62 @@
+import { Cell } from "@antv/x6";
+import { merge, cloneDeep } from "lodash-es";
+import { getCompMap } from "@/components/flowchartNode";
+import { nodeMenu, edgeMenu } from "@/utils/contentMenu";
+export const getFlowNodeMetaByBasic = (
+  item: Cell.Properties,
+  defaultDataByTheme: any
+) => {
+  if (item.shape !== "edge") {
+    const nodeMeta = getCompMap()[item.shape as string] || {};
+    // ports 先取自身 再判断节点本身是否带有 最后取通用的ports
+    const ports: { items: any[]; groups: any[] } = nodeMeta?.ports || defaultDataByTheme.ports;
+    if (item.ports) {
+      ports.items = JSON.parse(item.ports || "")?.items || [];
+    }
+    const newNode = {
+      ...item,
+      data: JSON.parse(item?.data || "{}"),
+      size: JSON.parse(item?.size || "{}"),
+      position: JSON.parse(item?.position || "{}"),
+      tools: [
+        {
+          name: "contextmenu",
+          args: {
+            menu: nodeMenu,
+          },
+        },
+      ],
+      ports,
+    };
+    return merge(
+      nodeMeta,
+      {
+        data: defaultDataByTheme.nodeDefaultData,
+        ports: defaultDataByTheme.ports,
+      },
+      newNode
+    );
+  } else {
+    const router = item?.router?.includes('{') ? JSON.parse(item?.router) : item?.router
+    const newEdge = {
+      ...item,
+      shape: "edge",
+      data: JSON.parse(item?.data || "{}"),
+      attrs: JSON.parse((item?.attrs as unknown as string) || "{}"),
+      source: JSON.parse(item?.source),
+      target: JSON.parse(item?.target),
+      ...(item?.connector ? { connector: JSON.parse(item.connector) } : {}),
+      ...(item?.labels ? { labels: JSON.parse(item.labels) } : {}),
+      router: router || "manhattan",
+      tools: [
+        {
+          name: "contextmenu",
+          args: {
+            menu: edgeMenu,
+          },
+        },
+      ],
+    };
+    return merge(cloneDeep(defaultDataByTheme.edgeDefaultData), newEdge);
+  }
+};