Explorar el Código

feat: 修改导航组件 添加表接口

liaojiaxing hace 6 meses
padre
commit
f0591403dd

+ 31 - 37
apps/designer/src/pages/home/index.tsx

@@ -1,17 +1,7 @@
-import {
-  PlusOutlined,
-} from "@ant-design/icons";
-import {
-  ProConfigProvider,
-  ProLayout,
-} from "@ant-design/pro-components";
+import { PlusOutlined } from "@ant-design/icons";
+import { ProConfigProvider, ProLayout } from "@ant-design/pro-components";
 import { css } from "@emotion/css";
-import {
-  Space,
-  Button,
-  ConfigProvider,
-  Popover,
-} from "antd";
+import { Space, Button, ConfigProvider, Popover } from "antd";
 import React, { useMemo, useRef, useState } from "react";
 import { Icon } from "umi";
 import { createNew } from "@/utils";
@@ -22,13 +12,13 @@ import Template from "./Template";
 import { GraphType } from "@/enum";
 
 export default () => {
-  const currentFolder = useRef('root');
+  const currentFolder = useRef("root");
   const [updateKey, setUpdateKey] = useState(0);
 
   const handleCreate = async (type: GraphType) => {
     await createNew(type, currentFolder.current);
     setUpdateKey(updateKey + 1);
-  }
+  };
 
   const basicGraph = [
     {
@@ -48,7 +38,7 @@ export default () => {
       onClick: () => handleCreate(GraphType.mindmap),
     },
   ];
-  
+
   const appList = [
     {
       id: "1",
@@ -87,12 +77,12 @@ export default () => {
       onClick: () => handleCreate(GraphType.net),
     },
   ];
-  
+
   const handleMenuClick = (item: any) => {
     console.log("click", item);
     item?.onClick?.();
   };
-  
+
   const renderBasicItem = (props: {
     title: string;
     subtitle: string;
@@ -127,7 +117,7 @@ export default () => {
       </div>
     );
   };
-  
+
   const renderProItem = (props: {
     id: string;
     title: string;
@@ -144,7 +134,7 @@ export default () => {
       </div>
     );
   };
-  
+
   const RenderAppList = () => {
     return (
       <div className="w-460px">
@@ -158,7 +148,7 @@ export default () => {
 
   const handleChangeFolder = (id: string) => {
     currentFolder.current = id;
-  }
+  };
 
   const [pathname, setPathname] = useState("/all");
   const routes = [
@@ -166,7 +156,9 @@ export default () => {
       path: "/all",
       name: "全部",
       icon: <i className="iconfont icon-xitong" />,
-      component: <All onChangeCurrentFolder={ handleChangeFolder } updateKey={updateKey} />,
+      component: (
+        <All onChangeCurrentFolder={handleChangeFolder} updateKey={updateKey} />
+      ),
     },
     {
       path: "/recently",
@@ -205,47 +197,49 @@ export default () => {
         headerTitleRender={() => null}
         logo={false}
         title={"系统设计"}
-        collapsedButtonRender={() => <></>}
         location={{ pathname }}
         route={{
           routes,
         }}
         bgLayoutImgList={[
           {
-            src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
+            src: "https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png",
             left: 85,
             bottom: 100,
-            height: '303px',
+            height: "303px",
           },
           {
-            src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
+            src: "https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png",
             bottom: -68,
             right: -45,
-            height: '303px',
+            height: "303px",
           },
           {
-            src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
+            src: "https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png",
             bottom: 0,
             left: 0,
-            width: '331px',
+            width: "331px",
           },
         ]}
-        menuHeaderRender={(logo, title) => {
+        menuHeaderRender={(logo, title, props) => {
           return (
             <div className="flex-1">
-              <div className="flex items-center h-20px">
-                <span className="mr-8px">{logo}</span>
-                {title}
-              </div>
-              <div className="mt-32px">
+              { !props?.collapsed && (
+                <div className="flex items-center justify-center h-20px">
+                  {title}
+                </div>
+              )}
+              <div style={{ marginTop: props?.collapsed ? 0 : 12}}>
                 <Popover key="1" placement="left" content={<RenderAppList />}>
                   <Button
                     type="primary"
-                    className="w-full"
+                    style={{
+                      width: props?.collapsed ? "50px" : "100%",
+                    }}
                     onClick={() => {}}
                     icon={<PlusOutlined />}
                   >
-                    新建
+                    {props?.collapsed ? "" : "新建"}
                   </Button>
                 </Popover>
               </div>

+ 9 - 2
apps/er-designer/.umirc.ts

@@ -8,15 +8,22 @@ export default defineConfig({
     '/favicon.ico'
   ],
   styles: [
-    '//at.alicdn.com/t/c/font_4767192_1ktdaesgr6i.css'
+    '//at.alicdn.com/t/c/font_4767192_a9e424reibw.css'
   ],
   metas: [
     { name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' }
   ],
+  proxy: {
+    '/api': {
+      'target': 'http://ab.dev.jbpm.shalu.com/',
+      'changeOrigin': true,
+      'pathRewrite': { '^/api' : '' },
+    },
+  },
   scripts: [
     // 字体加载
     // '//ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js'
-    '//at.alicdn.com/t/c/font_4767192_1ktdaesgr6i.js'
+    '//at.alicdn.com/t/c/font_4767192_a9e424reibw.js'
   ],
   plugins: [
     require.resolve('@umijs/plugins/dist/unocss'),

+ 11 - 92
apps/er-designer/src/api/index.ts

@@ -27,108 +27,27 @@ export const ListLangBySearchKey = (data: {
 };
 
 /**
- * 场景查询
+ * 获取表格字段列表
  * @param data 
  * @returns 
- * {
-    "currentPage": 1,
-    "pageSize": 10,
-    "orderByProperty": "Id",
-    "Ascending": false,
-    "totalPage": 1,
-    "totalCount": 1,
-    "filters": [
-        {
-            "name": "EnterpriseId"
-        }
-    ]
-}
  */
-export const GetAllWfScene = (data: commonParams) => {
-  return request("/api/wfScene/GetAllWfScene", {
+export const GetAllBusinessTableColumns = (data: commonParams) => {
+  return request("/api/table/GetAllBusinessTableColumns", {
     method: "POST",
-    data,
+    data
   });
-};
+}
 
 /**
- * 流程查询
+ * 获取全部表格设计
  * @param data 
  * @returns 
- * {
-    "currentPage": 1,
-    "pageSize": 10,
-    "orderByProperty": "Id",
-    "Ascending": false,
-    "totalPage": 1,
-    "totalCount": 1,
-    "filters": [
-        {
-            "name": "EnterpriseId"
-        }
-    ]
-}
  */
-export const GetAllWorkflows = (data: commonParams) => {
-  return request("/api/workflow/GetAllWorkflows", {
-    method: "POST",
-    data,
-  });
-};
-
-/**
- * 获取全部页面
- * @param data
- * @returns
- */
-export const GetAllPage = () => {
-  return request<{
-    codeItemCount: number;
-    description: string;
-    directory: string;
-    fileName: string;
-    id: string;
-    isFavourite: boolean;
-    is_page_load_from_localhost: boolean;
-    langDescription: string;
-    langName: string;
-    menuLinkUrl: string;
-    name: string;
-    processStatus: string;
-    randerType: number;
-    reportType: number;
-    type: number;
-  }>("/api/bpm/GetAllPage", {
-    method: "POST",
-  });
-};
-
-/**
- * 获取全部视图和表
- * @param data
- * @returns
- */
-export const GetAllTablesAndViews = () => {
-  return request<{
-    appBusinessTables: {
-      aliasName: string;
-      description: string;
-      id: string;
-      isLatest: boolean;
-      latest: boolean;
-      name: string;
-      schemaName: string;
-      type: number;
-    }[];
-    bpmViewTables: {
-      description: string;
-      directory: string;
-      id: string;
-      name: string;
-      schemaName: string;
-      type: number;
-    }[];
-  }>("/api/table/GetAllTablesAndViews", {
+export const GetAllDesignTables = (data: {
+  groupType: string
+}) => {
+  return request("/api/table/GetAllDesignTables", {
     method: "POST",
+    data
   });
 };

+ 43 - 0
apps/er-designer/src/app.ts

@@ -0,0 +1,43 @@
+import { message } from 'antd';
+import type { RequestConfig } from 'umi';
+
+export const request: RequestConfig = {
+  timeout: 10000,
+  // other axios options you want
+  errorConfig: {
+    errorHandler(){
+    },
+    errorThrower(){
+    }
+  },
+  requestInterceptors: [
+    (url, options) => {
+      const baseUrl = process.env.NODE_ENV === 'production' ? '' : '/api'//'http://ab.dev.jbpm.shalu.com' // https://edesign.shalu.com'
+      const enterpriseCode = sessionStorage.getItem('enterpriseCode');
+      const token = localStorage.getItem('token_' + enterpriseCode);
+ 
+      if(token) {
+        if(!options.headers) {
+          options.headers = {}
+        }
+        options.headers.Authorization = token
+      }
+
+      return {
+        url: baseUrl + url,
+        options
+      }
+    }
+  ],
+  responseInterceptors: [
+    (response) => {
+      const {data = {} as any, config} = response;
+      if(data?.error) {
+        message.error(data.error);
+        return Promise.reject(data.error);
+      }
+      
+      return response;
+    }
+  ]
+};

+ 80 - 9
apps/er-designer/src/components/TableNode.tsx

@@ -1,15 +1,85 @@
-import React 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, ViewTable } from "@/type";
 function TableNode({ node, graph }: { node: Node; graph: Graph }) {
+  const { table, tableColumnList } = node.getData<{
+    table: ViewTable;
+    tableColumnList: ColumnItem[];
+  }>();
+  const headerRef = useRef<HTMLDivElement>(null);
+  const contentRef = useRef<HTMLDivElement>(null);
+
+  // 重置宽高
+  useEffect(() => {
+    if (table.tableExtInfoDto.width < 0) {
+      const width = headerRef.current?.offsetWidth || 0;
+      const height = (headerRef.current?.offsetHeight || 0) + (contentRef.current?.offsetHeight || 0);
+      if(width && height) {
+        node.resize(width, height);
+      }
+    }
+  }, []);
+
+  // 主键字段
+  const primaryKeyList = tableColumnList.filter((item) => item.isUnique);
+
+  // 非主键字段
+  const otherKeyList = tableColumnList.filter((item) => !item.isUnique);
+
+  const FiledItem = ({ record }: { record: ColumnItem }) => {
+    return (
+      <div className="w-full flex py-4px px-8px">
+        {record.isUnique ? (
+          <span className="m-r-4px">
+            <i className="iconfont icon-key-fill color-#efc553 m-r--4px" />
+            <span className="text-12px color-#f15359">P</span>
+            <span className="text-12px color-#8dafd1">F</span>
+          </span>
+        ) : (
+          <span>
+            <i className="iconfont icon-24_beizhu color-#efc553 m-r-4px text-12px" />
+          </span>
+        )}
+        <span className="flex-1 truncate">
+          {record.schemaName}({record.name}):VARCHAR2(20)
+        </span>
+      </div>
+    );
+  };
+
   return (
-    <div className="w-full h-full border border-1px border-solid border-#333 flex flex-col">
-      <div className="header border-b-solid border-b-1px border-b-#333 truncate bg-#fafafa text-center py-4px">
-        user_name(用户信息)
+    <div className="w-full h-full border border-1px border-solid border-#333 flex flex-col overflow-hidden rounded-8px">
+      <div
+        ref={headerRef}
+        className="
+          header 
+          border-b-solid 
+          border-b-1px 
+          border-b-#333 
+          truncate 
+          bg-#99b4d1 
+          text-center 
+          py-4px 
+          font-bold 
+          text-14px 
+          min-h-32px 
+          px-8px
+        "
+      >
+        {table.schemaName}({table.name})
       </div>
-      <div className="bg-#fafafa flex-1">
-        <div className="primary-key">主键信息</div>
-        <div className="field-info">字段列表</div>
+      <div ref={contentRef} className="bg-#fafafa flex-1">
+        <div className="primary-key border-b-solid border-b-1px border-b-#333 py-4px min-h-32px">
+          {primaryKeyList.map((item) => {
+            return <FiledItem record={item} />;
+          })}
+        </div>
+        <div className="field-info">
+          {otherKeyList.map((item) => {
+            return <FiledItem record={item} />;
+          })}
+        </div>
       </div>
     </div>
   );
@@ -18,6 +88,7 @@ function TableNode({ node, graph }: { node: Node; graph: Graph }) {
 register({
   shape: "table-node",
   component: TableNode,
-  width: 200,
-  height: 120,
+  width: 300,
+  height: 220,
+  effect: ["data"],
 });

+ 47 - 28
apps/er-designer/src/models/erModel.tsx

@@ -2,10 +2,14 @@ import { useEffect, useMemo, useRef, useState } from "react";
 import { Graph } from "@antv/x6";
 import { History } from "@antv/x6-plugin-history";
 import { Transform } from "@antv/x6-plugin-transform";
+import { Scroller } from "@antv/x6-plugin-scroller";
 import "@/components/TableNode";
+import { GetAllDesignTables } from "@/api";
+import { useRequest } from "umi";
 export default function erModel() {
   const graphRef = useRef<Graph>();
   const [graph, setGraph] = useState<Graph>();
+
   const initGraph = (container: HTMLElement) => {
     const instance = new Graph({
       container,
@@ -23,51 +27,66 @@ export default function erModel() {
         connectionPoint: "anchor",
       },
       grid: {
-        visible: true,
+        visible: false,
+        size: 5
       },
       background: {
         color: "#F2F7FA",
       },
-    });
-
-    instance.use(new History());
-    instance.use(new Transform());
-
-    instance.addNode({
-      shape: "rect",
-      x: 100,
-      y: 100,
-      width: 100,
-      height: 100,
-      attrs: {
-        body: {
-          stroke: "#8f8f8f",
-          strokeWidth: 1,
-          fill: "#fff",
+      interacting: {
+        nodeMovable: (view) => {
+          const data = view.cell.getData<{
+            ignoreDrag: boolean;
+            lock: boolean;
+          }>();
+          // 禁止拖拽或锁节点
+          if (data?.ignoreDrag || data?.lock) return false;
+          return true;
         },
       },
     });
 
-    instance.addNode({
-      shape: "table-node",
-      x: 300,
-      y: 100,
-      width: 200,
-      height: 200,
-    })
+    instance.use(new History());
+    instance.use(new Transform({
+      resizing: {
+        enabled: true,
+      }
+    }));
+    instance.use(new Scroller());
 
+    setGraph(instance);
     graphRef.current = instance;
   };
 
+  const { data, run, loading } = useRequest(() => GetAllDesignTables({ groupType: "" }), {
+    manual: false
+  });
+
   useEffect(() => {
-    if (graphRef.current) {
-      console.log(1111, graphRef.current)
-      setGraph(graphRef.current);
+    const { result } = data || {};
+    if(result && graphRef.current) {
+      const { lines, objects, points, tableColumnList, tableList } = result;
+      // 创建表格节点
+      tableList?.forEach((table) => {
+        graphRef.current?.addNode({
+          shape: "table-node",
+          x: 300,
+          y: 100,
+          width: 200,
+          height: 200,
+          data: {
+            table,
+            tableColumnList: tableColumnList?.filter(item => item.businessTableId === table.id)
+          }
+        })
+      });
     }
-  }, [graphRef.current]);
+  }, [graphRef.current, data]);
 
   return {
     initGraph,
     graph,
+    graphRef,
+    loading
   };
 }

+ 3 - 4
apps/er-designer/src/pages/er/components/Navigator.tsx

@@ -11,8 +11,8 @@ export default function Navigator() {
       graph.use(
         new MiniMap({
           container: mapRef.current,
-          width: 330,
-          height: 210,
+          width: mapRef.current.offsetWidth || 300,
+          height: mapRef.current.offsetHeight || 200,
           padding: 10,
         })
       );
@@ -20,8 +20,7 @@ export default function Navigator() {
   }, [graph, mapRef.current]);
 
   return (
-    <div>
-      <div ref={mapRef}></div>
+    <div className="w-full h-full flex-1" ref={mapRef}>
     </div>
   );
 }

+ 25 - 0
apps/er-designer/src/pages/er/index.less

@@ -0,0 +1,25 @@
+.x6-widget-transform {
+  padding: 5px;
+  border: 1px dashed #239edd;
+  border-radius: 0;
+}
+
+.x6-widget-transform > div {
+  border: 1px solid #239edd;
+}
+
+.x6-widget-transform > div:hover {
+  background-color: #3dafe4;
+}
+
+.x6-widget-transform-active-handle {
+  background-color: #3dafe4;
+}
+
+.x6-widget-transform-resize {
+  border-radius: 0;
+}
+
+.x6-edge-selected {
+  filter: drop-shadow(0 0 2px #239edd);
+}

+ 4 - 3
apps/er-designer/src/pages/er/index.tsx

@@ -4,6 +4,7 @@ import Menu from "./components/Menu";
 import DataModel from "./components/DataModel";
 import Navigator from "./components/Navigator";
 import { useModel } from "umi";
+import "./index.less";
 
 const { Header, Content, Sider } = Layout;
 
@@ -13,9 +14,9 @@ const App: React.FC = () => {
 
   useEffect(() => {
     if (containerRef.current) {
-      initGraph?.(containerRef.current);
+      initGraph(containerRef.current);
     }
-  }, [containerRef.current]);
+  }, []);
 
   return (
     <Layout className="h-100vh">
@@ -33,7 +34,7 @@ const App: React.FC = () => {
               <DataModel />
             </div>
 
-            <div className="flex-1 bg-#eee">
+            <div className="flex-1 bg-#eee flex flex-col">
               <div className="header px-10px py-4px bg-#ddd">导航</div>
               <Navigator />
             </div>

+ 58 - 0
apps/er-designer/src/type.d.ts

@@ -0,0 +1,58 @@
+export interface ColumnItem {
+  aggregateEnable: boolean;
+  businessTableId: string;
+  charset: string;
+  cn_name: string;
+  defaultValue: any;
+  displayEnable: boolean;
+  displayOrder: number;
+  en_name: string;
+  groupByEnable: boolean;
+  id: string;
+  isAggregateEnable: boolean;
+  isDisplayEnable: boolean;
+  isGroupByEnable: boolean;
+  isLinkEnable: boolean;
+  isOrderByEnable: boolean;
+  isPreDefined: boolean;
+  isRequired: boolean;
+  isUnique: boolean;
+  isWhereEnable: boolean;
+  linkEnable: boolean;
+  maxLength: number;
+  name: string;
+  orderByEnable: boolean;
+  preDefined: boolean;
+  precision: number;
+  required: boolean;
+  scale: number;
+  schemaName: string;
+  type: number;
+  unique: boolean;
+  whereEnable: boolean;
+}
+
+export interface ViewTable {
+  aliasName: string;
+  creationTime: string;
+  creatorUserId: string;
+  displayOrder: number;
+  id: string;
+  isDeleted: boolean;
+  langDescription: string;
+  langName: string;
+  name: string;
+  parentBusinessTableId: string;
+  schemaName: string;
+  tableExtInfoDto: {
+    groupType: string;
+    height: number;
+    id: string;
+    style: Record<string, any>;
+    width: number;
+    x: number;
+    y: number;
+  };
+  type: number;
+  updateTime: string;
+}