前端重构

This commit is contained in:
Yvan 2026-01-08 12:23:58 +08:00
parent b988fcc0e1
commit f4fcdaa322
10 changed files with 207 additions and 53 deletions

View File

@ -83,9 +83,9 @@ export default defineConfig({
* @name layout
* @doc https://umijs.org/docs/max/layout-menu
*/
title: "Ant Design Pro",
title: "KRA Admin",
layout: {
locale: true,
locale: false, // 禁用菜单国际化,与 GVA 保持一致
...defaultSettings,
},
/**
@ -100,14 +100,9 @@ export default defineConfig({
/**
* @name
* @doc https://umijs.org/docs/max/i18n
* @description GVA
*/
locale: {
// default zh-CN
default: "zh-CN",
antd: true,
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
},
locale: false,
/**
* @name antd
* @description babel import

View File

@ -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

View File

@ -2,15 +2,22 @@
* KRA - App Configuration
*
*/
import { AvatarDropdown, AvatarName, Footer, Question } from '@/components';
import { AvatarDropdown, AvatarName, Footer } from '@/components';
import { getUserInfo } from '@/services/kratos/user';
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 { SettingDrawer } from '@ant-design/pro-components';
import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
import { history } from '@umijs/max';
import { message } from 'antd';
import { message, Tooltip } from 'antd';
import defaultSettings from '../config/defaultSettings';
import { errorConfig } from './requestErrorConfig';
@ -90,13 +97,93 @@ export async function getInitialState(): Promise<{
* @see https://procomponents.ant.design/components/layout
*/
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 {
// 先展开 settings让后面的配置可以覆盖
...initialState?.settings,
// 右上角操作区
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,
title: <AvatarName />,
icon: <UserOutlined />,
render: (_, avatarChildren) => <AvatarDropdown>{avatarChildren}</AvatarDropdown>,
render: (_, avatarChildren) => <AvatarDropdown menu>{avatarChildren}</AvatarDropdown>,
},
// 水印

View File

@ -21,7 +21,7 @@
*/
// 默认头像
export const defaultAvatar = '/logo.svg';
export const defaultAvatar = '/default-avatar.svg';
// Logo
export const logo = '/logo.svg';

View File

@ -32,7 +32,7 @@ const HeaderDropdown: React.FC<HeaderDropdownProps> = ({
const { styles } = useStyles();
return (
<Dropdown
overlayClassName={classNames(styles.dropdown, cls)}
classNames={{ root: classNames(styles.dropdown, cls) }}
{...restProps}
/>
);

View File

@ -1,12 +1,14 @@
import { createAdminService } from "@/services/index";
import { setUserAuthority } from "@/services/kratos/user";
import {
LogoutOutlined,
SettingOutlined,
SwapOutlined,
UserOutlined,
} from "@ant-design/icons";
import { history, useModel } from "@umijs/max";
import type { MenuProps } from "antd";
import { Spin } from "antd";
import { message, Spin } from "antd";
import { createStyles } from "antd-style";
import React from "react";
import { flushSync } from "react-dom";
@ -47,8 +49,11 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
menu,
children,
}) => {
const { styles } = useStyles();
const { initialState, setInitialState } = useModel("@@initialState");
/**
* 退 url
* 退
*/
const loginOut = async () => {
await adminService.Logout({});
@ -57,9 +62,7 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
const searchParams = new URLSearchParams({
redirect: pathname + search,
});
/** 此方法会跳转到 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",
@ -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 { key } = event;
@ -80,6 +101,15 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
loginOut();
return;
}
if (key === "person") {
history.push("/person");
return;
}
if (key.startsWith("switch_")) {
const authorityId = key.replace("switch_", "");
changeUserAuth(authorityId);
return;
}
history.push(`/account/${key}`);
};
@ -105,30 +135,67 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
return loading;
}
const menuItems = [
...(menu
? [
{
key: "center",
icon: <UserOutlined />,
label: "个人中心",
},
{
key: "settings",
icon: <SettingOutlined />,
label: "个人设置",
},
{
type: "divider" as const,
},
]
: []),
{
key: "logout",
icon: <LogoutOutlined />,
label: "退出登录",
},
];
// 构建菜单项
const menuItems: MenuProps['items'] = [];
// 当前角色显示
if (currentUser.authority) {
menuItems.push({
key: "current_role",
label: (
<span style={{ fontWeight: 'bold' }}>
{currentUser.authority.authorityName}
</span>
),
disabled: true,
});
}
// 切换角色选项
if (currentUser.authorities && currentUser.authorities.length > 1) {
const otherAuthorities = currentUser.authorities.filter(
(auth: any) => auth.authorityId !== currentUser.authority?.authorityId
);
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 (
<HeaderDropdown

View File

@ -30,5 +30,5 @@ export * from './ExportExcel';
// Layout components
export { default as Footer } from './Footer';
export { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown';
export { Question, SelectLang } from './RightContent';
export { SelectLang } from './RightContent';
export { default as HeaderDropdown } from './HeaderDropdown';

View File

@ -9,8 +9,8 @@ const Banner: React.FC = () => {
return (
<Card
className="kra-dashboard-card"
bodyStyle={{ padding: 0, height: '160px', overflow: 'hidden' }}
bordered={false}
styles={{ body: { padding: 0, height: '160px', overflow: 'hidden' } }}
variant="borderless"
>
<div style={{
height: '100%',

View File

@ -24,8 +24,8 @@ const Charts: React.FC = () => {
<Card
title="内容数据"
className="kra-dashboard-card"
bordered={false}
bodyStyle={{ height: '320px' }}
variant="borderless"
styles={{ body: { height: '320px' } }}
>
<LineChart
xAxisData={chartData.xAxis}

View File

@ -39,7 +39,7 @@ const StatCards: React.FC = () => {
<Row gutter={[16, 16]}>
{stats.map((stat, 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={{
@ -60,7 +60,7 @@ const StatCards: React.FC = () => {
title={stat.title}
value={stat.value}
suffix={stat.suffix}
valueStyle={{ color: stat.color }}
styles={{ content: { color: stat.color } }}
/>
</div>
</Card>