'init_again'
Change-Id: Ib7ecdb9f5baeab1e4681152a57b936edf7475b35
diff --git a/src/access.ts b/src/access.ts
new file mode 100644
index 0000000..8c11f31
--- /dev/null
+++ b/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/src/app.tsx b/src/app.tsx
new file mode 100644
index 0000000..38f76b4
--- /dev/null
+++ b/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/src/components/DictTag/index.tsx b/src/components/DictTag/index.tsx
new file mode 100644
index 0000000..2b2a791
--- /dev/null
+++ b/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/src/components/Footer/index.tsx b/src/components/Footer/index.tsx
new file mode 100644
index 0000000..f204ac2
--- /dev/null
+++ b/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/src/components/HeaderDropdown/index.tsx b/src/components/HeaderDropdown/index.tsx
new file mode 100644
index 0000000..f89052d
--- /dev/null
+++ b/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/src/components/IconSelector/Category.tsx b/src/components/IconSelector/Category.tsx
new file mode 100644
index 0000000..2391687
--- /dev/null
+++ b/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(Number(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/src/components/IconSelector/CopyableIcon.tsx b/src/components/IconSelector/CopyableIcon.tsx
new file mode 100644
index 0000000..a0d9656
--- /dev/null
+++ b/src/components/IconSelector/CopyableIcon.tsx
@@ -0,0 +1,48 @@
+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/src/components/IconSelector/IconPicSearcher.tsx b/src/components/IconSelector/IconPicSearcher.tsx
new file mode 100644
index 0000000..3a4cf01
--- /dev/null
+++ b/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/src/components/IconSelector/fields.ts b/src/components/IconSelector/fields.ts
new file mode 100644
index 0000000..de37e67
--- /dev/null
+++ b/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/src/components/IconSelector/index.tsx b/src/components/IconSelector/index.tsx
new file mode 100644
index 0000000..78dc931
--- /dev/null
+++ b/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/src/components/IconSelector/style.less b/src/components/IconSelector/style.less
new file mode 100644
index 0000000..0a4353d
--- /dev/null
+++ b/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/src/components/IconSelector/themeIcons.tsx b/src/components/IconSelector/themeIcons.tsx
new file mode 100644
index 0000000..abefe04
--- /dev/null
+++ b/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/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx
new file mode 100644
index 0000000..34ceb15
--- /dev/null
+++ b/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/src/components/RightContent/index.tsx b/src/components/RightContent/index.tsx
new file mode 100644
index 0000000..20a7831
--- /dev/null
+++ b/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/src/components/index.ts b/src/components/index.ts
new file mode 100644
index 0000000..ca88a6d
--- /dev/null
+++ b/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/src/enums/httpEnum.ts b/src/enums/httpEnum.ts
new file mode 100644
index 0000000..8b7ff1f
--- /dev/null
+++ b/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/src/enums/pagesEnums.ts b/src/enums/pagesEnums.ts
new file mode 100644
index 0000000..4bdb5fd
--- /dev/null
+++ b/src/enums/pagesEnums.ts
@@ -0,0 +1,4 @@
+
+export enum PageEnum {
+ LOGIN = '/user/login'
+}
\ No newline at end of file
diff --git a/src/global.less b/src/global.less
new file mode 100644
index 0000000..a9a0c51
--- /dev/null
+++ b/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/src/global.tsx b/src/global.tsx
new file mode 100644
index 0000000..afa1fab
--- /dev/null
+++ b/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/src/hooks/net/dict.ts b/src/hooks/net/dict.ts
new file mode 100644
index 0000000..9c2c41c
--- /dev/null
+++ b/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/src/locales/en-US.ts b/src/locales/en-US.ts
new file mode 100644
index 0000000..58407d2
--- /dev/null
+++ b/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/src/locales/en-US/app.ts b/src/locales/en-US/app.ts
new file mode 100644
index 0000000..074dcb8
--- /dev/null
+++ b/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/src/locales/en-US/component.ts b/src/locales/en-US/component.ts
new file mode 100644
index 0000000..3ba7eed
--- /dev/null
+++ b/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/src/locales/en-US/globalHeader.ts b/src/locales/en-US/globalHeader.ts
new file mode 100644
index 0000000..60b6d4e
--- /dev/null
+++ b/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/src/locales/en-US/menu.ts b/src/locales/en-US/menu.ts
new file mode 100644
index 0000000..eae3e53
--- /dev/null
+++ b/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/src/locales/en-US/pages.ts b/src/locales/en-US/pages.ts
new file mode 100644
index 0000000..58ff2c8
--- /dev/null
+++ b/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/src/locales/en-US/pwa.ts b/src/locales/en-US/pwa.ts
new file mode 100644
index 0000000..ed8d199
--- /dev/null
+++ b/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/src/locales/en-US/settingDrawer.ts b/src/locales/en-US/settingDrawer.ts
new file mode 100644
index 0000000..a644905
--- /dev/null
+++ b/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/src/locales/en-US/settings.ts b/src/locales/en-US/settings.ts
new file mode 100644
index 0000000..822dd00
--- /dev/null
+++ b/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/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
new file mode 100644
index 0000000..06e2eac
--- /dev/null
+++ b/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/src/locales/zh-CN/app.ts b/src/locales/zh-CN/app.ts
new file mode 100644
index 0000000..5be68e2
--- /dev/null
+++ b/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/src/locales/zh-CN/component.ts b/src/locales/zh-CN/component.ts
new file mode 100644
index 0000000..1f1fead
--- /dev/null
+++ b/src/locales/zh-CN/component.ts
@@ -0,0 +1,5 @@
+export default {
+ 'component.tagSelect.expand': '展开',
+ 'component.tagSelect.collapse': '收起',
+ 'component.tagSelect.all': '全部',
+};
diff --git a/src/locales/zh-CN/globalHeader.ts b/src/locales/zh-CN/globalHeader.ts
new file mode 100644
index 0000000..9fd66a5
--- /dev/null
+++ b/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/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts
new file mode 100644
index 0000000..fecb70a
--- /dev/null
+++ b/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/src/locales/zh-CN/monitor/job-log.ts b/src/locales/zh-CN/monitor/job-log.ts
new file mode 100644
index 0000000..75cf8d0
--- /dev/null
+++ b/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/src/locales/zh-CN/monitor/job.ts b/src/locales/zh-CN/monitor/job.ts
new file mode 100644
index 0000000..7e0d5b9
--- /dev/null
+++ b/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/src/locales/zh-CN/monitor/logininfor.ts b/src/locales/zh-CN/monitor/logininfor.ts
new file mode 100644
index 0000000..959a6d2
--- /dev/null
+++ b/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/src/locales/zh-CN/monitor/onlineUser.ts b/src/locales/zh-CN/monitor/onlineUser.ts
new file mode 100644
index 0000000..c693cde
--- /dev/null
+++ b/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/src/locales/zh-CN/monitor/operlog.ts b/src/locales/zh-CN/monitor/operlog.ts
new file mode 100644
index 0000000..c45824c
--- /dev/null
+++ b/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/src/locales/zh-CN/monitor/server.ts b/src/locales/zh-CN/monitor/server.ts
new file mode 100644
index 0000000..f887a22
--- /dev/null
+++ b/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/src/locales/zh-CN/pages.ts b/src/locales/zh-CN/pages.ts
new file mode 100644
index 0000000..fc7abfb
--- /dev/null
+++ b/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/src/locales/zh-CN/pwa.ts b/src/locales/zh-CN/pwa.ts
new file mode 100644
index 0000000..e950484
--- /dev/null
+++ b/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/src/locales/zh-CN/settingDrawer.ts b/src/locales/zh-CN/settingDrawer.ts
new file mode 100644
index 0000000..3f44958
--- /dev/null
+++ b/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/src/locales/zh-CN/settings.ts b/src/locales/zh-CN/settings.ts
new file mode 100644
index 0000000..df8af43
--- /dev/null
+++ b/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/src/locales/zh-CN/system/config.ts b/src/locales/zh-CN/system/config.ts
new file mode 100644
index 0000000..5e1e764
--- /dev/null
+++ b/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/src/locales/zh-CN/system/dept.ts b/src/locales/zh-CN/system/dept.ts
new file mode 100644
index 0000000..7774f2c
--- /dev/null
+++ b/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/src/locales/zh-CN/system/dict-data.ts b/src/locales/zh-CN/system/dict-data.ts
new file mode 100644
index 0000000..db2c742
--- /dev/null
+++ b/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/src/locales/zh-CN/system/dict.ts b/src/locales/zh-CN/system/dict.ts
new file mode 100644
index 0000000..cf00f66
--- /dev/null
+++ b/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/src/locales/zh-CN/system/menu.ts b/src/locales/zh-CN/system/menu.ts
new file mode 100644
index 0000000..8a01e58
--- /dev/null
+++ b/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/src/locales/zh-CN/system/notice.ts b/src/locales/zh-CN/system/notice.ts
new file mode 100644
index 0000000..ad55d51
--- /dev/null
+++ b/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/src/locales/zh-CN/system/post.ts b/src/locales/zh-CN/system/post.ts
new file mode 100644
index 0000000..40e589b
--- /dev/null
+++ b/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/src/locales/zh-CN/system/role.ts b/src/locales/zh-CN/system/role.ts
new file mode 100644
index 0000000..e88f979
--- /dev/null
+++ b/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/src/locales/zh-CN/system/user.ts b/src/locales/zh-CN/system/user.ts
new file mode 100644
index 0000000..7d676d0
--- /dev/null
+++ b/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/src/locales/zh-TW.ts b/src/locales/zh-TW.ts
new file mode 100644
index 0000000..6ad5f93
--- /dev/null
+++ b/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/src/locales/zh-TW/component.ts b/src/locales/zh-TW/component.ts
new file mode 100644
index 0000000..ba48e29
--- /dev/null
+++ b/src/locales/zh-TW/component.ts
@@ -0,0 +1,5 @@
+export default {
+ 'component.tagSelect.expand': '展開',
+ 'component.tagSelect.collapse': '收起',
+ 'component.tagSelect.all': '全部',
+};
diff --git a/src/locales/zh-TW/globalHeader.ts b/src/locales/zh-TW/globalHeader.ts
new file mode 100644
index 0000000..ed58451
--- /dev/null
+++ b/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/src/locales/zh-TW/menu.ts b/src/locales/zh-TW/menu.ts
new file mode 100644
index 0000000..0ef54c9
--- /dev/null
+++ b/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/src/locales/zh-TW/pages.ts b/src/locales/zh-TW/pages.ts
new file mode 100644
index 0000000..f9e265b
--- /dev/null
+++ b/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/src/locales/zh-TW/pwa.ts b/src/locales/zh-TW/pwa.ts
new file mode 100644
index 0000000..108a6e4
--- /dev/null
+++ b/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/src/locales/zh-TW/settingDrawer.ts b/src/locales/zh-TW/settingDrawer.ts
new file mode 100644
index 0000000..454da28
--- /dev/null
+++ b/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/src/locales/zh-TW/settings.ts b/src/locales/zh-TW/settings.ts
new file mode 100644
index 0000000..dd45151
--- /dev/null
+++ b/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/src/manifest.json b/src/manifest.json
new file mode 100644
index 0000000..839bc5b
--- /dev/null
+++ b/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/src/pages/404.tsx b/src/pages/404.tsx
new file mode 100644
index 0000000..0263687
--- /dev/null
+++ b/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/src/pages/Monitor/Cache/List.tsx b/src/pages/Monitor/Cache/List.tsx
new file mode 100644
index 0000000..af6147a
--- /dev/null
+++ b/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/src/pages/Monitor/Cache/List/index.less b/src/pages/Monitor/Cache/List/index.less
new file mode 100644
index 0000000..0c5aace
--- /dev/null
+++ b/src/pages/Monitor/Cache/List/index.less
@@ -0,0 +1,10 @@
+
+
+
+/* *
+ *
+ * @author whiteshader@163.com
+ * @datetime 2021/09/16
+ *
+ * */
+
diff --git a/src/pages/Monitor/Cache/index.less b/src/pages/Monitor/Cache/index.less
new file mode 100644
index 0000000..7112c50
--- /dev/null
+++ b/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/src/pages/Monitor/Cache/index.tsx b/src/pages/Monitor/Cache/index.tsx
new file mode 100644
index 0000000..4c247ee
--- /dev/null
+++ b/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/src/pages/Monitor/Druid/index.tsx b/src/pages/Monitor/Druid/index.tsx
new file mode 100644
index 0000000..55692d2
--- /dev/null
+++ b/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/src/pages/Monitor/Job/detail.tsx b/src/pages/Monitor/Job/detail.tsx
new file mode 100644
index 0000000..e27bfb7
--- /dev/null
+++ b/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/src/pages/Monitor/Job/edit.tsx b/src/pages/Monitor/Job/edit.tsx
new file mode 100644
index 0000000..e4bfc74
--- /dev/null
+++ b/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/src/pages/Monitor/Job/index.tsx b/src/pages/Monitor/Job/index.tsx
new file mode 100644
index 0000000..43030d9
--- /dev/null
+++ b/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/src/pages/Monitor/JobLog/detail.tsx b/src/pages/Monitor/JobLog/detail.tsx
new file mode 100644
index 0000000..560889c
--- /dev/null
+++ b/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/src/pages/Monitor/JobLog/index.tsx b/src/pages/Monitor/JobLog/index.tsx
new file mode 100644
index 0000000..46e3313
--- /dev/null
+++ b/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/src/pages/Monitor/Logininfor/edit.tsx b/src/pages/Monitor/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/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/src/pages/Monitor/Logininfor/index.tsx b/src/pages/Monitor/Logininfor/index.tsx
new file mode 100644
index 0000000..5a39281
--- /dev/null
+++ b/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/src/pages/Monitor/Online/index.tsx b/src/pages/Monitor/Online/index.tsx
new file mode 100644
index 0000000..2f81e3b
--- /dev/null
+++ b/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/src/pages/Monitor/Operlog/detail.tsx b/src/pages/Monitor/Operlog/detail.tsx
new file mode 100644
index 0000000..f247522
--- /dev/null
+++ b/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/src/pages/Monitor/Operlog/index.tsx b/src/pages/Monitor/Operlog/index.tsx
new file mode 100644
index 0000000..9440bc8
--- /dev/null
+++ b/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/src/pages/Monitor/Server/index.tsx b/src/pages/Monitor/Server/index.tsx
new file mode 100644
index 0000000..9ea3969
--- /dev/null
+++ b/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/src/pages/Monitor/Server/style.less b/src/pages/Monitor/Server/style.less
new file mode 100644
index 0000000..d244a53
--- /dev/null
+++ b/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/src/pages/System/Config/edit.tsx b/src/pages/System/Config/edit.tsx
new file mode 100644
index 0000000..6cb3942
--- /dev/null
+++ b/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/src/pages/System/Config/index.tsx b/src/pages/System/Config/index.tsx
new file mode 100644
index 0000000..1428891
--- /dev/null
+++ b/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/src/pages/System/Dept/edit.tsx b/src/pages/System/Dept/edit.tsx
new file mode 100644
index 0000000..cb64813
--- /dev/null
+++ b/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/src/pages/System/Dept/index.tsx b/src/pages/System/Dept/index.tsx
new file mode 100644
index 0000000..3a1c5de
--- /dev/null
+++ b/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/src/pages/System/Dict/edit.tsx b/src/pages/System/Dict/edit.tsx
new file mode 100644
index 0000000..882c1cb
--- /dev/null
+++ b/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/src/pages/System/Dict/index.tsx b/src/pages/System/Dict/index.tsx
new file mode 100644
index 0000000..ccdbc2a
--- /dev/null
+++ b/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/src/pages/System/DictData/edit.tsx b/src/pages/System/DictData/edit.tsx
new file mode 100644
index 0000000..188e01b
--- /dev/null
+++ b/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/src/pages/System/DictData/index.tsx b/src/pages/System/DictData/index.tsx
new file mode 100644
index 0000000..2bf1069
--- /dev/null
+++ b/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/src/pages/System/Logininfor/edit.tsx b/src/pages/System/Logininfor/edit.tsx
new file mode 100644
index 0000000..5243191
--- /dev/null
+++ b/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/src/pages/System/Logininfor/index.tsx b/src/pages/System/Logininfor/index.tsx
new file mode 100644
index 0000000..ee5c1e5
--- /dev/null
+++ b/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/src/pages/System/Menu/edit.tsx b/src/pages/System/Menu/edit.tsx
new file mode 100644
index 0000000..e094255
--- /dev/null
+++ b/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/src/pages/System/Menu/index.tsx b/src/pages/System/Menu/index.tsx
new file mode 100644
index 0000000..95ca2de
--- /dev/null
+++ b/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/src/pages/System/Notice/edit.tsx b/src/pages/System/Notice/edit.tsx
new file mode 100644
index 0000000..0b111ce
--- /dev/null
+++ b/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/src/pages/System/Notice/index.tsx b/src/pages/System/Notice/index.tsx
new file mode 100644
index 0000000..ea5b063
--- /dev/null
+++ b/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/src/pages/System/Operlog/detail.tsx b/src/pages/System/Operlog/detail.tsx
new file mode 100644
index 0000000..f5b4f65
--- /dev/null
+++ b/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/src/pages/System/Operlog/index.tsx b/src/pages/System/Operlog/index.tsx
new file mode 100644
index 0000000..be46709
--- /dev/null
+++ b/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/src/pages/System/Post/edit.tsx b/src/pages/System/Post/edit.tsx
new file mode 100644
index 0000000..555fcde
--- /dev/null
+++ b/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/src/pages/System/Post/index.tsx b/src/pages/System/Post/index.tsx
new file mode 100644
index 0000000..ad98aae
--- /dev/null
+++ b/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/src/pages/System/Role/authUser.tsx b/src/pages/System/Role/authUser.tsx
new file mode 100644
index 0000000..c553d99
--- /dev/null
+++ b/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/src/pages/System/Role/components/DataScope.tsx b/src/pages/System/Role/components/DataScope.tsx
new file mode 100644
index 0000000..a3dcce0
--- /dev/null
+++ b/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/src/pages/System/Role/components/UserSelectorModal.tsx b/src/pages/System/Role/components/UserSelectorModal.tsx
new file mode 100644
index 0000000..fdcfb85
--- /dev/null
+++ b/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/src/pages/System/Role/edit.tsx b/src/pages/System/Role/edit.tsx
new file mode 100644
index 0000000..580493a
--- /dev/null
+++ b/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/src/pages/System/Role/index.tsx b/src/pages/System/Role/index.tsx
new file mode 100644
index 0000000..ea15b2e
--- /dev/null
+++ b/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/src/pages/System/User/components/AuthRole.tsx b/src/pages/System/User/components/AuthRole.tsx
new file mode 100644
index 0000000..120d966
--- /dev/null
+++ b/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/src/pages/System/User/components/DeptTree.tsx b/src/pages/System/User/components/DeptTree.tsx
new file mode 100644
index 0000000..d1a085c
--- /dev/null
+++ b/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/src/pages/System/User/components/ResetPwd.tsx b/src/pages/System/User/components/ResetPwd.tsx
new file mode 100644
index 0000000..5523cd6
--- /dev/null
+++ b/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/src/pages/System/User/edit.tsx b/src/pages/System/User/edit.tsx
new file mode 100644
index 0000000..31d442b
--- /dev/null
+++ b/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/src/pages/System/User/index.tsx b/src/pages/System/User/index.tsx
new file mode 100644
index 0000000..6435787
--- /dev/null
+++ b/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/src/pages/Tool/Build/index.tsx b/src/pages/Tool/Build/index.tsx
new file mode 100644
index 0000000..647d729
--- /dev/null
+++ b/src/pages/Tool/Build/index.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+
+const testIndex :React.FC = ()=>{
+ return (
+ <div>111</div>
+ );
+}
+export default testIndex;
diff --git a/src/pages/Tool/Gen/components/BaseInfo.tsx b/src/pages/Tool/Gen/components/BaseInfo.tsx
new file mode 100644
index 0000000..6a0466f
--- /dev/null
+++ b/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/src/pages/Tool/Gen/components/ColumnInfo.tsx b/src/pages/Tool/Gen/components/ColumnInfo.tsx
new file mode 100644
index 0000000..2db5de4
--- /dev/null
+++ b/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/src/pages/Tool/Gen/components/GenInfo.tsx b/src/pages/Tool/Gen/components/GenInfo.tsx
new file mode 100644
index 0000000..97901ec
--- /dev/null
+++ b/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/src/pages/Tool/Gen/components/PreviewCode.tsx b/src/pages/Tool/Gen/components/PreviewCode.tsx
new file mode 100644
index 0000000..f3611ad
--- /dev/null
+++ b/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/src/pages/Tool/Gen/data.d.ts b/src/pages/Tool/Gen/data.d.ts
new file mode 100644
index 0000000..a63ea9d
--- /dev/null
+++ b/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/src/pages/Tool/Gen/edit.tsx b/src/pages/Tool/Gen/edit.tsx
new file mode 100644
index 0000000..99a0d1a
--- /dev/null
+++ b/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/src/pages/Tool/Gen/import.tsx b/src/pages/Tool/Gen/import.tsx
new file mode 100644
index 0000000..6e2c8df
--- /dev/null
+++ b/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/src/pages/Tool/Gen/index.tsx b/src/pages/Tool/Gen/index.tsx
new file mode 100644
index 0000000..9db00d7
--- /dev/null
+++ b/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/src/pages/Tool/Gen/locales/zh-CN.ts b/src/pages/Tool/Gen/locales/zh-CN.ts
new file mode 100644
index 0000000..9c3a5d4
--- /dev/null
+++ b/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/src/pages/Tool/Gen/service.ts b/src/pages/Tool/Gen/service.ts
new file mode 100644
index 0000000..0e472ee
--- /dev/null
+++ b/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/src/pages/Tool/Gen/style.less b/src/pages/Tool/Gen/style.less
new file mode 100644
index 0000000..6999d7a
--- /dev/null
+++ b/src/pages/Tool/Gen/style.less
@@ -0,0 +1,4 @@
+.steps:global(.ant-steps) {
+ max-width: 750px;
+ margin: 16px auto;
+}
diff --git a/src/pages/Tool/Swagger/index.tsx b/src/pages/Tool/Swagger/index.tsx
new file mode 100644
index 0000000..adcc968
--- /dev/null
+++ b/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/src/pages/User/Center/Center.less b/src/pages/User/Center/Center.less
new file mode 100644
index 0000000..430d88e
--- /dev/null
+++ b/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/src/pages/User/Center/components/AvatarCropper/cropper.css b/src/pages/User/Center/components/AvatarCropper/cropper.css
new file mode 100644
index 0000000..7f2f350
--- /dev/null
+++ b/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/src/pages/User/Center/components/AvatarCropper/images/bg.png b/src/pages/User/Center/components/AvatarCropper/images/bg.png
new file mode 100644
index 0000000..3c7056b
--- /dev/null
+++ b/src/pages/User/Center/components/AvatarCropper/images/bg.png
Binary files differ
diff --git a/src/pages/User/Center/components/AvatarCropper/index.less b/src/pages/User/Center/components/AvatarCropper/index.less
new file mode 100644
index 0000000..dc3fecf
--- /dev/null
+++ b/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/src/pages/User/Center/components/AvatarCropper/index.tsx b/src/pages/User/Center/components/AvatarCropper/index.tsx
new file mode 100644
index 0000000..83d0bcf
--- /dev/null
+++ b/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/src/pages/User/Center/components/BaseInfo/index.tsx b/src/pages/User/Center/components/BaseInfo/index.tsx
new file mode 100644
index 0000000..5cdca5f
--- /dev/null
+++ b/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/src/pages/User/Center/components/ResetPassword/index.tsx b/src/pages/User/Center/components/ResetPassword/index.tsx
new file mode 100644
index 0000000..26825d4
--- /dev/null
+++ b/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/src/pages/User/Center/index.tsx b/src/pages/User/Center/index.tsx
new file mode 100644
index 0000000..2ce308c
--- /dev/null
+++ b/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/src/pages/User/Login/index.tsx b/src/pages/User/Login/index.tsx
new file mode 100644
index 0000000..a18f785
--- /dev/null
+++ b/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/src/pages/User/Settings/index.tsx b/src/pages/User/Settings/index.tsx
new file mode 100644
index 0000000..f29d0b9
--- /dev/null
+++ b/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/src/pages/Welcome.tsx b/src/pages/Welcome.tsx
new file mode 100644
index 0000000..d0c49f7
--- /dev/null
+++ b/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/src/requestErrorConfig.ts b/src/requestErrorConfig.ts
new file mode 100644
index 0000000..c0c4a6a
--- /dev/null
+++ b/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/src/service-worker.js b/src/service-worker.js
new file mode 100644
index 0000000..b86726c
--- /dev/null
+++ b/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/src/services/ant-design-pro/api.ts b/src/services/ant-design-pro/api.ts
new file mode 100644
index 0000000..64a950a
--- /dev/null
+++ b/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/src/services/ant-design-pro/index.ts b/src/services/ant-design-pro/index.ts
new file mode 100644
index 0000000..9ae58be
--- /dev/null
+++ b/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/src/services/ant-design-pro/login.ts b/src/services/ant-design-pro/login.ts
new file mode 100644
index 0000000..3b00b43
--- /dev/null
+++ b/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/src/services/ant-design-pro/rule.ts b/src/services/ant-design-pro/rule.ts
new file mode 100644
index 0000000..4b8ebc5
--- /dev/null
+++ b/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/src/services/ant-design-pro/typings.d.ts b/src/services/ant-design-pro/typings.d.ts
new file mode 100644
index 0000000..4ee1264
--- /dev/null
+++ b/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/src/services/monitor/cache.ts b/src/services/monitor/cache.ts
new file mode 100644
index 0000000..244368e
--- /dev/null
+++ b/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/src/services/monitor/cachelist.ts b/src/services/monitor/cachelist.ts
new file mode 100644
index 0000000..d348d4b
--- /dev/null
+++ b/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/src/services/monitor/job.ts b/src/services/monitor/job.ts
new file mode 100644
index 0000000..192f819
--- /dev/null
+++ b/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/src/services/monitor/jobLog.ts b/src/services/monitor/jobLog.ts
new file mode 100644
index 0000000..9885aa7
--- /dev/null
+++ b/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/src/services/monitor/logininfor.ts b/src/services/monitor/logininfor.ts
new file mode 100644
index 0000000..ea1864a
--- /dev/null
+++ b/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/src/services/monitor/online.ts b/src/services/monitor/online.ts
new file mode 100644
index 0000000..ae2a6ac
--- /dev/null
+++ b/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/src/services/monitor/operlog.ts b/src/services/monitor/operlog.ts
new file mode 100644
index 0000000..004863a
--- /dev/null
+++ b/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/src/services/monitor/server.ts b/src/services/monitor/server.ts
new file mode 100644
index 0000000..24a849f
--- /dev/null
+++ b/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/src/services/session.ts b/src/services/session.ts
new file mode 100644
index 0000000..8b4e0d3
--- /dev/null
+++ b/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/src/services/swagger/index.ts b/src/services/swagger/index.ts
new file mode 100644
index 0000000..83cf97c
--- /dev/null
+++ b/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/src/services/swagger/pet.ts b/src/services/swagger/pet.ts
new file mode 100644
index 0000000..b887475
--- /dev/null
+++ b/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/src/services/swagger/store.ts b/src/services/swagger/store.ts
new file mode 100644
index 0000000..b9c689a
--- /dev/null
+++ b/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/src/services/swagger/typings.d.ts b/src/services/swagger/typings.d.ts
new file mode 100644
index 0000000..d06bcfc
--- /dev/null
+++ b/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/src/services/swagger/user.ts b/src/services/swagger/user.ts
new file mode 100644
index 0000000..4dd6f42
--- /dev/null
+++ b/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/src/services/system/auth.ts b/src/services/system/auth.ts
new file mode 100644
index 0000000..b757618
--- /dev/null
+++ b/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/src/services/system/config.ts b/src/services/system/config.ts
new file mode 100644
index 0000000..9843daf
--- /dev/null
+++ b/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/src/services/system/dept.ts b/src/services/system/dept.ts
new file mode 100644
index 0000000..ad58807
--- /dev/null
+++ b/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/src/services/system/dict.ts b/src/services/system/dict.ts
new file mode 100644
index 0000000..5671124
--- /dev/null
+++ b/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/src/services/system/dictdata.ts b/src/services/system/dictdata.ts
new file mode 100644
index 0000000..f7856e8
--- /dev/null
+++ b/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/src/services/system/index.ts b/src/services/system/index.ts
new file mode 100644
index 0000000..24ce62d
--- /dev/null
+++ b/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/src/services/system/menu.ts b/src/services/system/menu.ts
new file mode 100644
index 0000000..b2f411b
--- /dev/null
+++ b/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/src/services/system/notice.ts b/src/services/system/notice.ts
new file mode 100644
index 0000000..2c86a08
--- /dev/null
+++ b/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/src/services/system/post.ts b/src/services/system/post.ts
new file mode 100644
index 0000000..3170b89
--- /dev/null
+++ b/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/src/services/system/role.ts b/src/services/system/role.ts
new file mode 100644
index 0000000..3836243
--- /dev/null
+++ b/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/src/services/system/user.ts b/src/services/system/user.ts
new file mode 100644
index 0000000..da997bf
--- /dev/null
+++ b/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/src/services/typings.d.ts b/src/services/typings.d.ts
new file mode 100644
index 0000000..468320e
--- /dev/null
+++ b/src/services/typings.d.ts
@@ -0,0 +1,192 @@
+/* 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/src/types/monitor/cache.d.ts b/src/types/monitor/cache.d.ts
new file mode 100644
index 0000000..bfbab1a
--- /dev/null
+++ b/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/src/types/monitor/cacheList.d.ts b/src/types/monitor/cacheList.d.ts
new file mode 100644
index 0000000..e2de8cd
--- /dev/null
+++ b/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/src/types/monitor/job.d.ts b/src/types/monitor/job.d.ts
new file mode 100644
index 0000000..5b1c965
--- /dev/null
+++ b/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/src/types/monitor/jobLog.d.ts b/src/types/monitor/jobLog.d.ts
new file mode 100644
index 0000000..e62cb9f
--- /dev/null
+++ b/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/src/types/monitor/logininfor.d.ts b/src/types/monitor/logininfor.d.ts
new file mode 100644
index 0000000..ef9208a
--- /dev/null
+++ b/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/src/types/monitor/online.d.ts b/src/types/monitor/online.d.ts
new file mode 100644
index 0000000..5b8e0dd
--- /dev/null
+++ b/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/src/types/monitor/operlog.d.ts b/src/types/monitor/operlog.d.ts
new file mode 100644
index 0000000..96ee27e
--- /dev/null
+++ b/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/src/types/monitor/server.d.ts b/src/types/monitor/server.d.ts
new file mode 100644
index 0000000..8a32d16
--- /dev/null
+++ b/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/src/types/system/config.d.ts b/src/types/system/config.d.ts
new file mode 100644
index 0000000..7e7e43e
--- /dev/null
+++ b/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/src/types/system/dept.d.ts b/src/types/system/dept.d.ts
new file mode 100644
index 0000000..14ad274
--- /dev/null
+++ b/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/src/types/system/dict-data.d.ts b/src/types/system/dict-data.d.ts
new file mode 100644
index 0000000..30373f5
--- /dev/null
+++ b/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/src/types/system/dict.d.ts b/src/types/system/dict.d.ts
new file mode 100644
index 0000000..eef354a
--- /dev/null
+++ b/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/src/types/system/menu.d.ts b/src/types/system/menu.d.ts
new file mode 100644
index 0000000..15e68ce
--- /dev/null
+++ b/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/src/types/system/notice.d.ts b/src/types/system/notice.d.ts
new file mode 100644
index 0000000..89a77ee
--- /dev/null
+++ b/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/src/types/system/post.d.ts b/src/types/system/post.d.ts
new file mode 100644
index 0000000..772140c
--- /dev/null
+++ b/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/src/types/system/role.d.ts b/src/types/system/role.d.ts
new file mode 100644
index 0000000..1034e47
--- /dev/null
+++ b/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>;
+ }
+
+}
diff --git a/src/types/system/user.d.ts b/src/types/system/user.d.ts
new file mode 100644
index 0000000..5751956
--- /dev/null
+++ b/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/src/types/typings.d.ts b/src/types/typings.d.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/types/typings.d.ts
diff --git a/src/typings.d.ts b/src/typings.d.ts
new file mode 100644
index 0000000..742f70c
--- /dev/null
+++ b/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/src/utils/IconUtil.ts b/src/utils/IconUtil.ts
new file mode 100644
index 0000000..31102ee
--- /dev/null
+++ b/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/src/utils/downloadfile.ts b/src/utils/downloadfile.ts
new file mode 100644
index 0000000..5d56cda
--- /dev/null
+++ b/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/src/utils/options.ts b/src/utils/options.ts
new file mode 100644
index 0000000..79ee803
--- /dev/null
+++ b/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/src/utils/permission.ts b/src/utils/permission.ts
new file mode 100644
index 0000000..2b39e48
--- /dev/null
+++ b/src/utils/permission.ts
@@ -0,0 +1,67 @@
+// /**
+// * 字符权限校验
+// * @param {Array} value 校验值
+// * @returns {Boolean}
+
+// import Role = API.System.Role;
+
+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 roles
+ * @param {Array} value 校验值
+ * @returns {Boolean}
+ */
+export function checkRole(roles: 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/src/utils/tree.ts b/src/utils/tree.ts
new file mode 100644
index 0000000..7f1a2e5
--- /dev/null
+++ b/src/utils/tree.ts
@@ -0,0 +1,95 @@
+import { DataNode } from 'antd/es/tree';
+import { parse } from 'querystring';
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {*} id id字段 默认 'id'
+ * @param name
+ * @param {*} parentId 父节点字段 默认 'parentId'
+ * @param parentName
+ * @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;
+}