2025-12-19 16:02:38 +08:00
|
|
|
|
import { useState } from 'react';
|
2025-12-18 19:07:21 +08:00
|
|
|
|
import { Layout, Menu, Button, Avatar, Dropdown, message } from 'antd';
|
2025-12-19 16:02:38 +08:00
|
|
|
|
import { useNavigate, useLocation } from 'react-router-dom';
|
2025-12-18 19:07:21 +08:00
|
|
|
|
import {
|
|
|
|
|
|
DashboardOutlined,
|
|
|
|
|
|
QuestionCircleOutlined,
|
|
|
|
|
|
BarChartOutlined,
|
|
|
|
|
|
UserOutlined,
|
|
|
|
|
|
LogoutOutlined,
|
|
|
|
|
|
DatabaseOutlined,
|
|
|
|
|
|
SafetyOutlined,
|
|
|
|
|
|
BookOutlined,
|
|
|
|
|
|
CalendarOutlined,
|
2025-12-19 16:02:38 +08:00
|
|
|
|
TeamOutlined,
|
|
|
|
|
|
MenuFoldOutlined,
|
|
|
|
|
|
MenuUnfoldOutlined
|
2025-12-18 19:07:21 +08:00
|
|
|
|
} from '@ant-design/icons';
|
|
|
|
|
|
import { useAdmin } from '../contexts';
|
2025-12-19 16:02:38 +08:00
|
|
|
|
import { Logo } from '../components/common/Logo';
|
2025-12-18 19:07:21 +08:00
|
|
|
|
|
2025-12-19 16:02:38 +08:00
|
|
|
|
const { Header, Sider, Content, Footer } = Layout;
|
2025-12-18 19:07:21 +08:00
|
|
|
|
|
|
|
|
|
|
const AdminLayout = ({ children }: { children: React.ReactNode }) => {
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
|
const { admin, clearAdmin } = useAdmin();
|
|
|
|
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
const menuItems = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: '/admin/dashboard',
|
|
|
|
|
|
icon: <DashboardOutlined />,
|
|
|
|
|
|
label: '仪表盘',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: '/admin/questions',
|
|
|
|
|
|
icon: <QuestionCircleOutlined />,
|
|
|
|
|
|
label: '题库管理',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: '/admin/categories',
|
|
|
|
|
|
icon: <SafetyOutlined />,
|
|
|
|
|
|
label: '题目类别',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: '/admin/subjects',
|
|
|
|
|
|
icon: <BookOutlined />,
|
|
|
|
|
|
label: '考试科目',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: '/admin/tasks',
|
|
|
|
|
|
icon: <CalendarOutlined />,
|
|
|
|
|
|
label: '考试任务',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: '/admin/users',
|
|
|
|
|
|
icon: <TeamOutlined />,
|
|
|
|
|
|
label: '用户管理',
|
|
|
|
|
|
},
|
2025-12-19 00:58:58 +08:00
|
|
|
|
|
2025-12-18 19:07:21 +08:00
|
|
|
|
{
|
|
|
|
|
|
key: '/admin/statistics',
|
|
|
|
|
|
icon: <BarChartOutlined />,
|
|
|
|
|
|
label: '数据统计',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
key: '/admin/backup',
|
|
|
|
|
|
icon: <DatabaseOutlined />,
|
|
|
|
|
|
label: '数据备份',
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const handleMenuClick = ({ key }: { key: string }) => {
|
|
|
|
|
|
navigate(key);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleLogout = () => {
|
|
|
|
|
|
clearAdmin();
|
|
|
|
|
|
message.success('退出登录成功');
|
|
|
|
|
|
navigate('/admin/login');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const userMenuItems = [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'logout',
|
|
|
|
|
|
icon: <LogoutOutlined />,
|
|
|
|
|
|
label: '退出登录',
|
|
|
|
|
|
onClick: handleLogout,
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Layout style={{ minHeight: '100vh' }}>
|
2025-12-19 16:02:38 +08:00
|
|
|
|
<Sider
|
|
|
|
|
|
trigger={null}
|
|
|
|
|
|
collapsible
|
|
|
|
|
|
collapsed={collapsed}
|
|
|
|
|
|
theme="light"
|
|
|
|
|
|
className="shadow-md z-10"
|
|
|
|
|
|
width={240}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="h-16 flex items-center justify-center border-b border-gray-100">
|
|
|
|
|
|
{collapsed ? (
|
|
|
|
|
|
<span className="text-xl font-bold text-mars-500">OA</span>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Logo variant="primary" />
|
|
|
|
|
|
)}
|
2025-12-18 19:07:21 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<Menu
|
|
|
|
|
|
mode="inline"
|
|
|
|
|
|
selectedKeys={[location.pathname]}
|
|
|
|
|
|
items={menuItems}
|
|
|
|
|
|
onClick={handleMenuClick}
|
|
|
|
|
|
style={{ borderRight: 0 }}
|
2025-12-19 16:02:38 +08:00
|
|
|
|
className="py-4"
|
2025-12-18 19:07:21 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</Sider>
|
|
|
|
|
|
|
2025-12-19 16:02:38 +08:00
|
|
|
|
<Layout className="bg-gray-50/50">
|
|
|
|
|
|
<Header className="bg-white shadow-sm flex justify-between items-center px-6 h-16 sticky top-0 z-10">
|
2025-12-18 19:07:21 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
type="text"
|
2025-12-19 16:02:38 +08:00
|
|
|
|
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
2025-12-18 19:07:21 +08:00
|
|
|
|
onClick={() => setCollapsed(!collapsed)}
|
2025-12-19 16:02:38 +08:00
|
|
|
|
className="text-lg w-10 h-10 flex items-center justify-center"
|
2025-12-18 19:07:21 +08:00
|
|
|
|
/>
|
|
|
|
|
|
|
2025-12-19 16:02:38 +08:00
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
|
<span className="text-gray-600 hidden sm:block">
|
2025-12-18 19:07:21 +08:00
|
|
|
|
欢迎,{admin?.username}
|
|
|
|
|
|
</span>
|
2025-12-19 16:02:38 +08:00
|
|
|
|
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight" arrow>
|
|
|
|
|
|
<Avatar
|
|
|
|
|
|
icon={<UserOutlined />}
|
|
|
|
|
|
className="cursor-pointer bg-mars-100 text-mars-600 hover:bg-mars-200 transition-colors"
|
|
|
|
|
|
/>
|
2025-12-18 19:07:21 +08:00
|
|
|
|
</Dropdown>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Header>
|
|
|
|
|
|
|
2025-12-19 16:02:38 +08:00
|
|
|
|
<Content className="m-6 flex flex-col">
|
|
|
|
|
|
<div className="flex-1 bg-white rounded-xl shadow-sm p-6 min-h-[calc(100vh-160px)]">
|
|
|
|
|
|
{children}
|
|
|
|
|
|
</div>
|
2025-12-18 19:07:21 +08:00
|
|
|
|
</Content>
|
2025-12-19 16:02:38 +08:00
|
|
|
|
|
|
|
|
|
|
<Footer className="bg-transparent text-center py-6 px-8 text-gray-400 text-sm flex flex-col md:flex-row justify-between items-center">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
© {new Date().getFullYear()} Boonlive OA System. All Rights Reserved.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-4 md:mt-0">
|
|
|
|
|
|
<Logo variant="secondary" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Footer>
|
2025-12-18 19:07:21 +08:00
|
|
|
|
</Layout>
|
|
|
|
|
|
</Layout>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-19 16:02:38 +08:00
|
|
|
|
export default AdminLayout;
|