diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 64cb6cc..446e039 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -9,7 +9,7 @@ import RoomManagement from './pages/RoomManagement';
import DeviceFieldManagement from './pages/DeviceFieldManagement';
-const { Header, Content, Sider } = Layout;
+const { Content, Sider } = Layout;
function App() {
const {
@@ -19,18 +19,7 @@ function App() {
return (
-
-
-
+
-
-
-
- } />
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx
index 122f6b0..79b8b4a 100644
--- a/frontend/src/pages/Dashboard.jsx
+++ b/frontend/src/pages/Dashboard.jsx
@@ -1,6 +1,18 @@
import React, { useState, useEffect } from 'react';
-import { Card, Row, Col, Statistic, Spin, message } from 'antd';
-import { DatabaseOutlined, CloudServerOutlined, WarningOutlined, PoweroffOutlined } from '@ant-design/icons';
+import { Card, Row, Col, Statistic, Spin, message, Button, Tag } from 'antd';
+import {
+ DatabaseOutlined,
+ CloudServerOutlined,
+ WarningOutlined,
+ PoweroffOutlined,
+ HomeOutlined,
+ MonitorOutlined,
+ SettingOutlined,
+ ArrowUpOutlined,
+ ArrowDownOutlined,
+ DashboardOutlined,
+ ReloadOutlined
+} from '@ant-design/icons';
import axios from 'axios';
function Dashboard() {
@@ -8,7 +20,9 @@ function Dashboard() {
totalDevices: 0,
totalRacks: 0,
totalRooms: 0,
- faultDevices: 0
+ faultDevices: 0,
+ deviceGrowth: 2.5, // 示例增长数据
+ faultTrend: -12.3 // 示例趋势数据
});
const [loading, setLoading] = useState(true);
@@ -54,56 +68,424 @@ function Dashboard() {
fetchStats();
}, []);
+ // 科技感配色主题
+ const theme = {
+ primary: '#1890ff', // 科技蓝
+ primaryDark: '#096dd9', // 深蓝
+ secondary: '#722ed1', // 紫色
+ success: '#52c41a', // 绿色
+ warning: '#faad14', // 橙色
+ error: '#ff4d4f', // 红色
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+ cardBg: 'rgba(255, 255, 255, 0.95)',
+ textPrimary: '#262626',
+ textSecondary: '#8c8c8c'
+ };
+
+ const containerStyle = {
+ minHeight: '100vh',
+ background: 'linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%)',
+ padding: '24px'
+ };
+
+ const headerStyle = {
+ textAlign: 'center',
+ marginBottom: '32px'
+ };
+
+ const titleStyle = {
+ fontSize: '2.5rem',
+ fontWeight: '700',
+ background: theme.background,
+ WebkitBackgroundClip: 'text',
+ WebkitTextFillColor: 'transparent',
+ margin: '0 0 8px 0'
+ };
+
+ const subtitleStyle = {
+ fontSize: '1.1rem',
+ color: theme.textSecondary,
+ margin: '0'
+ };
+
+ const statCardStyle = {
+ borderRadius: '16px',
+ border: 'none',
+ boxShadow: '0 4px 16px rgba(24, 144, 255, 0.1)',
+ background: theme.cardBg,
+ backdropFilter: 'blur(10px)',
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ position: 'relative',
+ overflow: 'hidden',
+ cursor: 'pointer'
+ };
+
+ const statCardHoverStyle = {
+ transform: 'translateY(-4px)',
+ boxShadow: '0 8px 32px rgba(24, 144, 255, 0.2)'
+ };
+
+ const trendStyle = (trend) => ({
+ display: 'flex',
+ alignItems: 'center',
+ fontSize: '0.875rem',
+ fontWeight: '500',
+ color: trend > 0 ? theme.success : theme.error,
+ marginTop: '8px'
+ });
+
+ const systemOverviewStyle = {
+ marginTop: '32px'
+ };
+
+ const overviewCardStyle = {
+ borderRadius: '20px',
+ border: 'none',
+ boxShadow: '0 8px 32px rgba(24, 144, 255, 0.15)',
+ background: theme.cardBg,
+ backdropFilter: 'blur(10px)'
+ };
+
+ const welcomeSectionStyle = {
+ background: theme.background,
+ borderRadius: '12px',
+ padding: '20px',
+ textAlign: 'center',
+ color: 'white',
+ marginBottom: '24px'
+ };
+
+ const navigationGridStyle = {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))',
+ gap: '16px',
+ marginBottom: '24px'
+ };
+
+ const navButtonStyle = {
+ height: 'auto',
+ padding: '20px 16px',
+ borderRadius: '12px',
+ border: '2px solid rgba(24, 144, 255, 0.1)',
+ background: 'rgba(24, 144, 255, 0.02)',
+ transition: 'all 0.3s ease',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ gap: '8px'
+ };
+
+ const navButtonHoverStyle = {
+ borderColor: theme.primary,
+ background: 'rgba(24, 144, 255, 0.1)',
+ transform: 'translateY(-2px)'
+ };
+
+ const navIconStyle = {
+ fontSize: '2rem',
+ color: theme.primary
+ };
+
+ const navTextStyle = {
+ fontSize: '0.95rem',
+ fontWeight: '600',
+ color: theme.textPrimary
+ };
+
+ const systemInfoStyle = {
+ background: 'linear-gradient(135deg, #e8f4fd 0%, #f0f9ff 100%)',
+ borderRadius: '12px',
+ padding: '16px',
+ border: '1px solid rgba(24, 144, 255, 0.1)'
+ };
+
return (
-
-
仪表盘
-
-
-
- }
- loading={loading}
- />
+
+ {/* 页面头部 */}
+
+
+
+ IDC设备管理系统
+
+
实时监控 • 智能管理 • 高效运维
+
+
+ {/* 数据概览卡片 */}
+
+
+
+
+
+
+ 总设备数
+
+ }
+ value={stats.totalDevices}
+ prefix={}
+ valueStyle={{
+ fontSize: '2rem',
+ fontWeight: '700',
+ color: theme.textPrimary,
+ marginBottom: '8px'
+ }}
+ loading={loading}
+ />
+
+ {stats.deviceGrowth > 0 ?
:
}
+
{Math.abs(stats.deviceGrowth)}%
+
本月
+
+
-
-
- }
- loading={loading}
- />
+
+
+
+
+
+
+ 总机柜数
+
+ }
+ value={stats.totalRacks}
+ prefix={}
+ valueStyle={{
+ fontSize: '2rem',
+ fontWeight: '700',
+ color: theme.textPrimary,
+ marginBottom: '8px'
+ }}
+ loading={loading}
+ />
+
+
-
-
- }
- loading={loading}
- />
+
+
+
+
+
+
+ 总机房数
+
+ }
+ value={stats.totalRooms}
+ prefix={}
+ valueStyle={{
+ fontSize: '2rem',
+ fontWeight: '700',
+ color: theme.textPrimary,
+ marginBottom: '8px'
+ }}
+ loading={loading}
+ />
+
+
+ 全部在线
+
+
-
-
- }
- valueStyle={{ color: '#cf1322' }}
- loading={loading}
- />
+
+
+
+
+
+
+ 故障设备
+
+ }
+ value={stats.faultDevices}
+ prefix={}
+ valueStyle={{
+ fontSize: '2rem',
+ fontWeight: '700',
+ color: theme.error,
+ marginBottom: '8px'
+ }}
+ loading={loading}
+ />
+
+ {stats.faultTrend < 0 ?
:
}
+
{Math.abs(stats.faultTrend)}%
+
本周
+
+
-
- 欢迎使用IDC设备管理系统!
- 本系统用于管理IDC机房中的设备、机柜和机房信息,提供设备状态监控、资源分配等功能。
-
+
+ {/* 系统概览 */}
+
+
+
+ {/* 欢迎区域 */}
+
+
+ 欢迎使用IDC设备管理系统
+
+
+ 专业的机房设备管理解决方案
+
+
+
+ {/* 导航功能网格 */}
+
+
+
+
+
+
+
+
+
+
+ {/* 系统信息 */}
+
+
+
+
+ 系统版本: v1.0.0
+
+
+ 最后更新:{new Date().toLocaleDateString()}
+
+
+
}
+ size="small"
+ style={{ background: theme.primary, borderColor: theme.primary }}
+ >
+ 刷新数据
+
+
+
+
+
+
);
}
diff --git a/frontend/src/pages/DeviceManagement.jsx b/frontend/src/pages/DeviceManagement.jsx
index 6ac08ed..a9392dc 100644
--- a/frontend/src/pages/DeviceManagement.jsx
+++ b/frontend/src/pages/DeviceManagement.jsx
@@ -1,12 +1,72 @@
import React, { useState, useEffect } from 'react';
import { Table, Button, Modal, Form, Input, Select, DatePicker, message, Card, Space, InputNumber, Switch, Upload, Progress, Checkbox } from 'antd';
-import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined, DownloadOutlined, SettingOutlined } from '@ant-design/icons';
+import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined, DownloadOutlined, SettingOutlined, UndoOutlined, CloudServerOutlined, SwapOutlined, SafetyOutlined, DatabaseOutlined, AppstoreOutlined } from '@ant-design/icons';
import axios from 'axios';
import dayjs from 'dayjs';
const { Option } = Select;
const { RangePicker } = DatePicker;
+// 可调整列宽的表头组件
+const ResizeableTitle = (props) => {
+ const { onResize, width, ...restProps } = props;
+
+ if (!width) {
+ return | ;
+ }
+
+ const handleMouseDown = (e) => {
+ if (!onResize) return;
+
+
+ const startX = e.pageX;
+ const startWidth = width;
+
+ const handleMouseMove = (moveEvent) => {
+ const diff = moveEvent.pageX - startX;
+ const newWidth = Math.max(50, startWidth + diff); // 最小宽度50px
+ onResize(newWidth);
+ };
+
+ const handleMouseUp = () => {
+ document.removeEventListener('mousemove', handleMouseMove);
+ document.removeEventListener('mouseup', handleMouseUp);
+ };
+
+ document.addEventListener('mousemove', handleMouseMove);
+ document.addEventListener('mouseup', handleMouseUp);
+ };
+
+ return (
+
+ {restProps.children}
+
+ |
+ );
+};
+
function DeviceManagement() {
const [devices, setDevices] = useState([]);
const [racks, setRacks] = useState([]);
@@ -38,13 +98,18 @@ function DeviceManagement() {
const [isImporting, setIsImporting] = useState(false);
const [importResult, setImportResult] = useState(null);
const [selectedDevices, setSelectedDevices] = useState([]);
- const [selectAll, setSelectAll] = useState(false);
+ const [detailModalVisible, setDetailModalVisible] = useState(false);
+ const [selectedDevice, setSelectedDevice] = useState(null);
+
// 自定义字段状态
const [customFieldName, setCustomFieldName] = useState('');
const [customFieldValue, setCustomFieldValue] = useState('');
// 字段配置模态框
const [fieldConfigModalVisible, setFieldConfigModalVisible] = useState(false);
+
+ // 列宽状态
+ const [columnWidths, setColumnWidths] = useState({});
// 获取所有设备(支持搜索、筛选和分页)
const fetchDevices = async (page = 1, pageSize = 10, searchParams = {}) => {
@@ -341,6 +406,12 @@ function DeviceManagement() {
});
};
+ // 显示设备详情
+ const handleShowDetail = (device) => {
+ setSelectedDevice(device);
+ setDetailModalVisible(true);
+ };
+
// 导出设备数据
const handleExport = async () => {
try {
@@ -369,26 +440,7 @@ function DeviceManagement() {
}
};
- // 处理单个设备选择
- const handleDeviceSelect = (deviceId) => {
- setSelectedDevices(prev => {
- if (prev.includes(deviceId)) {
- return prev.filter(id => id !== deviceId);
- } else {
- return [...prev, deviceId];
- }
- });
- };
- // 处理全选/取消全选
- const handleSelectAll = (e) => {
- setSelectAll(e.target.checked);
- if (e.target.checked) {
- setSelectedDevices(devices.map(device => device.deviceId));
- } else {
- setSelectedDevices([]);
- }
- };
@@ -464,6 +516,36 @@ function DeviceManagement() {
other: '其他设备'
};
+ // 获取设备类型图标
+ const getDeviceTypeIcon = (type) => {
+ const iconMap = {
+ server: ,
+ switch: ,
+ router: ,
+ storage: ,
+ other:
+ };
+ return iconMap[type] || ;
+ };
+
+ // 处理列宽变化
+ const handleColumnResize = (key, width) => {
+ setColumnWidths(prev => ({ ...prev, [key]: width }));
+ };
+
+ // 重置列宽到默认值
+ const resetColumnWidths = () => {
+ setColumnWidths({});
+ message.success('列宽已重置为默认值');
+ };
+
+ // 处理表头单元格拖拽
+ const handleHeaderCellResize = (key) => (column) => ({
+ width: column.width,
+ onResize: (width) => handleColumnResize(key, width),
+ });
+
+
// 动态生成表格列配置
const columns = React.useMemo(() => {
const generatedColumns = [];
@@ -480,12 +562,16 @@ function DeviceManagement() {
title: '所在机房',
dataIndex: ['Rack', 'Room', 'name'],
key: 'roomName',
+ width: columnWidths.roomName || 120,
+ onHeaderCell: handleHeaderCellResize('roomName'),
});
// 添加机柜信息列
generatedColumns.push({
title: field.displayName,
dataIndex: ['Rack', 'name'],
key: field.fieldName,
+ width: columnWidths[field.fieldName] || 120,
+ onHeaderCell: handleHeaderCellResize(field.fieldName),
});
}
// 特殊处理设备类型
@@ -494,17 +580,15 @@ function DeviceManagement() {
title: field.displayName,
dataIndex: field.fieldName,
key: field.fieldName,
+ width: columnWidths[field.fieldName] || 100,
+ onHeaderCell: handleHeaderCellResize(field.fieldName),
render: (type) => {
- if (Array.isArray(type)) {
- return (
-
- {type.map(t => (
- {typeMap[t]}
- ))}
-
- );
- }
- return typeMap[type];
+ return (
+
+ {getDeviceTypeIcon(type)}
+ {typeMap[type]}
+
+ );
},
});
}
@@ -514,6 +598,8 @@ function DeviceManagement() {
title: field.displayName,
dataIndex: field.fieldName,
key: field.fieldName,
+ width: columnWidths[field.fieldName] || 100,
+ onHeaderCell: handleHeaderCellResize(field.fieldName),
render: (status) => {
if (Array.isArray(status)) {
return (
@@ -540,6 +626,8 @@ function DeviceManagement() {
title: field.displayName,
dataIndex: field.fieldName,
key: field.fieldName,
+ width: columnWidths[field.fieldName] || 120,
+ onHeaderCell: handleHeaderCellResize(field.fieldName),
render: (date) => {
if (!date) return '';
@@ -565,11 +653,46 @@ function DeviceManagement() {
}
// 普通字段
else {
- generatedColumns.push({
+ // 为不同类型的字段设置不同的默认宽度
+ let defaultWidth = 120;
+ if (field.fieldName === 'deviceId' || field.fieldName === 'name') {
+ defaultWidth = 150;
+ } else if (field.fieldName === 'description') {
+ defaultWidth = 200;
+ } else if (field.fieldType === 'number') {
+ defaultWidth = 80;
+ } else if (field.fieldType === 'textarea') {
+ defaultWidth = 180;
+ }
+
+ // 为设备名称和ID列添加点击查看详情功能
+ const columnConfig = {
title: field.displayName,
dataIndex: field.fieldName,
key: field.fieldName,
- });
+ width: columnWidths[field.fieldName] || defaultWidth,
+ onHeaderCell: handleHeaderCellResize(field.fieldName),
+ };
+
+ // 设备名称和ID列添加点击效果
+ if (field.fieldName === 'deviceId' || field.fieldName === 'name') {
+ columnConfig.render = (value, record) => (
+ handleShowDetail(record)}
+ style={{
+ color: '#1890ff',
+ textDecoration: 'none',
+ cursor: 'pointer'
+ }}
+ onMouseEnter={(e) => e.target.style.textDecoration = 'underline'}
+ onMouseLeave={(e) => e.target.style.textDecoration = 'none'}
+ >
+ {value || '-'}
+
+ );
+ }
+
+ generatedColumns.push(columnConfig);
}
});
@@ -577,6 +700,8 @@ function DeviceManagement() {
generatedColumns.push({
title: '操作',
key: 'action',
+ width: columnWidths.action || 120,
+ onHeaderCell: handleHeaderCellResize('action'),
render: (_, record) => (
} onClick={() => showModal(record)} size="small">
@@ -590,7 +715,7 @@ function DeviceManagement() {
});
return generatedColumns;
- }, [deviceFields]);
+ }, [deviceFields, columnWidths]);
// 保存字段配置
const handleSaveFieldConfig = async (values) => {
@@ -629,6 +754,13 @@ function DeviceManagement() {
} onClick={handleExport}>导出设备
} onClick={() => setImportModalVisible(true)}>导入设备
} onClick={() => setFieldConfigModalVisible(true)}>字段配置
+ }
+ onClick={resetColumnWidths}
+ title="重置列宽"
+ >
+ 重置列宽
+
@@ -744,7 +867,7 @@ function DeviceManagement() {
open={modalVisible}
onCancel={handleCancel}
footer={null}
- width={800}
+ width={700}
>
)}
+
+ {/* 设备详情模态框 */}
+ {
+ setDetailModalVisible(false);
+ setSelectedDevice(null);
+ }}
+ footer={[
+ ,
+
+ ]}
+ width={800}
+ destroyOnClose
+ >
+ {selectedDevice && (
+
+ {/* 基本信息区域 */}
+
+
+
+
+ {selectedDevice.deviceId || '-'}
+
+
+
+ {selectedDevice.name || '-'}
+
+
+
+
+ {selectedDevice.type ? (
+
+ {getDeviceTypeIcon(selectedDevice.type)}
+ {typeMap[selectedDevice.type] || selectedDevice.type}
+
+ ) : '-'}
+
+
+
+
+ {selectedDevice.model || '-'}
+
+
+
+ {selectedDevice.serialNumber || '-'}
+
+
+
+ {selectedDevice.ipAddress || '-'}
+
+
+
+ {selectedDevice.Rack?.Room?.name || '-'}
+
+
+
+ {selectedDevice.Rack?.name || '-'}
+
+
+
+ {selectedDevice.position || '-'}
+
+
+
+ {selectedDevice.height || '-'}
+
+
+
+ {selectedDevice.power || '-'}
+
+
+
+
+ {selectedDevice.status ? statusMap[selectedDevice.status]?.text || selectedDevice.status : '-'}
+
+
+
+
+
+ {selectedDevice.purchaseDate ? new Date(selectedDevice.purchaseDate).toLocaleDateString('zh-CN') : '-'}
+
+
+
+
+
+ {selectedDevice.warrantyExpiry ? new Date(selectedDevice.warrantyExpiry).toLocaleDateString('zh-CN') : '-'}
+
+
+
+ {selectedDevice.description && (
+
+
+
+ {selectedDevice.description}
+
+
+ )}
+
+
+ )}
+
);
}