实现顶部导航栏布局系统
- 重构布局架构:移除侧边栏,采用顶部导航设计 - 实现 MainLayout 主布局组件 - 实现 Header 顶部导航栏(Logo + 水平菜单 + 用户功能区) - 实现 Footer 页脚组件 - 导航菜单包含:仪表盘、上传图片、图片库、链接管理、图片工具、存储配置、统计分析
This commit is contained in:
parent
13e2bc9637
commit
0416f58b3d
36
src/layouts/MainLayout/Footer.tsx
Normal file
36
src/layouts/MainLayout/Footer.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Layout } from 'antd';
|
||||
import { GithubOutlined, HeartFilled } from '@ant-design/icons';
|
||||
|
||||
const { Footer: AntFooter } = Layout;
|
||||
|
||||
export const Footer: React.FC = () => {
|
||||
return (
|
||||
<AntFooter
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
background: '#fff',
|
||||
borderTop: '1px solid #F0F0F0',
|
||||
padding: '24px 50px',
|
||||
}}
|
||||
>
|
||||
<div style={{ color: '#9CA3AF', fontSize: 14 }}>
|
||||
Made with <HeartFilled style={{ color: '#EC4899', margin: '0 4px' }} /> by PicStack Team
|
||||
</div>
|
||||
<div style={{ marginTop: 8, color: '#D1D5DB', fontSize: 12 }}>
|
||||
<a
|
||||
href="https://github.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: '#9CA3AF', marginRight: 16, transition: 'color 0.3s' }}
|
||||
onMouseEnter={(e) => e.currentTarget.style.color = '#7C3AED'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.color = '#9CA3AF'}
|
||||
>
|
||||
<GithubOutlined style={{ marginRight: 4 }} />
|
||||
GitHub
|
||||
</a>
|
||||
<span>© 2025 PicStack. All rights reserved.</span>
|
||||
</div>
|
||||
</AntFooter>
|
||||
);
|
||||
};
|
||||
168
src/layouts/MainLayout/Header.tsx
Normal file
168
src/layouts/MainLayout/Header.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
import React from 'react';
|
||||
import { Layout, Space, Badge, Avatar, Dropdown, Menu } from 'antd';
|
||||
import {
|
||||
BellOutlined,
|
||||
SettingOutlined,
|
||||
UserOutlined,
|
||||
LogoutOutlined,
|
||||
CloudUploadOutlined,
|
||||
DashboardOutlined,
|
||||
PictureOutlined,
|
||||
LinkOutlined,
|
||||
ToolOutlined,
|
||||
DatabaseOutlined,
|
||||
BarChartOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import type { MenuProps } from 'antd';
|
||||
|
||||
const { Header: AntHeader } = Layout;
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
// 主导航菜单项
|
||||
const navMenuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: '/dashboard',
|
||||
icon: <DashboardOutlined />,
|
||||
label: '仪表盘',
|
||||
onClick: () => navigate('/dashboard'),
|
||||
},
|
||||
{
|
||||
key: '/upload',
|
||||
icon: <CloudUploadOutlined />,
|
||||
label: '上传图片',
|
||||
onClick: () => navigate('/upload'),
|
||||
},
|
||||
{
|
||||
key: '/gallery',
|
||||
icon: <PictureOutlined />,
|
||||
label: '图片库',
|
||||
onClick: () => navigate('/gallery'),
|
||||
},
|
||||
{
|
||||
key: '/links',
|
||||
icon: <LinkOutlined />,
|
||||
label: '链接管理',
|
||||
onClick: () => navigate('/links'),
|
||||
},
|
||||
{
|
||||
key: '/tools',
|
||||
icon: <ToolOutlined />,
|
||||
label: '图片工具',
|
||||
onClick: () => navigate('/tools'),
|
||||
},
|
||||
{
|
||||
key: '/storage',
|
||||
icon: <DatabaseOutlined />,
|
||||
label: '存储配置',
|
||||
onClick: () => navigate('/storage'),
|
||||
},
|
||||
{
|
||||
key: '/analytics',
|
||||
icon: <BarChartOutlined />,
|
||||
label: '统计分析',
|
||||
onClick: () => navigate('/analytics'),
|
||||
},
|
||||
];
|
||||
|
||||
// 用户菜单项
|
||||
const userMenuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: 'settings',
|
||||
icon: <SettingOutlined />,
|
||||
label: '设置',
|
||||
onClick: () => navigate('/settings'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <LogoutOutlined />,
|
||||
label: '退出登录',
|
||||
danger: true,
|
||||
},
|
||||
];
|
||||
|
||||
// 获取当前选中的菜单项
|
||||
const selectedKey = location.pathname === '/' ? '/dashboard' : location.pathname;
|
||||
|
||||
return (
|
||||
<AntHeader
|
||||
style={{
|
||||
background: '#fff',
|
||||
padding: '0 24px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 999,
|
||||
height: 64,
|
||||
}}
|
||||
>
|
||||
{/* Logo 区域 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, minWidth: 200 }}>
|
||||
<div style={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 10,
|
||||
background: 'linear-gradient(135deg, #7C3AED 0%, #EC4899 100%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 4px 12px rgba(124, 58, 237, 0.25)'
|
||||
}}>
|
||||
<CloudUploadOutlined style={{ fontSize: 20, color: '#FFFFFF' }} />
|
||||
</div>
|
||||
<span style={{
|
||||
fontSize: 20,
|
||||
fontWeight: 700,
|
||||
background: 'linear-gradient(135deg, #7C3AED 0%, #EC4899 100%)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text'
|
||||
}}>
|
||||
PicStack
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 中间导航菜单 */}
|
||||
<div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
selectedKeys={[selectedKey]}
|
||||
items={navMenuItems}
|
||||
className="top-nav-menu"
|
||||
style={{
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
minWidth: 600,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 右侧操作区 */}
|
||||
<div style={{ minWidth: 200, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Space size={20}>
|
||||
{/* 通知 */}
|
||||
<Badge count={3} offset={[-2, 2]}>
|
||||
<BellOutlined style={{ fontSize: 18, cursor: 'pointer' }} />
|
||||
</Badge>
|
||||
|
||||
{/* 用户菜单 */}
|
||||
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
|
||||
<div style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Avatar size={32} icon={<UserOutlined />} />
|
||||
<span style={{ fontSize: 14 }}>用户</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</div>
|
||||
</AntHeader>
|
||||
);
|
||||
};
|
||||
98
src/layouts/MainLayout/Sidebar.tsx
Normal file
98
src/layouts/MainLayout/Sidebar.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import React from 'react';
|
||||
import { Layout, Menu } from 'antd';
|
||||
import {
|
||||
DashboardOutlined,
|
||||
CloudUploadOutlined,
|
||||
PictureOutlined,
|
||||
LinkOutlined,
|
||||
ToolOutlined,
|
||||
DatabaseOutlined,
|
||||
BarChartOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import type { MenuProps } from 'antd';
|
||||
|
||||
const { Sider } = Layout;
|
||||
|
||||
type MenuItem = Required<MenuProps>['items'][number];
|
||||
|
||||
export const Sidebar: React.FC<{ collapsed: boolean }> = ({ collapsed }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
key: '/dashboard',
|
||||
icon: <DashboardOutlined />,
|
||||
label: '仪表盘',
|
||||
onClick: () => navigate('/dashboard'),
|
||||
},
|
||||
{
|
||||
key: '/upload',
|
||||
icon: <CloudUploadOutlined />,
|
||||
label: '上传图片',
|
||||
onClick: () => navigate('/upload'),
|
||||
},
|
||||
{
|
||||
key: '/gallery',
|
||||
icon: <PictureOutlined />,
|
||||
label: '图片库',
|
||||
onClick: () => navigate('/gallery'),
|
||||
},
|
||||
{
|
||||
key: '/links',
|
||||
icon: <LinkOutlined />,
|
||||
label: '链接管理',
|
||||
onClick: () => navigate('/links'),
|
||||
},
|
||||
{
|
||||
key: '/tools',
|
||||
icon: <ToolOutlined />,
|
||||
label: '图片工具',
|
||||
onClick: () => navigate('/tools'),
|
||||
},
|
||||
{
|
||||
key: '/storage',
|
||||
icon: <DatabaseOutlined />,
|
||||
label: '存储配置',
|
||||
onClick: () => navigate('/storage'),
|
||||
},
|
||||
{
|
||||
key: '/analytics',
|
||||
icon: <BarChartOutlined />,
|
||||
label: '统计分析',
|
||||
onClick: () => navigate('/analytics'),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
key: '/settings',
|
||||
icon: <SettingOutlined />,
|
||||
label: '设置',
|
||||
onClick: () => navigate('/settings'),
|
||||
},
|
||||
];
|
||||
|
||||
// 获取当前激活的菜单项
|
||||
const selectedKey = location.pathname === '/' ? '/dashboard' : location.pathname;
|
||||
|
||||
return (
|
||||
<Sider
|
||||
collapsed={collapsed}
|
||||
width={220}
|
||||
style={{
|
||||
background: '#fff',
|
||||
borderRight: '1px solid #F0F0F0',
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[selectedKey]}
|
||||
style={{ height: '100%', borderRight: 0, paddingTop: 16 }}
|
||||
items={menuItems}
|
||||
/>
|
||||
</Sider>
|
||||
);
|
||||
};
|
||||
26
src/layouts/MainLayout/index.tsx
Normal file
26
src/layouts/MainLayout/index.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Layout } from 'antd';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Header } from './Header';
|
||||
import { Footer } from './Footer';
|
||||
|
||||
const { Content } = Layout;
|
||||
|
||||
export const MainLayout: React.FC = () => {
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Header />
|
||||
<Content
|
||||
style={{
|
||||
padding: 24,
|
||||
margin: 0,
|
||||
minHeight: 280,
|
||||
background: '#F5F7FA',
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
</Content>
|
||||
<Footer />
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user