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

- 完成项目初始化
- 实现用户注册、登录功能
- 完成用户管理与权限管理模块
- 开发后端 Tracker 服务器项目管理接口
- 实现日志管理接口
Change-Id: Ia4bde1c9ff600352a7ff0caca0cc50b02cad1af7
diff --git a/react-ui/src/access.ts b/react-ui/src/access.ts
new file mode 100644
index 0000000..8c11f31
--- /dev/null
+++ b/react-ui/src/access.ts
@@ -0,0 +1,55 @@
+import { checkRole, matchPermission } from './utils/permission';
+/**
+ * @see https://umijs.org/zh-CN/plugins/plugin-access
+ * */
+export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) {
+  const { currentUser } = initialState ?? {};
+  const hasPerms = (perm: string) => {
+    return matchPermission(initialState?.currentUser?.permissions, perm);
+  };
+  const roleFiler = (route: { authority: string[] }) => {
+    return checkRole(initialState?.currentUser?.roles, route.authority);
+  };
+  return {
+    canAdmin: currentUser && currentUser.access === 'admin',
+    hasPerms,
+    roleFiler,
+  };
+}
+
+export function setSessionToken(
+  access_token: string | undefined,
+  refresh_token: string | undefined,
+  expireTime: number,
+): void {
+  if (access_token) {
+    localStorage.setItem('access_token', access_token);
+  } else {
+    localStorage.removeItem('access_token');
+  }
+  if (refresh_token) {
+    localStorage.setItem('refresh_token', refresh_token);
+  } else {
+    localStorage.removeItem('refresh_token');
+  }
+  localStorage.setItem('expireTime', `${expireTime}`);
+}
+
+export function getAccessToken() {
+  return localStorage.getItem('access_token');
+}
+
+export function getRefreshToken() {
+  return localStorage.getItem('refresh_token');
+}
+
+export function getTokenExpireTime() {
+  return localStorage.getItem('expireTime');
+}
+
+export function clearSessionToken() {
+  sessionStorage.removeItem('user');
+  localStorage.removeItem('access_token');
+  localStorage.removeItem('refresh_token');
+  localStorage.removeItem('expireTime');
+}
diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx
new file mode 100644
index 0000000..38f76b4
--- /dev/null
+++ b/react-ui/src/app.tsx
@@ -0,0 +1,233 @@
+import { Footer, Question, SelectLang, AvatarDropdown, AvatarName } from '@/components';
+import { LinkOutlined } from '@ant-design/icons';
+import type { Settings as LayoutSettings } from '@ant-design/pro-components';
+import { SettingDrawer } from '@ant-design/pro-components';
+import type { RunTimeLayoutConfig } from '@umijs/max';
+import { history, Link } from '@umijs/max';
+import defaultSettings from '../config/defaultSettings';
+import { errorConfig } from './requestErrorConfig';
+import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access';
+import { getRemoteMenu, getRoutersInfo, getUserInfo, patchRouteWithRemoteMenus, setRemoteMenu } from './services/session';
+import { PageEnum } from './enums/pagesEnums';
+
+
+const isDev = process.env.NODE_ENV === 'development';
+
+
+
+/**
+ * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
+ * */
+export async function getInitialState(): Promise<{
+  settings?: Partial<LayoutSettings>;
+  currentUser?: API.CurrentUser;
+  loading?: boolean;
+  fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
+}> {
+  const fetchUserInfo = async () => {
+    try {
+      const response = await getUserInfo({
+        skipErrorHandler: true,
+      });
+      if (response.user.avatar === '') {
+        response.user.avatar =
+          'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png';
+      }
+      return {
+        ...response.user,
+        permissions: response.permissions,
+        roles: response.roles,
+      } as API.CurrentUser;
+    } catch (error) {
+      console.log(error);
+      history.push(PageEnum.LOGIN);
+    }
+    return undefined;
+  };
+  // 如果不是登录页面,执行
+  const { location } = history;
+  if (location.pathname !== PageEnum.LOGIN) {
+    const currentUser = await fetchUserInfo();
+    return {
+      fetchUserInfo,
+      currentUser,
+      settings: defaultSettings as Partial<LayoutSettings>,
+    };
+  }
+  return {
+    fetchUserInfo,
+    settings: defaultSettings as Partial<LayoutSettings>,
+  };
+}
+
+// ProLayout 支持的api https://procomponents.ant.design/components/layout
+export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
+  return {
+    actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />],
+    avatarProps: {
+      src: initialState?.currentUser?.avatar,
+      title: <AvatarName />,
+      render: (_, avatarChildren) => {
+        return <AvatarDropdown menu="True">{avatarChildren}</AvatarDropdown>;
+      },
+    },
+    waterMarkProps: {
+      // content: initialState?.currentUser?.nickName,
+    },
+    menu: {
+      locale: false,
+      // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
+      params: {
+        userId: initialState?.currentUser?.userId,
+      },
+      request: async () => {
+        if (!initialState?.currentUser?.userId) {
+          return [];
+        }
+        return getRemoteMenu();
+      },
+    },
+    footerRender: () => <Footer />,
+    onPageChange: () => {
+      const { location } = history;
+      // 如果没有登录,重定向到 login
+      if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) {
+        history.push(PageEnum.LOGIN);
+      }
+    },
+    layoutBgImgList: [
+      {
+        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
+        left: 85,
+        bottom: 100,
+        height: '303px',
+      },
+      {
+        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
+        bottom: -68,
+        right: -45,
+        height: '303px',
+      },
+      {
+        src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
+        bottom: 0,
+        left: 0,
+        width: '331px',
+      },
+    ],
+    links: isDev
+      ? [
+        <Link key="openapi" to="/umi/plugin/openapi" target="_blank">
+          <LinkOutlined />
+          <span>OpenAPI 文档</span>
+        </Link>,
+      ]
+      : [],
+    menuHeaderRender: undefined,
+    // 自定义 403 页面
+    // unAccessible: <div>unAccessible</div>,
+    // 增加一个 loading 的状态
+    childrenRender: (children) => {
+      // if (initialState?.loading) return <PageLoading />;
+      return (
+        <>
+          {children}
+          <SettingDrawer
+            disableUrlParams
+            enableDarkTheme
+            settings={initialState?.settings}
+            onSettingChange={(settings) => {
+              setInitialState((preInitialState) => ({
+                ...preInitialState,
+                settings,
+              }));
+            }}
+          />
+        </>
+      );
+    },
+    ...initialState?.settings,
+  };
+};
+
+export async function onRouteChange({ clientRoutes, location }) {
+  const menus = getRemoteMenu();
+ // console.log('onRouteChange', clientRoutes, location, menus);
+  if(menus === null && location.pathname !== PageEnum.LOGIN) {
+    console.log('refresh')
+    history.go(0);
+  }
+}
+
+// export function patchRoutes({ routes, routeComponents }) {
+//   console.log('patchRoutes', routes, routeComponents);
+// }
+
+
+export async function patchClientRoutes({ routes }) {
+  // console.log('patchClientRoutes', routes);
+  patchRouteWithRemoteMenus(routes);
+}
+
+export function render(oldRender: () => void) {
+  // console.log('render get routers', oldRender)
+  const token = getAccessToken();
+  if(!token || token?.length === 0) {
+    oldRender();
+    return;
+  }
+  getRoutersInfo().then(res => {
+    setRemoteMenu(res);
+    oldRender()
+  });
+}
+
+/**
+ * @name request 配置,可以配置错误处理
+ * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
+ * @doc https://umijs.org/docs/max/request#配置
+ */
+const checkRegion = 5 * 60 * 1000;
+
+export const request = {
+  ...errorConfig,
+  requestInterceptors: [
+    (url: any, options: { headers: any }) => {
+      const headers = options.headers ? options.headers : [];
+      console.log('request ====>:', url);
+      const authHeader = headers['Authorization'];
+      const isToken = headers['isToken'];
+      if (!authHeader && isToken !== false) {
+        const expireTime = getTokenExpireTime();
+        if (expireTime) {
+          const left = Number(expireTime) - new Date().getTime();
+          const refreshToken = getRefreshToken();
+          if (left < checkRegion && refreshToken) {
+            if (left < 0) {
+              clearSessionToken();
+            }
+          } else {
+            const accessToken = getAccessToken();
+            if (accessToken) {
+              headers['Authorization'] = `Bearer ${accessToken}`;
+            }
+          }
+        } else {
+          clearSessionToken();
+        }
+      }
+      return { url, options };
+    },
+  ],
+  responseInterceptors: [
+    // (response) =>
+    // {
+    //   // // 不再需要异步处理读取返回体内容,可直接在data中读出,部分字段可在 config 中找到
+    //   // const { data = {} as any, config } = response;
+    //   // // do something
+    //   // console.log('data: ', data)
+    //   // console.log('config: ', config)
+    //   return response
+    // },
+  ],
+};
diff --git a/react-ui/src/components/DictTag/index.tsx b/react-ui/src/components/DictTag/index.tsx
new file mode 100644
index 0000000..2b2a791
--- /dev/null
+++ b/react-ui/src/components/DictTag/index.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { Tag } from 'antd';
+import { ProSchemaValueEnumType } from '@ant-design/pro-components';
+import { DefaultOptionType } from 'antd/es/select';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/10
+ *
+ * */
+
+export interface DictValueEnumType extends ProSchemaValueEnumType {
+    id?: string | number;
+    key?: string | number;
+    value: string | number;
+    label: string;
+    listClass?: string;
+}
+
+export interface DictOptionType extends DefaultOptionType {
+    id?: string | number;
+    key?: string | number;
+    text: string;
+    listClass?: string;
+}
+
+
+export type DictValueEnumObj = Record<string | number, DictValueEnumType>;
+
+export type DictTagProps = {
+    key?: string;
+    value?: string | number;
+    enums?: DictValueEnumObj;
+    options?: DictOptionType[];
+};
+
+const DictTag: React.FC<DictTagProps> = (props) => {
+    function getDictColor(type?: string) {
+        switch (type) {
+            case 'primary':
+                return 'blue';
+            case 'success':
+                return 'success';
+            case 'info':
+                return 'green';
+            case 'warning':
+                return 'warning';
+            case 'danger':
+                return 'error';
+            case 'default':
+            default:
+                return 'default';
+        }
+    }
+
+    function getDictLabelByValue(value: string | number | undefined): string {
+        if (value === undefined) {
+            return '';
+        }
+        if (props.enums) {
+            const item = props.enums[value];
+            return item.label;
+        }
+        if (props.options) {
+            if (!Array.isArray(props.options)) {
+                console.log('DictTag options is no array!')
+                return '';
+            }
+            for (const item of props.options) {
+                if (item.value === value) {
+                    return item.text;
+                }
+            }
+        }
+        return String(props.value);
+    }
+
+    function getDictListClassByValue(value: string | number | undefined): string {
+        if (value === undefined) {
+            return 'default';
+        }
+        if (props.enums) {
+            const item = props.enums[value];
+            return item.listClass || 'default';
+        }
+        if (props.options) {
+            if (!Array.isArray(props.options)) {
+                console.log('DictTag options is no array!')
+                return 'default';
+            }
+            for (const item of props.options) {
+                if (item.value === value) {
+                    return item.listClass || 'default';
+                }
+            }
+        }
+        return String(props.value);
+    }
+
+    const getTagColor = () => {
+        return getDictColor(getDictListClassByValue(props.value).toLowerCase());
+    };
+
+    const getTagText = (): string => {
+        return getDictLabelByValue(props.value);
+    };
+
+    return (
+        <Tag color={getTagColor()}>{getTagText()}</Tag>
+    )
+}
+
+
+export default DictTag;
\ No newline at end of file
diff --git a/react-ui/src/components/Footer/index.tsx b/react-ui/src/components/Footer/index.tsx
new file mode 100644
index 0000000..f204ac2
--- /dev/null
+++ b/react-ui/src/components/Footer/index.tsx
@@ -0,0 +1,35 @@
+import { GithubOutlined } from '@ant-design/icons';
+import { DefaultFooter } from '@ant-design/pro-components';
+import React from 'react';
+
+const Footer: React.FC = () => {
+  return (
+    <DefaultFooter
+      style={{
+        background: 'none',
+      }}
+      links={[
+        {
+          key: 'Ant Design Pro',
+          title: 'Ant Design Pro',
+          href: 'https://pro.ant.design',
+          blankTarget: true,
+        },
+        {
+          key: 'github',
+          title: <GithubOutlined />,
+          href: 'https://github.com/ant-design/ant-design-pro',
+          blankTarget: true,
+        },
+        {
+          key: 'Ant Design',
+          title: 'Ant Design',
+          href: 'https://ant.design',
+          blankTarget: true,
+        },
+      ]}
+    />
+  );
+};
+
+export default Footer;
diff --git a/react-ui/src/components/HeaderDropdown/index.tsx b/react-ui/src/components/HeaderDropdown/index.tsx
new file mode 100644
index 0000000..f89052d
--- /dev/null
+++ b/react-ui/src/components/HeaderDropdown/index.tsx
@@ -0,0 +1,27 @@
+import { Dropdown } from 'antd';
+import type { DropDownProps } from 'antd/es/dropdown';
+import React from 'react';
+import { createStyles } from 'antd-style';
+import classNames from 'classnames';
+
+const useStyles = createStyles(({ token }) => {
+  return {
+    dropdown: {
+      [`@media screen and (max-width: ${token.screenXS}px)`]: {
+        width: '100%',
+      },
+    },
+  };
+});
+
+export type HeaderDropdownProps = {
+  overlayClassName?: string;
+  placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
+} & Omit<DropDownProps, 'overlay'>;
+
+const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => {
+  const { styles } = useStyles();
+  return <Dropdown overlayClassName={classNames(styles.dropdown, cls)} {...restProps} />;
+};
+
+export default HeaderDropdown;
diff --git a/react-ui/src/components/IconSelector/Category.tsx b/react-ui/src/components/IconSelector/Category.tsx
new file mode 100644
index 0000000..dd0e93f
--- /dev/null
+++ b/react-ui/src/components/IconSelector/Category.tsx
@@ -0,0 +1,63 @@
+import * as React from 'react';
+import CopyableIcon from './CopyableIcon';
+import type { ThemeType } from './index';
+import type { CategoriesKeys } from './fields';
+import { useIntl } from '@umijs/max';
+import styles from './style.less';
+
+interface CategoryProps {
+  title: CategoriesKeys;
+  icons: string[];
+  theme: ThemeType;
+  newIcons: string[];
+  onSelect: (type: string, name: string) => any;
+}
+
+const Category: React.FC<CategoryProps> = props => {
+
+  const { icons, title, newIcons, theme } = props;
+  const intl = useIntl();
+  const [justCopied, setJustCopied] = React.useState<string | null>(null);
+  const copyId = React.useRef<NodeJS.Timeout | null>(null);
+  const onSelect = React.useCallback((type: string, text: string) => {
+    const { onSelect } = props;
+    if (onSelect) {
+      onSelect(type, text);
+    }
+    setJustCopied(type);
+    copyId.current = setTimeout(() => {
+      setJustCopied(null);
+    }, 2000);
+  }, []);
+  React.useEffect(
+    () => () => {
+      if (copyId.current) {
+        clearTimeout(copyId.current);
+      }
+    },
+    [],
+  );
+
+  return (
+    <div>
+      <h4>{intl.formatMessage({
+        id: `app.docs.components.icon.category.${title}`,
+        defaultMessage: '信息',
+      })}</h4>
+      <ul className={styles.anticonsList}>
+        {icons.map(name => (
+          <CopyableIcon
+            key={name}
+            name={name}
+            theme={theme}
+            isNew={newIcons.includes(name)}
+            justCopied={justCopied}
+            onSelect={onSelect}
+          />
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export default Category;
diff --git a/react-ui/src/components/IconSelector/CopyableIcon.tsx b/react-ui/src/components/IconSelector/CopyableIcon.tsx
new file mode 100644
index 0000000..371cba0
--- /dev/null
+++ b/react-ui/src/components/IconSelector/CopyableIcon.tsx
@@ -0,0 +1,47 @@
+import * as React from 'react';
+import { Tooltip } from 'antd';
+import classNames from 'classnames';
+import * as AntdIcons from '@ant-design/icons';
+import type { ThemeType } from './index';
+import styles from './style.less';
+
+const allIcons: {
+  [key: string]: any;
+} = AntdIcons;
+
+export interface CopyableIconProps {
+  name: string;
+  isNew: boolean;
+  theme: ThemeType;
+  justCopied: string | null;
+  onSelect: (type: string, text: string) => any;
+}
+
+const CopyableIcon: React.FC<CopyableIconProps> = ({
+  name,
+  justCopied,
+  onSelect,
+  theme,
+}) => {
+  const className = classNames({
+    copied: justCopied === name,
+    [theme]: !!theme,
+  });
+  return (
+    <li className={className}
+      onClick={() => {
+        if (onSelect) {
+          onSelect(theme, name);
+        }
+      }}>
+      <Tooltip title={name}>
+        {React.createElement(allIcons[name], { className: styles.anticon })}
+      </Tooltip>
+      {/* <span className={styles.anticonClass}>
+          <Badge dot={isNew}>{name}</Badge>
+        </span> */}
+    </li>
+  );
+};
+
+export default CopyableIcon;
diff --git a/react-ui/src/components/IconSelector/IconPicSearcher.tsx b/react-ui/src/components/IconSelector/IconPicSearcher.tsx
new file mode 100644
index 0000000..3a4cf01
--- /dev/null
+++ b/react-ui/src/components/IconSelector/IconPicSearcher.tsx
@@ -0,0 +1,233 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { Upload, Tooltip, Popover, Modal, Progress, Spin, Result } from 'antd';
+import * as AntdIcons from '@ant-design/icons';
+import { useIntl } from '@umijs/max';
+import './style.less';
+
+const allIcons: { [key: string]: any } = AntdIcons;
+
+const { Dragger } = Upload;
+interface AntdIconClassifier {
+  load: () => void;
+  predict: (imgEl: HTMLImageElement) => void;
+}
+declare global {
+  interface Window {
+    antdIconClassifier: AntdIconClassifier;
+  }
+}
+
+interface PicSearcherState {
+  loading: boolean;
+  modalOpen: boolean;
+  popoverVisible: boolean;
+  icons: iconObject[];
+  fileList: any[];
+  error: boolean;
+  modelLoaded: boolean;
+}
+
+interface iconObject {
+  type: string;
+  score: number;
+}
+
+const PicSearcher: React.FC = () => {
+  const intl = useIntl();
+  const {formatMessage} = intl;
+  const [state, setState] = useState<PicSearcherState>({
+    loading: false,
+    modalOpen: false,
+    popoverVisible: false,
+    icons: [],
+    fileList: [],
+    error: false,
+    modelLoaded: false,
+  });
+  const predict = (imgEl: HTMLImageElement) => {
+    try {
+      let icons: any[] = window.antdIconClassifier.predict(imgEl);
+      if (gtag && icons.length) {
+        gtag('event', 'icon', {
+          event_category: 'search-by-image',
+          event_label: icons[0].className,
+        });
+      }
+      icons = icons.map(i => ({ score: i.score, type: i.className.replace(/\s/g, '-') }));
+      setState(prev => ({ ...prev, loading: false, error: false, icons }));
+    } catch {
+      setState(prev => ({ ...prev, loading: false, error: true }));
+    }
+  };
+  // eslint-disable-next-line class-methods-use-this
+  const toImage = (url: string) =>
+    new Promise(resolve => {
+      const img = new Image();
+      img.setAttribute('crossOrigin', 'anonymous');
+      img.src = url;
+      img.onload = () => {
+        resolve(img);
+      };
+    });
+
+  const uploadFile = useCallback((file: File) => {
+    setState(prev => ({ ...prev, loading: true }));
+    const reader = new FileReader();
+    reader.onload = () => {
+      toImage(reader.result as string).then(predict);
+      setState(prev => ({
+        ...prev,
+        fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }],
+      }));
+    };
+    reader.readAsDataURL(file);
+  }, []);
+
+  const onPaste = useCallback((event: ClipboardEvent) => {
+    const items = event.clipboardData && event.clipboardData.items;
+    let file = null;
+    if (items && items.length) {
+      for (let i = 0; i < items.length; i++) {
+        if (items[i].type.includes('image')) {
+          file = items[i].getAsFile();
+          break;
+        }
+      }
+    }
+    if (file) {
+      uploadFile(file);
+    }
+  }, []);
+  const toggleModal = useCallback(() => {
+    setState(prev => ({
+      ...prev,
+      modalOpen: !prev.modalOpen,
+      popoverVisible: false,
+      fileList: [],
+      icons: [],
+    }));
+    if (!localStorage.getItem('disableIconTip')) {
+      localStorage.setItem('disableIconTip', 'true');
+    }
+  }, []);
+
+  useEffect(() => {
+    const script = document.createElement('script');
+    script.onload = async () => {
+      await window.antdIconClassifier.load();
+      setState(prev => ({ ...prev, modelLoaded: true }));
+      document.addEventListener('paste', onPaste);
+    };
+    script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js';
+    document.head.appendChild(script);
+    setState(prev => ({ ...prev, popoverVisible: !localStorage.getItem('disableIconTip') }));
+    return () => {
+      document.removeEventListener('paste', onPaste);
+    };
+  }, []);
+
+  return (
+    <div className="iconPicSearcher">
+      <Popover
+        content={formatMessage({id: 'app.docs.components.icon.pic-searcher.intro'})}
+        open={state.popoverVisible}
+      >
+        <AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
+      </Popover>
+      <Modal
+        title={intl.formatMessage({
+          id: 'app.docs.components.icon.pic-searcher.title',
+          defaultMessage: '信息',
+        })}
+        open={state.modalOpen}
+        onCancel={toggleModal}
+        footer={null}
+      >
+        {state.modelLoaded || (
+          <Spin
+            spinning={!state.modelLoaded}
+            tip={formatMessage({
+              id: 'app.docs.components.icon.pic-searcher.modelloading',
+
+            })}
+          >
+            <div style={{ height: 100 }} />
+          </Spin>
+        )}
+        {state.modelLoaded && (
+          <Dragger
+            accept="image/jpeg, image/png"
+            listType="picture"
+            customRequest={o => uploadFile(o.file as File)}
+            fileList={state.fileList}
+            showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
+          >
+            <p className="ant-upload-drag-icon">
+              <AntdIcons.InboxOutlined />
+            </p>
+            <p className="ant-upload-text">
+              {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-text'})}
+            </p>
+            <p className="ant-upload-hint">
+              {formatMessage({id: 'app.docs.components.icon.pic-searcher.upload-hint'})}
+            </p>
+          </Dragger>
+        )}
+        <Spin
+          spinning={state.loading}
+          tip={formatMessage({id: 'app.docs.components.icon.pic-searcher.matching'})}
+        >
+          <div className="icon-pic-search-result">
+            {state.icons.length > 0 && (
+              <div className="result-tip">
+                {formatMessage({id: 'app.docs.components.icon.pic-searcher.result-tip'})}
+              </div>
+            )}
+            <table>
+              {state.icons.length > 0 && (
+                <thead>
+                  <tr>
+                    <th className="col-icon">
+                      {formatMessage({id: 'app.docs.components.icon.pic-searcher.th-icon'})}
+                    </th>
+                    <th>{formatMessage({id: 'app.docs.components.icon.pic-searcher.th-score'})}</th>
+                  </tr>
+                </thead>
+              )}
+              <tbody>
+                {state.icons.map(icon => {
+                  const { type } = icon;
+                  const iconName = `${type
+                    .split('-')
+                    .map(str => `${str[0].toUpperCase()}${str.slice(1)}`)
+                    .join('')}Outlined`;
+                  return (
+                    <tr key={iconName}>
+                      <td className="col-icon">
+                          <Tooltip title={icon.type} placement="right">
+                            {React.createElement(allIcons[iconName])}
+                          </Tooltip>
+                      </td>
+                      <td>
+                        <Progress percent={Math.ceil(icon.score * 100)} />
+                      </td>
+                    </tr>
+                  );
+                })}
+              </tbody>
+            </table>
+            {state.error && (
+              <Result
+                status="500"
+                title="503"
+                subTitle={formatMessage({id: 'app.docs.components.icon.pic-searcher.server-error'})}
+              />
+            )}
+          </div>
+        </Spin>
+      </Modal>
+    </div>
+  );
+};
+
+export default PicSearcher;
diff --git a/react-ui/src/components/IconSelector/fields.ts b/react-ui/src/components/IconSelector/fields.ts
new file mode 100644
index 0000000..de37e67
--- /dev/null
+++ b/react-ui/src/components/IconSelector/fields.ts
@@ -0,0 +1,223 @@
+import * as AntdIcons from '@ant-design/icons/lib/icons';
+
+const all = Object.keys(AntdIcons)
+  .map(n => n.replace(/(Outlined|Filled|TwoTone)$/, ''))
+  .filter((n, i, arr) => arr.indexOf(n) === i);
+
+const direction = [
+  'StepBackward',
+  'StepForward',
+  'FastBackward',
+  'FastForward',
+  'Shrink',
+  'ArrowsAlt',
+  'Down',
+  'Up',
+  'Left',
+  'Right',
+  'CaretUp',
+  'CaretDown',
+  'CaretLeft',
+  'CaretRight',
+  'UpCircle',
+  'DownCircle',
+  'LeftCircle',
+  'RightCircle',
+  'DoubleRight',
+  'DoubleLeft',
+  'VerticalLeft',
+  'VerticalRight',
+  'VerticalAlignTop',
+  'VerticalAlignMiddle',
+  'VerticalAlignBottom',
+  'Forward',
+  'Backward',
+  'Rollback',
+  'Enter',
+  'Retweet',
+  'Swap',
+  'SwapLeft',
+  'SwapRight',
+  'ArrowUp',
+  'ArrowDown',
+  'ArrowLeft',
+  'ArrowRight',
+  'PlayCircle',
+  'UpSquare',
+  'DownSquare',
+  'LeftSquare',
+  'RightSquare',
+  'Login',
+  'Logout',
+  'MenuFold',
+  'MenuUnfold',
+  'BorderBottom',
+  'BorderHorizontal',
+  'BorderInner',
+  'BorderOuter',
+  'BorderLeft',
+  'BorderRight',
+  'BorderTop',
+  'BorderVerticle',
+  'PicCenter',
+  'PicLeft',
+  'PicRight',
+  'RadiusBottomleft',
+  'RadiusBottomright',
+  'RadiusUpleft',
+  'RadiusUpright',
+  'Fullscreen',
+  'FullscreenExit',
+];
+
+const suggestion = [
+  'Question',
+  'QuestionCircle',
+  'Plus',
+  'PlusCircle',
+  'Pause',
+  'PauseCircle',
+  'Minus',
+  'MinusCircle',
+  'PlusSquare',
+  'MinusSquare',
+  'Info',
+  'InfoCircle',
+  'Exclamation',
+  'ExclamationCircle',
+  'Close',
+  'CloseCircle',
+  'CloseSquare',
+  'Check',
+  'CheckCircle',
+  'CheckSquare',
+  'ClockCircle',
+  'Warning',
+  'IssuesClose',
+  'Stop',
+];
+
+const editor = [
+  'Edit',
+  'Form',
+  'Copy',
+  'Scissor',
+  'Delete',
+  'Snippets',
+  'Diff',
+  'Highlight',
+  'AlignCenter',
+  'AlignLeft',
+  'AlignRight',
+  'BgColors',
+  'Bold',
+  'Italic',
+  'Underline',
+  'Strikethrough',
+  'Redo',
+  'Undo',
+  'ZoomIn',
+  'ZoomOut',
+  'FontColors',
+  'FontSize',
+  'LineHeight',
+  'Dash',
+  'SmallDash',
+  'SortAscending',
+  'SortDescending',
+  'Drag',
+  'OrderedList',
+  'UnorderedList',
+  'RadiusSetting',
+  'ColumnWidth',
+  'ColumnHeight',
+];
+
+const data = [
+  'AreaChart',
+  'PieChart',
+  'BarChart',
+  'DotChart',
+  'LineChart',
+  'RadarChart',
+  'HeatMap',
+  'Fall',
+  'Rise',
+  'Stock',
+  'BoxPlot',
+  'Fund',
+  'Sliders',
+];
+
+const logo = [
+  'Android',
+  'Apple',
+  'Windows',
+  'Ie',
+  'Chrome',
+  'Github',
+  'Aliwangwang',
+  'Dingding',
+  'WeiboSquare',
+  'WeiboCircle',
+  'TaobaoCircle',
+  'Html5',
+  'Weibo',
+  'Twitter',
+  'Wechat',
+  'Youtube',
+  'AlipayCircle',
+  'Taobao',
+  'Skype',
+  'Qq',
+  'MediumWorkmark',
+  'Gitlab',
+  'Medium',
+  'Linkedin',
+  'GooglePlus',
+  'Dropbox',
+  'Facebook',
+  'Codepen',
+  'CodeSandbox',
+  'CodeSandboxCircle',
+  'Amazon',
+  'Google',
+  'CodepenCircle',
+  'Alipay',
+  'AntDesign',
+  'AntCloud',
+  'Aliyun',
+  'Zhihu',
+  'Slack',
+  'SlackSquare',
+  'Behance',
+  'BehanceSquare',
+  'Dribbble',
+  'DribbbleSquare',
+  'Instagram',
+  'Yuque',
+  'Alibaba',
+  'Yahoo',
+  'Reddit',
+  'Sketch',
+  'WhatsApp',
+  'Dingtalk',
+];
+
+const datum = [...direction, ...suggestion, ...editor, ...data, ...logo];
+
+const other = all.filter(n => !datum.includes(n));
+
+export const categories = {
+  direction,
+  suggestion,
+  editor,
+  data,
+  logo,
+  other,
+};
+
+export default categories;
+
+export type Categories = typeof categories;
+export type CategoriesKeys = keyof Categories;
diff --git a/react-ui/src/components/IconSelector/index.tsx b/react-ui/src/components/IconSelector/index.tsx
new file mode 100644
index 0000000..78dc931
--- /dev/null
+++ b/react-ui/src/components/IconSelector/index.tsx
@@ -0,0 +1,142 @@
+import * as React from 'react';
+import Icon, * as AntdIcons from '@ant-design/icons';
+import { Radio, Input, Empty } from 'antd';
+import type { RadioChangeEvent } from 'antd/es/radio/interface';
+import debounce from 'lodash/debounce';
+import Category from './Category';
+import IconPicSearcher from './IconPicSearcher';
+import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
+import type { CategoriesKeys } from './fields';
+import { categories } from './fields';
+// import { useIntl } from '@umijs/max';
+
+export enum ThemeType {
+  Filled = 'Filled',
+  Outlined = 'Outlined',
+  TwoTone = 'TwoTone',
+}
+
+const allIcons: { [key: string]: any } = AntdIcons;
+
+interface IconSelectorProps {
+  //intl: any;
+  onSelect: any;
+}
+
+interface IconSelectorState {
+  theme: ThemeType;
+  searchKey: string;
+}
+
+const IconSelector: React.FC<IconSelectorProps> = (props) => {
+  // const intl = useIntl();
+  // const { messages } = intl;
+  const { onSelect } = props;
+  const [displayState, setDisplayState] = React.useState<IconSelectorState>({
+    theme: ThemeType.Outlined,
+    searchKey: '',
+  });
+
+  const newIconNames: string[] = [];
+
+  const handleSearchIcon = React.useCallback(
+    debounce((searchKey: string) => {
+      setDisplayState(prevState => ({ ...prevState, searchKey }));
+    }),
+    [],
+  );
+
+  const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => {
+    setDisplayState(prevState => ({ ...prevState, theme: e.target.value as ThemeType }));
+  }, []);
+
+  const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
+    const { searchKey = '', theme } = displayState;
+
+    const categoriesResult = Object.keys(categories)
+      .map((key: CategoriesKeys) => {
+        let iconList = categories[key];
+        if (searchKey) {
+          const matchKey = searchKey
+            // eslint-disable-next-line prefer-regex-literals
+            .replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name)
+            .replace(/(Filled|Outlined|TwoTone)$/, '')
+            .toLowerCase();
+          iconList = iconList.filter((iconName:string) => iconName.toLowerCase().includes(matchKey));
+        }
+
+        // CopyrightCircle is same as Copyright, don't show it
+        iconList = iconList.filter((icon:string) => icon !== 'CopyrightCircle');
+
+        return {
+          category: key,
+          icons: iconList.map((iconName:string) => iconName + theme).filter((iconName:string) => allIcons[iconName]),
+        };
+      })
+      .filter(({ icons }) => !!icons.length)
+      .map(({ category, icons }) => (
+        <Category
+          key={category}
+          title={category as CategoriesKeys}
+          theme={theme}
+          icons={icons}
+          newIcons={newIconNames}
+          onSelect={(type, name) => {
+            if (onSelect) {
+              onSelect(name, allIcons[name]);
+            }
+          }}
+        />
+      ));
+    return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
+  }, [displayState.searchKey, displayState.theme]);
+  return (
+    <>
+      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+        <Radio.Group
+          value={displayState.theme}
+          onChange={handleChangeTheme}
+          size="large"
+          optionType="button"
+          buttonStyle="solid"
+          options={[
+            {
+              label:  <Icon component={OutlinedIcon} />,
+              value: ThemeType.Outlined
+            },
+            {
+              label: <Icon component={FilledIcon} />,
+              value: ThemeType.Filled
+            },
+            {
+              label: <Icon component={TwoToneIcon} />,
+              value: ThemeType.TwoTone
+            },
+          ]}
+        >
+          {/* <Radio.Button value={ThemeType.Outlined}>
+            <Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
+          </Radio.Button>
+          <Radio.Button value={ThemeType.Filled}>
+            <Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
+          </Radio.Button>
+          <Radio.Button value={ThemeType.TwoTone}>
+            <Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
+          </Radio.Button> */}
+        </Radio.Group>
+        <Input.Search
+          // placeholder={messages['app.docs.components.icon.search.placeholder']}
+          style={{ margin: '0 10px', flex: 1 }}
+          allowClear
+          onChange={e => handleSearchIcon(e.currentTarget.value)}
+          size="large"
+          autoFocus
+          suffix={<IconPicSearcher />}
+        />
+      </div>
+      {renderCategories}
+    </>
+  );
+};
+
+export default IconSelector
diff --git a/react-ui/src/components/IconSelector/style.less b/react-ui/src/components/IconSelector/style.less
new file mode 100644
index 0000000..0a4353d
--- /dev/null
+++ b/react-ui/src/components/IconSelector/style.less
@@ -0,0 +1,137 @@
+.iconPicSearcher {
+  display: inline-block;
+  margin: 0 8px;
+
+  .icon-pic-btn {
+    color: @text-color-secondary;
+    cursor: pointer;
+    transition: all 0.3s;
+
+    &:hover {
+      color: @input-icon-hover-color;
+    }
+  }
+}
+
+.icon-pic-preview {
+  width: 30px;
+  height: 30px;
+  margin-top: 10px;
+  padding: 8px;
+  text-align: center;
+  border: 1px solid @border-color-base;
+  border-radius: 4px;
+
+  > img {
+    max-width: 50px;
+    max-height: 50px;
+  }
+}
+
+.icon-pic-search-result {
+  min-height: 50px;
+  padding: 0 10px;
+
+  > .result-tip {
+    padding: 10px 0;
+    color: @text-color-secondary;
+  }
+
+  > table {
+    width: 100%;
+
+    .col-icon {
+      width: 80px;
+      padding: 10px 0;
+
+      > .anticon {
+        font-size: 30px;
+
+        :hover {
+          color: @link-hover-color;
+        }
+      }
+    }
+  }
+}
+
+ul.anticonsList {
+  margin: 2px 0;
+  overflow: hidden;
+  direction: ltr;
+  list-style: none;
+
+  li {
+    position: relative;
+    float: left;
+    width: 48px;
+    height: 48px;
+    margin: 3px 0;
+    padding: 2px 0 0;
+    overflow: hidden;
+    color: #555;
+    text-align: center;
+    list-style: none;
+    background-color: inherit;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
+
+    .rtl & {
+      margin: 3px 0;
+      padding: 2px 0 0;
+    }
+
+    .anticon {
+      margin: 4px 0 2px;
+      font-size: 24px;
+      transition: transform 0.3s ease-in-out;
+      will-change: transform;
+    }
+
+    .anticonClass {
+      display: block;
+      font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+      white-space: nowrap;
+      text-align: center;
+      transform: scale(0.83);
+
+      .ant-badge {
+        transition: color 0.3s ease-in-out;
+      }
+    }
+
+    &:hover {
+      color: #fff;
+      background-color: @primary-color;
+
+      .anticon {
+        transform: scale(1.4);
+      }
+
+      .ant-badge {
+        color: #fff;
+      }
+    }
+
+    &.TwoTone:hover {
+      background-color: #8ecafe;
+    }
+
+    &.copied:hover {
+      color: rgba(255, 255, 255, 0.2);
+    }
+
+    &.copied::after {
+      top: -2px;
+      opacity: 1;
+    }
+  }
+}
+
+.copied-code {
+  padding: 2px 4px;
+  font-size: 12px;
+  background: #f5f5f5;
+  border-radius: 2px;
+}
diff --git a/react-ui/src/components/IconSelector/themeIcons.tsx b/react-ui/src/components/IconSelector/themeIcons.tsx
new file mode 100644
index 0000000..abefe04
--- /dev/null
+++ b/react-ui/src/components/IconSelector/themeIcons.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+
+
+export const FilledIcon: React.FC = props => {
+  const path =
+    'M864 64H160C107 64 64 107 64 160v' +
+    '704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
+    '0c0-53-43-96-96-96z';
+  return (
+    <svg {...props} viewBox="0 0 1024 1024">
+      <path d={path} />
+    </svg>
+  );
+};
+
+export const OutlinedIcon: React.FC = props => {
+  const path =
+    'M864 64H160C107 64 64 107 64 160v7' +
+    '04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
+    '0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' +
+    '12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' +
+    ' 12 12v680c0 6.6-5.4 12-12 12z';
+  return (
+    <svg {...props} viewBox="0 0 1024 1024">
+      <path d={path} />
+    </svg>
+  );
+};
+
+export const TwoToneIcon: React.FC = props => {
+  const path =
+    'M16 512c0 273.932 222.066 496 496 49' +
+    '6s496-222.068 496-496S785.932 16 512 16 16 238.' +
+    '066 16 512z m496 368V144c203.41 0 368 164.622 3' +
+    '68 368 0 203.41-164.622 368-368 368z';
+  return (
+    <svg {...props} viewBox="0 0 1024 1024">
+      <path d={path} />
+    </svg>
+  );
+};
diff --git a/react-ui/src/components/RightContent/AvatarDropdown.tsx b/react-ui/src/components/RightContent/AvatarDropdown.tsx
new file mode 100644
index 0000000..34ceb15
--- /dev/null
+++ b/react-ui/src/components/RightContent/AvatarDropdown.tsx
@@ -0,0 +1,142 @@
+import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
+import { history, useModel } from '@umijs/max';
+import { Spin } from 'antd';
+import { createStyles } from 'antd-style';
+import { stringify } from 'querystring';
+import type { MenuInfo } from 'rc-menu/lib/interface';
+import React, { useCallback } from 'react';
+import { flushSync } from 'react-dom';
+import HeaderDropdown from '../HeaderDropdown';
+import { setRemoteMenu } from '@/services/session';
+import { clearSessionToken } from '@/access';
+import { logout } from '@/services/system/auth';
+
+export type GlobalHeaderRightProps = {
+  menu?: boolean;
+  children?: React.ReactNode;
+};
+
+export const AvatarName = () => {
+  const { initialState } = useModel('@@initialState');
+  const { currentUser } = initialState || {};
+  return <span className="anticon">{currentUser?.nickName}</span>;
+};
+
+const useStyles = createStyles(({ token }) => {
+  return {
+    action: {
+      display: 'flex',
+      height: '48px',
+      marginLeft: 'auto',
+      overflow: 'hidden',
+      alignItems: 'center',
+      padding: '0 8px',
+      cursor: 'pointer',
+      borderRadius: token.borderRadius,
+      '&:hover': {
+        backgroundColor: token.colorBgTextHover,
+      },
+    },
+  };
+});
+
+export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, children }) => {
+  /**
+   * 退出登录,并且将当前的 url 保存
+   */
+  const loginOut = async () => {
+    await logout();
+    clearSessionToken();
+    setRemoteMenu(null);
+    const { search, pathname } = window.location;
+    const urlParams = new URL(window.location.href).searchParams;
+    /** 此方法会跳转到 redirect 参数所在的位置 */
+    const redirect = urlParams.get('redirect');
+    // Note: There may be security issues, please note
+    if (window.location.pathname !== '/user/login' && !redirect) {
+      history.replace({
+        pathname: '/user/login',
+        search: stringify({
+          redirect: pathname + search,
+        }),
+      });
+    }
+  };
+  const { styles } = useStyles();
+
+  const { initialState, setInitialState } = useModel('@@initialState');
+
+  const onMenuClick = useCallback(
+    (event: MenuInfo) => {
+      const { key } = event;
+      if (key === 'logout') {
+        flushSync(() => {
+          setInitialState((s) => ({ ...s, currentUser: undefined }));
+        });
+        loginOut();
+        return;
+      }
+      history.push(`/account/${key}`);
+    },
+    [setInitialState],
+  );
+
+  const loading = (
+    <span className={styles.action}>
+      <Spin
+        size="small"
+        style={{
+          marginLeft: 8,
+          marginRight: 8,
+        }}
+      />
+    </span>
+  );
+
+  if (!initialState) {
+    return loading;
+  }
+
+  const { currentUser } = initialState;
+
+  if (!currentUser || !currentUser.nickName) {
+    return loading;
+  }
+
+  const menuItems = [
+    ...(menu
+      ? [
+          {
+            key: 'center',
+            icon: <UserOutlined />,
+            label: '个人中心',
+          },
+          {
+            key: 'settings',
+            icon: <SettingOutlined />,
+            label: '个人设置',
+          },
+          {
+            type: 'divider' as const,
+          },
+        ]
+      : []),
+    {
+      key: 'logout',
+      icon: <LogoutOutlined />,
+      label: '退出登录',
+    },
+  ];
+
+  return (
+    <HeaderDropdown
+      menu={{
+        selectedKeys: [],
+        onClick: onMenuClick,
+        items: menuItems,
+      }}
+    >
+      {children}
+    </HeaderDropdown>
+  );
+};
diff --git a/react-ui/src/components/RightContent/index.tsx b/react-ui/src/components/RightContent/index.tsx
new file mode 100644
index 0000000..20a7831
--- /dev/null
+++ b/react-ui/src/components/RightContent/index.tsx
@@ -0,0 +1,31 @@
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import { SelectLang as UmiSelectLang } from '@umijs/max';
+import React from 'react';
+
+export type SiderTheme = 'light' | 'dark';
+
+export const SelectLang = () => {
+  return (
+    <UmiSelectLang
+      style={{
+        padding: 4,
+      }}
+    />
+  );
+};
+
+export const Question = () => {
+  return (
+    <div
+      style={{
+        display: 'flex',
+        height: 26,
+      }}
+      onClick={() => {
+        window.open('https://pro.ant.design/docs/getting-started');
+      }}
+    >
+      <QuestionCircleOutlined />
+    </div>
+  );
+};
diff --git a/react-ui/src/components/index.ts b/react-ui/src/components/index.ts
new file mode 100644
index 0000000..ca88a6d
--- /dev/null
+++ b/react-ui/src/components/index.ts
@@ -0,0 +1,12 @@
+/**
+ * 这个文件作为组件的目录
+ * 目的是统一管理对外输出的组件,方便分类
+ */
+/**
+ * 布局组件
+ */
+import Footer from './Footer';
+import { Question, SelectLang } from './RightContent';
+import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown';
+
+export { Footer, Question, SelectLang, AvatarDropdown, AvatarName };
diff --git a/react-ui/src/enums/httpEnum.ts b/react-ui/src/enums/httpEnum.ts
new file mode 100644
index 0000000..8b7ff1f
--- /dev/null
+++ b/react-ui/src/enums/httpEnum.ts
@@ -0,0 +1,31 @@
+/**
+ * @description: Request result set
+ */
+export enum HttpResult {
+  SUCCESS = 200,
+  ERROR = -1,
+  TIMEOUT = 401,
+  TYPE = 'success',
+}
+
+/**
+ * @description: request method
+ */
+export enum RequestMethod {
+  GET = 'GET',
+  POST = 'POST',
+  PUT = 'PUT',
+  DELETE = 'DELETE',
+}
+
+/**
+ * @description:  contentType
+ */
+export enum ContentType {
+  // json
+  JSON = 'application/json;charset=UTF-8',
+  // form-data qs
+  FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
+  // form-data  upload
+  FORM_DATA = 'multipart/form-data;charset=UTF-8',
+}
diff --git a/react-ui/src/enums/pagesEnums.ts b/react-ui/src/enums/pagesEnums.ts
new file mode 100644
index 0000000..4bdb5fd
--- /dev/null
+++ b/react-ui/src/enums/pagesEnums.ts
@@ -0,0 +1,4 @@
+
+export enum PageEnum {
+    LOGIN = '/user/login'
+}
\ No newline at end of file
diff --git a/react-ui/src/global.less b/react-ui/src/global.less
new file mode 100644
index 0000000..a9a0c51
--- /dev/null
+++ b/react-ui/src/global.less
@@ -0,0 +1,53 @@
+html,
+body,
+#root {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
+    'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
+    'Noto Color Emoji';
+}
+
+.colorWeak {
+  filter: invert(80%);
+}
+
+.ant-layout {
+  min-height: 100vh;
+}
+.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
+  left: unset;
+}
+
+canvas {
+  display: block;
+}
+
+body {
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+ul,
+ol {
+  list-style: none;
+}
+
+@media (max-width: 768px) {
+  .ant-table {
+    width: 100%;
+    overflow-x: auto;
+    &-thead > tr,
+    &-tbody > tr {
+      > th,
+      > td {
+        white-space: pre;
+        > span {
+          display: block;
+        }
+      }
+    }
+  }
+}
diff --git a/react-ui/src/global.tsx b/react-ui/src/global.tsx
new file mode 100644
index 0000000..afa1fab
--- /dev/null
+++ b/react-ui/src/global.tsx
@@ -0,0 +1,91 @@
+import { useIntl } from '@umijs/max';
+import { Button, message, notification } from 'antd';
+import defaultSettings from '../config/defaultSettings';
+
+const { pwa } = defaultSettings;
+const isHttps = document.location.protocol === 'https:';
+
+const clearCache = () => {
+  // remove all caches
+  if (window.caches) {
+    caches
+      .keys()
+      .then((keys) => {
+        keys.forEach((key) => {
+          caches.delete(key);
+        });
+      })
+      .catch((e) => console.log(e));
+  }
+};
+
+// if pwa is true
+if (pwa) {
+  // Notify user if offline now
+  window.addEventListener('sw.offline', () => {
+    message.warning(useIntl().formatMessage({ id: 'app.pwa.offline' }));
+  });
+
+  // Pop up a prompt on the page asking the user if they want to use the latest version
+  window.addEventListener('sw.updated', (event: Event) => {
+    const e = event as CustomEvent;
+    const reloadSW = async () => {
+      // Check if there is sw whose state is waiting in ServiceWorkerRegistration
+      // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
+      const worker = e.detail && e.detail.waiting;
+      if (!worker) {
+        return true;
+      }
+      // Send skip-waiting event to waiting SW with MessageChannel
+      await new Promise((resolve, reject) => {
+        const channel = new MessageChannel();
+        channel.port1.onmessage = (msgEvent) => {
+          if (msgEvent.data.error) {
+            reject(msgEvent.data.error);
+          } else {
+            resolve(msgEvent.data);
+          }
+        };
+        worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
+      });
+
+      clearCache();
+      window.location.reload();
+      return true;
+    };
+    const key = `open${Date.now()}`;
+    const btn = (
+      <Button
+        type="primary"
+        onClick={() => {
+          notification.destroy(key);
+          reloadSW();
+        }}
+      >
+        {useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
+      </Button>
+    );
+    notification.open({
+      message: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }),
+      description: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
+      btn,
+      key,
+      onClose: async () => null,
+    });
+  });
+} else if ('serviceWorker' in navigator && isHttps) {
+  // unregister service worker
+  const { serviceWorker } = navigator;
+  if (serviceWorker.getRegistrations) {
+    serviceWorker.getRegistrations().then((sws) => {
+      sws.forEach((sw) => {
+        sw.unregister();
+      });
+    });
+  }
+  serviceWorker.getRegistration().then((sw) => {
+    if (sw) sw.unregister();
+  });
+
+  clearCache();
+}
diff --git a/react-ui/src/hooks/net/dict.ts b/react-ui/src/hooks/net/dict.ts
new file mode 100644
index 0000000..9c2c41c
--- /dev/null
+++ b/react-ui/src/hooks/net/dict.ts
@@ -0,0 +1,7 @@
+import { getDictValueEnum } from "@/services/system/dict";
+
+export function useDictEnum(name: string)
+{
+    const data = getDictValueEnum(name);
+    return data;
+}
\ No newline at end of file
diff --git a/react-ui/src/locales/en-US.ts b/react-ui/src/locales/en-US.ts
new file mode 100644
index 0000000..58407d2
--- /dev/null
+++ b/react-ui/src/locales/en-US.ts
@@ -0,0 +1,27 @@
+import app from './en-US/app';
+import component from './en-US/component';
+import globalHeader from './en-US/globalHeader';
+import menu from './en-US/menu';
+import pages from './en-US/pages';
+import pwa from './en-US/pwa';
+import settingDrawer from './en-US/settingDrawer';
+import settings from './en-US/settings';
+
+export default {
+  'navBar.lang': 'Languages',
+  'layout.user.link.help': 'Help',
+  'layout.user.link.privacy': 'Privacy',
+  'layout.user.link.terms': 'Terms',
+  'app.copyright.produced': 'Produced by Ant Financial Experience Department',
+  'app.preview.down.block': 'Download this page to your local project',
+  'app.welcome.link.fetch-blocks': 'Get all block',
+  'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
+  ...app,
+  ...globalHeader,
+  ...menu,
+  ...settingDrawer,
+  ...settings,
+  ...pwa,
+  ...component,
+  ...pages,
+};
diff --git a/react-ui/src/locales/en-US/app.ts b/react-ui/src/locales/en-US/app.ts
new file mode 100644
index 0000000..074dcb8
--- /dev/null
+++ b/react-ui/src/locales/en-US/app.ts
@@ -0,0 +1,26 @@
+export default {
+	'app.docs.components.icon.search.placeholder': 'Search icons here, click icon to copy code',
+	'app.docs.components.icon.outlined': 'Outlined',
+	'app.docs.components.icon.filled': 'Filled',
+	'app.docs.components.icon.two-tone': 'Two Tone',
+	'app.docs.components.icon.category.direction': 'Directional Icons',
+	'app.docs.components.icon.category.suggestion': 'Suggested Icons',
+	'app.docs.components.icon.category.editor': 'Editor Icons',
+	'app.docs.components.icon.category.data': 'Data Icons',
+	'app.docs.components.icon.category.other': 'Application Icons',
+	'app.docs.components.icon.category.logo': 'Brand and Logos',
+	'app.docs.components.icon.pic-searcher.intro':
+		'AI Search by image is online, you are welcome to use it! 🎉',
+	'app.docs.components.icon.pic-searcher.title': 'Search by image',
+	'app.docs.components.icon.pic-searcher.upload-text':
+		'Click, drag, or paste file to this area to upload',
+	'app.docs.components.icon.pic-searcher.upload-hint':
+		'We will find the best matching icon based on the image provided',
+	'app.docs.components.icon.pic-searcher.server-error':
+		'Predict service is temporarily unavailable',
+	'app.docs.components.icon.pic-searcher.matching': 'Matching...',
+	'app.docs.components.icon.pic-searcher.modelloading': 'Model is loading...',
+	'app.docs.components.icon.pic-searcher.result-tip': 'Matched the following icons for you:',
+	'app.docs.components.icon.pic-searcher.th-icon': 'Icon',
+	'app.docs.components.icon.pic-searcher.th-score': 'Probability',
+};
diff --git a/react-ui/src/locales/en-US/component.ts b/react-ui/src/locales/en-US/component.ts
new file mode 100644
index 0000000..3ba7eed
--- /dev/null
+++ b/react-ui/src/locales/en-US/component.ts
@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': 'Expand',
+  'component.tagSelect.collapse': 'Collapse',
+  'component.tagSelect.all': 'All',
+};
diff --git a/react-ui/src/locales/en-US/globalHeader.ts b/react-ui/src/locales/en-US/globalHeader.ts
new file mode 100644
index 0000000..60b6d4e
--- /dev/null
+++ b/react-ui/src/locales/en-US/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+  'component.globalHeader.search': 'Search',
+  'component.globalHeader.search.example1': 'Search example 1',
+  'component.globalHeader.search.example2': 'Search example 2',
+  'component.globalHeader.search.example3': 'Search example 3',
+  'component.globalHeader.help': 'Help',
+  'component.globalHeader.notification': 'Notification',
+  'component.globalHeader.notification.empty': 'You have viewed all notifications.',
+  'component.globalHeader.message': 'Message',
+  'component.globalHeader.message.empty': 'You have viewed all messsages.',
+  'component.globalHeader.event': 'Event',
+  'component.globalHeader.event.empty': 'You have viewed all events.',
+  'component.noticeIcon.clear': 'Clear',
+  'component.noticeIcon.cleared': 'Cleared',
+  'component.noticeIcon.empty': 'No notifications',
+  'component.noticeIcon.view-more': 'View more',
+};
diff --git a/react-ui/src/locales/en-US/menu.ts b/react-ui/src/locales/en-US/menu.ts
new file mode 100644
index 0000000..eae3e53
--- /dev/null
+++ b/react-ui/src/locales/en-US/menu.ts
@@ -0,0 +1,52 @@
+export default {
+  'menu.welcome': 'Welcome',
+  'menu.more-blocks': 'More Blocks',
+  'menu.home': 'Home',
+  'menu.admin': 'Admin',
+  'menu.admin.sub-page': 'Sub-Page',
+  'menu.login': 'Login',
+  'menu.register': 'Register',
+  'menu.register-result': 'Register Result',
+  'menu.dashboard': 'Dashboard',
+  'menu.dashboard.analysis': 'Analysis',
+  'menu.dashboard.monitor': 'Monitor',
+  'menu.dashboard.workplace': 'Workplace',
+  'menu.exception.403': '403',
+  'menu.exception.404': '404',
+  'menu.exception.500': '500',
+  'menu.form': 'Form',
+  'menu.form.basic-form': 'Basic Form',
+  'menu.form.step-form': 'Step Form',
+  'menu.form.step-form.info': 'Step Form(write transfer information)',
+  'menu.form.step-form.confirm': 'Step Form(confirm transfer information)',
+  'menu.form.step-form.result': 'Step Form(finished)',
+  'menu.form.advanced-form': 'Advanced Form',
+  'menu.list': 'List',
+  'menu.list.table-list': 'Search Table',
+  'menu.list.basic-list': 'Basic List',
+  'menu.list.card-list': 'Card List',
+  'menu.list.search-list': 'Search List',
+  'menu.list.search-list.articles': 'Search List(articles)',
+  'menu.list.search-list.projects': 'Search List(projects)',
+  'menu.list.search-list.applications': 'Search List(applications)',
+  'menu.profile': 'Profile',
+  'menu.profile.basic': 'Basic Profile',
+  'menu.profile.advanced': 'Advanced Profile',
+  'menu.result': 'Result',
+  'menu.result.success': 'Success',
+  'menu.result.fail': 'Fail',
+  'menu.exception': 'Exception',
+  'menu.exception.not-permission': '403',
+  'menu.exception.not-find': '404',
+  'menu.exception.server-error': '500',
+  'menu.exception.trigger': 'Trigger',
+  'menu.account': 'Account',
+  'menu.account.center': 'Account Center',
+  'menu.account.settings': 'Account Settings',
+  'menu.account.trigger': 'Trigger Error',
+  'menu.account.logout': 'Logout',
+  'menu.editor': 'Graphic Editor',
+  'menu.editor.flow': 'Flow Editor',
+  'menu.editor.mind': 'Mind Editor',
+  'menu.editor.koni': 'Koni Editor',
+};
diff --git a/react-ui/src/locales/en-US/pages.ts b/react-ui/src/locales/en-US/pages.ts
new file mode 100644
index 0000000..58ff2c8
--- /dev/null
+++ b/react-ui/src/locales/en-US/pages.ts
@@ -0,0 +1,71 @@
+export default {
+  'pages.layouts.userLayout.title':
+    'Ant Design is the most influential web design specification in Xihu district',
+  'pages.login.accountLogin.tab': 'Account Login',
+  'pages.login.accountLogin.errorMessage': 'Incorrect username/password(admin/admin123)',
+  'pages.login.failure': 'Login failed, please try again!',
+  'pages.login.success': 'Login successful!',
+  'pages.login.username.placeholder': 'Username: admin',
+  'pages.login.username.required': 'Please input your username!',
+  'pages.login.password.placeholder': 'Password: admin123',
+  'pages.login.password.required': 'Please input your password!',
+  'pages.login.phoneLogin.tab': 'Phone Login',
+  'pages.login.phoneLogin.errorMessage': 'Verification Code Error',
+  'pages.login.phoneNumber.placeholder': 'Phone Number',
+  'pages.login.phoneNumber.required': 'Please input your phone number!',
+  'pages.login.phoneNumber.invalid': 'Phone number is invalid!',
+  'pages.login.captcha.placeholder': 'Verification Code',
+  'pages.login.captcha.required': 'Please input verification code!',
+  'pages.login.phoneLogin.getVerificationCode': 'Get Code',
+  'pages.getCaptchaSecondText': 'sec(s)',
+  'pages.login.rememberMe': 'Remember me',
+  'pages.login.forgotPassword': 'Forgot Password ?',
+  'pages.login.submit': 'Login',
+  'pages.login.loginWith': 'Login with :',
+  'pages.login.registerAccount': 'Register Account',
+  'pages.welcome.link': 'Welcome',
+  'pages.welcome.alertMessage': 'Faster and stronger heavy-duty components have been released.',
+  'pages.admin.subPage.title': 'This page can only be viewed by Admin',
+  'pages.admin.subPage.alertMessage':
+    'Umi ui is now released, welcome to use npm run ui to start the experience.',
+  'pages.searchTable.createForm.newRule': 'New Rule',
+  'pages.searchTable.updateForm.ruleConfig': 'Rule configuration',
+  'pages.searchTable.updateForm.basicConfig': 'Basic Information',
+  'pages.searchTable.updateForm.ruleName.nameLabel': 'Rule Name',
+  'pages.searchTable.updateForm.ruleName.nameRules': 'Please enter the rule name!',
+  'pages.searchTable.updateForm.ruleDesc.descLabel': 'Rule Description',
+  'pages.searchTable.updateForm.ruleDesc.descPlaceholder': 'Please enter at least five characters',
+  'pages.searchTable.updateForm.ruleDesc.descRules':
+    'Please enter a rule description of at least five characters!',
+  'pages.searchTable.updateForm.ruleProps.title': 'Configure Properties',
+  'pages.searchTable.updateForm.object': 'Monitoring Object',
+  'pages.searchTable.updateForm.ruleProps.templateLabel': 'Rule Template',
+  'pages.searchTable.updateForm.ruleProps.typeLabel': 'Rule Type',
+  'pages.searchTable.updateForm.schedulingPeriod.title': 'Set Scheduling Period',
+  'pages.searchTable.updateForm.schedulingPeriod.timeLabel': 'Starting Time',
+  'pages.searchTable.updateForm.schedulingPeriod.timeRules': 'Please choose a start time!',
+  'pages.searchTable.titleDesc': 'Description',
+  'pages.searchTable.ruleName': 'Rule name is required',
+  'pages.searchTable.titleCallNo': 'Number of Service Calls',
+  'pages.searchTable.titleStatus': 'Status',
+  'pages.searchTable.nameStatus.default': 'default',
+  'pages.searchTable.nameStatus.running': 'running',
+  'pages.searchTable.nameStatus.online': 'online',
+  'pages.searchTable.nameStatus.abnormal': 'abnormal',
+  'pages.searchTable.titleUpdatedAt': 'Last Scheduled at',
+  'pages.searchTable.exception': 'Please enter the reason for the exception!',
+  'pages.searchTable.titleOption': 'Option',
+  'pages.searchTable.config': 'Configuration',
+  'pages.searchTable.subscribeAlert': 'Subscribe to alerts',
+  'pages.searchTable.title': 'Enquiry Form',
+  'pages.searchTable.new': 'New',
+  'pages.searchTable.edit': 'Edit',
+  'pages.searchTable.delete': 'Delete',
+  'pages.searchTable.export': 'Export',
+  'pages.searchTable.chosen': 'chosen',
+  'pages.searchTable.item': 'item',
+  'pages.searchTable.totalServiceCalls': 'Total Number of Service Calls',
+  'pages.searchTable.tenThousand': '0000',
+  'pages.searchTable.batchDeletion': 'batch deletion',
+  'pages.searchTable.batchApproval': 'batch approval',
+};
diff --git a/react-ui/src/locales/en-US/pwa.ts b/react-ui/src/locales/en-US/pwa.ts
new file mode 100644
index 0000000..ed8d199
--- /dev/null
+++ b/react-ui/src/locales/en-US/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': 'You are offline now',
+  'app.pwa.serviceworker.updated': 'New content is available',
+  'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
+  'app.pwa.serviceworker.updated.ok': 'Refresh',
+};
diff --git a/react-ui/src/locales/en-US/settingDrawer.ts b/react-ui/src/locales/en-US/settingDrawer.ts
new file mode 100644
index 0000000..a644905
--- /dev/null
+++ b/react-ui/src/locales/en-US/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+  'app.setting.pagestyle': 'Page style setting',
+  'app.setting.pagestyle.dark': 'Dark style',
+  'app.setting.pagestyle.light': 'Light style',
+  'app.setting.content-width': 'Content Width',
+  'app.setting.content-width.fixed': 'Fixed',
+  'app.setting.content-width.fluid': 'Fluid',
+  'app.setting.themecolor': 'Theme Color',
+  'app.setting.themecolor.dust': 'Dust Red',
+  'app.setting.themecolor.volcano': 'Volcano',
+  'app.setting.themecolor.sunset': 'Sunset Orange',
+  'app.setting.themecolor.cyan': 'Cyan',
+  'app.setting.themecolor.green': 'Polar Green',
+  'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
+  'app.setting.themecolor.geekblue': 'Geek Glue',
+  'app.setting.themecolor.purple': 'Golden Purple',
+  'app.setting.navigationmode': 'Navigation Mode',
+  'app.setting.sidemenu': 'Side Menu Layout',
+  'app.setting.topmenu': 'Top Menu Layout',
+  'app.setting.fixedheader': 'Fixed Header',
+  'app.setting.fixedsidebar': 'Fixed Sidebar',
+  'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout',
+  'app.setting.hideheader': 'Hidden Header when scrolling',
+  'app.setting.hideheader.hint': 'Works when Hidden Header is enabled',
+  'app.setting.othersettings': 'Other Settings',
+  'app.setting.weakmode': 'Weak Mode',
+  'app.setting.copy': 'Copy Setting',
+  'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
+  'app.setting.production.hint':
+    'Setting panel shows in development environment only, please manually modify',
+};
diff --git a/react-ui/src/locales/en-US/settings.ts b/react-ui/src/locales/en-US/settings.ts
new file mode 100644
index 0000000..822dd00
--- /dev/null
+++ b/react-ui/src/locales/en-US/settings.ts
@@ -0,0 +1,60 @@
+export default {
+  'app.settings.menuMap.basic': 'Basic Settings',
+  'app.settings.menuMap.security': 'Security Settings',
+  'app.settings.menuMap.binding': 'Account Binding',
+  'app.settings.menuMap.notification': 'New Message Notification',
+  'app.settings.basic.avatar': 'Avatar',
+  'app.settings.basic.change-avatar': 'Change avatar',
+  'app.settings.basic.email': 'Email',
+  'app.settings.basic.email-message': 'Please input your email!',
+  'app.settings.basic.nickname': 'Nickname',
+  'app.settings.basic.nickname-message': 'Please input your Nickname!',
+  'app.settings.basic.profile': 'Personal profile',
+  'app.settings.basic.profile-message': 'Please input your personal profile!',
+  'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
+  'app.settings.basic.country': 'Country/Region',
+  'app.settings.basic.country-message': 'Please input your country!',
+  'app.settings.basic.geographic': 'Province or city',
+  'app.settings.basic.geographic-message': 'Please input your geographic info!',
+  'app.settings.basic.address': 'Street Address',
+  'app.settings.basic.address-message': 'Please input your address!',
+  'app.settings.basic.phone': 'Phone Number',
+  'app.settings.basic.phone-message': 'Please input your phone!',
+  'app.settings.basic.update': 'Update Information',
+  'app.settings.security.strong': 'Strong',
+  'app.settings.security.medium': 'Medium',
+  'app.settings.security.weak': 'Weak',
+  'app.settings.security.password': 'Account Password',
+  'app.settings.security.password-description': 'Current password strength',
+  'app.settings.security.phone': 'Security Phone',
+  'app.settings.security.phone-description': 'Bound phone',
+  'app.settings.security.question': 'Security Question',
+  'app.settings.security.question-description':
+    'The security question is not set, and the security policy can effectively protect the account security',
+  'app.settings.security.email': 'Backup Email',
+  'app.settings.security.email-description': 'Bound Email',
+  'app.settings.security.mfa': 'MFA Device',
+  'app.settings.security.mfa-description':
+    'Unbound MFA device, after binding, can be confirmed twice',
+  'app.settings.security.modify': 'Modify',
+  'app.settings.security.set': 'Set',
+  'app.settings.security.bind': 'Bind',
+  'app.settings.binding.taobao': 'Binding Taobao',
+  'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
+  'app.settings.binding.alipay': 'Binding Alipay',
+  'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
+  'app.settings.binding.dingding': 'Binding DingTalk',
+  'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
+  'app.settings.binding.bind': 'Bind',
+  'app.settings.notification.password': 'Account Password',
+  'app.settings.notification.password-description':
+    'Messages from other users will be notified in the form of a station letter',
+  'app.settings.notification.messages': 'System Messages',
+  'app.settings.notification.messages-description':
+    'System messages will be notified in the form of a station letter',
+  'app.settings.notification.todo': 'To-do Notification',
+  'app.settings.notification.todo-description':
+    'The to-do list will be notified in the form of a letter from the station',
+  'app.settings.open': 'Open',
+  'app.settings.close': 'Close',
+};
diff --git a/react-ui/src/locales/zh-CN.ts b/react-ui/src/locales/zh-CN.ts
new file mode 100644
index 0000000..06e2eac
--- /dev/null
+++ b/react-ui/src/locales/zh-CN.ts
@@ -0,0 +1,57 @@
+import app from './zh-CN/app';
+import component from './zh-CN/component';
+import globalHeader from './zh-CN/globalHeader';
+import sysmenu from './zh-CN/menu';
+import pages from './zh-CN/pages';
+import pwa from './zh-CN/pwa';
+import settingDrawer from './zh-CN/settingDrawer';
+import settings from './zh-CN/settings';
+import user from './zh-CN/system/user';
+import menu from './zh-CN/system/menu';
+import dict from './zh-CN/system/dict';
+import dictData from './zh-CN/system/dict-data';
+import role from './zh-CN/system/role';
+import dept from './zh-CN/system/dept';
+import post from './zh-CN/system/post';
+import config from './zh-CN/system/config';
+import notice from './zh-CN/system/notice';
+import operlog from './zh-CN/monitor/operlog';
+import logininfor from './zh-CN/monitor/logininfor';
+import onlineUser from './zh-CN/monitor/onlineUser';
+import job from './zh-CN/monitor/job';
+import joblog from './zh-CN/monitor/job-log';
+import server from './zh-CN/monitor/server';
+
+export default {
+  'navBar.lang': '语言',
+  'layout.user.link.help': '帮助',
+  'layout.user.link.privacy': '隐私',
+  'layout.user.link.terms': '条款',
+  'app.copyright.produced': '蚂蚁集团体验技术部出品',
+  'app.preview.down.block': '下载此页面到本地项目',
+  'app.welcome.link.fetch-blocks': '获取全部区块',
+  'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
+  ...app,
+  ...pages,
+  ...globalHeader,
+  ...sysmenu,
+  ...settingDrawer,
+  ...settings,
+  ...pwa,
+  ...component,
+  ...user,
+  ...menu,
+  ...dict,
+  ...dictData,
+  ...role,
+  ...dept,
+  ...post,
+  ...config,
+  ...notice,
+  ...operlog,
+  ...logininfor,
+  ...onlineUser,
+  ...job,
+  ...joblog,
+  ...server,
+};
diff --git a/react-ui/src/locales/zh-CN/app.ts b/react-ui/src/locales/zh-CN/app.ts
new file mode 100644
index 0000000..5be68e2
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/app.ts
@@ -0,0 +1,23 @@
+export default {
+  'app.docs.components.icon.search.placeholder': '在此搜索图标,点击图标可复制代码',
+  'app.docs.components.icon.outlined': '线框风格',
+  'app.docs.components.icon.filled': '实底风格',
+  'app.docs.components.icon.two-tone': '双色风格',
+  'app.docs.components.icon.category.direction': '方向性图标',
+  'app.docs.components.icon.category.suggestion': '提示建议性图标',
+  'app.docs.components.icon.category.editor': '编辑类图标',
+  'app.docs.components.icon.category.data': '数据类图标',
+  'app.docs.components.icon.category.other': '网站通用图标',
+  'app.docs.components.icon.category.logo': '品牌和标识',
+  'app.docs.components.icon.pic-searcher.intro': 'AI 截图搜索上线了,快来体验吧!🎉',
+  'app.docs.components.icon.pic-searcher.title': '上传图片搜索图标',
+  'app.docs.components.icon.pic-searcher.upload-text': '点击/拖拽/粘贴上传图片',
+  'app.docs.components.icon.pic-searcher.upload-hint':
+    '我们会通过上传的图片进行匹配,得到最相似的图标',
+  'app.docs.components.icon.pic-searcher.server-error': '识别服务暂不可用',
+  'app.docs.components.icon.pic-searcher.matching': '匹配中...',
+  'app.docs.components.icon.pic-searcher.modelloading': '神经网络模型加载中...',
+  'app.docs.components.icon.pic-searcher.result-tip': '为您匹配到以下图标:',
+  'app.docs.components.icon.pic-searcher.th-icon': '图标',
+  'app.docs.components.icon.pic-searcher.th-score': '匹配度',
+};
diff --git a/react-ui/src/locales/zh-CN/component.ts b/react-ui/src/locales/zh-CN/component.ts
new file mode 100644
index 0000000..1f1fead
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/component.ts
@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': '展开',
+  'component.tagSelect.collapse': '收起',
+  'component.tagSelect.all': '全部',
+};
diff --git a/react-ui/src/locales/zh-CN/globalHeader.ts b/react-ui/src/locales/zh-CN/globalHeader.ts
new file mode 100644
index 0000000..9fd66a5
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+  'component.globalHeader.search': '站内搜索',
+  'component.globalHeader.search.example1': '搜索提示一',
+  'component.globalHeader.search.example2': '搜索提示二',
+  'component.globalHeader.search.example3': '搜索提示三',
+  'component.globalHeader.help': '使用文档',
+  'component.globalHeader.notification': '通知',
+  'component.globalHeader.notification.empty': '你已查看所有通知',
+  'component.globalHeader.message': '消息',
+  'component.globalHeader.message.empty': '您已读完所有消息',
+  'component.globalHeader.event': '待办',
+  'component.globalHeader.event.empty': '你已完成所有待办',
+  'component.noticeIcon.clear': '清空',
+  'component.noticeIcon.cleared': '清空了',
+  'component.noticeIcon.empty': '暂无数据',
+  'component.noticeIcon.view-more': '查看更多',
+};
diff --git a/react-ui/src/locales/zh-CN/menu.ts b/react-ui/src/locales/zh-CN/menu.ts
new file mode 100644
index 0000000..fecb70a
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/menu.ts
@@ -0,0 +1,52 @@
+export default {
+  'menu.welcome': '欢迎',
+  'menu.more-blocks': '更多区块',
+  'menu.home': '首页',
+  'menu.admin': '管理页',
+  'menu.admin.sub-page': '二级管理页',
+  'menu.login': '登录',
+  'menu.register': '注册',
+  'menu.register-result': '注册结果',
+  'menu.dashboard': 'Dashboard',
+  'menu.dashboard.analysis': '分析页',
+  'menu.dashboard.monitor': '监控页',
+  'menu.dashboard.workplace': '工作台',
+  'menu.exception.403': '403',
+  'menu.exception.404': '404',
+  'menu.exception.500': '500',
+  'menu.form': '表单页',
+  'menu.form.basic-form': '基础表单',
+  'menu.form.step-form': '分步表单',
+  'menu.form.step-form.info': '分步表单(填写转账信息)',
+  'menu.form.step-form.confirm': '分步表单(确认转账信息)',
+  'menu.form.step-form.result': '分步表单(完成)',
+  'menu.form.advanced-form': '高级表单',
+  'menu.list': '列表页',
+  'menu.list.table-list': '查询表格',
+  'menu.list.basic-list': '标准列表',
+  'menu.list.card-list': '卡片列表',
+  'menu.list.search-list': '搜索列表',
+  'menu.list.search-list.articles': '搜索列表(文章)',
+  'menu.list.search-list.projects': '搜索列表(项目)',
+  'menu.list.search-list.applications': '搜索列表(应用)',
+  'menu.profile': '详情页',
+  'menu.profile.basic': '基础详情页',
+  'menu.profile.advanced': '高级详情页',
+  'menu.result': '结果页',
+  'menu.result.success': '成功页',
+  'menu.result.fail': '失败页',
+  'menu.exception': '异常页',
+  'menu.exception.not-permission': '403',
+  'menu.exception.not-find': '404',
+  'menu.exception.server-error': '500',
+  'menu.exception.trigger': '触发错误',
+  'menu.account': '个人页',
+  'menu.account.center': '个人中心',
+  'menu.account.settings': '个人设置',
+  'menu.account.trigger': '触发报错',
+  'menu.account.logout': '退出登录',
+  'menu.editor': '图形编辑器',
+  'menu.editor.flow': '流程编辑器',
+  'menu.editor.mind': '脑图编辑器',
+  'menu.editor.koni': '拓扑编辑器',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/job-log.ts b/react-ui/src/locales/zh-CN/monitor/job-log.ts
new file mode 100644
index 0000000..75cf8d0
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/job-log.ts
@@ -0,0 +1,18 @@
+/**
+ * 定时任务调度日志
+ * 
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+export default {
+	'monitor.job.log.title': '定时任务调度日志',
+	'monitor.job.log.job_log_id': '任务日志编号',
+	'monitor.job.log.job_name': '任务名称',
+	'monitor.job.log.job_group': '任务组名',
+	'monitor.job.log.invoke_target': '调用方法',
+	'monitor.job.log.job_message': '日志信息',
+	'monitor.job.log.status': '执行状态',
+	'monitor.job.log.exception_info': '异常信息',
+	'monitor.job.log.create_time': '创建时间',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/job.ts b/react-ui/src/locales/zh-CN/monitor/job.ts
new file mode 100644
index 0000000..7e0d5b9
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/job.ts
@@ -0,0 +1,25 @@
+/**
+ * 定时任务调度
+ * 
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+export default {
+	'monitor.job.title': '定时任务调度',
+	'monitor.job.job_id': '任务编号',
+	'monitor.job.job_name': '任务名称',
+	'monitor.job.job_group': '任务组名',
+	'monitor.job.invoke_target': '调用方法',
+	'monitor.job.cron_expression': 'cron执行表达式',
+	'monitor.job.misfire_policy': '执行策略',
+	'monitor.job.concurrent': '是否并发执行',
+	'monitor.job.next_valid_time': '下次执行时间',
+	'monitor.job.status': '状态',
+	'monitor.job.create_by': '创建者',
+	'monitor.job.create_time': '创建时间',
+	'monitor.job.update_by': '更新者',
+	'monitor.job.update_time': '更新时间',
+	'monitor.job.remark': '备注信息',
+	'monitor.job.detail': '任务详情',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/logininfor.ts b/react-ui/src/locales/zh-CN/monitor/logininfor.ts
new file mode 100644
index 0000000..959a6d2
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/logininfor.ts
@@ -0,0 +1,13 @@
+export default {
+	'monitor.logininfor.title': '系统访问记录',
+	'monitor.logininfor.info_id': '访问编号',
+	'monitor.logininfor.user_name': '用户账号',
+	'monitor.logininfor.ipaddr': '登录IP地址',
+	'monitor.logininfor.login_location': '登录地点',
+	'monitor.logininfor.browser': '浏览器类型',
+	'monitor.logininfor.os': '操作系统',
+	'monitor.logininfor.status': '登录状态',
+	'monitor.logininfor.msg': '提示消息',
+	'monitor.logininfor.login_time': '访问时间',
+	'monitor.logininfor.unlock': '解锁',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/onlineUser.ts b/react-ui/src/locales/zh-CN/monitor/onlineUser.ts
new file mode 100644
index 0000000..c693cde
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/onlineUser.ts
@@ -0,0 +1,20 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+export default {
+  'monitor.online.user.id': '编号',
+  'monitor.online.user.token_id': '会话编号',
+  'monitor.online.user.user_name': '会话编号',
+  'monitor.online.user.ipaddr': '登录IP地址',
+  'monitor.online.user.login_location': '登录地点',
+  'monitor.online.user.browser': '浏览器类型',
+  'monitor.online.user.os': '操作系统',
+  'monitor.online.user.dept_name': '部门',
+  'monitor.online.user.login_time': '访问时间',
+  'monitor.online.user.force_logout': '强制退出',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/operlog.ts b/react-ui/src/locales/zh-CN/monitor/operlog.ts
new file mode 100644
index 0000000..c45824c
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/operlog.ts
@@ -0,0 +1,19 @@
+export default {
+	'monitor.operlog.title': '操作日志记录',
+	'monitor.operlog.oper_id': '日志主键',
+	'monitor.operlog.business_type': '业务类型',
+	'monitor.operlog.method': '方法名称',
+	'monitor.operlog.request_method': '请求方式',
+	'monitor.operlog.operator_type': '操作类别',
+	'monitor.operlog.oper_name': '操作人员',
+	'monitor.operlog.dept_name': '部门名称',
+	'monitor.operlog.oper_url': '请求URL',
+	'monitor.operlog.oper_ip': '主机地址',
+	'monitor.operlog.oper_location': '操作地点',
+	'monitor.operlog.oper_param': '请求参数',
+	'monitor.operlog.json_result': '返回参数',
+	'monitor.operlog.status': '操作状态',
+	'monitor.operlog.error_msg': '错误消息',
+	'monitor.operlog.oper_time': '操作时间',
+	'monitor.operlog.module': '操作模块',
+};
diff --git a/react-ui/src/locales/zh-CN/monitor/server.ts b/react-ui/src/locales/zh-CN/monitor/server.ts
new file mode 100644
index 0000000..f887a22
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/monitor/server.ts
@@ -0,0 +1,27 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+export default {
+  'monitor.server.cpu.cpuNum': '核心数',
+  'monitor.server.cpu.total': '总使用率',
+  'monitor.server.cpu.sys': '系统使用率',
+  'monitor.server.cpu.used': '用户使用率',
+  'monitor.server.cpu.wait': 'IO等待',
+  'monitor.server.cpu.free': '当前空闲率',
+  'monitor.server.mem.total': '总内存',
+  'monitor.server.mem.used': '已用内存',
+  'monitor.server.mem.free': '剩余内存',
+  'monitor.server.mem.usage': '使用率',
+  'monitor.server.disk.dirName': '盘符路径',
+  'monitor.server.disk.sysTypeName': '文件系统',
+  'monitor.server.disk.typeName': '盘符类型',
+  'monitor.server.disk.total': '总大小',
+  'monitor.server.disk.free': '可用大小',
+  'monitor.server.disk.used': '已用大小',
+  'monitor.server.disk.usage': '使用率',
+};
diff --git a/react-ui/src/locales/zh-CN/pages.ts b/react-ui/src/locales/zh-CN/pages.ts
new file mode 100644
index 0000000..fc7abfb
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/pages.ts
@@ -0,0 +1,71 @@
+export default {
+  'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范',
+  'pages.login.accountLogin.tab': '账户密码登录',
+  'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/admin123)',
+  'pages.login.failure': '登录失败,请重试!',
+  'pages.login.success': '登录成功!',
+  'pages.login.username.placeholder': '用户名: admin',
+  'pages.login.username.required': '用户名是必填项!',
+  'pages.login.password.placeholder': '密码: admin123',
+  'pages.login.password.required': '密码是必填项!',
+  'pages.login.phoneLogin.tab': '手机号登录',
+  'pages.login.phoneLogin.errorMessage': '验证码错误',
+  'pages.login.phoneNumber.placeholder': '请输入手机号!',
+  'pages.login.phoneNumber.required': '手机号是必填项!',
+  'pages.login.phoneNumber.invalid': '不合法的手机号!',
+  'pages.login.captcha.placeholder': '请输入验证码!',
+  'pages.login.captcha.required': '验证码是必填项!',
+  'pages.login.phoneLogin.getVerificationCode': '获取验证码',
+  'pages.getCaptchaSecondText': '秒后重新获取',
+  'pages.login.rememberMe': '自动登录',
+  'pages.login.forgotPassword': '忘记密码 ?',
+  'pages.login.submit': '登录',
+  'pages.login.loginWith': '其他登录方式 :',
+  'pages.login.registerAccount': '注册账户',
+  'pages.goback': '返回',
+  'pages.welcome.link': '欢迎使用',
+  'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。',
+  'pages.admin.subPage.title': ' 这个页面只有 admin 权限才能查看',
+  'pages.admin.subPage.alertMessage': 'umi ui 现已发布,欢迎使用 npm run ui 启动体验。',
+  'pages.searchTable.createForm.newRule': '新建规则',
+  'pages.searchTable.updateForm.ruleConfig': '规则配置',
+  'pages.searchTable.updateForm.basicConfig': '基本信息',
+  'pages.searchTable.updateForm.ruleName.nameLabel': '规则名称',
+  'pages.searchTable.updateForm.ruleName.nameRules': '请输入规则名称!',
+  'pages.searchTable.updateForm.ruleDesc.descLabel': '规则描述',
+  'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '请输入至少五个字符',
+  'pages.searchTable.updateForm.ruleDesc.descRules': '请输入至少五个字符的规则描述!',
+  'pages.searchTable.updateForm.ruleProps.title': '配置规则属性',
+  'pages.searchTable.updateForm.object': '监控对象',
+  'pages.searchTable.updateForm.ruleProps.templateLabel': '规则模板',
+  'pages.searchTable.updateForm.ruleProps.typeLabel': '规则类型',
+  'pages.searchTable.updateForm.schedulingPeriod.title': '设定调度周期',
+  'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '开始时间',
+  'pages.searchTable.updateForm.schedulingPeriod.timeRules': '请选择开始时间!',
+  'pages.searchTable.updateForm.pleaseInput': '请输入',
+  'pages.searchTable.titleDesc': '描述',
+  'pages.searchTable.ruleName': '规则名称为必填项',
+  'pages.searchTable.titleCallNo': '服务调用次数',
+  'pages.searchTable.titleStatus': '状态',
+  'pages.searchTable.nameStatus.default': '关闭',
+  'pages.searchTable.nameStatus.running': '运行中',
+  'pages.searchTable.nameStatus.online': '已上线',
+  'pages.searchTable.nameStatus.abnormal': '异常',
+  'pages.searchTable.titleUpdatedAt': '上次调度时间',
+  'pages.searchTable.exception': '请输入异常原因!',
+  'pages.searchTable.titleOption': '操作',
+  'pages.searchTable.config': '配置',
+  'pages.searchTable.subscribeAlert': '订阅警报',
+  'pages.searchTable.title': '查询表格',
+  'pages.searchTable.new': '新建',
+  'pages.searchTable.edit': '编辑',
+  'pages.searchTable.delete': '删除',
+  'pages.searchTable.cleanAll': '清空',
+  'pages.searchTable.export': '导出',
+  'pages.searchTable.chosen': '已选择',
+  'pages.searchTable.item': '项',
+  'pages.searchTable.totalServiceCalls': '服务调用次数总计',
+  'pages.searchTable.tenThousand': '万',
+  'pages.searchTable.batchDeletion': '批量删除',
+  'pages.searchTable.batchApproval': '批量审批',
+};
diff --git a/react-ui/src/locales/zh-CN/pwa.ts b/react-ui/src/locales/zh-CN/pwa.ts
new file mode 100644
index 0000000..e950484
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': '当前处于离线状态',
+  'app.pwa.serviceworker.updated': '有新内容',
+  'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
+  'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/react-ui/src/locales/zh-CN/settingDrawer.ts b/react-ui/src/locales/zh-CN/settingDrawer.ts
new file mode 100644
index 0000000..3f44958
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+  'app.setting.pagestyle': '整体风格设置',
+  'app.setting.pagestyle.dark': '暗色菜单风格',
+  'app.setting.pagestyle.light': '亮色菜单风格',
+  'app.setting.content-width': '内容区域宽度',
+  'app.setting.content-width.fixed': '定宽',
+  'app.setting.content-width.fluid': '流式',
+  'app.setting.themecolor': '主题色',
+  'app.setting.themecolor.dust': '薄暮',
+  'app.setting.themecolor.volcano': '火山',
+  'app.setting.themecolor.sunset': '日暮',
+  'app.setting.themecolor.cyan': '明青',
+  'app.setting.themecolor.green': '极光绿',
+  'app.setting.themecolor.daybreak': '拂晓蓝(默认)',
+  'app.setting.themecolor.geekblue': '极客蓝',
+  'app.setting.themecolor.purple': '酱紫',
+  'app.setting.navigationmode': '导航模式',
+  'app.setting.sidemenu': '侧边菜单布局',
+  'app.setting.topmenu': '顶部菜单布局',
+  'app.setting.fixedheader': '固定 Header',
+  'app.setting.fixedsidebar': '固定侧边菜单',
+  'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置',
+  'app.setting.hideheader': '下滑时隐藏 Header',
+  'app.setting.hideheader.hint': '固定 Header 时可配置',
+  'app.setting.othersettings': '其他设置',
+  'app.setting.weakmode': '色弱模式',
+  'app.setting.copy': '拷贝设置',
+  'app.setting.copyinfo': '拷贝成功,请到 config/defaultSettings.js 中替换默认配置',
+  'app.setting.production.hint':
+    '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
+};
diff --git a/react-ui/src/locales/zh-CN/settings.ts b/react-ui/src/locales/zh-CN/settings.ts
new file mode 100644
index 0000000..df8af43
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/settings.ts
@@ -0,0 +1,55 @@
+export default {
+  'app.settings.menuMap.basic': '基本设置',
+  'app.settings.menuMap.security': '安全设置',
+  'app.settings.menuMap.binding': '账号绑定',
+  'app.settings.menuMap.notification': '新消息通知',
+  'app.settings.basic.avatar': '头像',
+  'app.settings.basic.change-avatar': '更换头像',
+  'app.settings.basic.email': '邮箱',
+  'app.settings.basic.email-message': '请输入您的邮箱!',
+  'app.settings.basic.nickname': '昵称',
+  'app.settings.basic.nickname-message': '请输入您的昵称!',
+  'app.settings.basic.profile': '个人简介',
+  'app.settings.basic.profile-message': '请输入个人简介!',
+  'app.settings.basic.profile-placeholder': '个人简介',
+  'app.settings.basic.country': '国家/地区',
+  'app.settings.basic.country-message': '请输入您的国家或地区!',
+  'app.settings.basic.geographic': '所在省市',
+  'app.settings.basic.geographic-message': '请输入您的所在省市!',
+  'app.settings.basic.address': '街道地址',
+  'app.settings.basic.address-message': '请输入您的街道地址!',
+  'app.settings.basic.phone': '联系电话',
+  'app.settings.basic.phone-message': '请输入您的联系电话!',
+  'app.settings.basic.update': '更新基本信息',
+  'app.settings.security.strong': '强',
+  'app.settings.security.medium': '中',
+  'app.settings.security.weak': '弱',
+  'app.settings.security.password': '账户密码',
+  'app.settings.security.password-description': '当前密码强度',
+  'app.settings.security.phone': '密保手机',
+  'app.settings.security.phone-description': '已绑定手机',
+  'app.settings.security.question': '密保问题',
+  'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
+  'app.settings.security.email': '备用邮箱',
+  'app.settings.security.email-description': '已绑定邮箱',
+  'app.settings.security.mfa': 'MFA 设备',
+  'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
+  'app.settings.security.modify': '修改',
+  'app.settings.security.set': '设置',
+  'app.settings.security.bind': '绑定',
+  'app.settings.binding.taobao': '绑定淘宝',
+  'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
+  'app.settings.binding.alipay': '绑定支付宝',
+  'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
+  'app.settings.binding.dingding': '绑定钉钉',
+  'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
+  'app.settings.binding.bind': '绑定',
+  'app.settings.notification.password': '账户密码',
+  'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
+  'app.settings.notification.messages': '系统消息',
+  'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
+  'app.settings.notification.todo': '待办任务',
+  'app.settings.notification.todo-description': '待办任务将以站内信的形式通知',
+  'app.settings.open': '开',
+  'app.settings.close': '关',
+};
diff --git a/react-ui/src/locales/zh-CN/system/config.ts b/react-ui/src/locales/zh-CN/system/config.ts
new file mode 100644
index 0000000..5e1e764
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/config.ts
@@ -0,0 +1,14 @@
+export default {
+	'system.config.title': '参数配置',
+	'system.config.config_id': '参数主键',
+	'system.config.config_name': '参数名称',
+	'system.config.config_key': '参数键名',
+	'system.config.config_value': '参数键值',
+	'system.config.config_type': '系统内置',
+	'system.config.create_by': '创建者',
+	'system.config.create_time': '创建时间',
+	'system.config.update_by': '更新者',
+	'system.config.update_time': '更新时间',
+	'system.config.remark': '备注',
+	'system.config.refreshCache': '刷新缓存',
+};
diff --git a/react-ui/src/locales/zh-CN/system/dept.ts b/react-ui/src/locales/zh-CN/system/dept.ts
new file mode 100644
index 0000000..7774f2c
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/dept.ts
@@ -0,0 +1,18 @@
+export default {
+	'system.dept.title': '部门',
+	'system.dept.dept_id': '部门id',
+	'system.dept.parent_id': '父部门id',
+	'system.dept.parent_dept': '上级部门',
+	'system.dept.ancestors': '祖级列表',
+	'system.dept.dept_name': '部门名称',
+	'system.dept.order_num': '显示顺序',
+	'system.dept.leader': '负责人',
+	'system.dept.phone': '联系电话',
+	'system.dept.email': '邮箱',
+	'system.dept.status': '部门状态',
+	'system.dept.del_flag': '删除标志',
+	'system.dept.create_by': '创建者',
+	'system.dept.create_time': '创建时间',
+	'system.dept.update_by': '更新者',
+	'system.dept.update_time': '更新时间',
+};
diff --git a/react-ui/src/locales/zh-CN/system/dict-data.ts b/react-ui/src/locales/zh-CN/system/dict-data.ts
new file mode 100644
index 0000000..db2c742
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/dict-data.ts
@@ -0,0 +1,17 @@
+export default {
+  'system.dict.data.title': '字典数据',
+  'system.dict.data.dict_code': '字典编码',
+  'system.dict.data.dict_sort': '字典排序',
+  'system.dict.data.dict_label': '字典标签',
+  'system.dict.data.dict_value': '字典键值',
+  'system.dict.data.dict_type': '字典类型',
+  'system.dict.data.css_class': '样式属性',
+  'system.dict.data.list_class': '回显样式',
+  'system.dict.data.is_default': '是否默认',
+  'system.dict.data.status': '状态',
+  'system.dict.data.create_by': '创建者',
+  'system.dict.data.create_time': '创建时间',
+  'system.dict.data.update_by': '更新者',
+  'system.dict.data.update_time': '更新时间',
+  'system.dict.data.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/dict.ts b/react-ui/src/locales/zh-CN/system/dict.ts
new file mode 100644
index 0000000..cf00f66
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/dict.ts
@@ -0,0 +1,12 @@
+export default {
+  'system.dict.title': '字典类型',
+  'system.dict.dict_id': '字典主键',
+  'system.dict.dict_name': '字典名称',
+  'system.dict.dict_type': '字典类型',
+  'system.dict.status': '状态',
+  'system.dict.create_by': '创建者',
+  'system.dict.create_time': '创建时间',
+  'system.dict.update_by': '更新者',
+  'system.dict.update_time': '更新时间',
+  'system.dict.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/menu.ts b/react-ui/src/locales/zh-CN/system/menu.ts
new file mode 100644
index 0000000..8a01e58
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/menu.ts
@@ -0,0 +1,22 @@
+export default {
+	'system.menu.title': '菜单权限',
+	'system.menu.menu_id': '菜单编号',
+	'system.menu.menu_name': '菜单名称',
+	'system.menu.parent_id': '上级菜单',
+	'system.menu.order_num': '显示顺序',
+	'system.menu.path': '路由地址',
+	'system.menu.component': '组件路径',
+	'system.menu.query': '路由参数',
+	'system.menu.is_frame': '是否为外链',
+	'system.menu.is_cache': '是否缓存',
+	'system.menu.menu_type': '菜单类型',
+	'system.menu.visible': '显示状态',
+	'system.menu.status': '菜单状态',
+	'system.menu.perms': '权限标识',
+	'system.menu.icon': '菜单图标',
+	'system.menu.create_by': '创建者',
+	'system.menu.create_time': '创建时间',
+	'system.menu.update_by': '更新者',
+	'system.menu.update_time': '更新时间',
+	'system.menu.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/notice.ts b/react-ui/src/locales/zh-CN/system/notice.ts
new file mode 100644
index 0000000..ad55d51
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/notice.ts
@@ -0,0 +1,13 @@
+export default {
+	'system.notice.title': '通知公告',
+	'system.notice.notice_id': '公告编号',
+	'system.notice.notice_title': '公告标题',
+	'system.notice.notice_type': '公告类型',
+	'system.notice.notice_content': '公告内容',
+	'system.notice.status': '公告状态',
+	'system.notice.create_by': '创建者',
+	'system.notice.create_time': '创建时间',
+	'system.notice.update_by': '更新者',
+	'system.notice.update_time': '更新时间',
+	'system.notice.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/post.ts b/react-ui/src/locales/zh-CN/system/post.ts
new file mode 100644
index 0000000..40e589b
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/post.ts
@@ -0,0 +1,13 @@
+export default {
+	'system.post.title': '岗位信息',
+	'system.post.post_id': '岗位编号',
+	'system.post.post_code': '岗位编码',
+	'system.post.post_name': '岗位名称',
+	'system.post.post_sort': '显示顺序',
+	'system.post.status': '状态',
+	'system.post.create_by': '创建者',
+	'system.post.create_time': '创建时间',
+	'system.post.update_by': '更新者',
+	'system.post.update_time': '更新时间',
+	'system.post.remark': '备注',
+};
diff --git a/react-ui/src/locales/zh-CN/system/role.ts b/react-ui/src/locales/zh-CN/system/role.ts
new file mode 100644
index 0000000..e88f979
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/role.ts
@@ -0,0 +1,21 @@
+export default {
+	'system.role.title': '角色信息',
+	'system.role.role_id': '角色编号',
+	'system.role.role_name': '角色名称',
+	'system.role.role_key': '权限字符',
+	'system.role.role_sort': '显示顺序',
+	'system.role.data_scope': '数据范围',
+	'system.role.menu_check_strictly': '菜单树选择项是否关联显示',
+	'system.role.dept_check_strictly': '部门树选择项是否关联显示',
+	'system.role.status': '角色状态',
+	'system.role.del_flag': '删除标志',
+	'system.role.create_by': '创建者',
+	'system.role.create_time': '创建时间',
+	'system.role.update_by': '更新者',
+	'system.role.update_time': '更新时间',
+	'system.role.remark': '备注',
+	'system.role.auth': '菜单权限',
+	'system.role.auth.user': '选择用户',
+	'system.role.auth.addUser': '添加用户',
+	'system.role.auth.cancelAll': '批量取消授权',
+};
diff --git a/react-ui/src/locales/zh-CN/system/user.ts b/react-ui/src/locales/zh-CN/system/user.ts
new file mode 100644
index 0000000..7d676d0
--- /dev/null
+++ b/react-ui/src/locales/zh-CN/system/user.ts
@@ -0,0 +1,31 @@
+export default {
+	'system.user.title': '用户信息',
+	'system.user.user_id': '用户编号',
+	'system.user.dept_name': '部门',
+	'system.user.user_name': '用户账号',
+	'system.user.nick_name': '用户昵称',
+	'system.user.user_type': '用户类型',
+	'system.user.email': '用户邮箱',
+	'system.user.phonenumber': '手机号码',
+	'system.user.sex': '用户性别',
+	'system.user.avatar': '头像地址',
+	'system.user.password': '密码',
+	'system.user.status': '帐号状态',
+	'system.user.del_flag': '删除标志',
+	'system.user.login_ip': '最后登录IP',
+	'system.user.login_date': '最后登录时间',
+	'system.user.create_by': '创建者',
+	'system.user.create_time': '创建时间',
+	'system.user.update_by': '更新者',
+	'system.user.update_time': '更新时间',
+	'system.user.remark': '备注',
+	'system.user.post': '岗位',
+	'system.user.role': '角色',
+	'system.user.auth.role': '分配角色',
+	'system.user.reset.password': '密码重置',
+	'system.user.modify_info': '编辑用户信息',
+	'system.user.old_password': '旧密码',
+	'system.user.new_password': '新密码',
+	'system.user.confirm_password': '确认密码',
+	'system.user.modify_avatar': '修改头像',
+};
diff --git a/react-ui/src/locales/zh-TW.ts b/react-ui/src/locales/zh-TW.ts
new file mode 100644
index 0000000..6ad5f93
--- /dev/null
+++ b/react-ui/src/locales/zh-TW.ts
@@ -0,0 +1,20 @@
+import component from './zh-TW/component';
+import globalHeader from './zh-TW/globalHeader';
+import menu from './zh-TW/menu';
+import pwa from './zh-TW/pwa';
+import settingDrawer from './zh-TW/settingDrawer';
+import settings from './zh-TW/settings';
+
+export default {
+  'navBar.lang': '語言',
+  'layout.user.link.help': '幫助',
+  'layout.user.link.privacy': '隱私',
+  'layout.user.link.terms': '條款',
+  'app.preview.down.block': '下載此頁面到本地項目',
+  ...globalHeader,
+  ...menu,
+  ...settingDrawer,
+  ...settings,
+  ...pwa,
+  ...component,
+};
diff --git a/react-ui/src/locales/zh-TW/component.ts b/react-ui/src/locales/zh-TW/component.ts
new file mode 100644
index 0000000..ba48e29
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/component.ts
@@ -0,0 +1,5 @@
+export default {
+  'component.tagSelect.expand': '展開',
+  'component.tagSelect.collapse': '收起',
+  'component.tagSelect.all': '全部',
+};
diff --git a/react-ui/src/locales/zh-TW/globalHeader.ts b/react-ui/src/locales/zh-TW/globalHeader.ts
new file mode 100644
index 0000000..ed58451
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/globalHeader.ts
@@ -0,0 +1,17 @@
+export default {
+  'component.globalHeader.search': '站內搜索',
+  'component.globalHeader.search.example1': '搜索提示壹',
+  'component.globalHeader.search.example2': '搜索提示二',
+  'component.globalHeader.search.example3': '搜索提示三',
+  'component.globalHeader.help': '使用手冊',
+  'component.globalHeader.notification': '通知',
+  'component.globalHeader.notification.empty': '妳已查看所有通知',
+  'component.globalHeader.message': '消息',
+  'component.globalHeader.message.empty': '您已讀完所有消息',
+  'component.globalHeader.event': '待辦',
+  'component.globalHeader.event.empty': '妳已完成所有待辦',
+  'component.noticeIcon.clear': '清空',
+  'component.noticeIcon.cleared': '清空了',
+  'component.noticeIcon.empty': '暫無資料',
+  'component.noticeIcon.view-more': '查看更多',
+};
diff --git a/react-ui/src/locales/zh-TW/menu.ts b/react-ui/src/locales/zh-TW/menu.ts
new file mode 100644
index 0000000..0ef54c9
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/menu.ts
@@ -0,0 +1,52 @@
+export default {
+  'menu.welcome': '歡迎',
+  'menu.more-blocks': '更多區塊',
+  'menu.home': '首頁',
+  'menu.admin': '权限',
+  'menu.admin.sub-page': '二级管理页',
+  'menu.login': '登錄',
+  'menu.register': '註冊',
+  'menu.register-result': '註冊結果',
+  'menu.dashboard': 'Dashboard',
+  'menu.dashboard.analysis': '分析頁',
+  'menu.dashboard.monitor': '監控頁',
+  'menu.dashboard.workplace': '工作臺',
+  'menu.exception.403': '403',
+  'menu.exception.404': '404',
+  'menu.exception.500': '500',
+  'menu.form': '表單頁',
+  'menu.form.basic-form': '基礎表單',
+  'menu.form.step-form': '分步表單',
+  'menu.form.step-form.info': '分步表單(填寫轉賬信息)',
+  'menu.form.step-form.confirm': '分步表單(確認轉賬信息)',
+  'menu.form.step-form.result': '分步表單(完成)',
+  'menu.form.advanced-form': '高級表單',
+  'menu.list': '列表頁',
+  'menu.list.table-list': '查詢表格',
+  'menu.list.basic-list': '標淮列表',
+  'menu.list.card-list': '卡片列表',
+  'menu.list.search-list': '搜索列表',
+  'menu.list.search-list.articles': '搜索列表(文章)',
+  'menu.list.search-list.projects': '搜索列表(項目)',
+  'menu.list.search-list.applications': '搜索列表(應用)',
+  'menu.profile': '詳情頁',
+  'menu.profile.basic': '基礎詳情頁',
+  'menu.profile.advanced': '高級詳情頁',
+  'menu.result': '結果頁',
+  'menu.result.success': '成功頁',
+  'menu.result.fail': '失敗頁',
+  'menu.exception': '异常页',
+  'menu.exception.not-permission': '403',
+  'menu.exception.not-find': '404',
+  'menu.exception.server-error': '500',
+  'menu.exception.trigger': '触发错误',
+  'menu.account': '個人頁',
+  'menu.account.center': '個人中心',
+  'menu.account.settings': '個人設置',
+  'menu.account.trigger': '觸發報錯',
+  'menu.account.logout': '退出登錄',
+  'menu.editor': '圖形編輯器',
+  'menu.editor.flow': '流程編輯器',
+  'menu.editor.mind': '腦圖編輯器',
+  'menu.editor.koni': '拓撲編輯器',
+};
diff --git a/react-ui/src/locales/zh-TW/pages.ts b/react-ui/src/locales/zh-TW/pages.ts
new file mode 100644
index 0000000..f9e265b
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/pages.ts
@@ -0,0 +1,68 @@
+export default {
+  'pages.layouts.userLayout.title': 'Ant Design 是西湖區最具影響力的 Web 設計規範',
+  'pages.login.accountLogin.tab': '賬戶密碼登錄',
+  'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼(admin/admin123)',
+  'pages.login.failure': '登錄失敗,請重試!',
+  'pages.login.success': '登錄成功!',
+  'pages.login.username.placeholder': '用戶名: admin',
+  'pages.login.username.required': '用戶名是必填項!',
+  'pages.login.password.placeholder': '密碼: admin123',
+  'pages.login.password.required': '密碼是必填項!',
+  'pages.login.phoneLogin.tab': '手機號登錄',
+  'pages.login.phoneLogin.errorMessage': '驗證碼錯誤',
+  'pages.login.phoneNumber.placeholder': '請輸入手機號!',
+  'pages.login.phoneNumber.required': '手機號是必填項!',
+  'pages.login.phoneNumber.invalid': '不合法的手機號!',
+  'pages.login.captcha.placeholder': '請輸入驗證碼!',
+  'pages.login.captcha.required': '驗證碼是必填項!',
+  'pages.login.phoneLogin.getVerificationCode': '獲取驗證碼',
+  'pages.getCaptchaSecondText': '秒後重新獲取',
+  'pages.login.rememberMe': '自動登錄',
+  'pages.login.forgotPassword': '忘記密碼 ?',
+  'pages.login.submit': '登錄',
+  'pages.login.loginWith': '其他登錄方式 :',
+  'pages.login.registerAccount': '註冊賬戶',
+  'pages.welcome.link': '歡迎使用',
+  'pages.welcome.alertMessage': '更快更強的重型組件,已經發布。',
+  'pages.admin.subPage.title': '這個頁面只有 admin 權限才能查看',
+  'pages.admin.subPage.alertMessage': 'umi ui 現已發佈,歡迎使用 npm run ui 啓動體驗。',
+  'pages.searchTable.createForm.newRule': '新建規則',
+  'pages.searchTable.updateForm.ruleConfig': '規則配置',
+  'pages.searchTable.updateForm.basicConfig': '基本信息',
+  'pages.searchTable.updateForm.ruleName.nameLabel': '規則名稱',
+  'pages.searchTable.updateForm.ruleName.nameRules': '請輸入規則名稱!',
+  'pages.searchTable.updateForm.ruleDesc.descLabel': '規則描述',
+  'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '請輸入至少五個字符',
+  'pages.searchTable.updateForm.ruleDesc.descRules': '請輸入至少五個字符的規則描述!',
+  'pages.searchTable.updateForm.ruleProps.title': '配置規則屬性',
+  'pages.searchTable.updateForm.object': '監控對象',
+  'pages.searchTable.updateForm.ruleProps.templateLabel': '規則模板',
+  'pages.searchTable.updateForm.ruleProps.typeLabel': '規則類型',
+  'pages.searchTable.updateForm.schedulingPeriod.title': '設定調度週期',
+  'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '開始時間',
+  'pages.searchTable.updateForm.schedulingPeriod.timeRules': '請選擇開始時間!',
+  'pages.searchTable.titleDesc': '描述',
+  'pages.searchTable.ruleName': '規則名稱爲必填項',
+  'pages.searchTable.titleCallNo': '服務調用次數',
+  'pages.searchTable.titleStatus': '狀態',
+  'pages.searchTable.nameStatus.default': '關閉',
+  'pages.searchTable.nameStatus.running': '運行中',
+  'pages.searchTable.nameStatus.online': '已上線',
+  'pages.searchTable.nameStatus.abnormal': '異常',
+  'pages.searchTable.titleUpdatedAt': '上次調度時間',
+  'pages.searchTable.exception': '請輸入異常原因!',
+  'pages.searchTable.titleOption': '操作',
+  'pages.searchTable.config': '配置',
+  'pages.searchTable.subscribeAlert': '訂閱警報',
+  'pages.searchTable.title': '查詢表格',
+  'pages.searchTable.new': '新建',
+  'pages.searchTable.edit': '編輯',
+  'pages.searchTable.delete': '刪除',
+  'pages.searchTable.export': '導出',
+  'pages.searchTable.chosen': '已選擇',
+  'pages.searchTable.item': '項',
+  'pages.searchTable.totalServiceCalls': '服務調用次數總計',
+  'pages.searchTable.tenThousand': '萬',
+  'pages.searchTable.batchDeletion': '批量刪除',
+  'pages.searchTable.batchApproval': '批量審批',
+};
diff --git a/react-ui/src/locales/zh-TW/pwa.ts b/react-ui/src/locales/zh-TW/pwa.ts
new file mode 100644
index 0000000..108a6e4
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/pwa.ts
@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': '當前處於離線狀態',
+  'app.pwa.serviceworker.updated': '有新內容',
+  'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
+  'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/react-ui/src/locales/zh-TW/settingDrawer.ts b/react-ui/src/locales/zh-TW/settingDrawer.ts
new file mode 100644
index 0000000..454da28
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/settingDrawer.ts
@@ -0,0 +1,31 @@
+export default {
+  'app.setting.pagestyle': '整體風格設置',
+  'app.setting.pagestyle.dark': '暗色菜單風格',
+  'app.setting.pagestyle.light': '亮色菜單風格',
+  'app.setting.content-width': '內容區域寬度',
+  'app.setting.content-width.fixed': '定寬',
+  'app.setting.content-width.fluid': '流式',
+  'app.setting.themecolor': '主題色',
+  'app.setting.themecolor.dust': '薄暮',
+  'app.setting.themecolor.volcano': '火山',
+  'app.setting.themecolor.sunset': '日暮',
+  'app.setting.themecolor.cyan': '明青',
+  'app.setting.themecolor.green': '極光綠',
+  'app.setting.themecolor.daybreak': '拂曉藍(默認)',
+  'app.setting.themecolor.geekblue': '極客藍',
+  'app.setting.themecolor.purple': '醬紫',
+  'app.setting.navigationmode': '導航模式',
+  'app.setting.sidemenu': '側邊菜單布局',
+  'app.setting.topmenu': '頂部菜單布局',
+  'app.setting.fixedheader': '固定 Header',
+  'app.setting.fixedsidebar': '固定側邊菜單',
+  'app.setting.fixedsidebar.hint': '側邊菜單布局時可配置',
+  'app.setting.hideheader': '下滑時隱藏 Header',
+  'app.setting.hideheader.hint': '固定 Header 時可配置',
+  'app.setting.othersettings': '其他設置',
+  'app.setting.weakmode': '色弱模式',
+  'app.setting.copy': '拷貝設置',
+  'app.setting.copyinfo': '拷貝成功,請到 config/defaultSettings.js 中替換默認配置',
+  'app.setting.production.hint':
+    '配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件',
+};
diff --git a/react-ui/src/locales/zh-TW/settings.ts b/react-ui/src/locales/zh-TW/settings.ts
new file mode 100644
index 0000000..dd45151
--- /dev/null
+++ b/react-ui/src/locales/zh-TW/settings.ts
@@ -0,0 +1,55 @@
+export default {
+  'app.settings.menuMap.basic': '基本設置',
+  'app.settings.menuMap.security': '安全設置',
+  'app.settings.menuMap.binding': '賬號綁定',
+  'app.settings.menuMap.notification': '新消息通知',
+  'app.settings.basic.avatar': '頭像',
+  'app.settings.basic.change-avatar': '更換頭像',
+  'app.settings.basic.email': '郵箱',
+  'app.settings.basic.email-message': '請輸入您的郵箱!',
+  'app.settings.basic.nickname': '昵稱',
+  'app.settings.basic.nickname-message': '請輸入您的昵稱!',
+  'app.settings.basic.profile': '個人簡介',
+  'app.settings.basic.profile-message': '請輸入個人簡介!',
+  'app.settings.basic.profile-placeholder': '個人簡介',
+  'app.settings.basic.country': '國家/地區',
+  'app.settings.basic.country-message': '請輸入您的國家或地區!',
+  'app.settings.basic.geographic': '所在省市',
+  'app.settings.basic.geographic-message': '請輸入您的所在省市!',
+  'app.settings.basic.address': '街道地址',
+  'app.settings.basic.address-message': '請輸入您的街道地址!',
+  'app.settings.basic.phone': '聯系電話',
+  'app.settings.basic.phone-message': '請輸入您的聯系電話!',
+  'app.settings.basic.update': '更新基本信息',
+  'app.settings.security.strong': '強',
+  'app.settings.security.medium': '中',
+  'app.settings.security.weak': '弱',
+  'app.settings.security.password': '賬戶密碼',
+  'app.settings.security.password-description': '當前密碼強度',
+  'app.settings.security.phone': '密保手機',
+  'app.settings.security.phone-description': '已綁定手機',
+  'app.settings.security.question': '密保問題',
+  'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
+  'app.settings.security.email': '備用郵箱',
+  'app.settings.security.email-description': '已綁定郵箱',
+  'app.settings.security.mfa': 'MFA 設備',
+  'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
+  'app.settings.security.modify': '修改',
+  'app.settings.security.set': '設置',
+  'app.settings.security.bind': '綁定',
+  'app.settings.binding.taobao': '綁定淘寶',
+  'app.settings.binding.taobao-description': '當前未綁定淘寶賬號',
+  'app.settings.binding.alipay': '綁定支付寶',
+  'app.settings.binding.alipay-description': '當前未綁定支付寶賬號',
+  'app.settings.binding.dingding': '綁定釘釘',
+  'app.settings.binding.dingding-description': '當前未綁定釘釘賬號',
+  'app.settings.binding.bind': '綁定',
+  'app.settings.notification.password': '賬戶密碼',
+  'app.settings.notification.password-description': '其他用戶的消息將以站內信的形式通知',
+  'app.settings.notification.messages': '系統消息',
+  'app.settings.notification.messages-description': '系統消息將以站內信的形式通知',
+  'app.settings.notification.todo': '待辦任務',
+  'app.settings.notification.todo-description': '待辦任務將以站內信的形式通知',
+  'app.settings.open': '開',
+  'app.settings.close': '關',
+};
diff --git a/react-ui/src/manifest.json b/react-ui/src/manifest.json
new file mode 100644
index 0000000..839bc5b
--- /dev/null
+++ b/react-ui/src/manifest.json
@@ -0,0 +1,22 @@
+{
+  "name": "Ant Design Pro",
+  "short_name": "Ant Design Pro",
+  "display": "standalone",
+  "start_url": "./?utm_source=homescreen",
+  "theme_color": "#002140",
+  "background_color": "#001529",
+  "icons": [
+    {
+      "src": "icons/icon-192x192.png",
+      "sizes": "192x192"
+    },
+    {
+      "src": "icons/icon-128x128.png",
+      "sizes": "128x128"
+    },
+    {
+      "src": "icons/icon-512x512.png",
+      "sizes": "512x512"
+    }
+  ]
+}
diff --git a/react-ui/src/pages/404.tsx b/react-ui/src/pages/404.tsx
new file mode 100644
index 0000000..0263687
--- /dev/null
+++ b/react-ui/src/pages/404.tsx
@@ -0,0 +1,18 @@
+import { history } from '@umijs/max';
+import { Button, Result } from 'antd';
+import React from 'react';
+
+const NoFoundPage: React.FC = () => (
+  <Result
+    status="404"
+    title="404"
+    subTitle="Sorry, the page you visited does not exist."
+    extra={
+      <Button type="primary" onClick={() => history.push('/')}>
+        Back Home
+      </Button>
+    }
+  />
+);
+
+export default NoFoundPage;
diff --git a/react-ui/src/pages/Monitor/Cache/List.tsx b/react-ui/src/pages/Monitor/Cache/List.tsx
new file mode 100644
index 0000000..af6147a
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Cache/List.tsx
@@ -0,0 +1,240 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { clearCacheAll, clearCacheKey, clearCacheName, getCacheValue, listCacheKey, listCacheName } from '@/services/monitor/cachelist';
+import { Button, Card, Col, Form, FormInstance, Input, message, Row, Table } from 'antd';
+import styles from './index.less';
+import { FormattedMessage } from '@umijs/max';
+import { ReloadOutlined } from '@ant-design/icons';
+import { ProForm } from '@ant-design/pro-components';
+
+const { TextArea } = Input;
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2022/06/27
+ *
+ * */
+
+
+
+const CacheList: React.FC = () => {
+  const [cacheNames, setCacheNames] = useState<any>([]);
+  const [currentCacheName, setCurrentCacheName] = useState<any>([]);
+  const [cacheKeys, setCacheKeys] = useState<any>([]);
+  const [form] = Form.useForm();
+
+  const getCacheNames = () => {
+    listCacheName().then((res) => {
+      if (res.code === 200) {
+        setCacheNames(res.data);
+      }
+    });
+  }
+
+  useEffect(() => {
+    getCacheNames();
+  }, []);
+
+  const getCacheKeys = (cacheName: string) => {
+    listCacheKey(cacheName).then(res => {
+      if (res.code === 200) {
+        let index = 0;
+        const keysData = res.data.map((item: any) => {
+          return {
+            index: index++,
+            cacheKey: item
+          }
+        })
+        setCacheKeys(keysData);
+      }
+    });
+  };
+
+  const onClearAll = async () => {
+    clearCacheAll().then(res => {
+      if(res.code === 200) {
+        message.success("清理全部缓存成功");
+      }
+    });
+  };
+
+  const onClearAllFailed = (errorInfo: any) => {
+    message.error('Failed:', errorInfo);
+  };
+
+  const refreshCacheNames = () => {
+    getCacheNames();
+    message.success("刷新缓存列表成功");
+  };
+
+  const refreshCacheKeys = () => {
+    getCacheKeys(currentCacheName);
+    message.success("刷新键名列表成功");
+  };
+
+  const columns = [
+    {
+      title: '缓存名称',
+      dataIndex: 'cacheName',
+      key: 'cacheName',
+      render: (_: any, record: any) => {
+        return record.cacheName.replace(":", "");
+      }
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      key: 'remark',
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '40px',
+      valueType: 'option',
+      render: (_: any, record: API.Monitor.CacheContent) => [
+        <Button
+          type="link"
+          size="small"
+          key="remove"
+          danger
+          onClick={() => {
+            clearCacheName(record.cacheName).then(res => {
+              if(res.code === 200) {
+                message.success("清理缓存名称[" + record.cacheName + "]成功");
+                getCacheKeys(currentCacheName);
+              }
+            });
+          }}
+        >
+          <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+        </Button>,
+      ]
+    }
+  ];
+
+  const cacheKeysColumns = [
+    {
+      title: '序号',
+      dataIndex: 'index',
+      key: 'index'
+    },
+    {
+      title: '缓存键名',
+      dataIndex: 'cacheKey',
+      key: 'cacheKey',
+      render: (_: any, record: any) => {
+        return record.cacheKey.replace(currentCacheName, "");
+      }
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '40px',
+      valueType: 'option',
+      render: (_: any, record: API.Monitor.CacheContent) => [
+        <Button
+          type="link"
+          size="small"
+          key="remove"
+          danger
+          onClick={() => {
+            console.log(record)
+            clearCacheKey(record.cacheKey).then(res => {
+              if(res.code === 200) {
+                message.success("清理缓存键名[" + record.cacheKey + "]成功");
+                getCacheKeys(currentCacheName);
+              }
+            });
+          }}
+        >
+          <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+        </Button>,
+      ]
+    }
+  ];
+
+  return (
+    <div>
+      <Row gutter={[24, 24]}>
+        <Col span={8}>
+          <Card title="缓存列表" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ refreshCacheNames()}} type="link" />} className={styles.card}>
+            <Table
+              rowKey="cacheName"
+              dataSource={cacheNames}
+              columns={columns}
+              onRow={(record: API.Monitor.CacheContent) => {
+                return {
+                  onClick: () => {
+                    setCurrentCacheName(record.cacheName);
+                    getCacheKeys(record.cacheName);
+                  },
+                };
+              }}
+            />
+          </Card>
+        </Col>
+        <Col span={8}>
+          <Card title="键名列表" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ refreshCacheKeys()}} type="link" />} className={styles.card}>
+            <Table
+              rowKey="index"
+              dataSource={cacheKeys}
+              columns={cacheKeysColumns}
+              onRow={(record: any) => {
+                return {
+                  onClick: () => {
+                    getCacheValue(currentCacheName, record.cacheKey).then(res => {
+                      if (res.code === 200) {
+                        form.resetFields();
+                        form.setFieldsValue({
+                          cacheName: res.data.cacheName,
+                          cacheKey: res.data.cacheKey,
+                          cacheValue: res.data.cacheValue,
+                          remark: res.data.remark,
+                        });
+                      }
+                    });
+                  },
+                };
+              }}
+            />
+          </Card>
+        </Col>
+        <Col span={8}>
+          <Card title="缓存内容" extra={<Button icon={<ReloadOutlined />} onClick={()=>{ onClearAll()}} type="link" >清理全部</Button>} className={styles.card}>
+            <ProForm
+              name="basic"
+              form={form}
+              labelCol={{ span: 8 }}
+              wrapperCol={{ span: 16 }}
+              onFinish={onClearAll}
+              onFinishFailed={onClearAllFailed}
+              autoComplete="off"
+            >
+              <Form.Item
+                label="缓存名称"
+                name="cacheName"
+              >
+                <Input />
+              </Form.Item>
+              <Form.Item
+                label="缓存键名"
+                name="cacheKey"
+              >
+                <Input />
+              </Form.Item>
+              <Form.Item
+                label="缓存内容"
+                name="cacheValue"
+              >
+                <TextArea autoSize={{ minRows: 2 }} />
+              </Form.Item>
+            </ProForm>
+          </Card>
+        </Col>
+      </Row>
+    </div>
+  );
+};
+
+export default CacheList;
diff --git a/react-ui/src/pages/Monitor/Cache/List/index.less b/react-ui/src/pages/Monitor/Cache/List/index.less
new file mode 100644
index 0000000..0c5aace
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Cache/List/index.less
@@ -0,0 +1,10 @@
+
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ *
+ * */
+
diff --git a/react-ui/src/pages/Monitor/Cache/index.less b/react-ui/src/pages/Monitor/Cache/index.less
new file mode 100644
index 0000000..7112c50
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Cache/index.less
@@ -0,0 +1,33 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+ 
+.card {
+  margin-bottom: 12px;
+}
+
+
+.miniChart {
+  position: relative;
+  width: 100%;
+  .chartContent {
+    position: absolute;
+    bottom: -28px;
+    width: 100%;
+    > div {
+      margin: 0 -5px;
+      overflow: hidden;
+    }
+  }
+  .chartLoading {
+    position: absolute;
+    top: 16px;
+    left: 50%;
+    margin-left: -7px;
+  }
+}
diff --git a/react-ui/src/pages/Monitor/Cache/index.tsx b/react-ui/src/pages/Monitor/Cache/index.tsx
new file mode 100644
index 0000000..4c247ee
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Cache/index.tsx
@@ -0,0 +1,201 @@
+import React, { useEffect, useState } from 'react';
+import { Card, Col, Row, Table } from 'antd';
+import { DataItem } from '@antv/g2plot/esm/interface/config';
+import { Gauge, Pie } from '@ant-design/plots';
+import styles from './index.less';
+import { getCacheInfo } from '@/services/monitor/cache';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ *
+ * */
+
+
+const columns = [
+  {
+    title: 'col1',
+    dataIndex: 'col1',
+    key: 'col1',
+  },
+  {
+    title: 'col2',
+    dataIndex: 'col2',
+    key: 'col2',
+  },
+  {
+    title: 'col3',
+    dataIndex: 'col3',
+    key: 'col3',
+  },
+  {
+    title: 'col4',
+    dataIndex: 'col4',
+    key: 'col4',
+  },
+  {
+    title: 'col5',
+    dataIndex: 'col5',
+    key: 'col5',
+  },
+  {
+    title: 'col6',
+    dataIndex: 'col6',
+    key: 'col6',
+  },
+  {
+    title: 'col7',
+    dataIndex: 'col7',
+    key: 'col7',
+  },
+  {
+    title: 'col8',
+    dataIndex: 'col8',
+    key: 'col8',
+  },
+];
+
+const usageFormatter = (val: string): string => {
+  switch (val) {
+    case '10':
+      return '100%';
+    case '8':
+      return '80%';
+    case '6':
+      return '60%';
+    case '4':
+      return '40%';
+    case '2':
+      return '20%';
+    case '0':
+      return '0%';
+    default:
+      return '';
+  }
+};
+
+const CacheInfo: React.FC = () => {
+  const [baseInfoData, setBaseInfoData] = useState<any>([]);
+  const [memUsage, setMemUsage] = useState<Number>(0);
+  const [memUsageTitle, setMemUsageTitle] = useState<any>([]);
+  const [cmdInfoData, setCmdInfoData] = useState<DataItem[]>([]);
+  useEffect(() => {
+    getCacheInfo().then((res) => {
+      if (res.code === 200) {
+        const baseinfo = [];
+        baseinfo.push({
+          col1: 'Redis版本',
+          col2: res.data.info.redis_version,
+          col3: '运行模式',
+          col4: res.data.info.redis_mode === 'standalone' ? '单机' : '集群',
+          col5: '端口',
+          col6: res.data.info.tcp_port,
+          col7: '客户端数',
+          col8: res.data.info.connected_clients,
+        });
+        baseinfo.push({
+          col1: '运行时间(天)',
+          col2: res.data.info.uptime_in_days,
+          col3: '使用内存',
+          col4: res.data.info.used_memory_human,
+          col5: '使用CPU',
+          col6: `${res.data.info.used_cpu_user_children}%`,
+          col7: '内存配置',
+          col8: res.data.info.maxmemory_human,
+        });
+        baseinfo.push({
+          col1: 'AOF是否开启',
+          col2: res.data.info.aof_enabled === '0' ? '否' : '是',
+          col3: 'RDB是否成功',
+          col4: res.data.info.rdb_last_bgsave_status,
+          col5: 'Key数量',
+          col6: res.data.dbSize,
+          col7: '网络入口/出口',
+          col8: `${res.data.info.instantaneous_input_kbps}/${res.data.info.instantaneous_output_kbps}kps`,
+        });
+        setBaseInfoData(baseinfo);
+
+        const data: VisitDataType[] = res.data.commandStats.map((item) => {
+          return {
+            x: item.name,
+            y: Number(item.value),
+          };
+        });
+
+        setCmdInfoData(data);
+        setMemUsageTitle(res.data.info.used_memory_human);
+        setMemUsage(res.data.info.used_memory / res.data.info.total_system_memory);
+      }
+    });
+  }, []);
+
+  return (
+    <div>
+      <Row gutter={[24, 24]}>
+        <Col span={24}>
+          <Card title="基本信息" className={styles.card}>
+            <Table
+              rowKey="col1"
+              pagination={false}
+              showHeader={false}
+              dataSource={baseInfoData}
+              columns={columns}
+            />
+          </Card>
+        </Col>
+      </Row>
+      <Row gutter={[24, 24]}>
+        <Col span={12}>
+          <Card title="命令统计" className={styles.card}>
+            <Pie
+              height={320}
+              radius={0.8}
+              innerRadius={0.5}
+              angleField="y"
+              colorField="x"
+              data={cmdInfoData as any}
+              legend={false}
+              label={{
+                position: 'spider',
+                text: (item: { x: number; y: number }) => {
+                  return `${item.x}: ${item.y}`;
+                },
+              }}
+            />
+          </Card>
+        </Col>
+        <Col span={12}>
+          <Card title="内存信息" className={styles.card}>
+            <Gauge
+              title={memUsageTitle}
+              height={320}
+              percent={memUsage}
+              formatter={usageFormatter}
+              padding={-16}
+              style={{
+                textContent: () => { return memUsage.toFixed(2).toString() + '%'},
+              }}
+              data={
+                {
+                  target: memUsage,
+                  total: 100,
+                  name: 'score',
+                  thresholds: [20, 40, 60, 80, 100],
+                } as any
+              }
+              meta={{
+                color: {
+                  range: ['#C3F71F', '#B5E61D', '#FFC90E', '#FF7F27', '#FF2518'],
+                },
+              }}
+            />
+          </Card>
+        </Col>
+      </Row>
+    </div>
+  );
+};
+
+export default CacheInfo;
diff --git a/react-ui/src/pages/Monitor/Druid/index.tsx b/react-ui/src/pages/Monitor/Druid/index.tsx
new file mode 100644
index 0000000..55692d2
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Druid/index.tsx
@@ -0,0 +1,31 @@
+import React, { useEffect } from 'react'; 
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+const DruidInfo: 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) - 220}px`;
+      frame.style.height = `${Number(deviceHeight) - 120}px`;
+    }
+  });
+
+  return (
+    <iframe
+      style={{ width: '100%', border: '0px', height: '100%' }}
+      src={`/api/druid/login.html`}
+      id="bdIframe"
+     />
+    // </WrapContent>
+  );
+};
+
+export default DruidInfo;
diff --git a/react-ui/src/pages/Monitor/Job/detail.tsx b/react-ui/src/pages/Monitor/Job/detail.tsx
new file mode 100644
index 0000000..e27bfb7
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Job/detail.tsx
@@ -0,0 +1,131 @@
+import React, { useEffect } from 'react';
+import { Modal, Descriptions, Button } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { getValueEnumLabel } from '@/utils/options';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+export type OperlogFormValueType = Record<string, unknown> & Partial<API.Monitor.Job>;
+
+export type OperlogFormProps = {
+  onCancel: (flag?: boolean, formVals?: OperlogFormValueType) => void;
+  open: boolean;
+  values: Partial<API.Monitor.Job>;
+  statusOptions: DictValueEnumObj;
+};
+
+const OperlogForm: React.FC<OperlogFormProps> = (props) => {
+  const { values, statusOptions } = props;
+
+  useEffect(() => {}, [props]);
+
+  const intl = useIntl();
+
+  const misfirePolicy: any = {
+    '0': '默认策略',
+    '1': '立即执行',
+    '2': '执行一次',
+    '3': '放弃执行',
+  };
+
+  const handleCancel = () => {
+    props.onCancel();
+  };
+
+  return (
+    <Modal
+      width={800}
+      title={intl.formatMessage({
+        id: 'monitor.job.detail',
+        defaultMessage: '操作日志详细信息',
+      })}
+      open={props.open}
+      destroyOnClose
+      onCancel={handleCancel}
+      footer={[
+        <Button key="back" onClick={handleCancel}>
+          关闭
+        </Button>,
+      ]}
+    >
+      <Descriptions column={24}>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />}
+        >
+          {values.jobId}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />}
+        >
+          {values.jobName}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />}
+        >
+          {values.jobGroup}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.concurrent" defaultMessage="是否并发执行" />}
+        >
+          {values.concurrent === '1' ? '禁止' : '允许'}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={
+            <FormattedMessage id="monitor.job.misfire_policy" defaultMessage="计划执行错误策略" />
+          }
+        >
+          {misfirePolicy[values.misfirePolicy ? values.misfirePolicy : '0']}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.create_time" defaultMessage="创建时间" />}
+        >
+          {values.createTime?.toString()}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.status" defaultMessage="状态" />}
+        >
+          {getValueEnumLabel(statusOptions, values.status, '未知')}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={
+            <FormattedMessage id="monitor.job.next_valid_time" defaultMessage="下次执行时间" />
+          }
+        >
+          {values.nextValidTime}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={
+            <FormattedMessage id="monitor.job.cron_expression" defaultMessage="cron执行表达式" />
+          }
+        >
+          {values.cronExpression}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={
+            <FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标字符串" />
+          }
+        >
+          {values.invokeTarget}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default OperlogForm;
diff --git a/react-ui/src/pages/Monitor/Job/edit.tsx b/react-ui/src/pages/Monitor/Job/edit.tsx
new file mode 100644
index 0000000..e4bfc74
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Job/edit.tsx
@@ -0,0 +1,232 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormTextArea,
+  ProFormRadio,
+  ProFormSelect,
+  ProFormCaptcha,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictOptionType, DictValueEnumObj } from '@/components/DictTag';
+
+/**
+ * 定时任务调度 Edit Form
+ * 
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+export type JobFormData = Record<string, unknown> & Partial<API.Monitor.Job>;
+
+export type JobFormProps = {
+  onCancel: (flag?: boolean, formVals?: JobFormData) => void;
+  onSubmit: (values: JobFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Job>;
+  jobGroupOptions: DictOptionType[];
+  statusOptions: DictValueEnumObj;
+};
+
+const JobForm: React.FC<JobFormProps> = (props) => {
+  const [form] = Form.useForm();
+  const { jobGroupOptions, statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      jobId: props.values.jobId,
+      jobName: props.values.jobName,
+      jobGroup: props.values.jobGroup,
+      invokeTarget: props.values.invokeTarget,
+      cronExpression: props.values.cronExpression,
+      misfirePolicy: props.values.misfirePolicy,
+      concurrent: props.values.concurrent,
+      status: props.values.status,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+    form.resetFields();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as JobFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'monitor.job.title',
+        defaultMessage: '编辑定时任务调度',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="jobId"
+          label={intl.formatMessage({
+            id: 'monitor.job.job_id',
+            defaultMessage: '任务编号',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入任务编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入任务编号!" defaultMessage="请输入任务编号!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="jobName"
+          label={intl.formatMessage({
+            id: 'monitor.job.job_name',
+            defaultMessage: '任务名称',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入任务名称"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入任务名称!" defaultMessage="请输入任务名称!" />,
+            },
+          ]}
+        />
+        <ProFormSelect
+          name="jobGroup"
+          options={jobGroupOptions}
+          label={intl.formatMessage({
+            id: 'monitor.job.job_group',
+            defaultMessage: '任务组名',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入任务组名"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入任务组名!" defaultMessage="请输入任务组名!" />,
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="invokeTarget"
+          label={intl.formatMessage({
+            id: 'monitor.job.invoke_target',
+            defaultMessage: '调用目标字符串',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入调用目标字符串"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入调用目标字符串!" defaultMessage="请输入调用目标字符串!" />,
+            },
+          ]}
+        />
+        <ProFormCaptcha
+          name="cronExpression"
+          label={intl.formatMessage({
+            id: 'monitor.job.cron_expression',
+            defaultMessage: 'cron执行表达式',
+          })}
+          captchaTextRender={() => "生成表达式"}
+          onGetCaptcha={() => {
+            // form.setFieldValue('cronExpression', '0/20 * * * * ?');
+            return new Promise((resolve, reject) => {
+              reject();
+            });
+          }}
+        />
+        <ProFormRadio.Group
+          name="misfirePolicy"
+          label={intl.formatMessage({
+            id: 'monitor.job.misfire_policy',
+            defaultMessage: '计划执行错误策略',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入计划执行错误策略"
+          valueEnum={{
+            0: '立即执行',
+            1: '执行一次',
+            3: '放弃执行'
+          }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入计划执行错误策略!" defaultMessage="请输入计划执行错误策略!" />,
+            },
+          ]}
+          fieldProps={{
+            optionType: "button",
+            buttonStyle: "solid"
+          }}
+        />
+        <ProFormRadio.Group
+          name="concurrent"
+          label={intl.formatMessage({
+            id: 'monitor.job.concurrent',
+            defaultMessage: '是否并发执行',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入是否并发执行"
+          valueEnum={{
+            0: '允许',
+            1: '禁止',
+          }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入是否并发执行!" defaultMessage="请输入是否并发执行!" />,
+            },
+          ]}
+          fieldProps={{
+            optionType: "button",
+            buttonStyle: "solid"
+          }}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'monitor.job.status',
+            defaultMessage: '状态',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default JobForm;
diff --git a/react-ui/src/pages/Monitor/Job/index.tsx b/react-ui/src/pages/Monitor/Job/index.tsx
new file mode 100644
index 0000000..43030d9
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Job/index.tsx
@@ -0,0 +1,460 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import { Dropdown, FormInstance, Space } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined } from '@ant-design/icons';
+import { getJobList, removeJob, addJob, updateJob, exportJob, runJob } from '@/services/monitor/job';
+import { getDictSelectOption, getDictValueEnum } from '@/services/system/dict';
+import UpdateForm from './edit';
+import DetailForm from './detail';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 定时任务调度 List Page
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Job) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addJob({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Job) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateJob(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Job[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeJob(selectedRows.map((row) => row.jobId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.Monitor.Job) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.jobId];
+    const resp = await removeJob(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportJob();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const JobTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+  const [detailModalVisible, setDetailModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.Monitor.Job>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Job[]>([]);
+
+  const [jobGroupOptions, setJobGroupOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictSelectOption('sys_job_group').then((data) => {
+      setJobGroupOptions(data);
+    });
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Job>[] = [
+    {
+      title: <FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />,
+      dataIndex: 'jobId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />,
+      dataIndex: 'jobName',
+      valueType: 'text',
+      render: (dom, record) => {
+        return (
+          <a
+            onClick={() => {
+              setDetailModalVisible(true);
+              setCurrentRow(record);
+            }}
+          >
+            {dom}
+          </a>
+        );
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />,
+      dataIndex: 'jobGroup',
+      valueType: 'text',
+      valueEnum: jobGroupOptions,
+      render: (_, record) => {
+        return (<DictTag options={jobGroupOptions} value={record.jobGroup} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标字符串" />,
+      dataIndex: 'invokeTarget',
+      valueType: 'textarea',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.cron_expression" defaultMessage="cron执行表达式" />,
+      dataIndex: 'cronExpression',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.status" defaultMessage="状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          icon = <EditOutlined />
+          hidden={!access.hasPerms('monitor:job:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          icon = <DeleteOutlined />
+          hidden={!access.hasPerms('monitor:job:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+        <Dropdown
+          key="more"
+          menu={{
+            items: [
+              {
+                label: '执行一次',
+                key: 'runOnce',
+              },
+              {
+                label: '详细',
+                key: 'detail',
+              },
+              {
+                label: '历史',
+                key: 'log',
+              },
+            ],
+            onClick: ({ key }) => {
+              if (key === 'runOnce') {
+                Modal.confirm({
+                  title: '警告',
+                  content: '确认要立即执行一次?',
+                  okText: '确认',
+                  cancelText: '取消',
+                  onOk: async () => {
+                    const success = await runJob(record.jobId, record.jobGroup);
+                    if (success) {
+                      message.success('执行成功');
+                    }
+                  },
+                });
+              }
+              else if (key === 'detail') {
+                setDetailModalVisible(true);
+                setCurrentRow(record);
+              }
+              else if( key === 'log') {
+                history.push(`/monitor/job-log/index/${record.jobId}`);
+              }
+            }
+          }}
+        >
+          <a className="ant-dropdown-link" onClick={(e) => e.preventDefault()}>
+            <Space>
+              <DownOutlined />
+              更多
+            </Space>
+          </a>
+        </Dropdown>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Job>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="jobId"
+          key="jobList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('monitor:job:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:job:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('monitor:job:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getJobList({ ...params } as API.Monitor.JobListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('monitor:job:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.jobId) {
+            success = await handleUpdate({ ...values } as API.Monitor.Job);
+          } else {
+            success = await handleAdd({ ...values } as API.Monitor.Job);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        jobGroupOptions={jobGroupOptions||{}}
+        statusOptions={statusOptions}
+      />
+      <DetailForm
+        onCancel={() => {
+          setDetailModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={detailModalVisible}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default JobTableList;
diff --git a/react-ui/src/pages/Monitor/JobLog/detail.tsx b/react-ui/src/pages/Monitor/JobLog/detail.tsx
new file mode 100644
index 0000000..560889c
--- /dev/null
+++ b/react-ui/src/pages/Monitor/JobLog/detail.tsx
@@ -0,0 +1,105 @@
+import { getValueEnumLabel } from '@/utils/options';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { Descriptions, Modal } from 'antd';
+import React, { useEffect } from 'react';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+export type JobLogFormValueType = Record<string, unknown> & Partial<API.Monitor.JobLog>;
+
+export type JobLogFormProps = {
+  onCancel: (flag?: boolean, formVals?: JobLogFormValueType) => void;
+  open: boolean;
+  values: Partial<API.Monitor.JobLog>;
+  statusOptions: DictValueEnumObj;
+  jobGroupOptions: DictValueEnumObj;
+};
+
+const JobLogDetailForm: React.FC<JobLogFormProps> = (props) => {
+
+  const { values, statusOptions, jobGroupOptions } = props;
+
+  useEffect(() => {
+  }, []);
+
+  const intl = useIntl();
+  const handleOk = () => {
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'monitor.job.log.title',
+        defaultMessage: '定时任务调度日志',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Descriptions column={24}>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_id" defaultMessage="任务编号" />}
+        >
+          {values.jobLogId}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.create_time" defaultMessage="执行时间" />}
+        >
+          {values.createTime?.toString()}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_name" defaultMessage="任务名称" />}
+        >
+          {values.jobName}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.job_group" defaultMessage="任务组名" />}
+        >
+          {getValueEnumLabel(jobGroupOptions, values.jobGroup, '无')}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.job.invoke_target" defaultMessage="调用目标" />}
+        >
+          {values.invokeTarget}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.job.log.job_message" defaultMessage="日志信息" />}
+        >
+          {values.jobMessage}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.job.log.exception_info" defaultMessage="异常信息" />}
+        >
+          {values.exceptionInfo}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.job.status" defaultMessage="执行状态" />}
+        >
+          {getValueEnumLabel(statusOptions, values.status, '未知')}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default JobLogDetailForm;
diff --git a/react-ui/src/pages/Monitor/JobLog/index.tsx b/react-ui/src/pages/Monitor/JobLog/index.tsx
new file mode 100644
index 0000000..46e3313
--- /dev/null
+++ b/react-ui/src/pages/Monitor/JobLog/index.tsx
@@ -0,0 +1,343 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, useParams, history } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getJobLogList, removeJobLog, exportJobLog } from '@/services/monitor/jobLog';
+import DetailForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import { getJob } from '@/services/monitor/job';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 定时任务调度日志 List Page
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.JobLog[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeJobLog(selectedRows.map((row) => row.jobLogId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.Monitor.JobLog) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.jobLogId];
+    const resp = await removeJobLog(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 清空日志数据
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportJobLog();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const JobLogTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalOpen, setModalOpen] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.Monitor.JobLog>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.JobLog[]>([]);
+
+  const [jobGroupOptions, setJobGroupOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const [queryParams, setQueryParams] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  const params = useParams();
+  if (params.id === undefined) {
+    history.push('/monitor/job');
+  }
+  const jobId = params.id || 0;
+  useEffect(() => {
+    if (jobId !== undefined && jobId !== 0) {
+      getJob(Number(jobId)).then(response => {
+        setQueryParams({
+          jobName: response.data.jobName,
+          jobGroup: response.data.jobGroup
+        });
+      });
+    }
+    getDictValueEnum('sys_job_status').then((data) => {
+      setStatusOptions(data);
+    });
+    getDictValueEnum('sys_job_group').then((data) => {
+      setJobGroupOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.JobLog>[] = [
+    {
+      title: <FormattedMessage id="monitor.job.log.job_log_id" defaultMessage="任务日志编号" />,
+      dataIndex: 'jobLogId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.job_name" defaultMessage="任务名称" />,
+      dataIndex: 'jobName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.job_group" defaultMessage="任务组名" />,
+      dataIndex: 'jobGroup',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.invoke_target" defaultMessage="调用目标字符串" />,
+      dataIndex: 'invokeTarget',
+      valueType: 'textarea',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.job_message" defaultMessage="日志信息" />,
+      dataIndex: 'jobMessage',
+      valueType: 'textarea',
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.status" defaultMessage="执行状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.job.log.create_time" defaultMessage="异常信息" />,
+      dataIndex: 'createTime',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('monitor:job-log:edit')}
+          onClick={() => {
+            setModalOpen(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('monitor:job-log:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.JobLog>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="jobLogId"
+          key="job-logList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('monitor:job-log:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalOpen(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:job-log:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('monitor:job-log:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          params={queryParams}
+          request={(params) =>
+            getJobLogList({ ...params } as API.Monitor.JobLogListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('monitor:job-log:del')}
+            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>
+      )}
+      <DetailForm
+        onCancel={() => {
+          setModalOpen(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalOpen}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+        jobGroupOptions={jobGroupOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default JobLogTableList;
diff --git a/react-ui/src/pages/Monitor/Logininfor/edit.tsx b/react-ui/src/pages/Monitor/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Logininfor/edit.tsx
@@ -0,0 +1,216 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTimePicker,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type LogininforFormData = Record<string, unknown> & Partial<API.Monitor.Logininfor>;
+
+export type LogininforFormProps = {
+  onCancel: (flag?: boolean, formVals?: LogininforFormData) => void;
+  onSubmit: (values: LogininforFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Logininfor>;
+  statusOptions: DictValueEnumObj;
+};
+
+const LogininforForm: React.FC<LogininforFormProps> = (props) => {
+  const [form] = Form.useForm();
+  
+  const { statusOptions, } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			infoId: props.values.infoId,
+			userName: props.values.userName,
+			ipaddr: props.values.ipaddr,
+			loginLocation: props.values.loginLocation,
+			browser: props.values.browser,
+			os: props.values.os,
+			status: props.values.status,
+			msg: props.values.msg,
+			loginTime: props.values.loginTime,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+    form.resetFields();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as LogininforFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.logininfor.title',
+        defaultMessage: '编辑系统访问记录',
+      })}
+      open={props.open}
+      destroyOnClose
+      forceRender
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="infoId"
+          label={intl.formatMessage({
+            id: 'system.logininfor.info_id',
+            defaultMessage: '访问编号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入访问编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入访问编号!" defaultMessage="请输入访问编号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="userName"
+          label={intl.formatMessage({
+            id: 'system.logininfor.user_name',
+            defaultMessage: '用户账号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入用户账号"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入用户账号!" defaultMessage="请输入用户账号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="ipaddr"
+          label={intl.formatMessage({
+            id: 'system.logininfor.ipaddr',
+            defaultMessage: '登录IP地址',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录IP地址"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录IP地址!" defaultMessage="请输入登录IP地址!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="loginLocation"
+          label={intl.formatMessage({
+            id: 'system.logininfor.login_location',
+            defaultMessage: '登录地点',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录地点"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录地点!" defaultMessage="请输入登录地点!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="browser"
+          label={intl.formatMessage({
+            id: 'system.logininfor.browser',
+            defaultMessage: '浏览器类型',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入浏览器类型"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入浏览器类型!" defaultMessage="请输入浏览器类型!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="os"
+          label={intl.formatMessage({
+            id: 'system.logininfor.os',
+            defaultMessage: '操作系统',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入操作系统"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入操作系统!" defaultMessage="请输入操作系统!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.logininfor.status',
+            defaultMessage: '登录状态',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录状态!" defaultMessage="请输入登录状态!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="msg"
+          label={intl.formatMessage({
+            id: 'system.logininfor.msg',
+            defaultMessage: '提示消息',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入提示消息"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入提示消息!" defaultMessage="请输入提示消息!" />,                  
+            },
+          ]}
+        />
+        <ProFormTimePicker
+          name="loginTime"
+          label={intl.formatMessage({
+            id: 'system.logininfor.login_time',
+            defaultMessage: '访问时间',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入访问时间"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入访问时间!" defaultMessage="请输入访问时间!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default LogininforForm;
diff --git a/react-ui/src/pages/Monitor/Logininfor/index.tsx b/react-ui/src/pages/Monitor/Logininfor/index.tsx
new file mode 100644
index 0000000..5a39281
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Logininfor/index.tsx
@@ -0,0 +1,335 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, UnlockOutlined } from '@ant-design/icons';
+import { getLogininforList, removeLogininfor, exportLogininfor, unlockLogininfor, cleanLogininfor } from '@/services/monitor/logininfor';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Logininfor[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeLogininfor(selectedRows.map((row) => row.infoId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleClean = async () => {
+  const hide = message.loading('请稍候');
+  try {
+    const resp = await cleanLogininfor();
+    hide();
+    if (resp.code === 200) {
+      message.success('清空成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('请求失败,请重试');
+    return false;
+  }
+};
+
+const handleUnlock = async (userName: string) => {
+  const hide = message.loading('正在解锁');
+  try {
+    const resp = await unlockLogininfor(userName);
+    hide();
+    if (resp.code === 200) {
+      message.success('解锁成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('解锁失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ * @param id
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportLogininfor();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const LogininforTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const actionRef = useRef<ActionType>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Logininfor[]>([]);
+
+  const access = useAccess();
+
+  const statusOptions = {
+    0: {
+      label: '成功',
+      key: '0',
+      value: '0',
+      text: '成功',
+      status: 'success',
+      listClass: 'success'
+    },
+    1: {
+      label: '失败',
+      key: '1',
+      value: '1',
+      text: '失败',
+      status: 'error',
+      listClass: 'danger'
+    },
+  };
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Logininfor>[] = [
+    {
+      title: <FormattedMessage id="monitor.logininfor.info_id" defaultMessage="访问编号" />,
+      dataIndex: 'infoId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.ipaddr" defaultMessage="登录IP地址" />,
+      dataIndex: 'ipaddr',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.login_location" defaultMessage="登录地点" />,
+      dataIndex: 'loginLocation',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.browser" defaultMessage="浏览器类型" />,
+      dataIndex: 'browser',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.os" defaultMessage="操作系统" />,
+      dataIndex: 'os',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.status" defaultMessage="登录状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.msg" defaultMessage="提示消息" />,
+      dataIndex: 'msg',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.login_time" defaultMessage="访问时间" />,
+      dataIndex: 'loginTime',
+      valueType: 'dateTime',
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Logininfor>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="infoId"
+          key="logininforList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="clean"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认清空所有数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleClean();
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+            </Button>,
+            <Button
+              type="primary"
+              key="unlock"
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:unlock')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认解锁该用户的数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleUnlock(selectedRows[0].userName);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <UnlockOutlined />
+              <FormattedMessage id="monitor.logininfor.unlock" defaultMessage="解锁" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('monitor:logininfor:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getLogininforList({ ...params } as API.Monitor.LogininforListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('monitor:logininfor: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>
+      )}
+    </PageContainer>
+  );
+};
+
+export default LogininforTableList;
diff --git a/react-ui/src/pages/Monitor/Online/index.tsx b/react-ui/src/pages/Monitor/Online/index.tsx
new file mode 100644
index 0000000..2f81e3b
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Online/index.tsx
@@ -0,0 +1,162 @@
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import React, { useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { getOnlineUserList, forceLogout } from '@/services/monitor/online';
+import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
+import { DeleteOutlined } from '@ant-design/icons';
+ 
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+
+const handleForceLogout = async (selectedRow: API.Monitor.OnlineUserType) => {
+  const hide = message.loading('正在强制下线');
+  try {
+    await forceLogout(selectedRow.tokenId);
+    hide();
+    message.success('强制下线成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('强制下线失败,请重试');
+    return false;
+  }
+};
+
+const OnlineUserTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+  const actionRef = useRef<ActionType>();
+  const access = useAccess();
+  const intl = useIntl();
+
+  useEffect(() => {}, []);
+
+  const columns: ProColumns<API.Monitor.OnlineUserType>[] = [
+    {
+      title: <FormattedMessage id="monitor.online.user.token_id" defaultMessage="会话编号" />,
+      dataIndex: 'tokenId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.dept_name" defaultMessage="部门名称" />,
+      dataIndex: 'deptName',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.ipaddr" defaultMessage="登录IP地址" />,
+      dataIndex: 'ipaddr',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.login_location" defaultMessage="登录地点" />,
+      dataIndex: 'loginLocation',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.browser" defaultMessage="浏览器类型" />,
+      dataIndex: 'browser',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.os" defaultMessage="操作系统" />,
+      dataIndex: 'os',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.online.user.login_time" defaultMessage="登录时间" />,
+      dataIndex: 'loginTime',
+      valueType: 'dateRange',
+      render: (_, record) => <span>{record.loginTime}</span>,
+      hideInSearch: true,
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '60px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          icon=<DeleteOutlined />
+          hidden={!access.hasPerms('monitor:online:forceLogout')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '强踢',
+              content: '确定强制将该用户踢下线吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleForceLogout(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          强退
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.OnlineUserType>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="tokenId"
+          key="logininforList"
+          search={{
+            labelWidth: 120,
+          }}
+          request={(params) =>
+            getOnlineUserList({ ...params } as API.Monitor.OnlineUserListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+        />
+      </div>
+  );
+};
+
+export default OnlineUserTableList;
diff --git a/react-ui/src/pages/Monitor/Operlog/detail.tsx b/react-ui/src/pages/Monitor/Operlog/detail.tsx
new file mode 100644
index 0000000..f247522
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Operlog/detail.tsx
@@ -0,0 +1,113 @@
+import React from 'react';
+import { Descriptions, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { getValueEnumLabel } from '@/utils/options';
+
+export type OperlogFormData = Record<string, unknown> & Partial<API.Monitor.Operlog>;
+
+export type OperlogFormProps = {
+  onCancel: (flag?: boolean, formVals?: OperlogFormData) => void;
+  onSubmit: (values: OperlogFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Operlog>;
+  businessTypeOptions: DictValueEnumObj;
+  operatorTypeOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+};
+
+const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => {
+
+  const { values, businessTypeOptions, operatorTypeOptions, statusOptions, } = props;
+
+  const intl = useIntl();
+  const handleOk = () => {};
+  const handleCancel = () => {
+    props.onCancel();
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'monitor.operlog.title',
+        defaultMessage: '编辑操作日志记录',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Descriptions column={24}>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.module" defaultMessage="操作模块" />}
+        >
+          {`${values.title}/${getValueEnumLabel(businessTypeOptions, values.businessType)}`}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />}
+        >
+          {values.requestMethod}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />}
+        >
+          {`${values.operName}/${values.operIp}`}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />}
+        >
+          {getValueEnumLabel(operatorTypeOptions, values.operatorType)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.method" defaultMessage="方法名称" />}
+        >
+          {values.method}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.oper_url" defaultMessage="请求URL" />}
+        >
+          {values.operUrl}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.oper_param" defaultMessage="请求参数" />}
+        >
+          {values.operParam}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.json_result" defaultMessage="返回参数" />}
+        >
+          {values.jsonResult}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.error_msg" defaultMessage="错误消息" />}
+        >
+          {values.errorMsg}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />}
+        >
+          {getValueEnumLabel(statusOptions, values.status)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />}
+        >
+          {values.operTime?.toString()}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default OperlogDetailForm;
diff --git a/react-ui/src/pages/Monitor/Operlog/index.tsx b/react-ui/src/pages/Monitor/Operlog/index.tsx
new file mode 100644
index 0000000..9440bc8
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Operlog/index.tsx
@@ -0,0 +1,365 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getOperlogList, removeOperlog, addOperlog, updateOperlog, exportOperlog } from '@/services/monitor/operlog';
+import UpdateForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Operlog) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addOperlog({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Operlog) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateOperlog(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Operlog[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeOperlog(selectedRows.map((row) => row.operId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportOperlog();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const OperlogTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.Monitor.Operlog>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Operlog[]>([]);
+
+  const [businessTypeOptions, setBusinessTypeOptions] = useState<any>([]);
+  const [operatorTypeOptions, setOperatorTypeOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_oper_type', true).then((data) => {
+      setBusinessTypeOptions(data);
+    });
+    getDictValueEnum('sys_oper_type', true).then((data) => {
+      setOperatorTypeOptions(data);
+    });
+    getDictValueEnum('sys_common_status', true).then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Operlog>[] = [
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_id" defaultMessage="日志主键" />,
+      dataIndex: 'operId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.title" defaultMessage="操作模块" />,
+      dataIndex: 'title',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.business_type" defaultMessage="业务类型" />,
+      dataIndex: 'businessType',
+      valueType: 'select',
+      valueEnum: businessTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={businessTypeOptions} value={record.businessType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />,
+      dataIndex: 'requestMethod',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />,
+      dataIndex: 'operatorType',
+      valueType: 'select',
+      valueEnum: operatorTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={operatorTypeOptions} value={record.operatorType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />,
+      dataIndex: 'operName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_ip" defaultMessage="主机地址" />,
+      dataIndex: 'operIp',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_location" defaultMessage="操作地点" />,
+      dataIndex: 'operLocation',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag key="status" enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />,
+      dataIndex: 'operTime',
+      valueType: 'dateTime',
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:operlog:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          详细
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Operlog>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="operId"
+          key="operlogList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:operlog:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:operlog:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:operlog:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getOperlogList({ ...params } as API.Monitor.OperlogListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:operlog:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.operId) {
+            success = await handleUpdate({ ...values } as API.Monitor.Operlog);
+          } else {
+            success = await handleAdd({ ...values } as API.Monitor.Operlog);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        businessTypeOptions={businessTypeOptions}
+        operatorTypeOptions={operatorTypeOptions}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default OperlogTableList;
diff --git a/react-ui/src/pages/Monitor/Server/index.tsx b/react-ui/src/pages/Monitor/Server/index.tsx
new file mode 100644
index 0000000..9ea3969
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Server/index.tsx
@@ -0,0 +1,267 @@
+import React, { useEffect, useState } from 'react';
+import { getServerInfo } from '@/services/monitor/server';
+import { Card, Col, Row, Table } from 'antd';
+import { FormattedMessage } from '@umijs/max';
+import styles from './style.less';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+
+const columns = [
+	{
+		title: '属性',
+		dataIndex: 'name',
+		key: 'name',
+	},
+	{
+		title: '值',
+		dataIndex: 'value',
+		key: 'value',
+	},
+];
+
+const memColumns = [
+	{
+		title: '属性',
+		dataIndex: 'name',
+		key: 'name',
+	},
+	{
+		title: '内存',
+		dataIndex: 'mem',
+		key: 'mem',
+	},
+	{
+		title: 'JVM',
+		dataIndex: 'jvm',
+		key: 'jvm',
+	},
+];
+
+const hostColumns = [
+	{
+		title: 'col1',
+		dataIndex: 'col1',
+		key: 'col1',
+	},
+	{
+		title: 'col2',
+		dataIndex: 'col2',
+		key: 'col2',
+	},
+	{
+		title: 'col3',
+		dataIndex: 'col3',
+		key: 'col3',
+	},
+	{
+		title: 'col4',
+		dataIndex: 'col4',
+		key: 'col4',
+	},
+];
+
+const diskColumns = [
+	{
+		title: <FormattedMessage id="monitor.server.disk.dirName" defaultMessage="盘符路径" />,
+		dataIndex: 'dirName',
+		key: 'dirName',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.sysTypeName" defaultMessage="文件系统" />,
+		dataIndex: 'sysTypeName',
+		key: 'sysTypeName',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.typeName" defaultMessage="盘符类型" />,
+		dataIndex: 'typeName',
+		key: 'typeName',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.total" defaultMessage="总大小" />,
+		dataIndex: 'total',
+		key: 'total',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.free" defaultMessage="可用大小" />,
+		dataIndex: 'free',
+		key: 'free',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.used" defaultMessage="已用大小" />,
+		dataIndex: 'used',
+		key: 'used',
+	},
+	{
+		title: <FormattedMessage id="monitor.server.disk.usage" defaultMessage="已用百分比" />,
+		dataIndex: 'usage',
+		key: 'usage',
+	},
+];
+
+const ServerInfo: React.FC = () => {
+	const [cpuData, setCpuData] = useState<API.Monitor.CpuRowType[]>([]);
+	const [memData, setMemData] = useState<API.Monitor.MemRowType[]>([]);
+	const [hostData, setHostData] = useState<any>([]);
+	const [jvmData, setJvmData] = useState<any>([]);
+	const [diskData, setDiskData] = useState<any>([]);
+
+	useEffect(() => {
+		getServerInfo().then((res: API.Monitor.ServerInfoResponseType) => {
+			if (res.code === 200) {
+				// const cpuinfo: CpuRowType[] = [];
+				// Object.keys(res.data.cpu).forEach((item: any) => {
+				//   cpuinfo.push({
+				//     name: item,
+				//     value: res.data.cpu[item],
+				//   });
+				// });
+				// setCpuData(cpuinfo);
+
+				const cpuinfo: API.Monitor.CpuRowType[] = [];
+				cpuinfo.push({ name: '核心数', value: res.data.cpu.cpuNum });
+				cpuinfo.push({ name: '用户使用率', value: `${res.data.cpu.used}%` });
+				cpuinfo.push({ name: '系统使用率', value: `${res.data.cpu.sys}%` });
+				cpuinfo.push({ name: '当前空闲率', value: `${res.data.cpu.free}%` });
+
+				setCpuData(cpuinfo);
+
+				const memDatas: API.Monitor.MemRowType[] = [];
+				memDatas.push({
+					name: '总内存',
+					mem: `${res.data.mem.total}G`,
+					jvm: `${res.data.jvm.total}M`,
+				});
+				memDatas.push({
+					name: '已用内存',
+					mem: `${res.data.mem.used}G`,
+					jvm: `${res.data.jvm.used}M`,
+				});
+				memDatas.push({
+					name: '剩余内存',
+					mem: `${res.data.mem.free}G`,
+					jvm: `${res.data.jvm.free}M`,
+				});
+				memDatas.push({
+					name: '使用率',
+					mem: `${res.data.mem.usage}%`,
+					jvm: `${res.data.jvm.usage}%`,
+				});
+				setMemData(memDatas);
+
+				const hostinfo = [];
+				hostinfo.push({
+					col1: '服务器名称',
+					col2: res.data.sys.computerName,
+					col3: '操作系统',
+					col4: res.data.sys.osName,
+				});
+				hostinfo.push({
+					col1: '服务器IP',
+					col2: res.data.sys.computerIp,
+					col3: '系统架构',
+					col4: res.data.sys.osArch,
+				});
+				setHostData(hostinfo);
+
+				const jvminfo = [];
+				jvminfo.push({
+					col1: 'Java名称',
+					col2: res.data.jvm.name,
+					col3: 'Java版本',
+					col4: res.data.jvm.version,
+				});
+				jvminfo.push({
+					col1: '启动时间',
+					col2: res.data.jvm.startTime,
+					col3: '运行时长',
+					col4: res.data.jvm.runTime,
+				});
+				jvminfo.push({
+					col1: '安装路径',
+					col2: res.data.jvm.home,
+					col3: '项目路径',
+					col4: res.data.sys.userDir,
+				});
+				setJvmData(jvminfo);
+
+				const diskinfo = res.data.sysFiles.map((item: API.Monitor.DiskInfoType) => {
+					return {
+						dirName: item.dirName,
+						sysTypeName: item.sysTypeName,
+						typeName: item.typeName,
+						total: item.total,
+						free: item.free,
+						used: item.used,
+						usage: `${item.usage}%`,
+					};
+				});
+				setDiskData(diskinfo);
+			}
+		});
+	}, []);
+
+	return (
+		<div>
+			<Row gutter={[24, 24]}>
+				<Col span={12}>
+					<Card title="CPU" className={styles.card}>
+						<Table rowKey="name" pagination={false} showHeader={false} dataSource={cpuData} columns={columns} />
+					</Card>
+				</Col>
+				<Col span={12}>
+					<Card title="内存" className={styles.card}>
+						<Table
+							rowKey="name"
+							pagination={false}
+							showHeader={false}
+							dataSource={memData}
+							columns={memColumns}
+						/>
+					</Card>
+				</Col>
+			</Row>
+			<Row gutter={[16, 16]}>
+				<Col span={24}>
+					<Card title="服务器信息" className={styles.card}>
+						<Table
+							rowKey="col1"
+							pagination={false}
+							showHeader={false}
+							dataSource={hostData}
+							columns={hostColumns}
+						/>
+					</Card>
+				</Col>
+			</Row>
+			<Row gutter={[16, 16]}>
+				<Col span={24}>
+					<Card title="Java虚拟机信息" className={styles.card}>
+						<Table
+							rowKey="col1"
+							pagination={false}
+							showHeader={false}
+							dataSource={jvmData}
+							columns={hostColumns}
+						/>
+					</Card>
+				</Col>
+			</Row>
+			<Row gutter={[16, 16]}>
+				<Col span={24}>
+					<Card title="磁盘状态" className={styles.card}>
+						<Table rowKey="dirName" pagination={false} dataSource={diskData} columns={diskColumns} />
+					</Card>
+				</Col>
+			</Row>
+		</div>
+	);
+};
+
+export default ServerInfo;
diff --git a/react-ui/src/pages/Monitor/Server/style.less b/react-ui/src/pages/Monitor/Server/style.less
new file mode 100644
index 0000000..d244a53
--- /dev/null
+++ b/react-ui/src/pages/Monitor/Server/style.less
@@ -0,0 +1,11 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+.card {
+  margin-bottom: 12px;
+}
diff --git a/react-ui/src/pages/System/Config/edit.tsx b/react-ui/src/pages/System/Config/edit.tsx
new file mode 100644
index 0000000..6cb3942
--- /dev/null
+++ b/react-ui/src/pages/System/Config/edit.tsx
@@ -0,0 +1,172 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormTextArea,
+  ProFormRadio,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type ConfigFormData = Record<string, unknown> & Partial<API.System.Config>;
+
+export type ConfigFormProps = {
+  onCancel: (flag?: boolean, formVals?: ConfigFormData) => void;
+  onSubmit: (values: ConfigFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Config>;
+  configTypeOptions: DictValueEnumObj;
+};
+
+const ConfigForm: React.FC<ConfigFormProps> = (props) => {
+  const [form] = Form.useForm();
+  
+  const { configTypeOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			configId: props.values.configId,
+			configName: props.values.configName,
+			configKey: props.values.configKey,
+			configValue: props.values.configValue,
+			configType: props.values.configType,
+			createBy: props.values.createBy,
+			createTime: props.values.createTime,
+			updateBy: props.values.updateBy,
+			updateTime: props.values.updateTime,
+			remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as ConfigFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.config.title',
+        defaultMessage: '编辑参数配置',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="configId"
+          label={intl.formatMessage({
+            id: 'system.config.config_id',
+            defaultMessage: '参数主键',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入参数主键"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入参数主键!" defaultMessage="请输入参数主键!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="configName"
+          label={intl.formatMessage({
+            id: 'system.config.config_name',
+            defaultMessage: '参数名称',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入参数名称"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入参数名称!" defaultMessage="请输入参数名称!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="configKey"
+          label={intl.formatMessage({
+            id: 'system.config.config_key',
+            defaultMessage: '参数键名',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入参数键名"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入参数键名!" defaultMessage="请输入参数键名!" />,                  
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="configValue"
+          label={intl.formatMessage({
+            id: 'system.config.config_value',
+            defaultMessage: '参数键值',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入参数键值"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入参数键值!" defaultMessage="请输入参数键值!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={configTypeOptions}
+          name="configType"
+          label={intl.formatMessage({
+            id: 'system.config.config_type',
+            defaultMessage: '系统内置',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入系统内置"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入系统内置!" defaultMessage="请输入系统内置!" />,                  
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.config.remark',
+            defaultMessage: '备注',
+          })}
+          colProps={{ md: 24 }}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default ConfigForm;
diff --git a/react-ui/src/pages/System/Config/index.tsx b/react-ui/src/pages/System/Config/index.tsx
new file mode 100644
index 0000000..1428891
--- /dev/null
+++ b/react-ui/src/pages/System/Config/index.tsx
@@ -0,0 +1,397 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined, DownloadOutlined } from '@ant-design/icons';
+import { getConfigList, removeConfig, addConfig, updateConfig, exportConfig, refreshConfigCache } from '@/services/system/config';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Config) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addConfig({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Config) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateConfig(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Config[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeConfig(selectedRows.map((row) => row.configId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Config) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.configId];
+    const resp = await removeConfig(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportConfig();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+const handleRefreshCache = async () => {
+  const hide = message.loading('正在刷新');
+  try {
+    await refreshConfigCache();
+    hide();
+    message.success('刷新成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('刷新失败,请重试');
+    return false;
+  }
+};
+
+const ConfigTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Config>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Config[]>([]);
+
+  const [configTypeOptions, setConfigTypeOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_yes_no').then((data) => {
+      setConfigTypeOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Config>[] = [
+    {
+      title: <FormattedMessage id="system.config.config_id" defaultMessage="参数主键" />,
+      dataIndex: 'configId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.config.config_name" defaultMessage="参数名称" />,
+      dataIndex: 'configName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.config.config_key" defaultMessage="参数键名" />,
+      dataIndex: 'configKey',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.config.config_value" defaultMessage="参数键值" />,
+      dataIndex: 'configValue',
+      valueType: 'textarea',
+    },
+    {
+      title: <FormattedMessage id="system.config.config_type" defaultMessage="系统内置" />,
+      dataIndex: 'configType',
+      valueType: 'select',
+      valueEnum: configTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={configTypeOptions} value={record.configType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.config.remark" defaultMessage="备注" />,
+      dataIndex: 'remark',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:config:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:config:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Config>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="configId"
+          key="configList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:config:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:config:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:config:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <DownloadOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+            <Button
+              type="primary"
+              key="refresh"
+              danger
+              hidden={!access.hasPerms('system:config:remove')}
+              onClick={async () => {
+                handleRefreshCache();
+              }}
+            >
+              <ReloadOutlined />
+              <FormattedMessage id="system.config.refreshCache" defaultMessage="刷新缓存" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getConfigList({ ...params } as API.System.ConfigListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:config:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.configId) {
+            success = await handleUpdate({ ...values } as API.System.Config);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Config);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        configTypeOptions={configTypeOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default ConfigTableList;
diff --git a/react-ui/src/pages/System/Dept/edit.tsx b/react-ui/src/pages/System/Dept/edit.tsx
new file mode 100644
index 0000000..cb64813
--- /dev/null
+++ b/react-ui/src/pages/System/Dept/edit.tsx
@@ -0,0 +1,212 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTreeSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DeptFormData = Record<string, unknown> & Partial<API.System.Dept>;
+
+export type DeptFormProps = {
+  onCancel: (flag?: boolean, formVals?: DeptFormData) => void;
+  onSubmit: (values: DeptFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Dept>;
+  deptTree: DataNode[];
+  statusOptions: DictValueEnumObj;
+};
+
+const DeptForm: React.FC<DeptFormProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const { statusOptions, deptTree } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      deptId: props.values.deptId,
+      parentId: props.values.parentId,
+      ancestors: props.values.ancestors,
+      deptName: props.values.deptName,
+      orderNum: props.values.orderNum,
+      leader: props.values.leader,
+      phone: props.values.phone,
+      email: props.values.email,
+      status: props.values.status,
+      delFlag: props.values.delFlag,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as DeptFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.dept.title',
+        defaultMessage: '编辑部门',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="deptId"
+          label={intl.formatMessage({
+            id: 'system.dept.dept_id',
+            defaultMessage: '部门id',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入部门id"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入部门id!" defaultMessage="请输入部门id!" />,
+            },
+          ]}
+        />
+        <ProFormTreeSelect
+          name="parentId"
+          label={intl.formatMessage({
+            id: 'system.dept.parent_dept',
+            defaultMessage: '上级部门:',
+          })}
+          request={async () => {
+            return deptTree;
+          }}
+          placeholder="请选择上级部门"
+          rules={[
+            {
+              required: true,
+              message: (
+                <FormattedMessage id="请输入用户昵称!" defaultMessage="请选择上级部门!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormText
+          name="deptName"
+          label={intl.formatMessage({
+            id: 'system.dept.dept_name',
+            defaultMessage: '部门名称',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入部门名称"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入部门名称!" defaultMessage="请输入部门名称!" />,
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="orderNum"
+          label={intl.formatMessage({
+            id: 'system.dept.order_num',
+            defaultMessage: '显示顺序',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入显示顺序"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="leader"
+          label={intl.formatMessage({
+            id: 'system.dept.leader',
+            defaultMessage: '负责人',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入负责人"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入负责人!" defaultMessage="请输入负责人!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="phone"
+          label={intl.formatMessage({
+            id: 'system.dept.phone',
+            defaultMessage: '联系电话',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入联系电话"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入联系电话!" defaultMessage="请输入联系电话!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="email"
+          label={intl.formatMessage({
+            id: 'system.dept.email',
+            defaultMessage: '邮箱',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入邮箱"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.dept.status',
+            defaultMessage: '部门状态',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入部门状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入部门状态!" defaultMessage="请输入部门状态!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default DeptForm;
diff --git a/react-ui/src/pages/System/Dept/index.tsx b/react-ui/src/pages/System/Dept/index.tsx
new file mode 100644
index 0000000..3a1c5de
--- /dev/null
+++ b/react-ui/src/pages/System/Dept/index.tsx
@@ -0,0 +1,346 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDeptList, removeDept, addDept, updateDept, getDeptListExcludeChild } from '@/services/system/dept';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { buildTreeData } from '@/utils/tree';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Dept) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addDept({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Dept) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateDept(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Dept[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeDept(selectedRows.map((row) => row.deptId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Dept) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.deptId];
+    const resp = await removeDept(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+
+const DeptTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Dept>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Dept[]>([]);
+
+  const [deptTree, setDeptTree] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Dept>[] = [
+    {
+      title: <FormattedMessage id="system.dept.dept_name" defaultMessage="部门名称" />,
+      dataIndex: 'deptName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dept.order_num" defaultMessage="显示顺序" />,
+      dataIndex: 'orderNum',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dept.status" defaultMessage="部门状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:dept:edit')}
+          onClick={() => {
+            getDeptListExcludeChild(record.deptId).then((res) => {
+              if (res.code === 200) {
+                let depts = buildTreeData(res.data, 'deptId', 'deptName', '', '', '');
+                if(depts.length === 0) {
+                  depts = [{ id: 0, title: '无上级', children: undefined, key: 0, value: 0 }];
+                }
+                setDeptTree(depts);
+                setModalVisible(true);
+                setCurrentRow(record);
+              } else {
+                message.warning(res.msg);
+              }
+            });
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:dept:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Dept>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="deptId"
+          key="deptList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:dept:add')}
+              onClick={async () => {
+                getDeptList().then((res) => {
+                  if (res.code === 200) {
+                    setDeptTree(buildTreeData(res.data, 'deptId', 'deptName', '', '', ''));
+                    setCurrentRow(undefined);
+                    setModalVisible(true);
+                  } else {
+                    message.warning(res.msg);
+                  }
+                });
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:dept:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getDeptList({ ...params } as API.System.DeptListParams).then((res) => {
+              const result = {
+                data: buildTreeData(res.data, 'deptId', '', '', '', ''),
+                total: res.data.length,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:dept:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.deptId) {
+            success = await handleUpdate({ ...values } as API.System.Dept);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Dept);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        deptTree={deptTree}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default DeptTableList;
diff --git a/react-ui/src/pages/System/Dict/edit.tsx b/react-ui/src/pages/System/Dict/edit.tsx
new file mode 100644
index 0000000..882c1cb
--- /dev/null
+++ b/react-ui/src/pages/System/Dict/edit.tsx
@@ -0,0 +1,152 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTextArea,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DictTypeFormData = Record<string, unknown> & Partial<API.System.DictType>;
+
+export type DictTypeFormProps = {
+  onCancel: (flag?: boolean, formVals?: DictTypeFormData) => void;
+  onSubmit: (values: DictTypeFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.DictType>;
+  statusOptions: DictValueEnumObj;
+};
+
+const DictTypeForm: React.FC<DictTypeFormProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const { statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			dictId: props.values.dictId,
+			dictName: props.values.dictName,
+			dictType: props.values.dictType,
+			status: props.values.status,
+			createBy: props.values.createBy,
+			createTime: props.values.createTime,
+			updateBy: props.values.updateBy,
+			updateTime: props.values.updateTime,
+			remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as DictTypeFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.dict.title',
+        defaultMessage: '编辑字典类型',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="dictId"
+          label={intl.formatMessage({
+            id: 'system.dict.dict_id',
+            defaultMessage: '字典主键',
+          })}
+          placeholder="请输入字典主键"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典主键!" defaultMessage="请输入字典主键!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictName"
+          label={intl.formatMessage({
+            id: 'system.dict.dict_name',
+            defaultMessage: '字典名称',
+          })}
+          placeholder="请输入字典名称"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典名称!" defaultMessage="请输入字典名称!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictType"
+          label={intl.formatMessage({
+            id: 'system.dict.dict_type',
+            defaultMessage: '字典类型',
+          })}
+          placeholder="请输入字典类型"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典类型!" defaultMessage="请输入字典类型!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.dict.status',
+            defaultMessage: '状态',
+          })}
+          initialValue={'0'}
+          placeholder="请输入状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.dict.remark',
+            defaultMessage: '备注',
+          })}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default DictTypeForm;
diff --git a/react-ui/src/pages/System/Dict/index.tsx b/react-ui/src/pages/System/Dict/index.tsx
new file mode 100644
index 0000000..ccdbc2a
--- /dev/null
+++ b/react-ui/src/pages/System/Dict/index.tsx
@@ -0,0 +1,394 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDictTypeList, removeDictType, addDictType, updateDictType, exportDictType } from '@/services/system/dict';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.DictType) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addDictType({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.DictType) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateDictType(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.DictType[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeDictType(selectedRows.map((row) => row.dictId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.DictType) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.dictId];
+    const resp = await removeDictType(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportDictType();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const DictTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.DictType>();
+  const [selectedRows, setSelectedRows] = useState<API.System.DictType[]>([]);
+
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.DictType>[] = [
+    {
+      title: <FormattedMessage id="system.dict.dict_id" defaultMessage="字典编号" />,
+      dataIndex: 'dictId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.dict.dict_name" defaultMessage="字典名称" />,
+      dataIndex: 'dictName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dict.dict_type" defaultMessage="字典类型" />,
+      dataIndex: 'dictType',
+      valueType: 'text',
+      render: (dom, record) => {
+        return (
+          <a
+            onClick={() => {
+              history.push(`/system/dict-data/index/${record.dictId}`);
+            }}
+          >
+            {dom}
+          </a>
+        );
+      },
+    },
+    {
+      title: <FormattedMessage id="system.dict.status" defaultMessage="状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.dict.remark" defaultMessage="备注" />,
+      dataIndex: 'remark',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:dictType:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:dictType:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.DictType>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="dictId"
+          key="dictTypeList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:dictType:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:dictType:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:dictType:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getDictTypeList({ ...params } as API.System.DictTypeListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:dictType:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.dictId) {
+            success = await handleUpdate({ ...values } as API.System.DictType);
+          } else {
+            success = await handleAdd({ ...values } as API.System.DictType);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default DictTableList;
diff --git a/react-ui/src/pages/System/DictData/edit.tsx b/react-ui/src/pages/System/DictData/edit.tsx
new file mode 100644
index 0000000..188e01b
--- /dev/null
+++ b/react-ui/src/pages/System/DictData/edit.tsx
@@ -0,0 +1,252 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormSelect,
+  ProFormRadio,
+  ProFormTextArea,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type DataFormData = Record<string, unknown> & Partial<API.System.DictData>;
+
+export type DataFormProps = {
+  onCancel: (flag?: boolean, formVals?: DataFormData) => void;
+  onSubmit: (values: DataFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.DictData>;
+  statusOptions: DictValueEnumObj;
+};
+
+const DictDataForm: React.FC<DataFormProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const { statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      dictCode: props.values.dictCode,
+      dictSort: props.values.dictSort,
+      dictLabel: props.values.dictLabel,
+      dictValue: props.values.dictValue,
+      dictType: props.values.dictType,
+      cssClass: props.values.cssClass,
+      listClass: props.values.listClass,
+      isDefault: props.values.isDefault,
+      status: props.values.status,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as DataFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.dict.data.title',
+        defaultMessage: '编辑字典数据',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="dictCode"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_code',
+            defaultMessage: '字典编码',
+          })}
+          colProps={{ md: 24, xl: 24 }}
+          placeholder="请输入字典编码"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典编码!" defaultMessage="请输入字典编码!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictType"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_type',
+            defaultMessage: '字典类型',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入字典类型"
+          disabled
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典类型!" defaultMessage="请输入字典类型!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictLabel"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_label',
+            defaultMessage: '字典标签',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入字典标签"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典标签!" defaultMessage="请输入字典标签!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="dictValue"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_value',
+            defaultMessage: '字典键值',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入字典键值"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典键值!" defaultMessage="请输入字典键值!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="cssClass"
+          label={intl.formatMessage({
+            id: 'system.dict.data.css_class',
+            defaultMessage: '样式属性',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入样式属性"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入样式属性!" defaultMessage="请输入样式属性!" />,
+            },
+          ]}
+        />
+        <ProFormSelect
+          name="listClass"
+          label={intl.formatMessage({
+            id: 'system.dict.data.list_class',
+            defaultMessage: '回显样式',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入回显样式"
+          valueEnum={{
+            'default': '默认',
+            'primary': '主要',
+            'success': '成功',
+            'info': '信息',
+            'warning': '警告',
+            'danger': '危险',
+          }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入回显样式!" defaultMessage="请输入回显样式!" />,
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="dictSort"
+          label={intl.formatMessage({
+            id: 'system.dict.data.dict_sort',
+            defaultMessage: '字典排序',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入字典排序"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入字典排序!" defaultMessage="请输入字典排序!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          name="isDefault"
+          label={intl.formatMessage({
+            id: 'system.dict.data.is_default',
+            defaultMessage: '是否默认',
+          })}
+          valueEnum={{
+            'Y': '是',
+            'N': '否',
+          }}
+          initialValue={'N'}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入是否默认"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入是否默认!" defaultMessage="请输入是否默认!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.dict.data.status',
+            defaultMessage: '状态',
+          })}
+          initialValue={'0'}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.dict.data.remark',
+            defaultMessage: '备注',
+          })}
+          colProps={{ md: 24, xl: 24 }}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default DictDataForm;
diff --git a/react-ui/src/pages/System/DictData/index.tsx b/react-ui/src/pages/System/DictData/index.tsx
new file mode 100644
index 0000000..2bf1069
--- /dev/null
+++ b/react-ui/src/pages/System/DictData/index.tsx
@@ -0,0 +1,439 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history, useParams } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getDictDataList, removeDictData, addDictData, updateDictData, exportDictData } from '@/services/system/dictdata';
+import UpdateForm from './edit';
+import { getDictValueEnum, getDictType, getDictTypeOptionSelect } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.DictData) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addDictData({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.DictData) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateDictData(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.DictData[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeDictData(selectedRows.map((row) => row.dictCode).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.DictData) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.dictCode];
+    const resp = await removeDictData(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportDictData();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+export type DictTypeArgs = {
+  id: string;
+};
+
+
+const DictDataTableList: React.FC = () => {
+
+  const formTableRef = useRef<FormInstance>();
+
+  const [dictId, setDictId] = useState<string>('');
+  const [dictType, setDictType] = useState<string>('');
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.DictData>();
+  const [selectedRows, setSelectedRows] = useState<API.System.DictData[]>([]);
+
+  const [dictTypeOptions, setDictTypeOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  const params = useParams();
+  if (params.id === undefined) {
+    history.push('/system/dict');
+  }
+  const id = params.id || '0';
+
+  useEffect(() => {
+    if (dictId !== id) {
+      setDictId(id);
+      getDictTypeOptionSelect().then((res) => {
+        if (res.code === 200) {
+          const opts: any = {};
+          res.data.forEach((item: any) => {
+            opts[item.dictType] = item.dictName;
+          });
+          setDictTypeOptions(opts);
+        }
+      });
+      getDictValueEnum('sys_normal_disable').then((data) => {
+        setStatusOptions(data);
+      });
+      getDictType(id).then((res) => {
+        if (res.code === 200) {
+          setDictType(res.data.dictType);
+          formTableRef.current?.setFieldsValue({
+            dictType: res.data.dictType,
+          });
+          actionRef.current?.reloadAndRest?.();
+        } else {
+          message.error(res.msg);
+        }
+      });
+    }
+  }, [dictId, dictType, params]);
+
+  const columns: ProColumns<API.System.DictData>[] = [
+    {
+      title: <FormattedMessage id="system.dict.data.dict_code" defaultMessage="字典编码" />,
+      dataIndex: 'dictCode',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.dict_label" defaultMessage="字典标签" />,
+      dataIndex: 'dictLabel',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.dict_type" defaultMessage="字典类型" />,
+      dataIndex: 'dictType',
+      valueType: 'select',
+      hideInTable: true,
+      valueEnum: dictTypeOptions,
+      search: {
+        transform: (value) => {
+          setDictType(value);
+          return value;
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.dict_value" defaultMessage="字典键值" />,
+      dataIndex: 'dictValue',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.dict_sort" defaultMessage="字典排序" />,
+      dataIndex: 'dictSort',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.status" defaultMessage="状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.remark" defaultMessage="备注" />,
+      dataIndex: 'remark',
+      valueType: 'textarea',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.dict.data.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:data:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:data:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.DictData>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="dictCode"
+          key="dataList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:data:add')}
+              onClick={async () => {
+                setCurrentRow({ dictType: dictType, isDefault: 'N', status: '0' } as API.System.DictData);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:data:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:data:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getDictDataList({ ...params, dictType } as API.System.DictDataListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:data:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.dictCode) {
+            success = await handleUpdate({ ...values } as API.System.DictData);
+          } else {
+            success = await handleAdd({ ...values } as API.System.DictData);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default DictDataTableList;
diff --git a/react-ui/src/pages/System/Logininfor/edit.tsx b/react-ui/src/pages/System/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/react-ui/src/pages/System/Logininfor/edit.tsx
@@ -0,0 +1,216 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTimePicker,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type LogininforFormData = Record<string, unknown> & Partial<API.Monitor.Logininfor>;
+
+export type LogininforFormProps = {
+  onCancel: (flag?: boolean, formVals?: LogininforFormData) => void;
+  onSubmit: (values: LogininforFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Logininfor>;
+  statusOptions: DictValueEnumObj;
+};
+
+const LogininforForm: React.FC<LogininforFormProps> = (props) => {
+  const [form] = Form.useForm();
+  
+  const { statusOptions, } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			infoId: props.values.infoId,
+			userName: props.values.userName,
+			ipaddr: props.values.ipaddr,
+			loginLocation: props.values.loginLocation,
+			browser: props.values.browser,
+			os: props.values.os,
+			status: props.values.status,
+			msg: props.values.msg,
+			loginTime: props.values.loginTime,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+    form.resetFields();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as LogininforFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.logininfor.title',
+        defaultMessage: '编辑系统访问记录',
+      })}
+      open={props.open}
+      destroyOnClose
+      forceRender
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="infoId"
+          label={intl.formatMessage({
+            id: 'system.logininfor.info_id',
+            defaultMessage: '访问编号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入访问编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入访问编号!" defaultMessage="请输入访问编号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="userName"
+          label={intl.formatMessage({
+            id: 'system.logininfor.user_name',
+            defaultMessage: '用户账号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入用户账号"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入用户账号!" defaultMessage="请输入用户账号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="ipaddr"
+          label={intl.formatMessage({
+            id: 'system.logininfor.ipaddr',
+            defaultMessage: '登录IP地址',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录IP地址"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录IP地址!" defaultMessage="请输入登录IP地址!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="loginLocation"
+          label={intl.formatMessage({
+            id: 'system.logininfor.login_location',
+            defaultMessage: '登录地点',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录地点"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录地点!" defaultMessage="请输入登录地点!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="browser"
+          label={intl.formatMessage({
+            id: 'system.logininfor.browser',
+            defaultMessage: '浏览器类型',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入浏览器类型"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入浏览器类型!" defaultMessage="请输入浏览器类型!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="os"
+          label={intl.formatMessage({
+            id: 'system.logininfor.os',
+            defaultMessage: '操作系统',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入操作系统"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入操作系统!" defaultMessage="请输入操作系统!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.logininfor.status',
+            defaultMessage: '登录状态',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入登录状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入登录状态!" defaultMessage="请输入登录状态!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="msg"
+          label={intl.formatMessage({
+            id: 'system.logininfor.msg',
+            defaultMessage: '提示消息',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入提示消息"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入提示消息!" defaultMessage="请输入提示消息!" />,                  
+            },
+          ]}
+        />
+        <ProFormTimePicker
+          name="loginTime"
+          label={intl.formatMessage({
+            id: 'system.logininfor.login_time',
+            defaultMessage: '访问时间',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入访问时间"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入访问时间!" defaultMessage="请输入访问时间!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default LogininforForm;
diff --git a/react-ui/src/pages/System/Logininfor/index.tsx b/react-ui/src/pages/System/Logininfor/index.tsx
new file mode 100644
index 0000000..ee5c1e5
--- /dev/null
+++ b/react-ui/src/pages/System/Logininfor/index.tsx
@@ -0,0 +1,321 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, UnlockOutlined } from '@ant-design/icons';
+import { getLogininforList, removeLogininfor, exportLogininfor, unlockLogininfor, cleanLogininfor } from '@/services/monitor/logininfor';
+import DictTag from '@/components/DictTag';
+import { getDictValueEnum } from '@/services/system/dict';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Logininfor[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeLogininfor(selectedRows.map((row) => row.infoId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleClean = async () => {
+  const hide = message.loading('请稍候');
+  try {
+    const resp = await cleanLogininfor();
+    hide();
+    if (resp.code === 200) {
+      message.success('清空成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('请求失败,请重试');
+    return false;
+  }
+};
+
+const handleUnlock = async (userName: string) => {
+  const hide = message.loading('正在解锁');
+  try {
+    const resp = await unlockLogininfor(userName);
+    hide();
+    if (resp.code === 200) {
+      message.success('解锁成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('解锁失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ * @param id
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportLogininfor();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const LogininforTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const actionRef = useRef<ActionType>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Logininfor[]>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_common_status', true).then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Logininfor>[] = [
+    {
+      title: <FormattedMessage id="monitor.logininfor.info_id" defaultMessage="访问编号" />,
+      dataIndex: 'infoId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.ipaddr" defaultMessage="登录IP地址" />,
+      dataIndex: 'ipaddr',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.login_location" defaultMessage="登录地点" />,
+      dataIndex: 'loginLocation',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.browser" defaultMessage="浏览器类型" />,
+      dataIndex: 'browser',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.os" defaultMessage="操作系统" />,
+      dataIndex: 'os',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.status" defaultMessage="登录状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.msg" defaultMessage="提示消息" />,
+      dataIndex: 'msg',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.logininfor.login_time" defaultMessage="访问时间" />,
+      dataIndex: 'loginTime',
+      valueType: 'dateTime',
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Logininfor>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="infoId"
+          key="logininforList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="clean"
+              danger
+              hidden={!access.hasPerms('monitor:logininfor:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认清空所有数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleClean();
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+            </Button>,
+            <Button
+              type="primary"
+              key="unlock"
+              hidden={selectedRows?.length === 0 || !access.hasPerms('monitor:logininfor:unlock')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认解锁该用户的数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleUnlock(selectedRows[0].userName);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <UnlockOutlined />
+              <FormattedMessage id="monitor.logininfor.unlock" defaultMessage="解锁" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('monitor:logininfor:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getLogininforList({ ...params } as API.Monitor.LogininforListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('monitor:logininfor: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>
+      )}
+    </PageContainer>
+  );
+};
+
+export default LogininforTableList;
diff --git a/react-ui/src/pages/System/Menu/edit.tsx b/react-ui/src/pages/System/Menu/edit.tsx
new file mode 100644
index 0000000..e094255
--- /dev/null
+++ b/react-ui/src/pages/System/Menu/edit.tsx
@@ -0,0 +1,386 @@
+import React, { useEffect, useState } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTreeSelect,
+  ProFormSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { createIcon } from '@/utils/IconUtil';
+import { DictValueEnumObj } from '@/components/DictTag';
+import IconSelector from '@/components/IconSelector';
+
+export type MenuFormData = Record<string, unknown> & Partial<API.System.Menu>;
+
+export type MenuFormProps = {
+  onCancel: (flag?: boolean, formVals?: MenuFormData) => void;
+  onSubmit: (values: MenuFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Menu>;
+  visibleOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+  menuTree: DataNode[];
+};
+
+const MenuForm: React.FC<MenuFormProps> = (props) => {
+
+  const [form] = Form.useForm();
+
+  const [menuTypeId, setMenuTypeId] = useState<any>('M');
+  const [menuIconName, setMenuIconName] = useState<any>();
+  const [iconSelectorOpen, setIconSelectorOpen] = useState<boolean>(false);
+
+  const { menuTree, visibleOptions, statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    setMenuIconName(props.values.icon);
+    form.setFieldsValue({
+      menuId: props.values.menuId,
+      menuName: props.values.menuName,
+      parentId: props.values.parentId,
+      orderNum: props.values.orderNum,
+      path: props.values.path,
+      component: props.values.component,
+      query: props.values.query,
+      isFrame: props.values.isFrame,
+      isCache: props.values.isCache,
+      menuType: props.values.menuType,
+      visible: props.values.visible,
+      status: props.values.status,
+      perms: props.values.perms,
+      icon: props.values.icon,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as MenuFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.menu.title',
+        defaultMessage: '编辑菜单权限',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal"
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="menuId"
+          label={intl.formatMessage({
+            id: 'system.menu.menu_id',
+            defaultMessage: '菜单编号',
+          })}
+          placeholder="请输入菜单编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入菜单编号!" defaultMessage="请输入菜单编号!" />,
+            },
+          ]}
+        />
+        <ProFormTreeSelect
+          name="parentId"
+          label={intl.formatMessage({
+            id: 'system.menu.parent_id',
+            defaultMessage: '上级菜单',
+          })}
+          params={{menuTree}}
+          request={async () => {
+            return menuTree;
+          }}
+          placeholder="请输入父菜单编号"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入父菜单编号!" defaultMessage="请输入父菜单编号!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: 0
+          }}
+        />
+        <ProFormRadio.Group
+          name="menuType"
+          valueEnum={{
+            M: '目录',
+            C: '菜单',
+            F: '按钮',
+          }}
+          label={intl.formatMessage({
+            id: 'system.menu.menu_type',
+            defaultMessage: '菜单类型',
+          })}
+          placeholder="请输入菜单类型"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入菜单类型!" defaultMessage="请输入菜单类型!" />,
+            },
+          ]}
+          fieldProps={{
+            defaultValue: 'M',
+            onChange: (e) => {
+              setMenuTypeId(e.target.value);
+            },
+          }}
+        />
+        <ProFormSelect
+          name="icon"
+          label={intl.formatMessage({
+            id: 'system.menu.icon',
+            defaultMessage: '菜单图标',
+          })}
+          valueEnum={{}}
+          hidden={menuTypeId === 'F'}
+          addonBefore={createIcon(menuIconName)}
+          fieldProps={{
+            onClick: () => {
+              setIconSelectorOpen(true);
+            },
+          }}
+          placeholder="请输入菜单图标"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入菜单图标!" defaultMessage="请输入菜单图标!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="menuName"
+          label={intl.formatMessage({
+            id: 'system.menu.menu_name',
+            defaultMessage: '菜单名称',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入菜单名称"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入菜单名称!" defaultMessage="请输入菜单名称!" />,
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="orderNum"
+          label={intl.formatMessage({
+            id: 'system.menu.order_num',
+            defaultMessage: '显示顺序',
+          })}
+          width="lg"
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入显示顺序"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: 1
+          }}
+        />
+        <ProFormRadio.Group
+          name="isFrame"
+          valueEnum={{
+            0: '是',
+            1: '否',
+          }}
+          initialValue="1"
+          label={intl.formatMessage({
+            id: 'system.menu.is_frame',
+            defaultMessage: '是否为外链',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入是否为外链"
+          hidden={menuTypeId === 'F'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入是否为外链!" defaultMessage="请输入是否为外链!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: '1'
+          }}
+        />
+        <ProFormText
+          name="path"
+          label={intl.formatMessage({
+            id: 'system.menu.path',
+            defaultMessage: '路由地址',
+          })}
+          width="lg"
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入路由地址"
+          hidden={menuTypeId === 'F'}
+          rules={[
+            {
+              required: menuTypeId !== 'F',
+              message: <FormattedMessage id="请输入路由地址!" defaultMessage="请输入路由地址!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="component"
+          label={intl.formatMessage({
+            id: 'system.menu.component',
+            defaultMessage: '组件路径',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入组件路径"
+          hidden={menuTypeId !== 'C'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入组件路径!" defaultMessage="请输入组件路径!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="query"
+          label={intl.formatMessage({
+            id: 'system.menu.query',
+            defaultMessage: '路由参数',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入路由参数"
+          hidden={menuTypeId !== 'C'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入路由参数!" defaultMessage="请输入路由参数!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="perms"
+          label={intl.formatMessage({
+            id: 'system.menu.perms',
+            defaultMessage: '权限标识',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入权限标识"
+          hidden={menuTypeId === 'M'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入权限标识!" defaultMessage="请输入权限标识!" />,
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          name="isCache"
+          valueEnum={{
+            0: '缓存',
+            1: '不缓存',
+          }}
+          label={intl.formatMessage({
+            id: 'system.menu.is_cache',
+            defaultMessage: '是否缓存',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入是否缓存"
+          hidden={menuTypeId !== 'C'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入是否缓存!" defaultMessage="请输入是否缓存!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: 0
+          }}
+        />
+        <ProFormRadio.Group
+          name="visible"
+          valueEnum={visibleOptions}
+          label={intl.formatMessage({
+            id: 'system.menu.visible',
+            defaultMessage: '显示状态',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入显示状态"
+          hidden={menuTypeId === 'F'}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入显示状态!" defaultMessage="请输入显示状态!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: '0'
+          }}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.menu.status',
+            defaultMessage: '菜单状态',
+          })}
+          colProps={{ md: 12, xl: 12 }}
+          placeholder="请输入菜单状态"
+          hidden={menuTypeId === 'F'}
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入菜单状态!" defaultMessage="请输入菜单状态!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: '0'
+          }}
+        />
+      </ProForm>
+      <Modal
+        width={800}
+        open={iconSelectorOpen}
+        onCancel={() => {
+          setIconSelectorOpen(false);
+        }}
+        footer={null}
+      >
+        <IconSelector
+          onSelect={(name: string) => {
+            form.setFieldsValue({ icon: name });
+            setMenuIconName(name);
+            setIconSelectorOpen(false);
+          }}
+        />
+      </Modal>
+    </Modal>
+  );
+};
+
+export default MenuForm;
diff --git a/react-ui/src/pages/System/Menu/index.tsx b/react-ui/src/pages/System/Menu/index.tsx
new file mode 100644
index 0000000..95ca2de
--- /dev/null
+++ b/react-ui/src/pages/System/Menu/index.tsx
@@ -0,0 +1,339 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getMenuList, removeMenu, addMenu, updateMenu } from '@/services/system/menu';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { buildTreeData } from '@/utils/tree';
+import { DataNode } from 'antd/es/tree';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Menu) => {
+  const hide = message.loading('正在添加');
+  try {
+    await addMenu({ ...fields });
+    hide();
+    message.success('添加成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Menu) => {
+  const hide = message.loading('正在配置');
+  try {
+    await updateMenu(fields);
+    hide();
+    message.success('配置成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Menu[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    await removeMenu(selectedRows.map((row) => row.menuId).join(','));
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Menu) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.menuId];
+    await removeMenu(params.join(','));
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+
+const MenuTableList: React.FC = () => {
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Menu>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Menu[]>([]);
+
+  const [menuTree, setMenuTree] = useState<DataNode[]>([]);
+  const [visibleOptions, setVisibleOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_show_hide').then((data) => {
+      setVisibleOptions(data);
+    });
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Menu>[] = [
+    {
+      title: <FormattedMessage id="system.menu.menu_name" defaultMessage="菜单名称" />,
+      dataIndex: 'menuName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.menu.icon" defaultMessage="菜单图标" />,
+      dataIndex: 'icon',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.menu.order_num" defaultMessage="显示顺序" />,
+      dataIndex: 'orderNum',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.menu.component" defaultMessage="组件路径" />,
+      dataIndex: 'component',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.menu.perms" defaultMessage="权限标识" />,
+      dataIndex: 'perms',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.menu.status" defaultMessage="菜单状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:menu:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:menu:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Menu>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          rowKey="menuId"
+          key="menuList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:menu:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:menu:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getMenuList({ ...params } as API.System.MenuListParams).then((res) => {
+              const rootMenu = { id: 0, label: '主类目', children: [] as DataNode[], value: 0 };
+              const memuData = buildTreeData(res.data, 'menuId', 'menuName', '', '', '');
+              rootMenu.children = memuData;
+              const treeData: any = [];
+              treeData.push(rootMenu);
+              setMenuTree(treeData);
+              return {
+                data: memuData,
+                total: res.data.length,
+                success: true,
+              };
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:menu:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.menuId) {
+            success = await handleUpdate({ ...values } as API.System.Menu);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Menu);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        visibleOptions={visibleOptions}
+        statusOptions={statusOptions}
+        menuTree={menuTree}
+      />
+    </PageContainer>
+  );
+};
+
+export default MenuTableList;
diff --git a/react-ui/src/pages/System/Notice/edit.tsx b/react-ui/src/pages/System/Notice/edit.tsx
new file mode 100644
index 0000000..0b111ce
--- /dev/null
+++ b/react-ui/src/pages/System/Notice/edit.tsx
@@ -0,0 +1,174 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormSelect,
+  ProFormTextArea,
+  ProFormRadio,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type NoticeFormData = Record<string, unknown> & Partial<API.System.Notice>;
+
+export type NoticeFormProps = {
+  onCancel: (flag?: boolean, formVals?: NoticeFormData) => void;
+  onSubmit: (values: NoticeFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Notice>;
+  noticeTypeOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+};
+
+const NoticeForm: React.FC<NoticeFormProps> = (props) => {
+  const [form] = Form.useForm();
+  
+  const { noticeTypeOptions,statusOptions, } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			noticeId: props.values.noticeId,
+			noticeTitle: props.values.noticeTitle,
+			noticeType: props.values.noticeType,
+			noticeContent: props.values.noticeContent,
+			status: props.values.status,
+			createBy: props.values.createBy,
+			createTime: props.values.createTime,
+			updateBy: props.values.updateBy,
+			updateTime: props.values.updateTime,
+			remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as NoticeFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.notice.title',
+        defaultMessage: '编辑通知公告',
+      })}
+      forceRender
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="noticeId"
+          label={intl.formatMessage({
+            id: 'system.notice.notice_id',
+            defaultMessage: '公告编号',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入公告编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入公告编号!" defaultMessage="请输入公告编号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="noticeTitle"
+          label={intl.formatMessage({
+            id: 'system.notice.notice_title',
+            defaultMessage: '公告标题',
+          })}
+          placeholder="请输入公告标题"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入公告标题!" defaultMessage="请输入公告标题!" />,                  
+            },
+          ]}
+        />
+        <ProFormSelect
+          valueEnum={noticeTypeOptions}
+          name="noticeType"
+          label={intl.formatMessage({
+            id: 'system.notice.notice_type',
+            defaultMessage: '公告类型',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入公告类型"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入公告类型!" defaultMessage="请输入公告类型!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.notice.status',
+            defaultMessage: '公告状态',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入公告状态"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入公告状态!" defaultMessage="请输入公告状态!" />,                  
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="noticeContent"
+          label={intl.formatMessage({
+            id: 'system.notice.notice_content',
+            defaultMessage: '公告内容',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入公告内容"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入公告内容!" defaultMessage="请输入公告内容!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.notice.remark',
+            defaultMessage: '备注',
+          })}
+          colProps={{ md: 12, xl: 24 }}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default NoticeForm;
diff --git a/react-ui/src/pages/System/Notice/index.tsx b/react-ui/src/pages/System/Notice/index.tsx
new file mode 100644
index 0000000..ea5b063
--- /dev/null
+++ b/react-ui/src/pages/System/Notice/index.tsx
@@ -0,0 +1,366 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getNoticeList, removeNotice, addNotice, updateNotice } from '@/services/system/notice';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Notice) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addNotice({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Notice) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateNotice(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Notice[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeNotice(selectedRows.map((row) => row.noticeId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Notice) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.noticeId];
+    const resp = await removeNotice(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+
+
+const NoticeTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Notice>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Notice[]>([]);
+
+  const [noticeTypeOptions, setNoticeTypeOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_notice_type').then((data) => {
+      setNoticeTypeOptions(data);
+    });
+    getDictValueEnum('sys_notice_status').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Notice>[] = [
+    {
+      title: <FormattedMessage id="system.notice.notice_id" defaultMessage="公告编号" />,
+      dataIndex: 'noticeId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.notice.notice_title" defaultMessage="公告标题" />,
+      dataIndex: 'noticeTitle',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.notice.notice_type" defaultMessage="公告类型" />,
+      dataIndex: 'noticeType',
+      valueType: 'select',
+      valueEnum: noticeTypeOptions,
+    },
+    {
+      title: <FormattedMessage id="system.notice.notice_content" defaultMessage="公告内容" />,
+      dataIndex: 'noticeContent',
+      valueType: 'text',
+      hideInTable: true,
+    },
+    {
+      title: <FormattedMessage id="system.notice.status" defaultMessage="公告状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.notice.remark" defaultMessage="备注" />,
+      dataIndex: 'remark',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.notice.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:notice:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:notice:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Notice>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="noticeId"
+          key="noticeList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:notice:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:notice:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getNoticeList({ ...params } as API.System.NoticeListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:notice:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.noticeId) {
+            success = await handleUpdate({ ...values } as API.System.Notice);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Notice);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        noticeTypeOptions={noticeTypeOptions}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default NoticeTableList;
diff --git a/react-ui/src/pages/System/Operlog/detail.tsx b/react-ui/src/pages/System/Operlog/detail.tsx
new file mode 100644
index 0000000..f5b4f65
--- /dev/null
+++ b/react-ui/src/pages/System/Operlog/detail.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import { Descriptions, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { getValueEnumLabel } from '@/utils/options';
+
+export type OperlogFormData = Record<string, unknown> & Partial<API.Monitor.Operlog>;
+
+export type OperlogFormProps = {
+  onCancel: (flag?: boolean, formVals?: OperlogFormData) => void;
+  onSubmit: (values: OperlogFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.Monitor.Operlog>;
+  businessTypeOptions: DictValueEnumObj;
+  operatorTypeOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+};
+
+const OperlogDetailForm: React.FC<OperlogFormProps> = (props) => {
+
+  const { values, businessTypeOptions, operatorTypeOptions, statusOptions, } = props;
+
+  const intl = useIntl();
+  const handleOk = () => {
+    console.log("handle ok");
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'monitor.operlog.title',
+        defaultMessage: '编辑操作日志记录',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Descriptions column={24}>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.module" defaultMessage="操作模块" />}
+        >
+          {`${values.title}/${getValueEnumLabel(businessTypeOptions, values.businessType)}`}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />}
+        >
+          {values.requestMethod}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />}
+        >
+          {`${values.operName}/${values.operIp}`}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />}
+        >
+          {getValueEnumLabel(operatorTypeOptions, values.operatorType)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.method" defaultMessage="方法名称" />}
+        >
+          {values.method}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.oper_url" defaultMessage="请求URL" />}
+        >
+          {values.operUrl}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.oper_param" defaultMessage="请求参数" />}
+        >
+          {values.operParam}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.json_result" defaultMessage="返回参数" />}
+        >
+          {values.jsonResult}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={24}
+          label={<FormattedMessage id="monitor.operlog.error_msg" defaultMessage="错误消息" />}
+        >
+          {values.errorMsg}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />}
+        >
+          {getValueEnumLabel(statusOptions, values.status)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={12}
+          label={<FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />}
+        >
+          {values.operTime?.toString()}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+
+export default OperlogDetailForm;
diff --git a/react-ui/src/pages/System/Operlog/index.tsx b/react-ui/src/pages/System/Operlog/index.tsx
new file mode 100644
index 0000000..be46709
--- /dev/null
+++ b/react-ui/src/pages/System/Operlog/index.tsx
@@ -0,0 +1,411 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getOperlogList, removeOperlog, addOperlog, updateOperlog, cleanAllOperlog, exportOperlog } from '@/services/monitor/operlog';
+import UpdateForm from './detail';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.Monitor.Operlog) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addOperlog({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.Monitor.Operlog) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateOperlog(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.Monitor.Operlog[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeOperlog(selectedRows.map((row) => row.operId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 清空所有记录
+ *
+ */
+const handleCleanAll = async () => {
+  const hide = message.loading('正在清空');
+  try {
+    const resp = await cleanAllOperlog();
+    hide();
+    if (resp.code === 200) {
+      message.success('清空成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('清空失败,请重试');
+    return false;
+  }
+};
+
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportOperlog();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const OperlogTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.Monitor.Operlog>();
+  const [selectedRows, setSelectedRows] = useState<API.Monitor.Operlog[]>([]);
+
+  const [businessTypeOptions, setBusinessTypeOptions] = useState<any>([]);
+  const [operatorTypeOptions, setOperatorTypeOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_oper_type', true).then((data) => {
+      setBusinessTypeOptions(data);
+    });
+    getDictValueEnum('sys_oper_type', true).then((data) => {
+      setOperatorTypeOptions(data);
+    });
+    getDictValueEnum('sys_common_status', true).then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.Monitor.Operlog>[] = [
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_id" defaultMessage="日志主键" />,
+      dataIndex: 'operId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.title" defaultMessage="操作模块" />,
+      dataIndex: 'title',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.business_type" defaultMessage="业务类型" />,
+      dataIndex: 'businessType',
+      valueType: 'select',
+      valueEnum: businessTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={businessTypeOptions} value={record.businessType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.request_method" defaultMessage="请求方式" />,
+      dataIndex: 'requestMethod',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.operator_type" defaultMessage="操作类别" />,
+      dataIndex: 'operatorType',
+      valueType: 'select',
+      valueEnum: operatorTypeOptions,
+      render: (_, record) => {
+        return (<DictTag enums={operatorTypeOptions} value={record.operatorType} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_name" defaultMessage="操作人员" />,
+      dataIndex: 'operName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_ip" defaultMessage="主机地址" />,
+      dataIndex: 'operIp',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_location" defaultMessage="操作地点" />,
+      dataIndex: 'operLocation',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.status" defaultMessage="操作状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag key="status" enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="monitor.operlog.oper_time" defaultMessage="操作时间" />,
+      dataIndex: 'operTime',
+      valueType: 'dateTime',
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '120px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:operlog:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          详细
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.Monitor.Operlog>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="operId"
+          key="operlogList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:operlog:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:operlog:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="clean"
+              danger
+              hidden={!access.hasPerms('system:operlog:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认清空所有数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleCleanAll();
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.cleanAll" defaultMessage="清空" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:operlog:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getOperlogList({ ...params } as API.Monitor.OperlogListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:operlog:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.operId) {
+            success = await handleUpdate({ ...values } as API.Monitor.Operlog);
+          } else {
+            success = await handleAdd({ ...values } as API.Monitor.Operlog);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        businessTypeOptions={businessTypeOptions}
+        operatorTypeOptions={operatorTypeOptions}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default OperlogTableList;
diff --git a/react-ui/src/pages/System/Post/edit.tsx b/react-ui/src/pages/System/Post/edit.tsx
new file mode 100644
index 0000000..555fcde
--- /dev/null
+++ b/react-ui/src/pages/System/Post/edit.tsx
@@ -0,0 +1,166 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTextArea,
+  } from '@ant-design/pro-components';
+import { Form, Modal} from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type PostFormData = Record<string, unknown> & Partial<API.System.Post>;
+
+export type PostFormProps = {
+  onCancel: (flag?: boolean, formVals?: PostFormData) => void;
+  onSubmit: (values: PostFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Post>;
+  statusOptions: DictValueEnumObj;
+};
+
+const PostForm: React.FC<PostFormProps> = (props) => {
+  const [form] = Form.useForm();
+
+  const { statusOptions, } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+			postId: props.values.postId,
+			postCode: props.values.postCode,
+			postName: props.values.postName,
+			postSort: props.values.postSort,
+			status: props.values.status,
+			createBy: props.values.createBy,
+			createTime: props.values.createTime,
+			updateBy: props.values.updateBy,
+			updateTime: props.values.updateTime,
+			remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as PostFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.post.title',
+        defaultMessage: '编辑岗位信息',
+      })}
+      open={props.open}
+      forceRender
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+		  <ProForm 
+        form={form}
+        grid={true}
+        submitter={false}
+        layout="horizontal" 
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="postId"
+          label={intl.formatMessage({
+            id: 'system.post.post_id',
+            defaultMessage: '岗位编号',
+          })}
+          placeholder="请输入岗位编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入岗位编号!" defaultMessage="请输入岗位编号!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="postName"
+          label={intl.formatMessage({
+            id: 'system.post.post_name',
+            defaultMessage: '岗位名称',
+          })}
+          placeholder="请输入岗位名称"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入岗位名称!" defaultMessage="请输入岗位名称!" />,                  
+            },
+          ]}
+        />
+        <ProFormText
+          name="postCode"
+          label={intl.formatMessage({
+            id: 'system.post.post_code',
+            defaultMessage: '岗位编码',
+          })}
+          placeholder="请输入岗位编码"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入岗位编码!" defaultMessage="请输入岗位编码!" />,                  
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="postSort"
+          label={intl.formatMessage({
+            id: 'system.post.post_sort',
+            defaultMessage: '显示顺序',
+          })}
+          placeholder="请输入显示顺序"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,                  
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.post.status',
+            defaultMessage: '状态',
+          })}
+          placeholder="请输入状态"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入状态!" defaultMessage="请输入状态!" />,                  
+            },
+          ]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.post.remark',
+            defaultMessage: '备注',
+          })}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,                  
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default PostForm;
diff --git a/react-ui/src/pages/System/Post/index.tsx b/react-ui/src/pages/System/Post/index.tsx
new file mode 100644
index 0000000..ad98aae
--- /dev/null
+++ b/react-ui/src/pages/System/Post/index.tsx
@@ -0,0 +1,366 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import type { FormInstance } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getPostList, removePost, addPost, updatePost, exportPost } from '@/services/system/post';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Post) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addPost({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Post) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updatePost(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Post[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removePost(selectedRows.map((row) => row.postId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Post) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.postId];
+    const resp = await removePost(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportPost();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const PostTableList: React.FC = () => {
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Post>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Post[]>([]);
+
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const columns: ProColumns<API.System.Post>[] = [
+    {
+      title: <FormattedMessage id="system.post.post_id" defaultMessage="岗位编号" />,
+      dataIndex: 'postId',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.post.post_code" defaultMessage="岗位编码" />,
+      dataIndex: 'postCode',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.post.post_name" defaultMessage="岗位名称" />,
+      dataIndex: 'postName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.post.post_sort" defaultMessage="显示顺序" />,
+      dataIndex: 'postSort',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.post.status" defaultMessage="状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          hidden={!access.hasPerms('system:post:edit')}
+          onClick={() => {
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          hidden={!access.hasPerms('system:post:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Post>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="postId"
+          key="postList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:post:add')}
+              onClick={async () => {
+                setCurrentRow(undefined);
+                setModalVisible(true);
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:post:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() {},
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:post:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getPostList({ ...params } as API.System.PostListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:post:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.postId) {
+            success = await handleUpdate({ ...values } as API.System.Post);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Post);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        statusOptions={statusOptions}
+      />
+    </PageContainer>
+  );
+};
+
+export default PostTableList;
diff --git a/react-ui/src/pages/System/Role/authUser.tsx b/react-ui/src/pages/System/Role/authUser.tsx
new file mode 100644
index 0000000..c553d99
--- /dev/null
+++ b/react-ui/src/pages/System/Role/authUser.tsx
@@ -0,0 +1,274 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history, useParams } from '@umijs/max';
+import { Button, Modal, message } from 'antd';
+import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, RollbackOutlined } from '@ant-design/icons';
+import { authUserSelectAll, authUserCancel, authUserCancelAll, allocatedUserList, unallocatedUserList } from '@/services/system/role';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+import UserSelectorModal from './components/UserSelectorModal';
+import { HttpResult } from '@/enums/httpEnum';
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const cancelAuthUserAll = async (roleId: string, selectedRows: API.System.User[]) => {
+	const hide = message.loading('正在取消授权');
+	if (!selectedRows) return true;
+	try {
+		const userIds = selectedRows.map((row) => row.userId).join(',');
+		const resp = await authUserCancelAll({roleId, userIds});
+		hide();
+		if (resp.code === 200) {
+			message.success('取消授权成功,即将刷新');
+		} else {
+			message.error(resp.msg);
+		}
+		return true;
+	} catch (error) {
+		hide();
+		message.error('取消授权失败,请重试');
+		return false;
+	}
+};
+
+const cancelAuthUser = async (roleId: string, userId: number) => {
+	const hide = message.loading('正在取消授权');
+	try {
+		const resp = await authUserCancel({ userId, roleId });
+		hide();
+		if (resp.code === 200) {
+			message.success('取消授权成功,即将刷新');
+		} else {
+			message.error(resp.msg);
+		}
+		return true;
+	} catch (error) {
+		hide();
+		message.error('取消授权失败,请重试');
+		return false;
+	}
+};
+
+
+const AuthUserTableList: React.FC = () => {
+
+	const [modalVisible, setModalVisible] = useState<boolean>(false);
+
+	const actionRef = useRef<ActionType>();
+	const [selectedRows, setSelectedRows] = useState<API.System.User[]>([]);
+	const [statusOptions, setStatusOptions] = useState<any>([]);
+
+	const access = useAccess();
+
+	/** 国际化配置 */
+	const intl = useIntl();
+
+	const params = useParams();
+	if (params.id === undefined) {
+		history.back();
+	}
+	const roleId = params.id || '0';
+
+	useEffect(() => {
+		getDictValueEnum('sys_normal_disable').then((data) => {
+			setStatusOptions(data);
+		});
+	}, []);
+
+	const columns: ProColumns<API.System.User>[] = [
+		{
+			title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+			dataIndex: 'deptId',
+			valueType: 'text',
+		},
+		{
+			title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+			dataIndex: 'userName',
+			valueType: 'text',
+		},
+		{
+			title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+			dataIndex: 'nickName',
+			valueType: 'text',
+		},
+		{
+			title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+			dataIndex: 'phonenumber',
+			valueType: 'text',
+		},
+		{
+			title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+			dataIndex: 'createTime',
+			valueType: 'dateRange',
+			render: (_, record) => {
+				return (<span>{record.createTime.toString()} </span>);
+			},
+			hideInSearch: true,
+		},
+		{
+			title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+			dataIndex: 'status',
+			valueType: 'select',
+			valueEnum: statusOptions,
+			render: (_, record) => {
+				return (<DictTag enums={statusOptions} value={record.status} />);
+			},
+		},
+		{
+			title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+			dataIndex: 'option',
+			width: '60px',
+			valueType: 'option',
+			render: (_, record) => [
+				<Button
+					type="link"
+					size="small"
+					danger
+					icon={<DeleteOutlined />}
+					key="remove"
+					hidden={!access.hasPerms('system:role:remove')}
+					onClick={async () => {
+						Modal.confirm({
+							title: '删除',
+							content: '确认要取消该用户' + record.userName + '"角色授权吗?',
+							okText: '确认',
+							cancelText: '取消',
+							onOk: async () => {
+								const success = await cancelAuthUser(roleId, record.userId);
+								if (success) {
+									if (actionRef.current) {
+										actionRef.current.reload();
+									}
+								}
+							},
+						});
+					}}
+				>
+					取消授权
+				</Button>,
+			],
+		},
+	];
+
+	return (
+		<PageContainer>
+			<div style={{ width: '100%', float: 'right' }}>
+				<ProTable<API.System.User>
+					headerTitle={intl.formatMessage({
+						id: 'pages.searchTable.title',
+						defaultMessage: '信息',
+					})}
+					actionRef={actionRef}
+					rowKey="userId"
+					key="userList"
+					search={{
+						labelWidth: 120,
+					}}
+					toolBarRender={() => [
+						<Button
+							type="primary"
+							key="add"
+							hidden={!access.hasPerms('system:role:add')}
+							onClick={async () => {
+								setModalVisible(true);
+							}}
+						>
+							<PlusOutlined /> <FormattedMessage id="system.role.auth.addUser" defaultMessage="添加用户" />
+						</Button>,
+						<Button
+							type="primary"
+							key="remove"
+              danger
+							hidden={selectedRows?.length === 0 || !access.hasPerms('system:role:remove')}
+							onClick={async () => {
+								Modal.confirm({
+									title: '是否确认删除所选数据项?',
+									icon: <ExclamationCircleOutlined />,
+									content: '请谨慎操作',
+									async onOk() {
+										const success = await cancelAuthUserAll(roleId, selectedRows);
+										if (success) {
+											setSelectedRows([]);
+											actionRef.current?.reloadAndRest?.();
+										}
+									},
+									onCancel() { },
+								});
+							}}
+						>
+							<DeleteOutlined />
+							<FormattedMessage id="system.role.auth.cancelAll" defaultMessage="批量取消授权" />
+						</Button>,
+						<Button
+							type="primary"
+							key="back"
+							onClick={async () => {
+								history.back();
+							}}
+						>
+							<RollbackOutlined />
+							<FormattedMessage id="pages.goback" defaultMessage="返回" />
+						</Button>,
+					]}
+					request={(params) =>
+						allocatedUserList({ ...params, roleId } as API.System.RoleListParams).then((res) => {
+							const result = {
+								data: res.rows,
+								total: res.total,
+								success: true,
+							};
+							return result;
+						})
+					}
+					columns={columns}
+					rowSelection={{
+						onChange: (_, selectedRows) => {
+							setSelectedRows(selectedRows);
+						},
+					}}
+				/>
+			</div>
+			<UserSelectorModal
+				open={modalVisible}
+				onSubmit={(values: React.Key[]) => {
+					const userIds = values.join(",");
+					if (userIds === "") {
+						message.warning("请选择要分配的用户");
+						return;
+					}
+					authUserSelectAll({ roleId: roleId, userIds: userIds }).then(resp => {
+						if (resp.code === HttpResult.SUCCESS) {
+							message.success('更新成功!');
+							if (actionRef.current) {
+								actionRef.current.reload();
+							}
+						} else {
+							message.warning(resp.msg);
+						}
+					})
+					setModalVisible(false);
+				}}
+				onCancel={() => {
+					setModalVisible(false);
+				}}
+				params={{roleId}}
+				request={(params) =>
+					unallocatedUserList({ ...params } as API.System.RoleListParams).then((res) => {
+						const result = {
+							data: res.rows,
+							total: res.rows.length,
+							success: true,
+						};
+						return result;
+					})
+				}
+			/>
+		</PageContainer>
+	);
+};
+
+export default AuthUserTableList;
diff --git a/react-ui/src/pages/System/Role/components/DataScope.tsx b/react-ui/src/pages/System/Role/components/DataScope.tsx
new file mode 100644
index 0000000..a3dcce0
--- /dev/null
+++ b/react-ui/src/pages/System/Role/components/DataScope.tsx
@@ -0,0 +1,233 @@
+import React, { useEffect, useState } from 'react';
+import { Checkbox, Col, Form, Modal, Row, Tree } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { Key, ProForm, ProFormDigit, ProFormSelect, ProFormText } from '@ant-design/pro-components';
+import { DataNode } from 'antd/es/tree';
+import { CheckboxValueType } from 'antd/es/checkbox/Group';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+export type FormValueType = any & Partial<API.System.Dept>;
+
+export type DataScopeFormProps = {
+    onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+    onSubmit: (values: FormValueType) => Promise<void>;
+    open: boolean;
+    values: Partial<API.System.Role>;
+    deptTree: DataNode[];
+    deptCheckedKeys: string[];
+};
+
+const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
+    const [form] = Form.useForm();
+
+    const { deptTree, deptCheckedKeys } = props;
+    const [dataScopeType, setDataScopeType] = useState<string | undefined>('1');
+    const [deptIds, setDeptIds] = useState<string[] | {checked: string[], halfChecked: string[]}>([]);
+    const [deptTreeExpandKey, setDeptTreeExpandKey] = useState<Key[]>([]);
+    const [checkStrictly, setCheckStrictly] = useState<boolean>(true);
+
+
+    useEffect(() => {
+        setDeptIds(deptCheckedKeys);
+        form.resetFields();
+        form.setFieldsValue({
+            roleId: props.values.roleId,
+            roleName: props.values.roleName,
+            roleKey: props.values.roleKey,
+            dataScope: props.values.dataScope,
+        });
+        setDataScopeType(props.values.dataScope);
+    }, [props.values]);
+
+    const intl = useIntl();
+    const handleOk = () => {
+        form.submit();
+    };
+    const handleCancel = () => {
+        props.onCancel();
+    };
+    const handleFinish = async (values: Record<string, any>) => {
+        props.onSubmit({ ...values, deptIds } as FormValueType);
+    };
+
+    const getAllDeptNode = (node: DataNode[]) => {
+        let keys: any[] = [];
+        node.forEach(value => {
+            keys.push(value.key);
+            if(value.children) {
+                keys = keys.concat(getAllDeptNode(value.children));
+            }
+        });
+        return keys;
+    }
+
+    const deptAllNodes = getAllDeptNode(deptTree);
+
+
+    const onDeptOptionChange = (checkedValues: CheckboxValueType[]) => {
+        if(checkedValues.includes('deptExpand')) {
+            setDeptTreeExpandKey(deptAllNodes);
+        } else {
+            setDeptTreeExpandKey([]);
+        }
+        if(checkedValues.includes('deptNodeAll')) {
+            setDeptIds(deptAllNodes);
+        } else {
+            setDeptIds([]);
+        }
+        
+        if(checkedValues.includes('deptCheckStrictly')) {
+            setCheckStrictly(false);
+        } else {
+            setCheckStrictly(true);
+        }
+    };
+
+    return (
+        <Modal
+            width={640}
+            title={intl.formatMessage({
+                id: 'system.user.auth.role',
+                defaultMessage: '分配角色',
+            })}
+            open={props.open}
+            destroyOnClose
+            forceRender
+            onOk={handleOk}
+            onCancel={handleCancel}
+        >
+            <ProForm
+                form={form}
+                grid={true}
+                layout="horizontal"
+                onFinish={handleFinish}
+                initialValues={{
+                    login_password: '',
+                    confirm_password: '',
+                }}
+            >
+
+                <ProFormDigit
+                    name="roleId"
+                    label={intl.formatMessage({
+                        id: 'system.role.role_id',
+                        defaultMessage: '角色编号',
+                    })}
+                    colProps={{ md: 12, xl: 12 }}
+                    placeholder="请输入角色编号"
+                    disabled
+                    hidden={true}
+                    rules={[
+                        {
+                            required: false,
+                            message: <FormattedMessage id="请输入角色编号!" defaultMessage="请输入角色编号!" />,
+                        },
+                    ]}
+                />
+                <ProFormText
+                    name="roleName"
+                    label={intl.formatMessage({
+                        id: 'system.role.role_name',
+                        defaultMessage: '角色名称',
+                    })}
+                    disabled
+                    placeholder="请输入角色名称"
+                    rules={[
+                        {
+                            required: true,
+                            message: <FormattedMessage id="请输入角色名称!" defaultMessage="请输入角色名称!" />,
+                        },
+                    ]}
+                />
+                <ProFormText
+                    name="roleKey"
+                    label={intl.formatMessage({
+                        id: 'system.role.role_key',
+                        defaultMessage: '权限字符串',
+                    })}
+                    disabled
+                    placeholder="请输入角色权限字符串"
+                    rules={[
+                        {
+                            required: true,
+                            message: <FormattedMessage id="请输入角色权限字符串!" defaultMessage="请输入角色权限字符串!" />,
+                        },
+                    ]}
+                />
+                <ProFormSelect
+                    name="dataScope"
+                    label='权限范围'
+                    initialValue={'1'}
+                    placeholder="请输入用户性别"
+                    valueEnum={{
+                        "1": "全部数据权限",
+                        "2": "自定数据权限",
+                        "3": "本部门数据权限",
+                        "4": "本部门及以下数据权限",
+                        "5": "仅本人数据权限"
+                    }}
+                    rules={[
+                        {
+                            required: true,
+                        },
+                    ]}
+                    fieldProps={{
+                        onChange: (value) => {
+                            setDataScopeType(value);
+                        },
+                    }}
+                />
+                <ProForm.Item
+                    name="deptIds"
+                    label={intl.formatMessage({
+                        id: 'system.role.auth',
+                        defaultMessage: '菜单权限',
+                    })}
+                    required={dataScopeType === '1'}
+                    hidden={dataScopeType !== '1'}
+                >
+                    <Row gutter={[16, 16]}>
+                        <Col md={24}>
+                            <Checkbox.Group
+                                options={[
+                                    { label: '展开/折叠', value: 'deptExpand' },
+                                    { label: '全选/全不选', value: 'deptNodeAll' },
+                                    // { label: '父子联动', value: 'deptCheckStrictly' },
+                                ]}
+                                onChange={onDeptOptionChange} />
+                        </Col>
+                        <Col md={24}>
+                            <Tree
+                                checkable={true}
+                                checkStrictly={checkStrictly}
+                                expandedKeys={deptTreeExpandKey}
+                                treeData={deptTree}
+                                checkedKeys={deptIds}
+                                defaultCheckedKeys={deptCheckedKeys}
+                                onCheck={(checkedKeys: any, checkInfo: any) => {
+                                    console.log(checkedKeys, checkInfo);
+                                    if(checkStrictly) {
+                                        return setDeptIds(checkedKeys.checked);
+                                    } else {
+                                        return setDeptIds({checked: checkedKeys, halfChecked: checkInfo.halfCheckedKeys});
+                                    }
+                                }}
+                                onExpand={(expandedKeys: Key[]) => {
+                                    setDeptTreeExpandKey(deptTreeExpandKey.concat(expandedKeys));
+                                }}
+                            />
+                        </Col>
+                    </Row>
+                </ProForm.Item>
+            </ProForm>
+        </Modal>
+    );
+};
+
+export default DataScopeForm;
diff --git a/react-ui/src/pages/System/Role/components/UserSelectorModal.tsx b/react-ui/src/pages/System/Role/components/UserSelectorModal.tsx
new file mode 100644
index 0000000..fdcfb85
--- /dev/null
+++ b/react-ui/src/pages/System/Role/components/UserSelectorModal.tsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Modal } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { ActionType, ParamsType, ProColumns, ProTable, RequestData } from '@ant-design/pro-components';
+import { getDictValueEnum } from '@/services/system/dict';
+import DictTag from '@/components/DictTag';
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/10
+ * 
+ * */
+
+export type DataScopeFormProps = {
+  onCancel: () => void;
+  onSubmit: (values: React.Key[]) => void;
+  open: boolean;
+  params: ParamsType;
+  request?: (params: Record<string, any>) => Promise<Partial<RequestData<API.System.User>>>;
+};
+
+const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
+  
+  const actionRef = useRef<ActionType>();
+  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, [props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    props.onSubmit(selectedRowKeys);
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+
+  const columns: ProColumns<API.System.User>[] = [
+    {
+      title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+      dataIndex: 'userId',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+      dataIndex: 'nickName',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+      dataIndex: 'phonenumber',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      hideInSearch: true,
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (<DictTag enums={statusOptions} value={record.status} />);
+      },
+    },
+    {
+      title: <FormattedMessage id="system.user.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      hideInSearch: true,
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+    }
+  ];
+
+  return (
+    <Modal
+      width={800}
+      title={intl.formatMessage({
+        id: 'system.role.auth.user',
+        defaultMessage: '选择用户',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProTable<API.System.User>
+        headerTitle={intl.formatMessage({
+          id: 'pages.searchTable.title',
+          defaultMessage: '信息',
+        })}
+        actionRef={actionRef}
+        rowKey="userId"
+        key="userList"
+        search={{
+          labelWidth: 120,
+        }}
+        toolbar={{}}
+        params={props.params}
+        request={props.request}
+        columns={columns}
+        rowSelection={{
+          onChange: (selectedRowKeys: React.Key[]) => {
+            setSelectedRowKeys(selectedRowKeys);
+          },
+        }}
+      />
+    </Modal>
+  );
+};
+
+export default UserSelectorModal;
diff --git a/react-ui/src/pages/System/Role/edit.tsx b/react-ui/src/pages/System/Role/edit.tsx
new file mode 100644
index 0000000..580493a
--- /dev/null
+++ b/react-ui/src/pages/System/Role/edit.tsx
@@ -0,0 +1,199 @@
+import React, { useEffect, useState } from 'react';
+import {
+  ProForm,
+  ProFormDigit,
+  ProFormText,
+  ProFormRadio,
+  ProFormTextArea,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import Tree, { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+export type RoleFormData = Record<string, unknown> & Partial<API.System.Role>;
+
+export type RoleFormProps = {
+  onCancel: (flag?: boolean, formVals?: RoleFormData) => void;
+  onSubmit: (values: RoleFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.Role>;
+  menuTree: DataNode[];
+  menuCheckedKeys: string[];
+  statusOptions: DictValueEnumObj;
+};
+
+const RoleForm: React.FC<RoleFormProps> = (props) => {
+  const [form] = Form.useForm();
+  const { menuTree, menuCheckedKeys } = props;
+  const [menuIds, setMenuIds] = useState<string[]>([]);
+  const { statusOptions } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      roleId: props.values.roleId,
+      roleName: props.values.roleName,
+      roleKey: props.values.roleKey,
+      roleSort: props.values.roleSort,
+      dataScope: props.values.dataScope,
+      menuCheckStrictly: props.values.menuCheckStrictly,
+      deptCheckStrictly: props.values.deptCheckStrictly,
+      status: props.values.status,
+      delFlag: props.values.delFlag,
+      createBy: props.values.createBy,
+      createTime: props.values.createTime,
+      updateBy: props.values.updateBy,
+      updateTime: props.values.updateTime,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit({ ...values, menuIds } as RoleFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.role.title',
+        defaultMessage: '编辑角色信息',
+      })}
+      forceRender
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        form={form}
+        grid={true}
+        layout="horizontal"
+        submitter={false}
+        onFinish={handleFinish}>
+        <ProFormDigit
+          name="roleId"
+          label={intl.formatMessage({
+            id: 'system.role.role_id',
+            defaultMessage: '角色编号',
+          })}
+          placeholder="请输入角色编号"
+          disabled
+          hidden={true}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入角色编号!" defaultMessage="请输入角色编号!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="roleName"
+          label={intl.formatMessage({
+            id: 'system.role.role_name',
+            defaultMessage: '角色名称',
+          })}
+          placeholder="请输入角色名称"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入角色名称!" defaultMessage="请输入角色名称!" />,
+            },
+          ]}
+        />
+        <ProFormText
+          name="roleKey"
+          label={intl.formatMessage({
+            id: 'system.role.role_key',
+            defaultMessage: '权限字符串',
+          })}
+          placeholder="请输入角色权限字符串"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入角色权限字符串!" defaultMessage="请输入角色权限字符串!" />,
+            },
+          ]}
+        />
+        <ProFormDigit
+          name="roleSort"
+          label={intl.formatMessage({
+            id: 'system.role.role_sort',
+            defaultMessage: '显示顺序',
+          })}
+          placeholder="请输入显示顺序"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入显示顺序!" defaultMessage="请输入显示顺序!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: 1
+          }}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.role.status',
+            defaultMessage: '角色状态',
+          })}
+          placeholder="请输入角色状态"
+          rules={[
+            {
+              required: true,
+              message: <FormattedMessage id="请输入角色状态!" defaultMessage="请输入角色状态!" />,
+            },
+          ]}
+          fieldProps = {{
+            defaultValue: "0"
+          }}
+        />
+        <ProForm.Item
+          name="menuIds"
+          label={intl.formatMessage({
+            id: 'system.role.auth',
+            defaultMessage: '菜单权限',
+          })}
+        >
+          <Tree
+            checkable={true}
+            multiple={true}
+            checkStrictly={true}
+            defaultExpandAll={false}
+            treeData={menuTree}
+            defaultCheckedKeys={menuCheckedKeys}
+            onCheck={(checkedKeys: any) => {             
+              return setMenuIds(checkedKeys.checked);
+            }}
+          />
+        </ProForm.Item>
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.role.remark',
+            defaultMessage: '备注',
+          })}
+          placeholder="请输入备注"
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default RoleForm;
diff --git a/react-ui/src/pages/System/Role/index.tsx b/react-ui/src/pages/System/Role/index.tsx
new file mode 100644
index 0000000..ea15b2e
--- /dev/null
+++ b/react-ui/src/pages/System/Role/index.tsx
@@ -0,0 +1,513 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess, history } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { Button, message, Modal, Dropdown, FormInstance, Space, Switch } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined } from '@ant-design/icons';
+import { getRoleList, removeRole, addRole, updateRole, exportRole, getRoleMenuList, changeRoleStatus, updateRoleDataScope, getDeptTreeSelect, getRole } from '@/services/system/role';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { formatTreeData } from '@/utils/tree';
+import { getMenuTree } from '@/services/system/menu';
+import DataScopeForm from './components/DataScope';
+
+const { confirm } = Modal;
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.Role) => {
+  const hide = message.loading('正在添加');
+  try {
+    const resp = await addRole({ ...fields });
+    hide();
+    if (resp.code === 200) {
+      message.success('添加成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.Role) => {
+  const hide = message.loading('正在更新');
+  try {
+    const resp = await updateRole(fields);
+    hide();
+    if (resp.code === 200) {
+      message.success('更新成功');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.Role[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    const resp = await removeRole(selectedRows.map((row) => row.roleId).join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.Role) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.roleId];
+    const resp = await removeRole(params.join(','));
+    hide();
+    if (resp.code === 200) {
+      message.success('删除成功,即将刷新');
+    } else {
+      message.error(resp.msg);
+    }
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportRole();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+
+const RoleTableList: React.FC = () => {
+
+  const [messageApi, contextHolder] = message.useMessage();
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+  const [dataScopeModalOpen, setDataScopeModalOpen] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.Role>();
+  const [selectedRows, setSelectedRows] = useState<API.System.Role[]>([]);
+
+  const [menuTree, setMenuTree] = useState<DataNode[]>();
+  const [menuIds, setMenuIds] = useState<string[]>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const showChangeStatusConfirm = (record: API.System.Role) => {
+    let text = record.status === "1" ? "启用" : "停用";
+    const newStatus = record.status === '0' ? '1' : '0';
+    confirm({
+      title: `确认要${text}${record.roleName}角色吗?`,
+      onOk() {
+        changeRoleStatus(record.roleId, newStatus).then(resp => {
+          if (resp.code === 200) {
+            messageApi.open({
+              type: 'success',
+              content: '更新成功!',
+            });
+            actionRef.current?.reload();
+          } else {
+            messageApi.open({
+              type: 'error',
+              content: '更新失败!',
+            });
+          }
+        });
+      },
+    });
+  };
+
+  const columns: ProColumns<API.System.Role>[] = [
+    {
+      title: <FormattedMessage id="system.role.role_id" defaultMessage="角色编号" />,
+      dataIndex: 'roleId',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.role.role_name" defaultMessage="角色名称" />,
+      dataIndex: 'roleName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.role.role_key" defaultMessage="角色权限字符串" />,
+      dataIndex: 'roleKey',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.role.role_sort" defaultMessage="显示顺序" />,
+      dataIndex: 'roleSort',
+      valueType: 'text',
+      hideInSearch: true,
+    },
+    {
+      title: <FormattedMessage id="system.role.status" defaultMessage="角色状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (
+          <Switch
+            checked={record.status === '0'}
+            checkedChildren="正常"
+            unCheckedChildren="停用"
+            defaultChecked
+            onClick={() => showChangeStatusConfirm(record)}
+          />)
+      },
+    },
+    {
+      title: <FormattedMessage id="system.role.create_time" defaultMessage="创建时间" />,
+      dataIndex: 'createTime',
+      valueType: 'dateRange',
+      render: (_, record) => {
+        return (<span>{record.createTime.toString()} </span>);
+      },
+      search: {
+        transform: (value) => {
+          return {
+            'params[beginTime]': value[0],
+            'params[endTime]': value[1],
+          };
+        },
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          icon=<EditOutlined />
+          hidden={!access.hasPerms('system:role:edit')}
+          onClick={() => {
+            getRoleMenuList(record.roleId).then((res) => {
+              if (res.code === 200) {
+                const treeData = formatTreeData(res.menus);
+                setMenuTree(treeData);
+                setMenuIds(res.checkedKeys.map(item => {
+                  return `${item}`
+                }));
+                setModalVisible(true);
+                setCurrentRow(record);
+              } else {
+                message.warning(res.msg);
+              }
+            });
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          key="batchRemove"
+          icon=<DeleteOutlined />
+          hidden={!access.hasPerms('system:role:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+        <Dropdown
+          key="more"
+          menu={{
+            items: [
+              {
+                label: '数据权限',
+                key: 'datascope',
+                disabled: !access.hasPerms('system:role:edit'),
+              },
+              {
+                label: '分配用户',
+                key: 'authUser',
+                disabled: !access.hasPerms('system:role:edit'),
+              },
+            ],
+            onClick: ({ key }: any) => {
+              if (key === 'datascope') {
+                getRole(record.roleId).then(resp => {
+                  if(resp.code === 200) {
+                    setCurrentRow(resp.data);
+                    setDataScopeModalOpen(true);
+                  }
+                })
+                getDeptTreeSelect(record.roleId).then(resp => {
+                  if (resp.code === 200) {
+                    setMenuTree(formatTreeData(resp.depts));
+                    setMenuIds(resp.checkedKeys.map((item:number) => {
+                      return `${item}`
+                    }));
+                  }
+                })
+              }
+              else if (key === 'authUser') {
+                history.push(`/system/role-auth/user/${record.roleId}`);
+              }
+            }
+          }}
+        >
+          <a onClick={(e) => e.preventDefault()}>
+            <Space>
+              <DownOutlined />
+              更多
+            </Space>
+          </a>
+        </Dropdown>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      {contextHolder}
+      <div style={{ width: '100%', float: 'right' }}>
+        <ProTable<API.System.Role>
+          headerTitle={intl.formatMessage({
+            id: 'pages.searchTable.title',
+            defaultMessage: '信息',
+          })}
+          actionRef={actionRef}
+          formRef={formTableRef}
+          rowKey="roleId"
+          key="roleList"
+          search={{
+            labelWidth: 120,
+          }}
+          toolBarRender={() => [
+            <Button
+              type="primary"
+              key="add"
+              hidden={!access.hasPerms('system:role:add')}
+              onClick={async () => {
+                getMenuTree().then((res: any) => {
+                  if (res.code === 200) {
+                    const treeData = formatTreeData(res.data);
+                    setMenuTree(treeData);
+                    setMenuIds([]);
+                    setModalVisible(true);
+                    setCurrentRow(undefined);
+                  } else {
+                    message.warning(res.msg);
+                  }
+                });
+              }}
+            >
+              <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+            </Button>,
+            <Button
+              type="primary"
+              key="remove"
+              danger
+              hidden={selectedRows?.length === 0 || !access.hasPerms('system:role:remove')}
+              onClick={async () => {
+                Modal.confirm({
+                  title: '是否确认删除所选数据项?',
+                  icon: <ExclamationCircleOutlined />,
+                  content: '请谨慎操作',
+                  async onOk() {
+                    const success = await handleRemove(selectedRows);
+                    if (success) {
+                      setSelectedRows([]);
+                      actionRef.current?.reloadAndRest?.();
+                    }
+                  },
+                  onCancel() { },
+                });
+              }}
+            >
+              <DeleteOutlined />
+              <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+            </Button>,
+            <Button
+              type="primary"
+              key="export"
+              hidden={!access.hasPerms('system:role:export')}
+              onClick={async () => {
+                handleExport();
+              }}
+            >
+              <PlusOutlined />
+              <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+            </Button>,
+          ]}
+          request={(params) =>
+            getRoleList({ ...params } as API.System.RoleListParams).then((res) => {
+              const result = {
+                data: res.rows,
+                total: res.total,
+                success: true,
+              };
+              return result;
+            })
+          }
+          columns={columns}
+          rowSelection={{
+            onChange: (_, selectedRows) => {
+              setSelectedRows(selectedRows);
+            },
+          }}
+        />
+      </div>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:role:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.roleId) {
+            success = await handleUpdate({ ...values } as API.System.Role);
+          } else {
+            success = await handleAdd({ ...values } as API.System.Role);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        menuTree={menuTree || []}
+        menuCheckedKeys={menuIds || []}
+        statusOptions={statusOptions}
+      />
+      <DataScopeForm
+        onSubmit={async (values: any) => {
+          const success = await updateRoleDataScope(values);
+          if (success) {
+            setDataScopeModalOpen(false);
+            setSelectedRows([]);
+            setCurrentRow(undefined);
+            message.success('配置成功。');
+          }
+        }}
+        onCancel={() => {
+          setDataScopeModalOpen(false);
+          setSelectedRows([]);
+          setCurrentRow(undefined);
+        }}
+        open={dataScopeModalOpen}
+        values={currentRow || {}}
+        deptTree={menuTree || []}
+        deptCheckedKeys={menuIds || []}
+      />
+    </PageContainer>
+  );
+};
+
+export default RoleTableList;
diff --git a/react-ui/src/pages/System/User/components/AuthRole.tsx b/react-ui/src/pages/System/User/components/AuthRole.tsx
new file mode 100644
index 0000000..120d966
--- /dev/null
+++ b/react-ui/src/pages/System/User/components/AuthRole.tsx
@@ -0,0 +1,81 @@
+import React, { useEffect } from 'react';
+import { Form, Modal } from 'antd';
+import { useIntl } from '@umijs/max';
+import { ProForm, ProFormSelect } from '@ant-design/pro-components';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+export type FormValueType = any & Partial<API.System.Dept>;
+
+export type AuthRoleFormProps = {
+    onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+    onSubmit: (values: FormValueType) => Promise<void>;
+    open: boolean;
+    roleIds: number[];
+    roles: string[];
+};
+
+const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => {
+    const [form] = Form.useForm();
+
+    useEffect(() => {
+        form.resetFields();
+        form.setFieldValue( 'roleIds', props.roleIds);
+    });
+
+    const intl = useIntl();
+    const handleOk = () => {
+        form.submit();
+    };
+    const handleCancel = () => {
+        props.onCancel();
+    };
+    const handleFinish = async (values: Record<string, any>) => {
+        props.onSubmit(values as FormValueType);
+    };
+
+    return (
+        <Modal
+            width={640}
+            title={intl.formatMessage({
+                id: 'system.user.auth.role',
+                defaultMessage: '分配角色',
+            })}
+            open={props.open}
+            destroyOnClose
+            forceRender
+            onOk={handleOk}
+            onCancel={handleCancel}
+        >
+            <ProForm
+                form={form}
+                grid={true}
+                layout="horizontal"
+                onFinish={handleFinish}
+                initialValues={{
+                    login_password: '',
+                    confirm_password: '',
+                }}
+            >
+                <ProFormSelect
+                    name="roleIds"
+                    mode="multiple"
+                    label={intl.formatMessage({
+                        id: 'system.user.role',
+                        defaultMessage: '角色',
+                    })}
+                    options={props.roles}
+                    placeholder="请选择角色"
+                    rules={[{ required: true, message: '请选择角色!' }]}
+                />
+            </ProForm>
+        </Modal>
+    );
+};
+
+export default AuthRoleForm;
diff --git a/react-ui/src/pages/System/User/components/DeptTree.tsx b/react-ui/src/pages/System/User/components/DeptTree.tsx
new file mode 100644
index 0000000..d1a085c
--- /dev/null
+++ b/react-ui/src/pages/System/User/components/DeptTree.tsx
@@ -0,0 +1,69 @@
+import React, { useState, useEffect } from 'react';
+import { Tree, message } from 'antd';
+import { getDeptTree } from '@/services/system/user';
+
+const { DirectoryTree } = Tree;
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+
+export type TreeProps = {
+  onSelect: (values: any) => Promise<void>;
+};
+
+const DeptTree: React.FC<TreeProps> = (props) => {
+  const [treeData, setTreeData] = useState<any>([]);
+  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
+  const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
+
+  const fetchDeptList = async () => {
+    const hide = message.loading('正在查询');
+    try {
+      await getDeptTree({}).then((res: any) => {
+        const exKeys = [];
+        exKeys.push('1');
+        setTreeData(res);
+        exKeys.push(res[0].children[0].id);
+        setExpandedKeys(exKeys);
+        props.onSelect(res[0].children[0]);
+      });
+      hide();
+      return true;
+    } catch (error) {
+      hide();
+      return false;
+    }
+  };
+
+  useEffect(() => {
+    fetchDeptList();
+  }, []);
+
+  const onSelect = (keys: React.Key[], info: any) => {
+    props.onSelect(info.node);
+  };
+
+  const onExpand = (expandedKeysValue: React.Key[]) => {
+    setExpandedKeys(expandedKeysValue);
+    setAutoExpandParent(false);
+  };
+
+  return (
+    <DirectoryTree
+      // multiple
+      defaultExpandAll
+      onExpand={onExpand}
+      expandedKeys={expandedKeys}
+      autoExpandParent={autoExpandParent}
+      onSelect={onSelect}
+      treeData={treeData}
+    />
+  );
+};
+
+export default DeptTree;
diff --git a/react-ui/src/pages/System/User/components/ResetPwd.tsx b/react-ui/src/pages/System/User/components/ResetPwd.tsx
new file mode 100644
index 0000000..5523cd6
--- /dev/null
+++ b/react-ui/src/pages/System/User/components/ResetPwd.tsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import { Form, Modal } from 'antd';
+import { useIntl } from '@umijs/max';
+import { ProForm, ProFormText } from '@ant-design/pro-components';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+export type FormValueType = any & Partial<API.System.User>;
+
+export type UpdateFormProps = {
+  onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+  onSubmit: (values: FormValueType) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.User>;
+};
+
+const UpdateForm: React.FC<UpdateFormProps> = (props) => {
+  const [form] = Form.useForm();
+  const loginPassword = Form.useWatch('password', form);
+  const userId = props.values.userId;
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit({ ...values, userId } as FormValueType);
+  };
+
+  const checkPassword = (rule: any, value: string) => {
+    if (value === loginPassword) {
+      // 校验条件自定义
+      return Promise.resolve();
+    }
+    return Promise.reject(new Error('两次密码输入不一致'));
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.user.reset.password',
+        defaultMessage: '密码重置',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        grid={true}
+        form={form}
+        layout="horizontal"
+        onFinish={handleFinish}
+        initialValues={{
+          password: '',
+          confirm_password: '',
+        }}
+      >
+        <p>请输入用户{props.values.userName}的新密码!</p>
+        <ProFormText.Password
+          name="password"
+          label="登录密码"
+          rules={[
+            {
+              required: true,
+              message: '登录密码不可为空。',
+            },
+          ]}
+        />
+        <ProFormText.Password
+          name="confirm_password"
+          label="确认密码"
+          rules={[
+            {
+              required: true,
+              message: "确认密码",
+            },
+            { validator: checkPassword },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default UpdateForm;
diff --git a/react-ui/src/pages/System/User/edit.tsx b/react-ui/src/pages/System/User/edit.tsx
new file mode 100644
index 0000000..31d442b
--- /dev/null
+++ b/react-ui/src/pages/System/User/edit.tsx
@@ -0,0 +1,279 @@
+import React, { useEffect } from 'react';
+import {
+  ProForm,
+  ProFormText,
+  ProFormSelect,
+  ProFormRadio,
+  ProFormTextArea,
+  ProFormTreeSelect,
+} from '@ant-design/pro-components';
+import { Form, Modal } from 'antd';
+import { useIntl, FormattedMessage } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { DictValueEnumObj } from '@/components/DictTag';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ * 
+ * */
+
+
+export type UserFormData = Record<string, unknown> & Partial<API.System.User>;
+
+export type UserFormProps = {
+  onCancel: (flag?: boolean, formVals?: UserFormData) => void;
+  onSubmit: (values: UserFormData) => Promise<void>;
+  open: boolean;
+  values: Partial<API.System.User>;
+  sexOptions: DictValueEnumObj;
+  statusOptions: DictValueEnumObj;
+  postIds: number[];
+  posts: string[];
+  roleIds: number[];
+  roles: string[];
+  depts: DataNode[];
+};
+
+const UserForm: React.FC<UserFormProps> = (props) => {
+  const [form] = Form.useForm();
+  const userId = Form.useWatch('userId', form);
+  const { sexOptions, statusOptions, } = props;
+  const { roles, posts, depts } = props;
+
+  useEffect(() => {
+    form.resetFields();
+    form.setFieldsValue({
+      userId: props.values.userId,
+      deptId: props.values.deptId,
+      postIds: props.postIds,
+      roleIds: props.roleIds,
+      userName: props.values.userName,
+      nickName: props.values.nickName,
+      email: props.values.email,
+      phonenumber: props.values.phonenumber,
+      sex: props.values.sex,
+      avatar: props.values.avatar,
+      status: props.values.status,
+      delFlag: props.values.delFlag,
+      loginIp: props.values.loginIp,
+      loginDate: props.values.loginDate,
+      remark: props.values.remark,
+    });
+  }, [form, props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    form.submit();
+  };
+  const handleCancel = () => {
+    props.onCancel();
+  };
+  const handleFinish = async (values: Record<string, any>) => {
+    props.onSubmit(values as UserFormData);
+  };
+
+  return (
+    <Modal
+      width={640}
+      title={intl.formatMessage({
+        id: 'system.user.title',
+        defaultMessage: '编辑用户信息',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <ProForm
+        grid={true}
+        form={form}
+        layout="horizontal"
+        submitter={false}
+        onFinish={handleFinish}>
+        <ProFormText
+          name="nickName"
+          label={intl.formatMessage({
+            id: 'system.user.nick_name',
+            defaultMessage: '用户昵称',
+          })}
+          placeholder="请输入用户昵称"
+          colProps={{ xs: 24, md: 12, xl: 12 }}
+          rules={[
+            {
+              required: true,
+              message: (
+                <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormTreeSelect
+          name="deptId"
+          label={intl.formatMessage({
+            id: 'system.user.dept_name',
+            defaultMessage: '部门',
+          })}
+          request={async () => {
+            return depts;
+          }}
+          placeholder="请输入用户部门"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: true,
+              message: (
+                <FormattedMessage id="请输入用户部门!" defaultMessage="请输入用户部门!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormText
+          name="phonenumber"
+          label={intl.formatMessage({
+            id: 'system.user.phonenumber',
+            defaultMessage: '手机号码',
+          })}
+          placeholder="请输入手机号码"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: (
+                <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormText
+          name="email"
+          label={intl.formatMessage({
+            id: 'system.user.email',
+            defaultMessage: '用户邮箱',
+          })}
+          placeholder="请输入用户邮箱"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: (
+                <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormText
+          name="userName"
+          label={intl.formatMessage({
+            id: 'system.user.user_name',
+            defaultMessage: '用户账号',
+          })}
+          hidden={userId}
+          placeholder="请输入用户账号"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: true,
+            },
+          ]}
+        />
+        <ProFormText.Password
+          name="password"
+          label={intl.formatMessage({
+            id: 'system.user.password',
+            defaultMessage: '密码',
+          })}
+          hidden={userId}
+          placeholder="请输入密码"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入密码!" defaultMessage="请输入密码!" />,
+            },
+          ]}
+        />
+        <ProFormSelect
+          valueEnum={sexOptions}
+          name="sex"
+          label={intl.formatMessage({
+            id: 'system.user.sex',
+            defaultMessage: '用户性别',
+          })}
+          initialValue={'0'}
+          placeholder="请输入用户性别"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: (
+                <FormattedMessage id="请输入用户性别!" defaultMessage="请输入用户性别!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormRadio.Group
+          valueEnum={statusOptions}
+          name="status"
+          label={intl.formatMessage({
+            id: 'system.user.status',
+            defaultMessage: '帐号状态',
+          })}
+          initialValue={'0'}
+          placeholder="请输入帐号状态"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[
+            {
+              required: false,
+              message: (
+                <FormattedMessage id="请输入帐号状态!" defaultMessage="请输入帐号状态!" />
+              ),
+            },
+          ]}
+        />
+        <ProFormSelect
+          name="postIds"
+          mode="multiple"
+          label={intl.formatMessage({
+            id: 'system.user.post',
+            defaultMessage: '岗位',
+          })}
+          options={posts}
+          placeholder="请选择岗位"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[{ required: true, message: '请选择岗位!' }]}
+        />
+        <ProFormSelect
+          name="roleIds"
+          mode="multiple"
+          label={intl.formatMessage({
+            id: 'system.user.role',
+            defaultMessage: '角色',
+          })}
+          options={roles}
+          placeholder="请选择角色"
+          colProps={{ md: 12, xl: 12 }}
+          rules={[{ required: true, message: '请选择角色!' }]}
+        />
+        <ProFormTextArea
+          name="remark"
+          label={intl.formatMessage({
+            id: 'system.user.remark',
+            defaultMessage: '备注',
+          })}
+          placeholder="请输入备注"
+          colProps={{ md: 24, xl: 24 }}
+          rules={[
+            {
+              required: false,
+              message: <FormattedMessage id="请输入备注!" defaultMessage="请输入备注!" />,
+            },
+          ]}
+        />
+      </ProForm>
+    </Modal>
+  );
+};
+
+export default UserForm;
diff --git a/react-ui/src/pages/System/User/index.tsx b/react-ui/src/pages/System/User/index.tsx
new file mode 100644
index 0000000..6435787
--- /dev/null
+++ b/react-ui/src/pages/System/User/index.tsx
@@ -0,0 +1,561 @@
+
+import React, { useState, useRef, useEffect } from 'react';
+import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
+import { Card, Col, Dropdown, FormInstance, Row, Space, Switch } from 'antd';
+import { Button, message, Modal } from 'antd';
+import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
+import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined } from '@ant-design/icons';
+import { getUserList, removeUser, addUser, updateUser, exportUser, getUser, changeUserStatus, updateAuthRole, resetUserPwd } from '@/services/system/user';
+import UpdateForm from './edit';
+import { getDictValueEnum } from '@/services/system/dict';
+import { DataNode } from 'antd/es/tree';
+import { getDeptTree } from '@/services/system/user';
+import DeptTree from './components/DeptTree';
+import ResetPwd from './components/ResetPwd';
+import { getPostList } from '@/services/system/post';
+import { getRoleList } from '@/services/system/role';
+import AuthRoleForm from './components/AuthRole';
+
+const { confirm } = Modal;
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/06
+ *
+ * */
+
+/**
+ * 添加节点
+ *
+ * @param fields
+ */
+const handleAdd = async (fields: API.System.User) => {
+  const hide = message.loading('正在添加');
+  try {
+    await addUser({ ...fields });
+    hide();
+    message.success('添加成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ *
+ * @param fields
+ */
+const handleUpdate = async (fields: API.System.User) => {
+  const hide = message.loading('正在配置');
+  try {
+    await updateUser(fields);
+    hide();
+    message.success('配置成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 删除节点
+ *
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: API.System.User[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    await removeUser(selectedRows.map((row) => row.userId).join(','));
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const handleRemoveOne = async (selectedRow: API.System.User) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRow) return true;
+  try {
+    const params = [selectedRow.userId];
+    await removeUser(params.join(','));
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+/**
+ * 导出数据
+ *
+ *
+ */
+const handleExport = async () => {
+  const hide = message.loading('正在导出');
+  try {
+    await exportUser();
+    hide();
+    message.success('导出成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('导出失败,请重试');
+    return false;
+  }
+};
+
+const UserTableList: React.FC = () => {
+  const [messageApi, contextHolder] = message.useMessage();
+
+  const formTableRef = useRef<FormInstance>();
+
+  const [modalVisible, setModalVisible] = useState<boolean>(false);
+  const [resetPwdModalVisible, setResetPwdModalVisible] = useState<boolean>(false);
+  const [authRoleModalVisible, setAuthRoleModalVisible] = useState<boolean>(false);
+
+  const actionRef = useRef<ActionType>();
+  const [currentRow, setCurrentRow] = useState<API.System.User>();
+  const [selectedRows, setSelectedRows] = useState<API.System.User[]>([]);
+
+  const [selectDept, setSelectDept] = useState<any>({ id: 0 });
+  const [sexOptions, setSexOptions] = useState<any>([]);
+  const [statusOptions, setStatusOptions] = useState<any>([]);
+
+  const [postIds, setPostIds] = useState<number[]>();
+  const [postList, setPostList] = useState<any[]>();
+  const [roleIds, setRoleIds] = useState<number[]>();
+  const [roleList, setRoleList] = useState<any[]>();
+  const [deptTree, setDeptTree] = useState<DataNode[]>();
+
+  const access = useAccess();
+
+  /** 国际化配置 */
+  const intl = useIntl();
+
+  useEffect(() => {
+    getDictValueEnum('sys_user_sex').then((data) => {
+      setSexOptions(data);
+    });
+    getDictValueEnum('sys_normal_disable').then((data) => {
+      setStatusOptions(data);
+    });
+  }, []);
+
+  const showChangeStatusConfirm = (record: API.System.User) => {
+    let text = record.status === "1" ? "启用" : "停用";
+    const newStatus = record.status === '0' ? '1' : '0';
+    confirm({
+      title: `确认要${text}${record.userName}用户吗?`,
+      onOk() {
+        changeUserStatus(record.userId, newStatus).then(resp => {
+          if (resp.code === 200) {
+            messageApi.open({
+              type: 'success',
+              content: '更新成功!',
+            });
+            actionRef.current?.reload();
+          } else {
+            messageApi.open({
+              type: 'error',
+              content: '更新失败!',
+            });
+          }
+        });
+      },
+    });
+  };
+
+  const fetchUserInfo = async (userId: number) => {
+    const res = await getUser(userId);
+    setPostIds(res.postIds);
+    setPostList(
+      res.posts.map((item: any) => {
+        return {
+          value: item.postId,
+          label: item.postName,
+        };
+      }),
+    );
+    setRoleIds(res.roleIds);
+    setRoleList(
+      res.roles.map((item: any) => {
+        return {
+          value: item.roleId,
+          label: item.roleName,
+        };
+      }),
+    );
+  };
+
+  const columns: ProColumns<API.System.User>[] = [
+    {
+      title: <FormattedMessage id="system.user.user_id" defaultMessage="用户编号" />,
+      dataIndex: 'deptId',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.user_name" defaultMessage="用户账号" />,
+      dataIndex: 'userName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.nick_name" defaultMessage="用户昵称" />,
+      dataIndex: 'nickName',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.dept_name" defaultMessage="部门" />,
+      dataIndex: ['dept', 'deptName'],
+      valueType: 'text',
+      hideInSearch: true
+    },
+    {
+      title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
+      dataIndex: 'phonenumber',
+      valueType: 'text',
+    },
+    {
+      title: <FormattedMessage id="system.user.status" defaultMessage="帐号状态" />,
+      dataIndex: 'status',
+      valueType: 'select',
+      valueEnum: statusOptions,
+      render: (_, record) => {
+        return (
+          <Switch
+            checked={record.status === '0'}
+            checkedChildren="正常"
+            unCheckedChildren="停用"
+            defaultChecked
+            onClick={() => showChangeStatusConfirm(record)}
+          />)
+      },
+    },
+    {
+      title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
+      dataIndex: 'option',
+      width: '220px',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button
+          type="link"
+          size="small"
+          key="edit"
+          icon=<EditOutlined />
+          hidden={!access.hasPerms('system:user:edit')}
+          onClick={async () => {
+            fetchUserInfo(record.userId);
+            const treeData = await getDeptTree({});
+            setDeptTree(treeData);
+            setModalVisible(true);
+            setCurrentRow(record);
+          }}
+        >
+          编辑
+        </Button>,
+        <Button
+          type="link"
+          size="small"
+          danger
+          icon=<DeleteOutlined />
+          key="batchRemove"
+          hidden={!access.hasPerms('system:user:remove')}
+          onClick={async () => {
+            Modal.confirm({
+              title: '删除',
+              content: '确定删除该项吗?',
+              okText: '确认',
+              cancelText: '取消',
+              onOk: async () => {
+                const success = await handleRemoveOne(record);
+                if (success) {
+                  if (actionRef.current) {
+                    actionRef.current.reload();
+                  }
+                }
+              },
+            });
+          }}
+        >
+          删除
+        </Button>,
+        <Dropdown
+          key="more"
+          menu={{
+            items: [
+              {
+                label: <FormattedMessage id="system.user.reset.password" defaultMessage="密码重置" />,
+                key: 'reset',
+                disabled: !access.hasPerms('system:user:edit'),
+              },
+              {
+                label: '分配角色',
+                key: 'authRole',
+                disabled: !access.hasPerms('system:user:edit'),
+              },
+            ],
+            onClick: ({ key }) => {
+              if (key === 'reset') {
+                setResetPwdModalVisible(true);
+                setCurrentRow(record);
+              }
+              else if (key === 'authRole') {
+                fetchUserInfo(record.userId);
+                setAuthRoleModalVisible(true);
+                setCurrentRow(record);
+              }
+            }
+          }}
+        >
+          <a onClick={(e) => e.preventDefault()}>
+            <Space>
+              <DownOutlined />
+              更多
+            </Space>
+          </a>
+        </Dropdown>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      {contextHolder}
+      <Row gutter={[16, 24]}>
+        <Col lg={6} md={24}>
+          <Card>
+            <DeptTree
+              onSelect={async (value: any) => {
+                setSelectDept(value);
+                if (actionRef.current) {
+                  formTableRef?.current?.submit();
+                }
+              }}
+            />
+          </Card>
+        </Col>
+        <Col lg={18} md={24}>
+          <ProTable<API.System.User>
+            headerTitle={intl.formatMessage({
+              id: 'pages.searchTable.title',
+              defaultMessage: '信息',
+            })}
+            actionRef={actionRef}
+            formRef={formTableRef}
+            rowKey="userId"
+            key="userList"
+            search={{
+              labelWidth: 120,
+            }}
+            toolBarRender={() => [
+              <Button
+                type="primary"
+                key="add"
+                hidden={!access.hasPerms('system:user:add')}
+                onClick={async () => {
+                  const treeData = await getDeptTree({});
+                  setDeptTree(treeData);
+
+                  const postResp = await getPostList()
+                  if (postResp.code === 200) {
+                    setPostList(
+                      postResp.rows.map((item: any) => {
+                        return {
+                          value: item.postId,
+                          label: item.postName,
+                        };
+                      }),
+                    );
+                  }
+
+                  const roleResp = await getRoleList()
+                  if (roleResp.code === 200) {
+                    setRoleList(
+                      roleResp.rows.map((item: any) => {
+                        return {
+                          value: item.roleId,
+                          label: item.roleName,
+                        };
+                      }),
+                    );
+                  }
+                  setCurrentRow(undefined);
+                  setModalVisible(true);
+                }}
+              >
+                <PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
+              </Button>,
+              <Button
+                type="primary"
+                key="remove"
+                danger
+                hidden={selectedRows?.length === 0 || !access.hasPerms('system:user:remove')}
+                onClick={async () => {
+                  Modal.confirm({
+                    title: '是否确认删除所选数据项?',
+                    icon: <ExclamationCircleOutlined />,
+                    content: '请谨慎操作',
+                    async onOk() {
+                      const success = await handleRemove(selectedRows);
+                      if (success) {
+                        setSelectedRows([]);
+                        actionRef.current?.reloadAndRest?.();
+                      }
+                    },
+                    onCancel() { },
+                  });
+                }}
+              >
+                <DeleteOutlined />
+                <FormattedMessage id="pages.searchTable.delete" defaultMessage="删除" />
+              </Button>,
+              <Button
+                type="primary"
+                key="export"
+                hidden={!access.hasPerms('system:user:export')}
+                onClick={async () => {
+                  handleExport();
+                }}
+              >
+                <PlusOutlined />
+                <FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
+              </Button>,
+            ]}
+            request={(params) =>
+              getUserList({ ...params, deptId: selectDept.id } as API.System.UserListParams).then((res) => {
+                const result = {
+                  data: res.rows,
+                  total: res.total,
+                  success: true,
+                };
+                return result;
+              })
+            }
+            columns={columns}
+            rowSelection={{
+              onChange: (_, selectedRows) => {
+                setSelectedRows(selectedRows);
+              },
+            }}
+          />
+        </Col>
+      </Row>
+      {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="remove"
+            danger
+            hidden={!access.hasPerms('system:user:del')}
+            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>
+      )}
+      <UpdateForm
+        onSubmit={async (values) => {
+          let success = false;
+          if (values.userId) {
+            success = await handleUpdate({ ...values } as API.System.User);
+          } else {
+            success = await handleAdd({ ...values } as API.System.User);
+          }
+          if (success) {
+            setModalVisible(false);
+            setCurrentRow(undefined);
+            if (actionRef.current) {
+              actionRef.current.reload();
+            }
+          }
+        }}
+        onCancel={() => {
+          setModalVisible(false);
+          setCurrentRow(undefined);
+        }}
+        open={modalVisible}
+        values={currentRow || {}}
+        sexOptions={sexOptions}
+        statusOptions={statusOptions}
+        posts={postList || []}
+        postIds={postIds || []}
+        roles={roleList || []}
+        roleIds={roleIds || []}
+        depts={deptTree || []}
+      />
+      <ResetPwd
+        onSubmit={async (values: any) => {
+          const success = await resetUserPwd(values.userId, values.password);
+          if (success) {
+            setResetPwdModalVisible(false);
+            setSelectedRows([]);
+            setCurrentRow(undefined);
+            message.success('密码重置成功。');
+          }
+        }}
+        onCancel={() => {
+          setResetPwdModalVisible(false);
+          setSelectedRows([]);
+          setCurrentRow(undefined);
+        }}
+        open={resetPwdModalVisible}
+        values={currentRow || {}}
+      />
+      <AuthRoleForm
+        onSubmit={async (values: any) => {
+          const success = await updateAuthRole(values);
+          if (success) {
+            setAuthRoleModalVisible(false);
+            setSelectedRows([]);
+            setCurrentRow(undefined);
+            message.success('配置成功。');
+          }
+        }}
+        onCancel={() => {
+          setAuthRoleModalVisible(false);
+          setSelectedRows([]);
+          setCurrentRow(undefined);
+        }}
+        open={authRoleModalVisible}
+        roles={roleList || []}
+        roleIds={roleIds || []}
+      />
+    </PageContainer>
+  );
+};
+
+export default UserTableList;
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;
diff --git a/react-ui/src/pages/User/Center/Center.less b/react-ui/src/pages/User/Center/Center.less
new file mode 100644
index 0000000..430d88e
--- /dev/null
+++ b/react-ui/src/pages/User/Center/Center.less
@@ -0,0 +1,61 @@
+
+.avatarHolder {
+  margin-bottom: 16px;
+  text-align: center;
+  position: relative;
+  display: inline-block;
+  height: 120px;
+  
+  & > img {
+    width: 120px;
+    height: 120px;
+    margin-bottom: 20px;
+    border-radius: 50%;
+  }
+  &:hover:after {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    color: #eee;
+    font-size: 24px;
+    font-style: normal;
+    line-height: 110px;
+    background: rgba(0, 0, 0, 0.5);
+    border-radius: 50%;
+    cursor: pointer;
+    content: '+';
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+  }
+}
+
+.teamTitle {
+  margin-bottom: 12px;
+  color: @heading-color;
+  font-weight: 500;
+}
+
+.team {
+  :global {
+    .ant-avatar {
+      margin-right: 12px;
+    }
+  }
+
+  a {
+    display: block;
+    margin-bottom: 24px;
+    overflow: hidden;
+    color: @text-color;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    transition: color 0.3s;
+
+    &:hover {
+      color: @primary-color;
+    }
+  }
+}
diff --git a/react-ui/src/pages/User/Center/components/AvatarCropper/cropper.css b/react-ui/src/pages/User/Center/components/AvatarCropper/cropper.css
new file mode 100644
index 0000000..7f2f350
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/AvatarCropper/cropper.css
@@ -0,0 +1,309 @@
+/*!
+ * Cropper.js v1.5.13
+ * https://fengyuanchen.github.io/cropperjs
+ *
+ * Copyright 2015-present Chen Fengyuan
+ * Released under the MIT license
+ *
+ * Date: 2022-11-20T05:30:43.444Z
+ */
+
+.cropper-container {
+  direction: ltr;
+  font-size: 0;
+  line-height: 0;
+  position: relative;
+  -ms-touch-action: none;
+      touch-action: none;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+}
+
+.cropper-container img {
+    -webkit-backface-visibility: hidden;
+            backface-visibility: hidden;
+    display: block;
+    height: 100%;
+    image-orientation: 0deg;
+    max-height: none !important;
+    max-width: none !important;
+    min-height: 0 !important;
+    min-width: 0 !important;
+    width: 100%;
+  }
+
+.cropper-wrap-box,
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+  bottom: 0;
+  left: 0;
+  position: absolute;
+  right: 0;
+  top: 0;
+}
+
+.cropper-wrap-box,
+.cropper-canvas {
+  overflow: hidden;
+}
+
+.cropper-drag-box {
+  background-color: #fff;
+  opacity: 0;
+}
+
+.cropper-modal {
+  background-color: #000;
+  opacity: 0.5;
+}
+
+.cropper-view-box {
+  display: block;
+  height: 100%;
+  outline: 1px solid #39f;
+  outline-color: rgba(51, 153, 255, 75%);
+  overflow: hidden;
+  width: 100%;
+}
+
+.cropper-dashed {
+  border: 0 dashed #eee;
+  display: block;
+  opacity: 0.5;
+  position: absolute;
+}
+
+.cropper-dashed.dashed-h {
+    border-bottom-width: 1px;
+    border-top-width: 1px;
+    height: calc(100% / 3);
+    left: 0;
+    top: calc(100% / 3);
+    width: 100%;
+  }
+
+.cropper-dashed.dashed-v {
+    border-left-width: 1px;
+    border-right-width: 1px;
+    height: 100%;
+    left: calc(100% / 3);
+    top: 0;
+    width: calc(100% / 3);
+  }
+
+.cropper-center {
+  display: block;
+  height: 0;
+  left: 50%;
+  opacity: 0.75;
+  position: absolute;
+  top: 50%;
+  width: 0;
+}
+
+.cropper-center::before,
+  .cropper-center::after {
+    background-color: #eee;
+    content: " ";
+    display: block;
+    position: absolute;
+  }
+
+.cropper-center::before {
+    height: 1px;
+    left: -3px;
+    top: 0;
+    width: 7px;
+  }
+
+.cropper-center::after {
+    height: 7px;
+    left: 0;
+    top: -3px;
+    width: 1px;
+  }
+
+.cropper-face,
+.cropper-line,
+.cropper-point {
+  display: block;
+  height: 100%;
+  opacity: 0.1;
+  position: absolute;
+  width: 100%;
+}
+
+.cropper-face {
+  background-color: #fff;
+  left: 0;
+  top: 0;
+}
+
+.cropper-line {
+  background-color: #39f;
+}
+
+.cropper-line.line-e {
+    cursor: ew-resize;
+    right: -3px;
+    top: 0;
+    width: 5px;
+  }
+
+.cropper-line.line-n {
+    cursor: ns-resize;
+    height: 5px;
+    left: 0;
+    top: -3px;
+  }
+
+.cropper-line.line-w {
+    cursor: ew-resize;
+    left: -3px;
+    top: 0;
+    width: 5px;
+  }
+
+.cropper-line.line-s {
+    bottom: -3px;
+    cursor: ns-resize;
+    height: 5px;
+    left: 0;
+  }
+
+.cropper-point {
+  background-color: #39f;
+  height: 5px;
+  opacity: 0.75;
+  width: 5px;
+}
+
+.cropper-point.point-e {
+    cursor: ew-resize;
+    margin-top: -3px;
+    right: -3px;
+    top: 50%;
+  }
+
+.cropper-point.point-n {
+    cursor: ns-resize;
+    left: 50%;
+    margin-left: -3px;
+    top: -3px;
+  }
+
+.cropper-point.point-w {
+    cursor: ew-resize;
+    left: -3px;
+    margin-top: -3px;
+    top: 50%;
+  }
+
+.cropper-point.point-s {
+    bottom: -3px;
+    cursor: s-resize;
+    left: 50%;
+    margin-left: -3px;
+  }
+
+.cropper-point.point-ne {
+    cursor: nesw-resize;
+    right: -3px;
+    top: -3px;
+  }
+
+.cropper-point.point-nw {
+    cursor: nwse-resize;
+    left: -3px;
+    top: -3px;
+  }
+
+.cropper-point.point-sw {
+    bottom: -3px;
+    cursor: nesw-resize;
+    left: -3px;
+  }
+
+.cropper-point.point-se {
+    bottom: -3px;
+    cursor: nwse-resize;
+    height: 20px;
+    opacity: 1;
+    right: -3px;
+    width: 20px;
+  }
+
+@media (min-width: 768px) {
+
+.cropper-point.point-se {
+      height: 15px;
+      width: 15px;
+  }
+    }
+
+@media (min-width: 992px) {
+
+.cropper-point.point-se {
+      height: 10px;
+      width: 10px;
+  }
+    }
+
+@media (min-width: 1200px) {
+
+.cropper-point.point-se {
+      height: 5px;
+      opacity: 0.75;
+      width: 5px;
+  }
+    }
+
+.cropper-point.point-se::before {
+    background-color: #39f;
+    bottom: -50%;
+    content: " ";
+    display: block;
+    height: 200%;
+    opacity: 0;
+    position: absolute;
+    right: -50%;
+    width: 200%;
+  }
+
+.cropper-invisible {
+  opacity: 0;
+}
+
+.cropper-bg {
+  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
+}
+
+.cropper-hide {
+  display: block;
+  height: 0;
+  position: absolute;
+  width: 0;
+}
+
+.cropper-hidden {
+  display: none !important;
+}
+
+.cropper-move {
+  cursor: move;
+}
+
+.cropper-crop {
+  cursor: crosshair;
+}
+
+.cropper-disabled .cropper-drag-box,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+  cursor: not-allowed;
+}
diff --git a/react-ui/src/pages/User/Center/components/AvatarCropper/images/bg.png b/react-ui/src/pages/User/Center/components/AvatarCropper/images/bg.png
new file mode 100644
index 0000000..3c7056b
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/AvatarCropper/images/bg.png
Binary files differ
diff --git a/react-ui/src/pages/User/Center/components/AvatarCropper/index.less b/react-ui/src/pages/User/Center/components/AvatarCropper/index.less
new file mode 100644
index 0000000..dc3fecf
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/AvatarCropper/index.less
@@ -0,0 +1,10 @@
+.avatarPreview {
+	position: absolute;
+	top: 50%;
+	transform: translate(50%, -50%);
+	width: 200px;
+	height: 200px;
+	border-radius: 50%;
+	box-shadow: 0 0 4px #ccc;
+	overflow: hidden; 
+}
\ No newline at end of file
diff --git a/react-ui/src/pages/User/Center/components/AvatarCropper/index.tsx b/react-ui/src/pages/User/Center/components/AvatarCropper/index.tsx
new file mode 100644
index 0000000..83d0bcf
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/AvatarCropper/index.tsx
@@ -0,0 +1,144 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { Modal, Row, Col, Button, Space, Upload, message } from 'antd';
+import { useIntl } from '@umijs/max';
+import { uploadAvatar } from '@/services/system/user';
+import { Cropper } from 'react-cropper';
+import './cropper.css';
+import styles from './index.less';
+import {
+  MinusOutlined,
+  PlusOutlined,
+  RedoOutlined,
+  UndoOutlined,
+  UploadOutlined,
+} from '@ant-design/icons';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2022/02/24
+ *
+ * */
+
+export type AvatarCropperProps = {
+  onFinished: (isSuccess: boolean) => void;
+  open: boolean;
+  data: any;
+};
+
+const AvatarCropperForm: React.FC<AvatarCropperProps> = (props) => {
+  const cropperRef = useRef<HTMLImageElement>(null);
+  const [avatarData, setAvatarData] = useState<any>();
+  const [previewData, setPreviewData] = useState();
+
+  useEffect(() => {
+    setAvatarData(props.data);
+  }, [props]);
+
+  const intl = useIntl();
+  const handleOk = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.getCroppedCanvas().toBlob((blob: Blob) => {
+      const formData = new FormData();
+      formData.append('avatarfile', blob);
+      uploadAvatar(formData).then((res) => {
+        if (res.code === 200) {
+          message.success(res.msg);          
+          props.onFinished(true);
+        } else {
+          message.warning(res.msg);
+        }
+      });
+    }, 'image/png');
+  };
+  const handleCancel = () => {
+    props.onFinished(false);
+  };
+  const onCrop = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    setPreviewData(cropper.getCroppedCanvas().toDataURL());
+  };
+  const onRotateRight = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.rotate(90);
+  };
+  const onRotateLeft = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.rotate(-90);
+  };
+  const onZoomIn = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.zoom(0.1);
+  };
+  const onZoomOut = () => {
+    const imageElement: any = cropperRef?.current;
+    const cropper: any = imageElement?.cropper;
+    cropper.zoom(-0.1);
+  };
+  const beforeUpload = (file: any) => {
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = () => {
+      setAvatarData(reader.result);
+    };
+  };
+  return (
+    <Modal
+      width={800}
+      title={intl.formatMessage({
+        id: 'system.user.modify_avatar',
+        defaultMessage: '修改头像',
+      })}
+      open={props.open}
+      destroyOnClose
+      onOk={handleOk}
+      onCancel={handleCancel}
+    >
+      <Row gutter={[16, 16]}>
+        <Col span={12} order={1}>
+          <Cropper
+            ref={cropperRef}
+            src={avatarData}
+            style={{ height: 350, width: '100%', marginBottom: '16px' }}
+            initialAspectRatio={1}
+            guides={false}
+            crop={onCrop}
+            zoomable={true}
+            zoomOnWheel={true}
+            rotatable={true}
+          />
+        </Col>
+        <Col span={12} order={2}>
+          <div className={styles.avatarPreview}>
+            <img src={previewData} style={{ height: '100%', width: '100%' }} />
+          </div>
+        </Col>
+      </Row>
+      <Row gutter={[16, 16]}>
+        <Col span={6}>
+          <Upload beforeUpload={beforeUpload} maxCount={1}>
+            <Button>
+              <UploadOutlined />
+              上传
+            </Button>
+          </Upload>
+        </Col>
+        <Col>
+          <Space>
+            <Button icon={<RedoOutlined />} onClick={onRotateRight} />
+            <Button icon={<UndoOutlined />} onClick={onRotateLeft} />
+            <Button icon={<PlusOutlined />} onClick={onZoomIn} />
+            <Button icon={<MinusOutlined />} onClick={onZoomOut} />
+          </Space>
+        </Col>
+      </Row>
+    </Modal>
+  );
+};
+
+export default AvatarCropperForm;
diff --git a/react-ui/src/pages/User/Center/components/BaseInfo/index.tsx b/react-ui/src/pages/User/Center/components/BaseInfo/index.tsx
new file mode 100644
index 0000000..5cdca5f
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/BaseInfo/index.tsx
@@ -0,0 +1,119 @@
+import React from 'react';
+import { Form, message, Row } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { ProForm, ProFormRadio, ProFormText } from '@ant-design/pro-components';
+import { updateUserProfile } from '@/services/system/user';
+
+
+export type BaseInfoProps = {
+  values: Partial<API.CurrentUser> | undefined;
+};
+
+const BaseInfo: React.FC<BaseInfoProps> = (props) => {
+  const [form] = Form.useForm();
+  const intl = useIntl();
+
+  const handleFinish = async (values: Record<string, any>) => {
+    const data = { ...props.values, ...values } as API.CurrentUser;
+    const resp = await updateUserProfile(data);
+    if (resp.code === 200) {
+      message.success('修改成功');
+    } else {
+      message.warning(resp.msg);
+    }
+  };
+
+  return (
+    <>
+      <ProForm form={form} onFinish={handleFinish} initialValues={props.values}>
+        <Row>
+          <ProFormText
+            name="nickName"
+            label={intl.formatMessage({
+              id: 'system.user.nick_name',
+              defaultMessage: '用户昵称',
+            })}
+            width="xl"
+            placeholder="请输入用户昵称"
+            rules={[
+              {
+                required: true,
+                message: (
+                  <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
+                ),
+              },
+            ]}
+          />
+        </Row>
+        <Row>
+          <ProFormText
+            name="phonenumber"
+            label={intl.formatMessage({
+              id: 'system.user.phonenumber',
+              defaultMessage: '手机号码',
+            })}
+            width="xl"
+            placeholder="请输入手机号码"
+            rules={[
+              {
+                required: false,
+                message: (
+                  <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
+                ),
+              },
+            ]}
+          />
+        </Row>
+        <Row>
+          <ProFormText
+            name="email"
+            label={intl.formatMessage({
+              id: 'system.user.email',
+              defaultMessage: '邮箱',
+            })}
+            width="xl"
+            placeholder="请输入邮箱"
+            rules={[
+              {
+                type: 'email',
+                message: '无效的邮箱地址!',
+              },
+              {
+                required: false,
+                message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />,
+              },
+            ]}
+          />
+        </Row>
+        <Row>
+          <ProFormRadio.Group
+            options={[
+              {
+                label: '男',
+                value: '0',
+              },
+              {
+                label: '女',
+                value: '1',
+              },
+            ]}
+            name="sex"
+            label={intl.formatMessage({
+              id: 'system.user.sex',
+              defaultMessage: 'sex',
+            })}
+            width="xl"
+            rules={[
+              {
+                required: false,
+                message: <FormattedMessage id="请输入性别!" defaultMessage="请输入性别!" />,
+              },
+            ]}
+          />
+        </Row>
+      </ProForm>
+    </>
+  );
+};
+
+export default BaseInfo;
diff --git a/react-ui/src/pages/User/Center/components/ResetPassword/index.tsx b/react-ui/src/pages/User/Center/components/ResetPassword/index.tsx
new file mode 100644
index 0000000..26825d4
--- /dev/null
+++ b/react-ui/src/pages/User/Center/components/ResetPassword/index.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { Form, message } from 'antd';
+import { FormattedMessage, useIntl } from '@umijs/max';
+import { updateUserPwd } from '@/services/system/user';
+import { ProForm, ProFormText } from '@ant-design/pro-components';
+
+const ResetPassword: React.FC = () => {
+  const [form] = Form.useForm();
+  const intl = useIntl();
+
+  const handleFinish = async (values: Record<string, any>) => {
+    const resp = await updateUserPwd(values.oldPassword, values.newPassword);
+    if (resp.code === 200) {
+      message.success('密码重置成功。');
+    } else {
+      message.warning(resp.msg);
+    }
+  };
+
+  const checkPassword = (rule: any, value: string) => {
+    const login_password = form.getFieldValue('newPassword');
+    if (value === login_password) {
+      return Promise.resolve();
+    }
+    return Promise.reject(new Error('两次密码输入不一致'));
+  };
+
+  return (
+    <>
+      <ProForm form={form} onFinish={handleFinish}>       
+          <ProFormText.Password
+            name="oldPassword"
+            label={intl.formatMessage({
+              id: 'system.user.old_password',
+              defaultMessage: '旧密码',
+            })}
+            width="xl"
+            placeholder="请输入旧密码"
+            rules={[
+              {
+                required: true,
+                message: <FormattedMessage id="请输入旧密码!" defaultMessage="请输入旧密码!" />,
+              },
+            ]}
+          />
+          <ProFormText.Password
+            name="newPassword"
+            label={intl.formatMessage({
+              id: 'system.user.new_password',
+              defaultMessage: '新密码',
+            })}
+            width="xl"
+            placeholder="请输入新密码"
+            rules={[
+              {
+                required: true,
+                message: <FormattedMessage id="请输入新密码!" defaultMessage="请输入新密码!" />,
+              },
+            ]}
+          />
+          <ProFormText.Password
+            name="confirmPassword"
+            label={intl.formatMessage({
+              id: 'system.user.confirm_password',
+              defaultMessage: '确认密码',
+            })}
+            width="xl"
+            placeholder="请输入确认密码"
+            rules={[
+              {
+                required: true,
+                message: (
+                  <FormattedMessage id="请输入确认密码!" defaultMessage="请输入确认密码!" />
+                ),
+              },
+              { validator: checkPassword },
+            ]}
+          />
+      </ProForm>
+    </>
+  );
+};
+
+export default ResetPassword;
diff --git a/react-ui/src/pages/User/Center/index.tsx b/react-ui/src/pages/User/Center/index.tsx
new file mode 100644
index 0000000..2ce308c
--- /dev/null
+++ b/react-ui/src/pages/User/Center/index.tsx
@@ -0,0 +1,200 @@
+import {
+  ClusterOutlined,
+  MailOutlined,
+  TeamOutlined,
+  UserOutlined,
+  MobileOutlined,
+  ManOutlined,
+} from '@ant-design/icons';
+import { Card, Col, Divider, List, Row } from 'antd';
+import React, { useState } from 'react';
+import styles from './Center.less';
+import BaseInfo from './components/BaseInfo';
+import ResetPassword from './components/ResetPassword';
+import AvatarCropper from './components/AvatarCropper';
+import { useRequest } from '@umijs/max';
+import { getUserInfo } from '@/services/session';
+import { PageLoading } from '@ant-design/pro-components';
+
+const operationTabList = [
+  {
+    key: 'base',
+    tab: (
+      <span>
+        基本资料
+      </span>
+    ),
+  },
+  {
+    key: 'password',
+    tab: (
+      <span>
+        重置密码
+      </span>
+    ),
+  },
+];
+
+export type tabKeyType = 'base' | 'password';
+
+const Center: React.FC = () => {
+  
+  const [tabKey, setTabKey] = useState<tabKeyType>('base');
+  
+  const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false);
+  
+  //  获取用户信息
+  const { data: userInfo, loading } = useRequest(async () => {
+    return { data: await getUserInfo()};
+  });
+  if (loading) {
+    return <div>loading...</div>;
+  }
+
+  const currentUser = userInfo?.user;
+
+  //  渲染用户信息
+  const renderUserInfo = ({
+    userName,
+    phonenumber,
+    email,
+    sex,
+    dept,
+  }: Partial<API.CurrentUser>) => {
+    return (
+      <List>
+        <List.Item>
+          <div>
+            <UserOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            用户名
+          </div>
+          <div>{userName}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <ManOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            性别
+          </div>
+          <div>{sex === '1' ? '女' : '男'}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <MobileOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            电话
+          </div>
+          <div>{phonenumber}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <MailOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            邮箱
+          </div>
+          <div>{email}</div>
+        </List.Item>
+        <List.Item>
+          <div>
+            <ClusterOutlined
+              style={{
+                marginRight: 8,
+              }}
+            />
+            部门
+          </div>
+          <div>{dept?.deptName}</div>
+        </List.Item>
+      </List>
+    );
+  };
+
+  // 渲染tab切换
+  const renderChildrenByTabKey = (tabValue: tabKeyType) => {
+    if (tabValue === 'base') {
+      return <BaseInfo values={currentUser} />;
+    }
+    if (tabValue === 'password') {
+      return <ResetPassword />;
+    }
+    return null;
+  };
+
+  if (!currentUser) {
+    return <PageLoading />;
+  }
+
+  return (
+    <div>
+      <Row gutter={[16, 24]}>
+        <Col lg={8} md={24}>
+          <Card
+            title="个人信息"
+            bordered={false}
+            loading={loading}
+          >
+            {!loading && (
+              <div style={{ textAlign: "center"}}>
+                <div className={styles.avatarHolder} onClick={()=>{setCropperModalOpen(true)}}>
+                  <img alt="" src={currentUser.avatar} />
+                </div>
+                {renderUserInfo(currentUser)}
+                <Divider dashed />
+                <div className={styles.team}>
+                  <div className={styles.teamTitle}>角色</div>
+                  <Row gutter={36}>
+                    {currentUser.roles &&
+                      currentUser.roles.map((item: any) => (
+                        <Col key={item.roleId} lg={24} xl={12}>
+                          <TeamOutlined
+                            style={{
+                              marginRight: 8,
+                            }}
+                          />
+                          {item.roleName}
+                        </Col>
+                      ))}
+                  </Row>
+                </div>
+              </div>
+            )}
+          </Card>
+        </Col>
+        <Col lg={16} md={24}>
+          <Card
+            bordered={false}
+            tabList={operationTabList}
+            activeTabKey={tabKey}
+            onTabChange={(_tabKey: string) => {
+              setTabKey(_tabKey as tabKeyType);
+            }}
+          >
+            {renderChildrenByTabKey(tabKey)}
+          </Card>
+        </Col>
+      </Row>
+      <AvatarCropper
+        onFinished={() => {
+          setCropperModalOpen(false);     
+        }}
+        open={cropperModalOpen}
+        data={currentUser.avatar}
+      />
+    </div>
+  );
+};
+
+export default Center;
diff --git a/react-ui/src/pages/User/Login/index.tsx b/react-ui/src/pages/User/Login/index.tsx
new file mode 100644
index 0000000..a18f785
--- /dev/null
+++ b/react-ui/src/pages/User/Login/index.tsx
@@ -0,0 +1,436 @@
+import Footer from '@/components/Footer';
+import { getCaptchaImg, login } from '@/services/system/auth';
+import { getFakeCaptcha } from '@/services/ant-design-pro/login';
+import {
+  AlipayCircleOutlined,
+  LockOutlined,
+  MobileOutlined,
+  TaobaoCircleOutlined,
+  UserOutlined,
+  WeiboCircleOutlined,
+} from '@ant-design/icons';
+import {
+  LoginForm,
+  ProFormCaptcha,
+  ProFormCheckbox,
+  ProFormText,
+} from '@ant-design/pro-components';
+import { useEmotionCss } from '@ant-design/use-emotion-css';
+import { FormattedMessage, history, SelectLang, useIntl, useModel, Helmet } from '@umijs/max';
+import { Alert, Col, message, Row, Tabs, Image } from 'antd';
+import Settings from '../../../../config/defaultSettings';
+import React, { useEffect, useState } from 'react';
+import { flushSync } from 'react-dom';
+import { clearSessionToken, setSessionToken } from '@/access';
+
+const ActionIcons = () => {
+  const langClassName = useEmotionCss(({ token }) => {
+    return {
+      marginLeft: '8px',
+      color: 'rgba(0, 0, 0, 0.2)',
+      fontSize: '24px',
+      verticalAlign: 'middle',
+      cursor: 'pointer',
+      transition: 'color 0.3s',
+      '&:hover': {
+        color: token.colorPrimaryActive,
+      },
+    };
+  });
+
+  return (
+    <>
+      <AlipayCircleOutlined key="AlipayCircleOutlined" className={langClassName} />
+      <TaobaoCircleOutlined key="TaobaoCircleOutlined" className={langClassName} />
+      <WeiboCircleOutlined key="WeiboCircleOutlined" className={langClassName} />
+    </>
+  );
+};
+
+const Lang = () => {
+  const langClassName = useEmotionCss(({ token }) => {
+    return {
+      width: 42,
+      height: 42,
+      lineHeight: '42px',
+      position: 'fixed',
+      right: 16,
+      borderRadius: token.borderRadius,
+      ':hover': {
+        backgroundColor: token.colorBgTextHover,
+      },
+    };
+  });
+
+  return (
+    <div className={langClassName} data-lang>
+      {SelectLang && <SelectLang />}
+    </div>
+  );
+};
+
+const LoginMessage: React.FC<{
+  content: string;
+}> = ({ content }) => {
+  return (
+    <Alert
+      style={{
+        marginBottom: 24,
+      }}
+      message={content}
+      type="error"
+      showIcon
+    />
+  );
+};
+
+const Login: React.FC = () => {
+  const [userLoginState, setUserLoginState] = useState<API.LoginResult>({code: 200});
+  const [type, setType] = useState<string>('account');
+  const { initialState, setInitialState } = useModel('@@initialState');
+  const [captchaCode, setCaptchaCode] = useState<string>('');
+  const [uuid, setUuid] = useState<string>('');
+
+  const containerClassName = useEmotionCss(() => {
+    return {
+      display: 'flex',
+      flexDirection: 'column',
+      height: '100vh',
+      overflow: 'auto',
+      backgroundImage:
+        "url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
+      backgroundSize: '100% 100%',
+    };
+  });
+
+  const intl = useIntl();
+
+  const getCaptchaCode = async () => {
+    const response = await getCaptchaImg();
+    const imgdata = `data:image/png;base64,${response.img}`;
+    setCaptchaCode(imgdata);
+    setUuid(response.uuid);
+  };
+
+  const fetchUserInfo = async () => {
+    const userInfo = await initialState?.fetchUserInfo?.();
+    if (userInfo) {
+      flushSync(() => {
+        setInitialState((s) => ({
+          ...s,
+          currentUser: userInfo,
+        }));
+      });
+    }
+  };
+
+  const handleSubmit = async (values: API.LoginParams) => {
+    try {
+      // 登录
+      const response = await login({ ...values, uuid });
+      if (response.code === 200) {
+        const defaultLoginSuccessMessage = intl.formatMessage({
+          id: 'pages.login.success',
+          defaultMessage: '登录成功!',
+        });
+        const current = new Date();
+        const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60);
+        console.log('login response: ', response);
+        setSessionToken(response?.token, response?.token, expireTime);
+        message.success(defaultLoginSuccessMessage);
+        await fetchUserInfo();
+        console.log('login ok');
+        const urlParams = new URL(window.location.href).searchParams;
+        history.push(urlParams.get('redirect') || '/');
+        return;
+      } else {
+        console.log(response.msg);
+        clearSessionToken();
+        // 如果失败去设置用户错误信息
+        setUserLoginState({ ...response, type });
+        getCaptchaCode();
+      }
+    } catch (error) {
+      const defaultLoginFailureMessage = intl.formatMessage({
+        id: 'pages.login.failure',
+        defaultMessage: '登录失败,请重试!',
+      });
+      console.log(error);
+      message.error(defaultLoginFailureMessage);
+    }
+  };
+  const { code } = userLoginState;
+  const loginType = type;
+
+  useEffect(() => {
+    getCaptchaCode();
+  }, []);
+
+  return (
+    <div className={containerClassName}>
+      <Helmet>
+        <title>
+          {intl.formatMessage({
+            id: 'menu.login',
+            defaultMessage: '登录页',
+          })}
+          - {Settings.title}
+        </title>
+      </Helmet>
+      <Lang />
+      <div
+        style={{
+          flex: '1',
+          padding: '32px 0',
+        }}
+      >
+        <LoginForm
+          contentStyle={{
+            minWidth: 280,
+            maxWidth: '75vw',
+          }}
+          logo={<img alt="logo" src="/logo.svg" />}
+          title="Ant Design"
+          subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
+          initialValues={{
+            autoLogin: true,
+          }}
+          actions={[
+            <FormattedMessage
+              key="loginWith"
+              id="pages.login.loginWith"
+              defaultMessage="其他登录方式"
+            />,
+            <ActionIcons key="icons" />,
+          ]}
+          onFinish={async (values) => {
+            await handleSubmit(values as API.LoginParams);
+          }}
+        >
+          <Tabs
+            activeKey={type}
+            onChange={setType}
+            centered
+            items={[
+              {
+                key: 'account',
+                label: intl.formatMessage({
+                  id: 'pages.login.accountLogin.tab',
+                  defaultMessage: '账户密码登录',
+                }),
+              },
+              {
+                key: 'mobile',
+                label: intl.formatMessage({
+                  id: 'pages.login.phoneLogin.tab',
+                  defaultMessage: '手机号登录',
+                }),
+              },
+            ]}
+          />
+
+          {code !== 200 && loginType === 'account' && (
+            <LoginMessage
+              content={intl.formatMessage({
+                id: 'pages.login.accountLogin.errorMessage',
+                defaultMessage: '账户或密码错误(admin/admin123)',
+              })}
+            />
+          )}
+          {type === 'account' && (
+            <>
+              <ProFormText
+                name="username"
+                initialValue="admin"
+                fieldProps={{
+                  size: 'large',
+                  prefix: <UserOutlined />,
+                }}
+                placeholder={intl.formatMessage({
+                  id: 'pages.login.username.placeholder',
+                  defaultMessage: '用户名: admin',
+                })}
+                rules={[
+                  {
+                    required: true,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.username.required"
+                        defaultMessage="请输入用户名!"
+                      />
+                    ),
+                  },
+                ]}
+              />
+              <ProFormText.Password
+                name="password"
+                initialValue="admin123"
+                fieldProps={{
+                  size: 'large',
+                  prefix: <LockOutlined />,
+                }}
+                placeholder={intl.formatMessage({
+                  id: 'pages.login.password.placeholder',
+                  defaultMessage: '密码: admin123',
+                })}
+                rules={[
+                  {
+                    required: true,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.password.required"
+                        defaultMessage="请输入密码!"
+                      />
+                    ),
+                  },
+                ]}
+              />
+              <Row>
+                <Col flex={3}>
+                  <ProFormText
+                    style={{
+                      float: 'right',
+                    }}
+                    name="code"
+                    placeholder={intl.formatMessage({
+                      id: 'pages.login.captcha.placeholder',
+                      defaultMessage: '请输入验证',
+                    })}
+                    rules={[
+                      {
+                        required: true,
+                        message: (
+                          <FormattedMessage
+                            id="pages.searchTable.updateForm.ruleName.nameRules"
+                            defaultMessage="请输入验证啊"
+                          />
+                        ),
+                      },
+                    ]}
+                  />
+                </Col>
+                <Col flex={2}>
+                  <Image
+                    src={captchaCode}
+                    alt="验证码"
+                    style={{
+                      display: 'inline-block',
+                      verticalAlign: 'top',
+                      cursor: 'pointer',
+                      paddingLeft: '10px',
+                      width: '100px',
+                    }}
+                    preview={false}
+                    onClick={() => getCaptchaCode()}
+                  />
+                </Col>
+              </Row>
+            </>
+          )}
+
+          {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
+          {type === 'mobile' && (
+            <>
+              <ProFormText
+                fieldProps={{
+                  size: 'large',
+                  prefix: <MobileOutlined />,
+                }}
+                name="mobile"
+                placeholder={intl.formatMessage({
+                  id: 'pages.login.phoneNumber.placeholder',
+                  defaultMessage: '手机号',
+                })}
+                rules={[
+                  {
+                    required: true,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.phoneNumber.required"
+                        defaultMessage="请输入手机号!"
+                      />
+                    ),
+                  },
+                  {
+                    pattern: /^1\d{10}$/,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.phoneNumber.invalid"
+                        defaultMessage="手机号格式错误!"
+                      />
+                    ),
+                  },
+                ]}
+              />
+              <ProFormCaptcha
+                fieldProps={{
+                  size: 'large',
+                  prefix: <LockOutlined />,
+                }}
+                captchaProps={{
+                  size: 'large',
+                }}
+                placeholder={intl.formatMessage({
+                  id: 'pages.login.captcha.placeholder',
+                  defaultMessage: '请输入验证码',
+                })}
+                captchaTextRender={(timing, count) => {
+                  if (timing) {
+                    return `${count} ${intl.formatMessage({
+                      id: 'pages.getCaptchaSecondText',
+                      defaultMessage: '获取验证码',
+                    })}`;
+                  }
+                  return intl.formatMessage({
+                    id: 'pages.login.phoneLogin.getVerificationCode',
+                    defaultMessage: '获取验证码',
+                  });
+                }}
+                name="captcha"
+                rules={[
+                  {
+                    required: true,
+                    message: (
+                      <FormattedMessage
+                        id="pages.login.captcha.required"
+                        defaultMessage="请输入验证码!"
+                      />
+                    ),
+                  },
+                ]}
+                onGetCaptcha={async (phone) => {
+                  const result = await getFakeCaptcha({
+                    phone,
+                  });
+                  if (!result) {
+                    return;
+                  }
+                  message.success('获取验证码成功!验证码为:1234');
+                }}
+              />
+            </>
+          )}
+          <div
+            style={{
+              marginBottom: 24,
+            }}
+          >
+            <ProFormCheckbox noStyle name="autoLogin">
+              <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
+            </ProFormCheckbox>
+            <a
+              style={{
+                float: 'right',
+              }}
+            >
+              <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
+            </a>
+          </div>
+        </LoginForm>
+      </div>
+      <Footer />
+    </div>
+  );
+};
+
+export default Login;
diff --git a/react-ui/src/pages/User/Settings/index.tsx b/react-ui/src/pages/User/Settings/index.tsx
new file mode 100644
index 0000000..f29d0b9
--- /dev/null
+++ b/react-ui/src/pages/User/Settings/index.tsx
@@ -0,0 +1,20 @@
+import { PageContainer } from '@ant-design/pro-components';
+import { Card } from 'antd';
+import React from 'react';
+
+/**
+ *
+ * @author whiteshader@163.com
+ *
+ * */
+
+
+const Settings: React.FC = () => {
+  return (
+    <PageContainer>
+      <Card title="Developing" />
+    </PageContainer>
+  );
+};
+
+export default Settings;
diff --git a/react-ui/src/pages/Welcome.tsx b/react-ui/src/pages/Welcome.tsx
new file mode 100644
index 0000000..d0c49f7
--- /dev/null
+++ b/react-ui/src/pages/Welcome.tsx
@@ -0,0 +1,164 @@
+import { PageContainer } from '@ant-design/pro-components';
+import { useModel } from '@umijs/max';
+import { Card, theme } from 'antd';
+import React from 'react';
+
+/**
+ * 每个单独的卡片,为了复用样式抽成了组件
+ * @param param0
+ * @returns
+ */
+const InfoCard: React.FC<{
+  title: string;
+  index: number;
+  desc: string;
+  href: string;
+}> = ({ title, href, index, desc }) => {
+  const { useToken } = theme;
+
+  const { token } = useToken();
+
+  return (
+    <div
+      style={{
+        backgroundColor: token.colorBgContainer,
+        boxShadow: token.boxShadow,
+        borderRadius: '8px',
+        fontSize: '14px',
+        color: token.colorTextSecondary,
+        lineHeight: '22px',
+        padding: '16px 19px',
+        minWidth: '220px',
+        flex: 1,
+      }}
+    >
+      <div
+        style={{
+          display: 'flex',
+          gap: '4px',
+          alignItems: 'center',
+        }}
+      >
+        <div
+          style={{
+            width: 48,
+            height: 48,
+            lineHeight: '22px',
+            backgroundSize: '100%',
+            textAlign: 'center',
+            padding: '8px 16px 16px 12px',
+            color: '#FFF',
+            fontWeight: 'bold',
+            backgroundImage:
+              "url('https://gw.alipayobjects.com/zos/bmw-prod/daaf8d50-8e6d-4251-905d-676a24ddfa12.svg')",
+          }}
+        >
+          {index}
+        </div>
+        <div
+          style={{
+            fontSize: '16px',
+            color: token.colorText,
+            paddingBottom: 8,
+          }}
+        >
+          {title}
+        </div>
+      </div>
+      <div
+        style={{
+          fontSize: '14px',
+          color: token.colorTextSecondary,
+          textAlign: 'justify',
+          lineHeight: '22px',
+          marginBottom: 8,
+        }}
+      >
+        {desc}
+      </div>
+      <a href={href} target="_blank" rel="noreferrer">
+        了解更多 {'>'}
+      </a>
+    </div>
+  );
+};
+
+const Welcome: React.FC = () => {
+  const { token } = theme.useToken();
+  const { initialState } = useModel('@@initialState');
+  return (
+    <PageContainer>
+      <Card
+        style={{
+          borderRadius: 8,
+        }}
+        bodyStyle={{
+          backgroundImage:
+            initialState?.settings?.navTheme === 'realDark'
+              ? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
+              : 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
+        }}
+      >
+        <div
+          style={{
+            backgroundPosition: '100% -30%',
+            backgroundRepeat: 'no-repeat',
+            backgroundSize: '274px auto',
+            backgroundImage:
+              "url('https://gw.alipayobjects.com/mdn/rms_a9745b/afts/img/A*BuFmQqsB2iAAAAAAAAAAAAAAARQnAQ')",
+          }}
+        >
+          <div
+            style={{
+              fontSize: '20px',
+              color: token.colorTextHeading,
+            }}
+          >
+            欢迎使用 Ant Design Pro
+          </div>
+          <p
+            style={{
+              fontSize: '14px',
+              color: token.colorTextSecondary,
+              lineHeight: '22px',
+              marginTop: 16,
+              marginBottom: 32,
+              width: '65%',
+            }}
+          >
+            Ant Design Pro 是一个整合了 umi,Ant Design 和 ProComponents
+            的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
+          </p>
+          <div
+            style={{
+              display: 'flex',
+              flexWrap: 'wrap',
+              gap: 16,
+            }}
+          >
+            <InfoCard
+              index={1}
+              href="https://umijs.org/docs/introduce/introduce"
+              title="了解 umi"
+              desc="umi 是一个可扩展的企业级前端应用框架,umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。"
+            />
+            <InfoCard
+              index={2}
+              title="了解 ant design"
+              href="https://ant.design"
+              desc="antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。"
+            />
+            <InfoCard
+              index={3}
+              title="了解 Pro Components"
+              href="https://procomponents.ant.design"
+              desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
+            />
+          </div>
+        </div>
+      </Card>
+    </PageContainer>
+  );
+};
+
+export default Welcome;
diff --git a/react-ui/src/requestErrorConfig.ts b/react-ui/src/requestErrorConfig.ts
new file mode 100644
index 0000000..c0c4a6a
--- /dev/null
+++ b/react-ui/src/requestErrorConfig.ts
@@ -0,0 +1,109 @@
+import type { RequestOptions } from '@@/plugin-request/request';
+import type { RequestConfig } from '@umijs/max';
+import { message, notification } from 'antd';
+
+// 错误处理方案: 错误类型
+enum ErrorShowType {
+  SILENT = 0,
+  WARN_MESSAGE = 1,
+  ERROR_MESSAGE = 2,
+  NOTIFICATION = 3,
+  REDIRECT = 9,
+}
+// 与后端约定的响应数据格式
+interface ResponseStructure {
+  success: boolean;
+  data: any;
+  errorCode?: number;
+  errorMessage?: string;
+  showType?: ErrorShowType;
+}
+
+/**
+ * @name 错误处理
+ * pro 自带的错误处理, 可以在这里做自己的改动
+ * @doc https://umijs.org/docs/max/request#配置
+ */
+export const errorConfig: RequestConfig = {
+  // 错误处理: umi@3 的错误处理方案。
+  errorConfig: {
+    // 错误抛出
+    errorThrower: (res) => {
+      const { success, data, errorCode, errorMessage, showType } =
+        res as unknown as ResponseStructure;
+      if (!success) {
+        const error: any = new Error(errorMessage);
+        error.name = 'BizError';
+        error.info = { errorCode, errorMessage, showType, data };
+        throw error; // 抛出自制的错误
+      }
+    },
+    // 错误接收及处理
+    errorHandler: (error: any, opts: any) => {
+      if (opts?.skipErrorHandler) throw error;
+      // 我们的 errorThrower 抛出的错误。
+      if (error.name === 'BizError') {
+        const errorInfo: ResponseStructure | undefined = error.info;
+        if (errorInfo) {
+          const { errorMessage, errorCode } = errorInfo;
+          switch (errorInfo.showType) {
+            case ErrorShowType.SILENT:
+              // do nothing
+              break;
+            case ErrorShowType.WARN_MESSAGE:
+              message.warning(errorMessage);
+              break;
+            case ErrorShowType.ERROR_MESSAGE:
+              message.error(errorMessage);
+              break;
+            case ErrorShowType.NOTIFICATION:
+              notification.open({
+                description: errorMessage,
+                message: errorCode,
+              });
+              break;
+            case ErrorShowType.REDIRECT:
+              // TODO: redirect
+              break;
+            default:
+              message.error(errorMessage);
+          }
+        }
+      } else if (error.response) {
+        // Axios 的错误
+        // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
+        message.error(`Response status:${error.response.status}`);
+      } else if (error.request) {
+        // 请求已经成功发起,但没有收到响应
+        // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
+        // 而在node.js中是 http.ClientRequest 的实例
+        message.error('None response! Please retry.');
+      } else {
+        // 发送请求时出了点问题
+        message.error('Request error, please retry.');
+      }
+    },
+  },
+
+  // 请求拦截器
+  requestInterceptors: [
+    (config: RequestOptions) => {
+      // 拦截请求配置,进行个性化处理。
+      const url = config?.url?.concat('?token = 123');
+      return { ...config, url };
+    },
+  ],
+
+  // 响应拦截器
+  responseInterceptors: [
+    (response) => {
+      // 拦截响应数据,进行个性化处理
+      const { data } = response as unknown as ResponseStructure;
+
+      if (data?.success === false) {
+        message.error('请求失败!');
+      }
+      return response;
+    },
+  ],
+};
diff --git a/react-ui/src/service-worker.js b/react-ui/src/service-worker.js
new file mode 100644
index 0000000..b86726c
--- /dev/null
+++ b/react-ui/src/service-worker.js
@@ -0,0 +1,65 @@
+/* eslint-disable no-restricted-globals */
+/* eslint-disable no-underscore-dangle */
+/* globals workbox */
+workbox.core.setCacheNameDetails({
+  prefix: 'antd-pro',
+  suffix: 'v5',
+});
+// Control all opened tabs ASAP
+workbox.clientsClaim();
+
+/**
+ * Use precaching list generated by workbox in build process.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
+ */
+workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
+
+/**
+ * Register a navigation route.
+ * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
+ */
+workbox.routing.registerNavigationRoute('/index.html');
+
+/**
+ * Use runtime cache:
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
+ *
+ * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
+ */
+
+/** Handle API requests */
+workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
+
+/** Handle third party requests */
+workbox.routing.registerRoute(
+  /^https:\/\/gw\.alipayobjects\.com\//,
+  workbox.strategies.networkFirst(),
+);
+workbox.routing.registerRoute(
+  /^https:\/\/cdnjs\.cloudflare\.com\//,
+  workbox.strategies.networkFirst(),
+);
+workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
+
+/** Response to client after skipping waiting with MessageChannel */
+addEventListener('message', (event) => {
+  const replyPort = event.ports[0];
+  const message = event.data;
+  if (replyPort && message && message.type === 'skip-waiting') {
+    event.waitUntil(
+      self.skipWaiting().then(
+        () => {
+          replyPort.postMessage({
+            error: null,
+          });
+        },
+        (error) => {
+          replyPort.postMessage({
+            error,
+          });
+        },
+      ),
+    );
+  }
+});
diff --git a/react-ui/src/services/ant-design-pro/api.ts b/react-ui/src/services/ant-design-pro/api.ts
new file mode 100644
index 0000000..64a950a
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/api.ts
@@ -0,0 +1,11 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 此处后端没有提供注释 GET /api/notices */
+export async function getNotices(options?: { [key: string]: any }) {
+  return request<API.NoticeIconList>('/api/notices', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/ant-design-pro/index.ts b/react-ui/src/services/ant-design-pro/index.ts
new file mode 100644
index 0000000..9ae58be
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/index.ts
@@ -0,0 +1,12 @@
+// @ts-ignore
+/* eslint-disable */
+// API 更新时间:
+// API 唯一标识:
+import * as api from './api';
+import * as login from './login';
+import * as rule from './rule';
+export default {
+  api,
+  login,
+  rule,
+};
diff --git a/react-ui/src/services/ant-design-pro/login.ts b/react-ui/src/services/ant-design-pro/login.ts
new file mode 100644
index 0000000..3b00b43
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/login.ts
@@ -0,0 +1,38 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 登录接口 POST /api/login/account */
+export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
+  return request<API.LoginResult>('/api/login/account', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** 发送验证码 POST /api/login/captcha */
+export async function getFakeCaptcha(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.getFakeCaptchaParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.FakeCaptcha>('/api/login/captcha', {
+    method: 'POST',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
+
+/** 登录接口 POST /api/login/outLogin */
+export async function outLogin(options?: { [key: string]: any }) {
+  return request<Record<string, any>>('/api/login/outLogin', {
+    method: 'POST',
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/ant-design-pro/rule.ts b/react-ui/src/services/ant-design-pro/rule.ts
new file mode 100644
index 0000000..4b8ebc5
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/rule.ts
@@ -0,0 +1,42 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 获取规则列表 GET /api/rule */
+export async function rule(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.ruleParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.RuleList>('/api/rule', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
+
+/** 新建规则 PUT /api/rule */
+export async function updateRule(options?: { [key: string]: any }) {
+  return request<API.RuleListItem>('/api/rule', {
+    method: 'PUT',
+    ...(options || {}),
+  });
+}
+
+/** 新建规则 POST /api/rule */
+export async function addRule(options?: { [key: string]: any }) {
+  return request<API.RuleListItem>('/api/rule', {
+    method: 'POST',
+    ...(options || {}),
+  });
+}
+
+/** 删除规则 DELETE /api/rule */
+export async function removeRule(options?: { [key: string]: any }) {
+  return request<Record<string, any>>('/api/rule', {
+    method: 'DELETE',
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/ant-design-pro/typings.d.ts b/react-ui/src/services/ant-design-pro/typings.d.ts
new file mode 100644
index 0000000..4ee1264
--- /dev/null
+++ b/react-ui/src/services/ant-design-pro/typings.d.ts
@@ -0,0 +1,108 @@
+declare namespace API {
+  type CurrentUser = UserInfo & {
+    signature?: string;
+    title?: string;
+    group?: string;
+    tags?: { key?: string; label?: string }[];
+    notifyCount?: number;
+    unreadCount?: number;
+    country?: string;
+    access?: string;
+    geographic?: {
+      province?: { label?: string; key?: string };
+      city?: { label?: string; key?: string };
+    };
+    address?: string;
+    phone?: string;
+  };
+
+  type ErrorResponse = {
+    /** 业务约定的错误码 */
+    errorCode: string;
+    /** 业务上的错误信息 */
+    errorMessage?: string;
+    /** 业务上的请求是否成功 */
+    success?: boolean;
+  };
+
+  type FakeCaptcha = {
+    code?: number;
+    status?: string;
+  };
+
+  type getFakeCaptchaParams = {
+    /** 手机号 */
+    phone?: string;
+  };
+
+  type LoginParams = {
+    username?: string;
+    password?: string;
+    uuid?: string;
+    autoLogin?: boolean;
+    type?: string;
+  };
+
+  type LoginResult = {
+    code: number;
+    msg?: string;
+    type?: string;
+    token?: string;
+  };
+
+  type NoticeIconItem = {
+    id?: string;
+    extra?: string;
+    key?: string;
+    read?: boolean;
+    avatar?: string;
+    title?: string;
+    status?: string;
+    datetime?: string;
+    description?: string;
+    type?: NoticeIconItemType;
+  };
+
+  type NoticeIconItemType = 'notification' | 'message' | 'event';
+
+  type NoticeIconList = {
+    data?: NoticeIconItem[];
+    /** 列表的内容总数 */
+    total?: number;
+    success?: boolean;
+  };
+
+  type PageParams = {
+    current?: number;
+    pageSize?: number;
+  };
+
+  type RuleList = {
+    data?: RuleListItem[];
+    /** 列表的内容总数 */
+    total?: number;
+    success?: boolean;
+  };
+
+  type RuleListItem = {
+    key?: number;
+    disabled?: boolean;
+    href?: string;
+    avatar?: string;
+    name?: string;
+    owner?: string;
+    desc?: string;
+    callNo?: number;
+    status?: number;
+    updatedAt?: string;
+    createdAt?: string;
+    progress?: number;
+  };
+
+  type ruleParams = {
+    /** 当前的页码 */
+    current?: number;
+    /** 页面的容量 */
+    pageSize?: number;
+  };
+}
diff --git a/react-ui/src/services/monitor/cache.ts b/react-ui/src/services/monitor/cache.ts
new file mode 100644
index 0000000..244368e
--- /dev/null
+++ b/react-ui/src/services/monitor/cache.ts
@@ -0,0 +1,17 @@
+import { request } from '@umijs/max'; 
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+
+
+// 获取服务器信息
+export async function getCacheInfo() {
+  return request<API.Monitor.CacheInfoResult>('/api/monitor/cache', {
+    method: 'GET',
+  });
+}
diff --git a/react-ui/src/services/monitor/cachelist.ts b/react-ui/src/services/monitor/cachelist.ts
new file mode 100644
index 0000000..d348d4b
--- /dev/null
+++ b/react-ui/src/services/monitor/cachelist.ts
@@ -0,0 +1,51 @@
+import { request } from '@umijs/max'; 
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2022/06/27
+ * 
+ * */
+ 
+
+// 查询缓存名称列表
+export function listCacheName() {
+  return request<API.Monitor.CacheNamesResponse>('/api/monitor/cache/getNames', {
+    method: 'get'
+  })
+}
+
+// 查询缓存键名列表
+export function listCacheKey(cacheName: string) {
+  return request<API.Monitor.CacheKeysResponse>('/api/monitor/cache/getKeys/' + cacheName, {
+    method: 'get'
+  })
+}
+
+// 查询缓存内容
+export function getCacheValue(cacheName: string, cacheKey: string) {
+  return request<API.Monitor.CacheValueResponse>('/api/monitor/cache/getValue/' + cacheName + '/' + cacheKey, {
+    method: 'get'
+  })
+}
+
+// 清理指定名称缓存
+export function clearCacheName(cacheName: string) {
+  return request<API.Result>('/api/monitor/cache/clearCacheName/' + cacheName, {
+    method: 'delete'
+  })
+}
+
+// 清理指定键名缓存
+export function clearCacheKey(cacheKey: string) {
+  return request<API.Result>('/api/monitor/cache/clearCacheKey/' + cacheKey, {
+    method: 'delete'
+  })
+}
+
+// 清理全部缓存
+export function clearCacheAll() {
+  return request<API.Result>('/api/monitor/cache/clearCacheAll', {
+    method: 'delete'
+  })
+}
diff --git a/react-ui/src/services/monitor/job.ts b/react-ui/src/services/monitor/job.ts
new file mode 100644
index 0000000..192f819
--- /dev/null
+++ b/react-ui/src/services/monitor/job.ts
@@ -0,0 +1,73 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/**
+ * 定时任务调度 API
+ *
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+// 查询定时任务调度列表
+export async function getJobList(params?: API.Monitor.JobListParams) {
+  return request<API.Monitor.JobPageResult>('/api/monitor/job/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询定时任务调度详细
+export function getJob(jobId: number) {
+  return request<API.Monitor.JobInfoResult>(`/api/monitor/job/${jobId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增定时任务调度
+export async function addJob(params: API.Monitor.Job) {
+  return request<API.Result>('/api/monitor/job', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改定时任务调度
+export async function updateJob(params: API.Monitor.Job) {
+  return request<API.Result>('/api/monitor/job', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除定时任务调度
+export async function removeJob(ids: string) {
+  return request<API.Result>(`/api/monitor/job/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出定时任务调度
+export function exportJob(params?: API.Monitor.JobListParams) {
+  return downLoadXlsx(`/api/monitor/job/export`, { params }, `job_${new Date().getTime()}.xlsx`);
+}
+
+// 定时任务立即执行一次
+export async function runJob(jobId: number, jobGroup: string) {
+  const job = {
+    jobId,
+    jobGroup,
+  };
+  return request('/api/monitor/job/run', {
+    method: 'PUT',
+    data: job,
+  });
+}
diff --git a/react-ui/src/services/monitor/jobLog.ts b/react-ui/src/services/monitor/jobLog.ts
new file mode 100644
index 0000000..9885aa7
--- /dev/null
+++ b/react-ui/src/services/monitor/jobLog.ts
@@ -0,0 +1,40 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/**
+ * 定时任务调度日志 API
+ *
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+// 查询定时任务调度日志列表
+export async function getJobLogList(params?: API.Monitor.JobLogListParams) {
+  return request<API.Monitor.JobLogPageResult>('/api/schedule/job/log/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+
+// 删除定时任务调度日志
+export async function removeJobLog(jobLogId: string) {
+  return request<API.Result>(`/api/schedule/job/log/${jobLogId}`, {
+    method: 'DELETE'
+  });
+}
+
+// 清空调度日志
+export function cleanJobLog() {
+  return request('/api/schedule/job/log/clean', {
+    method: 'delete'
+  })
+}
+
+// 导出定时任务调度日志
+export function exportJobLog(params?: API.Monitor.JobLogListParams) {
+  return downLoadXlsx(`/api/monitor/jobLog/export`, { params }, `joblog_${new Date().getTime()}.xlsx`);
+}
diff --git a/react-ui/src/services/monitor/logininfor.ts b/react-ui/src/services/monitor/logininfor.ts
new file mode 100644
index 0000000..ea1864a
--- /dev/null
+++ b/react-ui/src/services/monitor/logininfor.ts
@@ -0,0 +1,68 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询系统访问记录列表
+export async function getLogininforList(params?: API.Monitor.LogininforListParams) {
+  return request<API.Monitor.LogininforPageResult>('/api/monitor/logininfor/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询系统访问记录详细
+export function getLogininfor(infoId: number) {
+  return request<API.Monitor.LogininforInfoResult>(`/api/monitor/logininfor/${infoId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增系统访问记录
+export async function addLogininfor(params: API.Monitor.Logininfor) {
+  return request<API.Result>('/api/monitor/logininfor', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改系统访问记录
+export async function updateLogininfor(params: API.Monitor.Logininfor) {
+  return request<API.Result>('/api/monitor/logininfor', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除系统访问记录
+export async function removeLogininfor(ids: string) {
+  return request<API.Result>(`/api/monitor/logininfor/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出系统访问记录
+export function exportLogininfor(params?: API.Monitor.LogininforListParams) {
+  return downLoadXlsx(`/api/monitor/logininfor/export`, { params }, `logininfor_${new Date().getTime()}.xlsx`);
+}
+
+// 解锁用户登录状态
+export function unlockLogininfor(userName: string) {
+  return request<API.Result>('/api/monitor/logininfor/unlock/' + userName, {
+    method: 'get'
+  })
+}
+
+// 清空登录日志
+export function cleanLogininfor() {
+  return request<API.Result>('/api/monitor/logininfor/clean', {
+    method: 'delete'
+  })
+}
diff --git a/react-ui/src/services/monitor/online.ts b/react-ui/src/services/monitor/online.ts
new file mode 100644
index 0000000..ae2a6ac
--- /dev/null
+++ b/react-ui/src/services/monitor/online.ts
@@ -0,0 +1,23 @@
+import { request } from '@umijs/max';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ *
+ * */
+
+// 查询在线用户列表
+export async function getOnlineUserList(params?: API.Monitor.OnlineUserListParams) {
+  return request<API.Monitor.OnlineUserPageResult>('/api/monitor/online/list', {
+    method: 'GET',
+    params,
+  });
+}
+
+// 强退用户
+export async function forceLogout(tokenId: string) {
+  return request(`/api/monitor/online/${tokenId}`, {
+    method: 'DELETE',
+  });
+}
diff --git a/react-ui/src/services/monitor/operlog.ts b/react-ui/src/services/monitor/operlog.ts
new file mode 100644
index 0000000..004863a
--- /dev/null
+++ b/react-ui/src/services/monitor/operlog.ts
@@ -0,0 +1,60 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询操作日志记录列表
+export async function getOperlogList(params?: API.Monitor.OperlogListParams) {
+  return request<API.Monitor.OperlogPageResult>('/api/monitor/operlog/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询操作日志记录详细
+export function getOperlog(operId: number) {
+  return request<API.Monitor.OperlogInfoResult>(`/api/monitor/operlog/${operId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增操作日志记录
+export async function addOperlog(params: API.Monitor.Operlog) {
+  return request<API.Result>('/api/monitor/operlog', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改操作日志记录
+export async function updateOperlog(params: API.Monitor.Operlog) {
+  return request<API.Result>('/api/monitor/operlog', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除操作日志记录
+export async function removeOperlog(ids: string) {
+  return request<API.Result>(`/api/monitor/operlog/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+export async function cleanAllOperlog() {
+  return request<API.Result>(`/api/monitor/operlog/clean`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出操作日志记录
+export function exportOperlog(params?: API.Monitor.OperlogListParams) {
+  return downLoadXlsx(`/api/monitor/operlog/export`, { params }, `operlog_${new Date().getTime()}.xlsx`);
+}
diff --git a/react-ui/src/services/monitor/server.ts b/react-ui/src/services/monitor/server.ts
new file mode 100644
index 0000000..24a849f
--- /dev/null
+++ b/react-ui/src/services/monitor/server.ts
@@ -0,0 +1,16 @@
+import { request } from '@umijs/max'; 
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+
+// 获取服务器信息
+export async function getServerInfo() {
+  return request('/api/monitor/server', {
+    method: 'GET',
+  });
+}
diff --git a/react-ui/src/services/session.ts b/react-ui/src/services/session.ts
new file mode 100644
index 0000000..8b4e0d3
--- /dev/null
+++ b/react-ui/src/services/session.ts
@@ -0,0 +1,159 @@
+import { createIcon } from '@/utils/IconUtil';
+import { MenuDataItem } from '@ant-design/pro-components';
+import { request } from '@umijs/max';
+import React, { lazy } from 'react';
+
+
+let remoteMenu: any = null;
+
+export function getRemoteMenu() {
+  return remoteMenu;
+}
+
+export function setRemoteMenu(data: any) {
+  remoteMenu = data;
+}
+
+
+function patchRouteItems(route: any, menu: any, parentPath: string) {
+  for (const menuItem of menu) {
+    if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') {
+      if (menuItem.routes) {
+        let hasItem = false;
+        let newItem = null;
+        for (const routeChild of route.routes) {
+          if (routeChild.path === menuItem.path) {
+            hasItem = true;
+            newItem = routeChild;
+          }
+        }
+        if (!hasItem) {
+          newItem = {
+            path: menuItem.path,
+            routes: [],
+            children: []
+          }
+          route.routes.push(newItem)
+        }
+        patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/');
+      }
+    } else {
+      const names: string[] = menuItem.component.split('/');
+      let path = '';
+      names.forEach(name => {
+        if (path.length > 0) {
+          path += '/';
+        }
+        if (name !== 'index') {
+          path += name.at(0)?.toUpperCase() + name.substr(1);
+        } else {
+          path += name;
+        }
+      })
+      if (!path.endsWith('.tsx')) {
+        path += '.tsx'
+      }
+      if (route.routes === undefined) {
+        route.routes = [];
+      }
+      if (route.children === undefined) {
+        route.children = [];
+      }
+      const newRoute = {
+        element: React.createElement(lazy(() => import('@/pages/' + path))),
+        path: parentPath + menuItem.path,
+      }
+      route.children.push(newRoute);
+      route.routes.push(newRoute);
+    }
+  }
+}
+
+export function patchRouteWithRemoteMenus(routes: any) {
+  if (remoteMenu === null) { return; }
+  let proLayout = null;
+  for (const routeItem of routes) {
+    if (routeItem.id === 'ant-design-pro-layout') {
+      proLayout = routeItem;
+      break;
+    }
+  }
+  patchRouteItems(proLayout, remoteMenu, '');
+}
+
+/** 获取当前的用户 GET /api/getUserInfo */
+export async function getUserInfo(options?: Record<string, any>) {
+  return request<API.UserInfoResult>('/api/getInfo', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
+
+// 刷新方法
+export async function refreshToken() {
+  return request('/api/auth/refresh', {
+    method: 'post'
+  })
+}
+
+export async function getRouters(): Promise<any> {
+  return request('/api/getRouters');
+}
+
+export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] {
+  return childrens.map((item: API.RoutersMenuItem) => {
+    return {
+      path: item.path,
+      icon: createIcon(item.meta.icon),
+      //  icon: item.meta.icon,
+      name: item.meta.title,
+      routes: item.children ? convertCompatRouters(item.children) : undefined,
+      hideChildrenInMenu: item.hidden,
+      hideInMenu: item.hidden,
+      component: item.component,
+      authority: item.perms,
+    };
+  });
+}
+
+export async function getRoutersInfo(): Promise<MenuDataItem[]> {
+  return getRouters().then((res) => {
+    if (res.code === 200) {
+      return convertCompatRouters(res.data);
+    } else {
+      return [];
+    }
+  });
+}
+
+export function getMatchMenuItem(
+  path: string,
+  menuData: MenuDataItem[] | undefined,
+): MenuDataItem[] {
+  if (!menuData) return [];
+  let items: MenuDataItem[] = [];
+  menuData.forEach((item) => {
+    if (item.path) {
+      if (item.path === path) {
+        items.push(item);
+        return;
+      }
+      if (path.length >= item.path?.length) {
+        const exp = `${item.path}/*`;
+        if (path.match(exp)) {
+          if (item.routes) {
+            const subpath = path.substr(item.path.length + 1);
+            const subItem: MenuDataItem[] = getMatchMenuItem(subpath, item.routes);
+            items = items.concat(subItem);
+          } else {
+            const paths = path.split('/');
+            if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') {
+              items.push(item);
+            }
+          }
+        }
+      }
+    }
+  });
+  return items;
+}
diff --git a/react-ui/src/services/swagger/index.ts b/react-ui/src/services/swagger/index.ts
new file mode 100644
index 0000000..83cf97c
--- /dev/null
+++ b/react-ui/src/services/swagger/index.ts
@@ -0,0 +1,12 @@
+// @ts-ignore
+/* eslint-disable */
+// API 更新时间:
+// API 唯一标识:
+import * as pet from './pet';
+import * as store from './store';
+import * as user from './user';
+export default {
+  pet,
+  store,
+  user,
+};
diff --git a/react-ui/src/services/swagger/pet.ts b/react-ui/src/services/swagger/pet.ts
new file mode 100644
index 0000000..b887475
--- /dev/null
+++ b/react-ui/src/services/swagger/pet.ts
@@ -0,0 +1,153 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Update an existing pet PUT /pet */
+export async function updatePet(body: API.Pet, options?: { [key: string]: any }) {
+  return request<any>('/pet', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Add a new pet to the store POST /pet */
+export async function addPet(body: API.Pet, options?: { [key: string]: any }) {
+  return request<any>('/pet', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Find pet by ID Returns a single pet GET /pet/${param0} */
+export async function getPetById(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.getPetByIdParams,
+  options?: { [key: string]: any },
+) {
+  const { petId: param0, ...queryParams } = params;
+  return request<API.Pet>(`/pet/${param0}`, {
+    method: 'GET',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** Updates a pet in the store with form data POST /pet/${param0} */
+export async function updatePetWithForm(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.updatePetWithFormParams,
+  body: { name?: string; status?: string },
+  options?: { [key: string]: any },
+) {
+  const { petId: param0, ...queryParams } = params;
+  const formData = new FormData();
+
+  Object.keys(body).forEach((ele) => {
+    const item = (body as any)[ele];
+
+    if (item !== undefined && item !== null) {
+      formData.append(
+        ele,
+        typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
+      );
+    }
+  });
+
+  return request<any>(`/pet/${param0}`, {
+    method: 'POST',
+    params: { ...queryParams },
+    data: formData,
+    ...(options || {}),
+  });
+}
+
+/** Deletes a pet DELETE /pet/${param0} */
+export async function deletePet(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.deletePetParams & {
+    // header
+    api_key?: string;
+  },
+  options?: { [key: string]: any },
+) {
+  const { petId: param0, ...queryParams } = params;
+  return request<any>(`/pet/${param0}`, {
+    method: 'DELETE',
+    headers: {},
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** uploads an image POST /pet/${param0}/uploadImage */
+export async function uploadFile(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.uploadFileParams,
+  body: { additionalMetadata?: string; file?: string },
+  file?: File,
+  options?: { [key: string]: any },
+) {
+  const { petId: param0, ...queryParams } = params;
+  const formData = new FormData();
+
+  if (file) {
+    formData.append('file', file);
+  }
+
+  Object.keys(body).forEach((ele) => {
+    const item = (body as any)[ele];
+
+    if (item !== undefined && item !== null) {
+      formData.append(
+        ele,
+        typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
+      );
+    }
+  });
+
+  return request<API.ApiResponse>(`/pet/${param0}/uploadImage`, {
+    method: 'POST',
+    params: { ...queryParams },
+    data: formData,
+    requestType: 'form',
+    ...(options || {}),
+  });
+}
+
+/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
+export async function findPetsByStatus(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.findPetsByStatusParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.Pet[]>('/pet/findByStatus', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
+
+/** Finds Pets by tags Muliple tags can be provided with comma separated strings. Use         tag1, tag2, tag3 for testing. GET /pet/findByTags */
+export async function findPetsByTags(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.findPetsByTagsParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.Pet[]>('/pet/findByTags', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/swagger/store.ts b/react-ui/src/services/swagger/store.ts
new file mode 100644
index 0000000..b9c689a
--- /dev/null
+++ b/react-ui/src/services/swagger/store.ts
@@ -0,0 +1,48 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
+export async function getInventory(options?: { [key: string]: any }) {
+  return request<Record<string, any>>('/store/inventory', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
+
+/** Place an order for a pet POST /store/order */
+export async function placeOrder(body: API.Order, options?: { [key: string]: any }) {
+  return request<API.Order>('/store/order', {
+    method: 'POST',
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10.         Other values will generated exceptions GET /store/order/${param0} */
+export async function getOrderById(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.getOrderByIdParams,
+  options?: { [key: string]: any },
+) {
+  const { orderId: param0, ...queryParams } = params;
+  return request<API.Order>(`/store/order/${param0}`, {
+    method: 'GET',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** Delete purchase order by ID For valid response try integer IDs with positive integer value.         Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
+export async function deleteOrder(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.deleteOrderParams,
+  options?: { [key: string]: any },
+) {
+  const { orderId: param0, ...queryParams } = params;
+  return request<any>(`/store/order/${param0}`, {
+    method: 'DELETE',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/swagger/typings.d.ts b/react-ui/src/services/swagger/typings.d.ts
new file mode 100644
index 0000000..d06bcfc
--- /dev/null
+++ b/react-ui/src/services/swagger/typings.d.ts
@@ -0,0 +1,112 @@
+declare namespace API {
+  type ApiResponse = {
+    code?: number;
+    type?: string;
+    message?: string;
+  };
+
+  type Category = {
+    id?: number;
+    name?: string;
+  };
+
+  type deleteOrderParams = {
+    /** ID of the order that needs to be deleted */
+    orderId: number;
+  };
+
+  type deletePetParams = {
+    api_key?: string;
+    /** Pet id to delete */
+    petId: number;
+  };
+
+  type deleteUserParams = {
+    /** The name that needs to be deleted */
+    username: string;
+  };
+
+  type findPetsByStatusParams = {
+    /** Status values that need to be considered for filter */
+    status: ('available' | 'pending' | 'sold')[];
+  };
+
+  type findPetsByTagsParams = {
+    /** Tags to filter by */
+    tags: string[];
+  };
+
+  type getOrderByIdParams = {
+    /** ID of pet that needs to be fetched */
+    orderId: number;
+  };
+
+  type getPetByIdParams = {
+    /** ID of pet to return */
+    petId: number;
+  };
+
+  type getUserByNameParams = {
+    /** The name that needs to be fetched. Use user1 for testing.  */
+    username: string;
+  };
+
+  type loginUserParams = {
+    /** The user name for login */
+    username: string;
+    /** The password for login in clear text */
+    password: string;
+  };
+
+  type Order = {
+    id?: number;
+    petId?: number;
+    quantity?: number;
+    shipDate?: string;
+    /** Order Status */
+    status?: 'placed' | 'approved' | 'delivered';
+    complete?: boolean;
+  };
+
+  type Pet = {
+    id?: number;
+    category?: Category;
+    name: string;
+    photoUrls: string[];
+    tags?: Tag[];
+    /** pet status in the store */
+    status?: 'available' | 'pending' | 'sold';
+  };
+
+  type Tag = {
+    id?: number;
+    name?: string;
+  };
+
+  type updatePetWithFormParams = {
+    /** ID of pet that needs to be updated */
+    petId: number;
+  };
+
+  type updateUserParams = {
+    /** name that need to be updated */
+    username: string;
+  };
+
+  type uploadFileParams = {
+    /** ID of pet to update */
+    petId: number;
+  };
+
+  type User = {
+    id?: number;
+    username?: string;
+    firstName?: string;
+    lastName?: string;
+    email?: string;
+    password?: string;
+    phone?: string;
+    /** User Status */
+    userStatus?: number;
+  };
+}
diff --git a/react-ui/src/services/swagger/user.ts b/react-ui/src/services/swagger/user.ts
new file mode 100644
index 0000000..4dd6f42
--- /dev/null
+++ b/react-ui/src/services/swagger/user.ts
@@ -0,0 +1,100 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** Create user This can only be done by the logged in user. POST /user */
+export async function createUser(body: API.User, options?: { [key: string]: any }) {
+  return request<any>('/user', {
+    method: 'POST',
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Get user by user name GET /user/${param0} */
+export async function getUserByName(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.getUserByNameParams,
+  options?: { [key: string]: any },
+) {
+  const { username: param0, ...queryParams } = params;
+  return request<API.User>(`/user/${param0}`, {
+    method: 'GET',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
+export async function updateUser(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.updateUserParams,
+  body: API.User,
+  options?: { [key: string]: any },
+) {
+  const { username: param0, ...queryParams } = params;
+  return request<any>(`/user/${param0}`, {
+    method: 'PUT',
+    params: { ...queryParams },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
+export async function deleteUser(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.deleteUserParams,
+  options?: { [key: string]: any },
+) {
+  const { username: param0, ...queryParams } = params;
+  return request<any>(`/user/${param0}`, {
+    method: 'DELETE',
+    params: { ...queryParams },
+    ...(options || {}),
+  });
+}
+
+/** Creates list of users with given input array POST /user/createWithArray */
+export async function createUsersWithArrayInput(
+  body: API.User[],
+  options?: { [key: string]: any },
+) {
+  return request<any>('/user/createWithArray', {
+    method: 'POST',
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Creates list of users with given input array POST /user/createWithList */
+export async function createUsersWithListInput(body: API.User[], options?: { [key: string]: any }) {
+  return request<any>('/user/createWithList', {
+    method: 'POST',
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** Logs user into the system GET /user/login */
+export async function loginUser(
+  // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
+  params: API.loginUserParams,
+  options?: { [key: string]: any },
+) {
+  return request<string>('/user/login', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    ...(options || {}),
+  });
+}
+
+/** Logs out current logged in user session GET /user/logout */
+export async function logoutUser(options?: { [key: string]: any }) {
+  return request<any>('/user/logout', {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
diff --git a/react-ui/src/services/system/auth.ts b/react-ui/src/services/system/auth.ts
new file mode 100644
index 0000000..b757618
--- /dev/null
+++ b/react-ui/src/services/system/auth.ts
@@ -0,0 +1,39 @@
+import { request } from '@umijs/max';
+
+export async function getCaptchaImg(params?: Record<string, any>, options?: Record<string, any>) {
+  return request('/api/captchaImage', {
+    method: 'GET',
+    params: {
+      ...params,
+    },
+    headers: {
+      isToken: false,
+    },
+    ...(options || {}),
+  });
+}
+
+/** 登录接口 POST /api/login/account */
+export async function login(body: API.LoginParams, options?: Record<string, any>) {
+  return request<API.LoginResult>('/api/login', {
+    method: 'POST',
+    headers: {
+      isToken: false,
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}
+
+/** 退出登录接口 POST /api/login/outLogin */
+export async function logout() {
+  return request<Record<string, any>>('/api/logout', {
+    method: 'delete',
+  });
+}
+
+// 获取手机验证码
+export async function getMobileCaptcha(mobile: string) {
+  return request(`/api/login/captcha?mobile=${mobile}`);
+}
diff --git a/react-ui/src/services/system/config.ts b/react-ui/src/services/system/config.ts
new file mode 100644
index 0000000..9843daf
--- /dev/null
+++ b/react-ui/src/services/system/config.ts
@@ -0,0 +1,62 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询参数配置列表
+export async function getConfigList(params?: API.System.ConfigListParams) {
+  return request<API.System.ConfigPageResult>('/api/system/config/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询参数配置详细
+export function getConfig(configId: number) {
+  return request<API.System.ConfigInfoResult>(`/api/system/config/${configId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增参数配置
+export async function addConfig(params: API.System.Config) {
+  return request<API.Result>('/api/system/config', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改参数配置
+export async function updateConfig(params: API.System.Config) {
+  return request<API.Result>('/api/system/config', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除参数配置
+export async function removeConfig(ids: string) {
+  return request<API.Result>(`/api/system/config/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出参数配置
+export function exportConfig(params?: API.System.ConfigListParams) {
+  return downLoadXlsx(`/api/system/config/export`, { params }, `config_${new Date().getTime()}.xlsx`);
+}
+
+
+// 刷新参数缓存
+export function refreshConfigCache() {
+  return request<API.Result>('/api/system/config/refreshCache', {
+    method: 'delete'
+  })
+}
diff --git a/react-ui/src/services/system/dept.ts b/react-ui/src/services/system/dept.ts
new file mode 100644
index 0000000..ad58807
--- /dev/null
+++ b/react-ui/src/services/system/dept.ts
@@ -0,0 +1,56 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询部门列表
+export async function getDeptList(params?: API.System.DeptListParams) {
+  return request<API.System.DeptPageResult>('/api/system/dept/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询部门列表(排除节点)
+export function getDeptListExcludeChild(deptId: number) {
+  return request(`/api/system/dept/list/exclude/${deptId}`, {
+    method: 'get',
+  });
+}
+
+// 查询部门详细
+export function getDept(deptId: number) {
+  return request<API.System.DeptInfoResult>(`/api/system/dept/${deptId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增部门
+export async function addDept(params: API.System.Dept) {
+  return request<API.Result>('/api/system/dept', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改部门
+export async function updateDept(params: API.System.Dept) {
+  return request<API.Result>('/api/system/dept', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除部门
+export async function removeDept(ids: string) {
+  return request<API.Result>(`/api/system/dept/${ids}`, {
+    method: 'DELETE'
+  });
+}
diff --git a/react-ui/src/services/system/dict.ts b/react-ui/src/services/system/dict.ts
new file mode 100644
index 0000000..5671124
--- /dev/null
+++ b/react-ui/src/services/system/dict.ts
@@ -0,0 +1,120 @@
+import { request } from '@umijs/max';
+import { DictValueEnumObj } from '@/components/DictTag';
+import { HttpResult } from '@/enums/httpEnum';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ *
+ * */
+
+// 查询字典类型列表
+export async function getDictTypeList(params?: API.DictTypeListParams) {
+  return request(`/api/system/dict/type/list`, {
+    params: {
+      ...params,
+    },
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
+
+// 查询字典类型详细
+export function getDictType(dictId: string) {
+  return request(`/api/system/dict/type/${dictId}`, {
+    method: 'GET',
+  });
+}
+
+// 查询字典数据详细
+export async function getDictValueEnum(dictType: string, isDigital?: boolean): Promise<DictValueEnumObj> {
+  const resp = await request<API.System.DictTypeResult>(`/api/system/dict/data/type/${dictType}`, {
+    method: 'GET',
+  });
+  if(resp.code === HttpResult.SUCCESS) {
+    const opts: DictValueEnumObj = {};
+    resp.data.forEach((item: any) => {
+      opts[item.dictValue] = {
+        text: item.dictLabel,
+        label: item.dictLabel,
+        value: isDigital ? Number(item.dictValue) : item.dictValue,
+        key: item.dictCode,
+        listClass: item.listClass,
+        status: item.listClass };
+    });
+    return opts;
+  } else {
+    return {};
+  }
+}
+
+export async function getDictSelectOption(dictType: string, isDigital?: boolean) {
+  const resp = await request<API.System.DictTypeResult>(`/api/system/dict/data/type/${dictType}`, {
+    method: 'GET',
+  });
+  if (resp.code === 200) {
+    const options: DictValueEnumObj[] = resp.data.map((item) => {
+      return {
+        text: item.dictLabel,
+        label: item.dictLabel,
+        value: isDigital ? Number(item.dictValue) : item.dictValue,
+        key: item.dictCode,
+        listClass: item.listClass,
+        status: item.listClass
+      };
+    });
+    return options;
+  }
+  return [];
+};
+
+// 新增字典类型
+export async function addDictType(params: API.System.DictType) {
+  return request<API.Result>('/api/system/dict/type', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改字典类型
+export async function updateDictType(params: API.System.DictType) {
+  return request<API.Result>('/api/system/dict/type', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除字典类型
+export async function removeDictType(ids: string) {
+  return request<API.Result>(`/api/system/dict/type/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出字典类型
+export function exportDictType(params?: API.System.DictTypeListParams) {
+  return downLoadXlsx(`/api/system/dict/type/export`, { params }, `dict_type_${new Date().getTime()}.xlsx`);
+}
+
+// 获取字典选择框列表
+export async function getDictTypeOptionSelect(params?: API.DictTypeListParams) {
+  return request('/api/system/dict/type/optionselect', {
+    params: {
+      ...params,
+    },
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+  });
+}
diff --git a/react-ui/src/services/system/dictdata.ts b/react-ui/src/services/system/dictdata.ts
new file mode 100644
index 0000000..f7856e8
--- /dev/null
+++ b/react-ui/src/services/system/dictdata.ts
@@ -0,0 +1,65 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询字典数据列表
+export async function getDictDataList(
+  params?: API.System.DictDataListParams,
+  options?: { [key: string]: any },
+) {
+  return request<API.System.DictDataPageResult>('/api/system/dict/data/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params,
+    ...(options || {}),
+  });
+}
+
+// 查询字典数据详细
+export function getDictData(dictCode: number, options?: { [key: string]: any }) {
+  return request<API.System.DictDataInfoResult>(`/api/system/dict/data/${dictCode}`, {
+    method: 'GET',
+    ...(options || {}),
+  });
+}
+
+// 新增字典数据
+export async function addDictData(params: API.System.DictData, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/dict/data', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {}),
+  });
+}
+
+// 修改字典数据
+export async function updateDictData(params: API.System.DictData, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/dict/data', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {}),
+  });
+}
+
+// 删除字典数据
+export async function removeDictData(ids: string, options?: { [key: string]: any }) {
+  return request<API.Result>(`/api/system/dict/data/${ids}`, {
+    method: 'DELETE',
+    ...(options || {}),
+  });
+}
+
+// 导出字典数据
+export function exportDictData(
+  params?: API.System.DictDataListParams,
+  options?: { [key: string]: any },
+) {
+  return downLoadXlsx(`/api/system/dict/data/export`, { params }, `dict_data_${new Date().getTime()}.xlsx`);
+}
diff --git a/react-ui/src/services/system/index.ts b/react-ui/src/services/system/index.ts
new file mode 100644
index 0000000..24ce62d
--- /dev/null
+++ b/react-ui/src/services/system/index.ts
@@ -0,0 +1,14 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+
+import * as Auth from './auth';
+import * as User from './User';
+import * as Dict from './dict';
+import * as Menu from './menu';
+
+export default {
+  Auth,
+  User,
+  Dict,
+  Menu,
+};
diff --git a/react-ui/src/services/system/menu.ts b/react-ui/src/services/system/menu.ts
new file mode 100644
index 0000000..b2f411b
--- /dev/null
+++ b/react-ui/src/services/system/menu.ts
@@ -0,0 +1,61 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询菜单权限列表
+export async function getMenuList(params?: API.System.MenuListParams, options?: { [key: string]: any }) {
+  return request<API.System.MenuPageResult>('/api/system/menu/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params,
+    ...(options || {}),
+  });
+}
+
+// 查询菜单权限详细
+export function getMenu(menuId: number, options?: { [key: string]: any }) {
+  return request<API.System.MenuInfoResult>(`/api/system/menu/${menuId}`, {
+    method: 'GET',
+    ...(options || {})
+  });
+}
+
+// 新增菜单权限
+export async function addMenu(params: API.System.Menu, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/menu', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {})
+  });
+}
+
+// 修改菜单权限
+export async function updateMenu(params: API.System.Menu, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/menu', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {})
+  });
+}
+
+// 删除菜单权限
+export async function removeMenu(ids: string, options?: { [key: string]: any }) {
+  return request<API.Result>(`/api/system/menu/${ids}`, {
+    method: 'DELETE',
+    ...(options || {})
+  });
+}
+
+// 查询菜单权限详细
+export function getMenuTree() {
+  return request('/api/system/menu/treeselect', {
+    method: 'GET',
+  });
+}
diff --git a/react-ui/src/services/system/notice.ts b/react-ui/src/services/system/notice.ts
new file mode 100644
index 0000000..2c86a08
--- /dev/null
+++ b/react-ui/src/services/system/notice.ts
@@ -0,0 +1,49 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询通知公告列表
+export async function getNoticeList(params?: API.System.NoticeListParams) {
+  return request<API.System.NoticePageResult>('/api/system/notice/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询通知公告详细
+export function getNotice(noticeId: number) {
+  return request<API.System.NoticeInfoResult>(`/api/system/notice/${noticeId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增通知公告
+export async function addNotice(params: API.System.Notice) {
+  return request<API.Result>('/api/system/notice', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改通知公告
+export async function updateNotice(params: API.System.Notice) {
+  return request<API.Result>('/api/system/notice', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除通知公告
+export async function removeNotice(ids: string) {
+  return request<API.Result>(`/api/system/notice/${ids}`, {
+    method: 'DELETE'
+  });
+}
diff --git a/react-ui/src/services/system/post.ts b/react-ui/src/services/system/post.ts
new file mode 100644
index 0000000..3170b89
--- /dev/null
+++ b/react-ui/src/services/system/post.ts
@@ -0,0 +1,54 @@
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询岗位信息列表
+export async function getPostList(params?: API.System.PostListParams) {
+  return request<API.System.PostPageResult>('/api/system/post/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params
+  });
+}
+
+// 查询岗位信息详细
+export function getPost(postId: number) {
+  return request<API.System.PostInfoResult>(`/api/system/post/${postId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增岗位信息
+export async function addPost(params: API.System.Post) {
+  return request<API.Result>('/api/system/post', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改岗位信息
+export async function updatePost(params: API.System.Post) {
+  return request<API.Result>('/api/system/post', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除岗位信息
+export async function removePost(ids: string) {
+  return request<API.Result>(`/api/system/post/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出岗位信息
+export function exportPost(params?: API.System.PostListParams) {
+  return downLoadXlsx(`/api/system/post/export`, { params }, `post_${new Date().getTime()}.xlsx`);
+}
diff --git a/react-ui/src/services/system/role.ts b/react-ui/src/services/system/role.ts
new file mode 100644
index 0000000..3836243
--- /dev/null
+++ b/react-ui/src/services/system/role.ts
@@ -0,0 +1,128 @@
+import { ContentType } from '@/enums/httpEnum';
+import { request } from '@umijs/max';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询角色信息列表
+export async function getRoleList(params?: API.System.RoleListParams) {
+  return request<API.System.RolePageResult>('/api/system/role/list', {
+    method: 'GET',
+    headers: { 'Content-Type': ContentType.FORM_URLENCODED },
+    params
+  });
+}
+
+// 查询角色信息详细
+export function getRole(roleId: number) {
+  return request<API.System.RoleInfoResult>(`/api/system/role/${roleId}`, {
+    method: 'GET'
+  });
+}
+
+// 新增角色信息
+export async function addRole(params: API.System.Role) {
+  return request<API.Result>('/api/system/role', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 修改角色信息
+export async function updateRole(params: API.System.Role) {
+  return request<API.Result>('/api/system/role', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params
+  });
+}
+
+// 删除角色信息
+export async function removeRole(ids: string) {
+  return request<API.Result>(`/api/system/role/${ids}`, {
+    method: 'DELETE'
+  });
+}
+
+// 导出角色信息
+export function exportRole(params?: API.System.RoleListParams) {
+  return downLoadXlsx(`/api/system/role/export`, { params }, `role_${new Date().getTime()}.xlsx`);
+}
+
+// 获取角色菜单列表
+export function getRoleMenuList(id: number) {
+  return request<API.System.RoleMenuResult>(`/api/system/menu/roleMenuTreeselect/${id}`, {
+    method: 'get',
+  });
+}
+
+// 角色数据权限
+export function updateRoleDataScope(data: Record<string, any>) {
+  return request('/api/system/role/dataScope', {
+    method: 'put',
+    data
+  })
+}
+
+// 角色状态修改
+export function changeRoleStatus(roleId: number, status: string) {
+  const data = {
+    roleId,
+    status
+  }
+  return request<API.Result>('/api/system/role/changeStatus', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 查询角色已授权用户列表
+export function allocatedUserList(params?: API.System.RoleListParams) {
+  return request('/api/system/role/authUser/allocatedList', {
+    method: 'get',
+    params
+  })
+}
+
+// 查询角色未授权用户列表
+export function unallocatedUserList(params?: API.System.RoleListParams) {
+  return request('/api/system/role/authUser/unallocatedList', {
+    method: 'get',
+    params
+  })
+}
+
+// 取消用户授权角色
+export function authUserCancel(data: any) {
+  return request<API.Result>('/api/system/role/authUser/cancel', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 批量取消用户授权角色
+export function authUserCancelAll(data: any) {
+  return request<API.Result>('/api/system/role/authUser/cancelAll', {
+    method: 'put',
+    params: data
+  })
+}
+
+// 授权用户选择
+export function authUserSelectAll(data: Record<string, any>) {
+  return request<API.Result>('/api/system/role/authUser/selectAll', {
+    method: 'put',
+    params: data,
+    headers: { 'Content-Type': ContentType.FORM_URLENCODED },
+  })
+}
+
+// 根据角色ID查询部门树结构
+export function getDeptTreeSelect(roleId: number) {
+  return request('/api/system/role/deptTree/' + roleId, {
+    method: 'get'
+  })
+}
diff --git a/react-ui/src/services/system/user.ts b/react-ui/src/services/system/user.ts
new file mode 100644
index 0000000..da997bf
--- /dev/null
+++ b/react-ui/src/services/system/user.ts
@@ -0,0 +1,152 @@
+import { formatTreeData } from '@/utils/tree';
+import { request } from '@umijs/max';
+import { DataNode } from 'antd/es/tree';
+import { downLoadXlsx } from '@/utils/downloadfile';
+
+// 查询用户信息列表
+export async function getUserList(params?: API.System.UserListParams, options?: { [key: string]: any }) {
+  return request<API.System.UserPageResult>('/api/system/user/list', {
+    method: 'GET',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    params,
+    ...(options || {})
+  });
+}
+
+// 查询用户信息详细
+export function getUser(userId: number, options?: { [key: string]: any }) {
+  return request<API.System.UserInfoResult>(`/api/system/user/${userId}`, {
+    method: 'GET',
+    ...(options || {})
+  });
+}
+
+// 新增用户信息
+export async function addUser(params: API.System.User, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/user', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {})
+  });
+}
+
+// 修改用户信息
+export async function updateUser(params: API.System.User, options?: { [key: string]: any }) {
+  return request<API.Result>('/api/system/user', {
+    method: 'PUT',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8',
+    },
+    data: params,
+    ...(options || {})
+  });
+}
+
+// 删除用户信息
+export async function removeUser(ids: string, options?: { [key: string]: any }) {
+  return request<API.Result>(`/api/system/user/${ids}`, {
+    method: 'DELETE',
+    ...(options || {})
+  });
+}
+
+// 导出用户信息
+export function exportUser(params?: API.System.UserListParams, options?: { [key: string]: any }) {
+  return downLoadXlsx(`/api/system/user/export`, { params }, `user_${new Date().getTime()}.xlsx`);
+}
+
+// 用户状态修改
+export function changeUserStatus(userId: number, status: string) {
+  const data = {
+    userId,
+    status
+  }
+  return request<API.Result>('/api/system/user/changeStatus', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 查询用户个人信息
+export function getUserProfile() {
+  return request('/api/system/user/profile', {
+    method: 'get'
+  })
+}
+
+export function updateUserProfile(data: API.CurrentUser) {
+  return request<API.Result>('/api/system/user/profile', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 用户密码重置
+export function resetUserPwd(userId: number, password: string) {
+  const data = {
+    userId,
+    password
+  }
+  return request<API.Result>('/api/system/user/resetPwd', {
+    method: 'put',
+    data: data
+  })
+}
+
+// 用户t个人密码重置
+export function updateUserPwd(oldPassword: string, newPassword: string) {
+  const data = {
+    oldPassword,
+    newPassword
+  }
+  return request<API.Result>('/api/system/user/profile/updatePwd', {
+    method: 'put',
+    params: data
+  })
+}
+
+// 用户头像上传
+export function uploadAvatar(data: any) {
+  return request('/api/system/user/profile/avatar', {
+    method: 'post',
+    data: data
+  })
+}
+
+
+// 查询授权角色
+export function getAuthRole(userId: number) {
+  return request('/system/user/authRole/' + userId, {
+    method: 'get'
+  })
+}
+
+// 保存授权角色
+export function updateAuthRole(data: Record<string, any>) {
+  return request('/system/user/authRole', {
+    method: 'put',
+    params: data
+  })
+}
+
+// 获取数据列表
+export function getDeptTree(params: any): Promise<DataNode[]> {
+  return new Promise((resolve) => {
+    request(`/api/system/user/deptTree`, {
+      method: 'get',
+      params,
+    }).then((res: any) => {
+      if (res && res.code === 200) {
+        const treeData = formatTreeData(res.data);
+        resolve(treeData);
+      } else {
+        resolve([]);
+      }
+    });
+  });
+}
diff --git a/react-ui/src/services/typings.d.ts b/react-ui/src/services/typings.d.ts
new file mode 100644
index 0000000..1e0b8b5
--- /dev/null
+++ b/react-ui/src/services/typings.d.ts
@@ -0,0 +1,191 @@
+/* eslint-disable */
+// 该文件由 OneAPI 自动生成,请勿手动修改!
+
+declare namespace API {
+  interface PageInfo {
+    current?: number;
+    pageSize?: number;
+    total?: number;
+    list?: Array<Record<string, any>>;
+  }
+
+  interface PageInfo_UserInfo_ {
+    current?: number;
+    pageSize?: number;
+    total?: number;
+    list?: Array<UserInfo>;
+  }
+
+  interface Result {
+    code: number;
+    msg: string;
+    data?: Record<string, any>;
+  }
+
+  interface Result_PageInfo_UserInfo__ {
+    code: number;
+    msg: string;
+    data?: PageInfo_UserInfo_;
+  }
+  interface UserInfoResult {
+    code?: number;
+    msg?: string;
+    user: UserInfo;
+    permissions: any;
+    roles: any;
+  }
+
+  interface Result_string_ {
+    success?: boolean;
+    errorMessage?: string;
+    data?: string;
+  }
+
+  type UserGenderEnum = 'MALE' | 'FEMALE';
+
+  interface UserInfo {
+    userId?: string;
+    userName?: string;
+    nickName?: string;
+    avatar?: string;
+    sex?: string;
+    email?: string;
+    gender?: UserGenderEnum;
+    unreadCount: number;
+    address?: string;
+    phonenumber?: string;
+    dept?: Dept;
+    roles?: Role[];
+    permissions: string[];
+  }
+
+  interface UserInfoVO {
+    name?: string;
+    /** nick */
+    nickName?: string;
+    /** email */
+    email?: string;
+  }
+
+  type definitions_0 = null;
+
+  type MenuItemMeta = {
+    title: string;
+    icon: string;
+    noCache: boolean;
+    link: string;
+  };
+
+  type RoutersMenuItem = {
+    alwaysShow?: boolean;
+    children?: RoutersMenuItem[];
+    component?: string;
+    hidden?: boolean;
+    meta: MenuItemMeta;
+    name: string;
+    path: string;
+    redirect?: string;
+    [key: string]: any;
+  };
+  interface GetRoutersResult {
+    code: number;
+    msg: string;
+    data: RoutersMenuItem[];
+  }
+
+  type NoticeIconList = {
+    data?: NoticeIconItem[];
+    /** 列表的内容总数 */
+    total?: number;
+    success?: boolean;
+  };
+
+  type NoticeIconItemType = 'notification' | 'message' | 'event';
+
+  type NoticeIconItem = {
+    id?: string;
+    extra?: string;
+    key?: string;
+    read?: boolean;
+    avatar?: string;
+    title?: string;
+    status?: string;
+    datetime?: string;
+    description?: string;
+    type?: NoticeIconItemType;
+  };
+
+  export type MenuType = {
+    menuId: number;
+    menuName: string;
+    parentId: string;
+    orderNum: number;
+    path: string;
+    component: string;
+    isFrame: number;
+    isCache: number;
+    menuType: string;
+    visible: string;
+    status: string;
+    perms: string;
+    icon: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  };
+
+  export type MenuListParams = {
+    menuId?: string;
+    menuName?: string;
+    parentId?: string;
+    orderNum?: string;
+    path?: string;
+    component?: string;
+    isFrame?: string;
+    isCache?: string;
+    menuType?: string;
+    visible?: string;
+    status?: string;
+    perms?: string;
+    icon?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  };
+
+  export type DictTypeType = {
+    dictId: number;
+    dictName: string;
+    dictType: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  };
+
+  export type DictTypeListParams = {
+    dictId?: string;
+    dictName?: string;
+    dictType?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  };
+}
diff --git a/react-ui/src/types/monitor/cache.d.ts b/react-ui/src/types/monitor/cache.d.ts
new file mode 100644
index 0000000..bfbab1a
--- /dev/null
+++ b/react-ui/src/types/monitor/cache.d.ts
@@ -0,0 +1,153 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2021/09/16
+ * 
+ * */
+declare namespace API.Monitor {
+
+  export type CommandInfo = {
+    name: string;
+    value: string;
+  };
+
+  export type CacheRuntimeInfo = {
+    active_defrag_hits: string;
+    active_defrag_key_hits: string;
+    active_defrag_key_misses: string;
+    active_defrag_misses: string;
+    active_defrag_running: string;
+    allocator_active: string;
+    allocator_allocated: string;
+    allocator_frag_bytes: string;
+    allocator_frag_ratio: string;
+    allocator_resident: string;
+    allocator_rss_bytes: string;
+    allocator_rss_ratio: string;
+    aof_current_rewrite_time_sec: string;
+    aof_enabled: string;
+    aof_last_bgrewrite_status: string;
+    aof_last_cow_size: string;
+    aof_last_rewrite_time_sec: string;
+    aof_last_write_status: string;
+    aof_rewrite_in_progress: string;
+    aof_rewrite_scheduled: string;
+    arch_bits: string;
+    atomicvar_api: string;
+    blocked_clients: string;
+    client_recent_max_input_buffer: string;
+    client_recent_max_output_buffer: string;
+    cluster_enabled: string;
+    config_file: string;
+    configured_hz: string;
+    connected_clients: string;
+    connected_slaves: string;
+    db0: string;
+    db1: string;
+    evicted_keys: string;
+    executable: string;
+    expired_keys: string;
+    expired_stale_perc: string;
+    expired_time_cap_reached_count: string;
+    gcc_version: string;
+    hz: string;
+    instantaneous_input_kbps: number;
+    instantaneous_ops_per_sec: string;
+    instantaneous_output_kbps: number;
+    keyspace_hits: string;
+    keyspace_misses: string;
+    latest_fork_usec: string;
+    lazyfree_pending_objects: string;
+    loading: string;
+    lru_clock: string;
+    master_repl_offset: string;
+    master_replid: string;
+    master_replid2: string;
+    maxmemory: string;
+    maxmemory_human: string;
+    maxmemory_policy: string;
+    mem_allocator: string;
+    mem_aof_buffer: string;
+    mem_clients_normal: string;
+    mem_clients_slaves: string;
+    mem_fragmentation_bytes: string;
+    mem_fragmentation_ratio: string;
+    mem_not_counted_for_evict: string;
+    mem_replication_backlog: string;
+    migrate_cached_sockets: string;
+    multiplexing_api: string;
+    number_of_cached_scripts: string;
+    os: string;
+    process_id: string;
+    pubsub_channels: string;
+    pubsub_patterns: string;
+    rdb_bgsave_in_progress: string;
+    rdb_changes_since_last_save: string;
+    rdb_current_bgsave_time_sec: string;
+    rdb_last_bgsave_status: string;
+    rdb_last_bgsave_time_sec: string;
+    rdb_last_cow_size: string;
+    rdb_last_save_time: string;
+    redis_build_id: string;
+    redis_git_dirty: string;
+    redis_git_sha1: string;
+    redis_mode: string;
+    redis_version: string;
+    rejected_connections: string;
+    repl_backlog_active: string;
+    repl_backlog_first_byte_offset: string;
+    repl_backlog_histlen: string;
+    repl_backlog_size: string;
+    role: string;
+    rss_overhead_bytes: string;
+    rss_overhead_ratio: string;
+    run_id: string;
+    second_repl_offset: string;
+    slave_expires_tracked_keys: string;
+    sync_full: string;
+    sync_partial_err: string;
+    sync_partial_ok: string;
+    tcp_port: string;
+    total_commands_processed: string;
+    total_connections_received: string;
+    total_net_input_bytes: string;
+    total_net_output_bytes: string;
+    total_system_memory: number;
+    total_system_memory_human: string;
+    uptime_in_days: string;
+    uptime_in_seconds: string;
+    used_cpu_sys: string;
+    used_cpu_sys_children: string;
+    used_cpu_user: string;
+    used_cpu_user_children: number;
+    used_memory: number;
+    used_memory_dataset: number;
+    used_memory_dataset_perc: string;
+    used_memory_human: string;
+    used_memory_lua: string;
+    used_memory_lua_human: string;
+    used_memory_overhead: string;
+    used_memory_peak: string;
+    used_memory_peak_human: string;
+    used_memory_peak_perc: string;
+    used_memory_rss: string;
+    used_memory_rss_human: string;
+    used_memory_scripts: string;
+    used_memory_scripts_human: string;
+    used_memory_startup: string;
+  };
+
+  export type CacheInfo = {
+    commandStats: CommandInfo[];
+    dbSize: number;
+    info: CacheRuntimeInfo;
+  };
+
+  export type CacheInfoResult = {
+    data: CacheInfo;
+    code: number;
+    msg: string;
+  };
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/cacheList.d.ts b/react-ui/src/types/monitor/cacheList.d.ts
new file mode 100644
index 0000000..e2de8cd
--- /dev/null
+++ b/react-ui/src/types/monitor/cacheList.d.ts
@@ -0,0 +1,36 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2022/06/27
+ * 
+ * */
+
+declare namespace API.Monitor {
+
+  export type CacheContent = {
+    cacheKey: string;
+    cacheName: string;
+    cacheValue: string;
+    remark: string;
+  };
+
+  export type CacheNamesResponse = {
+    data: CacheContent[];
+    code: number;
+    msg: string;
+  };
+
+  export type CacheKeysResponse = {
+    data: string[];
+    code: number;
+    msg: string;
+  };
+
+  export type CacheValueResponse = {
+    data: CacheContent;
+    code: number;
+    msg: string;
+  };
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/job.d.ts b/react-ui/src/types/monitor/job.d.ts
new file mode 100644
index 0000000..5b1c965
--- /dev/null
+++ b/react-ui/src/types/monitor/job.d.ts
@@ -0,0 +1,58 @@
+/**
+ * 定时任务调度 Model Declare
+ * 
+ * @author whiteshader@163.com
+ * @date 2023-02-07
+ */
+
+declare namespace API.Monitor {
+
+  export interface Job {
+    jobId: number;
+    jobName: string;
+    jobGroup: string;
+    invokeTarget: string;
+    cronExpression: string;
+    misfirePolicy: string;
+    concurrent: string;
+    nextValidTime: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface JobListParams {
+    jobId?: string;
+    jobName?: string;
+    jobGroup?: string;
+    invokeTarget?: string;
+    cronExpression?: string;
+    misfirePolicy?: string;
+    concurrent?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface JobInfoResult { 
+    code: number;
+    msg: string;
+    data: Job;
+  } 
+
+   export interface JobPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Job>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/jobLog.d.ts b/react-ui/src/types/monitor/jobLog.d.ts
new file mode 100644
index 0000000..e62cb9f
--- /dev/null
+++ b/react-ui/src/types/monitor/jobLog.d.ts
@@ -0,0 +1,47 @@
+/**
+ * 定时任务调度日志 Model Declare
+ * 
+ * @author whiteshader
+ * @date 2023-02-07
+ */
+
+declare namespace API.Monitor {
+
+  export interface JobLog {
+    jobLogId: number;
+    jobName: string;
+    jobGroup: string;
+    invokeTarget: string;
+    jobMessage: string;
+    status: string;
+    exceptionInfo: string;
+    createTime: Date;
+  }
+
+  export interface JobLogListParams {
+    jobLogId?: string;
+    jobName?: string;
+    jobGroup?: string;
+    invokeTarget?: string;
+    jobMessage?: string;
+    status?: string;
+    exceptionInfo?: string;
+    createTime?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface JobLogInfoResult { 
+    code: number;
+    msg: string;
+    data: JobLog;
+  } 
+
+   export interface JobLogPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<JobLog>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/logininfor.d.ts b/react-ui/src/types/monitor/logininfor.d.ts
new file mode 100644
index 0000000..ef9208a
--- /dev/null
+++ b/react-ui/src/types/monitor/logininfor.d.ts
@@ -0,0 +1,43 @@
+
+declare namespace API.Monitor {
+
+  export interface Logininfor {
+    infoId: number;
+    userName: string;
+    ipaddr: string;
+    loginLocation: string;
+    browser: string;
+    os: string;
+    status: string;
+    msg: string;
+    loginTime: Date;
+  }
+
+  export interface LogininforListParams {
+    infoId?: string;
+    userName?: string;
+    ipaddr?: string;
+    loginLocation?: string;
+    browser?: string;
+    os?: string;
+    status?: string;
+    msg?: string;
+    loginTime?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface LogininforInfoResult { 
+    code: number;
+    msg: string;
+    data: Logininfor;
+  } 
+
+   export interface LogininforPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Logininfor>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/online.d.ts b/react-ui/src/types/monitor/online.d.ts
new file mode 100644
index 0000000..5b8e0dd
--- /dev/null
+++ b/react-ui/src/types/monitor/online.d.ts
@@ -0,0 +1,55 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+declare namespace API.Monitor {
+
+  export type OnlineUserType = {
+    tokenId: string;
+    userName: string;
+    ipaddr: string;
+    loginLocation: string;
+    browser: string;
+    os: string;
+    deptName: string;
+    loginTime: string;
+  };
+
+  export type OnlineUserListPagination = {
+    total: number;
+    pageSize: number;
+    current: number;
+  };
+
+  export type OnlineUserListData = {
+    list: OnlineUserType[];
+    pagination: Partial<OnlineUserListPagination>;
+  };
+
+  export type OnlineUserListParams = {
+    tokenId?: string;
+    userName?: string;
+    ipaddr?: string;
+    loginLocation?: string;
+    browser?: string;
+    os?: string;
+    deptName?: string;
+    loginTime?: string;
+    pageSize?: string;
+    current?: string;
+    pageNum?: string;
+    filter?: string;
+    sorter?: string;
+  };  
+
+  export interface OnlineUserPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<OnlineUser>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/operlog.d.ts b/react-ui/src/types/monitor/operlog.d.ts
new file mode 100644
index 0000000..96ee27e
--- /dev/null
+++ b/react-ui/src/types/monitor/operlog.d.ts
@@ -0,0 +1,57 @@
+
+declare namespace API.Monitor {
+
+  export interface Operlog {
+    operId: number;
+    title: string;
+    businessType: number;
+    method: string;
+    requestMethod: string;
+    operatorType: number;
+    operName: string;
+    deptName: string;
+    operUrl: string;
+    operIp: string;
+    operLocation: string;
+    operParam: string;
+    jsonResult: string;
+    status: number;
+    errorMsg: string;
+    operTime: Date;
+  }
+
+  export interface OperlogListParams {
+    operId?: string;
+    title?: string;
+    businessType?: string;
+    method?: string;
+    requestMethod?: string;
+    operatorType?: string;
+    operName?: string;
+    deptName?: string;
+    operUrl?: string;
+    operIp?: string;
+    operLocation?: string;
+    operParam?: string;
+    jsonResult?: string;
+    status?: string;
+    errorMsg?: string;
+    operTime?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface OperlogInfoResult { 
+    code: number;
+    msg: string;
+    data: Operlog;
+  } 
+
+   export interface OperlogPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Operlog>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/monitor/server.d.ts b/react-ui/src/types/monitor/server.d.ts
new file mode 100644
index 0000000..8a32d16
--- /dev/null
+++ b/react-ui/src/types/monitor/server.d.ts
@@ -0,0 +1,84 @@
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime  2023/02/07
+ * 
+ * */
+
+
+declare namespace API.Monitor {
+
+  export type CpuInfoType = {
+    cpuNum: string;
+    total: string;
+    sys: string;
+    used: string;
+    wait: string;
+    free: string;
+  };
+
+  export type MemInfoType = {
+    total: string;
+    used: string;
+    usage: string;
+    free: string;
+  };
+
+  export type SysInfoType = {
+    computerIp: string;
+    computerName: string;
+    osArch: string;
+    osName: string;
+    userDir: string;
+  };
+
+  export type JvmInfoType = {
+    free: string;
+    home: string;
+    max: string;
+    name: string;
+    runTime: string;
+    startTime: string;
+    total: string;
+    usage: string;
+    used: string;
+    version: string;
+  };
+
+  export type DiskInfoType = {
+    dirName: string;
+    free: string;
+    sysTypeName: string;
+    total: string;
+    typeName: string;
+    usage: string;
+    used: string;
+  };
+
+  export type ServerInfoType = {
+    cpu: CpuInfoType;
+    mem: MemInfoType;
+    jvm: JvmInfoType;
+    sys: SysInfoType;
+    sysFiles: DiskInfoType[];
+  };
+
+  export type ServerInfoResponseType = {
+    data: ServerInfoType;
+    code: number;
+    msg: string;
+  };
+
+  export type CpuRowType = {
+    name: string;
+    value: string;
+  };
+
+  export type MemRowType = {
+    name: string;
+    mem: string;
+    jvm: string;
+  };
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/config.d.ts b/react-ui/src/types/system/config.d.ts
new file mode 100644
index 0000000..7e7e43e
--- /dev/null
+++ b/react-ui/src/types/system/config.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+  export interface Config {
+    configId: number;
+    configName: string;
+    configKey: string;
+    configValue: string;
+    configType: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface ConfigListParams {
+    configId?: string;
+    configName?: string;
+    configKey?: string;
+    configValue?: string;
+    configType?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface ConfigInfoResult { 
+    code: number;
+    msg: string;
+    data: Config;
+  } 
+
+   export interface ConfigPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Config>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/dept.d.ts b/react-ui/src/types/system/dept.d.ts
new file mode 100644
index 0000000..14ad274
--- /dev/null
+++ b/react-ui/src/types/system/dept.d.ts
@@ -0,0 +1,53 @@
+
+declare namespace API.System {
+
+  interface Dept {
+    deptId: number;
+    parentId: number;
+    ancestors: string;
+    deptName: string;
+    orderNum: number;
+    leader: string;
+    phone: string;
+    email: string;
+    status: string;
+    delFlag: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+  }
+
+  export interface DeptListParams {
+    deptId?: string;
+    parentId?: string;
+    ancestors?: string;
+    deptName?: string;
+    orderNum?: string;
+    leader?: string;
+    phone?: string;
+    email?: string;
+    status?: string;
+    delFlag?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface DeptInfoResult { 
+    code: number;
+    msg: string;
+    data: Dept;
+  } 
+
+   export interface DeptPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    data: Array<Dept>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/dict-data.d.ts b/react-ui/src/types/system/dict-data.d.ts
new file mode 100644
index 0000000..30373f5
--- /dev/null
+++ b/react-ui/src/types/system/dict-data.d.ts
@@ -0,0 +1,53 @@
+declare namespace API.System {
+  interface DictData {
+    dictCode: number;
+    dictSort: number;
+    dictLabel: string;
+    dictValue: string;
+    dictType: string;
+    cssClass: string;
+    listClass: string;
+    isDefault: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface DictDataListParams {
+    dictCode?: string;
+    dictSort?: string;
+    dictLabel?: string;
+    dictValue?: string;
+    dictType?: string;
+    cssClass?: string;
+    listClass?: string;
+    isDefault?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  }
+
+  export interface DictDataInfoResult {
+    current: number;
+    pageSize: number;
+    total: number;
+    data: DictData;
+  }
+
+  export interface DictDataPageResult {
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<DictData>;
+  }
+}
diff --git a/react-ui/src/types/system/dict.d.ts b/react-ui/src/types/system/dict.d.ts
new file mode 100644
index 0000000..eef354a
--- /dev/null
+++ b/react-ui/src/types/system/dict.d.ts
@@ -0,0 +1,48 @@
+declare namespace API.System {
+  export interface DictType {
+    dictId: number;
+    dictName: string;
+    dictType: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface DictTypeListParams {
+    dictId?: string;
+    dictName?: string;
+    dictType?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  }
+
+  export interface DictTypeInfoResult {
+    code: number;
+    msg: string;
+    data: Dict;
+  }
+
+  export interface DictTypePageResult {
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Dict>;
+  }
+
+  export interface DictTypeResult {
+    code: number;
+    msg: string;
+    data: Array<Dict>;
+  }
+}
diff --git a/react-ui/src/types/system/menu.d.ts b/react-ui/src/types/system/menu.d.ts
new file mode 100644
index 0000000..15e68ce
--- /dev/null
+++ b/react-ui/src/types/system/menu.d.ts
@@ -0,0 +1,66 @@
+
+declare namespace API.System {
+
+  interface Menu {
+    menuId: number;
+    menuName: string;
+    parentId: number;
+    orderNum: number;
+    path: string;
+    component: string;
+    query: string;
+    isFrame: number;
+    isCache: number;
+    menuType: string;
+    visible: string;
+    status: string;
+    perms: string;
+    icon: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface MenuListParams {
+    menuId?: string;
+    menuName?: string;
+    parentId?: string;
+    orderNum?: string;
+    path?: string;
+    component?: string;
+    query?: string;
+    isFrame?: string;
+    isCache?: string;
+    menuType?: string;
+    visible?: string;
+    status?: string;
+    perms?: string;
+    icon?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  }
+
+  export interface MenuInfoResult { 
+    current: number;
+    pageSize: number;
+    total: number;
+    data: Menu;
+  } 
+
+   export interface MenuPageResult { 
+    current: number;
+    pageSize: number;
+    total: number;
+    data: Array<Menu>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/notice.d.ts b/react-ui/src/types/system/notice.d.ts
new file mode 100644
index 0000000..89a77ee
--- /dev/null
+++ b/react-ui/src/types/system/notice.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+  export interface Notice {
+    noticeId: number;
+    noticeTitle: string;
+    noticeType: string;
+    noticeContent: string;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface NoticeListParams {
+    noticeId?: string;
+    noticeTitle?: string;
+    noticeType?: string;
+    noticeContent?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface NoticeInfoResult { 
+    code: number;
+    msg: string;
+    data: Notice;
+  } 
+
+   export interface NoticePageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Notice>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/post.d.ts b/react-ui/src/types/system/post.d.ts
new file mode 100644
index 0000000..772140c
--- /dev/null
+++ b/react-ui/src/types/system/post.d.ts
@@ -0,0 +1,45 @@
+
+declare namespace API.System {
+
+  interface Post {
+    postId: number;
+    postCode: string;
+    postName: string;
+    postSort: number;
+    status: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface PostListParams {
+    postId?: string;
+    postCode?: string;
+    postName?: string;
+    postSort?: string;
+    status?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface PostInfoResult { 
+    code: number;
+    msg: string;
+    data: Post;
+  } 
+
+   export interface PostPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Post>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/role.d.ts b/react-ui/src/types/system/role.d.ts
new file mode 100644
index 0000000..53191c9
--- /dev/null
+++ b/react-ui/src/types/system/role.d.ts
@@ -0,0 +1,65 @@
+
+declare namespace API.System {
+
+  interface Role {
+    roleId: number;
+    roleName: string;
+    roleKey: string;
+    roleSort: number;
+    dataScope: string;
+    menuCheckStrictly: number;
+    deptCheckStrictly: number;
+    status: string;
+    delFlag: string;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface RoleListParams {
+    roleId?: string;
+    roleName?: string;
+    roleKey?: string;
+    roleSort?: string;
+    dataScope?: string;
+    menuCheckStrictly?: string;
+    deptCheckStrictly?: string;
+    status?: string;
+    delFlag?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    current?: string;
+  }
+
+  export interface RoleInfoResult { 
+    code: number;
+    msg: string;
+    data: Role;
+  } 
+
+   export interface RolePageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<Role>;
+  }
+
+  export type RoleMenuNode = {
+    id: number|string;
+    label: string;
+    children?: Array<RoleMenuNode>;
+  }
+  export interface RoleMenuResult { 
+    code: number;
+    msg: string;
+    checkedKeys: number[];
+    menus: Array<RoleMenuNode>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/system/user.d.ts b/react-ui/src/types/system/user.d.ts
new file mode 100644
index 0000000..5751956
--- /dev/null
+++ b/react-ui/src/types/system/user.d.ts
@@ -0,0 +1,71 @@
+
+declare namespace API.System {
+
+  interface User {
+    userId: number;
+    deptId: number;
+    userName: string;
+    nickName: string;
+    userType: string;
+    email: string;
+    phonenumber: string;
+    sex: string;
+    avatar: string;
+    password: string;
+    status: string;
+    delFlag: string;
+    loginIp: string;
+    loginDate: Date;
+    createBy: string;
+    createTime: Date;
+    updateBy: string;
+    updateTime: Date;
+    remark: string;
+  }
+
+  export interface UserListParams {
+    userId?: string;
+    deptId?: string;
+    userName?: string;
+    nickName?: string;
+    userType?: string;
+    email?: string;
+    phonenumber?: string;
+    sex?: string;
+    avatar?: string;
+    password?: string;
+    status?: string;
+    delFlag?: string;
+    loginIp?: string;
+    loginDate?: string;
+    createBy?: string;
+    createTime?: string;
+    updateBy?: string;
+    updateTime?: string;
+    remark?: string;
+    pageSize?: string;
+    currentPage?: string;
+    filter?: string;
+    sorter?: string;
+  }
+
+  export interface UserInfoResult { 
+    current: number;
+    pageSize: number;
+    total: number;
+    data: User;
+    posts: [];
+    postIds: any;
+    roleId: number;
+    roleIds: [];
+    roles: [];
+  } 
+
+   export interface UserPageResult { 
+    code: number;
+    msg: string;
+    total: number;
+    rows: Array<User>;
+  }
+
+}
\ No newline at end of file
diff --git a/react-ui/src/types/typings.d.ts b/react-ui/src/types/typings.d.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/react-ui/src/types/typings.d.ts
diff --git a/react-ui/src/typings.d.ts b/react-ui/src/typings.d.ts
new file mode 100644
index 0000000..742f70c
--- /dev/null
+++ b/react-ui/src/typings.d.ts
@@ -0,0 +1,20 @@
+declare module 'slash2';
+declare module '*.css';
+declare module '*.less';
+declare module '*.scss';
+declare module '*.sass';
+declare module '*.svg';
+declare module '*.png';
+declare module '*.jpg';
+declare module '*.jpeg';
+declare module '*.gif';
+declare module '*.bmp';
+declare module '*.tiff';
+declare module 'omit.js';
+declare module 'numeral';
+declare module '@antv/data-set';
+declare module 'mockjs';
+declare module 'react-fittext';
+declare module 'bizcharts-plugin-slider';
+
+declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
diff --git a/react-ui/src/utils/IconUtil.ts b/react-ui/src/utils/IconUtil.ts
new file mode 100644
index 0000000..31102ee
--- /dev/null
+++ b/react-ui/src/utils/IconUtil.ts
@@ -0,0 +1,20 @@
+import * as AntdIcons from '@ant-design/icons';
+import React from 'react';
+
+const allIcons: Record<string, any> = AntdIcons;
+
+export function getIcon(name: string): React.ReactNode | string {
+  const icon = allIcons[name];
+  return icon || '';
+}
+
+export function createIcon(icon: string | any): React.ReactNode | string {
+  if (typeof icon === 'object') {
+    return icon;
+  }
+  const ele = allIcons[icon];
+  if (ele) {
+    return React.createElement(allIcons[icon]);
+  }
+  return '';
+}
diff --git a/react-ui/src/utils/downloadfile.ts b/react-ui/src/utils/downloadfile.ts
new file mode 100644
index 0000000..5d56cda
--- /dev/null
+++ b/react-ui/src/utils/downloadfile.ts
@@ -0,0 +1,63 @@
+import { request } from '@umijs/max';
+
+const mimeMap = {
+  xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+  zip: 'application/zip',
+};
+
+/**
+ * 解析blob响应内容并下载
+ * @param {*} res blob响应内容
+ * @param {String} mimeType MIME类型
+ */
+export function resolveBlob(res: any, mimeType: string) {
+  const aLink = document.createElement('a');
+  const blob = new Blob([res.data], { type: mimeType });
+  // //从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
+  const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*');
+  // console.log(res);
+  const contentDisposition = decodeURI(res.headers['content-disposition']);
+  const result = patt.exec(contentDisposition);
+  let fileName = result ? result[1] : 'file';
+  fileName = fileName.replace(/"/g, '');
+  aLink.style.display = 'none';
+  aLink.href = URL.createObjectURL(blob);
+  aLink.setAttribute('download', fileName); // 设置下载文件名称
+  document.body.appendChild(aLink);
+  aLink.click();
+  URL.revokeObjectURL(aLink.href); // 清除引用
+  document.body.removeChild(aLink);
+}
+
+export function downLoadZip(url: string) {
+  request(url, {
+    method: 'GET',
+    responseType: 'blob',
+    getResponse: true,
+  }).then((res) => {
+    resolveBlob(res, mimeMap.zip);
+  });
+}
+
+export async function downLoadXlsx(url: string, params: any, fileName: string) {
+  return request(url, {
+    ...params,
+    method: 'POST',
+    responseType: 'blob',
+  }).then((data) => {
+    const aLink = document.createElement('a');
+    const blob = data as any; // new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+    aLink.style.display = 'none';
+    aLink.href = URL.createObjectURL(blob);
+    aLink.setAttribute('download', fileName); // 设置下载文件名称
+    document.body.appendChild(aLink);
+    aLink.click();
+    URL.revokeObjectURL(aLink.href); // 清除引用
+    document.body.removeChild(aLink);
+  });
+}
+
+
+export function download(fileName: string) {
+  window.location.href = `/api/common/download?fileName=${encodeURI(fileName)}&delete=${true}`;
+}
diff --git a/react-ui/src/utils/options.ts b/react-ui/src/utils/options.ts
new file mode 100644
index 0000000..79ee803
--- /dev/null
+++ b/react-ui/src/utils/options.ts
@@ -0,0 +1,12 @@
+import { DictValueEnumObj } from "@/components/DictTag";
+import { ProSchemaValueEnumObj, ProSchemaValueEnumType } from "@ant-design/pro-components";
+
+export function getValueEnumLabel(options: DictValueEnumObj | ProSchemaValueEnumObj, val: string | number | undefined, defaultValue?: string) {
+    if (val !== undefined) {
+       const data = options[val] as ProSchemaValueEnumType;
+       if(data) {
+        return data.text;
+       }
+    }
+    return defaultValue?defaultValue:val;
+}
diff --git a/react-ui/src/utils/permission.ts b/react-ui/src/utils/permission.ts
new file mode 100644
index 0000000..e7c8a28
--- /dev/null
+++ b/react-ui/src/utils/permission.ts
@@ -0,0 +1,64 @@
+// /**
+//  * 字符权限校验
+//  * @param {Array} value 校验值
+//  * @returns {Boolean}
+//  */
+export function matchPerms(permissions: string[], value: string[]) {
+  if (value && value instanceof Array && value.length > 0) {
+    const permissionDatas = value;
+    const all_permission = '*:*:*';
+    const hasPermission = permissions.some((permission) => {
+      return all_permission === permission || permissionDatas.includes(permission);
+    });
+    if (!hasPermission) {
+      return false;
+    }
+    return true;
+  }
+  console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
+  return false;
+}
+
+export function matchPerm(permissions: string[], value: string) {
+  if (value && value.length > 0) {
+    const permissionDatas = value;
+    const all_permission = '*:*:*';
+    const hasPermission = permissions.some((permission) => {
+      return all_permission === permission || permissionDatas === permission;
+    });
+    if (!hasPermission) {
+      return false;
+    }
+    return true;
+  }
+  console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`);
+  return false;
+}
+
+export function matchPermission(permissions: string[] | undefined, value: any): boolean {
+  if (permissions === undefined) return false;
+  const type = typeof value;
+  if (type === 'string') {
+    return matchPerm(permissions, value);
+  }
+  return matchPerms(permissions, value);
+}
+
+/**
+ * 角色权限校验
+ * @param {Array} value 校验值
+ * @returns {Boolean}
+ */
+export function checkRole(roles: API.System.Role[] | undefined, value: string[]) {
+  if (roles && value && value.length > 0) {
+    for (let i = 0; i < roles?.length; i++) {
+      for (let j = 0; j < value?.length; j++) {
+        if (value[j] === roles[i].roleKey) {
+          return true;
+        }
+      }
+    }
+  }
+  console.error(`need roles! Like checkRole="['admin','editor']"`);
+  return false;
+}
diff --git a/react-ui/src/utils/tree.ts b/react-ui/src/utils/tree.ts
new file mode 100644
index 0000000..d75395e
--- /dev/null
+++ b/react-ui/src/utils/tree.ts
@@ -0,0 +1,93 @@
+import { DataNode } from 'antd/es/tree';
+import { parse } from 'querystring';
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ */
+export function buildTreeData(
+  data: any[],
+  id: string,
+  name: string,
+  parentId: string,
+  parentName: string,
+  children: string,
+) {
+  const config = {
+    id: id || 'id',
+    name: name || 'name',
+    parentId: parentId || 'parentId',
+    parentName: parentName || 'parentName',
+    childrenList: children || 'children',
+  };
+
+  const childrenListMap: any[] = [];
+  const nodeIds: any[] = [];
+  const tree: any[] = [];
+  data.forEach((item) => {
+    const d = item;
+    const pId = d[config.parentId];
+    if (!childrenListMap[pId]) {
+      childrenListMap[pId] = [];
+    }
+    d.key = d[config.id];
+    d.title = d[config.name];
+    d.value = d[config.id];
+    d[config.childrenList] = null;
+    nodeIds[d[config.id]] = d;
+    childrenListMap[pId].push(d);
+  });
+
+  data.forEach((item: any) => {
+    const d = item;
+    const pId = d[config.parentId];
+    if (!nodeIds[pId]) {
+      d[config.parentName] = '';
+      tree.push(d);
+    }
+  });
+
+  function adaptToChildrenList(item: any) {
+    const o = item;
+    if (childrenListMap[o[config.id]]) {
+      if (!o[config.childrenList]) {
+        o[config.childrenList] = [];
+      }
+      o[config.childrenList] = childrenListMap[o[config.id]];
+    }
+    if (o[config.childrenList]) {
+      o[config.childrenList].forEach((child: any) => {
+        const c = child;
+        c[config.parentName] = o[config.name];
+        adaptToChildrenList(c);
+      });
+    }
+  }
+
+  tree.forEach((t: any) => {
+    adaptToChildrenList(t);
+  });
+
+  return tree;
+}
+
+export const getPageQuery = () => parse(window.location.href.split('?')[1]);
+
+export function formatTreeData(arrayList: any): DataNode[] {
+  const treeSelectData: DataNode[] = arrayList.map((item: any) => {
+    const node: DataNode = {
+      id: item.id,
+      title: item.label,
+      key: `${item.id}`,
+      value: item.id,
+    } as DataNode;
+    if (item.children) {
+      node.children = formatTreeData(item.children);
+    }
+    return node;
+  });
+  return treeSelectData;
+}