实现顶部导航栏布局系统
- 重构布局架构:移除侧边栏,采用顶部导航设计 - 实现 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