前端重构
This commit is contained in:
parent
b988fcc0e1
commit
f4fcdaa322
|
|
@ -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 插件
|
||||
|
|
|
|||
|
|
@ -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
|
||||
* 应用运行时配置
|
||||
*/
|
||||
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>,
|
||||
},
|
||||
|
||||
// 水印
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
|
||||
// 默认头像
|
||||
export const defaultAvatar = '/logo.svg';
|
||||
export const defaultAvatar = '/default-avatar.svg';
|
||||
|
||||
// Logo
|
||||
export const logo = '/logo.svg';
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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%',
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue