liaojiaxing 6 місяців тому
батько
коміт
453f4b83e8

+ 2 - 2
apps/designer/src/components/mindMap/Topic.tsx

@@ -26,7 +26,7 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
     tags,
     extraModules,
     remark,
-    link,
+    herf,
     children,
     type,
     collapsed,
@@ -174,7 +174,7 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
             </div>
 
             <div ref={remarkRef}>
-              {link && <Link link={link} />}
+              {herf && <Link link={herf} />}
               {remark && <i className="iconfont icon-pinglun1" />}
             </div>
           </div>

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

@@ -138,7 +138,8 @@ export const topicData = {
     width: 3,
   },
   children: [],
-  extraModules: undefined
+  extraModules: undefined,
+  links: []
 }
 
 // 初始化项目

+ 111 - 47
apps/designer/src/events/mindMapEvent.ts

@@ -11,9 +11,9 @@ import { selectTopic } from "@/utils/mindmapHander";
 import { uuid } from "@/utils";
 
 enum positionType {
-  left = 'left',
-  right = 'right',
-  outside = 'outside',
+  left = "left",
+  right = "right",
+  outside = "outside",
 }
 // 拖拽主题时指示器
 let indicatorNode: Node | undefined;
@@ -98,7 +98,6 @@ export const bindMindMapEvents = (
     if (currentShadowNode && setMindProjectInfo) {
       topicDragHander(
         graph,
-        setMindProjectInfo,
         { x: args.x, y: args.y },
         args.node
       );
@@ -108,13 +107,18 @@ export const bindMindMapEvents = (
   graph.on("node:mouseup", (args) => {
     // 拖拽结束
     if (indicatorNode && insertNode) {
-      graph.removeCell(args.node.id + '-edge');
-      setMindProjectInfo && handleSwitchPosition(setMindProjectInfo, args.node.id, insertNode.id);
+      graph.removeCell(args.node.id + "-edge");
+      setMindProjectInfo &&
+        handleSwitchPosition(setMindProjectInfo, args.node.id, insertNode.id);
     }
 
     // 成为自由节点
-    if(currentShadowNode && !indicatorNode && args.node.data?.parentId) {
-      setMindProjectInfo && handleSwitchPosition(setMindProjectInfo, args.node.id, undefined, { x: args.x, y: args.y});
+    if (currentShadowNode && !indicatorNode && args.node.data?.parentId) {
+      setMindProjectInfo &&
+        handleSwitchPosition(setMindProjectInfo, args.node.id, undefined, {
+          x: args.x,
+          y: args.y,
+        });
     }
 
     currentShadowNode && setShadowMode(false, args.node, graph);
@@ -131,17 +135,16 @@ export const bindMindMapEvents = (
 
   graph.on("node:move", (args) => {
     // 自由节点拖拽
-    if(!args.node.data?.parentId) {
+    if (!args.node.data?.parentId) {
       setShadowMode(true, args.node, graph);
     }
   });
 
   graph.on("node:moving", (args) => {
-    if(!args.node.data?.parentId && setMindProjectInfo) {
+    if (!args.node.data?.parentId && setMindProjectInfo) {
       setShadowMode(false, args.node, graph);
       topicDragHander(
         graph,
-        setMindProjectInfo,
         { x: args.x, y: args.y },
         args.node
       );
@@ -149,7 +152,7 @@ export const bindMindMapEvents = (
   });
 
   graph.on("node:moved", (args) => {
-    if(!args.node.data?.parentId) {
+    if (!args.node.data?.parentId) {
       setShadowMode(false, args.node, graph);
     }
   });
@@ -171,12 +174,14 @@ export const bindMindMapEvents = (
     }
   });
 
-  // 节点数据更改
+  /**
+   * 节点数据更改
+   */
   graph.on("node:change:*", (args) => {
     // console.log("node:change:*", args);
     const { current, previous } = args;
     if (args.key === "data") {
-      // 收折子项
+      // 收折子项 setMindProjectInfo更新会重新渲染
       if (current.collapsed !== previous.collapsed) {
         setMindProjectInfo &&
           updateTopic(
@@ -184,8 +189,24 @@ export const bindMindMapEvents = (
             { collapsed: current.collapsed },
             setMindProjectInfo
           );
-      } else {
-        updateTopic(args.cell.id, current, (info) => {
+        return;
+      }
+      if(current?.links && current.links.length !== previous?.links?.length) {
+        setMindProjectInfo &&
+          updateTopic(
+            args.cell.id,
+            { links: current.links },
+            setMindProjectInfo
+          );
+      } 
+      // 本地缓存更新不会重新渲染
+      updateTopic(args.cell.id, current, (info) => {
+        localStorage.setItem("minMapProjectInfo", JSON.stringify(info));
+      });
+    }
+    if(args.key === "position") {
+      if(args.cell.isNode() && !args.cell.data.parentId) {
+        updateTopic(args.cell.id, {...args.cell.data, x: current.x, y: current.y}, (info) => {
           localStorage.setItem("minMapProjectInfo", JSON.stringify(info));
         });
       }
@@ -203,6 +224,37 @@ export const bindMindMapEvents = (
       );
     }
   });
+
+  /**
+   * 连接线更改
+   */
+  graph.on("edge:change:*", args => {
+    if(args.key === "vertices" || args.key === "labels") {
+      const link = args.edge.toJSON();
+      const source = args.edge.getSourceCell();
+      source?.setData({
+        links: source.data?.links.map((item: Edge.Properties) => {
+          if(item.id === link.id) return link;
+          return item;
+        })
+      })
+    }
+  });
+
+  /**
+   * 连接线删除
+   */
+  graph.on("edge:removed", (args) => {
+    if(args.edge.data?.isLink) {
+      // @ts-ignore
+      const source = graph.getCellById(args.edge.source?.cell as string);
+      source?.setData({
+        links: source.data?.links?.filter((item: Edge.Properties) => item.id !== args.edge.id)
+      }, {
+        deep: false
+      });
+    }
+  })
 };
 
 /**
@@ -276,8 +328,8 @@ const addIndicator = (
         rx: 2,
         ry: 2,
         style: {
-          opacity: 0.6
-        }
+          opacity: 0.6,
+        },
       },
     },
   });
@@ -313,8 +365,8 @@ const addIndicator = (
           name: "",
         },
         style: {
-          opacity: 0.6
-        }
+          opacity: 0.6,
+        },
       },
     },
   });
@@ -373,7 +425,6 @@ const setIndicator = (
     }
     // 外部位置
     case positionType.outside: {
-
     }
   }
 };
@@ -407,7 +458,6 @@ const isDescendantNode = (targetNode: Node, originNode: Node, graph: Graph) => {
  */
 export const topicDragHander = (
   graph: Graph,
-  setMindProjectInfo: (info: MindMapProjectInfo) => void,
   position: { x: number; y: number },
   originNode: Node
 ) => {
@@ -440,11 +490,11 @@ export const topicDragHander = (
 
 /**
  * 拖拽完毕,切换主题位置
- * @param setMindProjectInfo 
+ * @param setMindProjectInfo
  * @param sourceId 拖拽的节点
  * @param targetId 放置的节点
  * @param position 自由节点放置的位置
- * @returns 
+ * @returns
  */
 const handleSwitchPosition = (
   setMindProjectInfo: (info: MindMapProjectInfo) => void,
@@ -453,39 +503,45 @@ const handleSwitchPosition = (
   position?: { x: number; y: number }
 ) => {
   const mindmapProjectInfo = getMindMapProjectByLocal();
-  if(!mindmapProjectInfo) return;
+  if (!mindmapProjectInfo) return;
 
   // 找到要拖拽的节点并删除
   let source: TopicItem | undefined;
-  mindmapProjectInfo.topics.forEach(topic => {
-    if(topic.id === sourceId) {
+  mindmapProjectInfo.topics.forEach((topic) => {
+    if (topic.id === sourceId) {
       source = topic;
-    };
-    mindmapProjectInfo.topics = mindmapProjectInfo.topics.filter(item => item.id !== sourceId);
-  })
-  const topics = source 
-    ? mindmapProjectInfo.topics 
-    :traverseNode(mindmapProjectInfo.topics, (topic) => {
-    const findItem = topic?.children?.find(item => item.id === sourceId);
-    if(findItem) {
-      source = findItem;
-      topic.children = topic.children?.filter(item => item.id !== sourceId);
     }
+    mindmapProjectInfo.topics = mindmapProjectInfo.topics.filter(
+      (item) => item.id !== sourceId
+    );
   });
-  if(!source) return;
+  const topics = source
+    ? mindmapProjectInfo.topics
+    : traverseNode(mindmapProjectInfo.topics, (topic) => {
+        const findItem = topic?.children?.find((item) => item.id === sourceId);
+        if (findItem) {
+          source = findItem;
+          topic.children = topic.children?.filter(
+            (item) => item.id !== sourceId
+          );
+        }
+      });
+  if (!source) return;
 
   if (targetId) {
     // 加入到目标节点下
     mindmapProjectInfo.topics = traverseNode(topics, (topic) => {
-      if(topic.id === targetId) {
-        if(!topic.children) {
+      if (topic.id === targetId) {
+        if (!topic.children) {
           topic.children = [];
         }
-        source && topic.children.push({
-          ...source,
-          type: topic.type === TopicType.main ? TopicType.branch : TopicType.sub,
-          parentId: topic.id
-        })
+        source &&
+          topic.children.push({
+            ...source,
+            type:
+              topic.type === TopicType.main ? TopicType.branch : TopicType.sub,
+            parentId: topic.id,
+          });
       }
     });
   } else {
@@ -502,8 +558,16 @@ const handleSwitchPosition = (
       x: position?.x,
       y: position?.y,
       type: TopicType.branch,
-      parentId: null
-    })
+      parentId: null,
+      links: (source.links || []).map(item => {
+        // 修改sourceId
+        item.source = {
+          ...item.source,
+          id
+        };
+        return item;
+      })
+    });
   }
 
   setMindProjectInfo(mindmapProjectInfo);

+ 104 - 3
apps/designer/src/models/mindMapModel.ts

@@ -1,7 +1,7 @@
 import { cellStyle } from "@/types";
-import { Cell, EventArgs, Graph } from "@antv/x6";
+import { Cell, Edge, EventArgs, Graph, Node } from "@antv/x6";
 import { message } from "antd";
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useEffect, useRef, useState } from "react";
 import { Selection } from "@repo/x6-plugin-selection";
 import { Keyboard } from "@antv/x6-plugin-keyboard";
 import { History } from "@antv/x6-plugin-history";
@@ -11,7 +11,7 @@ import { Clipboard } from "@antv/x6-plugin-clipboard";
 import { MindMapProjectInfo } from "@/types";
 import { bindMindMapEvents } from "@/events/mindMapEvent";
 import { useLocalStorageState } from "ahooks";
-import { renderMindMap } from "@/pages/mindmap/mindMap";
+import { renderMindMap, updateTopic } from "@/pages/mindmap/mindMap";
 import { defaultProject } from "@/config/data";
 import { TopicType } from "@/enum";
 import { isEqual } from "lodash-es";
@@ -245,6 +245,106 @@ export default function mindMapModel() {
     setRightToolbarActive(rightToobarActive === type ? undefined : type);
   };
 
+
+  // 关联线
+  const handleCorrelation = (e: MouseEvent) => {
+    if(correlationEdgeRef.current) {
+      const point = graph?.clientToLocal(e.x, e.y);
+      point && correlationEdgeRef.current?.setTarget(point);
+    }
+  };
+  const correlationEdgeRef = useRef<Edge>();
+  const setCorrelationEdgeInfo = (sourceNode?: Node) => {
+    if(sourceNode) {
+      correlationEdgeRef.current = graph?.addEdge({
+        source: { cell: sourceNode},
+        target: {
+          x: sourceNode.position().x,
+          y: sourceNode.position().y,
+        },
+        connector: "normal",
+        attrs: {
+          line: {
+            stroke: "#71cb2d",
+            strokeWidth: 2,
+            sourceMarker: {
+              name: "",
+            },
+            targetMarker: {
+              name: "",
+            },
+            style: {
+              opacity: 0.6,
+            },
+          },
+        },
+        data: {
+          ignoreDrag: true
+        },
+        zIndex: 0
+      });
+      document.body.addEventListener("mousemove", handleCorrelation);
+    } else {
+      document.body.removeEventListener("mousemove", handleCorrelation);
+      if(correlationEdgeRef.current) {
+        graph?.removeCell(correlationEdgeRef.current);
+        correlationEdgeRef.current = undefined;
+      }
+    }
+  }
+
+  const handleAddCorrelation = (source: Cell, target: Cell) => {
+    const link = graph?.createEdge({
+      source: {
+        cell: source.id,
+        connectionPoint: "rect",
+        anchor: "center"
+      },
+      target: {
+        cell: target.id,
+        connectionPoint: "rect",
+        anchor: "center"
+      },
+      zIndex: 0,
+      connector: "smooth",
+      attrs: {
+        line: {
+          stroke: "#71cb2d",
+          strokeWidth: 2,
+        },
+      },
+      data: {
+        isLink: true
+      },
+      tools: ["vertices", "edge-editor", "button-remove"]
+    }).toJSON();
+    if(link) {
+      source.setData({
+        links: [...(source.data?.links || []), link]
+      });
+    }
+  }
+
+  useEffect(() => {
+    if(graph) {
+      graph.on("node:click", (args) => {
+        if(correlationEdgeRef.current) {
+          handleAddCorrelation(correlationEdgeRef.current.getSourceCell()!, args.node);
+        }
+      })
+      graph.on("blank:click", () => {
+        setTimeout(() => {
+          setCorrelationEdgeInfo(undefined);
+        }, 50);
+      });
+      graph.on("cell:click", () => {
+        setTimeout(() => {
+          setCorrelationEdgeInfo(undefined);
+        }, 50);
+      });
+    }
+  }, [graph]);
+
   return {
     graph,
     selectedCell,
@@ -259,5 +359,6 @@ export default function mindMapModel() {
     onRedo,
     enableFormatBrush,
     toggleFormatBrush,
+    setCorrelationEdgeInfo,
   };
 }

+ 6 - 3
apps/designer/src/pages/mindmap/components/HeaderToolbar/index.tsx

@@ -18,7 +18,8 @@ export default function index() {
     enableFormatBrush,
     toggleFormatBrush,
     graph,
-    selectedCell
+    selectedCell,
+    setCorrelationEdgeInfo,
   } = useModel("mindMapModel");
 
   const currentNode = useMemo(() => {
@@ -56,8 +57,10 @@ export default function index() {
   };
 
   // 添加关联线
-  const handleAddLink = () => {
+  const handleAddCorrelation = () => {
+    if(!currentNode.length) return;
 
+    setCorrelationEdgeInfo(currentNode[0]);
   };
 
   const menuData: MenuProps["items"] = [
@@ -233,7 +236,7 @@ export default function index() {
           type="text"
           icon={<i className="iconfont icon-guanlianxian"></i>}
           disabled={!currentNode.length}
-          onClick={handleAddLink}
+          onClick={handleAddCorrelation}
         />
       </Tooltip>
 

+ 63 - 46
apps/designer/src/pages/mindmap/mindMap.tsx

@@ -17,33 +17,30 @@ interface HierarchyResult {
 }
 /**
  * 渲染思维导图项目
- * @param graph 
+ * @param graph
  */
-export const renderMindMap = (
-  graph: Graph,
-  setMindProjectInfo: () => void
-) => {
+export const renderMindMap = (graph: Graph, setMindProjectInfo: () => void) => {
   const projectInfo = getMindMapProjectByLocal();
-  if(!projectInfo) return;
+  if (!projectInfo) return;
   const { topics, pageSetting } = projectInfo;
   const cells: Cell[] = [];
   topics.forEach((topic) => {
     // 遍历出层次结构
-    const result: HierarchyResult = hierarchyMethodMap[projectInfo.structure]?.(topic, pageSetting);
+    const result: HierarchyResult = hierarchyMethodMap[projectInfo.structure]?.(
+      topic,
+      pageSetting
+    );
 
     let originPosition = { x: topic?.x ?? -10, y: topic?.y ?? -10 };
-    if(graph.hasCell(topic.id)) {
+    if (graph.hasCell(topic.id)) {
       const node = graph.getCellById(topic.id);
-      if(node.isNode()) {
+      if (node.isNode()) {
         originPosition = node.position();
       }
     }
     const offsetX = originPosition.x - result.x;
     const offsetY = originPosition.y - result.y;
-    const traverse = (
-      hierarchyItem: HierarchyResult,
-      parent?: Node
-    ) => {
+    const traverse = (hierarchyItem: HierarchyResult, parent?: Node) => {
       if (hierarchyItem) {
         const { data, children, x, y } = hierarchyItem;
         const id = data?.id || uuid();
@@ -63,12 +60,21 @@ export const renderMindMap = (
         });
         cells.push(node);
         parent && parent.addChild(node);
+        if(data?.links) {
+          cells.push(...data.links.map(item => graph.createEdge(item)));
+        }
 
         if (children) {
           children.forEach((item: HierarchyResult) => {
             cells.push(
               // 创建连线
-              createEdge(graph, id, item, projectInfo.structure, projectInfo.theme)
+              createEdge(
+                graph,
+                id,
+                item,
+                projectInfo.structure,
+                projectInfo.theme
+              )
             );
             // 递归遍历
             traverse(item, node);
@@ -76,33 +82,37 @@ export const renderMindMap = (
         }
       }
     };
-    
+
     traverse(result);
   });
   // 处理节点
-  cells.filter(cell => cell.isNode()).forEach((cell) => {
-    // 存在更新位置,否则添加
-    if (graph.hasCell(cell.id)) {
-      const oldCell = graph.getCellById(cell.id);
-      if(oldCell.isNode()) {
-        oldCell.position(cell.position().x, cell.position().y);
-        oldCell.setData({
-          ...cell.data
-        })
+  cells
+    .filter((cell) => cell.isNode())
+    .forEach((cell) => {
+      // 存在更新位置,否则添加
+      if (graph.hasCell(cell.id)) {
+        const oldCell = graph.getCellById(cell.id);
+        if (oldCell.isNode()) {
+          oldCell.position(cell.position().x, cell.position().y);
+          oldCell.setData({
+            ...cell.data,
+          });
+        }
+      } else {
+        graph.addCell(cell);
       }
-    } else {
+    });
+  cells
+    .filter((cell) => cell.isEdge())
+    .forEach((cell) => {
+      // graph.removeCell(cell.id);
       graph.addCell(cell);
-    }
-  });
-  cells.filter(cell => cell.isEdge()).forEach((cell) => {
-    graph.removeCell(cell.id);
-    graph.addCell(cell);
-  })
+    });
   const oldCells = graph.getCells();
   // 移除不存在的节点
   oldCells.forEach((cell) => {
-    if (!cells.find(item => cell.id === item.id)) {
-      graph.removeCell(cell.id + '-edge');
+    if (!cells.find((item) => cell.id === item.id)) {
+      graph.removeCell(cell.id + "-edge");
       graph.removeCell(cell);
     }
   });
@@ -116,17 +126,21 @@ export const addTopic = (
   type: TopicType,
   setMindProjectInfo: (info: MindMapProjectInfo) => void,
   node?: Node,
-  otherData: Record<string, any> = {},
+  otherData: Record<string, any> = {}
 ) => {
   const projectInfo = getMindMapProjectByLocal();
   if (!projectInfo || !setMindProjectInfo) return;
 
-  const topic = buildTopic(type, {
-    ...(otherData || {}),
-    parentId: node?.id
-  }, node);
+  const topic = buildTopic(
+    type,
+    {
+      ...(otherData || {}),
+      parentId: node?.id,
+    },
+    node
+  );
 
-  if( node) {
+  if (node) {
     const parentId = node.id;
     const traverse = (topics: TopicItem[]) => {
       topics.forEach((item) => {
@@ -155,18 +169,18 @@ const topicMap = {
   [TopicType.main]: {
     label: "中心主题",
     width: 206,
-    height: 70
+    height: 70,
   },
   [TopicType.branch]: {
     label: "分支主题",
     width: 104,
-    height: 40
+    height: 40,
   },
   [TopicType.sub]: {
     label: "子主题",
     width: 76,
-    height: 27
-  }
+    height: 27,
+  },
 };
 
 /**
@@ -182,7 +196,10 @@ export const buildTopic = (
 ) => {
   const projectInfo = getMindMapProjectByLocal();
 
-  const theme = getTheme(projectInfo?.theme, type === TopicType.sub ? parentNode : undefined);
+  const theme = getTheme(
+    projectInfo?.theme,
+    type === TopicType.sub ? parentNode : undefined
+  );
   return {
     ...topicData,
     id: uuid(),
@@ -231,7 +248,7 @@ export const updateTopic = (
 ) => {
   const projectInfo = getMindMapProjectByLocal();
   if (!projectInfo || !setMindProjectInfo) return;
-  
+
   const traverse = (topics: TopicItem[]) => {
     topics.forEach((item) => {
       if (item.id === id) {
@@ -245,4 +262,4 @@ export const updateTopic = (
 
   traverse(projectInfo?.topics || []);
   setMindProjectInfo(projectInfo);
-}
+};

+ 5 - 1
apps/designer/src/types.d.ts

@@ -93,7 +93,7 @@ export interface TopicItem {
   /**
    * 链接
    */
-  link?: {
+  herf?: {
     title: string;
     value: string;
   };
@@ -124,6 +124,10 @@ export interface TopicItem {
    * 折叠子节点
    */
   collapsed?: boolean;
+  /**
+   * 关联线
+   */
+  links?: any[];
 }
 export interface MindMapProjectInfo{
   name: string;