flat:初始化
This commit is contained in:
55
src/access.ts
Normal file
55
src/access.ts
Normal file
@@ -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');
|
||||
}
|
||||
233
src/app.tsx
Normal file
233
src/app.tsx
Normal file
@@ -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
|
||||
// },
|
||||
],
|
||||
};
|
||||
115
src/components/DictTag/index.tsx
Normal file
115
src/components/DictTag/index.tsx
Normal file
@@ -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;
|
||||
35
src/components/Footer/index.tsx
Normal file
35
src/components/Footer/index.tsx
Normal file
@@ -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;
|
||||
27
src/components/HeaderDropdown/index.tsx
Normal file
27
src/components/HeaderDropdown/index.tsx
Normal file
@@ -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;
|
||||
63
src/components/IconSelector/Category.tsx
Normal file
63
src/components/IconSelector/Category.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as React from 'react';
|
||||
import CopyableIcon from './CopyableIcon';
|
||||
import type { ThemeType } from './index';
|
||||
import type { CategoriesKeys } from './fields';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import styles from './style.less';
|
||||
|
||||
interface CategoryProps {
|
||||
title: CategoriesKeys;
|
||||
icons: string[];
|
||||
theme: ThemeType;
|
||||
newIcons: string[];
|
||||
onSelect: (type: string, name: string) => any;
|
||||
}
|
||||
|
||||
const Category: React.FC<CategoryProps> = props => {
|
||||
|
||||
const { icons, title, newIcons, theme } = props;
|
||||
const intl = useIntl();
|
||||
const [justCopied, setJustCopied] = React.useState<string | null>(null);
|
||||
const copyId = React.useRef<NodeJS.Timeout | null>(null);
|
||||
const onSelect = React.useCallback((type: string, text: string) => {
|
||||
const { onSelect } = props;
|
||||
if (onSelect) {
|
||||
onSelect(type, text);
|
||||
}
|
||||
setJustCopied(type);
|
||||
copyId.current = setTimeout(() => {
|
||||
setJustCopied(null);
|
||||
}, 2000);
|
||||
}, []);
|
||||
React.useEffect(
|
||||
() => () => {
|
||||
if (copyId.current) {
|
||||
clearTimeout(copyId.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>{intl.formatMessage({
|
||||
id: `app.docs.components.icon.category.${title}`,
|
||||
defaultMessage: '信息',
|
||||
})}</h4>
|
||||
<ul className={styles.anticonsList}>
|
||||
{icons.map(name => (
|
||||
<CopyableIcon
|
||||
key={name}
|
||||
name={name}
|
||||
theme={theme}
|
||||
isNew={newIcons.includes(name)}
|
||||
justCopied={justCopied}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Category;
|
||||
47
src/components/IconSelector/CopyableIcon.tsx
Normal file
47
src/components/IconSelector/CopyableIcon.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as React from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import * as AntdIcons from '@ant-design/icons';
|
||||
import type { ThemeType } from './index';
|
||||
import styles from './style.less';
|
||||
|
||||
const allIcons: {
|
||||
[key: string]: any;
|
||||
} = AntdIcons;
|
||||
|
||||
export interface CopyableIconProps {
|
||||
name: string;
|
||||
isNew: boolean;
|
||||
theme: ThemeType;
|
||||
justCopied: string | null;
|
||||
onSelect: (type: string, text: string) => any;
|
||||
}
|
||||
|
||||
const CopyableIcon: React.FC<CopyableIconProps> = ({
|
||||
name,
|
||||
justCopied,
|
||||
onSelect,
|
||||
theme,
|
||||
}) => {
|
||||
const className = classNames({
|
||||
copied: justCopied === name,
|
||||
[theme]: !!theme,
|
||||
});
|
||||
return (
|
||||
<li className={className}
|
||||
onClick={() => {
|
||||
if (onSelect) {
|
||||
onSelect(theme, name);
|
||||
}
|
||||
}}>
|
||||
<Tooltip title={name}>
|
||||
{React.createElement(allIcons[name], { className: styles.anticon })}
|
||||
</Tooltip>
|
||||
{/* <span className={styles.anticonClass}>
|
||||
<Badge dot={isNew}>{name}</Badge>
|
||||
</span> */}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyableIcon;
|
||||
233
src/components/IconSelector/IconPicSearcher.tsx
Normal file
233
src/components/IconSelector/IconPicSearcher.tsx
Normal file
@@ -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;
|
||||
223
src/components/IconSelector/fields.ts
Normal file
223
src/components/IconSelector/fields.ts
Normal file
@@ -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;
|
||||
142
src/components/IconSelector/index.tsx
Normal file
142
src/components/IconSelector/index.tsx
Normal file
@@ -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
|
||||
137
src/components/IconSelector/style.less
Normal file
137
src/components/IconSelector/style.less
Normal file
@@ -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;
|
||||
}
|
||||
41
src/components/IconSelector/themeIcons.tsx
Normal file
41
src/components/IconSelector/themeIcons.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
142
src/components/RightContent/AvatarDropdown.tsx
Normal file
142
src/components/RightContent/AvatarDropdown.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
31
src/components/RightContent/index.tsx
Normal file
31
src/components/RightContent/index.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
12
src/components/index.ts
Normal file
12
src/components/index.ts
Normal file
@@ -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 };
|
||||
31
src/enums/httpEnum.ts
Normal file
31
src/enums/httpEnum.ts
Normal file
@@ -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',
|
||||
}
|
||||
4
src/enums/pagesEnums.ts
Normal file
4
src/enums/pagesEnums.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export enum PageEnum {
|
||||
LOGIN = '/user/login'
|
||||
}
|
||||
53
src/global.less
Normal file
53
src/global.less
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/global.tsx
Normal file
91
src/global.tsx
Normal file
@@ -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();
|
||||
}
|
||||
7
src/hooks/net/dict.ts
Normal file
7
src/hooks/net/dict.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { getDictValueEnum } from "@/services/system/dict";
|
||||
|
||||
export function useDictEnum(name: string)
|
||||
{
|
||||
const data = getDictValueEnum(name);
|
||||
return data;
|
||||
}
|
||||
27
src/locales/en-US.ts
Normal file
27
src/locales/en-US.ts
Normal file
@@ -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,
|
||||
};
|
||||
26
src/locales/en-US/app.ts
Normal file
26
src/locales/en-US/app.ts
Normal file
@@ -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',
|
||||
};
|
||||
5
src/locales/en-US/component.ts
Normal file
5
src/locales/en-US/component.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
'component.tagSelect.expand': 'Expand',
|
||||
'component.tagSelect.collapse': 'Collapse',
|
||||
'component.tagSelect.all': 'All',
|
||||
};
|
||||
17
src/locales/en-US/globalHeader.ts
Normal file
17
src/locales/en-US/globalHeader.ts
Normal file
@@ -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',
|
||||
};
|
||||
52
src/locales/en-US/menu.ts
Normal file
52
src/locales/en-US/menu.ts
Normal file
@@ -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',
|
||||
};
|
||||
71
src/locales/en-US/pages.ts
Normal file
71
src/locales/en-US/pages.ts
Normal file
@@ -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',
|
||||
};
|
||||
6
src/locales/en-US/pwa.ts
Normal file
6
src/locales/en-US/pwa.ts
Normal file
@@ -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',
|
||||
};
|
||||
31
src/locales/en-US/settingDrawer.ts
Normal file
31
src/locales/en-US/settingDrawer.ts
Normal file
@@ -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',
|
||||
};
|
||||
60
src/locales/en-US/settings.ts
Normal file
60
src/locales/en-US/settings.ts
Normal file
@@ -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',
|
||||
};
|
||||
57
src/locales/zh-CN.ts
Normal file
57
src/locales/zh-CN.ts
Normal file
@@ -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,
|
||||
};
|
||||
23
src/locales/zh-CN/app.ts
Normal file
23
src/locales/zh-CN/app.ts
Normal file
@@ -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': '匹配度',
|
||||
};
|
||||
5
src/locales/zh-CN/component.ts
Normal file
5
src/locales/zh-CN/component.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
'component.tagSelect.expand': '展开',
|
||||
'component.tagSelect.collapse': '收起',
|
||||
'component.tagSelect.all': '全部',
|
||||
};
|
||||
17
src/locales/zh-CN/globalHeader.ts
Normal file
17
src/locales/zh-CN/globalHeader.ts
Normal file
@@ -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': '查看更多',
|
||||
};
|
||||
52
src/locales/zh-CN/menu.ts
Normal file
52
src/locales/zh-CN/menu.ts
Normal file
@@ -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': '拓扑编辑器',
|
||||
};
|
||||
18
src/locales/zh-CN/monitor/job-log.ts
Normal file
18
src/locales/zh-CN/monitor/job-log.ts
Normal file
@@ -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': '创建时间',
|
||||
};
|
||||
25
src/locales/zh-CN/monitor/job.ts
Normal file
25
src/locales/zh-CN/monitor/job.ts
Normal file
@@ -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': '任务详情',
|
||||
};
|
||||
13
src/locales/zh-CN/monitor/logininfor.ts
Normal file
13
src/locales/zh-CN/monitor/logininfor.ts
Normal file
@@ -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': '解锁',
|
||||
};
|
||||
20
src/locales/zh-CN/monitor/onlineUser.ts
Normal file
20
src/locales/zh-CN/monitor/onlineUser.ts
Normal file
@@ -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': '强制退出',
|
||||
};
|
||||
19
src/locales/zh-CN/monitor/operlog.ts
Normal file
19
src/locales/zh-CN/monitor/operlog.ts
Normal file
@@ -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': '操作模块',
|
||||
};
|
||||
27
src/locales/zh-CN/monitor/server.ts
Normal file
27
src/locales/zh-CN/monitor/server.ts
Normal file
@@ -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': '使用率',
|
||||
};
|
||||
71
src/locales/zh-CN/pages.ts
Normal file
71
src/locales/zh-CN/pages.ts
Normal file
@@ -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': '批量审批',
|
||||
};
|
||||
6
src/locales/zh-CN/pwa.ts
Normal file
6
src/locales/zh-CN/pwa.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
'app.pwa.offline': '当前处于离线状态',
|
||||
'app.pwa.serviceworker.updated': '有新内容',
|
||||
'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
|
||||
'app.pwa.serviceworker.updated.ok': '刷新',
|
||||
};
|
||||
31
src/locales/zh-CN/settingDrawer.ts
Normal file
31
src/locales/zh-CN/settingDrawer.ts
Normal file
@@ -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':
|
||||
'配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
|
||||
};
|
||||
55
src/locales/zh-CN/settings.ts
Normal file
55
src/locales/zh-CN/settings.ts
Normal file
@@ -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': '关',
|
||||
};
|
||||
14
src/locales/zh-CN/system/config.ts
Normal file
14
src/locales/zh-CN/system/config.ts
Normal file
@@ -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': '刷新缓存',
|
||||
};
|
||||
18
src/locales/zh-CN/system/dept.ts
Normal file
18
src/locales/zh-CN/system/dept.ts
Normal file
@@ -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': '更新时间',
|
||||
};
|
||||
17
src/locales/zh-CN/system/dict-data.ts
Normal file
17
src/locales/zh-CN/system/dict-data.ts
Normal file
@@ -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': '备注',
|
||||
};
|
||||
12
src/locales/zh-CN/system/dict.ts
Normal file
12
src/locales/zh-CN/system/dict.ts
Normal file
@@ -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': '备注',
|
||||
};
|
||||
22
src/locales/zh-CN/system/menu.ts
Normal file
22
src/locales/zh-CN/system/menu.ts
Normal file
@@ -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': '备注',
|
||||
};
|
||||
13
src/locales/zh-CN/system/notice.ts
Normal file
13
src/locales/zh-CN/system/notice.ts
Normal file
@@ -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': '备注',
|
||||
};
|
||||
13
src/locales/zh-CN/system/post.ts
Normal file
13
src/locales/zh-CN/system/post.ts
Normal file
@@ -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': '备注',
|
||||
};
|
||||
21
src/locales/zh-CN/system/role.ts
Normal file
21
src/locales/zh-CN/system/role.ts
Normal file
@@ -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': '批量取消授权',
|
||||
};
|
||||
31
src/locales/zh-CN/system/user.ts
Normal file
31
src/locales/zh-CN/system/user.ts
Normal file
@@ -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': '修改头像',
|
||||
};
|
||||
20
src/locales/zh-TW.ts
Normal file
20
src/locales/zh-TW.ts
Normal file
@@ -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,
|
||||
};
|
||||
5
src/locales/zh-TW/component.ts
Normal file
5
src/locales/zh-TW/component.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
'component.tagSelect.expand': '展開',
|
||||
'component.tagSelect.collapse': '收起',
|
||||
'component.tagSelect.all': '全部',
|
||||
};
|
||||
17
src/locales/zh-TW/globalHeader.ts
Normal file
17
src/locales/zh-TW/globalHeader.ts
Normal file
@@ -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': '查看更多',
|
||||
};
|
||||
52
src/locales/zh-TW/menu.ts
Normal file
52
src/locales/zh-TW/menu.ts
Normal file
@@ -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': '拓撲編輯器',
|
||||
};
|
||||
68
src/locales/zh-TW/pages.ts
Normal file
68
src/locales/zh-TW/pages.ts
Normal file
@@ -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': '批量審批',
|
||||
};
|
||||
6
src/locales/zh-TW/pwa.ts
Normal file
6
src/locales/zh-TW/pwa.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
'app.pwa.offline': '當前處於離線狀態',
|
||||
'app.pwa.serviceworker.updated': '有新內容',
|
||||
'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
|
||||
'app.pwa.serviceworker.updated.ok': '刷新',
|
||||
};
|
||||
31
src/locales/zh-TW/settingDrawer.ts
Normal file
31
src/locales/zh-TW/settingDrawer.ts
Normal file
@@ -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':
|
||||
'配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件',
|
||||
};
|
||||
55
src/locales/zh-TW/settings.ts
Normal file
55
src/locales/zh-TW/settings.ts
Normal file
@@ -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': '關',
|
||||
};
|
||||
22
src/manifest.json
Normal file
22
src/manifest.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
src/pages/404.tsx
Normal file
18
src/pages/404.tsx
Normal file
@@ -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;
|
||||
240
src/pages/Monitor/Cache/List.tsx
Normal file
240
src/pages/Monitor/Cache/List.tsx
Normal file
@@ -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;
|
||||
10
src/pages/Monitor/Cache/List/index.less
Normal file
10
src/pages/Monitor/Cache/List/index.less
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
|
||||
/* *
|
||||
*
|
||||
* @author whiteshader@163.com
|
||||
* @datetime 2021/09/16
|
||||
*
|
||||
* */
|
||||
|
||||
33
src/pages/Monitor/Cache/index.less
Normal file
33
src/pages/Monitor/Cache/index.less
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
201
src/pages/Monitor/Cache/index.tsx
Normal file
201
src/pages/Monitor/Cache/index.tsx
Normal file
@@ -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;
|
||||
31
src/pages/Monitor/Druid/index.tsx
Normal file
31
src/pages/Monitor/Druid/index.tsx
Normal file
@@ -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;
|
||||
131
src/pages/Monitor/Job/detail.tsx
Normal file
131
src/pages/Monitor/Job/detail.tsx
Normal file
@@ -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;
|
||||
232
src/pages/Monitor/Job/edit.tsx
Normal file
232
src/pages/Monitor/Job/edit.tsx
Normal file
@@ -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;
|
||||
460
src/pages/Monitor/Job/index.tsx
Normal file
460
src/pages/Monitor/Job/index.tsx
Normal file
@@ -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;
|
||||
105
src/pages/Monitor/JobLog/detail.tsx
Normal file
105
src/pages/Monitor/JobLog/detail.tsx
Normal file
@@ -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;
|
||||
343
src/pages/Monitor/JobLog/index.tsx
Normal file
343
src/pages/Monitor/JobLog/index.tsx
Normal file
@@ -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;
|
||||
216
src/pages/Monitor/Logininfor/edit.tsx
Normal file
216
src/pages/Monitor/Logininfor/edit.tsx
Normal file
@@ -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;
|
||||
335
src/pages/Monitor/Logininfor/index.tsx
Normal file
335
src/pages/Monitor/Logininfor/index.tsx
Normal file
@@ -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;
|
||||
162
src/pages/Monitor/Online/index.tsx
Normal file
162
src/pages/Monitor/Online/index.tsx
Normal file
@@ -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;
|
||||
113
src/pages/Monitor/Operlog/detail.tsx
Normal file
113
src/pages/Monitor/Operlog/detail.tsx
Normal file
@@ -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;
|
||||
365
src/pages/Monitor/Operlog/index.tsx
Normal file
365
src/pages/Monitor/Operlog/index.tsx
Normal file
@@ -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;
|
||||
267
src/pages/Monitor/Server/index.tsx
Normal file
267
src/pages/Monitor/Server/index.tsx
Normal file
@@ -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;
|
||||
11
src/pages/Monitor/Server/style.less
Normal file
11
src/pages/Monitor/Server/style.less
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
/* *
|
||||
*
|
||||
* @author whiteshader@163.com
|
||||
* @datetime 2021/09/16
|
||||
*
|
||||
* */
|
||||
|
||||
.card {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
172
src/pages/System/Config/edit.tsx
Normal file
172
src/pages/System/Config/edit.tsx
Normal file
@@ -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;
|
||||
397
src/pages/System/Config/index.tsx
Normal file
397
src/pages/System/Config/index.tsx
Normal file
@@ -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;
|
||||
212
src/pages/System/Dept/edit.tsx
Normal file
212
src/pages/System/Dept/edit.tsx
Normal file
@@ -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;
|
||||
346
src/pages/System/Dept/index.tsx
Normal file
346
src/pages/System/Dept/index.tsx
Normal file
@@ -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;
|
||||
152
src/pages/System/Dict/edit.tsx
Normal file
152
src/pages/System/Dict/edit.tsx
Normal file
@@ -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;
|
||||
394
src/pages/System/Dict/index.tsx
Normal file
394
src/pages/System/Dict/index.tsx
Normal file
@@ -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;
|
||||
252
src/pages/System/DictData/edit.tsx
Normal file
252
src/pages/System/DictData/edit.tsx
Normal file
@@ -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;
|
||||
439
src/pages/System/DictData/index.tsx
Normal file
439
src/pages/System/DictData/index.tsx
Normal file
@@ -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;
|
||||
216
src/pages/System/Logininfor/edit.tsx
Normal file
216
src/pages/System/Logininfor/edit.tsx
Normal file
@@ -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;
|
||||
321
src/pages/System/Logininfor/index.tsx
Normal file
321
src/pages/System/Logininfor/index.tsx
Normal file
@@ -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;
|
||||
386
src/pages/System/Menu/edit.tsx
Normal file
386
src/pages/System/Menu/edit.tsx
Normal file
@@ -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;
|
||||
339
src/pages/System/Menu/index.tsx
Normal file
339
src/pages/System/Menu/index.tsx
Normal file
@@ -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;
|
||||
174
src/pages/System/Notice/edit.tsx
Normal file
174
src/pages/System/Notice/edit.tsx
Normal file
@@ -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;
|
||||
366
src/pages/System/Notice/index.tsx
Normal file
366
src/pages/System/Notice/index.tsx
Normal file
@@ -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;
|
||||
115
src/pages/System/Operlog/detail.tsx
Normal file
115
src/pages/System/Operlog/detail.tsx
Normal file
@@ -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;
|
||||
411
src/pages/System/Operlog/index.tsx
Normal file
411
src/pages/System/Operlog/index.tsx
Normal file
@@ -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;
|
||||
166
src/pages/System/Post/edit.tsx
Normal file
166
src/pages/System/Post/edit.tsx
Normal file
@@ -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;
|
||||
366
src/pages/System/Post/index.tsx
Normal file
366
src/pages/System/Post/index.tsx
Normal file
@@ -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;
|
||||
274
src/pages/System/Role/authUser.tsx
Normal file
274
src/pages/System/Role/authUser.tsx
Normal file
@@ -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;
|
||||
233
src/pages/System/Role/components/DataScope.tsx
Normal file
233
src/pages/System/Role/components/DataScope.tsx
Normal file
@@ -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;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user