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

feat: 添加节点快捷添加功能

liaojiaxing 1 неделя назад
Родитель
Сommit
42dfd68660

+ 0 - 1
apps/designer/src/utils/mindmapHander.tsx

@@ -273,7 +273,6 @@ export const handleMindmapPaste = (
             } else {
               // 根据空格或者换行切分
               const labels = text.split("\n").map(txt => txt.trim().split(" ")).flat();
-              console.log(labels);
               labels.forEach(label => {
                 if(label.trim()) {
                   const topic = addTopic(

+ 14 - 8
apps/flowchart-designer/src/components/NodeMenu.tsx

@@ -9,7 +9,7 @@ import and from "@/assets/wf_icon_and.gif";
 import handle from "@/assets/wf_icon_handle.gif";
 import judge from "@/assets/wf_icon_judge.gif";
 import or from "@/assets/wf_icon_or.gif";
-import { Graph } from "@antv/x6";
+import { Graph, Node } from "@antv/x6";
 
 const items = [
   { key: NodeType.START, icon: start, text: "开始" },
@@ -21,22 +21,28 @@ const items = [
   { key: NodeType.END, icon: end, text: "结束" },
 ];
 
-export default function NodeMenu(props: { graph?: Graph}) {
+export default function NodeMenu(props: {
+  graph?: Graph;
+  onChange?: (n?: Node) => void;
+  position?: { x: number; y: number };
+}) {
   const { graph } = props;
-  const handleAddNode = (type: NodeType) => { 
-    const node = nodes.find(item => item.type === type);
-    const { width = 100, height = 100} = graph?.getGraphArea() || {};
+  const handleAddNode = (type: NodeType) => {
+    const node = nodes.find((item) => item.type === type);
+    const { width = 100, height = 100 } = graph?.getGraphArea() || {};
     const rect = graph?.getAllCellsBBox();
     const y = rect ? rect.y + rect.height - 80 : height / 2 - 100;
 
-    graph?.addNode({
+    const n = graph?.addNode({
       shape: node?.name,
-      position: {
+      position: props?.position || {
         x: width / 2 - 150,
         y,
       },
       data: node?.data,
     });
+
+    props?.onChange?.(n);
   };
 
   return (
@@ -46,7 +52,7 @@ export default function NodeMenu(props: { graph?: Graph}) {
           <div
             className="w-[38%] border-box h-40px px-12px rounded-8px flex items-center cursor-pointer hover:bg-#eff0f8"
             key={item.key}
-            onClick={() => handleAddNode(item.key)}
+            onClick={(e) => {handleAddNode(item.key)}}
           >
             <img src={item.icon} className="w-24px" />
             <span>{item.text}</span>

+ 2 - 1
apps/flowchart-designer/src/components/nodes/And.tsx

@@ -46,7 +46,8 @@ export default ({ node, graph }: { node: Node; graph: Graph }) => {
         }}
       />
 
-      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }}  />
+      <Port hovered={hovered} out={false} style={{ left: -7, cursor: "default" }}  node={node} graph={graph} />
+      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }}  node={node} graph={graph} />
     </div>
   );
 };

+ 2 - 1
apps/flowchart-designer/src/components/nodes/AutoHandle.tsx

@@ -46,7 +46,8 @@ export default ({ node, graph }: { node: Node; graph: Graph }) => {
         }}
       />
 
-      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }} />
+      <Port hovered={hovered} out={false} style={{ left: -7, cursor: "default" }}  node={node} graph={graph} />
+      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }}  node={node} graph={graph} />
     </div>
   );
 };

+ 2 - 1
apps/flowchart-designer/src/components/nodes/Decision.tsx

@@ -46,7 +46,8 @@ export default ({ node, graph }: { node: Node; graph: Graph }) => {
         }}
       />
 
-      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }} />
+      <Port hovered={hovered} out={false} style={{ left: -7, cursor: "default" }}  node={node} graph={graph} />
+      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }}  node={node} graph={graph} />
     </div>
   );
 };

+ 1 - 1
apps/flowchart-designer/src/components/nodes/End.tsx

@@ -46,7 +46,7 @@ export default ({ node, graph }: { node: Node; graph: Graph }) => {
         }}
       />
 
-      <Port out={false} hovered={hovered} style={{ left: -7 }} />
+      <Port out={false} hovered={hovered} style={{ left: -7 }} node={node} graph={graph} />
     </div>
   );
 };

+ 2 - 2
apps/flowchart-designer/src/components/nodes/Handle.tsx

