Browse Source

feat: 添加文件夹管理

liaojiaxing 5 months ago
parent
commit
778f00a3df

+ 45 - 1
apps/designer/src/api/systemDesigner.ts

@@ -27,7 +27,7 @@ export const FlowchartMindMapInfo = (data: { id: string }) => {
  * 添加系统设计图
  * @param 1思维导图 其他自定义
  */
-export const AddGraph = (data: { type: string }) => {
+export const AddGraph = (data: { type: string, folderId: string }) => {
   return request("/api/flowchartMindMap/addGraph", {
     method: "POST",
     data,
@@ -120,4 +120,48 @@ export const DeleteMindMapElement = (data: { id: string }) => {
     method: "POST",
     data,
   });
+};
+
+/**
+ * 新增文件夹
+ * @param
+ */
+export const AddFolder = (data: { name: string, parentId: string }) => {
+  return request("/api/flowchartMindMap/addFolder", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 编辑文件夹
+ * @param
+ */
+export const EditFolder = (data: { id: string, name: string }) => {
+  return request("/api/flowchartMindMap/editFolder", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 删除文件夹
+ * @param
+ */
+export const DeleteFolder = (data: { id: string }) => {
+  return request("/api/flowchartMindMap/deleteFolder", {
+    method: "POST",
+    data,
+  });
+};
+
+/**
+ * 移动文件
+ * @param
+ */
+export const Move = (data: { fileType: string, fileId: string, targetFolderId: string }) => {
+  return request("/api/flowchartMindMap/move", {
+    method: "POST",
+    data,
+  });
 };

+ 0 - 12
apps/designer/src/events/mindMapEvent.ts

@@ -29,7 +29,6 @@ let currentShadowNode: Node | undefined;
 
 export const bindMindMapEvents = (
   graph: Graph,
-  mindProjectInfo?: MindMapProjectInfo,
   setMindProjectInfo?: (info: MindMapProjectInfo) => void,
   setSelectedCell?: (cell: Cell[]) => void,
   dndRef?: React.MutableRefObject<Dnd | undefined>
@@ -310,17 +309,6 @@ export const bindMindMapEvents = (
     }
   });
 
-  // 修改主题 传给后端
-  // graph.on("node:change:*", (args: EventArgs["node:change:*"]) => {
-  //   const graphId = sessionStorage.getItem("projectId");
-  //   if(graphId && !args.current?.parentId && args.key === "data") {
-  //     EditMindMapElement({
-  //       ...args.current,
-  //       graphId,
-  //       tools: ''
-  //     });
-  //   }
-  // });
 };
 
 const canBeFreeNode = (x: number, y: number, node: Node): boolean => {

+ 1 - 2
apps/designer/src/models/mindMapModel.ts

@@ -212,7 +212,6 @@ export default function mindMapModel() {
     // 绑定事件
     bindMindMapEvents(
       instance,
-      mindProjectInfo,
       setMindProjectInfo,
       setSelectedCell,
       dndRef
@@ -225,7 +224,7 @@ export default function mindMapModel() {
       setRightToolbarActive,
       correlationEdgeRef,
       setMindProjectInfo,
-      getMindProjectInfo: () => projectInfoRef.current
+      getMindProjectInfo: () => cloneDeep(projectInfoRef.current)
     };
 
     setGraph(instance);

+ 427 - 165
apps/designer/src/pages/home/All.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
 import { ProTable, PageContainer } from "@ant-design/pro-components";
 import {
   Card,
@@ -12,28 +12,57 @@ import {
   Modal,
   Tree,
   Dropdown,
+  message,
 } from "antd";
-import { useRequest } from "umi";
-import { AppstoreOutlined, MenuOutlined } from "@ant-design/icons";
-import { FlowchartMindMapList } from "@/api/systemDesigner";
+import type { MenuProps } from "antd";
+import { Icon, useRequest } from "umi";
+import {
+  AppstoreOutlined,
+  MenuOutlined,
+  MoreOutlined,
+} from "@ant-design/icons";
+import {
+  FlowchartMindMapList,
+  AddFolder,
+  EditFolder,
+  DeleteFolder,
+  Move,
+} 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";
 
-export default function All() {
+type BreadcrumbItem = {
+  title: React.ReactNode;
+  index: number;
+  id: string;
+  onClick: () => void;
+};
+export default function All({
+  onChangeCurrentFolder,
+}: {
+  onChangeCurrentFolder: (id: string) => void;
+}) {
   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 [currentPage, setCurrentPage] = useState(1);
+  const [pageSize, setPageSize] = useState(12);
   const [open, setOpen] = useState(false);
-  const [currentFolder, setCurrentFolder] = useState("root");
+  const currentFolder = useRef("root");
+  const { confirm } = Modal;
+  const moveSource = useRef<{ id: string; type: "folder" | "chart" }>();
 
   const { loading, run, refresh } = useRequest(FlowchartMindMapList, {
     defaultParams: [
       {
         currentPage,
-        pageSize: 12,
+        pageSize,
         filters: [
           {
             name: "name",
@@ -41,17 +70,73 @@ export default function All() {
           },
           {
             name: "folderId",
-            value: currentFolder
-          }
+            value: currentFolder.current,
+          },
         ],
       },
     ],
     onSuccess: (res) => {
-      setDataSource(res?.result?.model || []);
-      setTotal(res?.result?.totalCount || 0);
+      const result = res?.result || {};
+      setDataSource(result?.pageList?.model || []);
+      setFolderData(result?.folders || []);
+      setTotal(result?.pageList?.totalCount || 0);
     },
   });
 
+  useEffect(() => {
+    onChangeCurrentFolder(currentFolder.current);
+  }, [currentFolder.current]);
+
+  // 面包屑点击
+  const handleBreadcrumbClick = (index: number) => {
+    const folderItem = breadcrumbDataRef.current[index];
+    if (currentFolder.current === folderItem.id) return;
+    currentFolder.current = folderItem.id;
+
+    const newData = breadcrumbDataRef.current.slice(0, index + 1);
+    setBreadcrumbData(newData);
+    breadcrumbDataRef.current = newData;
+
+    updateCurrentFolderAndGetData(folderItem.id);
+  };
+  const defultData = [
+    {
+      title: (
+        <span className="ant-breadcrumb-link">
+          <a>我的文件</a>
+        </span>
+      ),
+      index: 0,
+      id: "root",
+      onClick: () => {
+        handleBreadcrumbClick(0);
+      },
+    },
+  ];
+
+  const [breadcrumbData, setBreadcrumbData] =
+    useState<BreadcrumbItem[]>(defultData);
+  const breadcrumbDataRef = useRef<BreadcrumbItem[]>(breadcrumbData);
+
+  const getData = (page: number, pageSize: number) => {
+    setCurrentPage(page);
+    setPageSize(pageSize);
+    run({
+      currentPage: page,
+      pageSize: pageSize,
+      filters: [
+        {
+          name: "searchText",
+          value: searchName,
+        },
+        {
+          name: "folderId",
+          value: currentFolder.current,
+        },
+      ],
+    });
+  };
+
   const FolderIcon = function () {
     return (
       <svg className="icon" aria-hidden="true">
@@ -79,51 +164,143 @@ export default function All() {
     },
   ];
 
+  const contextMenu: MenuProps["items"] = [
+    {
+      key: "1",
+      label: <span className="ml-4px">文件夹</span>,
+      icon: <FolderIcon />,
+      onClick: () => {
+        let name = "";
+        confirm({
+          title: "添加文件夹",
+          centered: true,
+          icon: <></>,
+          content: (
+            <Input
+              defaultValue={name}
+              onChange={(e) => {
+                name = e.target.value;
+              }}
+            />
+          ),
+          onOk: async () => {
+            await AddFolder({
+              parentId: currentFolder.current,
+              name,
+            });
+            message.success("添加成功");
+            refresh();
+          },
+        });
+      },
+    },
+    { type: "divider" },
+    {
+      key: "2",
+      label: <span className="ml-4px">流程图</span>,
+      icon: <Icon icon="local:flow" width="22px" />,
+      onClick: () => {
+        createNew(GraphType.flowchart, currentFolder.current);
+      }
+    },
+    {
+      key: "3",
+      label: <span className="ml-4px">思维导图</span>,
+      icon: <Icon icon="local:mind" width="22px" />,
+      onClick: () => {
+        createNew(GraphType.mindmap, currentFolder.current);
+      }
+    },
+  ];
+
+  // 更新当前文件夹并获取数据
+  const updateCurrentFolderAndGetData = (folderId: string) => {
+    currentFolder.current = folderId;
+    getData(1, pageSize);
+  };
+
   // 打开文件夹
-  const handleOpenFolder = () => {};
+  const handleOpenFolder = (folderItem: { id: string; name: string }) => {
+    const newData = [
+      ...breadcrumbData,
+      {
+        title: (
+          <span className="ant-breadcrumb-link">
+            <a>{folderItem.name}</a>
+          </span>
+        ),
+        index: breadcrumbData.length,
+        id: folderItem.id,
+        onClick: () => {
+          handleBreadcrumbClick(breadcrumbData.length);
+        },
+      },
+    ];
+    setBreadcrumbData(newData);
+    breadcrumbDataRef.current = newData;
+    updateCurrentFolderAndGetData(folderItem.id);
+  };
 
   // 搜索文件
   const handleSearch = () => {
     setCurrentPage(1);
-    run({
-      currentPage: 1,
-      pageSize: 12,
-      filters: [
-        {
-          name: "name",
-          value: searchName,
-        },
-      ],
-    });
+    getData(1, pageSize);
   };
 
   // 搜索图形类型
-  const setSearchGraphType = (type: GraphType) => {
-
-  }
+  const setSearchGraphType = (type: GraphType) => {};
 
-  const handleOpenFolderModal = (id: string) => {
+  const handleOpenFolderModal = (id: string, type: "folder" | "chart") => {
+    moveSource.current = {
+      id,
+      type,
+    };
     setOpen(true);
   };
 
+  const setMoveSource = (id: string, type: "folder" | "chart") => {
+    moveSource.current = {
+      id,
+      type,
+    };
+  };
+
+  const handleMove = (targetFolderId: string) => {
+    if (!moveSource.current || targetFolderId === moveSource.current?.id) return;
+    Move({
+      fileType: moveSource.current.type,
+      fileId: moveSource.current.id,
+      targetFolderId,
+    }).then(() => {
+      message.success("移动成功");
+      refresh();
+    });
+  };
+
+  const clearMoveSource = () => {
+    moveSource.current = undefined;
+  };
+
   return (
     <>
       <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
+            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"
@@ -135,7 +312,7 @@ export default function All() {
         ]}
         title={
           <Input
-            placeholder="搜索文件/文件夹"
+            placeholder="搜索文件"
             prefix={<i className="iconfont icon-chazhao" />}
             value={searchName}
             onChange={(e) => {
@@ -146,132 +323,217 @@ export default function All() {
         }
         footer={[]}
       >
-        <Breadcrumb
-          items={[
-            {
-              title: "我的文件",
-            },
-            {
-              title: "文件夹",
-            },
-          ]}
-        ></Breadcrumb>
-        {display === "card" ? (
-          <>
-            <div className="text-12px color-#999 my-8px">文件夹</div>
-            <div className="flex gap-12px flex-wrap">
-              {new Array(2).fill(0).map((item, index) => {
-                return (
-                  <Card
-                    hoverable
-                    bordered
-                    className="w-240px"
-                    onClick={() => handleOpenFolder()}
-                    key={index}
-                  >
-                    <Card.Meta
-                      title={
-                        <span>
-                          <svg className="icon" aria-hidden="true">
-                            <use xlinkHref="#icon-weibiaoti-_huabanfuben"></use>
-                          </svg>
-                          <span className="ml-8px">文件夹</span>
-                        </span>
-                      }
-                    />
-                  </Card>
-                );
-              })}
-            </div>
-            <div className="text-12px color-#999 my-8px">文件</div>
-            <Row gutter={[8, 16]}>
-              {dataSource.map((item, index) => {
-                return (
-                  <Col xs={12} sm={8} md={6} lg={6} xl={6} xxl={6} key={index}>
-                    <ProjectCard
-                      record={item}
-                      onFresh={refresh}
-                      onDelete={refresh}
-                      onChangeLocation={handleOpenFolderModal}
-                    />
-                  </Col>
-                );
-              })}
-            </Row>
-            <div className="flex justify-end py-16px">
-              <Pagination
-                total={total}
-                current={currentPage}
-                pageSize={12}
-                pageSizeOptions={["12", "24", "36"]}
-                onChange={(page, pageSize) => {
-                  setCurrentPage(page);
-                  run({
-                    currentPage: page,
-                    pageSize: pageSize,
-                    filters: [
-                      {
-                        name: "name",
-                        value: searchName,
-                      },
-                      {
-                        name: "folderId",
-                        value: currentFolder
-                      }
-                    ],
-                  });
+        <Breadcrumb items={breadcrumbData} />
+        <Dropdown
+          menu={{
+            items: contextMenu,
+          }}
+          trigger={["contextMenu"]}
+        >
+          <div>
+            {display === "card" ? (
+              <>
+                {folderData.length ? (
+                  <>
+                    <div className="text-12px color-#999 my-8px">文件夹</div>
+                    <div className="flex gap-12px flex-wrap">
+                      {folderData.map((item, index) => {
+                        return (
+                          <DragItem
+                            key={index}
+                            title={item.name}
+                            onDragStart={() => {
+                              setMoveSource(item.id, "folder");
+                            }}
+                            onDragEnd={() => {
+                              clearMoveSource();
+                            }}
+                          >
+                            <DropItem
+                              onDrop={() => {
+                                handleMove(item.id);
+                              }}
+                            >
+                              <Card
+                                hoverable
+                                bordered
+                                className="w-240px"
+                                onClick={() => handleOpenFolder(item)}
+                                key={index}
+                              >
+                                <Card.Meta
+                                  title={
+                                    <span className="flex justify-between items-center group/item">
+                                      <span>
+                                        <svg
+                                          className="icon"
+                                          aria-hidden="true"
+                                        >
+                                          <use xlinkHref="#icon-weibiaoti-_huabanfuben"></use>
+                                        </svg>
+                                        <span className="ml-8px">
+                                          {item.name}
+                                        </span>
+                                      </span>
+                                      <Dropdown
+                                        menu={{
+                                          items: [
+                                            {
+                                              key: "1",
+                                              label: "重命名",
+                                              onClick: () => {
+                                                let name = item.name;
+                                                confirm({
+                                                  title: "重命名",
+                                                  centered: true,
+                                                  icon: <></>,
+                                                  content: (
+                                                    <Input
+                                                      defaultValue={name}
+                                                      onChange={(e) => {
+                                                        name = e.target.value;
+                                                      }}
+                                                    />
+                                                  ),
+                                                  onOk: async () => {
+                                                    await EditFolder({
+                                                      id: item.id,
+                                                      name,
+                                                    });
+                                                    refresh();
+                                                    message.success(
+                                                      "重命名成功"
+                                                    );
+                                                  },
+                                                });
+                                              },
+                                            },
+                                            {
+                                              key: "2",
+                                              label: "移动或复制",
+                                              onClick: () => {
+                                                handleOpenFolderModal(item.id, 'folder');
+                                              },
+                                            },
+                                            {
+                                              key: "3",
+                                              label: "删除",
+                                              onClick: () => {
+                                                confirm({
+                                                  title: "删除",
+                                                  content: "确定删除该文件夹?",
+                                                  centered: true,
+                                                  onOk: async () => {
+                                                    await DeleteFolder({
+                                                      id: item.id,
+                                                    });
+                                                    message.success("删除成功");
+                                                  },
+                                                });
+                                              },
+                                            },
+                                          ],
+                                          onClick: (e) => {
+                                            e.domEvent.stopPropagation();
+                                          },
+                                        }}
+                                      >
+                                        <MoreOutlined className="hidden group-hover/item:inline-block" onClick={(e) => e.stopPropagation()} />
+                                      </Dropdown>
+                                    </span>
+                                  }
+                                />
+                              </Card>
+                            </DropItem>
+                          </DragItem>
+                        );
+                      })}
+                    </div>
+                  </>
+                ) : null}
+                <div className="text-12px color-#999 my-8px">文件</div>
+                <Row gutter={[8, 16]}>
+                  {dataSource.map((item, index) => {
+                    return (
+                      <Col
+                        xs={12}
+                        sm={8}
+                        md={6}
+                        lg={6}
+                        xl={6}
+                        xxl={6}
+                        key={index}
+                      >
+                        <DragItem
+                          title={item.name}
+                          onDragStart={() => {
+                            setMoveSource(item.id, "chart");
+                          }}
+                          onDragEnd={() => {
+                            clearMoveSource();
+                          }}
+                        >
+                          <ProjectCard
+                            record={item}
+                            onFresh={refresh}
+                            onDelete={refresh}
+                            onChangeLocation={() => handleOpenFolderModal(item.id, 'chart')}
+                          />
+                        </DragItem>
+                      </Col>
+                    );
+                  })}
+                </Row>
+                <div className="flex justify-end py-16px">
+                  <Pagination
+                    total={total}
+                    current={currentPage}
+                    pageSize={12}
+                    pageSizeOptions={["12", "24", "36"]}
+                    onChange={(page, pageSize) => {
+                      getData(page, pageSize);
+                    }}
+                    hideOnSinglePage
+                  />
+                </div>
+                {dataSource.length == 0 ? (
+                  <Empty description="暂无数据" />
+                ) : null}
+              </>
+            ) : (
+              <ProTable
+                loading={loading}
+                columns={[
+                  {
+                    title: "名称",
+                    dataIndex: "name",
+                  },
+                  {
+                    title: "类型",
+                    dataIndex: "type",
+                  },
+                  {
+                    title: "修改时间",
+                    dataIndex: "updatedTime",
+                    sorter: (a, b) => a.updateTime - b.updateTime,
+                  },
+                ]}
+                dataSource={dataSource}
+                rowKey={"id"}
+                search={false}
+                pagination={{
+                  total: total,
+                  pageSize: 12,
+                  current: currentPage,
+                  pageSizeOptions: ["12", "24", "36"],
+                  onChange: (page, pageSize) => {
+                    getData(page, pageSize);
+                  },
                 }}
-                hideOnSinglePage
               />
-            </div>
-            {dataSource.length == 0 ? <Empty description="暂无数据" /> : null}
-          </>
-        ) : (
-          <ProTable
-            loading={loading}
-            columns={[
-              {
-                title: "名称",
-                dataIndex: "name",
-              },
-              {
-                title: "类型",
-                dataIndex: "type",
-              },
-              {
-                title: "修改时间",
-                dataIndex: "updatedTime",
-                sorter: (a, b) => a.updateTime - b.updateTime,
-              },
-            ]}
-            dataSource={dataSource}
-            rowKey={"id"}
-            search={false}
-            pagination={{
-              total: total,
-              pageSize: 12,
-              current: currentPage,
-              pageSizeOptions: ["12", "24", "36"],
-              onChange: (page, pageSize) => {
-                setCurrentPage(page);
-                run({
-                  currentPage: page,
-                  pageSize: pageSize,
-                  filters: [
-                    {
-                      name: "name",
-                      value: searchName,
-                    },
-                    {
-                      name: "folderId",
-                      value: ""
-                    }
-                  ],
-                });
-              },
-            }}
-          />
-        )}
+            )}
+          </div>
+        </Dropdown>
       </PageContainer>
 
       <Modal

+ 57 - 0
apps/designer/src/pages/home/DragItem.tsx

@@ -0,0 +1,57 @@
+import React, { useState, useRef } from "react";
+import { useDrag } from "ahooks";
+export default function DragItem({
+  children,
+  onDragStart,
+  onDragEnd,
+  title
+}: {
+  children: any;
+  onDragStart: () => void;
+  onDragEnd: () => void;
+  title: string;
+}) {
+  const dragRef = useRef(null);
+
+  const [dragging, setDragging] = useState(false);
+
+  // 创建一个canvas元素,将图片绘制到canvas上,然后获取canvas的dataURL作为拖拽时的图片
+  const canvas = document.createElement("canvas");
+  canvas.width = title.length * 10 + 20;
+  canvas.height = 30;
+  const ctx = canvas.getContext("2d");
+  if(ctx) {
+    // 设置背景色为蓝色
+    ctx.fillStyle = "#4096ff";
+    ctx.fillRect(0, 0, canvas.width, 30);
+    ctx.fillStyle = "#fff";
+    ctx.fillText(title, 10, 20);
+  }
+  
+  const dataUrl = canvas.toDataURL("image/png");
+
+  useDrag("", dragRef, {
+    dragImage: {
+      image: dataUrl,
+    },
+    onDragStart: () => {
+      onDragStart();
+      setDragging(true);
+    },
+    onDragEnd: () => {
+      onDragEnd();
+      setDragging(false);
+    },
+  });
+
+  return (
+    <div
+      ref={dragRef}
+      style={{
+        opacity: dragging ? 0.7 : 1,
+      }}
+    >
+      {children}
+    </div>
+  );
+}

+ 26 - 0
apps/designer/src/pages/home/DropItem.tsx

@@ -0,0 +1,26 @@
+import React, { useState, useRef } from 'react'
+import { useDrop } from 'ahooks';
+
+export default function DropItem({ children, onDrop }: { children: any, onDrop: (e: any) => void }) {
+  const dropRef = useRef(null);
+  const [isHovering, setIsHovering] = useState(false);
+
+  useDrop(dropRef, {
+    onDom: (_, e) => {
+      onDrop(e);
+      setIsHovering(false);
+    },
+    onDragEnter: () => setIsHovering(true),
+    onDragLeave: () => setIsHovering(false),
+  });
+
+  return (
+    <div>
+      <div ref={dropRef} style={{border: isHovering ? '1px solid #067bef' : ''}}>
+        {
+          children
+        }
+      </div>
+    </div>
+  );
+}

+ 1 - 0
apps/designer/src/pages/home/ProjectCard.tsx

@@ -63,6 +63,7 @@ export default function ProjectCard({
                   confirm({
                     title: "重命名",
                     centered: true,
+                    icon: <></>,
                     content: (
                       <Input
                         defaultValue={name}

+ 132 - 137
apps/designer/src/pages/home/index.tsx

@@ -1,29 +1,18 @@
 import {
-  MenuOutlined,
-  AppstoreOutlined,
   PlusOutlined,
 } from "@ant-design/icons";
 import {
-  PageContainer,
-  ProCard,
   ProConfigProvider,
-  ProTable,
   ProLayout,
 } from "@ant-design/pro-components";
 import { css } from "@emotion/css";
 import {
   Space,
-  Input,
   Button,
-  Row,
-  Col,
-  Card,
   ConfigProvider,
-  Divider,
   Popover,
 } from "antd";
-import React, { useMemo, useState } from "react";
-import { history } from "umi";
+import React, { useMemo, useRef, useState } from "react";
 import logo from "@/assets/logo.png";
 import { Icon } from "umi";
 import { createNew } from "@/utils";
@@ -33,133 +22,138 @@ import Collect from "./Collect";
 import Template from "./Template";
 import { GraphType } from "@/enum";
 
-const basicGraph = [
-  {
-    id: "1",
-    title: "流程图",
-    subtitle: "图形化表单方式",
-    color: "#dfecff",
-    icon: <Icon icon="local:flow" />,
-    onClick: () => createNew(GraphType.flowchart),
-  },
-  {
-    id: "2",
-    title: "思维导图",
-    subtitle: "结构化表单方式",
-    color: "#dff4ea",
-    icon: <Icon icon="local:mind" />,
-    onClick: () => createNew(GraphType.mindmap),
-  },
-];
-
-const appList = [
-  {
-    id: "1",
-    title: "UML",
-    icon: <Icon icon="local:uml" />,
-    onClick: () => createNew(GraphType.uml),
-  },
-  {
-    id: "2",
-    title: "E-R图",
-    icon: <Icon icon="local:er" />,
-    onClick: () => createNew(GraphType.er),
-  },
-  {
-    id: "3",
-    title: "泳道图",
-    icon: <Icon icon="local:swimlane" />,
-    onClick: () => createNew(GraphType.lane),
-  },
-  {
-    id: "4",
-    title: "BPMN",
-    icon: <Icon icon="local:bpmn" />,
-    onClick: () => createNew(GraphType.bpmn),
-  },
-  {
-    id: "5",
-    title: "韦恩图",
-    icon: <Icon icon="local:we" />,
-    onClick: () => createNew(GraphType.venn),
-  },
-  {
-    id: "6",
-    title: "网络拓扑图",
-    icon: <Icon icon="local:net" />,
-    onClick: () => createNew(GraphType.net),
-  },
-];
-
-const handleMenuClick = (item: any) => {
-  console.log("click", item);
-  item?.onClick?.();
-};
+export default () => {
+  const currentFolder = useRef('root');
 
-const renderBasicItem = (props: {
-  title: string;
-  subtitle: string;
-  color: string;
-  id: string;
-  icon: React.ReactNode;
-}) => {
-  return (
-    <div
-      key={props.id}
-      className={css`
-        width: 136px;
-        height: 60px;
-        padding: 8px;
-        border-radius: 8px;
-        display: flex;
-        align-items: center;
-        cursor: pointer;
-        margin-top: 8px;
-        background: ${props.color + "70"};
-        &:hover {
-          background: ${props.color};
-        }
-      `}
-      onClick={() => handleMenuClick(props)}
-    >
-      <span className="w-32px h-33px">{props.icon}</span>
-      <div className="flex flex-col justify-center ml-4px">
-        <div className="text-14px">{props.title}</div>
-        <div className="text-12px text-#9aa5b8">{props.subtitle}</div>
+  const basicGraph = [
+    {
+      id: "1",
+      title: "流程图",
+      subtitle: "图形化表单方式",
+      color: "#dfecff",
+      icon: <Icon icon="local:flow" />,
+      onClick: () => createNew(GraphType.flowchart, currentFolder.current),
+    },
+    {
+      id: "2",
+      title: "思维导图",
+      subtitle: "结构化表单方式",
+      color: "#dff4ea",
+      icon: <Icon icon="local:mind" />,
+      onClick: () => createNew(GraphType.mindmap, currentFolder.current),
+    },
+  ];
+  
+  const appList = [
+    {
+      id: "1",
+      title: "UML",
+      icon: <Icon icon="local:uml" />,
+      onClick: () => createNew(GraphType.uml, currentFolder.current),
+    },
+    {
+      id: "2",
+      title: "E-R图",
+      icon: <Icon icon="local:er" />,
+      onClick: () => createNew(GraphType.er, currentFolder.current),
+    },
+    {
+      id: "3",
+      title: "泳道图",
+      icon: <Icon icon="local:swimlane" />,
+      onClick: () => createNew(GraphType.lane, currentFolder.current),
+    },
+    {
+      id: "4",
+      title: "BPMN",
+      icon: <Icon icon="local:bpmn" />,
+      onClick: () => createNew(GraphType.bpmn, currentFolder.current),
+    },
+    {
+      id: "5",
+      title: "韦恩图",
+      icon: <Icon icon="local:we" />,
+      onClick: () => createNew(GraphType.venn, currentFolder.current),
+    },
+    {
+      id: "6",
+      title: "网络拓扑图",
+      icon: <Icon icon="local:net" />,
+      onClick: () => createNew(GraphType.net, currentFolder.current),
+    },
+  ];
+  
+  const handleMenuClick = (item: any) => {
+    console.log("click", item);
+    item?.onClick?.();
+  };
+  
+  const renderBasicItem = (props: {
+    title: string;
+    subtitle: string;
+    color: string;
+    id: string;
+    icon: React.ReactNode;
+  }) => {
+    return (
+      <div
+        key={props.id}
+        className={css`
+          width: 136px;
+          height: 60px;
+          padding: 8px;
+          border-radius: 8px;
+          display: flex;
+          align-items: center;
+          cursor: pointer;
+          margin-top: 8px;
+          background: ${props.color + "70"};
+          &:hover {
+            background: ${props.color};
+          }
+        `}
+        onClick={() => handleMenuClick(props)}
+      >
+        <span className="w-32px h-33px">{props.icon}</span>
+        <div className="flex flex-col justify-center ml-4px">
+          <div className="text-14px">{props.title}</div>
+          <div className="text-12px text-#9aa5b8">{props.subtitle}</div>
+        </div>
       </div>
-    </div>
-  );
-};
-
-const renderProItem = (props: {
-  id: string;
-  title: string;
-  icon: React.ReactNode;
-}) => {
-  return (
-    <div
-      key={props.id}
-      className="w-66px h-70px flex flex-col items-center justify-center mt-8px rounded-lg hover:bg-#f3f5f9 cursor-pointer"
-      onClick={() => handleMenuClick(props)}
-    >
-      {props.icon}
-      <span className="text-12px">{props.title}</span>
-    </div>
-  );
-};
-
-const RenderAppList = () => {
-  return (
-    <div className="w-460px">
-      <div className="color-#6c7d8f text-xs">基础图形</div>
-      <Space>{basicGraph.map((item) => renderBasicItem(item))}</Space>
-      <div className="color-#6c7d8f text-xs mt-8px">专业图形</div>
-      <Space>{appList.map((item) => renderProItem(item))}</Space>
-    </div>
-  );
-};
+    );
+  };
+  
+  const renderProItem = (props: {
+    id: string;
+    title: string;
+    icon: React.ReactNode;
+  }) => {
+    return (
+      <div
+        key={props.id}
+        className="w-66px h-70px flex flex-col items-center justify-center mt-8px rounded-lg hover:bg-#f3f5f9 cursor-pointer"
+        onClick={() => handleMenuClick(props)}
+      >
+        {props.icon}
+        <span className="text-12px">{props.title}</span>
+      </div>
+    );
+  };
+  
+  const RenderAppList = () => {
+    return (
+      <div className="w-460px">
+        <div className="color-#6c7d8f text-xs">基础图形</div>
+        <Space>{basicGraph.map((item) => renderBasicItem(item))}</Space>
+        <div className="color-#6c7d8f text-xs mt-8px">专业图形</div>
+        <Space>{appList.map((item) => renderProItem(item))}</Space>
+      </div>
+    );
+  };
 
-export default () => {
+  const handleChangeFolder = (id: string) => {
+    currentFolder.current = id;
+  }
 
   const [pathname, setPathname] = useState("/all");
   const routes = [
@@ -167,7 +161,7 @@ export default () => {
       path: "/all",
       name: "全部",
       icon: <i className="iconfont icon-xitong" />,
-      component: <All />,
+      component: <All onChangeCurrentFolder={ handleChangeFolder }/>,
     },
     {
       path: "/recently",
@@ -190,6 +184,7 @@ export default () => {
   ];
 
   const content = useMemo(() => {
+    currentFolder.current = "root";
     return routes.find((item) => item.path === pathname)?.component;
   }, [pathname]);
 

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

@@ -52,7 +52,7 @@ export default function index() {
       setMindProjectInfo({
         ...mindProjectInfo,
         name: title,
-      });
+      }, false, true);
   };
   // 预览 todo
   const handlePreview = () => {};

+ 39 - 14
apps/designer/src/pages/mindmap/edge.ts

@@ -95,10 +95,15 @@ Graph.registerConnector(
     const midY = sourcePoint.y;
     // 可能存在误差
     const deviationY = Math.abs(targetPoint.y - sourcePoint.y);
-    const pathData = `
+    const pathData =
+      deviationY < 10
+        ? `M ${sourcePoint.x} ${sourcePoint.y}
+        L ${targetPoint.x} ${targetPoint.y}
+      `
+        : `
      M ${sourcePoint.x} ${sourcePoint.y}
      L ${midX} ${midY}
-     L ${midX} ${targetPoint.y + (deviationY < 10 ? 0 : targetPoint.y < sourcePoint.y ? 5 : -5)}
+     L ${midX} ${targetPoint.y + (targetPoint.y < sourcePoint.y ? 5 : -5)}
      Q ${midX} ${targetPoint.y} ${midX + (targetPoint.x < sourcePoint.x ? -5 : 5)} ${targetPoint.y}
      L ${targetPoint.x} ${targetPoint.y}
     `;
@@ -310,7 +315,12 @@ Graph.registerConnector(
     if (node?.isNode() && direction === "down") {
       targetY -= node.size().height;
     }
-    const midX = caclculateX({ x: targetPoint.x, y: targetY }, sourcePoint.y, "right", direction);
+    const midX = caclculateX(
+      { x: targetPoint.x, y: targetY },
+      sourcePoint.y,
+      "right",
+      direction
+    );
     let pathData = "";
     if (direction === "up") {
       pathData = `
@@ -379,7 +389,12 @@ Graph.registerConnector(
     if (node?.isNode() && direction === "down") {
       targetY -= node.size().height;
     }
-    const midX = caclculateX({ x: targetPoint.x, y: targetY }, sourcePoint.y, "left", direction);
+    const midX = caclculateX(
+      { x: targetPoint.x, y: targetY },
+      sourcePoint.y,
+      "left",
+      direction
+    );
     let pathData = "";
     if (direction === "up") {
       pathData = `
@@ -421,17 +436,24 @@ Graph.registerConnector(
     } else {
       // 分支子主题到子主题
       const direction = sourcePoint.y < targetPoint.y ? "up" : "down";
-      const size = sourceNode?.isNode() ? sourceNode.size() : { width: 0, height: 0};
-      let midX = caclculateX({
-        x: sourcePoint.x - size.width / 2,
-        y: sourcePoint.y + size.height / 2,
-      }, targetPoint.y, "left", direction);
+      const size = sourceNode?.isNode()
+        ? sourceNode.size()
+        : { width: 0, height: 0 };
+      let midX = caclculateX(
+        {
+          x: sourcePoint.x - size.width / 2,
+          y: sourcePoint.y + size.height / 2,
+        },
+        targetPoint.y,
+        "left",
+        direction
+      );
       if (direction === "down") {
         midX = caclculateX(
-          { 
+          {
             x: sourcePoint.x - size.width / 2,
             y: sourcePoint.y - size.height / 2,
-           },
+          },
           targetPoint.y,
           "left",
           direction
@@ -529,7 +551,11 @@ const getSourceAnchor = (
           };
     }
     case StructureType.leftRight: {
-      return type === TopicType.branch ? 'center' : options?.direction === 'left' ? 'left' : 'right'
+      return type === TopicType.branch
+        ? "center"
+        : options?.direction === "left"
+          ? "left"
+          : "right";
     }
     case StructureType.tree: {
       return type === TopicType.branch
@@ -611,7 +637,7 @@ const getTargetAnchor = (
       };
     }
     case StructureType.leftRight: {
-      return options?.direction === 'left' ? 'right' : 'left'
+      return options?.direction === "left" ? "right" : "left";
     }
     case StructureType.tree: {
       return type === TopicType.branch
@@ -690,7 +716,6 @@ export const createEdge = (
   theme: string,
   options: Record<string, any> = {}
 ): Edge => {
-
   return graph.createEdge({
     id: item.id + "-edge",
     inherit: "edge",

+ 1 - 2
apps/designer/src/pages/mindmap/mindMap.tsx

@@ -287,7 +287,6 @@ export const addTopic = (
   // @ts-ignore
   const projectInfo: MindMapProjectInfo = graph.extendAttr.getMindProjectInfo();
   if (!projectInfo || !setMindProjectInfo) return;
-
   const topic = buildTopic(
     type,
     {
@@ -429,7 +428,7 @@ export const updateTopic = (
   graph: Graph
 ) => {
   // @ts-ignore
-  const projectInfo = graph.extendAttr.getMindProjectInfo;
+  const projectInfo = graph.extendAttr.getMindProjectInfo();
   if (!projectInfo || !setMindProjectInfo) return;
 
   const traverse = (topics: TopicItem[]) => {

+ 2 - 2
apps/designer/src/utils/index.ts

@@ -42,8 +42,8 @@ export function uuid() {
 /**
  * 创建新的图形
  */
-export const createNew = async (type: string) => {
-  const res = await AddGraph({ type });
+export const createNew = async (type: string, folderId: string = 'root') => {
+  const res = await AddGraph({ type, folderId });
   const id = res?.result?.graph?.id;
   if(!id) return false;
   const { origin, pathname } = window.location;

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

@@ -143,7 +143,7 @@ export const deleteTopics = (
 
   mindProjectInfo.topics = filterTopics(topics);
 
-  setMindProjectInfo(mindProjectInfo); // TODO 这个方法删除更新有问题
+  setMindProjectInfo(mindProjectInfo);
   localStorage.setItem("minMapProjectInfo", JSON.stringify(mindProjectInfo));
 };