2026-01-29 10:41:39 +08:00

190 lines
5.6 KiB
TypeScript

import React, { useState } from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import {
Layout,
Menu,
Avatar,
Dropdown,
theme,
Button,
Space,
} from 'antd';
import {
DashboardOutlined,
AppstoreOutlined,
InboxOutlined,
MobileOutlined,
UserOutlined,
TeamOutlined,
LogoutOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from '@ant-design/icons';
import { useAuthStore } from '../../store/useAuthStore';
const { Header, Sider, Content } = Layout;
const menuItems = [
{
key: '/dashboard',
icon: <DashboardOutlined />,
label: '仪表盘',
},
{
key: '/device-types',
icon: <AppstoreOutlined />,
label: '设备类型',
},
{
key: '/batches',
icon: <InboxOutlined />,
label: '批次管理',
},
{
key: '/devices',
icon: <MobileOutlined />,
label: '设备列表',
},
{
key: '/users',
icon: <UserOutlined />,
label: '用户管理',
},
{
key: '/admins',
icon: <TeamOutlined />,
label: '管理员',
},
];
const MainLayout: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const navigate = useNavigate();
const location = useLocation();
const { adminInfo, logout } = useAuthStore();
const { token: themeToken } = theme.useToken();
const handleMenuClick = ({ key }: { key: string }) => {
navigate(key);
};
const handleLogout = () => {
logout();
navigate('/login');
};
const userMenuItems = [
{
key: 'profile',
icon: <UserOutlined />,
label: '个人信息',
},
{
type: 'divider' as const,
},
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
danger: true,
onClick: handleLogout,
},
];
const getRoleLabel = (role?: string) => {
const roleMap: Record<string, string> = {
super_admin: '超级管理员',
admin: '管理员',
operator: '操作员',
};
return roleMap[role || ''] || role;
};
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider
trigger={null}
collapsible
collapsed={collapsed}
theme="dark"
style={{
overflow: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
}}
>
<div
style={{
height: 64,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontSize: collapsed ? 16 : 20,
fontWeight: 'bold',
borderBottom: '1px solid rgba(255,255,255,0.1)',
}}
>
{collapsed ? 'RTC' : 'RTC 管理后台'}
</div>
<Menu
theme="dark"
mode="inline"
selectedKeys={[location.pathname]}
items={menuItems}
onClick={handleMenuClick}
/>
</Sider>
<Layout style={{ marginLeft: collapsed ? 80 : 200, transition: 'margin-left 0.2s' }}>
<Header
style={{
padding: '0 24px',
background: themeToken.colorBgContainer,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
boxShadow: '0 1px 4px rgba(0,0,0,0.08)',
position: 'sticky',
top: 0,
zIndex: 10,
}}
>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
style={{ fontSize: 16 }}
/>
<Space>
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
<Space style={{ cursor: 'pointer' }}>
<Avatar icon={<UserOutlined />} style={{ backgroundColor: themeToken.colorPrimary }} />
<span>{adminInfo?.name || adminInfo?.username}</span>
<span style={{ color: themeToken.colorTextSecondary, fontSize: 12 }}>
({getRoleLabel(adminInfo?.role)})
</span>
</Space>
</Dropdown>
</Space>
</Header>
<Content
style={{
margin: 24,
padding: 24,
background: themeToken.colorBgContainer,
borderRadius: themeToken.borderRadiusLG,
minHeight: 280,
}}
>
<Outlet />
</Content>
</Layout>
</Layout>
);
};
export default MainLayout;