前端重构
This commit is contained in:
parent
b988fcc0e1
commit
f4fcdaa322
|
|
@ -83,9 +83,9 @@ export default defineConfig({
|
||||||
* @name layout 插件
|
* @name layout 插件
|
||||||
* @doc https://umijs.org/docs/max/layout-menu
|
* @doc https://umijs.org/docs/max/layout-menu
|
||||||
*/
|
*/
|
||||||
title: "Ant Design Pro",
|
title: "KRA Admin",
|
||||||
layout: {
|
layout: {
|
||||||
locale: true,
|
locale: false, // 禁用菜单国际化,与 GVA 保持一致
|
||||||
...defaultSettings,
|
...defaultSettings,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|
@ -100,14 +100,9 @@ export default defineConfig({
|
||||||
/**
|
/**
|
||||||
* @name 国际化插件
|
* @name 国际化插件
|
||||||
* @doc https://umijs.org/docs/max/i18n
|
* @doc https://umijs.org/docs/max/i18n
|
||||||
|
* @description 禁用国际化,与 GVA 保持一致
|
||||||
*/
|
*/
|
||||||
locale: {
|
locale: false,
|
||||||
// default zh-CN
|
|
||||||
default: "zh-CN",
|
|
||||||
antd: true,
|
|
||||||
// default true, when it is true, will use `navigator.language` overwrite default
|
|
||||||
baseNavigator: true,
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* @name antd 插件
|
* @name antd 插件
|
||||||
* @description 内置了 babel import 插件
|
* @description 内置了 babel import 插件
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
||||||
|
<circle cx="50" cy="50" r="50" fill="#e8e8e8"/>
|
||||||
|
<circle cx="50" cy="38" r="18" fill="#bfbfbf"/>
|
||||||
|
<ellipse cx="50" cy="80" rx="30" ry="22" fill="#bfbfbf"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 254 B |
|
|
@ -2,15 +2,22 @@
|
||||||
* KRA - App Configuration
|
* KRA - App Configuration
|
||||||
* 应用运行时配置
|
* 应用运行时配置
|
||||||
*/
|
*/
|
||||||
import { AvatarDropdown, AvatarName, Footer, Question } from '@/components';
|
import { AvatarDropdown, AvatarName, Footer } from '@/components';
|
||||||
import { getUserInfo } from '@/services/kratos/user';
|
import { getUserInfo } from '@/services/kratos/user';
|
||||||
import { getToken, removeToken } from '@/utils/auth';
|
import { getToken, removeToken } from '@/utils/auth';
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
BulbOutlined,
|
||||||
|
BulbFilled,
|
||||||
|
ReloadOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
QuestionCircleOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
|
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
|
||||||
import { SettingDrawer } from '@ant-design/pro-components';
|
import { SettingDrawer } from '@ant-design/pro-components';
|
||||||
import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
|
import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
|
||||||
import { history } from '@umijs/max';
|
import { history } from '@umijs/max';
|
||||||
import { message } from 'antd';
|
import { message, Tooltip } from 'antd';
|
||||||
import defaultSettings from '../config/defaultSettings';
|
import defaultSettings from '../config/defaultSettings';
|
||||||
import { errorConfig } from './requestErrorConfig';
|
import { errorConfig } from './requestErrorConfig';
|
||||||
|
|
||||||
|
|
@ -90,13 +97,93 @@ export async function getInitialState(): Promise<{
|
||||||
* @see https://procomponents.ant.design/components/layout
|
* @see https://procomponents.ant.design/components/layout
|
||||||
*/
|
*/
|
||||||
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
|
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
|
||||||
|
// 刷新页面
|
||||||
|
const handleRefresh = () => {
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换主题
|
||||||
|
const handleToggleTheme = () => {
|
||||||
|
const newNavTheme = initialState?.settings?.navTheme === 'realDark' ? 'light' : 'realDark';
|
||||||
|
setInitialState((preInitialState) => ({
|
||||||
|
...preInitialState,
|
||||||
|
settings: {
|
||||||
|
...preInitialState?.settings,
|
||||||
|
navTheme: newNavTheme,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 先展开 settings,让后面的配置可以覆盖
|
// 先展开 settings,让后面的配置可以覆盖
|
||||||
...initialState?.settings,
|
...initialState?.settings,
|
||||||
|
|
||||||
// 右上角操作区
|
// 右上角操作区
|
||||||
actionsRender: () => {
|
actionsRender: () => {
|
||||||
return [<Question key="doc" />];
|
const isDark = initialState?.settings?.navTheme === 'realDark';
|
||||||
|
return [
|
||||||
|
// 帮助文档
|
||||||
|
<Tooltip title="帮助文档" key="doc">
|
||||||
|
<a
|
||||||
|
href="https://github.com/go-kratos/kratos"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: 'inherit',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<QuestionCircleOutlined style={{ fontSize: 16 }} />
|
||||||
|
</a>
|
||||||
|
</Tooltip>,
|
||||||
|
// 刷新
|
||||||
|
<Tooltip title="刷新" key="refresh">
|
||||||
|
<span
|
||||||
|
onClick={handleRefresh}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ReloadOutlined style={{ fontSize: 16 }} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>,
|
||||||
|
// 切换主题
|
||||||
|
<Tooltip title="切换主题" key="theme">
|
||||||
|
<span
|
||||||
|
onClick={handleToggleTheme}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isDark ? (
|
||||||
|
<BulbFilled style={{ fontSize: 16, color: '#faad14' }} />
|
||||||
|
) : (
|
||||||
|
<BulbOutlined style={{ fontSize: 16 }} />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Tooltip>,
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
// 头像配置
|
// 头像配置
|
||||||
|
|
@ -104,7 +191,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
||||||
src: initialState?.currentUser?.avatar || initialState?.currentUser?.headerImg,
|
src: initialState?.currentUser?.avatar || initialState?.currentUser?.headerImg,
|
||||||
title: <AvatarName />,
|
title: <AvatarName />,
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
render: (_, avatarChildren) => <AvatarDropdown>{avatarChildren}</AvatarDropdown>,
|
render: (_, avatarChildren) => <AvatarDropdown menu>{avatarChildren}</AvatarDropdown>,
|
||||||
},
|
},
|
||||||
|
|
||||||
// 水印
|
// 水印
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 默认头像
|
// 默认头像
|
||||||
export const defaultAvatar = '/logo.svg';
|
export const defaultAvatar = '/default-avatar.svg';
|
||||||
|
|
||||||
// Logo
|
// Logo
|
||||||
export const logo = '/logo.svg';
|
export const logo = '/logo.svg';
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const HeaderDropdown: React.FC<HeaderDropdownProps> = ({
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
overlayClassName={classNames(styles.dropdown, cls)}
|
classNames={{ root: classNames(styles.dropdown, cls) }}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import { createAdminService } from "@/services/index";
|
import { createAdminService } from "@/services/index";
|
||||||
|
import { setUserAuthority } from "@/services/kratos/user";
|
||||||
import {
|
import {
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
|
SwapOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { history, useModel } from "@umijs/max";
|
import { history, useModel } from "@umijs/max";
|
||||||
import type { MenuProps } from "antd";
|
import type { MenuProps } from "antd";
|
||||||
import { Spin } from "antd";
|
import { message, Spin } from "antd";
|
||||||
import { createStyles } from "antd-style";
|
import { createStyles } from "antd-style";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { flushSync } from "react-dom";
|
import { flushSync } from "react-dom";
|
||||||
|
|
@ -47,8 +49,11 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
menu,
|
menu,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { styles } = useStyles();
|
||||||
|
const { initialState, setInitialState } = useModel("@@initialState");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退出登录,并且将当前的 url 保存
|
* 退出登录
|
||||||
*/
|
*/
|
||||||
const loginOut = async () => {
|
const loginOut = async () => {
|
||||||
await adminService.Logout({});
|
await adminService.Logout({});
|
||||||
|
|
@ -57,9 +62,7 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
redirect: pathname + search,
|
redirect: pathname + search,
|
||||||
});
|
});
|
||||||
/** 此方法会跳转到 redirect 参数所在的位置 */
|
|
||||||
const redirect = urlParams.get("redirect");
|
const redirect = urlParams.get("redirect");
|
||||||
// Note: There may be security issues, please note
|
|
||||||
if (window.location.pathname !== "/user/login" && !redirect) {
|
if (window.location.pathname !== "/user/login" && !redirect) {
|
||||||
history.replace({
|
history.replace({
|
||||||
pathname: "/user/login",
|
pathname: "/user/login",
|
||||||
|
|
@ -67,9 +70,27 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const { styles } = useStyles();
|
|
||||||
|
|
||||||
const { initialState, setInitialState } = useModel("@@initialState");
|
/**
|
||||||
|
* 切换角色
|
||||||
|
*/
|
||||||
|
const changeUserAuth = async (authorityId: string) => {
|
||||||
|
try {
|
||||||
|
const res = await setUserAuthority({
|
||||||
|
uuid: initialState?.currentUser?.uuid || '',
|
||||||
|
authorityId
|
||||||
|
});
|
||||||
|
if (res.code === 0) {
|
||||||
|
message.success('角色切换成功');
|
||||||
|
// 标记需要关闭所有标签页并跳转首页
|
||||||
|
window.sessionStorage.setItem('needCloseAll', 'true');
|
||||||
|
window.sessionStorage.setItem('needToHome', 'true');
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('角色切换失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onMenuClick: MenuProps["onClick"] = (event) => {
|
const onMenuClick: MenuProps["onClick"] = (event) => {
|
||||||
const { key } = event;
|
const { key } = event;
|
||||||
|
|
@ -80,6 +101,15 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
loginOut();
|
loginOut();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (key === "person") {
|
||||||
|
history.push("/person");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.startsWith("switch_")) {
|
||||||
|
const authorityId = key.replace("switch_", "");
|
||||||
|
changeUserAuth(authorityId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
history.push(`/account/${key}`);
|
history.push(`/account/${key}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -105,30 +135,67 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
return loading;
|
return loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems = [
|
// 构建菜单项
|
||||||
...(menu
|
const menuItems: MenuProps['items'] = [];
|
||||||
? [
|
|
||||||
{
|
// 当前角色显示
|
||||||
key: "center",
|
if (currentUser.authority) {
|
||||||
icon: <UserOutlined />,
|
menuItems.push({
|
||||||
label: "个人中心",
|
key: "current_role",
|
||||||
},
|
label: (
|
||||||
{
|
<span style={{ fontWeight: 'bold' }}>
|
||||||
key: "settings",
|
当前角色:{currentUser.authority.authorityName}
|
||||||
icon: <SettingOutlined />,
|
</span>
|
||||||
label: "个人设置",
|
),
|
||||||
},
|
disabled: true,
|
||||||
{
|
});
|
||||||
type: "divider" as const,
|
}
|
||||||
},
|
|
||||||
]
|
// 切换角色选项
|
||||||
: []),
|
if (currentUser.authorities && currentUser.authorities.length > 1) {
|
||||||
{
|
const otherAuthorities = currentUser.authorities.filter(
|
||||||
key: "logout",
|
(auth: any) => auth.authorityId !== currentUser.authority?.authorityId
|
||||||
icon: <LogoutOutlined />,
|
);
|
||||||
label: "退出登录",
|
if (otherAuthorities.length > 0) {
|
||||||
},
|
menuItems.push({ type: "divider" as const });
|
||||||
];
|
otherAuthorities.forEach((auth: any) => {
|
||||||
|
menuItems.push({
|
||||||
|
key: `switch_${auth.authorityId}`,
|
||||||
|
icon: <SwapOutlined />,
|
||||||
|
label: `切换为:${auth.authorityName}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
menuItems.push({ type: "divider" as const });
|
||||||
|
|
||||||
|
// 个人信息
|
||||||
|
menuItems.push({
|
||||||
|
key: "person",
|
||||||
|
icon: <UserOutlined />,
|
||||||
|
label: "个人信息",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 个人设置(可选)
|
||||||
|
if (menu) {
|
||||||
|
menuItems.push({
|
||||||
|
key: "settings",
|
||||||
|
icon: <SettingOutlined />,
|
||||||
|
label: "个人设置",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
menuItems.push({ type: "divider" as const });
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
menuItems.push({
|
||||||
|
key: "logout",
|
||||||
|
icon: <LogoutOutlined />,
|
||||||
|
label: "退出登录",
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderDropdown
|
<HeaderDropdown
|
||||||
|
|
|
||||||
|
|
@ -30,5 +30,5 @@ export * from './ExportExcel';
|
||||||
// Layout components
|
// Layout components
|
||||||
export { default as Footer } from './Footer';
|
export { default as Footer } from './Footer';
|
||||||
export { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown';
|
export { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown';
|
||||||
export { Question, SelectLang } from './RightContent';
|
export { SelectLang } from './RightContent';
|
||||||
export { default as HeaderDropdown } from './HeaderDropdown';
|
export { default as HeaderDropdown } from './HeaderDropdown';
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ const Banner: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className="kra-dashboard-card"
|
className="kra-dashboard-card"
|
||||||
bodyStyle={{ padding: 0, height: '160px', overflow: 'hidden' }}
|
styles={{ body: { padding: 0, height: '160px', overflow: 'hidden' } }}
|
||||||
bordered={false}
|
variant="borderless"
|
||||||
>
|
>
|
||||||
<div style={{
|
<div style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ const Charts: React.FC = () => {
|
||||||
<Card
|
<Card
|
||||||
title="内容数据"
|
title="内容数据"
|
||||||
className="kra-dashboard-card"
|
className="kra-dashboard-card"
|
||||||
bordered={false}
|
variant="borderless"
|
||||||
bodyStyle={{ height: '320px' }}
|
styles={{ body: { height: '320px' } }}
|
||||||
>
|
>
|
||||||
<LineChart
|
<LineChart
|
||||||
xAxisData={chartData.xAxis}
|
xAxisData={chartData.xAxis}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ const StatCards: React.FC = () => {
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{stats.map((stat, index) => (
|
{stats.map((stat, index) => (
|
||||||
<Col xs={24} sm={8} key={index}>
|
<Col xs={24} sm={8} key={index}>
|
||||||
<Card className="kra-dashboard-card" bordered={false}>
|
<Card className="kra-dashboard-card" variant="borderless">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -60,7 +60,7 @@ const StatCards: React.FC = () => {
|
||||||
title={stat.title}
|
title={stat.title}
|
||||||
value={stat.value}
|
value={stat.value}
|
||||||
suffix={stat.suffix}
|
suffix={stat.suffix}
|
||||||
valueStyle={{ color: stat.color }}
|
styles={{ content: { color: stat.color } }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue