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

208 lines
6.0 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import {
Layout, Menu, Button, Space, ConfigProvider, theme, Select, Avatar
} from 'antd';
import zhCN from 'antd/locale/zh_CN';
import {
DashboardOutlined, ExperimentOutlined, VideoCameraOutlined,
BellOutlined, UserOutlined, LogoutOutlined,
AppstoreOutlined, SettingOutlined, MenuUnfoldOutlined, MenuFoldOutlined
} 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 { setCurrentStation, clearStation } from '../store/stationSlice';
import { getMenuList } from '../api/mune';
import { getStationList } from '../api/stationManage';
import envConfig from '../../env';
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 [stationList, setStationList] = useState([]);
const navigate = useNavigate();
const location = useLocation();
const dispatch = useAppDispatch();
const { userInfo } = useSelector((state: RootState) => state.user);
const { currentStation, stationId } = useSelector((state: RootState) => state.station);
const getStationData = () => {
const params = {
pageNum: 1,
pageSize: 9999,
orgId: userInfo?.orgId,
};
getStationList(params).then(res => {
if (res.code === 200) {
const list = res.rows || [];
setStationList(list);
if (!currentStation && list.length > 0) {
dispatch(setCurrentStation(list[0]));
}
}
});
};
const handleStationChange = (id: number) => {
const station = stationList.find(s => s.id === id);
if (station) {
dispatch(setCurrentStation(station));
}
};
const getMenu = () => {
const data = { pageNum: 1, pageSize: 10000 };
getMenuList(data).then(res => {
if (res.code == 200) {
const rawList = res.rows || [];
const topMenus = rawList.filter(item => item.parentId === 0);
setMenuList(topMenus);
}
});
};
const getIcon = (path) => {
if (path.includes('overview')) return <DashboardOutlined />;
if (path.includes('device')) return <ExperimentOutlined />;
if (path.includes('monitor')) return <VideoCameraOutlined />;
if (path.includes('alert')) return <BellOutlined />;
if (path.includes('user')) return <UserOutlined />;
if (path.includes('system')) return <SettingOutlined />;
if (path.includes('menu')) return <AppstoreOutlined />;
return <AppstoreOutlined />;
};
const menuItems = menuList.map(item => ({
key: item.path || `/menu/${item.menuId}`,
icon: getIcon(item.path),
label: item.menuName,
}));
const handleLogout = async () => {
await dispatch(logout());
dispatch(clearStation());
navigate('/login', { replace: true });
};
useEffect(() => {
const timer = setInterval(() => setTime(new Date().toLocaleString()), 1000);
getMenu();
getStationData();
return () => clearInterval(timer);
}, [userInfo]);
return (
<ConfigProvider
locale={zhCN}
theme={{
algorithm: theme.defaultAlgorithm,
token: { colorPrimary: '#1890ff', borderRadius: 2 },
components: {
Layout: { headerBg: '#fff', siderBg: '#fff' },
Menu: { itemBg: '#fff', itemSelectedBg: '#e6f4ff' }
}
}}
>
<Layout style={{ maxHeight: '100vh' }}>
<Header style={{
background: '#0837a8',
color: '#fff',
height: "50px",
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 16px'
}}>
<Space size="large" style={{ display: 'flex', alignItems: 'center' }}>
{/* ====================== 收起/展开按钮 ====================== */}
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
style={{
fontSize: '16px',
color: '#fff',
width: 40,
height: 40,
}}
/>
<Space size="small">
<img src={logo} alt="Logo" style={{ width: 24, height: 24 }} />
{!collapsed && (
<span style={{ fontWeight: 'bold', fontSize: 16, color: '#fff' }}>
光伏电站智能监控与运维系统
</span>
)}
</Space>
<Select
value={stationId}
placeholder="请选择电站"
onChange={handleStationChange}
options={stationList.map(s => ({ label: s.siteName, value: s.id }))}
style={{
width: 180,
backgroundColor: '#0837a8',
color: '#fff',
border: 'none',
}}
/>
</Space>
<Space size="middle">
<span style={{ fontSize: 13 }}>{time}</span>
<Space
style={{ cursor: 'pointer', padding: '0 4px' }}
onClick={() => navigate('/profile')}
className="hover:opacity-80"
>
{userInfo?.avatar && (
<Avatar
size="small"
src={userInfo.avatar.startsWith('http') ? userInfo.avatar : `${envConfig.baseURL}${userInfo.avatar}`}
/>
)}
<span style={{ fontSize: 14 }}>{userInfo?.nickName || userInfo?.username}</span>
</Space>
<Button type="text" danger icon={<LogoutOutlined />} onClick={handleLogout}>
退出登录
</Button>
</Space>
</Header>
<Layout style={{ height: 'calc(100vh - 50px)', overflow: 'hidden', background: '#fff' }}>
<Sider
collapsible
collapsed={collapsed}
trigger={null}
width={150}
theme="light"
style={{ borderRight: '1px solid #e5e7eb' }}
>
<Menu
mode="inline"
theme="light"
selectedKeys={[location.pathname]}
items={menuItems}
onClick={({ key }) => navigate(key)}
/>
</Sider>
<Content style={{ background: '#f4f7f9', overflowY: 'auto', padding: '0px 16px' }}>
<Outlet />
</Content>
</Layout>
</Layout>
</ConfigProvider>
);
}