소스 검색

perf: 优化保存,编辑

liaojiaxing 4 달 전
부모
커밋
ed44bd04b0

+ 0 - 3
apps/designer/src/pages/flow/components/MenuBar/index.tsx

@@ -128,9 +128,6 @@ export default function MenuBar() {
 
   const [selectionStrict, setSelectionStrict] = useState(true);
 
-  // 预览 todo
-  const handlePreview = () => {};
-
   // 保存
   const handleSave = () => {
     const elements = graph?.toJSON()?.cells;

+ 2 - 1
apps/er-designer/.gitignore

@@ -6,5 +6,6 @@
 /src/.umi-production
 /src/.umi-test
 /dist
-/systemDesign
+/dataModel
+/dataModel.zip
 .swc

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

@@ -3,7 +3,7 @@ import { defineConfig } from "umi";
 export default defineConfig({
   base: '/',
   publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
-  outputPath: 'systemDesign',
+  outputPath: 'dataModel',
   favicons: [
     '/favicon.ico'
   ],

+ 15 - 0
apps/er-designer/src/components/SyncModal.tsx

@@ -0,0 +1,15 @@
+import { forwardRef, useImperativeHandle, useState } from "react";
+import { Modal } from "antd";
+export default forwardRef(function SyncModal(props, ref) {
+  const [open, setOpen] = useState(true);
+  useImperativeHandle(ref, () => ({
+    open: () => {
+      setOpen(true);
+    },
+    close: () => setOpen(false),
+  }));
+
+  return <Modal title="数据同步" width={"80%"} open={open} okText="提交" onCancel={() => setOpen(false)}>
+
+  </Modal>;
+});

+ 71 - 16
apps/er-designer/src/models/erModel.tsx

@@ -6,9 +6,9 @@ import { Snapline } from "@antv/x6-plugin-snapline";
 import { Keyboard } from "@antv/x6-plugin-keyboard";
 import { Export } from "@antv/x6-plugin-export";
 import { Selection } from "@antv/x6-plugin-selection";
-import { SaveDataModel } from "@/api";
+import { SaveDataModel, UploadFile } from "@/api";
 import { useFullscreen, useSessionStorageState } from "ahooks";
-import { createTable, uuid } from "@/utils";
+import { base64ToFile, createTable, uuid } from "@/utils";
 import dayjs from "dayjs";
 
 import type {
@@ -314,6 +314,32 @@ export default function erModel() {
       }
     );
 
+    instance.on("node:resized", (args) => {
+      console.log("node:resized", args);
+      const size = args.node.getSize();
+      const data = args.node.data;
+      if (data.isTopicArea) {
+        updateTopicArea({
+          ...data,
+          style: {
+            ...data.style,
+            width: size.width,
+            height: size.height,
+          },
+        });
+      }
+      if (data.isRemark) {
+        updateRemark({
+          ...data,
+          style: {
+            ...data.style,
+            width: size.width,
+            height: size.height,
+          },
+        });
+      }
+    })
+
     instance.bindKey("ctrl+z", onUndo);
     instance.bindKey("ctrl+y", onRedo);
     instance.bindKey("ctrl+c", onCopy);
@@ -332,7 +358,7 @@ export default function erModel() {
       // todo 新建
     });
     instance.bindKey("ctrl+s", () => {
-      // todo 保存
+      onSave();
     });
   };
 
@@ -448,6 +474,8 @@ export default function erModel() {
         background: "#175e7a",
         x: 300,
         y: 300,
+        width: 200,
+        height: 200,
       },
     };
     setProject({
@@ -500,7 +528,7 @@ export default function erModel() {
         x: 300,
         y: 300,
         width: 200,
-        height: 100,
+        height: 200,
         background: "#fcf7ac",
       },
     };
@@ -759,10 +787,10 @@ export default function erModel() {
             };
           }),
         };
