Files
web-Iot/src/components/Layout.tsx
2026-04-28 14:45:07 +08:00

163 lines
5.3 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import {
Layout, Menu, Button, Space, ConfigProvider, theme,
Breadcrumb, Badge, message
} from 'antd';
// 先引入中文语言包
import zhCN from 'antd/locale/zh_CN';
import {
DashboardOutlined, ExperimentOutlined, VideoCameraOutlined,
BellOutlined, DownloadOutlined, UserOutlined, LogoutOutlined,
MenuUnfoldOutlined, MenuFoldOutlined, TeamOutlined, MenuOutlined
} from '@ant-design/icons';
import logo from '../assets/logo.png';
import { useSelector } from 'react-redux';
import { RootState, useAppDispatch } from '../store';
import { logout } from '../store/userSlice';
import { getMenuList } from '../api/mune';
const { Header, Sider, Content } = Layout;
export default function AppLayout() {
const [collapsed, setCollapsed] = useState(false);
const [time, setTime] = useState(new Date().toLocaleString());
const [menuList, setMenuList] = useState([]);
const navigate = useNavigate();
const location = useLocation();
const { userInfo } = useSelector((state: RootState) => state.user);
const dispatch = useAppDispatch();
const getMenu = () => {
getMenuList().then(res => {
if (res.code == 200) {
setMenuList(res.data);
} else {
message.error("获取菜单失败:" + res.msg)
}
})
}
useEffect(() => {
const timer = setInterval(() => setTime(new Date().toLocaleString()), 1000);
getMenu();
return () => clearInterval(timer);
}, []);
const handleLogout = async () => {
await dispatch(logout());
navigate('/login', { replace: true });
};
const menuItems = [
{ key: '/', icon: <DashboardOutlined />, label: '总览首页', roles: ['admin', 'operator', 'viewer'] },
{ key: '/devices', icon: <ExperimentOutlined />, label: '设备管理', roles: ['admin', 'operator'] },
{ key: '/video', icon: <VideoCameraOutlined />, label: '在线视频', roles: ['admin', 'operator', 'viewer'] },
{ key: '/alerts', icon: <BellOutlined />, label: '告警中心', roles: ['admin', 'operator'] },
{ key: '/downloads', icon: <DownloadOutlined />, label: '视频下载', roles: ['admin', 'operator'] },
{ key: '/serialNumGenerate', icon: <UserOutlined />, label: '序列号生成', roles: ['admin'] },
{ key: '/users', icon: <TeamOutlined />, label: '用户管理', roles: ['admin'] },
{ key: '/roles', icon: <UserOutlined />, label: '角色管理', roles: ['admin'] },
{ key: '/menuList', icon: <MenuOutlined />, label: '菜单管理', roles: ['admin'] },
{ key: '/organization', icon: <MenuOutlined />, label: '组织管理', roles: ['admin'] },
{ key: '/station', icon: <MenuOutlined />, label: '场站管理', roles: ['admin'] }
];
const filteredMenu = menuItems;
const currentPathLabel = menuItems.find(i => i.key === location.pathname)?.label || '总览首页';
return (
<ConfigProvider
locale={zhCN}
theme={{
algorithm: theme.defaultAlgorithm,
token: { colorPrimary: '#1890ff', borderRadius: 2 },
components: {
Layout: { headerBg: '#fff', siderBg: '#001529' },
Menu: { darkItemBg: '#001529', darkItemSelectedBg: '#1890ff' }
}
}}
>
<Layout style={{ maxHeight: '100vh' }}>
{/* 顶部通栏 */}
<Header className=" px-6 flex items-center justify-between shadow-sm border-b border-gray-100 h-0 shrink-0 z-10" style={{
background: '#0837a8',
color: '#fff',
height: "50px"
}}>
<div className="h-16 flex items-center justify-center border-b border-white/10 overflow-hidden">
<Space size="small" className="px-2">
<img src={logo} alt="Logo" style={{ width: "24px", height: "24px" }} />
{!collapsed && (
<span className="text-white font-bold text-lg whitespace-nowrap">
光伏电站智能监控与运维系统
</span>
)}
</Space>
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-3 pr-6 border-r border-gray-100">
<span className="text-gray-400 font-mono text-[13px]">{time}</span>
<Badge status="success" />
<span className="text-white font-medium">{userInfo?.username}</span>
</div>
<Button
type="text"
danger
icon={<LogoutOutlined />}
onClick={handleLogout}
className="font-bold text-[12px] uppercase"
>
退出登录
</Button>
</div>
</Header>
<Layout style={{
overflow: 'auto', background: '#ffffff',
height: "calc(100vh - 50px)",
overflowX: 'hidden'
}}>
{/* 左侧:菜单 + 下部区域 */}
<Sider
trigger={null}
collapsible
collapsed={collapsed}
width={220}
theme="light"
style={{
overflow: 'auto', background: '#ffffff',
borderRight: '1px solid #e5e7eb',
}}
>
<Menu
theme="light"
mode="inline"
selectedKeys={[location.pathname]}
items={filteredMenu}
onClick={({ key }) => navigate(key)}
/>
{/* 左侧下方固定区域(你可以放任何内容) */}
<div className="p-3 text-white text-xs opacity-70 text-center mt-4">
{!collapsed && '快捷操作面板'}
</div>
</Sider>
{/* 右侧:主内容 */}
<Content className=" overflow-y-auto bg-[#f4f7f9] min-h-0 overflow-x-hidden">
<Outlet />
</Content>
</Layout>
</Layout>
</ConfigProvider >
);
}