소스 검색

feat: 添加文件夹管理、批量操作等

liaojiaxing 7 달 전
부모
커밋
70a9c6207d

+ 97 - 0
apps/designer/src/api/systemDesigner.ts

@@ -89,6 +89,39 @@ export const DeleteFlowchartElement = (data: { id: string }) => {
   });
 };
 
+/**
+ * 批量新增思维导图元素
+ * @param
+ */
+export const BatchAddFlowchartElement = (data: any[]) => {
+  return request("/api/flowchartMindMap/batchAddFlowchartElement", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 批量删除思维导图元素
+ * @param
+ */
+export const BatchDeleteFlowchartElement = (data: { ids: string[] }) => {
+  return request("/api/flowchartMindMap/batchDeleteFlowchartElement", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 批量编辑思维导图元素
+ * @param
+ */
+export const BatchEditFlowchartElement = (data: any[]) => {
+  return request("/api/flowchartMindMap/batchEditFlowchartElement", {
+    method: "POST",
+    data,
+  });
+};
+
 /**
  * 添加思维导图元素
  * @param
@@ -122,6 +155,39 @@ export const DeleteMindMapElement = (data: { id: string }) => {
   });
 };
 
+/**
+ * 批量新增思维导图元素
+ * @param
+ */
+export const BatchAddMindMapElement = (data: any[]) => {
+  return request("/api/flowchartMindMap/batchAddMindMapElement", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 批量删除思维导图元素
+ * @param
+ */
+export const BatchDeleteMindMapElement = (data: { ids: string[] }) => {
+  return request("/api/flowchartMindMap/batchDeleteMindMapElement", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 批量编辑思维导图元素
+ * @param
+ */
+export const BatchEditMindMapElement = (data: any[]) => {
+  return request("/api/flowchartMindMap/batchEditMindMapElement", {
+    method: "POST",
+    data,
+  });
+};
+
 /**
  * 新增文件夹
  * @param
@@ -164,4 +230,35 @@ export const Move = (data: { fileType: string, fileId: string, targetFolderId: s
     method: "POST",
     data,
   });
+};
+
+/**
+ * 全部文件夹
+ * @param
+ */
+export const GetAllFolders = () => {
+  return request("/api/flowchartMindMap/getAllFolders", {
+    method: "POST",
+  });
+};
+
+/**
+ * 保存全部
+ * @param
+ */
+export const SaveAll = (data: any) => {
+  return request("/api/flowchartMindMap/saveAll", {
+    method: "POST",
+    data
+  });
+};
+
+/**
+ * 最近文件
+ * @param
+ */
+export const RecentFile = () => {
+  return request("/api/flowchartMindMap/recentFile", {
+    method: "POST",
+  });
 };

+ 48 - 15
apps/designer/src/events/flowEvent.ts

@@ -1,6 +1,5 @@
 import { Graph, Node, EventArgs } from "@antv/x6";
-import { AddFlowchartElement, EditFlowchartElement, DeleteFlowchartElement } from "@/api/systemDesigner";
-import { debounce, throttle } from "lodash-es";
+import { AddFlowchartElement, BatchEditFlowchartElement, BatchDeleteFlowchartElement } from "@/api/systemDesigner";
 export const handleGraphEvent = (graph: Graph) => {
   const sourceArrowhead = {
     name: "source-arrowhead",
@@ -90,6 +89,8 @@ export const handleGraphEvent = (graph: Graph) => {
 
 export const handleGraphApiEvent = (graph: Graph) => {
   graph.on("cell:added", (args) => {
+    if(args.cell?.data?.isPage) return;
+
     const graphId = sessionStorage.getItem("projectId");
     const data = args.cell.toJSON();
     graphId && AddFlowchartElement({
@@ -99,21 +100,53 @@ export const handleGraphApiEvent = (graph: Graph) => {
     });
   });
 
-  graph.on("cell:change:*", throttle((args: EventArgs["cell:change:*"]) => {
+  // 批量处理节点更新
+  let timer1: any;
+  let map: Record<string, any> = {};
+  graph.on("cell:change:*", (args: EventArgs["cell:change:*"]) => {
     if(args.cell?.data?.isPage) return;
-    
-    const graphId = sessionStorage.getItem("projectId");
-    const data = args.cell.toJSON();
-    graphId && EditFlowchartElement({
-      ...data,
-      graphId,
-      tools: ''
-    });
-  }, 300));
+    const setData = () => {
+      const id = args.cell.id;
+      const data = args.cell.toJSON();
+      delete data.tools;
+      map[id] = data;
+    }
+    if(timer1) {
+      setData();
+      return;
+    }
+    setData();
+    timer1 = setTimeout(() => {
+      const graphId = sessionStorage.getItem("projectId");
+      const list = Object.values(map);
+      if(graphId && list.length > 0) {
+        BatchEditFlowchartElement(list.map(item => {
+          return {
+            ...item,
+            graphId
+          }
+        }));
+      }
+      timer1 = null;
+      map = {};
+    }, 1000);
+  });
 
+  let timer: any
+  const ids: string[] = [];
   graph.on("cell:removed", (args) => {
-    DeleteFlowchartElement({
-      id: args.cell.id
-    });
+    // 加入防抖 如果连续多个删除调用批量删除
+    if(timer) {
+      ids.push(args.cell.id);
+      return;
+    }
+    ids.push(args.cell.id);
+    timer = setTimeout(() => {
+      timer = null;
+      if(ids.length > 0) {
+        BatchDeleteFlowchartElement({ids});
+      }
+      ids.splice(0, ids.length);
+    }, 500);
   });
 };

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

@@ -11,7 +11,7 @@ import { uuid } from "@/utils";
 import { getTheme } from "@/pages/mindmap/theme";
 import { traverseNode } from "@/utils/mindmapHander";
 import { EditMindMapElement, AddMindMapElement } from "@/api/systemDesigner";
-import { debounce } from "lodash-es"
+import { debounce, isEqual } from "lodash-es"
 
 enum positionType {
   left = "left",
@@ -190,6 +190,7 @@ export const bindMindMapEvents = (
    */
   graph.on("node:change:data", (args) => {
     const { current, previous } = args;
+    console.log('数据变更:', current, previous)
     // 收折子项 setMindProjectInfo更新会重新渲染
     if (current.collapsed !== previous.collapsed) {
       setMindProjectInfo &&
@@ -232,6 +233,17 @@ export const bindMindMapEvents = (
           graph
         );
     }
+    // 改线段
+    if (current?.edge && !isEqual(current.edge, previous?.edge)) {
+      setMindProjectInfo &&
+        updateTopic(
+          args.cell.id,
+          { edge: current.edge },
+          setMindProjectInfo,
+          graph
+        );
+    }
+    
     // 本地缓存更新不会重新渲染
     if (args.cell.id.includes("-border")) {
       updateTopic(args.current.origin, { border: current }, (info) => {

+ 1 - 0
apps/designer/src/models/graphModel.ts

@@ -45,6 +45,7 @@ export default function GraphModel() {
   };
 
   const addPageNode = () => {
+    console.log('增加画布节点')
     const graph = graphRef.current;
     pageNodeRef.current = graph?.addNode({
       shape: "page-container-node",

+ 12 - 6
apps/designer/src/models/mindMapModel.ts

@@ -17,7 +17,7 @@ import { isEqual, cloneDeep } from "lodash-es";
 import { bindMindmapKeys } from "@/utils/fastKey";
 import { handleCreateCorrelationEdge } from "@/utils/mindmapHander";
 import { Dnd } from "@antv/x6-plugin-dnd";
-import { EditGraph, EditMindMapElement } from "@/api/systemDesigner";
+import { EditGraph, BatchEditMindMapElement } from "@/api/systemDesigner";
 export default function mindMapModel() {
   const [rightToobarActive, setRightToolbarActive] = useState<string>();
   // 格式刷启用
@@ -43,12 +43,18 @@ export default function mindMapModel() {
     if(!init && !isSetting) {
       const graphId = sessionStorage.getItem("projectId");
       if(graphId && info?.topics) {
-        info.topics.forEach((topic) => {
-          EditMindMapElement({
+        BatchEditMindMapElement(info.topics.map(topic => {
+          return {
             ...topic,
-            graphId,
-          });
-        });
+            graphId
+          }
+        }));
+        // info.topics.forEach((topic) => {
+        //   EditMindMapElement({
+        //     ...topic,
+        //     graphId,
+        //   });
+        // });
       }
     }
 

+ 16 - 5
apps/designer/src/pages/flow/components/MenuBar/index.tsx

@@ -1,5 +1,5 @@
 import { DownloadOutlined, LeftOutlined } from "@ant-design/icons";
-import { Button, Input, Dropdown, MenuProps, Tooltip, InputRef } from "antd";
+import { Button, Input, Dropdown, MenuProps, Tooltip, InputRef, message } from "antd";
 import React, { useEffect, useRef, useState } from "react";
 import logo from "@/assets/logo.png";
 import { Icon, useModel } from "umi";
@@ -28,7 +28,7 @@ import {
 import FindReplaceModal from "@/components/FindReplaceModal";
 import { useFindReplace } from "@/hooks/useFindReplace";
 import { GraphType } from "@/enum";
-import { EditGraph } from "@/api/systemDesigner";
+import { EditGraph, SaveAll } from "@/api/systemDesigner";
 
 InsertCss(`
   .custom-color-picker-popover {
@@ -46,7 +46,7 @@ export default function MenuBar() {
     setShowHistory,
   } = useModel("appModel");
   const { projectInfo, setProjectInfo } = useModel("projectModel");
-  const { graph, canRedo, canUndo, selectedCell } = useModel("graphModel");
+  const { graph, canRedo, canUndo, selectedCell, } = useModel("graphModel");
   const inputRef = useRef<InputRef>(null);
 
   // 重命名
@@ -122,8 +122,19 @@ export default function MenuBar() {
   // 预览 todo
   const handlePreview = () => {};
 
-  // 保存 todo
-  const handleSave = () => {};
+  // 保存
+  const handleSave = async () => {
+    const elements = graph?.toJSON()?.cells;
+    await SaveAll({
+      graph: {
+        ...(projectInfo?.graph || {}),
+        ...pageState,
+        type: GraphType.flowchart
+      },
+      elements: elements?.filter(item => !item.data?.isPage)
+    });
+    message.success("保存成功");
+  };
 
   // 克隆 todo
   const handleClone = () => {};

+ 1 - 1
apps/designer/src/pages/flow/components/ToolBar/index.tsx

@@ -144,7 +144,7 @@ export default function ToolBar() {
         }
         if ((cell.isEdge() && target === "edge") || target === "all") {
           cell.setData(data);
-          handleSetEdgeStyle(cell, formData.connectorType, pageState.jumpover);
+          handleSetEdgeStyle(cell, formData.connectorType, pageState.jumpOver);
         }
       });
 

+ 136 - 49
apps/designer/src/pages/home/All.tsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useRef, useState } from "react";
+import React, { useEffect, useMemo, useRef, useState } from "react";
 import { ProTable, PageContainer } from "@ant-design/pro-components";
 import {
   Card,
@@ -13,6 +13,8 @@ import {
   Tree,
   Dropdown,
   message,
+  TreeDataNode,
+  Tag,
 } from "antd";
 import type { MenuProps } from "antd";
 import { Icon, useRequest } from "umi";
@@ -27,13 +29,14 @@ import {
   EditFolder,
   DeleteFolder,
   Move,
+  GetAllFolders,
 } from "@/api/systemDesigner";
 import ProjectCard from "./ProjectCard";
 import { graphOptions } from "./data";
 import { GraphType } from "@/enum";
 import DragItem from "./DragItem";
 import DropItem from "./DropItem";
-import { createNew } from "@/utils";
+import { createNew, listToTree } from "@/utils";
 
 type BreadcrumbItem = {
   title: React.ReactNode;
@@ -43,20 +46,25 @@ type BreadcrumbItem = {
 };
 export default function All({
   onChangeCurrentFolder,
+  updateKey,
 }: {
   onChangeCurrentFolder: (id: string) => void;
+  updateKey: number;
 }) {
   const [display, setDisplay] = useState("card");
   const [folderData, setFolderData] = useState<any[]>([]);
   const [dataSource, setDataSource] = useState<any[]>([]);
   const [total, setTotal] = useState(0);
   const [searchName, setSearchName] = useState("");
+  const [graphType, setGraphType] = useState<GraphType | ''>('');
   const [currentPage, setCurrentPage] = useState(1);
   const [pageSize, setPageSize] = useState(12);
   const [open, setOpen] = useState(false);
   const currentFolder = useRef("root");
   const { confirm } = Modal;
   const moveSource = useRef<{ id: string; type: "folder" | "chart" }>();
+  const [folderList, setFolderList] = useState<any[]>([]);
+  const [selectFolder, setSelectFolder] = useState("");
 
   const { loading, run, refresh } = useRequest(FlowchartMindMapList, {
     defaultParams: [
@@ -83,6 +91,16 @@ export default function All({
     },
   });
 
+  useRequest(GetAllFolders, {
+    onSuccess: (res) => {
+      setFolderList(res?.result || []);
+    },
+  });
+
+  useEffect(() => {
+    updateKey && getData(1, pageSize);
+  }, [updateKey]);
+
   useEffect(() => {
     onChangeCurrentFolder(currentFolder.current);
   }, [currentFolder.current]);
@@ -118,7 +136,7 @@ export default function All({
     useState<BreadcrumbItem[]>(defultData);
   const breadcrumbDataRef = useRef<BreadcrumbItem[]>(breadcrumbData);
 
-  const getData = (page: number, pageSize: number) => {
+  const getData = (page: number, pageSize: number, type?: GraphType | '') => {
     setCurrentPage(page);
     setPageSize(pageSize);
     run({
@@ -133,6 +151,10 @@ export default function All({
           name: "folderId",
           value: currentFolder.current,
         },
+        {
+          name: "type",
+          value: type !== undefined ? type : graphType,
+        },
       ],
     });
   };
@@ -145,24 +167,46 @@ export default function All({
     );
   };
 
-  const folderTreeData = [
-    {
-      key: "1",
-      title: (
-        <span>
-          我的文件<span className="text-12px color-#999">(当前)</span>
-        </span>
+  const root = {
+    id: "root",
+    name: "我的文件",
+    key: "root",
+    title: (
+      <span>
+        我的文件
+        {currentFolder.current === "root" ? (
+          <span className="text-12px color-#999">(当前)</span>
+        ) : null}
+      </span>
+    ),
+    icon: <FolderIcon />,
+    parentId: "",
+    children: [],
+  };
+
+  // 构建文件夹树
+  const folderTreeData = useMemo((): TreeDataNode[] => {
+    return [
+      listToTree(
+        folderList.map((item) => {
+          return {
+            ...item,
+            key: item.id,
+            title: (
+              <span>
+                {item.name}
+                {currentFolder.current === item.id ? (
+                  <span className="text-12px color-#999">(当前)</span>
+                ) : null}
+              </span>
+            ),
+            icon: <FolderIcon />,
+          };
+        }),
+        root
       ),
-      icon: <FolderIcon />,
-      children: [
-        {
-          key: "1-1",
-          title: "文件夹1",
-          icon: <FolderIcon />,
-        },
-      ],
-    },
-  ];
+    ] as unknown as TreeDataNode[];
+  }, [folderList, currentFolder.current]);
 
   const contextMenu: MenuProps["items"] = [
     {
@@ -201,7 +245,7 @@ export default function All({
       icon: <Icon icon="local:flow" width="22px" />,
       onClick: () => {
         createNew(GraphType.flowchart, currentFolder.current);
-      }
+      },
     },
     {
       key: "3",
@@ -209,7 +253,7 @@ export default function All({
       icon: <Icon icon="local:mind" width="22px" />,
       onClick: () => {
         createNew(GraphType.mindmap, currentFolder.current);
-      }
+      },
     },
   ];
 
@@ -248,7 +292,10 @@ export default function All({
   };
 
   // 搜索图形类型
-  const setSearchGraphType = (type: GraphType) => {};
+  const setSearchGraphType = (type: GraphType | '') => {
+    setGraphType(type);
+    getData(1, pageSize, type);
+  };
 
   const handleOpenFolderModal = (id: string, type: "folder" | "chart") => {
     moveSource.current = {
@@ -263,10 +310,12 @@ export default function All({
       id,
       type,
     };
+    setOpen(false);
   };
 
   const handleMove = (targetFolderId: string) => {
-    if (!moveSource.current || targetFolderId === moveSource.current?.id) return;
+    if (!moveSource.current || targetFolderId === moveSource.current?.id)
+      return;
     Move({
       fileType: moveSource.current.type,
       fileId: moveSource.current.id,
@@ -277,6 +326,10 @@ export default function All({
     });
   };
 
+  const handleCopy = (targetFolderId: string) => {
+    // TODO: 复制
+  };
+
   const clearMoveSource = () => {
     moveSource.current = undefined;
   };
@@ -285,23 +338,38 @@ export default function All({
     <>
       <PageContainer
         extra={[
-          <Dropdown
-            key="1"
-            menu={{
-              items: graphOptions.map((item) => {
-                return {
-                  key: item.key,
-                  label: item.label,
-                  value: item.value,
-                  onClick: () => {
-                    setSearchGraphType(item.value);
-                  },
-                };
-              }),
-            }}
-          >
-            <Button key="1" icon={<i className="iconfont icon-shaixuan" />} />
-          </Dropdown>,
+          <span key="1">
+            {graphType ? (
+              <Tag
+                color="#108ee9"
+                closable
+                onClose={() => setSearchGraphType('')}
+              >
+                { graphOptions.find(item => item.value === graphType)?.label}
+              </Tag>
+            ) : (
+              <Dropdown
+                key="1"
+                menu={{
+                  items: graphOptions.map((item) => {
+                    return {
+                      key: item.key,
+                      label: item.label,
+                      value: item.value,
+                      onClick: () => {
+                        setSearchGraphType(item.value);
+                      },
+                    };
+                  }),
+                }}
+              >
+                <Button
+                  key="1"
+                  icon={<i className="iconfont icon-shaixuan" />}
+                />
+              </Dropdown>
+            )}
+          </span>,
           <Button
             key="2"
             onClick={() => {
@@ -412,7 +480,10 @@ export default function All({
                                               key: "2",
                                               label: "移动或复制",
                                               onClick: () => {
-                                                handleOpenFolderModal(item.id, 'folder');
+                                                handleOpenFolderModal(
+                                                  item.id,
+                                                  "folder"
+                                                );
                                               },
                                             },
                                             {
@@ -438,7 +509,10 @@ export default function All({
                                           },
                                         }}
                                       >
-                                        <MoreOutlined className="hidden group-hover/item:inline-block" onClick={(e) => e.stopPropagation()} />
+                                        <MoreOutlined
+                                          className="hidden group-hover/item:inline-block"
+                                          onClick={(e) => e.stopPropagation()}
+                                        />
                                       </Dropdown>
                                     </span>
                                   }
@@ -477,7 +551,9 @@ export default function All({
                             record={item}
                             onFresh={refresh}
                             onDelete={refresh}
-                            onChangeLocation={() => handleOpenFolderModal(item.id, 'chart')}
+                            onChangeLocation={() =>
+                              handleOpenFolderModal(item.id, "chart")
+                            }
                           />
                         </DragItem>
                       </Col>
@@ -540,7 +616,10 @@ export default function All({
         title="移动/复制到"
         width={440}
         open={open}
-        onCancel={() => setOpen(false)}
+        onCancel={() => {
+          setOpen(false);
+          setSelectFolder("");
+        }}
         centered
         footer={
           <div>
@@ -550,18 +629,26 @@ export default function All({
             <Button
               className="m-r-8px"
               type="primary"
-              onClick={() => setOpen(false)}
+              onClick={() => handleMove(selectFolder)}
+              disabled={selectFolder === currentFolder.current}
             >
               移动
             </Button>
-            <Button type="primary" onClick={() => setOpen(false)}>
+            {/* <Button type="primary" onClick={() => handleCopy(selectFolder)}>
               复制
-            </Button>
+            </Button> */}
           </div>
         }
       >
         <div className="min-h-300px">
-          <Tree treeData={folderTreeData} showIcon blockNode />
+          <Tree
+            treeData={folderTreeData}
+            selectedKeys={[selectFolder]}
+            onSelect={(keys) => setSelectFolder(keys[0] as string)}
+            showIcon
+            blockNode
+            autoExpandParent
+          />
         </div>
       </Modal>
     </>

+ 19 - 70
apps/designer/src/pages/home/Recently.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
 import { ProTable, PageContainer } from "@ant-design/pro-components";
 import {
   Col,
@@ -14,12 +14,18 @@ import {
 } from "antd";
 import { useRequest } from "umi";
 import { AppstoreOutlined, MenuOutlined } from "@ant-design/icons";
-import { FlowchartMindMapList } from "@/api/systemDesigner";
+import { RecentFile } from "@/api/systemDesigner";
 import ProjectCard from "./ProjectCard";
 import { graphOptions } from "./data";
 import { GraphType } from "@/enum";
 
-export default function Recently() {
+export default function Recently(
+  {
+    updateKey,
+  }: {
+    updateKey: number;
+  }
+) {
   const [display, setDisplay] = useState("card");
   const [dataSource, setDataSource] = useState<any[]>([]);
   const [total, setTotal] = useState(0);
@@ -27,19 +33,18 @@ export default function Recently() {
   const [currentPage, setCurrentPage] = useState(1);
   const [open, setOpen] = useState(false);
 
-  const { loading, run, refresh } = useRequest(FlowchartMindMapList, {
-    defaultParams: [
-      {
-        currentPage: 1,
-        pageSize: 12,
-      },
-    ],
+  const { loading, run, refresh } = useRequest(RecentFile, {
     onSuccess: (res) => {
-      setDataSource(res?.result?.model || []);
-      setTotal(res?.result?.totalCount || 0);
+      const list = res?.result || []
+      setDataSource(list);
+      setTotal(list.length);
     },
   });
 
+  useEffect(() => {
+    run();
+  }, [updateKey]);
+
   const FolderIcon = function () {
     return (
       <svg className="icon" aria-hidden="true">
@@ -70,16 +75,7 @@ export default function Recently() {
   // 搜索文件
   const handleSearch = () => {
     setCurrentPage(1);
-    run({
-      currentPage: 1,
-      pageSize: 12,
-      filters: [
-        {
-          name: "name",
-          value: searchName,
-        },
-      ],
-    });
+    run();
   };
   // 搜索图形类型
   const setSearchGraphType = (type: GraphType) => {};
@@ -88,23 +84,6 @@ export default function Recently() {
     <>
       <PageContainer
         extra={[
-          <Dropdown
-            key="1"
-            menu={{
-              items: graphOptions.map((item) => {
-                return {
-                  key: item.key,
-                  label: item.label,
-                  value: item.value,
-                  onClick: () => {
-                    setSearchGraphType(item.value);
-                  },
-                };
-              }),
-            }}
-          >
-            <Button key="1" icon={<i className="iconfont icon-shaixuan" />} />
-          </Dropdown>,
           <Button
             key="2"
             onClick={() => {
@@ -113,17 +92,7 @@ export default function Recently() {
             icon={display === "card" ? <MenuOutlined /> : <AppstoreOutlined />}
           />,
         ]}
-        title={
-          <Input
-            placeholder="搜索最近文件"
-            prefix={<i className="iconfont icon-chazhao" />}
-            value={searchName}
-            onChange={(e) => {
-              setSearchName(e.target.value);
-            }}
-            onPressEnter={handleSearch}
-          />
-        }
+        title={''}
         footer={[]}
       >
         <Breadcrumb
@@ -150,22 +119,6 @@ export default function Recently() {
                 );
               })}
             </Row>
-            <div className="flex justify-end py-16px">
-              <Pagination
-                total={total}
-                defaultCurrent={1}
-                pageSize={12}
-                pageSizeOptions={["12", "24", "36"]}
-                onChange={(page, pageSize) => {
-                  setCurrentPage(page);
-                  run({
-                    currentPage: page,
-                    pageSize: pageSize,
-                  });
-                }}
-                hideOnSinglePage
-              />
-            </div>
             {dataSource.length == 0 ? <Empty description="暂无数据" /> : null}
           </>
         ) : (
@@ -196,10 +149,6 @@ export default function Recently() {
               pageSizeOptions: ["12", "24", "36"],
               onChange: (page, pageSize) => {
                 setCurrentPage(page);
-                run({
-                  currentPage: page,
-                  pageSize: pageSize,
-                });
               },
             }}
           />

+ 17 - 11
apps/designer/src/pages/home/index.tsx

@@ -24,6 +24,12 @@ import { GraphType } from "@/enum";
 
 export default () => {
   const currentFolder = useRef('root');
+  const [updateKey, setUpdateKey] = useState(0);
+
+  const handleCreate = async (type: GraphType) => {
+    await createNew(type, currentFolder.current);
+    setUpdateKey(updateKey + 1);
+  }
 
   const basicGraph = [
     {
@@ -32,7 +38,7 @@ export default () => {
       subtitle: "图形化表单方式",
       color: "#dfecff",
       icon: <Icon icon="local:flow" />,
-      onClick: () => createNew(GraphType.flowchart, currentFolder.current),
+      onClick: () => handleCreate(GraphType.flowchart),
     },
     {
       id: "2",
@@ -40,7 +46,7 @@ export default () => {
       subtitle: "结构化表单方式",
       color: "#dff4ea",
       icon: <Icon icon="local:mind" />,
-      onClick: () => createNew(GraphType.mindmap, currentFolder.current),
+      onClick: () => handleCreate(GraphType.mindmap),
     },
   ];
   
@@ -49,37 +55,37 @@ export default () => {
       id: "1",
       title: "UML",
       icon: <Icon icon="local:uml" />,
-      onClick: () => createNew(GraphType.uml, currentFolder.current),
+      onClick: () => handleCreate(GraphType.uml),
     },
     {
       id: "2",
       title: "E-R图",
       icon: <Icon icon="local:er" />,
-      onClick: () => createNew(GraphType.er, currentFolder.current),
+      onClick: () => handleCreate(GraphType.er),
     },
     {
       id: "3",
       title: "泳道图",
       icon: <Icon icon="local:swimlane" />,
-      onClick: () => createNew(GraphType.lane, currentFolder.current),
+      onClick: () => handleCreate(GraphType.lane),
     },
     {
       id: "4",
       title: "BPMN",
       icon: <Icon icon="local:bpmn" />,
-      onClick: () => createNew(GraphType.bpmn, currentFolder.current),
+      onClick: () => handleCreate(GraphType.bpmn),
     },
     {
       id: "5",
       title: "韦恩图",
       icon: <Icon icon="local:we" />,
-      onClick: () => createNew(GraphType.venn, currentFolder.current),
+      onClick: () => handleCreate(GraphType.venn),
     },
     {
       id: "6",
       title: "网络拓扑图",
       icon: <Icon icon="local:net" />,
-      onClick: () => createNew(GraphType.net, currentFolder.current),
+      onClick: () => handleCreate(GraphType.net),
     },
   ];
   
@@ -161,13 +167,13 @@ export default () => {
       path: "/all",
       name: "全部",
       icon: <i className="iconfont icon-xitong" />,
-      component: <All onChangeCurrentFolder={ handleChangeFolder }/>,
+      component: <All onChangeCurrentFolder={ handleChangeFolder } updateKey={updateKey} />,
     },
     {
       path: "/recently",
       name: "最近项目",
       icon: <i className="iconfont icon-shijian" />,
-      component: <Recently />,
+      component: <Recently updateKey={updateKey} />,
     },
     {
       path: "/template",
@@ -186,7 +192,7 @@ export default () => {
   const content = useMemo(() => {
     currentFolder.current = "root";
     return routes.find((item) => item.path === pathname)?.component;
-  }, [pathname]);
+  }, [pathname, updateKey]);
 
   return (
     <div

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

@@ -1,5 +1,5 @@
 import React, { useEffect, useMemo, useRef, useState } from "react";
-import { Button, Input, Dropdown, Tooltip, MenuProps, Divider } from "antd";
+import { Button, Input, Dropdown, Tooltip, MenuProps, Divider, message } from "antd";
 import { LeftOutlined, MenuOutlined } from "@ant-design/icons";
 import logo from "@/assets/logo.png";
 import { useModel, Icon } from "umi";
@@ -9,6 +9,7 @@ import { selectTopic, addBorder, addSummary } from "@/utils/mindmapHander";
 import { createNew } from "@/utils";
 import FindReplaceModal from "@/components/FindReplaceModal";
 import { useFindReplace } from "@/hooks/useFindReplace";
+import { SaveAll } from "@/api/systemDesigner";
 
 export default function index() {
   const {
@@ -57,8 +58,22 @@ export default function index() {
   // 预览 todo
   const handlePreview = () => {};
 
-  // 保存 todo
-  const handleSave = () => {};
+  // 保存
+  const handleSave = async () => {
+    const graphInfo = {
+      ...(mindProjectInfo || {}),
+      ...mindProjectInfo?.pageSetting,
+      type: GraphType.mindmap
+    }
+    delete graphInfo.pageSetting;
+    delete graphInfo.topics;
+    await SaveAll({
+      graph: graphInfo,
+      elements: mindProjectInfo?.topics
+    });
+
+    message.success("保存成功");
+  };
 
   // 克隆 todo
   const handleClone = () => {};

+ 33 - 0
apps/designer/src/utils/index.ts

@@ -54,4 +54,37 @@ export const createNew = async (type: string, folderId: string = 'root') => {
     window.open(`${origin}${pathname}#/flow/${id}?enterpriseCode=${enterpriseCode}`);
   }
   return id;
+};
+
+type TreeNode = {
+  id: string;
+  parentId?: string;
+  name: string;
+  children?: TreeNode[];
+} & Record<string, any>;
+
+/**
+ * 列表转树结构
+ * @param list
+ * @param pid
+ * @param parentKey
+ */
+export const listToTree = (list: TreeNode[], parent: TreeNode): TreeNode => {
+  const map: { [key: string]: TreeNode } = {
+    [parent.id]: parent,
+  };
+
+  // 首先将所有节点放入 map 中
+  list.forEach((node) => {
+    map[node.id] = { ...node, children: [] };
+  });
+  // 再次遍历列表,构建树结构
+  list.forEach((node) => {
+    const currentNode = map[node.id];
+    if (node.parentId && map[node.parentId]) {
+      // 如果有父节点,则将当前节点添加到父节点的 children 中
+      map?.[node.parentId]?.children?.push(currentNode);
+    }
+  });
+  return parent;
 };

+ 4 - 2
apps/designer/src/utils/mindmapHander.tsx

@@ -11,7 +11,7 @@ import { exportImage } from "@/components/ExportImage";
 import TopicBorder from "@/components/mindMap/Border";
 import SummaryBorder from "@/components/mindMap/SummaryBorder";
 import { openInsertImageModal } from "@/components/ImageModal";
-import { DeleteMindMapElement } from "@/api/systemDesigner";
+import { BatchDeleteMindMapElement } from "@/api/systemDesigner";
 
 export const selectTopic = (graph: Graph, topic?: TopicItem) => {
   if (topic?.id) {
@@ -120,6 +120,7 @@ export const deleteTopics = (
   const mindProjectInfo: MindMapProjectInfo = graph.extendAttr.getMindProjectInfo();
   if (!mindProjectInfo || !setMindProjectInfo) return;
   const topics = cloneDeep(mindProjectInfo.topics);
+  const deleteIds: string[] = [];
   const filterTopics = (list: TopicItem[]): TopicItem[] => {
     const result: TopicItem[] = [];
     for (const item of list) {
@@ -134,7 +135,7 @@ export const deleteTopics = (
       } else {
         if(!item.parentId && !item.isSummary) {
           // 删除自由主题
-          DeleteMindMapElement({id: item.id});
+          deleteIds.push(item.id);
         }
       }
     }
@@ -142,6 +143,7 @@ export const deleteTopics = (
   };
 
   mindProjectInfo.topics = filterTopics(topics);
+  BatchDeleteMindMapElement({ids: deleteIds});
 
   setMindProjectInfo(mindProjectInfo);
   localStorage.setItem("minMapProjectInfo", JSON.stringify(mindProjectInfo));