feat(web): 多主题系统 — 4 套主题 + CSS 变量 + Ant Design 动态主题

- CSS 变量层: :root 默认 blue, [data-theme] 覆盖 warm/dark/emerald
- Ant Design: ConfigProvider 按 ThemeName 切换 token + algorithm
- ThemeSwitcher: 下拉面板含 4 主题色块预览 + localStorage 持久化
- useThemeMode: 从 store 读取主题名替代色值比对(修复 33 页面暗色失效)
- index.html: 添加 Noto Serif SC 字体(warm 主题衬线标题)
This commit is contained in:
iven
2026-04-28 00:20:02 +08:00
parent 50eae8b809
commit e56cd73e49
8 changed files with 918 additions and 276 deletions

View File

@@ -12,8 +12,6 @@ import {
LogoutOutlined,
MessageOutlined,
SearchOutlined,
BulbOutlined,
BulbFilled,
AppstoreOutlined,
TeamOutlined,
TableOutlined,
@@ -39,6 +37,7 @@ import { usePluginStore } from '../stores/plugin';
import type { PluginMenuGroup } from '../stores/plugin';
import { getMenusForUser, type MenuInfo } from '../api/menus';
import NotificationPanel from '../components/NotificationPanel';
import ThemeSwitcher from '../components/ThemeSwitcher';
const { Header, Sider, Content, Footer } = Layout;
@@ -345,7 +344,7 @@ const DynamicMenuSection = memo(function DynamicMenuSection({
});
export default function MainLayout({ children }: { children: React.ReactNode }) {
const { sidebarCollapsed, toggleSidebar, theme: themeMode, setTheme } = useAppStore();
const { sidebarCollapsed, toggleSidebar } = useAppStore();
const { user, logout } = useAuthStore();
const pluginMenuItems = usePluginStore((s) => s.pluginMenuItems);
const pluginMenuGroups = usePluginStore((s) => s.pluginMenuGroups);
@@ -413,24 +412,23 @@ export default function MainLayout({ children }: { children: React.ReactNode })
];
const sidebarWidth = sidebarCollapsed ? 72 : 240;
const isDark = themeMode === 'dark';
return (
<Layout style={{ minHeight: '100vh' }}>
{/* 现代深色侧边栏 */}
{/* 侧边栏 */}
<Sider
trigger={null}
collapsible
collapsed={sidebarCollapsed}
width={240}
collapsedWidth={72}
className={isDark ? 'erp-sider-dark' : 'erp-sider-dark erp-sider-default'}
className="erp-sider-dark"
>
{/* Logo 区域 */}
<div className="erp-sidebar-logo" onClick={() => navigate('/')}>
<div className="erp-sidebar-logo-icon">E</div>
<div className="erp-sidebar-logo-icon">H</div>
{!sidebarCollapsed && (
<span className="erp-sidebar-logo-text">ERP Platform</span>
<span className="erp-sidebar-logo-text">HMS </span>
)}
</div>
@@ -469,22 +467,22 @@ export default function MainLayout({ children }: { children: React.ReactNode })
{/* 右侧主区域 */}
<Layout
className={`erp-main-layout ${isDark ? 'erp-main-layout-dark' : 'erp-main-layout-light'}`}
className="erp-main-layout"
style={{ marginLeft: sidebarWidth }}
>
{/* 顶部导航栏 */}
<Header className={`erp-header ${isDark ? 'erp-header-dark' : 'erp-header-light'}`}>
<Header className="erp-header">
{/* 左侧:折叠按钮 + 标题 */}
<Space size="middle" style={{ alignItems: 'center' }}>
<div className="erp-header-btn" onClick={toggleSidebar}>
{sidebarCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
</div>
<span className={`erp-header-title ${isDark ? 'erp-text-dark' : 'erp-text-light'}`}>
<span className="erp-header-title">
{headerTitle}
</span>
</Space>
{/* 右侧:搜索 + 通知 + 主题切换 + 用户 */}
{/* 右侧:搜索 + 主题切换 + 通知 + 用户 */}
<Space size={4} style={{ alignItems: 'center' }}>
<Tooltip title="搜索">
<div className="erp-header-btn">
@@ -492,15 +490,11 @@ export default function MainLayout({ children }: { children: React.ReactNode })
</div>
</Tooltip>
<Tooltip title={isDark ? '切换亮色模式' : '切换暗色模式'}>
<div className="erp-header-btn" onClick={() => setTheme(isDark ? 'blue' : 'dark')}>
{isDark ? <BulbFilled style={{ fontSize: 16 }} /> : <BulbOutlined style={{ fontSize: 16 }} />}
</div>
</Tooltip>
<ThemeSwitcher />
<NotificationPanel />
<div className={`erp-header-divider ${isDark ? 'erp-header-divider-dark' : 'erp-header-divider-light'}`} />
<div className="erp-header-divider" />
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight" trigger={['click']}>
<div className="erp-header-user">
@@ -511,7 +505,7 @@ export default function MainLayout({ children }: { children: React.ReactNode })
{(user?.display_name?.[0] || user?.username?.[0] || 'U').toUpperCase()}
</Avatar>
{!sidebarCollapsed && (
<span className={`erp-user-name ${isDark ? 'erp-text-dark-secondary' : 'erp-text-light-secondary'}`}>
<span className="erp-user-name">
{user?.display_name || user?.username || 'User'}
</span>
)}
@@ -526,7 +520,7 @@ export default function MainLayout({ children }: { children: React.ReactNode })
</Content>
{/* 底部 */}
<Footer className={`erp-footer ${isDark ? 'erp-footer-dark' : 'erp-footer-light'}`}>
<Footer className="erp-footer">
HMS
</Footer>
</Layout>