Selaa lähdekoodia

feat: 添加水印、添加连接器样式

liaojiaxing 6 kuukautta sitten
vanhempi
commit
743c805ef3

+ 74 - 48
apps/designer/src/components/mindMap/Topic.tsx

@@ -11,6 +11,7 @@ import { addTopic } from "@/pages/mindmap/mindMap";
 import Link from "./Link";
 import ExtraModule from "./ExtraModule";
 import CustomTag from "@/components/CustomTag";
+import { TopicItem } from "@/types";
 const component = ({ node, graph }: { node: Node; graph: Graph }) => {
   const {
     fill,
@@ -25,7 +26,9 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
     extraModules,
     remark,
     link,
-    children
+    children,
+    type,
+    collapsed,
   } = node.getData();
   const { size, ref } = useSizeHook();
   const { fillContent, strokeColor, strokeWidth, strokeDasharray } =
@@ -35,6 +38,7 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
     const cells = graph.getSelectedCells();
     setSelected(cells.length === 1 && cells[0].id === node.id);
   };
+  const [showCollapsePoint, setShowCollapsePoint] = useState(collapsed);
 
   useEffect(() => {
     graph.createTransformWidget(node);
@@ -50,15 +54,29 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
   useEffect(() => {
     let maxWidth = size.width - 8;
     let maxHeight = size.height;
-
   }, [tags, label, icons]);
 
   const getSize = useMemo(() => {
     return {
       contentHeight: node.size().height - (extraModules ? 40 : 0),
-    }
+    };
   }, [icons, label, tags, extraModules, remark, link, size]);
 
+  const childrenCount = useMemo(() => {
+    let count = 0;
+    const traverse = (topics: TopicItem[]) => {
+      topics.forEach((item) => {
+        count++;
+        if (item.children) {
+          traverse(item.children);
+        }
+      });
+    };
+
+    traverse(children);
+    return count;
+  }, [children]);
+
   const handleAddBranch = () => {
     const data = node.getData();
     let topic;
@@ -67,7 +85,13 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
     } else {
       topic = addTopic(TopicType.sub, setMindProjectInfo, node);
     }
-    graph.resetSelection(topic?.id)
+    graph.resetSelection(topic?.id);
+  };
+  
+  const handleToggleCollapse = () => {
+    node.setData({
+      collapsed: !collapsed
+    })
   };
 
   return (
@@ -81,6 +105,8 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
           background: fillContent,
           borderRadius: borderSize,
         }}
+        onMouseOver={() => !collapsed && setShowCollapsePoint(true)}
+        onMouseLeave={() => !collapsed && setShowCollapsePoint(false)}
       >
         <div className="w-full h-full flex flex-col">
           {extraModules && (
@@ -88,8 +114,10 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
           )}
 
           <div className="flex-1">
