2025-12-26 11:03:47 +08:00
|
|
|
import React, { useState, Suspense, lazy } from 'react';
|
2025-12-25 10:48:35 +08:00
|
|
|
import { Layout, Menu, theme, Button, Dropdown, Avatar, message, Space, Divider } from 'antd';
|
2025-12-26 11:03:47 +08:00
|
|
|
import { BarChartOutlined, DatabaseOutlined, CloudServerOutlined, MenuUnfoldOutlined, MenuFoldOutlined, EyeOutlined, BuildOutlined, HomeOutlined, ShoppingCartOutlined, InboxOutlined, ImportOutlined, FileTextOutlined, UserOutlined, LogoutOutlined, HistoryOutlined, AuditOutlined, ToolOutlined, ScheduleOutlined } from '@ant-design/icons';
|
2025-12-25 10:48:35 +08:00
|
|
|
import { BrowserRouter as Router, Routes, Route, Link, Navigate, useLocation, useNavigate } from 'react-router-dom';
|
|
|
|
|
import { useAuth } from './context/AuthContext';
|
|
|
|
|
import { Spin } from 'antd';
|
2025-12-18 10:45:13 +08:00
|
|
|
|
2025-12-26 11:03:47 +08:00
|
|
|
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
|
|
|
|
const DeviceManagement = lazy(() => import('./pages/DeviceManagement'));
|
|
|
|
|
const RackManagement = lazy(() => import('./pages/RackManagement'));
|
|
|
|
|
const RoomManagement = lazy(() => import('./pages/RoomManagement'));
|
|
|
|
|
const DeviceFieldManagement = lazy(() => import('./pages/DeviceFieldManagement'));
|
|
|
|
|
const RackVisualization = lazy(() => import('./pages/RackVisualization'));
|
|
|
|
|
const ConsumableManagement = lazy(() => import('./pages/ConsumableManagement'));
|
|
|
|
|
const ConsumableStatistics = lazy(() => import('./pages/ConsumableStatistics'));
|
|
|
|
|
const ConsumableLogs = lazy(() => import('./pages/ConsumableLogs'));
|
|
|
|
|
const CategoryManagement = lazy(() => import('./pages/CategoryManagement'));
|
|
|
|
|
const UserManagement = lazy(() => import('./pages/UserManagement'));
|
|
|
|
|
const LoginHistory = lazy(() => import('./pages/LoginHistory'));
|
|
|
|
|
const OperationLogs = lazy(() => import('./pages/OperationLogs'));
|
|
|
|
|
const Login = lazy(() => import('./pages/Login'));
|
|
|
|
|
const TicketManagement = lazy(() => import('./pages/TicketManagement'));
|
|
|
|
|
const TicketCategoryManagement = lazy(() => import('./pages/TicketCategoryManagement'));
|
|
|
|
|
const TicketStatistics = lazy(() => import('./pages/TicketStatistics'));
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
const { Header, Content, Sider } = Layout;
|
2025-12-18 10:45:13 +08:00
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
const PrivateRoute = ({ children }) => {
|
|
|
|
|
const { token, initialized, loading } = useAuth();
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
|
|
|
|
|
if (!initialized) {
|
|
|
|
|
return (
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
height: '100vh',
|
|
|
|
|
background: '#f5f5f5'
|
|
|
|
|
}}>
|
|
|
|
|
<Spin size="large" tip="正在加载认证状态..." />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
return <Navigate to="/login" state={{ from: location }} replace />;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-26 11:03:47 +08:00
|
|
|
return (
|
|
|
|
|
<Suspense
|
|
|
|
|
fallback={
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
height: '100vh',
|
|
|
|
|
background: '#f5f5f5'
|
|
|
|
|
}}>
|
|
|
|
|
<Spin size="large" tip="正在加载页面..." />
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<AppLayout>
|
|
|
|
|
{children}
|
|
|
|
|
</AppLayout>
|
|
|
|
|
</Suspense>
|
|
|
|
|
);
|
2025-12-25 10:48:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AppLayout = ({ children }) => {
|
2025-12-19 20:01:15 +08:00
|
|
|
const [collapsed, setCollapsed] = useState(false);
|
2025-12-25 10:48:35 +08:00
|
|
|
const { user, logout } = useAuth();
|
|
|
|
|
const navigate = useNavigate();
|
2025-12-18 10:45:13 +08:00
|
|
|
const {
|
|
|
|
|
token: { colorBgContainer, borderRadiusLG },
|
|
|
|
|
} = theme.useToken();
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
const handleLogout = () => {
|
|
|
|
|
logout();
|
|
|
|
|
message.success('已退出登录');
|
|
|
|
|
navigate('/login');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const userMenuItems = [
|
|
|
|
|
{
|
|
|
|
|
key: 'logout',
|
|
|
|
|
icon: <LogoutOutlined />,
|
|
|
|
|
label: '退出登录',
|
|
|
|
|
onClick: handleLogout
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
2025-12-18 10:45:13 +08:00
|
|
|
return (
|
2025-12-25 10:48:35 +08:00
|
|
|
<Layout>
|
|
|
|
|
<Sider
|
|
|
|
|
width={220}
|
|
|
|
|
collapsedWidth={80}
|
|
|
|
|
collapsed={collapsed}
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: colorBgContainer,
|
|
|
|
|
boxShadow: '2px 0 8px rgba(0,0,0,0.08)',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column'
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
height: 64,
|
|
|
|
|
padding: '0 12px',
|
|
|
|
|
borderBottom: '1px solid #f0f0f0',
|
|
|
|
|
marginBottom: 8
|
|
|
|
|
}}>
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
|
|
|
onClick={() => setCollapsed(!collapsed)}
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
padding: '8px',
|
|
|
|
|
borderRadius: 4
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Menu
|
|
|
|
|
mode="inline"
|
|
|
|
|
defaultSelectedKeys={['dashboard']}
|
2025-12-19 20:01:15 +08:00
|
|
|
style={{
|
2025-12-25 10:48:35 +08:00
|
|
|
flex: 1,
|
|
|
|
|
borderRight: 0,
|
|
|
|
|
backgroundColor: 'transparent'
|
|
|
|
|
}}
|
|
|
|
|
items={[
|
|
|
|
|
{
|
|
|
|
|
key: 'dashboard',
|
|
|
|
|
icon: <BarChartOutlined />,
|
|
|
|
|
label: <Link to="/">仪表盘</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'room-management',
|
|
|
|
|
icon: <HomeOutlined />,
|
|
|
|
|
label: '机房管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'rooms',
|
|
|
|
|
icon: <HomeOutlined />,
|
|
|
|
|
label: <Link to="/rooms">机房管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'racks',
|
|
|
|
|
icon: <DatabaseOutlined />,
|
|
|
|
|
label: <Link to="/racks">机柜管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'visualization',
|
|
|
|
|
icon: <EyeOutlined />,
|
|
|
|
|
label: <Link to="/visualization">机柜可视化</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'asset-management',
|
|
|
|
|
icon: <BuildOutlined />,
|
|
|
|
|
label: '资产管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'devices',
|
|
|
|
|
icon: <CloudServerOutlined />,
|
|
|
|
|
label: <Link to="/devices">设备管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'fields',
|
|
|
|
|
icon: <DatabaseOutlined />,
|
|
|
|
|
label: <Link to="/fields">字段管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables-management',
|
|
|
|
|
icon: <ShoppingCartOutlined />,
|
|
|
|
|
label: '耗材管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables-stats',
|
|
|
|
|
icon: <BarChartOutlined />,
|
|
|
|
|
label: <Link to="/consumables-stats">耗材统计</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables',
|
|
|
|
|
icon: <DatabaseOutlined />,
|
|
|
|
|
label: <Link to="/consumables">耗材列表</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables-categories',
|
|
|
|
|
icon: <ImportOutlined />,
|
|
|
|
|
label: <Link to="/consumables-categories">分类管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables-logs',
|
|
|
|
|
icon: <FileTextOutlined />,
|
|
|
|
|
label: <Link to="/consumables-logs">操作日志</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'system-management',
|
|
|
|
|
icon: <UserOutlined />,
|
|
|
|
|
label: '系统管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'users',
|
|
|
|
|
icon: <UserOutlined />,
|
|
|
|
|
label: <Link to="/users">用户管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'login-history',
|
|
|
|
|
icon: <HistoryOutlined />,
|
|
|
|
|
label: <Link to="/login-history">登录历史</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'operation-logs',
|
|
|
|
|
icon: <AuditOutlined />,
|
|
|
|
|
label: <Link to="/operation-logs">操作日志</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2025-12-25 16:49:00 +08:00
|
|
|
{
|
|
|
|
|
key: 'ticket-management',
|
|
|
|
|
icon: <ToolOutlined />,
|
|
|
|
|
label: '工单管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'tickets',
|
|
|
|
|
icon: <ScheduleOutlined />,
|
|
|
|
|
label: <Link to="/tickets">工单列表</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'ticket-categories',
|
|
|
|
|
icon: <InboxOutlined />,
|
|
|
|
|
label: <Link to="/ticket-categories">故障分类</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'ticket-statistics',
|
|
|
|
|
icon: <BarChartOutlined />,
|
|
|
|
|
label: <Link to="/ticket-statistics">统计报表</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2025-12-25 10:48:35 +08:00
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
</Sider>
|
|
|
|
|
<Layout style={{ padding: '0 24px 24px' }}>
|
|
|
|
|
<Header style={{
|
|
|
|
|
padding: '0 16px',
|
|
|
|
|
height: 56,
|
|
|
|
|
background: colorBgContainer,
|
|
|
|
|
marginBottom: 24,
|
|
|
|
|
borderRadius: borderRadiusLG,
|
|
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'flex-end',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
boxShadow: '0 1px 4px rgba(0,0,0,0.08)'
|
|
|
|
|
}}>
|
|
|
|
|
{user && (
|
|
|
|
|
<Space size={12}>
|
|
|
|
|
<Avatar
|
|
|
|
|
style={{ backgroundColor: '#1890ff', cursor: 'pointer' }}
|
|
|
|
|
icon={<UserOutlined />}
|
|
|
|
|
/>
|
|
|
|
|
<span style={{ color: '#666', fontSize: 14 }}>{user.username}</span>
|
|
|
|
|
<Divider type="vertical" style={{ margin: 0 }} />
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
danger
|
|
|
|
|
icon={<LogoutOutlined />}
|
|
|
|
|
onClick={handleLogout}
|
|
|
|
|
style={{ padding: '4px 8px' }}
|
|
|
|
|
>
|
|
|
|
|
退出
|
|
|
|
|
</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
)}
|
|
|
|
|
</Header>
|
|
|
|
|
<Content
|
|
|
|
|
style={{
|
|
|
|
|
padding: 24,
|
|
|
|
|
margin: 0,
|
|
|
|
|
minHeight: 280,
|
|
|
|
|
background: colorBgContainer,
|
|
|
|
|
borderRadius: borderRadiusLG,
|
2025-12-19 20:01:15 +08:00
|
|
|
}}
|
|
|
|
|
>
|
2025-12-25 10:48:35 +08:00
|
|
|
{children}
|
|
|
|
|
</Content>
|
2025-12-18 10:45:13 +08:00
|
|
|
</Layout>
|
2025-12-25 10:48:35 +08:00
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
return (
|
|
|
|
|
<Router>
|
|
|
|
|
<Routes>
|
2025-12-26 11:03:47 +08:00
|
|
|
<Route path="/login" element={
|
|
|
|
|
<Suspense
|
|
|
|
|
fallback={
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
height: '100vh',
|
|
|
|
|
background: '#f5f5f5'
|
|
|
|
|
}}>
|
|
|
|
|
<Spin size="large" tip="正在加载登录页面..." />
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<Login />
|
|
|
|
|
</Suspense>
|
|
|
|
|
} />
|
2025-12-25 10:48:35 +08:00
|
|
|
<Route
|
|
|
|
|
path="/"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<Dashboard />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/devices"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<DeviceManagement />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/racks"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<RackManagement />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/rooms"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<RoomManagement />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/fields"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<DeviceFieldManagement />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/visualization"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<RackVisualization />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/consumables"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<ConsumableManagement />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/consumables-categories"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<CategoryManagement />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/consumables-stats"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<ConsumableStatistics />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/consumables-logs"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<ConsumableLogs />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/users"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<UserManagement />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/login-history"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<LoginHistory />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/operation-logs"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<OperationLogs />
|
2025-12-25 10:48:35 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
2025-12-25 16:49:00 +08:00
|
|
|
<Route
|
|
|
|
|
path="/tickets"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<TicketManagement />
|
2025-12-25 16:49:00 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/ticket-categories"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<TicketCategoryManagement />
|
2025-12-25 16:49:00 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Route
|
|
|
|
|
path="/ticket-statistics"
|
|
|
|
|
element={
|
|
|
|
|
<PrivateRoute>
|
2025-12-26 11:03:47 +08:00
|
|
|
<TicketStatistics />
|
2025-12-25 16:49:00 +08:00
|
|
|
</PrivateRoute>
|
|
|
|
|
}
|
|
|
|
|
/>
|
2025-12-25 10:48:35 +08:00
|
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
|
|
|
</Routes>
|
2025-12-18 10:45:13 +08:00
|
|
|
</Router>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App;
|