feat: 初始化项目并完成基础功能开发

- 完成项目初始化
- 实现用户注册、登录功能
- 完成用户管理与权限管理模块
- 开发后端 Tracker 服务器项目管理接口
- 实现日志管理接口
Change-Id: Ia4bde1c9ff600352a7ff0caca0cc50b02cad1af7
diff --git a/react-ui/src/pages/Tool/Gen/components/BaseInfo.tsx b/react-ui/src/pages/Tool/Gen/components/BaseInfo.tsx
new file mode 100644
index 0000000..6a0466f
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/components/BaseInfo.tsx
@@ -0,0 +1,124 @@
+import { Button, Col, Form, message, Row } from 'antd';
+import React, { Fragment, useEffect } from 'react';
+import { history } from '@umijs/max';
+import styles from '../style.less';
+import { ProForm, ProFormText, ProFormTextArea } from '@ant-design/pro-components';
+
+export type BaseInfoProps = {
+  values?: any;
+  onStepSubmit?: any;
+};
+
+const BaseInfo: React.FC<BaseInfoProps> = (props) => {
+  const [form] = Form.useForm();
+  const { onStepSubmit } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      tableName: props.values.tableName,
+    });
+  });
+
+  const onValidateForm = async () => {
+    const values = await form.validateFields();
+    if (onStepSubmit) {
+      onStepSubmit('base', values);
+    }
+  };
+
+  return (
+    <Fragment>
+      <Row>
+        <Col span={24}>
+          <ProForm
+            form={form}
+            onFinish={async () => {
+              message.success('提交成功');
+            }}
+            initialValues={{
+              tableName: props.values?.tableName,
+              tableComment: props.values?.tableComment,
+              className: props.values?.className,
+              functionAuthor: props.values?.functionAuthor,
+              remark: props.values?.remark,
+            }}
+            submitter={{
+              resetButtonProps: {
+                style: { display: 'none' },
+              },
+              submitButtonProps: {
+                style: { display: 'none' },
+              },
+            }}
+          >
+            <Row>
+              <Col span={12} order={1}>
+                <ProFormText
+
+                  name="tableName"
+                  label="表名称"
+                  rules={[
+                    {
+                      required: true,
+                      message: '表名称不可为空。',
+                    },
+                  ]}
+                />
+              </Col>
+              <Col span={12} order={2}>
+                <ProFormText
+                  name="tableComment"
+                  label="表描述"
+                />
+              </Col>
+            </Row>
+            <Row>
+              <Col span={12} order={1}>
+                <ProFormText
+
+                  name="className"
+                  label="实体类名称"
+                  rules={[
+                    {
+                      required: true,
+                      message: '实体类名称不可为空。',
+                    },
+                  ]}
+                />
+              </Col>
+              <Col span={12} order={2}>
+                <ProFormText name="functionAuthor" label="作者" />
+              </Col>
+            </Row>
+            <Row>
+              <Col span={24}>
+                <ProFormTextArea name="remark" label="备注" />
+              </Col>
+            </Row>
+          </ProForm>
+        </Col>
+      </Row>
+      <Row justify="center">
+        <Col span={4}>
+          <Button
+            type="primary"
+            className={styles.step_buttons}
+            onClick={() => {
+              history.back();
+            }}
+          >
+            返回
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button type="primary" onClick={onValidateForm}>
+            下一步
+          </Button>
+        </Col>
+      </Row>
+    </Fragment>
+  );
+};
+
+export default BaseInfo;
diff --git a/react-ui/src/pages/Tool/Gen/components/ColumnInfo.tsx b/react-ui/src/pages/Tool/Gen/components/ColumnInfo.tsx
new file mode 100644
index 0000000..2db5de4
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/components/ColumnInfo.tsx
@@ -0,0 +1,304 @@
+import React, { Fragment, useEffect, useRef, useState } from 'react';
+import type { GenCodeType } from '../data';
+import { Button, Checkbox, Col, Row, Tag } from 'antd';
+import type { FormInstance } from 'antd';
+import { history } from '@umijs/max';
+import styles from '../style.less';
+import { EditableProTable, ProColumns } from '@ant-design/pro-components';
+
+export type ColumnInfoProps = {
+  parentType?: string;
+  data?: any[];
+  dictData?: any[];
+  onStepSubmit?: any;
+};
+
+const booleanEnum = [
+  {
+    label: 'true',
+    value: '1',
+  },
+  {
+    label: 'false',
+    value: '0',
+  },
+];
+
+const ColumnInfo: React.FC<ColumnInfoProps> = (props) => {
+  const formRef = useRef<FormInstance>();
+
+  const [dataSource, setDataSource] = useState<any[]>();
+
+  const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
+
+  const { data, dictData, onStepSubmit } = props;
+
+  const columns: ProColumns<GenCodeType>[] = [
+    {
+      title: '编号',
+      dataIndex: 'columnId',
+      editable: false,
+      width: 80,
+    },
+    {
+      title: '字段名',
+      dataIndex: 'columnName',
+      editable: false,
+    },
+    {
+      title: '字段描述',
+      dataIndex: 'columnComment',
+      hideInForm: true,
+      hideInSearch: true,
+      width: 200,
+    },
+    {
+      title: '字段类型',
+      dataIndex: 'columnType',
+      editable: false,
+    },
+    {
+      title: 'Java类型',
+      dataIndex: 'javaType',
+      valueType: 'select',
+      valueEnum: {
+        Long: {
+          text: 'Long',
+        },
+        String: {
+          text: 'String',
+        },
+        Integer: {
+          text: 'Integer',
+        },
+        Double: {
+          text: 'Double',
+        },
+        BigDecimal: {
+          text: 'BigDecimal',
+        },
+        Date: {
+          text: 'Date',
+        },
+      },
+    },
+    {
+      title: 'Java属性',
+      dataIndex: 'javaField',
+    },
+    {
+      title: '插入',
+      dataIndex: 'isInsert',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isInsert === '1'} />;
+      },
+    },
+    {
+      title: '编辑',
+      dataIndex: 'isEdit',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isEdit === '1'} />;
+      },
+    },
+    {
+      title: '列表',
+      dataIndex: 'isList',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isList === '1'} />;
+      },
+    },
+    {
+      title: '查询',
+      dataIndex: 'isQuery',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isQuery === '1'} />;
+      },
+    },
+    {
+      title: '查询方式',
+      dataIndex: 'queryType',
+      valueType: 'select',
+      valueEnum: {
+        EQ: {
+          text: '=',
+        },
+        NE: {
+          text: '!=',
+        },
+        GT: {
+          text: '>',
+        },
+        GTE: {
+          text: '>=',
+        },
+        LT: {
+          text: '<',
+        },
+        LTE: {
+          text: '<=',
+        },
+        LIKE: {
+          text: 'LIKE',
+        },
+        BETWEEN: {
+          text: 'BETWEEN',
+        },
+      },
+    },
+    {
+      title: '必填',
+      dataIndex: 'isRequired',
+      valueType: 'select',
+      fieldProps: {
+        options: booleanEnum,
+      },
+      render: (_, record) => {
+        return <Checkbox checked={record.isRequired === '1'} />;
+      },
+    },
+    {
+      title: '显示类型',
+      dataIndex: 'htmlType',
+      hideInSearch: true,
+      valueType: 'select',
+      valueEnum: {
+        input: {
+          text: '文本框',
+        },
+        textarea: {
+          text: '文本域',
+        },
+        select: {
+          text: '下拉框',
+        },
+        radio: {
+          text: '单选框',
+        },
+        checkbox: {
+          text: '复选框',
+        },
+        datetime: {
+          text: '日期控件',
+        },
+        imageUpload: {
+          text: '图片上传',
+        },
+        fileUpload: {
+          text: '文件上传',
+        },
+        editor: {
+          text: '富文本控件',
+        },
+      },
+    },
+    {
+      title: '字典类型',
+      dataIndex: 'dictType',
+      hideInSearch: true,
+      valueType: 'select',
+      fieldProps: {
+        options: dictData,
+      },
+      render: (text) => {
+        return <Tag color="#108ee9">{text}</Tag>;
+      },
+    },
+  ];
+
+  useEffect(() => {
+    setDataSource(data);
+    if (data) {
+      setEditableRowKeys(data.map((item) => item.columnId));
+    }
+  }, [data]);
+
+  const onSubmit = (direction: string) => {
+    if (onStepSubmit) {
+      onStepSubmit('column', dataSource, direction);
+    }
+  };
+
+  const onDataChange = (value: readonly GenCodeType[]) => {
+    setDataSource({ ...value } as []);
+  };
+
+  return (
+    <Fragment>
+      <Row>
+        <Col span={24}>
+          <EditableProTable<GenCodeType>
+            formRef={formRef}
+            rowKey="columnId"
+            search={false}
+            columns={columns}
+            value={dataSource}
+            editable={{
+              type: 'multiple',
+              editableKeys,
+              onChange: setEditableRowKeys,
+              actionRender: (row, config, defaultDoms) => {
+                return [defaultDoms.delete];
+              },
+              onValuesChange: (record, recordList) => {
+                setDataSource(recordList);
+              },
+            }}
+            onChange={onDataChange}
+            recordCreatorProps={false}
+          />
+        </Col>
+      </Row>
+      <Row justify="center">
+        <Col span={4}>
+          <Button
+            type="primary"
+            onClick={() => {
+              history.back();
+            }}
+          >
+            返回
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button
+            type="primary"
+            className={styles.step_buttons}
+            onClick={() => {
+              onSubmit('prev');
+            }}
+          >
+            上一步
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button
+            type="primary"
+            onClick={() => {
+              onSubmit('next');
+            }}
+          >
+            下一步
+          </Button>
+        </Col>
+      </Row>
+    </Fragment>
+  );
+};
+
+export default ColumnInfo;
diff --git a/react-ui/src/pages/Tool/Gen/components/GenInfo.tsx b/react-ui/src/pages/Tool/Gen/components/GenInfo.tsx
new file mode 100644
index 0000000..97901ec
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/components/GenInfo.tsx
@@ -0,0 +1,330 @@
+import { Button, Col, Divider, Form, Row, TreeSelect } from 'antd';
+import React, { Fragment, useEffect, useState } from 'react';
+import { history } from '@umijs/max';
+import type { TableInfo } from '../data';
+import styles from '../style.less';
+import { DataNode } from 'antd/es/tree';
+import { ProForm, ProFormRadio, ProFormSelect, ProFormText } from '@ant-design/pro-components';
+
+export type GenInfoProps = {
+  values?: any;
+  menuData?: DataNode[];
+  tableInfo?: TableInfo[];
+  onStepSubmit?: any;
+};
+
+const GenInfo: React.FC<GenInfoProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const [pathType, setPathType] = useState<string>('0');
+  const [tlpType, setTlpType] = useState<string>('curd');
+
+  const [subTablesColumnOptions, setSubTablesColumnOptions] = useState<any[]>();
+
+  const { menuData, tableInfo, onStepSubmit } = props;
+
+  const tablesOptions = tableInfo?.map((item: any) => {
+    return {
+      value: item.tableName,
+      label: `${item.tableName}:${item.tableComment}`,
+    };
+  });
+
+  if (tableInfo) {
+    for (let index = 0; index < tableInfo?.length; index += 1) {
+      const tbl = tableInfo[index];
+      if (tbl.tableName === props.values.subTableName) {
+        const opts = [];
+        tbl.columns.forEach((item) => {
+          opts.push({
+            value: item.columnName,
+            label: `${item.columnName}: ${item.columnComment}`,
+          });
+        });
+        break;
+      }
+    }
+  }
+
+  const treeColumns = props.values.columns.map((item: any) => {
+    return {
+      value: item.columnName,
+      label: `${item.columnName}: ${item.columnComment}`,
+    };
+  });
+
+  const onSubmit = async (direction: string) => {
+    const values = await form.validateFields();
+    onStepSubmit('gen', values, direction);
+  };
+
+  useEffect(() => {
+    setPathType(props.values.genType);
+    setTlpType(props.values.tplCategory);
+  }, [props.values.genType, props.values.tplCategory]);
+
+  return (
+    <Fragment>
+      <Row>
+        <Col span={24}>
+          <ProForm
+            form={form}
+            onFinish={async () => {
+              const values = await form.validateFields();
+              onStepSubmit('gen', values);
+            }}
+            initialValues={{
+              curd: props.values.curd,
+              tree: props.values.tree,
+              sub: props.values.sub,
+              tplCategory: props.values.tplCategory,
+              packageName: props.values.packageName,
+              moduleName: props.values.moduleName,
+              businessName: props.values.businessName,
+              functionName: props.values.functionName,
+              parentMenuId: props.values.parentMenuId,
+              genType: props.values.genType,
+              genPath: props.values.genPath,
+              treeCode: props.values.treeCode,
+              treeParentCode: props.values.treeParentCode,
+              treeName: props.values.treeName,
+              subTableName: props.values.subTableName,
+              subTableFkName: props.values.subTableFkName,
+            }}
+            submitter={{
+              resetButtonProps: {
+                style: { display: 'none' },
+              },
+              submitButtonProps: {
+                style: { display: 'none' },
+              },
+            }}
+          >
+            <Row gutter={[16, 16]}>
+              <Col span={12} order={1}>
+                <ProFormSelect
+                  fieldProps={{
+                    onChange: (val) => {
+                      setTlpType(val);
+                    },
+                  }}
+                  valueEnum={{
+                    crud: '单表(增删改查)',
+                    tree: '树表(增删改查)',
+                    sub: '主子表(增删改查)',
+                  }}
+                  name="tplCategory"
+                  label="生成模板"
+                  rules={[
+                    {
+                      required: true,
+                      message: '选择类型',
+                    },
+                  ]}
+                />
+              </Col>
+              <Col span={12} order={2}>
+                <ProFormText name="packageName" label="生成包路径" />
+              </Col>
+            </Row>
+            <Row gutter={[16, 16]}>
+              <Col span={12} order={1}>
+                <ProFormText name="moduleName" label="生成模块名" />
+              </Col>
+              <Col span={12} order={2}>
+                <ProFormText name="businessName" label="生成业务名" />
+              </Col>
+            </Row>
+            <Row gutter={[16, 16]}>
+              <Col span={12} order={1}>
+                <ProFormText name="functionName" label="生成功能名" />
+              </Col>
+              <Col span={12} order={2}>
+                <ProForm.Item
+                  labelCol={{ span: 20 }}
+                  name="parentMenuId"
+                  label="父菜单"
+                >
+                  <TreeSelect
+                    style={{ width: '74%' }}
+                    defaultValue={props.values.parentMenuId}
+                    treeData={menuData}
+                    placeholder="请选择父菜单"
+                  />
+                </ProForm.Item>
+              </Col>
+            </Row>
+            <Row gutter={[16, 16]}>
+              <Col span={24}>
+                <ProFormRadio.Group
+                  valueEnum={{
+                    '0': 'zip压缩包',
+                    '1': '自定义路径',
+                  }}
+                  name="genType"
+                  label="生成代码方式"
+                  rules={[
+                    {
+                      required: true,
+                      message: '选择类型',
+                    },
+                  ]}
+                  fieldProps={{
+                    onChange: (e) => {
+                      setPathType(e.target.value);
+                    },
+                  }}
+                />
+              </Col>
+            </Row>
+            <Row gutter={[16, 16]}>
+              <Col span={24} order={1}>
+                <ProFormText
+                  hidden={pathType === '0'}
+                  width="md"
+                  name="genPath"
+                  label="自定义路径"
+                />
+              </Col>
+            </Row>
+            <div hidden={tlpType !== 'tree'}>
+              <Divider orientation="left">其他信息</Divider>
+              <Row gutter={[16, 16]}>
+                <Col span={12} order={1}>
+                  <ProFormSelect
+                    name="treeCode"
+                    label="树编码字段"
+                    options={treeColumns}
+                    rules={[
+                      {
+                        required: tlpType === 'tree',
+                        message: '树编码字段',
+                      },
+                    ]}
+                  />
+                </Col>
+                <Col span={12} order={2}>
+                  <ProFormSelect
+                    name="treeParentCode"
+                    label="树父编码字段"
+                    options={treeColumns}
+                    rules={[
+                      {
+                        required: tlpType === 'tree',
+                        message: '树父编码字段',
+                      },
+                    ]}
+                  />
+                </Col>
+              </Row>
+              <Row gutter={[16, 16]}>
+                <Col span={12} order={1}>
+                  <ProFormSelect
+                    name="treeName"
+                    label="树名称字段"
+                    options={treeColumns}
+                    rules={[
+                      {
+                        required: tlpType === 'tree',
+                        message: '树名称字段',
+                      },
+                    ]}
+                  />
+                </Col>
+              </Row>
+            </div>
+            <div hidden={tlpType !== 'sub'}>
+              <Divider orientation="left">关联信息</Divider>
+              <Row gutter={[16, 16]}>
+                <Col span={12} order={1}>
+                  <ProFormSelect
+                    name="subTableName"
+                    label="关联子表的表名"
+                    options={tablesOptions}
+                    rules={[
+                      {
+                        required: tlpType === 'sub',
+                        message: '关联子表的表名',
+                      },
+                    ]}
+                    fieldProps={{
+                      onChange: (val) => {
+                        form.setFieldsValue({
+                          subTableFkName: '',
+                        });
+                        if (tableInfo) {
+                          for (let index = 0; index < tableInfo?.length; index += 1) {
+                            const tbl = tableInfo[index];
+                            if (tbl.tableName === val) {
+                              const opts: any[] = [];
+                              tbl.columns.forEach((item) => {
+                                opts.push({
+                                  value: item.columnName,
+                                  label: `${item.columnName}:${item.columnComment}`,
+                                });
+                              });
+                              setSubTablesColumnOptions(opts);
+                              break;
+                            }
+                          }
+                        }
+                      },
+                    }}
+                  />
+                </Col>
+                <Col span={12} order={2}>
+                  <ProFormSelect
+                    name="subTableFkName"
+                    options={subTablesColumnOptions}
+                    label="子表关联的外键名"
+                    rules={[
+                      {
+                        required: tlpType === 'sub',
+                        message: '子表关联的外键名',
+                      },
+                    ]}
+                  />
+                </Col>
+              </Row>
+            </div>
+          </ProForm>
+        </Col>
+      </Row>
+      <Row justify="center">
+        <Col span={4}>
+          <Button
+            type="primary"
+            onClick={() => {
+              history.back();
+            }}
+          >
+            返回
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button
+            type="primary"
+            className={styles.step_buttons}
+            onClick={() => {
+              onSubmit('prev');
+            }}
+          >
+            上一步
+          </Button>
+        </Col>
+        <Col span={4}>
+          <Button
+            type="primary"
+            onClick={() => {
+              onSubmit('next');
+            }}
+          >
+            提交
+          </Button>
+        </Col>
+      </Row>
+    </Fragment>
+  );
+};
+
+export default GenInfo;
diff --git a/react-ui/src/pages/Tool/Gen/components/PreviewCode.tsx b/react-ui/src/pages/Tool/Gen/components/PreviewCode.tsx
new file mode 100644
index 0000000..f3611ad
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/components/PreviewCode.tsx
@@ -0,0 +1,50 @@
+import React, { useEffect } from 'react';
+import { useIntl } from '@umijs/max';
+import { Modal, Tabs } from 'antd';
+import type { TabsProps } from 'antd';
+import Highlight from 'react-highlight';
+import 'highlight.js/styles/base16/material.css';
+
+interface PreviewTableProps {
+  open: boolean;
+  data?: any;
+  onHide: () => void;
+}
+
+const PreviewTableCode: React.FC<PreviewTableProps> = (props) => {
+  const intl = useIntl();
+  const panes: any = [];
+  const keys = Object.keys(props.data);
+  keys.forEach((key) => {
+    panes.push({
+        key: key + '1',
+        label: key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm')),
+        children: <Highlight className="java">{props.data[key]}</Highlight>,
+      } as TabsProps);
+  });
+
+  useEffect(() => {}, []);
+
+  return (
+    <Modal
+      width={900}
+      title={intl.formatMessage({
+        id: 'gen.preview',
+        defaultMessage: '预览',
+      })}
+      open={props.open}
+      destroyOnClose
+      footer={false}
+      onOk={() => {
+        props.onHide();
+      }}
+      onCancel={() => {
+        props.onHide();
+      }}
+    >
+      <Tabs defaultActiveKey="1" items={panes}></Tabs>
+    </Modal>
+  );
+};
+
+export default PreviewTableCode;
diff --git a/react-ui/src/pages/Tool/Gen/data.d.ts b/react-ui/src/pages/Tool/Gen/data.d.ts
new file mode 100644
index 0000000..a63ea9d
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/data.d.ts
@@ -0,0 +1,106 @@
+export type GenCodeType = {
+  columnId: string;
+  businessName: string;
+  className: string;
+  createBy: string;
+  createTime: string;
+  crud: string;
+  functionAuthor: string;
+  functionName: string;
+  genPath: string;
+  genType: string;
+  moduleName: string;
+  options: string;
+  packageName: string;
+  params: string;
+  parentMenuId: string;
+  parentMenuName: string;
+  remark: string;
+  status: string;
+  isList: string;
+  isEdit: string;
+  isQuery: string;
+  isInsert: string;
+  isRequired: string;
+  tableComment: string;
+  tableId: string;
+  tableName: string;
+  tplCategory: string;
+  tree: string;
+  treeCode: number;
+  treeName: string;
+  treeParentCode: number;
+  updateBy: string;
+  updateTime: string;
+  columns: any;
+};
+
+export type GenCodePagination = {
+  total: number;
+  pageSize: number;
+  current: number;
+};
+
+export type GenCodeListData = {
+  list: GenCodeType[];
+  pagination: Partial<GenCodePagination>;
+};
+
+export type GenCodeTableListParams = {
+  businessName?: string;
+  className?: string;
+  createBy?: string;
+  createTime?: string;
+  crud?: string;
+  functionAuthor?: string;
+  functionName?: string;
+  genPath?: string;
+  genType?: string;
+  moduleName?: string;
+  isList?: string;
+  isEdit?: string;
+  isQuery?: string;
+  isInsert?: string;
+  isRequired?: string;
+  options?: string;
+  params?: any;
+  packageName?: string;
+  parentMenuId?: string;
+  columns?: any;
+  parentMenuName?: string;
+  remark?: string;
+  tableComment?: string;
+  tableId?: string;
+  tableName?: string;
+  tplCategory?: string;
+  tree?: string;
+  treeCode?: string;
+  treeName?: string;
+  treeParentCode?: string;
+  updateBy?: string;
+  updateTime?: string;
+  pageSize?: string;
+  currentPage?: string;
+  filter?: string;
+  sorter?: string;
+};
+
+export type TableColumnInfo = {
+  columnName: string;
+  columnComment: string;
+};
+
+export type TableInfo = {
+  tableName: string;
+  tableComment: string;
+  columns: TableColumnInfo[];
+};
+
+export type ImportTableListItem = {
+  tableName: string;
+  tableComment: string;
+};
+
+export type ImportTableParams = {
+  tables: string;
+};
diff --git a/react-ui/src/pages/Tool/Gen/edit.tsx b/react-ui/src/pages/Tool/Gen/edit.tsx
new file mode 100644
index 0000000..99a0d1a
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/edit.tsx
@@ -0,0 +1,183 @@
+import React, { useEffect, useState } from 'react';
+import BaseInfo from './components/BaseInfo';
+import { Card, Layout, message, Steps } from 'antd';
+import ColumnInfo from './components/ColumnInfo';
+import GenInfo from './components/GenInfo';
+import { getGenCode, updateData } from './service';
+import { formatTreeData } from '@/utils/tree';
+import styles from './style.less';
+import type { GenCodeType } from './data';
+import { getMenuTree } from '@/services/system/menu';
+import { getDictTypeList } from '@/services/system/dict';
+import queryString from 'query-string';
+import { useLocation } from '@umijs/max';
+
+const { Content } = Layout;
+
+export type GenCodeArgs = {
+  id: string;
+};
+
+const TableList: React.FC = () => {
+  const location = useLocation();
+  const query = queryString.parse(location.search);
+  const { id } = query as GenCodeArgs;
+  const tableId = id;
+
+  const [currentStep, setCurrentStep] = useState<number>(0);
+  const [columnData, setColumnData] = useState<any>([]);
+  const [baseInfoData, setBaseInfoData] = useState<any>([]);
+  const [genInfoData, setGenInfoData] = useState<any>([]);
+  const [menuTree, setMenuTree] = useState<any>([]);
+  const [dictData, setDictData] = useState<any>([]);
+  const [tableInfo, setTableInfo] = useState<any>([]);
+  const [formData, setFormData] = useState<any>([]);
+  const [stepComponent, setStepComponent] = useState<any>([]);
+  const [stepKey, setStepKey] = useState<string>('');
+
+  const getCurrentStepAndComponent = (key?: string) => {
+    if (key === 'base') {
+      return (
+        <BaseInfo
+          values={baseInfoData}
+          // eslint-disable-next-line @typescript-eslint/no-use-before-define
+          onStepSubmit={onNextStep}
+        />
+      );
+    }
+    if (key === 'column') {
+      return (
+        <ColumnInfo
+          data={columnData}
+          dictData={dictData}
+          // eslint-disable-next-line @typescript-eslint/no-use-before-define
+          onStepSubmit={onNextStep}
+        />
+      );
+    }
+    if (key === 'gen') {
+      return (
+        <GenInfo
+          values={genInfoData}
+          menuData={menuTree}
+          tableInfo={tableInfo}
+          // eslint-disable-next-line @typescript-eslint/no-use-before-define
+          onStepSubmit={onNextStep}
+        />
+      );
+    }
+    return null;
+  };
+
+  const onNextStep = (step: string, values: any, direction: string) => {
+    let stepKey = 'base';
+    if (step === 'base') {
+      setStepKey('column');
+      setCurrentStep(1);
+      setFormData(values);
+      setStepComponent(getCurrentStepAndComponent(stepKey));
+    } else if (step === 'column') {
+      if (direction === 'prev') {
+        setStepKey('base');
+        setCurrentStep(0);
+      } else {
+        setStepKey('gen');
+        const tableData: GenCodeType = formData || ({} as GenCodeType);
+        tableData.columns = values;
+        setCurrentStep(2);
+        setFormData(tableData);
+      }
+      setStepComponent(getCurrentStepAndComponent(stepKey));
+    } else if (step === 'gen') {
+      if (direction === 'prev') {
+        setStepKey('column');
+        setCurrentStep(1);
+        setStepComponent(getCurrentStepAndComponent(stepKey));
+      } else {
+        const postData: GenCodeType = {
+          ...formData,
+          ...values,
+          params: values,
+          tableId: tableId,
+        };
+        setFormData(postData);
+        updateData({ ...postData } as GenCodeType).then((res) => {
+          if (res.code === 200) {
+            message.success('提交成功');
+            history.back();
+          } else {
+            message.success('提交失败');
+          }
+        });
+      }
+    }
+  };
+  useEffect(() => {
+    setStepComponent(getCurrentStepAndComponent(stepKey));
+  }, [stepKey]);
+
+  useEffect(() => {
+    getGenCode(tableId).then((res) => {
+      if (res.code === 200) {
+        setBaseInfoData(res.data.info);
+        setColumnData(res.data.rows);
+        setGenInfoData(res.data.info);
+        setTableInfo(res.data.tables);
+        setStepKey('base');
+      } else {
+        message.error(res.msg);
+      }
+    });
+    getMenuTree().then((res) => {
+      if (res.code === 200) {
+        const treeData = formatTreeData(res.data);
+        setMenuTree(treeData);
+      } else {
+        message.error(res.msg);
+      }
+    });
+
+    getDictTypeList().then((res: any) => {
+      if (res.code === 200) {
+        const dicts = res.rows.map((item: any) => {
+          return {
+            label: item.dictName,
+            value: item.dictType,
+          };
+        });
+        setDictData(dicts);
+      } else {
+        message.error(res.msg);
+      }
+    });
+  }, []);
+
+  // const onFinish = (values: any) => {
+  //   console.log('Success:', values);
+  // };
+
+  // const onFinishFailed = (errorInfo: any) => {
+  //   console.log('Failed:', errorInfo);
+  // };
+
+  return (
+    <Content>
+      <Card className={styles.tabsCard} bordered={false}>
+        <Steps current={currentStep} className={styles.steps} items={[
+          {
+            title: '基本信息',
+          },
+          {
+            title: '字段信息',
+          },
+          {
+            title: '生成信息',
+          },
+        ]} />
+        {stepComponent}
+      </Card>
+    </Content>
+  );
+};
+
+export default TableList;
diff --git a/react-ui/src/pages/Tool/Gen/import.tsx b/react-ui/src/pages/Tool/Gen/import.tsx
new file mode 100644
index 0000000..6e2c8df
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/import.tsx
@@ -0,0 +1,107 @@
+import { Button, Card, message, Layout } from 'antd';
+import React, { useState } from 'react';
+import { history, FormattedMessage } from '@umijs/max';
+import { importTables, queryTableList } from './service';
+import type { GenCodeType } from './data.d';
+import { ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, RollbackOutlined } from '@ant-design/icons';
+
+const { Content } = Layout;
+
+const handleImport = async (tables: string) => {
+  const hide = message.loading('正在配置');
+  try {
+    await importTables(tables);
+    hide();
+    message.success('配置成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+const ImportTableList: React.FC = () => {
+  const [selectTables, setSelectTables] = useState<string[]>([]);
+
+  const columns: ProColumns<GenCodeType>[] = [
+    {
+      title: '表名称',
+      dataIndex: 'tableName',
+    },
+    {
+      title: '表描述',
+      dataIndex: 'tableComment',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+  ];
+
+  return (
+    <Content>
+      <Card bordered={false}>
+        <ProTable<GenCodeType>
+          headerTitle="代码生成信息"
+          rowKey="tableName"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="primary"
+              onClick={async () => {
+                if (selectTables.length < 1) {
+                  message.error('请选择要导入的表!');
+                  return;
+                }
+                const success = await handleImport(selectTables.join(','));
+                if (success) {
+                  history.back();
+                }
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="gen.submit" defaultMessage="提交" />
+            </Button>,
+            <Button
+              type="primary"
+              key="goback"
+              onClick={() => {
+                history.back();
+              }}
+            >
+              <RollbackOutlined /> <FormattedMessage id="gen.goback" defaultMessage="返回" />
+            </Button>,
+          ]}
+          request={(params) =>
+            queryTableList({ ...params }).then((res) => {
+              return {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              if (selectedRows && selectedRows.length > 0) {
+                const tables = selectedRows.map((row) => {
+                  return row.tableName;
+                });
+                setSelectTables(tables);
+              }
+            },
+          }}
+        />
+      </Card>
+    </Content>
+  );
+};
+
+export default ImportTableList;
diff --git a/react-ui/src/pages/Tool/Gen/index.tsx b/react-ui/src/pages/Tool/Gen/index.tsx
new file mode 100644
index 0000000..9db00d7
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/index.tsx
@@ -0,0 +1,360 @@
+import { DownloadOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, message, Drawer, Modal, Card, Layout } from 'antd';
+import type { FormInstance } from 'antd';
+import React, { useState, useRef } from 'react';
+import { history, FormattedMessage, useAccess } from '@umijs/max';
+import PreviewForm from './components/PreviewCode';
+import type { GenCodeTableListParams, GenCodeType } from './data.d';
+import {
+  batchGenCode,
+  genCode,
+  previewCode,
+  getGenCodeList,
+  removeData,
+  syncDbInfo,
+} from './service';
+import {
+  ActionType,
+  FooterToolbar,
+  ProColumns,
+  ProDescriptions,
+  ProDescriptionsItemProps,
+  ProTable,
+} from '@ant-design/pro-components';
+
+const { Content } = Layout;
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: GenCodeType[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    await removeData({
+      ids: selectedRows.map((row) => row.tableId),
+    });
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: GenCodeType) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.tableId];
+    await removeData({
+      ids: params,
+    });
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const GenCodeView: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [showDetail, setShowDetail] = useState<boolean>(false);
+  const [showPreview, setShowPreview] = useState<boolean>(false);
+  const [preivewData, setPreivewData] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<GenCodeType>();
+  const [selectedRows, setSelectedRows] = useState<GenCodeType[]>([]);
+
+  const access = useAccess();
+
+  const columns: ProColumns<GenCodeType>[] = [
+    {
+      title: '编号',
+      dataIndex: 'tableId',
+      tip: '编号',
+      render: (dom, entity) => {
+        return (
+          <a
+            onClick={() => {
+              setCurrentRow(entity);
+              setShowDetail(true);
+            }}
+          >
+            {dom}
+          </a>
+        );
+      },
+    },
+    {
+      title: '表名',
+      dataIndex: 'tableName',
+      valueType: 'textarea',
+    },
+    {
+      title: '表描述',
+      dataIndex: 'tableComment',
+      hideInForm: true,
+      hideInSearch: true,
+    },
+    {
+      title: '实体',
+      dataIndex: 'className',
+      valueType: 'textarea',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: '更新时间',
+      dataIndex: 'updateTime',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: '操作',
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="preview"
+          hidden={!access.hasPerms('tool:gen:edit')}
+          onClick={() => {
+            previewCode(record.tableId).then((res) => {
+              if (res.code === 200) {
+                setPreivewData(res.data);
+                setShowPreview(true);
+              } else {
+                message.error('获取数据失败');
+              }
+            });
+          }}
+        >
+          预览
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          key="config"
+          hidden={!access.hasPerms('tool:gen:edit')}
+          onClick={() => {
+            history.push(`/tool/gen/edit?id=${record.tableId}`);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="delete"
+          hidden={!access.hasPerms('tool:gen:del')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除任务',
+              content: '确定删除该任务吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          key="sync"
+          hidden={!access.hasPerms('tool:gen:edit')}
+          onClick={() => {
+            syncDbInfo(record.tableName).then((res) => {
+              if (res.code === 200) {
+                message.success('同步成功');
+              } else {
+                message.error('同步失败');
+              }
+            });
+          }}
+        >
+          同步
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          key="gencode"
+          hidden={!access.hasPerms('tool:gen:edit')}
+          onClick={() => {
+            if (record.genType === '1') {
+              genCode(record.tableName).then((res) => {
+                if (res.code === 200) {
+                  message.success(`成功生成到自定义路径:${record.genPath}`);
+                } else {
+                  message.error(res.msg);
+                }
+              });
+            } else {
+              batchGenCode(record.tableName);
+            }
+          }}
+        >
+          生成代码
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <Content>
+      <Card bordered={false}>
+        <ProTable<GenCodeType>
+          headerTitle="代码生成信息"
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="tableId"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="gen"
+              hidden={!access.hasPerms('tool:gen:edit')}
+              onClick={() => {
+                if (selectedRows.length === 0) {
+                  message.error('请选择要生成的数据');
+                  return;
+                }
+                const tableNames = selectedRows.map((row) => row.tableName);
+                if (selectedRows[0].genType === '1') {
+                  genCode(tableNames.join(',')).then((res) => {
+                    if (res.code === 200) {
+                      message.success(`成功生成到自定义路径:${selectedRows[0].genPath}`);
+                    } else {
+                      message.error(res.msg);
+                    }
+                  });
+                } else {
+                  batchGenCode(tableNames.join(','));
+                }
+              }}
+            >
+              <DownloadOutlined /> <FormattedMessage id="gen.gencode" defaultMessage="生成" />
+            </Button>,
+            <Button
+              type="primary"
+              key="import"
+              hidden={!access.hasPerms('tool:gen:add')}
+              onClick={() => {
+                history.push('/tool/gen/import');
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="gen.import" defaultMessage="导入" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getGenCodeList({ ...params } as GenCodeTableListParams).then((res) => {
+              return {
+                data: res.rows,
+                total: res.rows.length,
+                success: true,
+              };
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+        {selectedRows?.length > 0 && (
+          <FooterToolbar
+            extra={
+              <div>
+                <FormattedMessage id="pages.searchTable.chosen" defaultMessage="已选择" />{' '}
+                <a style={{ fontWeight: 600 }}>{selectedRows.length}</a>{' '}
+                <FormattedMessage id="pages.searchTable.item" defaultMessage="项" />
+              </div>
+            }
+          >
+            <Button
+              key="delete"
+              hidden={!access.hasPerms('tool:gen:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '删除任务',
+                  content: '确定删除该任务吗?',
+                  okText: '确认',
+                  cancelText: '取消',
+                  onOk: async () => {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                });
+              }}
+            >
+              <FormattedMessage id="pages.searchTable.batchDeletion" defaultMessage="批量删除" />
+            </Button>
+          </FooterToolbar>
+        )}
+        <PreviewForm
+          open={showPreview}
+          data={preivewData}
+          onHide={() => {
+            setShowPreview(false);
+          }}
+        />
+        <Drawer
+          width={600}
+          open={showDetail}
+          onClose={() => {
+            setCurrentRow(undefined);
+            setShowDetail(false);
+          }}
+          closable={false}
+        >
+          {currentRow?.tableName && (
+            <ProDescriptions<GenCodeType>
+              column={2}
+              title={currentRow?.tableName}
+              request={async () => ({
+                data: currentRow || {},
+              })}
+              params={{
+                id: currentRow?.tableName,
+              }}
+              columns={columns as ProDescriptionsItemProps<GenCodeType>[]}
+            />
+          )}
+        </Drawer>
+      </Card>
+    </Content>
+  );
+};
+
+export default GenCodeView;
diff --git a/react-ui/src/pages/Tool/Gen/locales/zh-CN.ts b/react-ui/src/pages/Tool/Gen/locales/zh-CN.ts
new file mode 100644
index 0000000..9c3a5d4
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/locales/zh-CN.ts
@@ -0,0 +1,8 @@
+export default {
+  'gen.import': '导入',
+  'gen.title': '表信息',
+  'gen.goback': '返回',
+  'gen.submit': '提交',
+  'gen.gencode': '生成',
+  'gen.preview': '预览',
+};
diff --git a/react-ui/src/pages/Tool/Gen/service.ts b/react-ui/src/pages/Tool/Gen/service.ts
new file mode 100644
index 0000000..0e472ee
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/service.ts
@@ -0,0 +1,106 @@
+import { request } from '@umijs/max';
+import { downLoadZip } from '@/utils/downloadfile';
+import type { GenCodeType, GenCodeTableListParams } from './data.d';
+
+// 查询分页列表
+export async function getGenCodeList(params?: GenCodeTableListParams) {
+  const queryString = new URLSearchParams(params).toString();
+  return request(`/api/code/gen/list?${queryString}`, {
+    data: params,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 查询表信息
+export async function getGenCode(id?: string) {
+  return request(`/api/code/gen/${id}`, {
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 查询数据表信息
+export async function queryTableList(params?: any) {
+  const queryString = new URLSearchParams(params).toString();
+  return request(`/api/code/gen/db/list?${queryString}`, {
+    data: params,
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 导入数据表信息
+export async function importTables(tables?: string) {
+  return request(`/api/code/gen/importTable?tables=${tables}`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 删除
+export async function removeData(params: { ids: string[] }) {
+  return request(`/api/code/gen/${params.ids}`, {
+    method: 'delete',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 添加数据
+export async function addData(params: GenCodeType) {
+  return request('/api/code/gen', {
+    method: 'POST',
+    data: {
+      ...params,
+    },
+  });
+}
+
+// 更新数据
+export async function updateData(params: GenCodeType) {
+  return request('/api/code/gen', {
+    method: 'PUT',
+    data: {
+      ...params,
+    },
+  });
+}
+
+// 更新状态
+export async function syncDbInfo(tableName: string) {
+  return request(`/api/code/gen/synchDb/${tableName}`, {
+    method: 'GET',
+  });
+}
+
+// 生成代码(自定义路径)
+export async function genCode(tableName: string) {
+  return request(`/api/code/gen/genCode/${tableName}`, {
+    method: 'GET',
+  });
+}
+
+// 生成代码(压缩包)
+export async function batchGenCode(tableName: string) {
+  return downLoadZip(`/api/code/gen/batchGenCode?tables=${tableName}`);
+}
+
+// 预览
+export async function previewCode(id: string) {
+  return request(`/api/code/gen/preview/${id}`, {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
diff --git a/react-ui/src/pages/Tool/Gen/style.less b/react-ui/src/pages/Tool/Gen/style.less
new file mode 100644
index 0000000..6999d7a
--- /dev/null
+++ b/react-ui/src/pages/Tool/Gen/style.less
@@ -0,0 +1,4 @@
+.steps:global(.ant-steps) {
+  max-width: 750px;
+  margin: 16px auto;
+}