瀏覽代碼

fix: 修复bug,重构思维导图历史记录

liaojiaxing 3 周之前
父節點
當前提交
7c748483aa

+ 1 - 1
apps/designer/.umirc.ts

@@ -9,7 +9,7 @@ export default defineConfig({
     '/favicon.ico'
   ],
   styles: [
-    '//at.alicdn.com/t/c/font_4676747_kbfv8otb8de.css'
+    '//at.alicdn.com/t/c/font_4676747_4n0ccqebfqj.css'
   ],
   metas: [
     { name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' }

+ 1 - 1
apps/designer/src/components/FindReplaceModal.tsx

@@ -170,7 +170,7 @@ export default forwardRef(function FindReplaceModal(
         </div>
         {active === 2 && (
           <>
-            <div>
+            <div className="mt-12px">
               <Input
                 placeholder="替换内容"
                 value={replaceText}

+ 1 - 0
apps/designer/src/components/ai/RenderGraph.tsx

@@ -115,6 +115,7 @@ export default function RenderGraph(props: {
           instance: messageApi,
         },
         onSuccess: (data) => {
+          console.log('data: ', data)
           const result = replaceFlowId(data);
           parseData.current = result;
           // graphRef.current?.fromJSON(data);

+ 3 - 3
apps/designer/src/components/mindMap/Text.tsx

@@ -91,9 +91,9 @@ export default function Text(props: {
 
   const handleReplace = (args: any) => {
     const { type, searchText, replaceText, currentIndex, currentCellId } = args?.current || {};
-    if(args.current?.type === 'replaceAll') {
-      handleChange(value.replaceAll(searchText, replaceText));
-    }
+    // if(args.current?.type === 'replaceAll') {
+    //   handleChange(value.replaceAll(searchText, replaceText));
+    // }
     if(type === 'replace' && currentCellId === node.id) {
       const list = value.split(searchText);
       const text = list.map((str, index) => {

+ 26 - 1
apps/designer/src/components/mindMap/Topic.tsx

@@ -35,6 +35,7 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
     collapsed,
     fixedWidth,
     linkTopicId,
+    parentId
   } = node.getData();
   const { size, ref } = useSizeHook();
   const { fillContent, strokeColor, strokeWidth } = useShapeProps(
@@ -75,6 +76,22 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
     }
   }, [type]);
 
+  type Position = 'right' | 'left' | 'top' | 'bottom';
+  // 折叠按钮位置
+  const collapsedPosition = useMemo(() => { 
+    let position: Position = 'right';
+    const parentNode = graph.getCellById(parentId);
+    if (parentNode?.isNode()) {
+      const { x, y } = parentNode?.getPosition() || { x: 0, y: 0 };
+      const { x: x1, y: y1} = node.getPosition();
+      if(x1 < x) {
+        position = 'left';
+      }
+    }
+    
+    return position;
+  }, [parentId, node.getAttrs()]);
+
   const showHrefConfig = () => {
     setShowPopover(true);
     setPopoverContent(
@@ -335,17 +352,22 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
               hover:bg-#067bef 
               hover:color-white`}
               onClick={handleAddBranch}
+              style={collapsedPosition === 'left' ? {left: -25} : {right: -25}}
             >
               <PlusOutlined />
             </div>
           )}
+
+          {/* 折叠按钮 */}
           {type !== TopicType.main && children.length && (
             <div
               className="absolute right--30px top-0 w-30px h-full"
+              style={collapsedPosition === 'left' ? { left: -30 } : { right: -30 }}
               onMouseOver={() => !collapsed && setShowCollapsePoint(true)}
               onMouseOut={() => !collapsed && setShowCollapsePoint(false)}
             />
           )}
+
           {/* 折叠按钮 */}
           {type !== TopicType.main && children?.length && showCollapsePoint && (
             <div
@@ -361,12 +383,15 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
               flex
               items-center
               justify-center
-              ${collapsed ? "w-16px h-16px right--20px" : "w-10px h-10px right--15px"}
+              leading-16px
+              ${collapsed ? "w-16px h-16px" : "w-10px h-10px"}
              `}
               onClick={handleToggleCollapse}
               style={{
                 border: `1px solid ${fill.color1}`,
                 color: fill.color1,
+                right: collapsedPosition === 'right' ? collapsed ? -20 : -15 : 'auto',
+                left: collapsedPosition === 'left' ? collapsed ? -20 : -15 : 'auto',
               }}
             >
               {collapsed && childrenCount}

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

@@ -138,8 +138,8 @@ export default function PageStyle() {
           maxLength={15}
           value={watermark}
           onChange={(e) => setWatermark(e.target.value)}
-          onPressEnter={() => handleSetPageSetting("watermark", watermark)}
-          onBlur={() => handleSetPageSetting("watermark", watermark)}
+          onPressEnter={() => handleSetPageSetting("watermarkText", watermark)}
+          onBlur={() => handleSetPageSetting("watermarkText", watermark)}
         />
       </section>
     </div>

+ 19 - 2
apps/designer/src/pages/mindmap/components/HeaderToolbar/index.tsx

@@ -32,6 +32,7 @@ import { Copy, SaveAll } from "@/api/systemDesigner";
 import { UploadFile } from "@/api";
 import { base64ToFile, getClassRules } from "@repo/utils";
 import { useMindMapStore } from "../../models/mindMapModel";
+import { cloneDeep } from "lodash-es";
 
 export default function index() {
   const {
@@ -55,7 +56,7 @@ export default function index() {
   const {
     handleFind,
     handleReplace,
-    handleReplaceAll,
+    // handleReplaceAll,
     handleClose,
     handleFindNext,
     handleFindPrev,
@@ -207,6 +208,7 @@ export default function index() {
     graph.exportPNG("", {
       quality: 1,
       copyStyles: false,
+      stylesheet: getClassRules(),
     });
   };
 
@@ -250,6 +252,21 @@ export default function index() {
     graph?.centerContent();
   };
 
+  /**
+   * 全部替换
+   */
+  const handleReplaceAll = (searchText: string, replaceText: string) => {
+    const info = cloneDeep(mindProjectInfo);
+    if(searchText && replaceText) {
+      traverseNode(info.topics, (topic) => {
+        if (topic.label.includes(searchText)) {
+          topic.label = topic.label.replaceAll(searchText, replaceText);
+        }
+      });
+    }
+    setMindProjectInfo({ info });
+  }
+
   const menuData: MenuProps["items"] = [
     {
       key: "1",
@@ -332,7 +349,7 @@ export default function index() {
     {
       key: "5",
       label: "查找替换",
-      icon: <i className="w-20px iconfont icon-lishijilu" />,
+      icon: <i className="w-20px iconfont icon-chazhaotihuan" />,
       onClick: () => {
         findModalRef.current?.open();
       },

+ 43 - 48
apps/designer/src/pages/mindmap/mindMapEvent.ts

@@ -6,7 +6,11 @@ import { Dnd } from "@antv/x6-plugin-dnd";
 import { selectTopic, traverseNode } from "./mindmapHander";
 import { uuid } from "@repo/utils";
 import { getTheme } from "./render/theme";
-import { EditMindMapElement, AddMindMapElement, BatchDeleteMindMapElement } from "@/api/systemDesigner";
+import {
+  EditMindMapElement,
+  AddMindMapElement,
+  BatchDeleteMindMapElement,
+} from "@/api/systemDesigner";
 import { cloneDeep, debounce, isEqual } from "lodash-es";
 import { useMindMapStore } from "./models/mindMapModel";
 
@@ -27,9 +31,9 @@ let currentShadowNode: Node | undefined;
 export const bindMindMapEvents = (
   graph: Graph,
   setMindProjectInfo?: (data: {
-    info: MindMapProjectInfo,
-    init?: boolean,
-    ignoreRender?: boolean
+    info: MindMapProjectInfo;
+    init?: boolean;
+    ignoreRender?: boolean;
   }) => void,
   setSelectedCell?: (cell: Cell[]) => void,
   dndRef?: React.MutableRefObject<Dnd | undefined>
@@ -153,12 +157,13 @@ export const bindMindMapEvents = (
     // 自由节点拖拽
     if (!args.node.data?.parentId) {
       setShadowMode(true, args.node, graph);
+      args.node.toFront();
     }
   });
 
   graph.on("node:moving", (args) => {
     if (!args.node.data?.parentId && setMindProjectInfo) {
-      setShadowMode(false, args.node, graph);
+      setShadowMode(true, args.node, graph);
       topicDragHander(graph, { x: args.x, y: args.y }, args.node);
     }
   });
@@ -197,7 +202,10 @@ export const bindMindMapEvents = (
    */
   graph.on("node:change:data", (args) => {
     const { current, previous } = args;
-    console.log("node:change:data", current, previous);
+    if (current?.ingoreDrag && current?.ignoreDrag) {
+      // 忽略拖拽
+      return;
+    }
     // 收折子项 setMindProjectInfo更新会重新渲染
     if (current.collapsed !== previous.collapsed) {
       setMindProjectInfo &&
@@ -210,11 +218,7 @@ export const bindMindMapEvents = (
     }
     if (current?.links && current.links.length !== previous?.links?.length) {
       setMindProjectInfo &&
-        updateTopic(
-          args.cell.id,
-          { links: current.links },
-          setMindProjectInfo
-        );
+        updateTopic(args.cell.id, { links: current.links }, setMindProjectInfo);
     }
     // 宽度和高度有变化
     if (
@@ -258,22 +262,22 @@ export const bindMindMapEvents = (
     // 改线段
     if (current?.edge && !isEqual(current.edge, previous?.edge)) {
       setMindProjectInfo &&
-        updateTopic(
-          args.cell.id,
-          { edge: current.edge },
-          setMindProjectInfo
-        );
+        updateTopic(args.cell.id, { edge: current.edge }, setMindProjectInfo);
     }
 
     // 本地缓存更新不会重新渲染
     if (args.cell.id.includes("-border")) {
-      updateTopic(
-        args.current.origin,
-        { border: current },
-        (info) => {
-          setMindProjectInfo?.({...info, ignoreRender: true});
-        }
-      );
+      updateTopic(args.current.origin, { border: current }, (info) => {
+        setMindProjectInfo?.({ ...info, ignoreRender: true });
+      });
+    }
+
+    // 内容修改
+    if (current?.label !== previous?.label) {
+      setMindProjectInfo &&
+        updateTopic(args.cell.id, { label: current.label }, (info) =>
+          setMindProjectInfo?.({ ...info, ignoreRender: true })
+        );
     }
 
     // TODO 其余未处理的
@@ -287,27 +291,18 @@ export const bindMindMapEvents = (
     });
   });
 
+  // 修改自由节点位置
   graph.on(
-    "node:change:position",
+    "node:moved",
     debounce((args) => {
-      const { current } = args;
+      const { x, y } = args.node.position();
       if (
         args.cell.isNode() &&
         !args.cell.data?.parentId &&
         args.cell.data.type !== TopicType.main
       ) {
-        updateTopic(
-          args.cell.id,
-          { ...args.cell.data, x: current?.x, y: current?.y },
-          (info) => {
-            setMindProjectInfo && setMindProjectInfo({info: info});
-          }
-        );
-        EditMindMapElement({
-          ...args.cell.data,
-          ...args.current,
-          graphId: sessionStorage.getItem("projectId"),
-          tools: "",
+        updateTopic(args.cell.id, { ...args.cell.data, x, y }, (info) => {
+          setMindProjectInfo && setMindProjectInfo(info);
         });
       }
     }, 500)
@@ -400,6 +395,7 @@ const addIndicator = (
     height: 26,
     x,
     y,
+    zIndex: 0,
     attrs: {
       body: {
         fill: "#fecb99",
@@ -417,6 +413,7 @@ const addIndicator = (
   indicatorEdge = graph.addEdge({
     source: {
       cell: targetNode.id,
+      zIndex: 0,
       anchor: {
         name: atPosition === positionType.left ? "left" : "right",
         args: {
@@ -474,10 +471,8 @@ const setIndicator = (
           addIndicator(x, y, graph, targetNode, atPosition);
         }
       } else {
-        const mindProjectInfo = cloneDeep(useMindMapStore.getState().mindProjectInfo);
-        if (mindProjectInfo?.structure === StructureType.left) {
-          addIndicator(x, y, graph, targetNode, atPosition);
-        }
+        // 自由节点、中心主题
+        addIndicator(x, y, graph, targetNode, atPosition);
       }
       break;
     }
@@ -496,10 +491,8 @@ const setIndicator = (
           addIndicator(x, y, graph, targetNode, atPosition);
         }
       } else {
-        const mindProjectInfo = cloneDeep(useMindMapStore.getState().mindProjectInfo);
-        if (mindProjectInfo?.structure === StructureType.right) {
-          addIndicator(x, y, graph, targetNode, atPosition);
-        }
+        // 自由节点、中心主题
+        addIndicator(x, y, graph, targetNode, atPosition);
       }
       break;
     }
@@ -583,7 +576,9 @@ const handleSwitchPosition = (
   position?: { x: number; y: number },
   graph?: Graph
 ) => {
-  const mindmapProjectInfo: MindMapProjectInfo = cloneDeep(useMindMapStore.getState().mindProjectInfo);
+  const mindmapProjectInfo: MindMapProjectInfo = cloneDeep(
+    useMindMapStore.getState().mindProjectInfo
+  );
   if (!mindmapProjectInfo) return;
 
   // 找到要拖拽的节点并删除
@@ -595,7 +590,7 @@ const handleSwitchPosition = (
   });
   // 顶节点时删除节点
   if (source) {
-    BatchDeleteMindMapElement({ ids: [sourceId ]});
+    BatchDeleteMindMapElement({ ids: [sourceId] });
   }
   // 删除自由节点
   mindmapProjectInfo.topics = mindmapProjectInfo.topics.filter(
@@ -716,7 +711,7 @@ const handleSwitchPosition = (
     });
   }
 
-  setMindProjectInfo({info: mindmapProjectInfo});
+  setMindProjectInfo({ info: mindmapProjectInfo });
 };
 
 /**

+ 31 - 36
apps/designer/src/pages/mindmap/models/mindMapModel.ts

@@ -18,6 +18,7 @@ import { handleCreateCorrelationEdge } from "../mindmapHander";
 import { Dnd } from "@antv/x6-plugin-dnd";
 import { EditGraph, BatchEditMindMapElement } from "@/api/systemDesigner";
 import { create } from "zustand";
+import { useHistoryTravel } from "ahooks";
 
 class MindmapState {
   updateState: (para: Partial<MindmapState>) => void;
@@ -57,13 +58,21 @@ export default function mindMapModel() {
   const [graph, setGraph] = useState<Graph>();
   const [selectedCell, setSelectedCell] = useState<Cell[]>([]);
   const correlationEdgeRef = useRef<Edge>();
-  const historyRef = useRef<MindMapProjectInfo[]>([]);
-  const activeIndex = useRef(0);
   const mindProjectInfo = useMindMapStore((state) => state.mindProjectInfo);
 
   const projectInfoRef = useRef<MindMapProjectInfo>();
   const timer = useRef<any>();
   const settingTimer = useRef<any>();
+  // 历史记录
+  const {
+    value: historyValue,
+    setValue: setHistoryValue,
+    backLength,
+    forwardLength,
+    back,
+    forward,
+    reset,
+  } = useHistoryTravel<MindMapProjectInfo>(undefined, 20);
 
   /**
    * 更新项目信息
@@ -98,7 +107,7 @@ export default function mindMapModel() {
     // 初始化加载数据 清空画布
     if (init) {
       graph?.clearCells();
-      historyRef.current = [];
+      reset(info);
       graph?.centerContent();
       setTimeout(() => {
         graph?.centerContent();
@@ -169,20 +178,22 @@ export default function mindMapModel() {
     }
 
     // 添加记录
-    if (!ignoreHistory && !ignoreRender) {
-      historyRef.current?.push(info);
-      activeIndex.current = historyRef.current?.length - 1;
-      if (historyRef.current?.length > 20) {
-        historyRef.current?.shift();
-        activeIndex.current -= 1;
-      }
+    if (!ignoreHistory && !ignoreRender && !init) {
+      setHistoryValue(info);
     }
 
     projectInfoRef.current = JSON.parse(JSON.stringify(info));
   };
 
-  const pageSettingRef = useRef<MindMapProjectInfo["pageSetting"]>();
 
+  useEffect(() => {
+    historyValue && setMindProjectInfo({
+      info: historyValue,
+      ignoreHistory: true
+    })
+  }, [historyValue]);
+
+  const pageSettingRef = useRef<MindMapProjectInfo["pageSetting"]>();
   // 画布配置更新
   useEffect(() => {
     if (mindProjectInfo?.pageSetting && graph) {
@@ -203,6 +214,7 @@ export default function mindMapModel() {
           repeat: "repeat",
         });
       }
+      console.log(pageSetting)
       // 设置水印
       if (pageSetting.showWatermark && pageSetting.watermarkText) {
         const canvas = document.createElement("canvas");
@@ -346,36 +358,19 @@ export default function mindMapModel() {
     });
   };
 
-  // 能否重做
-  const canRedo = useMemo(() => {
-    return (
-      historyRef.current?.length > 1 &&
-      activeIndex.current < historyRef.current?.length - 1
-    );
-  }, [historyRef.current, activeIndex.current]);
-
-  // 能否撤销
-  const canUndo = useMemo(() => {
-    return activeIndex.current > 0 && historyRef.current?.length > 1;
-  }, [historyRef.current, activeIndex.current]);
+  // 设置右侧工具激活项
+  const rightToolbarActive = (type: string) => {
+    setRightToolbarActive(rightToobarActive === type ? undefined : type);
+  };
 
   // 撤销
   const onUndo = () => {
-    const info = historyRef.current?.[activeIndex.current - 1];
-    activeIndex.current -= 1;
-    setMindProjectInfo({ info, ignoreHistory: true });
+    back();
   };
 
   // 重做
   const onRedo = () => {
-    const info = historyRef.current?.[activeIndex.current + 1];
-    activeIndex.current += 1;
-    setMindProjectInfo({ info, ignoreHistory: true });
-  };
-
-  // 设置右侧工具激活项
-  const rightToolbarActive = (type: string) => {
-    setRightToolbarActive(rightToobarActive === type ? undefined : type);
+    forward();
   };
 
   const setCorrelationEdgeInfo = (sourceNode?: Node) => {
@@ -447,8 +442,8 @@ export default function mindMapModel() {
     rightToobarActive,
     rightToolbarActive,
     setMindProjectInfo,
-    canUndo,
-    canRedo,
+    canUndo: !!backLength,
+    canRedo: !!forwardLength,
     onUndo,
     onRedo,
     enableFormatBrush,

+ 28 - 21
apps/designer/src/pages/mindmap/render/index.tsx

@@ -47,19 +47,19 @@ export const renderMindMap = ({
       pageSetting
     );
     let originPosition = { x: topic?.x ?? -10, y: topic?.y ?? -10 };
-    if (graph.hasCell(topic.id) && !topic.isSummary) {
-      const node = graph.getCellById(topic.id);
-      if (node.isNode()) {
-        originPosition = node.position();
-      }
-    }
+    // if (graph.hasCell(topic.id) && !topic.isSummary) {
+    //   const node = graph.getCellById(topic.id);
+    //   if (node.isNode()) {
+    //     originPosition = node.position();
+    //   }
+    // }
     const offsetX = originPosition.x - result.x;
     const offsetY = originPosition.y - result.y;
     const traverse = (hierarchyItem: HierarchyResult) => {
       if (hierarchyItem) {
         const { data, children, x, y } = hierarchyItem;
         const id = data?.id || uuid();
-        // 创建主题
+        // 创建主题节点
         const node = graph.createNode({
           ...Topic,
           width: data.width,
@@ -132,10 +132,12 @@ export const renderMindMap = ({
 
     traverse(result);
   });
-  // 概要节点 返回节点数据
+
+  // 数据为概要节点 返回节点数据
   if(returnCells) {
     return cells;
   }
+
   const oldCells = graph.getCells();
   // 移除不要的节点及对应的边
   oldCells.forEach((cell) => {
@@ -144,33 +146,38 @@ export const renderMindMap = ({
       graph.removeCell(cell);
     }
   });
-  // 添加或删除节点
+
+  // 添加节点
   cells
     .filter((cell) => cell.isNode() && !graph.hasCell(cell.id))
     .forEach((cell) => {
       graph.addCell(cell);
     });
+
   // 更新老的节点
   cells
     .filter((cell) => cell.isNode() && graph.hasCell(cell.id))
     .forEach((cell) => {
       cell.isNode() && updateNode(cell, graph);
     });
-  // 添加所需的节点
-  const edgeCells = cells.filter((cell) => cell.isEdge());
-  graph.removeCells(edgeCells);
-  graph.addCell(edgeCells);
+
   // 处理父子节点关系
   cells.forEach((cell) => {
     const data = cell.getData();
     if (data?.parentId) {
       const parent = graph.getCellById(data.parentId);
       const child = graph.getCellById(cell.id);
-      if(parent && child) {
+      if(parent && child && !child.hasParent()) {
+        parent.removeChild(child);
         parent.addChild(child);
       }
     }
   });
+
+  // 添加所需的节点
+  const edgeCells = cells.filter((cell) => cell.isEdge());
+  graph.removeCells(edgeCells);
+  graph.addCell(edgeCells);
 };
 
 // 渲染概要
@@ -271,16 +278,14 @@ const createBorderComponent = (
   });
 };
 
-// 更新现有节点
+// 更新现有节点 优化点
 const updateNode = (node: Node, graph: Graph) => {
   const oldCell = graph.getCellById(node.id);
   if (oldCell.isNode()) {
-    oldCell.setData(node.data, { deep: false});
-    oldCell.position(node.position().x, node.position().y);
+    oldCell.setData(node.data, { silent: false, deep: false });
+    const p = node.position();
+    oldCell.setPosition(p)
     oldCell.setSize(node.size().width, node.size().height);
-    // oldCell.setAttrs(node.attrs);
-    // const cells = node.children?.map(item => graph.getCellById(item.id));
-    // oldCell.setChildren(cells ?? null);
   }
 };
 
@@ -315,6 +320,7 @@ export const addTopic = (
     const traverse = (topics: TopicItem[]) => {
       topics.forEach((item) => {
         if (item.id === parentId) {
+          item.collapsed = false; // 展开父节点
           if (item.children) {
             item.children?.push(topic);
           } else {
@@ -458,7 +464,8 @@ export const getTopicsByAiData = (
     graph?: Graph
   ) => {
     const ids: string[] = [];
-    const topics = data.map((item) => {
+    data = Array.isArray(data) ? data : [data];
+    const topics = data?.map((item) => {
       const topic = buildTopic(
         type,
         { id: item.id, label: item.label },

+ 1 - 1
packages/utils/src/index.ts

@@ -33,7 +33,7 @@ export const getClassRules = (): string => {
       }
     }
   }
-
+  console.log("getClassRules", rules);
   return rules;
 };