@@ -46,8 +46,8 @@ export default ({ node, graph }: { node: Node; graph: Graph }) => {
         }}
       />
 
-      <Port out={false} hovered={hovered} style={{ left: -7, cursor: "crosshair" }} />
-      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }} />
+      <Port hovered={hovered} out={false} style={{ left: -7, cursor: "default" }}  node={node} graph={graph} />
+      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }}  node={node} graph={graph} />
     </div>
   );
 };

+ 2 - 1
apps/flowchart-designer/src/components/nodes/Link.tsx

@@ -46,7 +46,8 @@ export default ({ node, graph }: { node: Node; graph: Graph }) => {
         }}
       />
 
-      <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }} />
+       <Port hovered={hovered} out={false} style={{ left: -7, cursor: "default" }}  node={node} graph={graph} />
+       <Port hovered={hovered} style={{ right: -7, cursor: "crosshair" }}  node={node} graph={graph} />
     </div>
   );
 };

+ 47 - 0
apps/flowchart-designer/src/components/nodes/PopoverNode.tsx

@@ -0,0 +1,47 @@
+import { Graph, Node } from "@antv/x6";
+import React from "react";
+import NodeMenu from "../NodeMenu";
+import { Popover } from "antd";
+
+export default function PopoverNode({
+  node,
+  graph,
+}: {
+  node: Node;
+  graph: Graph;
+}) {
+  const handleChange = (addNode?: Node) => {
+    node.prop("addedNode", { addNode });
+  };
+
+  const handleChangeOpen = (open: boolean) => {
+    if (!open) {
+      node.prop("closedPopover", {});
+      node.removeProp("closedPopover");
+      node.removeProp("addedNode");
+      setTimeout(() => {
+        graph.removeCells([node]);
+      }, 300);
+    }
+  };
+
+  return (
+    <div className="w-full h-full">
+      <Popover
+        content={
+          <NodeMenu
+            graph={graph}
+            onChange={handleChange}
+            position={node.position()}
+          />
+        }
+        getPopupContainer={(n) => n}
+        trigger={"click"}
+        placement="bottom"
+        arrow={false}
+        defaultOpen={true}
+        onOpenChange={handleChangeOpen}
+      ></Popover>
+    </div>
+  );
+}

+ 135 - 7
apps/flowchart-designer/src/components/nodes/Port.tsx

@@ -1,6 +1,6 @@
 import { PlusOutlined } from "@ant-design/icons";
-import { Graph, Node } from "@antv/x6";
-import React from "react";
+import { Edge, EventArgs, Graph, Node } from "@antv/x6";
+import React, { useEffect, useRef } from "react";
 import { Dropdown, Popover } from "antd";
 import NodeMenu from "../NodeMenu";
 
