2025-12-26 11:03:47 +08:00
|
|
|
import React, { useState, Suspense, lazy } from 'react';
|
2026-01-21 10:40:30 +08:00
|
|
|
import { Layout, Menu, theme, Button, Dropdown, Avatar, message, Space, Divider, ConfigProvider as AntdConfigProvider } from 'antd';
|
2026-01-23 08:55:16 +08:00
|
|
|
import { BarChartOutlined, DatabaseOutlined, CloudServerOutlined, MenuUnfoldOutlined, MenuFoldOutlined, EyeOutlined, BuildOutlined, HomeOutlined, ShoppingCartOutlined, InboxOutlined, ImportOutlined, FileTextOutlined, UserOutlined, LogoutOutlined, HistoryOutlined, AuditOutlined, ToolOutlined, ScheduleOutlined, SettingOutlined, ApiOutlined, PartitionOutlined } 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';
|
2026-01-21 10:40:30 +08:00
|
|
|
import { ConfigProvider, useConfig } from './context/ConfigContext';
|
2025-12-25 10:48:35 +08:00
|
|
|
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 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-29 08:40:08 +08:00
|
|
|
const TicketFieldManagement = lazy(() => import('./pages/TicketFieldManagement'));
|
2025-12-29 16:10:42 +08:00
|
|
|
const SystemSettings = lazy(() => import('./pages/SystemSettings'));
|
2026-01-23 08:55:16 +08:00
|
|
|
const CableManagement = lazy(() => import('./pages/CableManagement'));
|
|
|
|
|
const PortManagement = lazy(() => import('./pages/PortManagement'));
|
2025-12-26 11:03:47 +08:00
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
const { Header, Content, Sider } = Layout;
|
2025-12-18 10:45:13 +08:00
|
|
|
|
2026-01-20 14:31:20 +08:00
|
|
|
const PageLoading = () => (
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
height: '100vh',
|
|
|
|
|
background: '#f5f5f5',
|
|
|
|
|
gap: '16px'
|
|
|
|
|
}}>
|
|
|
|
|
<Spin size="large" />
|
|
|
|
|
<span style={{ color: '#8c8c8c', fontSize: '14px' }}>正在加载页面...</span>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const AuthLoading = () => (
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
height: '100vh',
|
|
|
|
|
background: '#f5f5f5',
|
|
|
|
|
gap: '16px'
|
|
|
|
|
}}>
|
|
|
|
|
<Spin size="large" />
|
|
|
|
|
<span style={{ color: '#8c8c8c', fontSize: '14px' }}>正在加载认证状态...</span>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
2026-01-20 11:28:53 +08:00
|
|
|
const designTokens = {
|
|
|
|
|
colors: {
|
|
|
|
|
primary: {
|
|
|
|
|
main: '#667eea',
|
|
|
|
|
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
|
|
|
light: '#8b9ff0'
|
|
|
|
|
},
|
|
|
|
|
success: { main: '#10b981' },
|
|
|
|
|
warning: { main: '#f59e0b' },
|
|
|
|
|
error: { main: '#ef4444' },
|
|
|
|
|
text: {
|
|
|
|
|
primary: '#1e293b',
|
|
|
|
|
secondary: '#64748b',
|
|
|
|
|
inverse: '#ffffff'
|
|
|
|
|
},
|
|
|
|
|
background: {
|
|
|
|
|
primary: '#ffffff',
|
|
|
|
|
secondary: '#f8fafc',
|
|
|
|
|
dark: '#1e293b'
|
|
|
|
|
},
|
|
|
|
|
border: {
|
|
|
|
|
light: '#e2e8f0'
|
|
|
|
|
},
|
|
|
|
|
sidebar: {
|
|
|
|
|
bg: '#ffffff',
|
|
|
|
|
bgHover: 'rgba(102, 126, 234, 0.08)',
|
|
|
|
|
bgActive: 'rgba(102, 126, 234, 0.15)',
|
|
|
|
|
text: '#475569',
|
|
|
|
|
textHover: '#667eea',
|
|
|
|
|
textActive: '#667eea',
|
|
|
|
|
border: '#e2e8f0'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
shadows: {
|
|
|
|
|
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
|
|
|
|
medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
|
|
|
large: '0 10px 15px -3px rgba(0, 0, 0, 0.1)'
|
|
|
|
|
},
|
|
|
|
|
borderRadius: {
|
|
|
|
|
small: '6px',
|
|
|
|
|
medium: '10px'
|
|
|
|
|
},
|
|
|
|
|
spacing: {
|
|
|
|
|
sm: '8px',
|
|
|
|
|
md: '16px',
|
|
|
|
|
lg: '24px'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
const PrivateRoute = ({ children }) => {
|
|
|
|
|
const { token, initialized, loading } = useAuth();
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
|
|
|
|
|
if (!initialized) {
|
2026-01-20 14:31:20 +08:00
|
|
|
return <AuthLoading />;
|
2025-12-25 10:48:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
return <Navigate to="/login" state={{ from: location }} replace />;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 14:31:20 +08:00
|
|
|
return <AppLayout>{children}</AppLayout>;
|
2025-12-25 10:48:35 +08:00
|
|
|
};
|
|
|
|
|
|
2026-01-20 14:31:20 +08:00
|
|
|
const ProtectedRoute = ({ component: Component }) => (
|
|
|
|
|
<PrivateRoute>
|
|
|
|
|
<Component />
|
|
|
|
|
</PrivateRoute>
|
|
|
|
|
);
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
const AppLayout = ({ children }) => {
|
2025-12-19 20:01:15 +08:00
|
|
|
const [collapsed, setCollapsed] = useState(false);
|
2026-01-20 11:28:53 +08:00
|
|
|
const [activeKey, setActiveKey] = useState('dashboard');
|
2025-12-25 10:48:35 +08:00
|
|
|
const { user, logout } = useAuth();
|
|
|
|
|
const navigate = useNavigate();
|
2026-01-20 11:28:53 +08:00
|
|
|
const location = useLocation();
|
2026-01-21 10:40:30 +08:00
|
|
|
const { config } = useConfig();
|
2026-01-20 11:28:53 +08:00
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
const handleLogout = () => {
|
|
|
|
|
logout();
|
|
|
|
|
message.success('已退出登录');
|
|
|
|
|
navigate('/login');
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-20 11:28:53 +08:00
|
|
|
const getSelectedKey = () => {
|
|
|
|
|
const path = location.pathname;
|
|
|
|
|
if (path === '/') return 'dashboard';
|
|
|
|
|
if (path.startsWith('/rooms') || path.startsWith('/racks') || path.startsWith('/visualization')) return 'room-management';
|
2026-01-23 08:55:16 +08:00
|
|
|
if (path.startsWith('/devices') || path.startsWith('/fields') || path.startsWith('/cables') || path.startsWith('/ports')) return 'asset-management';
|
2026-01-20 11:28:53 +08:00
|
|
|
if (path.startsWith('/consumables')) return 'consumables-management';
|
|
|
|
|
if (path.startsWith('/users') || path.startsWith('/login-history') || path.startsWith('/operation-logs') || path.startsWith('/settings')) return 'system-management';
|
|
|
|
|
if (path.startsWith('/tickets')) return 'ticket-management';
|
|
|
|
|
return 'dashboard';
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-21 10:40:30 +08:00
|
|
|
// 动态设计令牌
|
|
|
|
|
const designTokens = {
|
|
|
|
|
colors: {
|
|
|
|
|
primary: {
|
|
|
|
|
main: config.primary_color || '#667eea',
|
|
|
|
|
gradient: `linear-gradient(135deg, ${config.primary_color || '#667eea'} 0%, ${config.secondary_color || '#764ba2'} 100%)`,
|
|
|
|
|
light: '#8b9ff0'
|
|
|
|
|
},
|
|
|
|
|
success: { main: '#10b981' },
|
|
|
|
|
warning: { main: '#f59e0b' },
|
|
|
|
|
error: { main: '#ef4444' },
|
|
|
|
|
text: {
|
|
|
|
|
primary: '#1e293b',
|
|
|
|
|
secondary: '#64748b',
|
|
|
|
|
inverse: '#ffffff'
|
|
|
|
|
},
|
|
|
|
|
background: {
|
|
|
|
|
primary: '#ffffff',
|
|
|
|
|
secondary: '#f8fafc',
|
|
|
|
|
dark: '#1e293b'
|
|
|
|
|
},
|
|
|
|
|
border: {
|
|
|
|
|
light: '#e2e8f0'
|
|
|
|
|
},
|
|
|
|
|
sidebar: {
|
|
|
|
|
bg: '#ffffff',
|
|
|
|
|
bgHover: 'rgba(102, 126, 234, 0.08)',
|
|
|
|
|
bgActive: 'rgba(102, 126, 234, 0.15)',
|
|
|
|
|
text: '#475569',
|
|
|
|
|
textHover: config.primary_color || '#667eea',
|
|
|
|
|
textActive: config.primary_color || '#667eea',
|
|
|
|
|
border: '#e2e8f0'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
shadows: {
|
|
|
|
|
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
|
|
|
|
medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
|
|
|
|
|
large: '0 10px 15px -3px rgba(0, 0, 0, 0.1)'
|
|
|
|
|
},
|
|
|
|
|
borderRadius: {
|
|
|
|
|
small: '6px',
|
|
|
|
|
medium: '10px'
|
|
|
|
|
},
|
|
|
|
|
spacing: {
|
|
|
|
|
sm: '8px',
|
|
|
|
|
md: '16px',
|
|
|
|
|
lg: '24px'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-20 11:28:53 +08:00
|
|
|
const menuItems = [
|
2025-12-25 10:48:35 +08:00
|
|
|
{
|
2026-01-20 11:28:53 +08:00
|
|
|
key: 'dashboard',
|
|
|
|
|
icon: <BarChartOutlined style={{ fontSize: '18px' }} />,
|
|
|
|
|
label: <Link to="/">仪表盘</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'room-management',
|
|
|
|
|
icon: <HomeOutlined style={{ fontSize: '18px' }} />,
|
|
|
|
|
label: '机房管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'rooms',
|
|
|
|
|
icon: <HomeOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/rooms">机房管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'racks',
|
|
|
|
|
icon: <DatabaseOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/racks">机柜管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'visualization',
|
|
|
|
|
icon: <EyeOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/visualization">机柜可视化</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'asset-management',
|
|
|
|
|
icon: <BuildOutlined style={{ fontSize: '18px' }} />,
|
|
|
|
|
label: '资产管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'devices',
|
|
|
|
|
icon: <CloudServerOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/devices">设备管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'fields',
|
|
|
|
|
icon: <DatabaseOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/fields">字段管理</Link>,
|
|
|
|
|
},
|
2026-01-23 08:55:16 +08:00
|
|
|
{
|
|
|
|
|
key: 'cables',
|
|
|
|
|
icon: <ApiOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/cables">接线管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'ports',
|
|
|
|
|
icon: <PartitionOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/ports">端口管理</Link>,
|
|
|
|
|
},
|
2026-01-20 11:28:53 +08:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables-management',
|
|
|
|
|
icon: <ShoppingCartOutlined style={{ fontSize: '18px' }} />,
|
|
|
|
|
label: '耗材管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables-stats',
|
|
|
|
|
icon: <BarChartOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/consumables-stats">耗材统计</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables',
|
|
|
|
|
icon: <DatabaseOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/consumables">耗材列表</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables-categories',
|
|
|
|
|
icon: <ImportOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/consumables-categories">分类管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'consumables-logs',
|
|
|
|
|
icon: <FileTextOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/consumables-logs">操作日志</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'ticket-management',
|
|
|
|
|
icon: <ToolOutlined style={{ fontSize: '18px' }} />,
|
|
|
|
|
label: '工单管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'tickets',
|
|
|
|
|
icon: <ScheduleOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/tickets">工单列表</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'ticket-categories',
|
|
|
|
|
icon: <InboxOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/ticket-categories">故障分类</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'ticket-statistics',
|
|
|
|
|
icon: <BarChartOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/ticket-statistics">统计报表</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'ticket-fields',
|
|
|
|
|
icon: <DatabaseOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/ticket-fields">字段管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-01-20 16:54:26 +08:00
|
|
|
{
|
|
|
|
|
key: 'system-management',
|
|
|
|
|
icon: <UserOutlined style={{ fontSize: '18px' }} />,
|
|
|
|
|
label: '系统管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: 'users',
|
|
|
|
|
icon: <UserOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/users">用户管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'system-settings',
|
|
|
|
|
icon: <SettingOutlined style={{ fontSize: '16px' }} />,
|
|
|
|
|
label: <Link to="/settings">系统设置</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2025-12-25 10:48:35 +08:00
|
|
|
];
|
|
|
|
|
|
2025-12-18 10:45:13 +08:00
|
|
|
return (
|
2026-01-20 11:28:53 +08:00
|
|
|
<Layout style={{ minHeight: '100vh' }}>
|
2025-12-25 10:48:35 +08:00
|
|
|
<Sider
|
2026-01-20 11:28:53 +08:00
|
|
|
width={240}
|
|
|
|
|
collapsedWidth={72}
|
2025-12-25 10:48:35 +08:00
|
|
|
collapsed={collapsed}
|
|
|
|
|
style={{
|
2026-01-20 11:28:53 +08:00
|
|
|
background: designTokens.colors.sidebar.bg,
|
|
|
|
|
boxShadow: '2px 0 8px rgba(0, 0, 0, 0.06)',
|
|
|
|
|
borderRight: `1px solid ${designTokens.colors.sidebar.border}`,
|
2025-12-25 10:48:35 +08:00
|
|
|
display: 'flex',
|
2026-01-20 11:28:53 +08:00
|
|
|
flexDirection: 'column',
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
left: 0,
|
|
|
|
|
top: 0,
|
|
|
|
|
bottom: 0,
|
|
|
|
|
zIndex: 100
|
2025-12-25 10:48:35 +08:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
2026-01-20 11:28:53 +08:00
|
|
|
justifyContent: collapsed ? 'center' : 'flex-start',
|
|
|
|
|
padding: collapsed ? '16px 0' : '16px 20px',
|
|
|
|
|
borderBottom: `1px solid ${designTokens.colors.sidebar.border}`,
|
|
|
|
|
minHeight: '64px',
|
|
|
|
|
background: designTokens.colors.sidebar.bg
|
|
|
|
|
}}>
|
|
|
|
|
<div style={{
|
|
|
|
|
width: '36px',
|
|
|
|
|
height: '36px',
|
|
|
|
|
borderRadius: designTokens.borderRadius.small,
|
|
|
|
|
background: designTokens.colors.primary.gradient,
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
flexShrink: 0
|
|
|
|
|
}}>
|
|
|
|
|
<CloudServerOutlined style={{ fontSize: '18px', color: '#ffffff' }} />
|
|
|
|
|
</div>
|
|
|
|
|
{!collapsed && (
|
|
|
|
|
<div>
|
|
|
|
|
<div style={{
|
|
|
|
|
fontSize: '15px',
|
|
|
|
|
fontWeight: '600',
|
|
|
|
|
color: designTokens.colors.primary.main,
|
|
|
|
|
lineHeight: 1.2
|
|
|
|
|
}}>
|
2026-01-21 10:40:30 +08:00
|
|
|
{config.site_name || 'IDC管理'}
|
2026-01-20 11:28:53 +08:00
|
|
|
</div>
|
|
|
|
|
<div style={{
|
|
|
|
|
fontSize: '11px',
|
|
|
|
|
color: designTokens.colors.sidebar.text,
|
|
|
|
|
marginTop: '2px'
|
|
|
|
|
}}>
|
|
|
|
|
数据中心管理平台
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
padding: collapsed ? '12px 0' : '12px 8px',
|
|
|
|
|
overflowY: 'auto',
|
|
|
|
|
flex: 1
|
|
|
|
|
}}>
|
|
|
|
|
<Menu
|
|
|
|
|
mode="inline"
|
|
|
|
|
selectedKeys={[getSelectedKey()]}
|
2026-01-21 10:40:30 +08:00
|
|
|
defaultOpenKeys={[]}
|
2026-01-20 11:28:53 +08:00
|
|
|
style={{
|
|
|
|
|
background: 'transparent',
|
|
|
|
|
borderRight: 0,
|
|
|
|
|
fontSize: '14px'
|
|
|
|
|
}}
|
|
|
|
|
items={menuItems}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
padding: '12px',
|
|
|
|
|
borderTop: `1px solid ${designTokens.colors.sidebar.border}`
|
2025-12-25 10:48:35 +08:00
|
|
|
}}>
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
|
|
|
onClick={() => setCollapsed(!collapsed)}
|
|
|
|
|
style={{
|
2026-01-20 11:28:53 +08:00
|
|
|
width: '100%',
|
|
|
|
|
height: '40px',
|
|
|
|
|
color: designTokens.colors.text.secondary,
|
|
|
|
|
fontSize: '16px',
|
|
|
|
|
borderRadius: designTokens.borderRadius.small,
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: collapsed ? 'center' : 'flex-start',
|
|
|
|
|
gap: '8px'
|
2025-12-25 10:48:35 +08:00
|
|
|
}}
|
2026-01-20 11:28:53 +08:00
|
|
|
>
|
|
|
|
|
{!collapsed && <span style={{ fontSize: '13px', color: designTokens.colors.text.secondary }}>收起菜单</span>}
|
|
|
|
|
</Button>
|
2025-12-25 10:48:35 +08:00
|
|
|
</div>
|
|
|
|
|
</Sider>
|
2026-01-20 11:28:53 +08:00
|
|
|
|
|
|
|
|
<Layout style={{
|
|
|
|
|
marginLeft: collapsed ? 72 : 240,
|
|
|
|
|
transition: 'margin-left 0.2s ease'
|
|
|
|
|
}}>
|
2025-12-25 10:48:35 +08:00
|
|
|
<Header style={{
|
2026-01-20 11:28:53 +08:00
|
|
|
padding: '0 24px',
|
|
|
|
|
height: 64,
|
|
|
|
|
background: designTokens.colors.background.primary,
|
2025-12-25 10:48:35 +08:00
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'flex-end',
|
|
|
|
|
alignItems: 'center',
|
2026-01-20 11:28:53 +08:00
|
|
|
boxShadow: designTokens.shadows.small,
|
|
|
|
|
position: 'sticky',
|
|
|
|
|
top: 0,
|
|
|
|
|
zIndex: 99
|
2025-12-25 10:48:35 +08:00
|
|
|
}}>
|
|
|
|
|
{user && (
|
2026-01-20 11:28:53 +08:00
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: '12px'
|
|
|
|
|
}}>
|
|
|
|
|
<div style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: '8px',
|
|
|
|
|
padding: '6px 12px',
|
|
|
|
|
background: designTokens.colors.background.secondary,
|
|
|
|
|
borderRadius: designTokens.borderRadius.medium
|
|
|
|
|
}}>
|
|
|
|
|
<Avatar
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: designTokens.colors.primary.main,
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
width: 32,
|
|
|
|
|
height: 32,
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center'
|
|
|
|
|
}}
|
|
|
|
|
icon={<UserOutlined style={{ fontSize: '14px' }} />}
|
|
|
|
|
/>
|
|
|
|
|
<span style={{
|
|
|
|
|
color: designTokens.colors.text.primary,
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
fontWeight: 500
|
|
|
|
|
}}>{user.username}</span>
|
|
|
|
|
</div>
|
2025-12-25 10:48:35 +08:00
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
danger
|
|
|
|
|
icon={<LogoutOutlined />}
|
|
|
|
|
onClick={handleLogout}
|
2026-01-20 11:28:53 +08:00
|
|
|
style={{
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
height: 'auto',
|
|
|
|
|
borderRadius: designTokens.borderRadius.small,
|
|
|
|
|
fontSize: 13
|
|
|
|
|
}}
|
2025-12-25 10:48:35 +08:00
|
|
|
>
|
|
|
|
|
退出
|
|
|
|
|
</Button>
|
2026-01-20 11:28:53 +08:00
|
|
|
</div>
|
2025-12-25 10:48:35 +08:00
|
|
|
)}
|
|
|
|
|
</Header>
|
|
|
|
|
<Content
|
|
|
|
|
style={{
|
|
|
|
|
padding: 24,
|
|
|
|
|
margin: 0,
|
2026-01-20 11:28:53 +08:00
|
|
|
minHeight: 'calc(100vh - 64px)',
|
|
|
|
|
background: designTokens.colors.background.secondary
|
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>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-21 10:40:30 +08:00
|
|
|
const ThemeConfig = () => {
|
|
|
|
|
const { config } = useConfig();
|
|
|
|
|
|
|
|
|
|
// 动态设计令牌
|
|
|
|
|
const designTokens = {
|
|
|
|
|
colors: {
|
|
|
|
|
primary: {
|
|
|
|
|
main: config.primary_color || '#667eea',
|
|
|
|
|
gradient: `linear-gradient(135deg, ${config.primary_color || '#667eea'} 0%, ${config.secondary_color || '#764ba2'} 100%)`,
|
|
|
|
|
light: '#8b9ff0'
|
|
|
|
|
},
|
|
|
|
|
success: { main: '#10b981' },
|
|
|
|
|
warning: { main: '#f59e0b' },
|
|
|
|
|
error: { main: '#ef4444' },
|
|
|
|
|
text: {
|
|
|
|
|
primary: '#1e293b',
|
|
|
|
|
secondary: '#64748b',
|
|
|
|
|
inverse: '#ffffff'
|
|
|
|
|
},
|
|
|
|
|
background: {
|
|
|
|
|
primary: '#ffffff',
|
|
|
|
|
secondary: '#f8fafc',
|
|
|
|
|
dark: '#1e293b'
|
|
|
|
|
},
|
|
|
|
|
border: {
|
|
|
|
|
light: '#e2e8f0'
|
|
|
|
|
},
|
|
|
|
|
sidebar: {
|
|
|
|
|
bg: '#ffffff',
|
|
|
|
|
bgHover: 'rgba(102, 126, 234, 0.08)',
|
|
|
|
|
bgActive: 'rgba(102, 126, 234, 0.15)',
|
|
|
|
|
text: '#475569',
|
|
|
|
|
textHover: config.primary_color || '#667eea',
|
|
|
|
|
textActive: config.primary_color || '#667eea',
|
|
|
|
|
border: '#e2e8f0'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
shadows: {
|
|
|
|
|
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
|
|
|
|
medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
|
|
|
|
|
large: '0 10px 15px -3px rgba(0, 0, 0, 0.1)'
|
|
|
|
|
},
|
|
|
|
|
borderRadius: {
|
|
|
|
|
small: '6px',
|
|
|
|
|
medium: '10px'
|
|
|
|
|
},
|
|
|
|
|
spacing: {
|
|
|
|
|
sm: '8px',
|
|
|
|
|
md: '16px',
|
|
|
|
|
lg: '24px'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<AntdConfigProvider theme={{ token: designTokens }}>
|
|
|
|
|
<Router>
|
|
|
|
|
<Suspense fallback={<PageLoading />}>
|
|
|
|
|
<Routes>
|
|
|
|
|
<Route path="/login" element={<Login />} />
|
|
|
|
|
<Route path="/" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
|
|
|
|
|
<Route path="/devices" element={<PrivateRoute><DeviceManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/racks" element={<PrivateRoute><RackManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/rooms" element={<PrivateRoute><RoomManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/fields" element={<PrivateRoute><DeviceFieldManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/visualization" element={<PrivateRoute><RackVisualization /></PrivateRoute>} />
|
|
|
|
|
<Route path="/consumables" element={<PrivateRoute><ConsumableManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/consumables-categories" element={<PrivateRoute><CategoryManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/consumables-stats" element={<PrivateRoute><ConsumableStatistics /></PrivateRoute>} />
|
|
|
|
|
<Route path="/consumables-logs" element={<PrivateRoute><ConsumableLogs /></PrivateRoute>} />
|
|
|
|
|
<Route path="/users" element={<PrivateRoute><UserManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/tickets" element={<PrivateRoute><TicketManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/ticket-categories" element={<PrivateRoute><TicketCategoryManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/ticket-statistics" element={<PrivateRoute><TicketStatistics /></PrivateRoute>} />
|
|
|
|
|
<Route path="/ticket-fields" element={<PrivateRoute><TicketFieldManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/settings" element={<PrivateRoute><SystemSettings /></PrivateRoute>} />
|
2026-01-23 08:55:16 +08:00
|
|
|
<Route path="/cables" element={<PrivateRoute><CableManagement /></PrivateRoute>} />
|
|
|
|
|
<Route path="/ports" element={<PrivateRoute><PortManagement /></PrivateRoute>} />
|
2026-01-21 10:40:30 +08:00
|
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
|
|
|
</Routes>
|
|
|
|
|
</Suspense>
|
|
|
|
|
</Router>
|
|
|
|
|
</AntdConfigProvider>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
function App() {
|
|
|
|
|
return (
|
2026-01-21 10:40:30 +08:00
|
|
|
<ConfigProvider>
|
|
|
|
|
<ThemeConfig />
|
|
|
|
|
</ConfigProvider>
|
2025-12-18 10:45:13 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App;
|