Browse Source

feat: 添加推送同步功能

liaojiaxing 2 months ago
parent
commit
36a7575e1f

+ 11 - 0
apps/er-designer/src/api/dataModel.ts

@@ -53,3 +53,14 @@ export const GetDataModelDetail = (data: { id: string }) => {
     data,
   });
 };
+
+/**
+ * 同步推送数据表
+ * @param data
+ */
+export const PushDataModelTable = (data: any) => {
+  return request("/api/erDiagram/dataModel/push", {
+    method: "POST",
+    data,
+  });
+}

+ 311 - 0
apps/er-designer/src/components/DiffTable.tsx

@@ -0,0 +1,311 @@
+import type { TableItemType, ColumnItem } from "@/type";
+import React, { useEffect, useState } from "react";
+import {
+  ProDescriptions,
+  ProDescriptionsProps,
+} from "@ant-design/pro-components";
+import LangInput from "@/components/LangInput";
+import LangInputTextarea from "@/components/LangInputTextarea";
+import { Button, Table, Tooltip, TableProps, Spin } from "antd";
+import { validateAliasName, validateTableCode } from "@/utils/validator";
+import { GetBusinessTablesByTableId, GetAllBusinessTableColumns, ListLangByKey } from "@/api";
+import { useRequest } from "umi";
+import { DoubleLeftOutlined } from "@ant-design/icons";
+import { createColumn } from "@/utils";
+import { pick } from "lodash-es";
+
+const baseColumns: TableProps["columns"] = [
+  {
+    title: "字段代码",
+    dataIndex: "schemaName",
+  },
+  {
+    title: "字段名称",
+    dataIndex: "name",
+    render: (_dom, entity) => {
+      return entity.langNameList
+        ? entity.langNameList.find(
+            (item: Record<string, string>) => item.name === "zh-CN"
+          )?.value || "-"
+        : entity.name;
+    },
+  },
+  {
+    title: "类型",
+    dataIndex: "type",
+  },
+  {
+    title: "长度",
+    dataIndex: "maxLength",
+  },
+  {
+    title: "必填",
+    dataIndex: "isRequired",
+  },
+  {
+    title: "唯一",
+    dataIndex: "isUnique",
+  },
+  {
+    title: "默认值",
+    dataIndex: "defaultValue",
+  },
+  {
+    title: "字符集",
+    dataIndex: "chartset",
+  },
+  {
+    title: "内容",
+    dataIndex: "whereInputContent",
+  },
+  {
+    title: "描述",
+    dataIndex: "memo",
+  },
+];
+
+// 表详情组件
+const DescComp = function ({
+  detail,
+  editable,
+  title,
+}: {
+  detail: TableItemType["table"];
+  editable?: ProDescriptionsProps["editable"];
+  title?: string;
+}) {
+  return (
+    <ProDescriptions
+      title={title}
+      dataSource={detail}
+      editable={editable}
+      columns={[
+        {
+          label: "类型",
+          dataIndex: "type",
+          valueType: "select",
+          valueEnum: {
+            3: "业务表",
+            2: "流程表",
+          },
+          editable: false,
+        },
+        {
+          label: "编码",
+          dataIndex: "schemaName",
+          valueType: "text",
+          formItemProps: {
+            rules: [
+              { required: true, message: "请输入编码" },
+              validateTableCode,
+            ],
+          },
+        },
+        {
+          label: "别名",
+          dataIndex: "aliasName",
+          valueType: "text",
+          formItemProps: {
+            rules: [
+              { required: true, message: "请输入别名" },
+              validateAliasName,
+            ],
+          },
+        },
+        {
+          label: "名称",
+          dataIndex: "langNameList",
+          render: (_, record) => {
+            return (
+              record.langNameList?.find((item: any) => item.name === "zh-CN")
+                ?.value || "-"
+            );
+          },
+          renderFormItem: (_schema, config, form) => {
+            return (
+              <LangInput
+                style={{ width: 200 }}
+                onChange={(val) => form.setFieldValue("langNameList", val)}
+              />
+            );
+          },
+        },
+        {
+          label: "描述",
+          dataIndex: "langDescriptionList",
+          render: (_, record) => {
+            return (
+              record?.langDescriptionList?.find(
+                (item: any) => item.name === "zh-CN"
+              )?.value || "-"
+            );
+          },
+          renderFormItem: (_schema, config, form) => {
+            return (
+              <>
+                <LangInputTextarea
+                  value={form.getFieldValue("langDescriptionList")}
+                  onChange={(val) =>
+                    form.setFieldValue("langDescriptionList", val)
+                  }
+                />
+              </>
+            );
+          },
+        },
+      ]}
+    ></ProDescriptions>
+  );
+};
+export default function DiffTable({
+  sourceTable,
+  targetTableId,
+  onChange
+}: {
+  sourceTable: TableItemType;
+  targetTableId?: string;
+  onChange: (tableItem: TableItemType) => void;
+}) {
+  const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
+  const [selectedRows, setSelectedRows] = useState<any[]>([]);
+
+  const {
+    data: tableDetailRes,
+    run: run1,
+    loading: loading1,
+  } = useRequest(GetBusinessTablesByTableId, { manual: true });
+
+  const {
+    data: tableColumnRes,
+    run: run2,
+    loading: loading2,
+  } = useRequest(GetAllBusinessTableColumns, { manual: true });
+
+  useEffect(() => {
+    if (targetTableId) {
+      run1(targetTableId);
+      run2({
+        currentPage: 1,
+        pageSize: 2000,
+        orderByProperty: "isPreDefined DESC, DisplayOrder",
+        Ascending: true,
+        totalPage: 1,
+        totalCount: 1,
+        filters: [
+          {
+            name: "BusinessTableId",
+            value: targetTableId,
+          },
+        ],
+      });
+    }
+  }, []);
+
+  // 右侧选中数据覆盖左侧数据
+  const handleCoverTable = async () => {
+    if(!selectedRows.length) return;
+    const arr = [...sourceTable.tableColumnList];
+
+    // 获取全部多语言数据
+    const langList = await Promise.all(
+      [...new Set(selectedRows.map(item => item.langName))].map((key) =>
+        ListLangByKey({ key }).then((res) => res.result)
+      )
+    );
+
+    // 1、存在的数据进行更新
+    // 2、不存在的数据进行新增
+    selectedRows.forEach((item) => {
+      const index = arr.findIndex(
+        (item2: any) => item2.schemaName === item.schemaName
+      );
+      const newColumn = createColumn(sourceTable.table.id);
+      const langName = langList.find((lang) => lang.key === item.langName);
+
+      const column = {
+        ...newColumn,
+        ...pick(item, Object.keys(newColumn)),
+        langNameList: [
+          { name: "zh-CN", value: langName?.["zh-CN"] || "" },
+          { name: "en", value: langName?.["en"] || "" },
+        ],
+      };
+      if (index !== -1) {
+        arr[index] = column;
+      } else {
+        arr.push(column);
+      }
+    });
+
+    onChange({
+      ...sourceTable,
+      tableColumnList: arr,
+    });
+
+    setSelectedKeys([]);
+    setSelectedRows([]);
+  };
+
+  return (
+    <Spin spinning={loading1 || loading2}>
+      <div className="flex">
+        <div className="left rounded-8px border border-1px border-solid border-#979797 overflow-hidden">
+          <div className="h-120px p-y-10px p-l-20px">
+            <DescComp
+              title="当前模型表"
+              detail={sourceTable.table}
+              editable={{
+                onSave: async (keypath, newInfo, oriInfo) => {
+                  onChange({
+                    ...sourceTable,
+                    table: {
+                      ...sourceTable.table,
+                      ...newInfo
+                    }
+                  });
+                  return true;
+                },
+              }}
+            />
+          </div>
+          <Table
+            pagination={false}
+            columns={baseColumns}
+            dataSource={sourceTable.tableColumnList || []}
+          />
+        </div>
+        <div className="middle w-100px flex-shrink-0">
+          <Tooltip title="使用右侧数据">
+            <Button
+              className="block m-x-auto m-y-0 m-t-140px"
+              icon={<DoubleLeftOutlined />}
+              disabled={!selectedKeys.length}
+              onClick={handleCoverTable}
+            />
+          </Tooltip>
+        </div>
+        <div className="right rounded-8px border border-1px border-solid border-#979797 overflow-hidden">
+          <div className="h-120px p-y-10px p-l-20px">
+            <DescComp
+              title="远程数据表"
+              detail={tableDetailRes?.result?.[0] || {}}
+            />
+          </div>
+          <Table
+            pagination={false}
+            columns={baseColumns}
+            dataSource={tableColumnRes?.result?.model || []}
+            rowSelection={{
+              type: "checkbox",
+              selectedRowKeys: selectedKeys,
+              onChange: (keys, selectedRows) => {
+                setSelectedKeys(keys);
+                setSelectedRows(selectedRows);
+              },
+            }}
+          />
+        </div>
+      </div>
+    </Spin>
+  );
+}