-
-            <div className="flex-1 flex flex-col justify-center" style={{ height: getSize.contentHeight }}>
+            <div
+              className="flex-1 flex flex-col justify-center"
+              style={{ height: getSize.contentHeight }}
+            >
               <div className="flex justify-start items-center text-20px">
                 {icons?.map((icon: string) => {
                   return (
@@ -103,10 +131,10 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
                   node={node}
                   styles={{
                     ...text,
-                    position: "relative"
+                    position: "relative",
                   }}
-                  txtStyle={{ 
-                    position: "relative", 
+                  txtStyle={{
+                    position: "relative",
                     flex: 1,
                   }}
                 />
@@ -135,6 +163,7 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
           </div>
         </div>
 
+        {/* 添加主题 */}
         {selected && !children?.length && (
           <div
             className={`
@@ -159,50 +188,47 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
             <PlusOutlined />
           </div>
         )}
+        {
+          type !== TopicType.main && children.length && <div
+            className="absolute right--30px top-0 w-30px h-full"
+            onMouseOver={() => !collapsed && setShowCollapsePoint(true)}
+            onMouseOut={() => !collapsed && setShowCollapsePoint(false)}
+            />
+        }
+        {/* 折叠子主题 */}
+        {type !== TopicType.main && children?.length && showCollapsePoint && (
+          <div
+            className={`
+              absolute
+              rounded-full
+              bg-white
+              top-50%
+              translate-y-[-50%]
+              cursor-pointer
+              hover:bg-#e6e6e6
+              text-12px
+              flex
+              items-center
+              justify-center
+              ${collapsed ? "w-16px h-16px right--20px" : "w-10px h-10px right--15px"}
+             `}
+            onClick={handleToggleCollapse}
+            style={{
+              border: `1px solid ${fill.color1}`,
+              color: fill.color1
+            }}
+          >
+            {
+              collapsed && childrenCount
+            }
+          </div>
+        )}
       </div>
     </>
   );
 };
 
-// 连接器
-Graph.registerConnector(
-  "mindmap",
-  (sourcePoint, targetPoint, routerPoints, options) => {
-    const midX = sourcePoint.x + 10;
-    const midY = sourcePoint.y;
-    const ctrX = (targetPoint.x - midX) / 5 + midX;
-    const ctrY = targetPoint.y;
-    const pathData = `
-     M ${sourcePoint.x} ${sourcePoint.y}
-     L ${midX} ${midY}
-     Q ${ctrX} ${ctrY} ${targetPoint.x} ${targetPoint.y}
-    `;
-    return options.raw ? Path.parse(pathData) : pathData;
-  },
-  true
-);
-
-// 注册思维导图边
-Graph.registerEdge(
-  "mindmap-edge",
-  {
-    inherit: "edge",
-    connector: {
-      name: "mindmap",
-    },
-    attrs: {
-      line: {
-        targetMarker: "",
-        stroke: "#A2B1C3",
-        strokeWidth: 2,
-      },
-    },
-    zIndex: 0,
-  },
-  true
-);
-
-// 主题
+// 主题节点
 register({
   shape: "mind-map-topic",
   width: 206,

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

@@ -132,6 +132,7 @@ export const topicData = {
   type: TopicType.main,
   borderSize: BorderSize.medium,
   fixedWidth: false,
+  collapsed: false,
   edge: {
     color: "#323232",
     width: 3,

+ 8 - 0
apps/designer/src/enum/index.ts

@@ -54,4 +54,12 @@ export enum StructureType {
   leftTreeShape = 'leftTreeShape',
   rightBracket = 'rightBracket',
   leftBracket = 'leftBracket',
+}
+
+// 思维导图连接器样式 1、曲线 2、直线 3、圆角折线 4、折线
+export enum MindmapConnectorType {
+  curve = 'curve',
+  straight = 'straight',
+  rounded = 'rounded',
+  poly = 'poly',
 }

+ 25 - 1
apps/designer/src/events/mindMapEvent.ts

@@ -1,6 +1,6 @@
 import { Graph, Cell } from "@antv/x6";
 import { BorderSize, TopicType } from "@/enum";
-import { addTopic } from "@/pages/mindmap/mindMap";
+import { addTopic, updateTopic } from "@/pages/mindmap/mindMap";
 import { MindMapProjectInfo } from "@/types";
 
 export const bindMindMapEvents = (
@@ -29,4 +29,28 @@ export const bindMindMapEvents = (
       graph.resetSelection(topic?.id)
     }
   });
+
+  // 节点数据更改
+  graph.on("node:change:*", (args) => {
+    // console.log("node:change:*", args);
+    const { current, previous } = args;
+    if(args.key === "data") {
+      // 收折子项
+      if (current.collapsed !== previous.collapsed) {
+        setMindProjectInfo && updateTopic(args.cell.id, { collapsed: current.collapsed }, setMindProjectInfo);
+      } else {
+        updateTopic(args.cell.id, current, (info) => {
+          localStorage.setItem("minMapProjectInfo", JSON.stringify(info));
+        });
+      }
+    }
+    if(args.key === "size") {
+      updateTopic(args.cell.id, { 
+        width: current.width, 
+        height: current.height
+       }, (info) => {
+        localStorage.setItem("minMapProjectInfo", JSON.stringify(info));
+      });
+    }
+  })
 };

+ 31 - 5
apps/designer/src/models/mindMapModel.ts

@@ -1,7 +1,7 @@
 import { cellStyle } from "@/types";
 import { Cell, EventArgs, Graph } from "@antv/x6";
 import { message } from "antd";
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useMemo, 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";
@@ -13,6 +13,7 @@ import { useLocalStorageState } from "ahooks";
 import { renderMindMap } from "@/pages/mindmap/mindMap";
 import { defaultProject } from "@/config/data";
 import { TopicType } from "@/enum";
+import { isEqual } from "lodash-es";
 
 type RightToolbarType =
   | "style"
@@ -49,20 +50,45 @@ export default function mindMapModel() {
     renderMindMap(graph, setMindProjectInfo);
   }, [mindProjectInfo, graph]);
 
+  const pageSettingRef = useRef<MindMapProjectInfo["pageSetting"]>();
+
   useEffect(() => {
     if (mindProjectInfo?.pageSetting && graph) {
-      if (mindProjectInfo.pageSetting.fillType === "color") {
+      if(isEqual(pageSettingRef.current, mindProjectInfo?.pageSetting)) {
+        return;
+      }
+      pageSettingRef.current = mindProjectInfo?.pageSetting;
+      const pageSetting = pageSettingRef.current;
+      if (pageSetting?.fillType === "color") {
         graph.drawBackground({
-          color: mindProjectInfo?.pageSetting?.fill,
+          color: pageSetting?.fill,
         });
       } else {
         graph.drawBackground({
-          image: mindProjectInfo?.pageSetting?.fillImageUrl,
+          image: pageSetting?.fillImageUrl,
           repeat: "repeat",
         });
       }
+      // 设置水印
+      if(pageSetting.showWatermark && pageSetting.watermark) {
+        const canvas = document.createElement("canvas");
+        canvas.width = pageSetting.watermark.length * 16;
+        canvas.height = 100;
+        const ctx = canvas.getContext("2d");
+        if(ctx) {
+          ctx.fillStyle = "#aaa";
+          ctx.font = "16px Arial";
+          ctx.fillText(pageSetting.watermark, 1, 15);
+        };
+        const img = canvas.toDataURL();
+
+        graph.drawBackground({
+          image: img,
+          repeat: 'watermark'
+        })
+      }
     }
-  }, [mindProjectInfo?.pageSetting, graph]);
+  }, [graph, mindProjectInfo?.pageSetting]);
 
   // 初始化脑图
   const initMindMap = (container: HTMLElement) => {

+ 5 - 2
apps/designer/src/pages/mindmap/components/Config/PageStyle.tsx

@@ -8,6 +8,7 @@ export default function PageStyle() {
   const { mindProjectInfo, setMindProjectInfo } = useModel("mindMapModel");
 
   const pageSetting = mindProjectInfo?.pageSetting;
+  const [watermark, setWatermark] = useState(pageSetting?.watermark);
 
   const handleSetPageSetting = (key: string, value: any) => {
     if (pageSetting) {
@@ -129,8 +130,10 @@ export default function PageStyle() {
         <Input
           placeholder="不超过15字,回车保存"
           maxLength={15}
-          value={pageSetting?.watermark}
-          onChange={(e) => handleSetPageSetting("watermark", e.target.value)}
+          value={watermark}
+          onChange={(e) => setWatermark(e.target.value)}
+          onPressEnter={() => handleSetPageSetting("watermark", watermark)}
+          onBlur={() => handleSetPageSetting("watermark", watermark)}
         />
       </section>
     </div>

+ 35 - 23
apps/designer/src/pages/mindmap/components/Config/Structure.tsx

@@ -1,6 +1,7 @@
 import { StructureType } from "@/enum";
 import { Tabs } from "antd";
 import React from "react";
+import { useModel } from "umi";
 
 const structures = [
   {
@@ -58,27 +59,27 @@ const structures = [
         fill="none"
         xmlns="http://www.w3.org/2000/svg"
       >
-        <g clip-path="url(#clip0_31667_57555)">
+        <g clipPath="url(#clip0_31667_57555)">
           <path
             d="M26.4241 15.0547L8.93872 15.0547"
             stroke="#9AA5B8"
-            stroke-width="1.4"
-            stroke-linecap="round"
-            stroke-linejoin="round"
+            strokeWidth="1.4"
+            strokeLinecap="round"
+            strokeLinejoin="round"
           ></path>
           <path
             d="M7.40805 15.0547L13.708 15.0547L13.708 9.00107L15.8855 9.00107"
             stroke="#9AA5B8"
-            stroke-width="1.4"
-            stroke-linecap="round"
-            stroke-linejoin="round"
+            strokeWidth="1.4"
+            strokeLinecap="round"
+            strokeLinejoin="round"
           ></path>
           <path
             d="M16.2302 15.0547L22.5302 15.0547L22.5302 9.00107L24.499 9.00107"
             stroke="#9AA5B8"
-            stroke-width="1.4"
-            stroke-linecap="round"
-            stroke-linejoin="round"
+            strokeWidth="1.4"
+            strokeLinecap="round"
+            strokeLinejoin="round"
           ></path>
           <rect
             width="8.4"
@@ -107,27 +108,27 @@ const structures = [
         viewBox="0 0 28 28"
         fill="none"
       >
-        <g clip-path="url(#clip0_31757_133105)">
+        <g clipPath="url(#clip0_31757_133105)">
           <path
             d="M26.2122 12.5469L8.72681 12.5469"
             stroke="#9AA5B8"
-            stroke-width="1.4"
-            stroke-linecap="round"
-            stroke-linejoin="round"
+            strokeWidth="1.4"
+            strokeLinecap="round"
+            strokeLinejoin="round"
           ></path>
           <path
             d="M8.72689 12.5469L15.0269 12.5469L15.0269 18.6005L17.2043 18.6005"
             stroke="#9AA5B8"
-            stroke-width="1.4"
-            stroke-linecap="round"
-            stroke-linejoin="round"
+            strokeWidth="1.4"
+            strokeLinecap="round"
+            strokeLinejoin="round"
           ></path>
           <path
             d="M17.5491 12.5469L23.8491 12.5469L23.8491 18.6005L25.8179 18.6005"
             stroke="#9AA5B8"
-            stroke-width="1.4"
-            stroke-linecap="round"
-            stroke-linejoin="round"
+            strokeWidth="1.4"
+            strokeLinecap="round"
+            strokeLinejoin="round"
           ></path>
           <rect
             width="8.4"
@@ -174,13 +175,17 @@ const structures = [
 ];
 
 export default function Structure() {
+  const { mindProjectInfo, setMindProjectInfo } = useModel("mindMapModel");
   return (
     <>
       <Tabs>
         <Tabs.TabPane tab="结构类型" key="1">
           <div className="flex gap-4 flex-wrap justify-between px-8px">
             {structures.map((item) => (
-              <div className="w-70px flex flex-col items-center">
+              <div
+                key={item.type}
+                className="w-70px flex flex-col items-center"
+              >
                 <span
                   className={`
               w-50px
@@ -193,12 +198,19 @@ export default function Structure() {
               justify-center
               border-1px
               border-solid
-              border-#e6ebf5
               cursor-pointer
+              ${mindProjectInfo?.structure === item.type ? "border-#3f8eff" : "border-#e6ebf5"}
               `}
+                  onClick={() =>
+                    mindProjectInfo &&
+                    setMindProjectInfo({
+                      ...mindProjectInfo,
+                      structure: item.type,
+                    })
+                  }
                 >
                   {typeof item.icon === "string" ? (
-                    <svg className="w-1em h-2em fill-current text-24px">
+                    <svg className={`w-1em h-2em fill-current text-24px`}>
                       <use xlinkHref={`#${item.icon}`} />
                     </svg>
                   ) : (

+ 257 - 0
apps/designer/src/pages/mindmap/edge.ts

@@ -0,0 +1,257 @@
+import { StructureType, MindmapConnectorType, TopicType } from "@/enum";
+import { TopicItem } from "@/types";
+import { Edge, Graph, Path } from "@antv/x6";
+import { getTheme } from "./theme";
+
+// 曲线
+Graph.registerConnector(
+  "curve-branch-connector",
+  (sourcePoint, targetPoint, routerPoints, options) => {
+    const midX = sourcePoint.x;
+    const ctrX = (targetPoint.x - midX) / 5 + midX;
+    const ctrY = targetPoint.y;
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     Q ${ctrX} ${ctrY} ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+Graph.registerConnector(
+  "curve-sub-connector",
+  (sourcePoint, targetPoint, routerPoints, options) => {
+    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10);
+    const midY = sourcePoint.y;
+    const ctrX = (targetPoint.x - midX) / 5 + midX;
+    const ctrY = targetPoint.y;
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${midX} ${midY}
+     Q ${ctrX} ${ctrY} ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+// 直线
+Graph.registerConnector(
+  "straight-branch-connector",
+  function(sourcePoint, targetPoint, routerPoints, options){
+    const halfWidth = this.sourceBBox.width / 2;
+    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth-10 );
+    const midY = sourcePoint.y;
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${midX} ${midY}
+     L ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+Graph.registerConnector(
+  "straight-sub-connector",
+  function(sourcePoint, targetPoint, routerPoints, options) {
+    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10 );
+    const midY = sourcePoint.y;
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${midX} ${midY}
+     L ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+// 圆角折线
+Graph.registerConnector(
+  "rounded-branch-connector",
+  function(sourcePoint, targetPoint, routerPoints, options) {
+    const halfWidth = this.sourceBBox.width / 2;
+    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth-10 );
+    const midY = sourcePoint.y;
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${midX} ${midY}
+     L ${midX} ${targetPoint.y + (targetPoint.y < sourcePoint.y ? 5 : targetPoint.y === sourcePoint.y ? 0 : -5)}
+     Q ${midX} ${targetPoint.y} ${midX + (targetPoint.x < sourcePoint.x ? -5 : 5)} ${targetPoint.y}
+     L ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+Graph.registerConnector(
+  "rounded-sub-connector",
+  (sourcePoint, targetPoint, routerPoints, options) => {
+    const midX = sourcePoint.x < targetPoint.x ? sourcePoint.x + 10 : sourcePoint.x - 10;
+    const midY = sourcePoint.y;
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${midX} ${midY}
+     L ${midX} ${targetPoint.y + (targetPoint.y < sourcePoint.y ? 5 : targetPoint.y === sourcePoint.y ? 0 : -5)}
+     Q ${midX} ${targetPoint.y} ${midX + (targetPoint.x < sourcePoint.x ? -5 : 5)} ${targetPoint.y}
+     L ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+// 折线
+Graph.registerConnector(
+  "poly-branch-connector",
+  function(sourcePoint, targetPoint, routerPoints, options) {
+    const halfWidth = this.sourceBBox.width / 2;
+    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth-10 );
+    const midY = sourcePoint.y;
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${midX} ${midY}
+     L ${midX} ${targetPoint.y}
+     L ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+Graph.registerConnector(
+  "poly-sub-connector",
+  function(sourcePoint, targetPoint, routerPoints, options) {
+    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10 );
+    const midY = sourcePoint.y;
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${midX} ${midY}
+     L ${midX} ${targetPoint.y}
+     L ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+
+const getConnector = (
+  structure: StructureType,
+  theme: string,
+  type: TopicType
+) => {
+  // TODO根据结构处理连接线
+  const themeObj = getTheme(theme);
+  if (type === TopicType.branch) {
+    return `${themeObj.branchConnector}-${type}-connector`;
+  } else {
+    return `${themeObj.subConnector}-${type}-connector`;
+  }
+};
+
+const getSourceAnchor = (type: TopicType, structure: StructureType) => {
+  switch (structure) {
+    case StructureType.left: {
+      return type === TopicType.branch
+        ? {
+            name: "center",
+          }
+        : {
+            name: "left",
+          };
+    }
+    case StructureType.right: {
+      return type === TopicType.branch
+        ? {
+            name: "center",
+          }
+        : {
+            name: "right",
+          };
+    }
+    case StructureType.leftBracket:
+    case StructureType.leftFishbone:
+    case StructureType.leftTreeShape:
+    case StructureType.organization:
+    case StructureType.rightBracket:
+    case StructureType.rightFishbone:
+    case StructureType.rightTreeShape:
+      return {
+        name: "center",
+      };
+  }
+  return {
+    name: "center",
+  };
+};
+
+const getTargetAnchor = (type: TopicType, structure: StructureType) => {
+  switch (structure) {
+    case StructureType.left: {
+      return {
+            name: "right",
+          };
+    }
+    case StructureType.right: {
+      return {
+            name: "left",
+          };
+    }
+    case StructureType.leftBracket:
+    case StructureType.leftFishbone:
+    case StructureType.leftTreeShape:
+    case StructureType.organization:
+    case StructureType.rightBracket:
+    case StructureType.rightFishbone:
+    case StructureType.rightTreeShape:
+      return {
+        name: "center",
+        args: {
+          dx: "25%",
+        },
+      };
+  }
+  return {
+    name: "center",
+    args: {
+      dx: "25%",
+    },
+  };
+};
+
+/**
+ * 创建连线
+ * @param graph
+ * @param sourceId
+ * @param item
+ * @param structure
+ * @param theme
+ * @returns
+ */
+export const createEdge = (
+  graph: Graph,
+  sourceId: string,
+  item: { id: string; data: TopicItem },
+  structure: StructureType,
+  theme: string
+): Edge => {
+  return graph.createEdge({
+    id: item.id + "-edge",
+    inherit: "edge",
+    connector: {
+      name: getConnector(structure, theme, item.data.type),
+    },
+    zIndex: 0,
+    source: {
+      cell: sourceId,
+      anchor: getSourceAnchor(item.data.type, structure),
+    },
+    target: {
+      cell: item.id,
+      anchor: getTargetAnchor(item.data.type, structure),
+    },
+    attrs: {
+      line: {
+        targetMarker: "",
+        stroke: item.data.edge?.color || "#A2B1C3",
+        strokeWidth: item.data?.edge?.width || 3,
+      },
+    },
+  });
+};

+ 27 - 1
apps/designer/src/pages/mindmap/hierarchy.ts

@@ -34,5 +34,31 @@ export const hierarchyMethodMap: Record<
       },
     });
   },
-
+  // 左侧图
+  [StructureType.left]: <T>(
+    topic: TopicItem,
+    pageSetting: MindMapProjectInfo["pageSetting"]
+  ): T => {
+    return Hierarchy.mindmap(topic, {
+      direction: "H",
+      getHeight(d: TopicItem) {
+        return d.height;
+      },
+      getWidth(d: TopicItem) {
+        return d.width;
+      },
+      getHGap(d: TopicItem) {
+        if(d.type === TopicType.main) return pageSetting.branchX || 20;
+        if(d.type === TopicType.branch) return pageSetting.subTopicX || 20;
+        if(d.type === TopicType.sub) return pageSetting.subTopicX || 20;
+        return pageSetting.branchX || 40;
+      },
+      getVGap() {
+        return pageSetting.branchY || 20;
+      },
+      getSide: () => {
+        return "left";
+      },
+    });
+  },
 };

+ 11 - 29
apps/designer/src/pages/mindmap/mindMap.tsx

@@ -5,6 +5,7 @@ import TopicComponent from "@/components/mindMap/Topic";
 import { topicData } from "@/config/data";
 import { uuid } from "@/utils";
 import { hierarchyMethodMap } from "@/pages/mindmap/hierarchy";
+import { createEdge } from "./edge";
 import { getTheme } from "./theme";
 
 interface HierarchyResult {
@@ -64,35 +65,9 @@ export const renderMindMap = (
 
         if (children) {
           children.forEach((item: HierarchyResult) => {
-            console.log(item)
             cells.push(
               // 创建连线
-              graph.createEdge({
-                shape: "mindmap-edge",
-                id: item.id + '-edge',
-                source: {
-                  cell: id,
-                  anchor: {
-                    name: "center",
-                    args: {
-                      dx: "25%",
-                    },
-                  },
-                },
-                target: {
-                  cell: item.id,
-                  anchor: {
-                    name: "left",
-                  },
-                },
-                attrs: {
-                  line: {
-                    targetMarker: "",
-                    stroke: item.data.edge?.color || "#A2B1C3",
-                    strokeWidth: item.data?.edge?.width || 3,
-                  },
-                },
-              })
+              createEdge(graph, id, item, projectInfo.structure, projectInfo.theme)
             );
             // 递归遍历
             traverse(item, node);
@@ -108,18 +83,25 @@ export const renderMindMap = (
     // 存在更新位置,否则添加
     if (graph.hasCell(cell.id)) {
       const oldCell = graph.getCellById(cell.id);
-      oldCell.isNode() && oldCell.position(cell.position().x, cell.position().y);
+      if(oldCell.isNode()) {
+        oldCell.position(cell.position().x, cell.position().y);
+        oldCell.setData({
+          ...cell.data
+        })
+      }
     } else {
       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');
       graph.removeCell(cell);
     }
   });
@@ -222,7 +204,7 @@ export const buildTopic = (
     },
     edge: {
       ...topicData.edge,
-      color: theme[type]?.fill.color1,
+      color: theme[type]?.edge.color,
     },
     ...options,
   };

+ 17 - 5
apps/designer/src/pages/mindmap/theme.ts

@@ -1,8 +1,9 @@
+import { MindmapConnectorType, TopicType } from "@/enum";
 import { getRandomColor, lightenColor } from "@/utils/color";
 import { Node } from "@antv/x6";
 
 export const getTheme = (key?: string, parentNode?: Node) => {
-
+  const color1 = getRandomColor(["#3D4BCF", "#9C2CB8"]);
   const map = {
     // 默认主题
     default: {
@@ -17,12 +18,15 @@ export const getTheme = (key?: string, parentNode?: Node) => {
         },
         stroke: {
           width: 0,
+        },
+        edge: {
+          color: ""
         }
       },
       branch: {
         fill: {
           fillType: "color",
-          color1: getRandomColor(["#3D4BCF", "#9C2CB8"]),
+          color1,
         },
         text: {
           color: "#fff",
@@ -31,20 +35,28 @@ export const getTheme = (key?: string, parentNode?: Node) => {
         stroke: {
           width: 0,
         },
+        edge: {
+          color: color1,
+        }
       },
       sub: {
         fill: {
           fillType: "color",
-          color1: parentNode?.data?.fill?.color1 ? lightenColor(parentNode.data.fill.color1, 150) : getRandomColor(["#3D4BCF", "#9C2CB8"]),
+          color1: parentNode?.data?.type === TopicType.branch ? lightenColor(parentNode.data.fill.color1, 150) : parentNode?.data?.fill?.color1,
         },
         text: {
-          color: parentNode?.data?.fill?.color1 ? lightenColor(parentNode.data.fill.color1, -10) : "#fff",
+          color: parentNode?.data?.type === TopicType.branch ? lightenColor(parentNode.data.fill.color1, -10) : parentNode?.data?.text?.color,
           fontSize: 14,
         },
         stroke: {
           width: 0,
         },
-      }
+        edge: {
+          color: parentNode?.data?.type === TopicType.branch ? parentNode?.data?.fill?.color1 : parentNode?.data?.edge?.color || '#323232',
+        }
+      },
+      branchConnector: MindmapConnectorType.curve,
+      subConnector: MindmapConnectorType.rounded,
     }
   }
 

+ 4 - 0
apps/designer/src/types.d.ts

@@ -120,6 +120,10 @@ export interface TopicItem {
     color: string;
     width: number;
   }
+  /**
+   * 折叠子节点
+   */
+  collapsed?: boolean;
 }
 export interface MindMapProjectInfo{
   name: string;

+ 3 - 3
apps/designer/src/utils/color.ts

@@ -6,12 +6,12 @@
 export const getRandomColor = (baseColors: string[], alpha?: number) => {
   const randomIndex = Math.floor(Math.random() * baseColors.length);
   const randomAlpha = Math.random();
-  alpha = alpha || randomAlpha < 0.3 ? 0.3 : randomAlpha;
+  alpha = alpha || randomAlpha < 0.5 ? 0.5 : randomAlpha;
   const r = parseInt(baseColors[randomIndex].slice(1, 3), 16);
-  const g = parseInt(baseColors[randomIndex].slice(3, 5), 16);
+  const g = parseInt(baseColors[randomIndex].slice(3, 5), 16) * alpha;
   const b = parseInt(baseColors[randomIndex].slice(5, 7), 16) * alpha;
   // 返回hex颜色
-  return `#${r.toString(16)}${g.toString(16)}${Number(b.toFixed(0)).toString(16)}`
+  return `#${r.toString(16)}${Number(g.toFixed(0)).toString(16)}${Number(b.toFixed(0)).toString(16)}`
 }
 
 /**