123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- import React, { useState, useEffect } from 'react';
- import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom';
- import { Layout as AntLayout, Menu, Button, Dropdown, Avatar } from 'antd';
- import {
- HomeOutlined,
- CheckSquareOutlined,
- ScheduleOutlined,
- RobotOutlined,
- BarChartOutlined,
- UserOutlined,
- LogoutOutlined,
- MenuFoldOutlined,
- MenuUnfoldOutlined
- } from '@ant-design/icons';
- import { useAuth } from '../context/AuthContext';
- const { Header, Sider, Content } = AntLayout;
- const Layout = () => {
- const { user, logout } = useAuth();
- const location = useLocation();
- const navigate = useNavigate();
- const [collapsed, setCollapsed] = useState(false);
- const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
- // 监听窗口大小变化
- useEffect(() => {
- const handleResize = () => {
- setIsMobile(window.innerWidth < 768);
- if (window.innerWidth < 768) {
- setCollapsed(true);
- }
- };
- window.addEventListener('resize', handleResize);
- handleResize();
- return () => window.removeEventListener('resize', handleResize);
- }, []);
- const toggleCollapsed = () => {
- setCollapsed(!collapsed);
- };
- // 当前选中的菜单项
- const selectedKey = (() => {
- const path = location.pathname;
- if (path === '/') return '1';
- if (path === '/tasks') return '2';
- if (path === '/schedule') return '3';
- if (path === '/ai-chat') return '4';
- if (path === '/stats') return '5';
- return '1';
- })();
- // 用户菜单
- const userMenu = {
- items: [
- {
- key: '1',
- label: (
- <div className="flex items-center">
- <UserOutlined className="mr-2" />
- <span>{user?.email}</span>
- </div>
- ),
- },
- {
- key: '2',
- label: (
- <div className="flex items-center text-red-600" onClick={logout}>
- <LogoutOutlined className="mr-2" />
- <span>退出登录</span>
- </div>
- ),
- }
- ],
- };
- return (
- <AntLayout className="min-h-screen">
- <Sider
- trigger={null}
- collapsible
- collapsed={collapsed}
- className="bg-white shadow-md"
- width={220}
- collapsedWidth={isMobile ? 0 : 80}
- style={{
- overflow: 'auto',
- height: '100vh',
- position: 'fixed',
- left: 0,
- top: 0,
- bottom: 0,
- zIndex: 999,
- display: isMobile && collapsed ? 'none' : 'block'
- }}
- >
- <div className="flex justify-center items-center h-16 m-4">
- <Link to="/" className="flex items-center">
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#3B82F6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-check-circle">
- <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
- <polyline points="22 4 12 14.01 9 11.01"/>
- </svg>
- {!collapsed && <span className="ml-2 text-lg font-semibold text-primary-500">任务管理系统</span>}
- </Link>
- </div>
-
- <Menu
- theme="light"
- mode="inline"
- selectedKeys={[selectedKey]}
- className="border-r-0 mt-4"
- items={[
- {
- key: '1',
- icon: <HomeOutlined />,
- label: '仪表盘',
- onClick: () => navigate('/')
- },
- {
- key: '2',
- icon: <CheckSquareOutlined />,
- label: '任务管理',
- onClick: () => navigate('/tasks')
- },
- {
- key: '3',
- icon: <ScheduleOutlined />,
- label: '时间安排',
- onClick: () => navigate('/schedule')
- },
- {
- key: '4',
- icon: <RobotOutlined />,
- label: 'AI 助手',
- onClick: () => navigate('/ai-chat')
- },
- {
- key: '5',
- icon: <BarChartOutlined />,
- label: '数据统计',
- onClick: () => navigate('/stats')
- },
- ]}
- />
- </Sider>
-
- <AntLayout style={{ marginLeft: isMobile ? 0 : (collapsed ? 80 : 220), transition: 'margin-left 0.2s' }}>
- <Header className="bg-white p-0 px-4 flex justify-between items-center shadow-sm z-10">
- <Button
- type="text"
- icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
- onClick={toggleCollapsed}
- className="mr-3"
- />
-
- <Dropdown menu={userMenu} placement="bottomRight">
- <div className="flex items-center cursor-pointer">
- <Avatar icon={<UserOutlined />} className="bg-primary-500" />
- {!isMobile && <span className="ml-2">{user?.email}</span>}
- </div>
- </Dropdown>
- </Header>
-
- <Content className="m-6 p-6 bg-white rounded-lg shadow-sm">
- <Outlet />
- </Content>
- </AntLayout>
-
- {/* 移动端侧边栏蒙层 */}
- {isMobile && !collapsed && (
- <div
- className="fixed inset-0 bg-black bg-opacity-50 z-40"
- onClick={toggleCollapsed}
- />
- )}
- {/* 移动端收起时显示展开按钮 */}
- {isMobile && collapsed && (
- <Button
- type="primary"
- shape="circle"
- icon={<MenuUnfoldOutlined />}
- onClick={toggleCollapsed}
- style={{
- position: 'fixed',
- top: 16,
- left: 16,
- zIndex: 2000,
- boxShadow: '0 2px 8px rgba(0,0,0,0.15)'
- }}
- />
- )}
- </AntLayout>
- );
- };
- export default Layout;
|