+ 110 - 19
apps/er-designer/src/components/SyncModal.tsx

@@ -1,46 +1,137 @@
-import { forwardRef, useImperativeHandle, useState } from "react";
-import { Modal, Table, Collapse, Checkbox } from "antd";
-import type { TableProps } from "antd";
-import TrasnferTable from "./TransferTable";
-import { useModel } from "umi";
+import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
+import { Modal, Table, Collapse, Checkbox, message } from "antd";
+import DiffTable from "./DiffTable";
+import { useModel, useRequest } from "umi";
+import { PushDataModelTable } from "@/api/dataModel";
+import { GetAllDesignTables, GetAllBusinessTableColumns } from "@/api";
+import { TableItemType } from "@/type";
 
-export default forwardRef(function SyncModal(props, ref) {
+export default forwardRef(function SyncModal(props: {onPush: () => void}, ref) {
   const [open, setOpen] = useState(false);
   const { project } = useModel("erModel");
+  const { data, loading, run } = useRequest(GetAllDesignTables, {
+    manual: true,
+  });
+  const [tableList, setTableList] = useState<TableItemType[]>(project?.tables);
+
   useImperativeHandle(ref, () => ({
     open: () => {
       setOpen(true);
+      run();
     },
     close: () => setOpen(false),
   }));
 
-  const [useModelData, setUseModelData] = useState(false);
+  useEffect(() => {
+    setTableList(project.tables);
+  }, [project.tables]);
+
+  // 已存在的数据表
+  const existTableList = useMemo(() => {
+    const list: {
+      modelTable: TableItemType;
+      dataTable: any;
+    }[] = [];
+    const tables = data?.result?.appBusinessTables || [];
+    tableList.forEach((tableItem) => {
+      const dataTable = tables.find(
+        (item: any) =>
+          item.schemaName === tableItem.table.schemaName ||
+          item.aliasName === tableItem.table.aliasName
+      );
+      if (dataTable) {
+        list.push({
+          modelTable: tableItem,
+          dataTable,
+        });
+      }
+    });
+
+    return list;
+  }, [tableList, data]);
+
+  const handleUpdateTable = (tableItem: TableItemType) => {
+    setTableList(
+      tableList.map((item) =>
+        item.table.id === tableItem.table.id ? tableItem : item
+      )
+    );
+  }
+
+  const [okLoading, setOkLoading] = useState(false);
+  const handleSubmit = async () => {
+    try {
+      setOkLoading(true);
+      await PushDataModelTable(tableList);
+      message.success("同步推送完成");
+      setOpen(false);
+      props.onPush?.();
+    } finally {
+      setOkLoading(false);
+    }
+  }
 
   return (
     <Modal
-      title="数据同步"
+      title="数据同步推送"
       width={"100%"}
       open={open}
       okText="同步"
+      loading={loading}
+      style={{
+        top: 10,
+        padding: 0,
+      }}
+      bodyProps={{
+        style: {
+          height: "80vh",
+          overflowY: "auto",
+        },
+      }}
+      okButtonProps={{
+        loading: okLoading
+      }}
       onCancel={() => setOpen(false)}
+      onOk={handleSubmit}
     >
-      <Checkbox
-        checked={useModelData}
-        onChange={(e) => setUseModelData(e.target.checked)}
-      >
-        全部使用模型数据
-      </Checkbox>
+      <div className="text-14px font-bold m-y-12px">
+        提示:将当前模型表推送至数据表,一共
+        <span className="color-green">{tableList.length}</span>
+        张表格数据。
+        {existTableList.length ? (
+          <>
+            其中有<span className="color-#faad14">{existTableList.length}</span>
+            张表存在相同的编码或别名,可对比确认后提交。
+          </>
+        ) : null}
+      </div>
       <Collapse
-        defaultActiveKey={project.tables.map((item) => item.table.id)}
-        items={project.tables.map((item) => {
-          const { table, tableColumnList } = item;
+        items={tableList.map((item) => {
+          const { table } = item;
           const name = table.langNameList?.find(
             (item) => item.name === "zh-CN"
           )?.value;
+          const existTable = existTableList.find(
+            (item) => item.modelTable.table.id === table.id
+          );
           return {
             key: table.id,
-            label: `${table.schemaName}${name ? `(${name})` : ""}`,
-            children: <TrasnferTable dataSource={tableColumnList} useModelData={useModelData} />,
+            label: (
+              <span
+                style={{
+                  color: existTable
+                    ? "#faad14"
+                    : "",
+                }}
+              >{`${table.schemaName}${name ? `(${name})` : ""}`}</span>
+            ),
+            children: (
+              <DiffTable
+                sourceTable={item}
+                targetTableId={existTable?.dataTable?.id}
+                onChange={handleUpdateTable}
+              />
+            ),
           };
         })}
       />

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

@@ -1,177 +0,0 @@
-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;

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

@@ -64,7 +64,7 @@ export default forwardRef(function AddTable(
     },
   }));
 
-  const { data, loading, run } = useRequest(() => GetAllDesignTables(), {
+  const { data, loading, run } = useRequest(() => GetAllDesignTables, {
     manual: true,
   });
 

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

@@ -67,7 +67,7 @@ export default function index() {
 
   const params = useParams();
 
-  const { run, loading } = useRequest(GetDataModelDetail, {
+  const { run, loading, refresh } = useRequest(GetDataModelDetail, {
     manual: true,
     onSuccess: (res) => {
       console.log("模型详情:", res);
@@ -102,6 +102,11 @@ export default function index() {
         label: "创建时间",
         children: project?.createTime || "-",
       },
+      {
+        key: "5",
+        label: "发布状态",
+        children: project?.publishStatus || "-",
+      },
       {
         key: "4-1",
         label: "更新用户",
@@ -112,11 +117,6 @@ export default function index() {
         label: "更新时间",
         children: project?.updateTime || "-",
       },
-      {
-        key: "5",
-        label: "发布状态",
-        children: project?.publishStatus || "-",
-      },
       {
         key: "6",
         label: "表数",
@@ -260,7 +260,7 @@ export default function index() {
         }}
       />
       {/* 同步弹窗 */}
-      <SyncModal ref={syncModalRef} />
+      <SyncModal ref={syncModalRef} onPush={refresh}/>
       <Layout className="h-100vh flex flex-col bg-#fafafa p-12px">
         <Header
           className="shadow-sm"