chore: 📌 优化后台风格显示
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Layout, Menu, Button, theme, App } from 'antd';
|
import { Layout, Menu, Button, theme, App } from 'antd';
|
||||||
import {
|
import {
|
||||||
MenuFoldOutlined,
|
MenuFoldOutlined,
|
||||||
@@ -6,9 +6,12 @@ import {
|
|||||||
DashboardOutlined,
|
DashboardOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
|
SunOutlined,
|
||||||
|
MoonOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { useAuthStore } from '../stores/authStore';
|
import { useAuthStore } from '../stores/authStore';
|
||||||
|
import { useThemeStore } from '../stores/themeStore';
|
||||||
import { adminAuthService } from '../services/adminAuthService';
|
import { adminAuthService } from '../services/adminAuthService';
|
||||||
|
|
||||||
const { Header, Sider, Content, Footer } = Layout;
|
const { Header, Sider, Content, Footer } = Layout;
|
||||||
@@ -23,6 +26,16 @@ const AdminLayout = () => {
|
|||||||
const adminUser = useAuthStore((state) => state.adminUser);
|
const adminUser = useAuthStore((state) => state.adminUser);
|
||||||
const logout = useAuthStore((state) => state.logout);
|
const logout = useAuthStore((state) => state.logout);
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
|
const { themeMode, toggleTheme } = useThemeStore();
|
||||||
|
|
||||||
|
// 根据当前路径计算应该展开的菜单
|
||||||
|
const openKeys = useMemo(() => {
|
||||||
|
const path = location.pathname;
|
||||||
|
if (path.startsWith('/admin/users')) {
|
||||||
|
return ['user'];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -56,26 +69,34 @@ const AdminLayout = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '100vh' }}>
|
<Layout style={{ minHeight: '100vh' }}>
|
||||||
<Sider trigger={null} collapsible collapsed={collapsed}>
|
<Sider
|
||||||
|
trigger={null}
|
||||||
|
collapsible
|
||||||
|
collapsed={collapsed}
|
||||||
|
style={{
|
||||||
|
background: themeMode === 'dark' ? '#001529' : '#ffffff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: 32,
|
height: 32,
|
||||||
margin: 16,
|
margin: 16,
|
||||||
background: 'rgba(255, 255, 255, 0.2)',
|
background: themeMode === 'dark' ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.05)',
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
color: 'white',
|
color: themeMode === 'dark' ? 'white' : '#1890ff',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{collapsed ? '运营' : '运营管理系统'}
|
{collapsed ? '运营' : '运营管理系统'}
|
||||||
</div>
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
theme="dark"
|
theme={themeMode === 'dark' ? 'dark' : 'light'}
|
||||||
mode="inline"
|
mode="inline"
|
||||||
selectedKeys={[location.pathname]}
|
selectedKeys={[location.pathname]}
|
||||||
|
openKeys={openKeys}
|
||||||
items={menuItems}
|
items={menuItems}
|
||||||
onClick={({ key }) => navigate(key)}
|
onClick={({ key }) => navigate(key)}
|
||||||
/>
|
/>
|
||||||
@@ -99,14 +120,23 @@ const AdminLayout = () => {
|
|||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
width: 64,
|
width: 64,
|
||||||
height: 64,
|
height: 64,
|
||||||
|
outline: 'none',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={themeMode === 'dark' ? <SunOutlined /> : <MoonOutlined />}
|
||||||
|
onClick={toggleTheme}
|
||||||
|
style={{ outline: 'none' }}
|
||||||
|
title={themeMode === 'dark' ? '切换到亮色主题' : '切换到暗色主题'}
|
||||||
|
/>
|
||||||
<span>欢迎, {adminUser?.username}</span>
|
<span>欢迎, {adminUser?.username}</span>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<LogoutOutlined />}
|
icon={<LogoutOutlined />}
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
|
style={{ outline: 'none' }}
|
||||||
>
|
>
|
||||||
退出
|
退出
|
||||||
</Button>
|
</Button>
|
||||||
@@ -123,7 +153,12 @@ const AdminLayout = () => {
|
|||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Content>
|
</Content>
|
||||||
<Footer style={{ textAlign: 'center' }}>
|
<Footer
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
background: themeMode === 'dark' ? '#001529' : '#ffffff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
梦幻西游一站式运营管理平台 ©2025 Created by JGE
|
梦幻西游一站式运营管理平台 ©2025 Created by JGE
|
||||||
</Footer>
|
</Footer>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
34
frontend/src/components/ThemeProvider.tsx
Normal file
34
frontend/src/components/ThemeProvider.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ReactNode, useMemo } from 'react';
|
||||||
|
import { ConfigProvider, theme } from 'antd';
|
||||||
|
import zhCN from 'antd/locale/zh_CN';
|
||||||
|
import { useThemeStore } from '../stores/themeStore';
|
||||||
|
import { getThemeByMode } from '../theme';
|
||||||
|
|
||||||
|
interface ThemeProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主题提供者组件
|
||||||
|
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
|
||||||
|
const { themeMode } = useThemeStore();
|
||||||
|
|
||||||
|
// 根据主题模式获取主题配置和算法
|
||||||
|
const currentTheme = useMemo(() => {
|
||||||
|
const customTheme = getThemeByMode(themeMode);
|
||||||
|
const algorithm = themeMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm;
|
||||||
|
|
||||||
|
return {
|
||||||
|
algorithm,
|
||||||
|
...customTheme,
|
||||||
|
};
|
||||||
|
}, [themeMode]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider
|
||||||
|
locale={zhCN}
|
||||||
|
theme={currentTheme}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,24 +1,17 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { RouterProvider } from 'react-router-dom';
|
import { RouterProvider } from 'react-router-dom';
|
||||||
import { ConfigProvider, App, theme } from 'antd';
|
import { App } from 'antd';
|
||||||
import zhCN from 'antd/locale/zh_CN';
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import { appTheme } from './theme';
|
import { ThemeProvider } from './components/ThemeProvider';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ConfigProvider
|
<ThemeProvider>
|
||||||
locale={zhCN}
|
|
||||||
theme={{
|
|
||||||
algorithm: theme.defaultAlgorithm,
|
|
||||||
...appTheme,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<App>
|
<App>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</App>
|
</App>
|
||||||
</ConfigProvider>
|
</ThemeProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
34
frontend/src/stores/themeStore.ts
Normal file
34
frontend/src/stores/themeStore.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
// 主题类型
|
||||||
|
export type ThemeMode = 'light' | 'dark';
|
||||||
|
|
||||||
|
// 从 localStorage 读取主题模式
|
||||||
|
const getInitialThemeMode = (): ThemeMode => {
|
||||||
|
const savedTheme = localStorage.getItem('adminThemeMode');
|
||||||
|
if (savedTheme === 'light' || savedTheme === 'dark') {
|
||||||
|
return savedTheme;
|
||||||
|
}
|
||||||
|
return 'dark'; // 默认暗色主题
|
||||||
|
};
|
||||||
|
|
||||||
|
// 主题状态接口
|
||||||
|
interface ThemeState {
|
||||||
|
themeMode: ThemeMode;
|
||||||
|
toggleTheme: () => void;
|
||||||
|
setTheme: (mode: ThemeMode) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主题状态管理
|
||||||
|
export const useThemeStore = create<ThemeState>((set) => ({
|
||||||
|
themeMode: getInitialThemeMode(),
|
||||||
|
toggleTheme: () => set((state) => {
|
||||||
|
const newMode = state.themeMode === 'light' ? 'dark' : 'light';
|
||||||
|
localStorage.setItem('adminThemeMode', newMode);
|
||||||
|
return { themeMode: newMode };
|
||||||
|
}),
|
||||||
|
setTheme: (mode) => {
|
||||||
|
localStorage.setItem('adminThemeMode', mode);
|
||||||
|
set({ themeMode: mode });
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
// 全局主题配置
|
// 全局主题配置
|
||||||
import type { ThemeConfig } from 'antd';
|
import type { ThemeConfig } from 'antd';
|
||||||
|
import type { ThemeMode } from '../stores/themeStore';
|
||||||
|
|
||||||
// 自定义主题配置
|
// 亮色主题配置
|
||||||
export const appTheme: ThemeConfig = {
|
export const lightTheme: ThemeConfig = {
|
||||||
token: {
|
token: {
|
||||||
// 主色调
|
// 主色调
|
||||||
colorPrimary: '#1890ff',
|
colorPrimary: '#1890ff',
|
||||||
@@ -22,18 +23,66 @@ export const appTheme: ThemeConfig = {
|
|||||||
components: {
|
components: {
|
||||||
// Layout 组件主题
|
// Layout 组件主题
|
||||||
Layout: {
|
Layout: {
|
||||||
headerBg: '#001529', // 顶部导航和侧边栏背景色
|
headerBg: '#ffffff', // 顶部导航背景色(亮色)
|
||||||
footerBg: '#001529', // 页脚背景色
|
footerBg: '#ffffff', // 页脚背景色(亮色)
|
||||||
siderBg: '#001529', // 侧边栏背景色
|
siderBg: '#ffffff', // 侧边栏背景色(亮色)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Menu 组件主题
|
// Menu 组件主题
|
||||||
Menu: {
|
Menu: {
|
||||||
darkItemSelectedBg: '#1890ff', // 菜单激活背景色
|
itemSelectedBg: '#e6f7ff', // 菜单激活背景色(亮色)
|
||||||
darkItemHoverBg: 'rgba(24, 144, 255, 0.2)', // 菜单悬停背景色
|
itemHoverBg: 'rgba(24, 144, 255, 0.1)', // 菜单悬停背景色(亮色)
|
||||||
darkItemColor: 'rgba(255, 255, 255, 0.65)', // 菜单项文字颜色
|
itemColor: 'rgba(0, 0, 0, 0.65)', // 菜单项文字颜色(亮色)
|
||||||
darkItemSelectedColor: '#fff', // 菜单激活文字颜色
|
itemSelectedColor: '#1890ff', // 菜单激活文字颜色(亮色)
|
||||||
darkItemHoverColor: '#fff', // 菜单悬停文字颜色
|
itemHoverColor: '#1890ff', // 菜单悬停文字颜色(亮色)
|
||||||
|
itemSelectedStyle: {
|
||||||
|
boxShadow: 'none', // 去除菜单激活项的阴影
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 暗色主题配置
|
||||||
|
export const darkTheme: ThemeConfig = {
|
||||||
|
token: {
|
||||||
|
// 主色调
|
||||||
|
colorPrimary: '#1890ff',
|
||||||
|
colorSuccess: '#52c41a',
|
||||||
|
colorWarning: '#faad14',
|
||||||
|
colorError: '#ff4d4f',
|
||||||
|
colorInfo: '#1890ff',
|
||||||
|
|
||||||
|
// 字体设置
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||||
|
fontSize: 14,
|
||||||
|
|
||||||
|
// 圆角
|
||||||
|
borderRadius: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
// Layout 组件主题
|
||||||
|
Layout: {
|
||||||
|
headerBg: '#001529', // 顶部导航和侧边栏背景色(暗色)
|
||||||
|
footerBg: '#001529', // 页脚背景色(暗色)
|
||||||
|
siderBg: '#001529', // 侧边栏背景色(暗色)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Menu 组件主题
|
||||||
|
Menu: {
|
||||||
|
darkItemSelectedBg: '#1890ff', // 菜单激活背景色(暗色)
|
||||||
|
darkItemHoverBg: 'rgba(24, 144, 255, 0.2)', // 菜单悬停背景色(暗色)
|
||||||
|
darkItemColor: 'rgba(255, 255, 255, 0.65)', // 菜单项文字颜色(暗色)
|
||||||
|
darkItemSelectedColor: '#fff', // 菜单激活文字颜色(暗色)
|
||||||
|
darkItemHoverColor: '#fff', // 菜单悬停文字颜色(暗色)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据主题模式获取主题配置
|
||||||
|
export const getThemeByMode = (mode: ThemeMode): ThemeConfig => {
|
||||||
|
return mode === 'light' ? lightTheme : darkTheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 默认主题配置(向后兼容)
|
||||||
|
export const appTheme: ThemeConfig = darkTheme;
|
||||||
|
|||||||
Reference in New Issue
Block a user