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;
+}
diff --git a/react-ui/src/pages/Tool/Swagger/index.tsx b/react-ui/src/pages/Tool/Swagger/index.tsx
new file mode 100644
index 0000000..adcc968
--- /dev/null
+++ b/react-ui/src/pages/Tool/Swagger/index.tsx
@@ -0,0 +1,31 @@
+import React, { useEffect } from 'react';
+
+/**
+ *
+ * @author whiteshader@163.com
+ *
+ * */
+
+const CacheInfo: React.FC = () => {
+ useEffect(() => {
+ const frame = document.getElementById('bdIframe');
+ if (frame) {
+ const deviceWidth = document.documentElement.clientWidth;
+ const deviceHeight = document.documentElement.clientHeight;
+ frame.style.width = `${Number(deviceWidth) - 260}px`;
+ frame.style.height = `${Number(deviceHeight) - 120}px`;
+ }
+ });
+
+ return (
+ <div style={{}}>
+ <iframe
+ style={{ width: '100%', border: '0px', height: '100%' }}
+ src={`/api/swagger-ui/index.html`}
+ id="bdIframe"
+ />
+ </div>
+ );
+};
+
+export default CacheInfo;