1
0

feat: 现代化 UI 布局,实现侧边栏导航和统计仪表盘

- 重构 AppLayout 为可折叠侧边栏导航布局
- 实现统计仪表盘:统计摘要卡片 + 请求趋势图表
- Provider 页面使用 Card 包裹优化视觉层次
- 主题切换按钮移至侧边栏底部,支持折叠态
- Header 适配暗色主题,添加分隔线优化视觉过渡
- 添加全局样式重置(SCSS)
- 完善组件测试和 E2E 测试覆盖
- 同步 OpenSpec 规范到主 specs
This commit is contained in:
2026-04-16 19:24:02 +08:00
parent 5dd26d29a7
commit 870004af23
24 changed files with 983 additions and 153 deletions

View File

@@ -1,7 +1,9 @@
import { useState } from 'react';
import { Layout, Menu } from 'antd';
import { CloudServerOutlined, BarChartOutlined } from '@ant-design/icons';
import { Outlet, useLocation, useNavigate } from 'react-router';
import { ThemeToggle } from '@/components/ThemeToggle';
import { useTheme } from '@/contexts/ThemeContext';
const menuItems = [
{ key: '/providers', label: '供应商管理', icon: <CloudServerOutlined /> },
@@ -11,48 +13,92 @@ const menuItems = [
export function AppLayout() {
const location = useLocation();
const navigate = useNavigate();
const { mode } = useTheme();
const [collapsed, setCollapsed] = useState(false);
const getPageTitle = () => {
if (location.pathname === '/providers') return '供应商管理';
if (location.pathname === '/stats') return '用量统计';
return 'AI Gateway';
};
return (
<Layout style={{ minHeight: '100vh' }}>
<Layout.Header
<Layout.Sider
collapsible
collapsed={collapsed}
onCollapse={setCollapsed}
breakpoint="lg"
style={{
display: 'flex',
alignItems: 'center',
padding: '0 2rem',
overflow: 'hidden',
height: '100vh',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
}}
>
<div
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div
style={{
height: 64,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontSize: collapsed ? '1rem' : '1.25rem',
fontWeight: 600,
transition: 'all 0.2s',
flexShrink: 0,
}}
>
{collapsed ? 'AI' : 'AI Gateway'}
</div>
<Menu
theme="dark"
mode="inline"
selectedKeys={[location.pathname]}
items={menuItems}
onClick={({ key }) => navigate(key)}
style={{ flex: 1, overflow: 'auto' }}
/>
<div
style={{
padding: '16px 0',
display: 'flex',
justifyContent: 'center',
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
flexShrink: 0,
}}
>
<ThemeToggle />
</div>
</div>
</Layout.Sider>
<Layout style={{ marginLeft: collapsed ? 80 : 200, transition: 'all 0.2s' }}>
<Layout.Header
style={{
color: '#fff',
fontSize: '1.25rem',
fontWeight: 600,
marginRight: '2rem',
whiteSpace: 'nowrap',
padding: '0 2rem',
background: mode === 'dark' ? '#141414' : '#fff',
display: 'flex',
alignItems: 'center',
borderBottom: mode === 'dark' ? '1px solid #303030' : '1px solid #f0f0f0',
}}
>
AI Gateway
</div>
<Menu
theme="dark"
mode="horizontal"
selectedKeys={[location.pathname]}
items={menuItems}
onClick={({ key }) => navigate(key)}
style={{ flex: 1, minWidth: 0 }}
/>
<ThemeToggle />
</Layout.Header>
<Layout.Content
style={{
padding: '2rem',
maxWidth: '1400px',
width: '100%',
margin: '0 auto',
boxSizing: 'border-box',
}}
>
<Outlet />
</Layout.Content>
<h1 style={{ margin: 0, fontSize: '1.25rem' }}>{getPageTitle()}</h1>
</Layout.Header>
<Layout.Content
style={{
padding: '2rem',
maxWidth: '1400px',
width: '100%',
margin: '0 auto',
boxSizing: 'border-box',
}}
>
<Outlet />
</Layout.Content>
</Layout>
</Layout>
);
}