@@ -13,6 +13,10 @@ export default function Port(props: {
 }) {
   const { hovered, style = {}, out = true, node, graph } = props;
   const [canAdd, setCanAdd] = React.useState(false);
+  const [open, setOpen] = React.useState(false);
+  const isDown = useRef(false);
+  const isMove = useRef(false);
+  const newEdge = React.useRef<Edge>();
 
   const extraStyle = React.useMemo(() => {
     if (out && canAdd) {
@@ -29,28 +33,152 @@ export default function Port(props: {
     graph?.togglePanning(!value);
   };
 
-  const handleClickPort = (e: React.MouseEvent<HTMLDivElement>) => {
+  const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
+    isDown.current = true;
+  };
+
+  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
     
+  }
+
+  useEffect(() => {
+    graph?.on("node:mouseup", (args) => {
+      console.log("node:mouseup");
+      isDown.current = false;
+      isMove.current = false;
+      // 拖拽过程中释放鼠标 检测当前是否有节点
+      if(newEdge.current) {
+        // 检测当前位置是否有元素
+        const nodes = graph.getNodesInArea(args.x, args.y, 5, 5);
+        if(!nodes.length) {
+          console.log('展示添加菜单')
+          graph.addNode({
+            shape: 'menu-popover',
+            x: args.x,
+            y: args.y,
+          })
+        }
+      }
+    });
+
+    // 检测移动 添加边 设置边目标位置
+    graph?.on("node:mousemove", (args) => {
+      if(isDown.current) {
+        // 按下还没移动时创建边
+        if(!isMove.current && node) {
+          isMove.current = true;
+          const ports = node.getPorts();
+          const rightPort = ports.find(item => item.group === 'right');
+          newEdge.current = graph?.addEdge({
+            source: { cell: node.id, port: rightPort?.id},
+            connector: { name: 'smooth' },
+            target: {
+              x: args.x,
+              y: args.y
+            },
+            attrs: {
+              line: {
+                stroke: "#37d0ff",
+                strokeWidth: 2,
+              },
+            }
+          });
+        } else {
+          // 否则修改边的终点
+          newEdge.current?.setTarget({
+            x: args.x,
+            y: args.y
+          });
+        }
+      }
+    });
+
+    // 连续添加节点完成
+    graph?.on("node:change:addedNode", (args: EventArgs["cell:change:*"]) => {
+      const { current } = args;
+      if(newEdge.current && current) {
+        const addNode = current?.addNode as Node;
+        const ports = addNode.getPorts();
+        const leftPort = ports?.find(item => item.group === 'left');
+        newEdge.current.setTarget({
+          cell: addNode.id,
+          port: leftPort?.id
+        });
+        newEdge.current.setAttrs({
+          line: {
+            stroke: '#1b5cdf',
+          },
+        });
+        newEdge.current = undefined;
+      }
+    });
+
+    // 连续添加popver关闭
+    graph?.on("node:change:closedPopover", () => {
+      setTimeout(() => {
+        if(Object.hasOwn(newEdge.current?.target || {}, 'x')) {
+          graph?.removeCells([newEdge.current!]);
+          newEdge.current = undefined;
+        }
+      }, 100);
+    });
+  }, [graph]);
+
+  const {x, y} = node?.position() || {x: 0, y: 0};
+  const x1 = (node?.getBBox()?.width || 0) + x + 50;
+
+  const handleAddChange = (addNode?: Node) => {
+    setOpen(false);
+    if(addNode && node) {
+
+      const sourcePorts = node?.getPorts();
+      const sourcePort = sourcePorts?.find(item => item.group === 'right');
+      const targetPorts = addNode?.getPorts();
+      const targetPort = targetPorts?.find(item => item.group === 'left');
+
+      graph?.addEdge({
+        source: {
+          cell: node.id,
+          port: sourcePort?.id
+        },
+        target: {
+          cell: addNode.id,
+          port: targetPort?.id
+        },
+        connector: { name: 'smooth' },
+        attrs: {
+          line: {
+            stroke: '#1b5cdf',
+            strokeWidth: 2,
+          },
+        },
+      });
+    }
   };
 
   return (
     <Popover
-      content={<NodeMenu graph={graph} />}
-      trigger="click"
+      content={<NodeMenu graph={graph} onChange={handleAddChange} position={{ x: x1, y}} />}
+      trigger={"click"}
       placement="right"
       arrow={false}
+      open={open}
+      onOpenChange={(open) => {
+        setOpen(open);
+      }}
     >
       <div
         className="node-port flex items-center justify-center"
         style={{
-          transform: hovered ? "scale(1.5) translateY(-50%)" : "scale(1) translateY(-50%)",
+          transform: hovered ? "scale(1.2) translateY(-50%)" : "scale(1) translateY(-50%)",
           opacity: hovered ? 1 : 0.5,
           ...style,
           ...extraStyle
         }}
         onMouseEnter={() => handleSetCanAdd(true)}
         onMouseLeave={() => handleSetCanAdd(false)}
-        onClick={handleClickPort}
+        onMouseDown={handleMouseDown}
+        onMouseMove={handleMouseMove}
       >
         { out && <PlusOutlined
           className="transform scale-0 transition duration-300 color-#fff text-8px"

+ 8 - 0
apps/flowchart-designer/src/components/nodes/index.ts

@@ -8,6 +8,7 @@ import Decision from "./Decision";
 import End from "./End";
 import Handle from "./Handle";
 import Link from "./Link";
+import PopoverNode from "./PopoverNode";
 
 // 通用连接桩
 const ports = {
@@ -109,3 +110,10 @@ nodes.forEach((node) => {
     ports: {...ports}
   });
 });
+
+register({
+  shape: 'menu-popover',
+  width: 304,
+  height: 184,
+  component: PopoverNode
+});

+ 3 - 3
apps/flowchart-designer/src/models/flowModel.ts

@@ -68,10 +68,10 @@ export default function flowModel() {
     instance.use(
       new Selection({
         enabled: true,
-        multiple: true,
-        rubberband: true,
+        multiple: false,
+        rubberband: false,
         movable: true,
-        showNodeSelectionBox: true,
+        showNodeSelectionBox: false,
         // showEdgeSelectionBox: true,
         pointerEvents: "none",
         strict: true