Browse Source

fix: 修改表删除bug优化表节点

liaojiaxing 5 months ago
parent
commit
68ae90cbb2

+ 1 - 1
apps/er-designer/src/components/AddModel.tsx

@@ -62,7 +62,7 @@ export default React.forwardRef(function AddModel(
     <Modal
       open={open}
       width={440}
-      title="创建模型"
+      title={editInfo.current ? "编辑模型" : "创建模型"}
       onCancel={() => setOpen(false)}
       onOk={handleSubmit}
     >

+ 38 - 4
apps/er-designer/src/components/SyncModal.tsx

@@ -1,7 +1,12 @@
 import { forwardRef, useImperativeHandle, useState } from "react";
-import { Modal } from "antd";
+import { Modal, Table, Collapse, Checkbox } from "antd";
+import type { TableProps } from "antd";
+import TrasnferTable from "./TransferTable";
+import { useModel } from "umi";
+
 export default forwardRef(function SyncModal(props, ref) {
-  const [open, setOpen] = useState(true);
+  const [open, setOpen] = useState(false);
+  const { project } = useModel("erModel");
   useImperativeHandle(ref, () => ({
     open: () => {
       setOpen(true);
@@ -9,7 +14,36 @@ export default forwardRef(function SyncModal(props, ref) {
     close: () => setOpen(false),
   }));
 
-  return <Modal title="数据同步" width={"80%"} open={open} okText="提交" onCancel={() => setOpen(false)}>
+  const [useModelData, setUseModelData] = useState(false);
 
-  </Modal>;
+  return (
+    <Modal
+      title="数据同步"
+      width={"100%"}
+      open={open}
+      okText="同步"
+      onCancel={() => setOpen(false)}
+    >
+      <Checkbox
+        checked={useModelData}
+        onChange={(e) => setUseModelData(e.target.checked)}
+      >
+        全部使用模型数据
+      </Checkbox>
+      <Collapse
+        defaultActiveKey={project.tables.map((item) => item.table.id)}
+        items={project.tables.map((item) => {
+          const { table, tableColumnList } = item;
+          const name = table.langNameList?.find(
+            (item) => item.name === "zh-CN"
+          )?.value;
+          return {
+            key: table.id,
+            label: `${table.schemaName}${name ? `(${name})` : ""}`,
+            children: <TrasnferTable dataSource={tableColumnList} useModelData={useModelData} />,
+          };
+        })}
+      />
+    </Modal>
+  );
 });

+ 8 - 2
apps/er-designer/src/components/TableEdit.tsx

@@ -4,7 +4,7 @@ import type { ColumnItem } from "@/type";
 import { createColumn } from "@/utils";
 import { DataType } from "@/enum";
 import { DATA_TYPE_OPTIONS } from "@/constants";
-import { Button, Input, InputNumber } from "antd";
+import { Button, Input, InputNumber, Switch } from "antd";
 import LangInput from "./LangInput";
 import { validateColumnCode } from "@/utils/validator";
 import VariableModal from "./VariableModal";
@@ -199,12 +199,18 @@ export default function TableEdit(props: {
       title: "必填",
       dataIndex: "isRequired",
       valueType: "switch",
+      render: (text, record) => {
+        return <Switch disabled checked={record.isRequired} />;
+      },
       width: 80,
     },
     {
       title: "唯一",
       dataIndex: "isUnique",
       valueType: "switch",
+      render: (text, record) => {
+        return <Switch disabled checked={record.isUnique} />;
+      },
       width: 80,
     },
     {
@@ -278,7 +284,7 @@ export default function TableEdit(props: {
   ];
 
   const handleAdd = () => {
-    return createColumn(props?.tableId);
+    return createColumn(props?.tableId, dataSource.length + 1);
   };
 
   return (

+ 21 - 16
apps/er-designer/src/components/TableNode.tsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useRef } from "react";
+import React, { useEffect, useMemo, useRef } from "react";
 import { register } from "@antv/x6-react-shape";
 import { Graph, Node } from "@antv/x6";
 import type { ColumnItem, TableItemType } from "@/type";
@@ -11,13 +11,10 @@ function TableNode({ node, graph }: { node: Node; graph: Graph }) {
   const [showEdit, setShowEdit] = React.useState(false);
   const [_tabActiveKey, setTabActiveKey] =
     useSessionStorageState("tabs-active-key");
-  const [_active, setTableActive] = useSessionStorageState('table-active');
-  const [playModeEnable] = useSessionStorageState(
-      "playModeEnable",
-      {
-        listenStorageChange: true,
-      }
-    );
+  const [_active, setTableActive] = useSessionStorageState("table-active");
+  const [playModeEnable] = useSessionStorageState("playModeEnable", {
+    listenStorageChange: true,
+  });
 
   useEffect(() => {
     const container = containerRef.current;
@@ -85,7 +82,7 @@ function TableNode({ node, graph }: { node: Node; graph: Graph }) {
           id: item.id + "_port1",
           group: "columnPort",
           args: {
-            y: 42 + 16 + index * 27,
+            y: 44 + 16 + index * 27,
           },
           attrs: {
             circle: {
@@ -145,21 +142,27 @@ function TableNode({ node, graph }: { node: Node; graph: Graph }) {
     setTableActive(node.id);
   };
 
-  const tableName = table.langNameList?.find((item) => item.lang === "zh-CN")?.value;
+  const tableName = useMemo(() => {
+    return table.langNameList?.find((item) => item.name === "zh-CN")?.value;
+  }, [table]);
 
   const ColumnItem = ({ record }: { record: ColumnItem }) => {
     const type = DATA_TYPE_OPTIONS.find((item) => item.value === record.type);
-    const columnName = record.langNameList?.find((item) => item.lang === "zh-CN")?.value;
+    const columnName = record.langNameList?.find(
+      (item) => item.name === "zh-CN"
+    )?.value;
     return (
       <div
         className="w-full flex py-4px px-8px"
         onMouseUp={() => handleColumnMouseUp(record)}
       >
         <span className="flex-1 truncate flex items-center justify-between">
-          <span className="flex items-center">
-            <span className=" w-6px h-6px rounded-full mr-4px  inline-block cursor-pointer" />
-            {record.schemaName}
-            {columnName ? `(${columnName})` : ""}
+          <span className="flex items-center truncate">
+            <span className=" w-6px h-6px rounded-full mr-4px shrink-0 inline-block cursor-pointer" />
+            <span className="flex-1 truncate">
+              {record.schemaName}
+              {columnName ? `(${columnName})` : ""}
+            </span>
           </span>
           <span>
             {record.isUnique ? (
@@ -209,7 +212,9 @@ function TableNode({ node, graph }: { node: Node; graph: Graph }) {
         "
       >
         <div className="truncate flex-1">
-          <span className="text-12px font-normal">{table.parentBusinessTableId && '[子]'}</span>
+          <span className="text-12px font-normal">
+            {table.parentBusinessTableId && "[子]"}
+          </span>
           {table.schemaName}({tableName})
         </div>
         {showEdit && !playModeEnable && (

+ 177 - 0
apps/er-designer/src/components/TransferTable.tsx

@@ -0,0 +1,177 @@
+import React, { useState } from "react";
+import { Flex, Switch, Table, Tag, Transfer } from "antd";
+import type {
+  GetProp,
+  TableColumnsType,
+  TableProps,
+  TransferProps,
+} from "antd";
+import { DATA_TYPE_OPTIONS } from "@/constants";
+import type { ViewTable, ColumnItem } from "@/type";
+import { DataType } from "@/enum";
+
+type TransferItem = GetProp<TransferProps, "dataSource">[number];
+type TableRowSelection<T extends object> = TableProps<T>["rowSelection"];
+
+interface TableTransferProps extends TransferProps<TransferItem> {
+  dataSource: ColumnItem[];
+  leftColumns: TableColumnsType<ColumnItem>;
+  rightColumns: TableColumnsType<ColumnItem>;
+}
+
+const TableTransfer: React.FC<TableTransferProps> = (props) => {
+  const { leftColumns, rightColumns, ...restProps } = props;
+  return (
+    <Transfer style={{ width: "100%" }} {...restProps}>
+      {({
+        direction,
+        filteredItems,
+        onItemSelect,
+        onItemSelectAll,
+        selectedKeys: listSelectedKeys,
+        disabled: listDisabled,
+      }) => {
+        const columns = direction === "left" ? leftColumns : rightColumns;
+        const rowSelection: TableRowSelection<TransferItem> = {
+          getCheckboxProps: () => ({ disabled: listDisabled }),
+          onChange(selectedRowKeys) {
+            onItemSelectAll(selectedRowKeys, "replace");
+          },
+          selectedRowKeys: listSelectedKeys,
+          selections: [
+            Table.SELECTION_ALL,
+            Table.SELECTION_INVERT,
+            Table.SELECTION_NONE,
+          ],
+        };
+
+        return (
+          <Table
+            rowSelection={rowSelection}
+            columns={columns}
+            dataSource={filteredItems}
+            size="small"
+            style={{ pointerEvents: listDisabled ? "none" : undefined }}
+            onRow={({ key, disabled: itemDisabled }) => ({
+              onClick: () => {
+                if (itemDisabled || listDisabled) {
+                  return;
+                }
+                onItemSelect(key, !listSelectedKeys.includes(key));
+              },
+            })}
+          />
+        );
+      }}
+    </Transfer>
+  );
+};
+
+const columns: TableColumnsType<ColumnItem> = [
+  {
+    title: "字段代码",
+    dataIndex: "schemaName",
+    width: 150,
+  },
+  {
+    title: "字段名称",
+    dataIndex: "langName",
+    width: 100,
+    render: (_dom, entity) => {
+      return (
+        entity.langNameList?.find(
+          (item: Record<string, string>) => item.name === "zh-CN"
+        )?.value || "-"
+      );
+    },
+  },
+  {
+    title: "类型",
+    dataIndex: "type",
+    width: 120,
+    render: (val) => {
+      return DATA_TYPE_OPTIONS.find((item) => item.value === val)?.label || "-";
+    },
+  },
+  {
+    title: "长度",
+    dataIndex: "maxLength",
+    width: 80,
+    render: (text, record) => {
+      return record.type === DataType.Decimal
+        ? `${record.precision},${record.scale}`
+        : text;
+    },
+  },
+  {
+    title: "必填",
+    dataIndex: "isRequired",
+    render: (val) => {
+      return <Switch disabled checked={val} />;
+    },
+    width: 80,
+  },
+  {
+    title: "唯一",
+    dataIndex: "isUnique",
+    render: (val) => {
+      return <Switch disabled checked={val} />;
+    },
+    width: 80,
+  },
+  {
+    title: "默认值",
+    dataIndex: "defaultValue",
+    width: 150,
+  },
+  {
+    title: "字符集",
+    dataIndex: "chartset",
+    width: 120,
+  },
+  {
+    title: "内容",
+    dataIndex: "whereInputContent",
+    width: 120,
+  },
+  {
+    title: "描述",
+    dataIndex: "memo",
+    width: 120,
+  },
+];
+
+const filterOption = (input: string, item: ColumnItem) =>
+  !!(
+    item?.schemaName?.includes(input) ||
+    item.langNameList
+      ?.find((item) => item.name === "zh-CN")
+      ?.value?.includes(input)
+  );
+
+const TransferTable: React.FC<{ dataSource: ColumnItem[], useModelData: boolean }> = (props) => {
+  const [targetKeys, setTargetKeys] = useState<TransferProps["targetKeys"]>([]);
+
+  const onChange: TableTransferProps["onChange"] = (nextTargetKeys) => {
+    setTargetKeys(nextTargetKeys);
+  };
+
+  return (
+    <Flex align="start" gap="middle" vertical>
+      <TableTransfer
+        dataSource={props.dataSource}
+        rowKey={(item) => item.id}
+        titles={["当前字段", "数据表字段"]}
+        targetKeys={targetKeys}
+        showSearch
+        showSelectAll={false}
+        onChange={onChange}
+        filterOption={filterOption}
+        leftColumns={columns}
+        rightColumns={columns}
+      />
+    </Flex>
+  );
+};
+
+export default TransferTable;

+ 23 - 21
apps/er-designer/src/models/erModel.tsx

@@ -442,11 +442,11 @@ export default function erModel() {
     const childTableIds = project.tables
       .filter((item) => item.table.parentBusinessTableId === tableId)
       .map((item) => item.table.id);
-    setProject({
+    const newInfo = {
       ...project,
       tables: project.tables.filter(
         (item) =>
-          item.table.id !== tableId ||
+          item.table.id !== tableId &&
           item.table.parentBusinessTableId !== tableId
       ),
       // 对应关系
@@ -457,7 +457,8 @@ export default function erModel() {
           !childTableIds.includes(item.primaryTable) &&
           !childTableIds.includes(item.foreignTable)
       ),
-    });
+    };
+    setProject(newInfo);
   };
 
   /**
@@ -886,24 +887,25 @@ export default function erModel() {
   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
-    // });
+    // todo 获取新数据
+    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 {

+ 4 - 0
apps/er-designer/src/models/initInfo.ts

@@ -32,7 +32,11 @@ export const initInfo = (info: ProjectInfo) => {
     tableItem.table.style = JSON.parse(
       tableItem.table.style as unknown as string
     );
+    tableItem.tableColumnList = tableItem.tableColumnList.sort(
+      (a, b) => (a.displayOrder || 0) - (b.displayOrder || 0)
+    );
   });
+
   // json字符串转换
   info.relations.forEach((relationItem) => {
     relationItem.style = JSON.parse(relationItem.style as unknown as string);

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

@@ -40,6 +40,7 @@ export default function index() {
   );
   const erRef = useRef<HTMLDivElement>(null);
   const addModelRef = useRef<{ edit: (info: ProjectInfo) => void }>();
+  const syncModalRef = useRef<{ open: () => void }>();
   const [isFullscreen, { enterFullscreen, exitFullscreen }] =
     useFullscreen(erRef);
   const [collapsed, setCollapsed] = useState(false);
@@ -200,11 +201,16 @@ export default function index() {
       });
   };
 
+  // 同步数据表
+  const handleSync = () => { 
+    syncModalRef.current?.open()
+  };
+
   const extra = (
     <div className="flex gap-12px">
-      <a>
+      <a onClick={handleSync}>
         <i className="iconfont icon-tongbu text-12px" />
-        一键同步
+        数据同步
       </a>
       <a onClick={() => project.id && addModelRef.current?.edit(project)}>
         <i className="iconfont icon-bianji text-12px" />
@@ -224,9 +230,14 @@ export default function index() {
   return (
     <Spin spinning={loading}>
       {/* 基础信息修改弹窗 */}
-      <AddModel ref={addModelRef} onChange={(info) => { typeof info === 'object' && setProject(info)}}/>
+      <AddModel
+        ref={addModelRef}
+        onChange={(info) => {
+          typeof info === "object" && setProject(info);
+        }}
+      />
       {/* 同步弹窗 */}
-      <SyncModal/>
+      <SyncModal ref={syncModalRef} />
       <Layout className="h-100vh flex flex-col bg-#fafafa p-12px">
         <Header
           className="shadow-sm"
@@ -248,7 +259,7 @@ export default function index() {
                 {project?.name || "-"}
                 <Button
                   type="text"
-                  className={collapsed ? "rotate-180" : ""}
+                  className={!collapsed ? "rotate-180" : ""}
                   icon={<DownOutlined />}
                   onClick={() => setCollapsed(!collapsed)}
                 />
@@ -454,6 +465,9 @@ export default function index() {
                             return (
                               <>
                                 <LangInputTextarea
+                                  value={form.getFieldValue(
+                                    "langDescriptionList"
+                                  )}
                                   onChange={(val) =>
                                     form.setFieldValue(
                                       "langDescriptionList",
@@ -479,7 +493,7 @@ export default function index() {
             </div>
           </div>
         </Content>
-        <AddTable ref={addTableRef} onChange={handleAddTable}/>
+        <AddTable ref={addTableRef} onChange={handleAddTable} />
       </Layout>
     </Spin>
   );

+ 0 - 1
apps/er-designer/src/pages/er/components/ColumnItem.tsx

@@ -45,7 +45,6 @@ export default function ColumnItem({
   };
 
   const validCodeErrMsg = useMemo(() => {
-    setOpen(true);
     if (!code) return "编码不能为空";
     if (code.length >= 50) {
       return "编码长度不能超过50";

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

@@ -81,7 +81,7 @@ export default function TableItem({
 
   // 添加字段
   const handleAddColumn = () => {
-    const newColumn: ColumnItemType = createColumn(table.id);
+    const newColumn: ColumnItemType = createColumn(table.id, tableColumnList.length + 1);
     onChange({
       table,
       tableColumnList: [...tableColumnList, newColumn],

+ 30 - 31
apps/er-designer/src/pages/er/index.tsx

@@ -11,7 +11,11 @@ import Menu from "./components/Menu";
 import { useModel, useRequest, useParams } from "umi";
 import "./index.less";
 import { useSessionStorageState } from "ahooks";
-import { EnvironmentOutlined, FullscreenExitOutlined, UnorderedListOutlined } from "@ant-design/icons";
+import {
+  EnvironmentOutlined,
+  FullscreenExitOutlined,
+  UnorderedListOutlined,
+} from "@ant-design/icons";
 import { GetDataModelDetail } from "@/api";
 
 const { Header, Content, Sider } = Layout;
@@ -27,7 +31,7 @@ const App: React.FC = () => {
       listenStorageChange: true,
     }
   );
-  const [show, setShow] = useSessionStorageState('show-navigator');
+  const [show, setShow] = useSessionStorageState("show-navigator");
   const params = useParams();
 
   const { run, loading } = useRequest(GetDataModelDetail, {
@@ -36,11 +40,7 @@ const App: React.FC = () => {
       console.log("模型详情:", res);
       const result = res?.result;
       if (result) {
-        setProject(
-          result,
-          false,
-          true
-        );
+        setProject(result, false, true);
       }
     },
   });
@@ -50,9 +50,9 @@ const App: React.FC = () => {
     if (containerRef.current) {
       initGraph(containerRef.current);
     }
-    if(!project.id && params?.id) {
+    if (!project.id && params?.id) {
       run({
-        id: params.id
+        id: params.id,
       });
     }
   }, []);
@@ -133,12 +133,12 @@ const App: React.FC = () => {
           <Layout>
             <Content>
               <div id="graph-container" ref={containerRef}></div>
-              <Navigator key="editor"/>
-              {
-                playModeEnable && <div className="absolute top-32px right-32px z-2">
-                <div className="left bg-#fff shadow-md p-x-4px p-y-4px flex items-center gap-8px">
-                  <div
-                    className="
+              <Navigator key="editor" />
+              {playModeEnable && (
+                <div className="absolute top-32px right-32px z-2">
+                  <div className="left bg-#fff shadow-md p-x-4px p-y-4px flex items-center gap-8px">
+                    <div
+                      className="
                     rounded-4px 
                     cus-btn 
                     w-32px 
@@ -149,11 +149,11 @@ const App: React.FC = () => {
                     leading-32px 
                     cursor-pointer 
                     hover:bg-#ddd"
-                  >
-                    <UnorderedListOutlined />
-                  </div>
-                  <div
-                    className="
+                    >
+                      <UnorderedListOutlined />
+                    </div>
+                    <div
+                      className="
                     rounded-4px 
                     cus-btn 
                     w-32px 
@@ -164,12 +164,11 @@ const App: React.FC = () => {
                     leading-32px 
                     cursor-pointer 
                     hover:bg-#ddd"
-
-                  >
-                    <EnvironmentOutlined onClick={() => setShow(!show)}/>
-                  </div>
-                  <div
-                    className="
+                    >
+                      <EnvironmentOutlined onClick={() => setShow(!show)} />
+                    </div>
+                    <div
+                      className="
                     rounded-4px 
                     cus-btn 
                     w-32px 
@@ -180,13 +179,13 @@ const App: React.FC = () => {
                     leading-32px 
                     cursor-pointer 
                     hover:bg-#ddd"
-                    onClick={() => exitPlayMode()}
-                  >
-                    <FullscreenExitOutlined />
+                      onClick={() => exitPlayMode()}
+                    >
+                      <FullscreenExitOutlined />
+                    </div>
                   </div>
                 </div>
-              </div>
-              }
+              )}
             </Content>
           </Layout>
         </Layout>

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

@@ -465,7 +465,7 @@ export default function All({
                             onFresh={refresh}
                             onDelete={refresh}
                             onChangeLocation={() =>
-                              handleOpenFolderModal(item.id, "model")
+                              handleOpenFolderModal(item, "model")
                             }
                           />
                         </DragItem>

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

@@ -101,7 +101,7 @@ export const createTable = (tableType: TableType, dataModelId: string, parentId?
  * @param tableId 表id
  * @returns
  */
-export const createColumn = (tableId?: string): ColumnItem => {
+export const createColumn = (tableId?: string, displayOrder?: number): ColumnItem => {
   return {
     id: uuid(),
     schemaName: "",
@@ -113,7 +113,7 @@ export const createColumn = (tableId?: string): ColumnItem => {
     isUnique: false,
     isPreDefined: false,
     defaultValue: "",
-    displayOrder: 0,
+    displayOrder,
     tableId: tableId || "",
     memo: "",
     whereInputType: "",