-        setProject({
+        setProject((project) => ({
           ...project,
           tables: [...project.tables, newTable],
-        });
+        }));
       }
       // 主题区域
       if (data?.isTopicArea) {
@@ -776,10 +804,10 @@ export default function erModel() {
             y: data.style.y + 20,
           },
         };
-        setProject({
+        setProject((project) => ({
           ...project,
           topicAreas: [...project.topicAreas, newTopicArea],
-        });
+        }));
       }
       // 注释节点
       if (data?.isRemark) {
@@ -793,10 +821,10 @@ export default function erModel() {
             y: data.style.y + 20,
           },
         };
-        setProject({
+        setProject((project) => ({
           ...project,
           remarkInfos: [...project.remarkInfos, newRemark],
-        });
+        }));
       }
     }
   };
@@ -809,26 +837,26 @@ export default function erModel() {
     if (cell?.[0]?.isNode()) {
       const data = cell[0].data;
       if (data?.isTable) {
-        setProject({
+        setProject((project) => ({
           ...project,
           tables: project.tables.filter((item) => item.table.id !== cell[0].id),
-        });
+        }));
       }
       if (data?.isTopicArea) {
-        setProject({
+        setProject((project) => ({
           ...project,
           topicAreas: project.topicAreas.filter(
             (item) => item.id !== cell[0].id
           ),
-        });
+        }));
       }
       if (data?.isRemark) {
-        setProject({
+        setProject((project) => ({
           ...project,
           remarkInfos: project.remarkInfos.filter(
             (item) => item.id !== cell[0].id
           ),
-        });
+        }));
       }
     }
   };
@@ -852,6 +880,32 @@ export default function erModel() {
     setPlayModeEnable(false);
   };
 
+  /**
+   * 保存项目
+   */
+  const onSave = async () => {
+    const msg = message.loading('保存中...', 0);
+    console.log(msg, project);
+    // graph?.toPNG(async (dataUri) => {
+    //   const file = base64ToFile(dataUri, project?.id || '封面图', "image/png");
+
+    //   const formData = new FormData();
+    //   formData.append("file", file);
+    //   const res = await UploadFile(formData);
+
+    //   await SaveDataModel({...project, coverImage: res?.result?.[0]?.id}).finally(() => {
+    //     message.destroy();
+    //   });
+    //   setSaveTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
+    //   message.success("保存成功");
+    // }, {
+    //   width: 300,
+    //   height: 150,
+    //   quality: 0.2,
+    //   copyStyles: true
+    // });
+  }
+
   return {
     initGraph,
     graph,
@@ -884,5 +938,6 @@ export default function erModel() {
     setPlayModeEnable,
     exitPlayMode,
     saveTime,
+    onSave,
   };
 }

+ 10 - 7
apps/er-designer/src/models/initInfo.ts

