flat:初始化

This commit is contained in:
史典卓
2024-11-18 16:38:38 +08:00
commit 903776f4b9
242 changed files with 24577 additions and 0 deletions

55
src/access.ts Normal file
View 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
View 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
// },
],
};

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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

View 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;
}

View 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>
);
};

View 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>
);
};

View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
export enum PageEnum {
LOGIN = '/user/login'
}

53
src/global.less Normal file
View 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
View 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
View 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
View 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
View 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',
};

View File

@@ -0,0 +1,5 @@
export default {
'component.tagSelect.expand': 'Expand',
'component.tagSelect.collapse': 'Collapse',
'component.tagSelect.all': 'All',
};

View 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
View 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',
};

View 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
View 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',
};

View 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 successplease replace defaultSettings in src/models/setting.js',
'app.setting.production.hint':
'Setting panel shows in development environment only, please manually modify',
};

View 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
View 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
View 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': '匹配度',
};

View File

@@ -0,0 +1,5 @@
export default {
'component.tagSelect.expand': '展开',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
};

View 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
View 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': '拓扑编辑器',
};

View 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': '创建时间',
};

View 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': '任务详情',
};

View 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': '解锁',
};

View 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': '强制退出',
};

View 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': '操作模块',
};

View 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': '使用率',
};

View 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
View File

@@ -0,0 +1,6 @@
export default {
'app.pwa.offline': '当前处于离线状态',
'app.pwa.serviceworker.updated': '有新内容',
'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
'app.pwa.serviceworker.updated.ok': '刷新',
};

View 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':
'配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
};

View 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': '关',
};

View 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': '刷新缓存',
};

View 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': '更新时间',
};

View 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': '备注',
};

View 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': '备注',
};

View 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': '备注',
};

View 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': '备注',
};

View 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': '备注',
};

View 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': '批量取消授权',
};

View 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
View 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,
};

View File

@@ -0,0 +1,5 @@
export default {
'component.tagSelect.expand': '展開',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
};

View 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
View 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': '拓撲編輯器',
};

View 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
View File

@@ -0,0 +1,6 @@
export default {
'app.pwa.offline': '當前處於離線狀態',
'app.pwa.serviceworker.updated': '有新內容',
'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
'app.pwa.serviceworker.updated.ok': '刷新',
};

View 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':
'配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件',
};

View 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
View 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
View 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;

View 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;

View File

@@ -0,0 +1,10 @@
/* *
*
* @author whiteshader@163.com
* @datetime 2021/09/16
*
* */

View 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;
}
}

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -0,0 +1,11 @@
/* *
*
* @author whiteshader@163.com
* @datetime 2021/09/16
*
* */
.card {
margin-bottom: 12px;
}

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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