@@ -6,25 +6,28 @@ export const initInfo = (info: ProjectInfo) => {
   info.setting = info?.setting
     ? JSON.parse(info.setting as unknown as string)
     : { ...DEFAULT_SETTING };
-  
+
   info.tables.forEach((tableItem) => {
     // 名称多语言
     tableItem.table.langNameList = [
-      { name: 'en', value: tableItem.table?.enName || ''},
-      { name: 'zh-CN', value: tableItem.table?.cnName || ''},
+      { name: "en", value: tableItem.table?.enName || "" },
+      { name: "zh-CN", value: tableItem.table?.cnName || "" },
     ];
+    tableItem.table.langName = "";
     // 描述多语言
     tableItem.table.langDescriptionList = [
-      { name: 'en', value: tableItem.table?.enDescription || ''},
-      { name: 'zh-CN', value: tableItem.table?.cnDescription || ''},
+      { name: "en", value: tableItem.table?.enDescription || "" },
+      { name: "zh-CN", value: tableItem.table?.cnDescription || "" },
     ];
+    tableItem.table.langDescription = "";
     // 字段处理
     tableItem.tableColumnList.forEach((columnItem) => {
       // 名称多语言
       columnItem.langNameList = [
-        { name: 'en', value: columnItem?.enName || ''},
-        { name: 'zh-CN', value: columnItem?.cnName || ''},
+        { name: "en", value: columnItem?.enName || "" },
+        { name: "zh-CN", value: columnItem?.cnName || "" },
       ];
+      columnItem.langName = "";
     });
     tableItem.table.style = JSON.parse(
       tableItem.table.style as unknown as string

+ 6 - 4
apps/er-designer/src/models/renderer.ts

@@ -53,8 +53,8 @@ export const render = (graph: Graph, project: ProjectInfo) => {
       shape: "topic-node",
       x: topicArea.style?.x || 100,
       y: topicArea.style?.y || 100,
-      width: 200,
-      height: 200,
+      width: topicArea.style?.width || 200,
+      height: topicArea.style?.height || 200,
       id: topicArea.id,
       data: topicArea,
       zIndex: 0,
@@ -66,8 +66,8 @@ export const render = (graph: Graph, project: ProjectInfo) => {
       shape: "notice-node",
       x: remark.style?.x || 100,
       y: remark.style?.y || 100,
-      width: 200,
-      height: 200,
+      width: remark.style?.width || 200,
+      height: remark.style?.height || 200,
       id: remark.id,
       data: remark,
       zIndex: 1,
@@ -203,6 +203,7 @@ export const render = (graph: Graph, project: ProjectInfo) => {
       if (cell?.isNode()) {
         cell.setData(topicArea);
         cell.setPosition(topicArea.style?.x, topicArea.style?.y);
+        cell.setSize(topicArea.style?.width, topicArea.style?.height);
       }
     }
   });
@@ -214,6 +215,7 @@ export const render = (graph: Graph, project: ProjectInfo) => {
       if (cell?.isNode()) {
         cell.setData(remark);
         cell.setPosition(remark.style?.x, remark.style?.y);
+        cell.setSize(remark.style?.width, remark.style?.height);
       }
     }
   });

+ 6 - 2
apps/er-designer/src/pages/detail/index.tsx

@@ -25,6 +25,7 @@ import insertCss from "insert-css";
 import LangInput from "@/components/LangInput";
 import LangInputTextarea from "@/components/LangInputTextarea";
 import { validateAliasName, validateTableCode } from "@/utils/validator";
+import SyncModal from "@/components/SyncModal";
 
 const { Content, Header } = Layout;
 export default function index() {
@@ -109,8 +110,8 @@ export default function index() {
       },
       {
         key: "6",
-        label: "数据库同步",
-        children: "5/6",
+        label: "数",
+        children: `${project.tables.length}`,
       },
       {
         key: "7",
@@ -222,7 +223,10 @@ export default function index() {
 
   return (
     <Spin spinning={loading}>
+      {/* 基础信息修改弹窗 */}
       <AddModel ref={addModelRef} onChange={(info) => { typeof info === 'object' && setProject(info)}}/>
+      {/* 同步弹窗 */}
+      <SyncModal/>
       <Layout className="h-100vh flex flex-col bg-#fafafa p-12px">
         <Header
           className="shadow-sm"

+ 2 - 0
apps/er-designer/src/pages/er/components/Menu.tsx

@@ -19,6 +19,7 @@ export default function Menu() {
     onDelete,
     enterPlayMode,
     saveTime,
+    onSave,
   } = useModel("erModel");
   const [modal, contextHolder] = Modal.useModal();
   const [isFullscreen, { toggleFullscreen }] = useFullscreen(document.body);
@@ -89,6 +90,7 @@ export default function Menu() {
               <span className="color-#666">ctrl+s</span>
             </span>
           ),
+          onClick: onSave,
         },
         { key: "1-4", label: "保存为模版" },
         { key: "1-5", label: "发布模版" },

+ 54 - 30
apps/er-designer/src/pages/er/components/TableItem.tsx

@@ -4,7 +4,6 @@ import {
   Row,
   Form,
   Input,
-  InputNumber,
   Select,
   Button,
   Tooltip,
@@ -28,6 +27,7 @@ import { restrictToParentElement } from "@dnd-kit/modifiers";
 import ColumnItem from "./ColumnItem";
 import LangInput from "@/components/LangInput";
 import LangInputTextarea from "@/components/LangInputTextarea";
+import { validateAliasName, validateTableCode } from "@/utils/validator";
 
 export default function TableItem({
   data,
@@ -146,7 +146,8 @@ export default function TableItem({
         onClick={() => setActive(active === table.id ? "" : table.id)}
       >
         <div className="font-bold truncate flex-1">
-          {table.schemaName}({table?.langNameList?.find(item => item.name === 'zh-CN')?.value})
+          {table.schemaName}(
+          {table?.langNameList?.find((item) => item.name === "zh-CN")?.value})
         </div>
         <div>
           <Popover
@@ -154,8 +155,8 @@ export default function TableItem({
             placement="right"
             content={
               <div className="w-200px" onClick={(e) => e.stopPropagation()}>
-                <Form layout="vertical">
-                  <Form.Item label="表名称" name="pkName">
+                <Form layout="vertical" initialValues={table}>
+                  <Form.Item label="表名称" name="langNameList">
                     <LangInput
                       value={table.langNameList}
                       onChange={(lang) => {
@@ -163,7 +164,7 @@ export default function TableItem({
                       }}
                     />
                   </Form.Item>
-                  <Form.Item label="描述" name="pkName">
+                  <Form.Item label="描述" name="langDescriptionList">
                     <LangInputTextarea
                       value={table.langDescriptionList}
                       onChange={(lang) => {
@@ -199,31 +200,11 @@ export default function TableItem({
         }}
       >
         <div className="overflow-hidden">
-          <Form layout="horizontal" labelCol={{ span: 8 }}>
-            <Row gutter={8}>
-              <Col span={12}>
-                <Form.Item label="编码">
-                  <Input
-                    placeholder="请输入"
-                    value={table.schemaName}
-                    onChange={(e) =>
-                      handleTableChange("schemaName", e.target.value)
-                    }
-                  />
-                </Form.Item>
-              </Col>
-              <Col span={12}>
-                <Form.Item label="别名">
-                  <Input
-                    placeholder="请输入"
-                    value={table.aliasName}
-                    onChange={(e) =>
-                      handleTableChange("aliasName", e.target.value)
-                    }
-                  />
-                </Form.Item>
-              </Col>
-            </Row>
+          <Form
+            layout="horizontal"
+            labelCol={{ span: 8 }}
+            initialValues={table}
+          >
             <Form.Item
               label="类型"
               labelCol={{ span: 4 }}
@@ -233,9 +214,52 @@ export default function TableItem({
                 placeholder="请选择"
                 options={TABLE_TYPE_OPTIONS}
                 value={table.type}
+                disabled
                 onChange={(val) => handleTableChange("type", val)}
               />
             </Form.Item>
+            <Row gutter={8}>
+              <Col span={12}>
+                <Form.Item
+                  label="编码"
+                  name="schemaName"
+                  rules={[
+                    { required: true, message: "请输入编码" },
+                    validateTableCode,
+                  ]}
+                >
+                  <Tooltip title={table.schemaName}>
+                    <Input
+                      placeholder="请输入"
+                      value={table.schemaName}
+                      onChange={(e) =>
+                        handleTableChange("schemaName", e.target.value)
+                      }
+                    />
+                  </Tooltip>
+                </Form.Item>
+              </Col>
+              <Col span={12}>
+                <Form.Item
+                  label="别名"
+                  name="aliasName"
+                  rules={[
+                    { required: true, message: "请输入编码" },
+                    validateAliasName,
+                  ]}
+                >
+                  <Tooltip title={table.aliasName}>
+                    <Input
+                      placeholder="请输入"
+                      value={table.aliasName}
+                      onChange={(e) =>
+                        handleTableChange("aliasName", e.target.value)
+                      }
+                    />
+                  </Tooltip>
+                </Form.Item>
+              </Col>
+            </Row>
           </Form>
 
           <div className="flex justify-between m-b-10px">

+ 4 - 2
apps/er-designer/src/pages/er/components/TodoDrawer.tsx

@@ -10,6 +10,7 @@ import {
   Input,
   Popover,
   Radio,
+  Tooltip,
 } from "antd";
 import { useModel } from "umi";
 import { TodoItem } from "@/type";
@@ -85,6 +86,7 @@ const TodoDrawer = forwardRef((props: {}, ref) => {
           ...project.todos,
           {
             id: uuid(),
+            dataModelId: project.id,
             name: "",
             text: "",
             isDone: false,
@@ -138,12 +140,12 @@ const TodoDrawer = forwardRef((props: {}, ref) => {
           onClick={() => setActiveKey(todo.id)}
         >
           <div className="left m-r-12px">
-            <Checkbox
+            <Tooltip title="完成"><Checkbox
               checked={todo.isDone}
               onChange={(e) => {
                 handleChange(index, "isDone", e.target.checked);
               }}
-            />
+            /></Tooltip>
           </div>
           <div className="right flex-1">
             <div className="flex m-b-12px">

+ 3 - 2
apps/er-designer/src/pages/er/components/Toolbar.tsx

@@ -15,6 +15,7 @@ export default function Toolbar() {
     onUndo,
     project,
     setProject,
+    onSave,
   } = useModel("erModel");
   const todoRef = React.useRef<{ open: () => void }>();
   const scaleMenu = {
@@ -173,14 +174,14 @@ export default function Toolbar() {
         <div className="group">
           <div className="flex items-center">
             <Tooltip title="保存模型">
-              <div className="btn flex flex-col items-center cursor-pointer py-4px px-10px hover:bg-gray-200">
+              <div className="btn flex flex-col items-center cursor-pointer py-4px px-10px hover:bg-gray-200" onClick={onSave}>
                 <svg className="icon h-24px w-24px" aria-hidden="true">
                   <use xlinkHref="#icon-baocun"></use>
                 </svg>
               </div>
             </Tooltip>
             <Tooltip title="待办项">
-              <div className="btn flex flex-col items-center cursor-pointer py-4px px-10px hover:bg-gray-200 opacity-50" onClick={() => todoRef.current?.open()}>
+              <div className="btn flex flex-col items-center cursor-pointer py-4px px-10px hover:bg-gray-200" onClick={() => todoRef.current?.open()}>
                 <svg className="icon h-24px w-24px" aria-hidden="true">
                   <use xlinkHref="#icon-daiban"></use>
                 </svg>

+ 33 - 37
apps/er-designer/src/pages/home/All.tsx

@@ -149,41 +149,39 @@ export default function All({
     );
   };
 
-  const root = {
-    id: "root",
-    name: "",
-    key: "root",
-    title: (
-      <span>
-        {type === TableType.BusinessTable ? "业务模型" : "流程模型"}
-        {currentFolder === "" ? (
-          <span className="text-12px color-#999">(当前)</span>
-        ) : null}
-      </span>
-    ),
-    icon: <FolderIcon />,
-    parentId: "",
-    children: [],
-  };
-
   // 构建文件夹树
   const folderTreeData = useMemo((): TreeDataNode[] => {
     return [
-      folderData.map((item) => {
-        return {
-          ...item,
-          key: item.id,
-          title: (
-            <span>
-              {item.name}
-              {currentFolder === item.name ? (
-                <span className="text-12px color-#999">(当前)</span>
-              ) : null}
-            </span>
-          ),
-          icon: <FolderIcon />,
-        };
-      }, root),
+      {
+        id: "root",
+        name: "",
+        key: "",
+        title: (
+          <span>
+            {type === TableType.BusinessTable ? "业务模型" : "流程模型"}
+            {currentFolder === "" ? (
+              <span className="text-12px color-#999">(当前)</span>
+            ) : null}
+          </span>
+        ),
+        icon: <FolderIcon />,
+        parentId: "",
+        children: folderData.map((item) => {
+          return {
+            ...item,
+            key: item.name,
+            title: (
+              <span>
+                {item.name}
+                {currentFolder === item.name ? (
+                  <span className="text-12px color-#999">(当前)</span>
+                ) : null}
+              </span>
+            ),
+            icon: <FolderIcon />,
+          };
+        }),
+      },
     ] as unknown as TreeDataNode[];
   }, [folderData, currentFolder]);
 
@@ -306,6 +304,7 @@ export default function All({
             return item.id === newData.id ? newData : item;
           })
         );
+        setOpen(false);
         message.success("移动成功");
       });
     }
@@ -530,7 +529,7 @@ export default function All({
       </PageContainer>
 
       <Modal
-        title="移动/复制到"
+        title="移动"
         width={440}
         open={open}
         onCancel={() => {
@@ -551,15 +550,12 @@ export default function All({
             >
               移动
             </Button>
-            <Button type="primary" onClick={() => handleCopy(selectFolder)}>
-              复制
-            </Button>
           </div>
         }
       >
         <div className="min-h-300px">
           <Tree
-            // treeData={folderTreeData}
+            treeData={folderTreeData}
             selectedKeys={[selectFolder]}
             onSelect={(keys) => setSelectFolder(keys[0] as string)}
             showIcon

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

@@ -92,7 +92,7 @@ export default function ProjectCard({
               !hideRemove
                 ? {
                     key: "2",
-                    label: "移动/复制",
+                    label: "移动",
                     onClick: () => {
                       onChangeLocation?.(record.id);
                     },

+ 49 - 5
apps/er-designer/src/utils/index.ts

@@ -12,6 +12,50 @@ export function uuid() {
   });
 }
 
+/**
+ * base64 转 file
+ * @param base64String
+ * @param fileName
+ * @param fileType
+ * @returns
+ */
+export function base64ToFile(
+  base64String: string,
+  fileName: string,
+  fileType: string
+): File {
+  // 移除Base64字符串中的前缀(如"data:image/png;base64,")
+  const base64Data = base64String.split(",")[1];
+
+  // 解码Base64字符串
+  const byteCharacters = atob(base64Data);
+
+  // 创建一个Uint8Array来存储二进制数据
+  const byteArrays = new Uint8Array(byteCharacters.length);
+
+  for (let i = 0; i < byteCharacters.length; i++) {
+    byteArrays[i] = byteCharacters.charCodeAt(i);
+  }
+
+  // 创建Blob对象
+  const blob = new Blob([byteArrays], { type: fileType });
+
+  // 创建File对象
+  const file = new File([blob], fileName, { type: fileType });
+
+  return file;
+}
+
+/**
+ * 获取token
+ */
+export const getToken = () => {
+  const enterpriseCode = sessionStorage.getItem("enterpriseCode");
+  const token = localStorage.getItem("token_" + enterpriseCode);
+
+  return token;
+};
+
 export const createTable = (tableType: TableType, dataModelId: string, parentId?: string): TableItemType => {
   const tableId = uuid();
   const tableColumnList: ColumnItem[] = [];
@@ -26,7 +70,7 @@ export const createTable = (tableType: TableType, dataModelId: string, parentId?
   return {
     isTable: true,
     table: {
-      aliasName: "newtable",
+      aliasName: `newTableAliasName${new Date().getTime()}`,
       creationTime: "",
       creatorUserId: "",
       displayOrder: 0,
@@ -35,13 +79,13 @@ export const createTable = (tableType: TableType, dataModelId: string, parentId?
       langName: "",
       dataModelId,
       parentBusinessTableId: parentId || "",
-      schemaName: "new_table",
-      type: 1,
+      schemaName: `new_table_${new Date().getTime()}`,
+      type: tableType,
       updateTime: "",
       openSync: false,
       langNameList: [
-        { name: "zh-CN", value: "" },
-        { name: "en", value: "" },
+        { name: "zh-CN", value: "新建表" },
+        { name: "en", value: "new table" },
       ],
       style: {
         // 随机颜色