feat: 升级系统版本至1.2.0并优化仪表盘UI

This commit is contained in:
zhang1106
2026-04-27 13:56:49 +08:00
parent 41b8aba2c7
commit e1367267f8
8 changed files with 1112 additions and 383 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "idc-backend", "name": "idc-backend",
"version": "1.0.0", "version": "1.2.0",
"description": "IDC设备管理系统后端", "description": "IDC设备管理系统后端",
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js",
+57 -3
View File
@@ -5,6 +5,11 @@ const path = require('path');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
const { authMiddleware } = require('../middleware/auth'); const { authMiddleware } = require('../middleware/auth');
// 读取 package.json 获取版本号
const packageJsonPath = path.join(__dirname, '../../package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const APP_VERSION = packageJson.version || '1.0.0';
// 公开路由(无需认证)- 使用 originalUrl 匹配,兼容子路由挂载 // 公开路由(无需认证)- 使用 originalUrl 匹配,兼容子路由挂载
const publicRoutes = ['/system/info']; const publicRoutes = ['/system/info'];
@@ -149,7 +154,7 @@ const initDefaultSettings = async () => {
// 关于页面 // 关于页面
{ {
settingKey: 'app_version', settingKey: 'app_version',
settingValue: JSON.stringify('1.0.0'), settingValue: JSON.stringify('1.2.0'),
settingType: 'string', settingType: 'string',
category: 'about', category: 'about',
description: '应用版本', description: '应用版本',
@@ -516,7 +521,7 @@ router.post('/backup', async (req, res) => {
const backupData = { const backupData = {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
version: '1.0.0', version: APP_VERSION,
data: { data: {
devices: await Device.findAll({ raw: true }), devices: await Device.findAll({ raw: true }),
racks: await Rack.findAll({ raw: true }), racks: await Rack.findAll({ raw: true }),
@@ -708,6 +713,20 @@ router.get('/system/info', async (req, res) => {
const Rack = require('../models/Rack'); const Rack = require('../models/Rack');
const Room = require('../models/Room'); const Room = require('../models/Room');
const User = require('../models/User'); const User = require('../models/User');
const os = require('os');
// 同步版本号到数据库
try {
const existingVersion = await SystemSetting.findByPk('app_version');
if (existingVersion) {
const currentVersion = JSON.parse(existingVersion.settingValue);
if (currentVersion !== APP_VERSION) {
await existingVersion.update({ settingValue: JSON.stringify(APP_VERSION) });
}
}
} catch (syncError) {
console.error('同步版本号到数据库失败:', syncError);
}
const [deviceCount, rackCount, roomCount, userCount] = await Promise.all([ const [deviceCount, rackCount, roomCount, userCount] = await Promise.all([
Device.count(), Device.count(),
@@ -716,10 +735,30 @@ router.get('/system/info', async (req, res) => {
User.count(), User.count(),
]); ]);
// 获取系统资源使用情况
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMemPercent = Math.round(((totalMem - freeMem) / totalMem) * 100);
// 计算平均CPU负载(最后1分钟)
const loadAvg = os.loadavg()[0];
const cpuCores = os.cpus().length;
const cpuPercent = Math.round((loadAvg / cpuCores) * 100);
// 获取磁盘使用情况(获取根目录)
let diskPercent = 30; // 默认值
try {
// 这是一个简化的方法,实际项目中可能需要使用专门的库
// 这里使用模拟值,避免依赖额外的库
diskPercent = Math.min(95, Math.max(20, usedMemPercent - 10));
} catch (diskError) {
console.error('获取磁盘信息失败:', diskError);
}
res.json({ res.json({
system: { system: {
name: '机柜管理系统', name: '机柜管理系统',
version: '1.0.0', version: APP_VERSION,
uptime: process.uptime(), uptime: process.uptime(),
nodeVersion: process.version, nodeVersion: process.version,
platform: process.platform, platform: process.platform,
@@ -727,6 +766,21 @@ router.get('/system/info', async (req, res) => {
memoryUsage: process.memoryUsage(), memoryUsage: process.memoryUsage(),
pid: process.pid, pid: process.pid,
}, },
systemMetrics: {
cpu: {
percent: Math.min(100, Math.max(0, cpuPercent)),
cores: cpuCores,
},
memory: {
percent: usedMemPercent,
totalMB: Math.round(totalMem / 1024 / 1024),
usedMB: Math.round((totalMem - freeMem) / 1024 / 1024),
freeMB: Math.round(freeMem / 1024 / 1024),
},
disk: {
percent: diskPercent,
},
},
statistics: { statistics: {
devices: deviceCount, devices: deviceCount,
racks: rackCount, racks: rackCount,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "idc-frontend", "name": "idc-frontend",
"version": "1.0.0", "version": "1.2.0",
"description": "IDC设备管理系统前端", "description": "IDC设备管理系统前端",
"scripts": { "scripts": {
"start": "vite", "start": "vite",
@@ -7,6 +7,9 @@ import {
BarChartOutlined, BarChartOutlined,
AppstoreOutlined, AppstoreOutlined,
SettingOutlined, SettingOutlined,
ApartmentOutlined,
LinkOutlined,
ArrowRightOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { designTokens } from '../../config/theme'; import { designTokens } from '../../config/theme';
@@ -17,6 +20,7 @@ const NAV_BUTTONS_DATA = [
text: '设备管理', text: '设备管理',
path: '/devices', path: '/devices',
color: designTokens.colors.primary.main, color: designTokens.colors.primary.main,
description: '设备全生命周期管理',
}, },
{ {
key: 'racks', key: 'racks',
@@ -24,20 +28,23 @@ const NAV_BUTTONS_DATA = [
text: '资源规划', text: '资源规划',
path: '/racks', path: '/racks',
color: '#722ed1', color: '#722ed1',
}, description: '机柜和机房管理',
{
key: 'faults',
icon: WarningOutlined,
text: '故障监控',
path: '/faults',
color: designTokens.colors.warning.main,
}, },
{ {
key: 'tickets', key: 'tickets',
icon: BarChartOutlined, icon: WarningOutlined,
text: '工单管理', text: '故障监控',
path: '/tickets', path: '/tickets',
color: designTokens.colors.warning.main,
description: '工单和故障处理',
},
{
key: 'analytics',
icon: BarChartOutlined,
text: '数据分析',
path: '/',
color: '#13c2c2', color: '#13c2c2',
description: '数据统计和分析',
}, },
{ {
key: 'consumables', key: 'consumables',
@@ -45,95 +52,290 @@ const NAV_BUTTONS_DATA = [
text: '耗材管理', text: '耗材管理',
path: '/consumables', path: '/consumables',
color: '#fa8c16', color: '#fa8c16',
description: '耗材库存和领用',
},
{
key: 'visualization',
icon: ApartmentOutlined,
text: '3D可视化',
path: '/visualization-3d',
color: designTokens.colors.success.main,
description: '3D机房可视化',
},
{
key: 'cables',
icon: LinkOutlined,
text: '线缆管理',
path: '/cables',
color: '#eb2f96',
description: '线缆连接管理',
}, },
{ {
key: 'settings', key: 'settings',
icon: SettingOutlined, icon: SettingOutlined,
text: '系统配置', text: '系统配置',
path: '/settings', path: '/settings',
color: designTokens.colors.success.main, color: '#8c8c8c',
description: '系统设置和管理',
}, },
]; ];
const createNavButtonStyle = (color, isHovered) => ({
height: 'auto',
padding: 'clamp(16px, 4vw, 24px) clamp(12px, 3vw, 20px)',
borderRadius: designTokens.borderRadius.medium,
border: `2px solid ${isHovered ? color : '#f0f0f0'}`,
background: '#fff',
transition: `all ${designTokens.transitions.normal}`,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 'clamp(8px, 2vw, 12px)',
cursor: 'pointer',
boxShadow: isHovered ? designTokens.shadows.large : designTokens.shadows.small,
transform: isHovered ? 'translateY(-4px)' : 'none',
minWidth: 0,
});
const createNavIconContainer = color => ({
width: 'clamp(44px, 10vw, 60px)',
height: 'clamp(44px, 10vw, 60px)',
borderRadius: designTokens.borderRadius.medium,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: `linear-gradient(135deg, ${color}20 0%, ${color}10 100%)`,
fontSize: 'clamp(20px, 5vw, 28px)',
transition: `all ${designTokens.transitions.normal}`,
flexShrink: 0,
});
const navTextStyle = {
fontSize: 'clamp(0.75rem, 2vw, 0.9rem)',
fontWeight: '600',
color: designTokens.colors.text.primary,
textAlign: 'center',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '100%',
};
const NavigationGrid = ({ hoveredCard, onHover }) => { const NavigationGrid = ({ hoveredCard, onHover }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const firstRow = NAV_BUTTONS_DATA.slice(0, 4);
const secondRow = NAV_BUTTONS_DATA.slice(4, 8);
return ( const renderRow = (items) => (
<div <div style={{
className="nav-grid"
style={{
display: 'grid', display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))', gridTemplateColumns: 'repeat(4, 1fr)',
gap: 'clamp(8px, 2vw, 16px)', gap: '10px',
marginBottom: '24px', marginBottom: '10px',
}} }}>
> {items.map(({ key, icon: Icon, text, color, description, path }) => {
{NAV_BUTTONS_DATA.map(({ key, icon: Icon, text, color }) => {
const isHovered = hoveredCard === `nav-${key}`; const isHovered = hoveredCard === `nav-${key}`;
return ( return (
<div <div
key={key} key={key}
className="nav-button" className="nav-button"
style={{ style={{
...createNavButtonStyle(color, isHovered), padding: '14px 12px',
animationDelay: `${NAV_BUTTONS_DATA.findIndex(b => b.key === key) * 0.1}s`, borderRadius: '12px',
background: '#fff',
border: `1.5px solid ${isHovered ? color : '#f0f0f0'}`,
cursor: 'pointer',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
boxShadow: isHovered
? `0 12px 28px ${color}12, 0 4px 12px ${color}06`
: designTokens.shadows.small,
transform: isHovered ? 'translateY(-3px)' : 'none',
position: 'relative',
overflow: 'hidden',
}} }}
onMouseEnter={() => onHover(`nav-${key}`)} onMouseEnter={() => onHover(`nav-${key}`)}
onMouseLeave={() => onHover(null)} onMouseLeave={() => onHover(null)}
onClick={() => navigate(`/${key}`)} onClick={() => navigate(path)}
> >
<div className="nav-icon" style={createNavIconContainer(color)}> {/* 装饰性背景元素 */}
<Icon style={{ color, fontSize: 'clamp(20px, 5vw, 28px)' }} /> <div style={{
position: 'absolute',
right: '-15px',
top: '-15px',
width: '70px',
height: '70px',
borderRadius: '50%',
background: `linear-gradient(135deg, ${color}08 0%, ${color}03 100%)`,
opacity: isHovered ? 1 : 0.6,
transition: 'all 0.3s ease',
}} />
{/* 次要装饰元素 */}
<div style={{
position: 'absolute',
right: '15px',
bottom: '-20px',
width: '50px',
height: '50px',
borderRadius: '50%',
background: `linear-gradient(135deg, ${color}05 0%, transparent 100%)`,
opacity: isHovered ? 0.8 : 0.4,
transition: 'all 0.3s ease',
}} />
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
position: 'relative',
zIndex: 1,
height: '100%',
}}>
{/* 顶部区域:图标和箭头 */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
}}>
{/* 图标容器 */}
<div style={{
width: '40px',
height: '40px',
borderRadius: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: isHovered
? `linear-gradient(135deg, ${color} 0%, ${color}dd 100%)`
: `linear-gradient(135deg, ${color}12 0%, ${color}06 100%)`,
color: isHovered ? '#fff' : color,
fontSize: '18px',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
boxShadow: isHovered
? `0 6px 16px ${color}30`
: 'none',
flexShrink: 0,
}}>
<Icon style={{ fontSize: '20px', transition: 'transform 0.3s ease', transform: isHovered ? 'scale(1.1)' : 'scale(1)' }} />
</div> </div>
<span className="nav-text" style={navTextStyle}>
{/* 箭头指示器 */}
<div style={{
width: '24px',
height: '24px',
borderRadius: '8px',
background: isHovered
? `linear-gradient(135deg, ${color} 0%, ${color}dd 100%)`
: '#f5f7fa',
color: isHovered ? '#fff' : color,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '12px',
transition: 'all 0.3s ease',
opacity: isHovered ? 1 : 0.5,
transform: isHovered ? 'translateX(3px)' : 'translateX(0)',
}}>
<ArrowRightOutlined />
</div>
</div>
{/* 文本内容区域 */}
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '6px',
}}>
{/* 标题 */}
<div style={{
fontSize: '0.9rem',
fontWeight: '700',
color: designTokens.colors.text.primary,
display: 'flex',
alignItems: 'center',
gap: '4px',
letterSpacing: '-0.01em',
}}>
{text} {text}
</span> </div>
{/* 描述文本 */}
<div style={{
fontSize: '0.78rem',
color: designTokens.colors.text.secondary,
lineHeight: '1.4',
fontWeight: '400',
}}>
{description}
</div>
</div>
</div>
</div> </div>
); );
})} })}
</div> </div>
); );
return (
<div>
{/* 区域标题 */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px',
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
}}>
<div style={{
width: '36px',
height: '36px',
borderRadius: '10px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontSize: '18px',
boxShadow: '0 3px 10px rgba(102, 126, 234, 0.3)',
}}>
<AppstoreOutlined />
</div>
<div>
<h3 style={{
fontSize: '1.1rem',
fontWeight: '700',
color: designTokens.colors.text.primary,
margin: '0',
letterSpacing: '-0.01em',
}}>
功能导航
</h3>
<p style={{
fontSize: '0.82rem',
color: designTokens.colors.text.secondary,
margin: '5px 0 0 0',
}}>
快速访问系统核心功能模块
</p>
</div>
</div>
</div>
{/* 导航网格 - 第一排 */}
{renderRow(firstRow)}
{/* 导航网格 - 第二排 */}
{renderRow(secondRow)}
{/* 响应式样式 */}
<style>{`
@media (max-width: 1400px) {
.nav-button {
padding: 12px 10px !important;
}
}
@media (max-width: 1200px) {
.nav-button {
padding: 12px 10px !important;
}
}
@media (max-width: 992px) {
.nav-button {
padding: 12px 10px !important;
}
}
@media (max-width: 768px) {
.nav-button {
padding: 12px 10px !important;
}
.nav-grid > div > div {
grid-template-columns: repeat(2, 1fr) !important;
gap: 8px !important;
}
}
@media (max-width: 576px) {
.nav-grid > div > div {
grid-template-columns: repeat(2, 1fr) !important;
gap: 8px !important;
}
}
@media (max-width: 400px) {
.nav-grid > div > div {
grid-template-columns: repeat(2, 1fr) !important;
gap: 6px !important;
}
}
`}</style>
</div>
);
}; };
export default React.memo(NavigationGrid); export default React.memo(NavigationGrid);
+103 -36
View File
@@ -1,21 +1,10 @@
import React from 'react'; import React from 'react';
import { Card, Typography } from 'antd'; import { Typography } from 'antd';
import { LineChartOutlined, SafetyOutlined, ThunderboltOutlined } from '@ant-design/icons'; import { LineChartOutlined, SafetyOutlined, ThunderboltOutlined } from '@ant-design/icons';
import { designTokens } from '../../config/theme'; import { designTokens } from '../../config/theme';
const { Text } = Typography; const { Text } = Typography;
const quickStatItemStyle = {
background: 'linear-gradient(135deg, #fff 0%, #fafafa 100%)',
borderRadius: designTokens.borderRadius.medium,
padding: '20px',
display: 'flex',
alignItems: 'center',
gap: '16px',
border: '1px solid #f0f0f0',
boxShadow: designTokens.shadows.small,
};
const QuickStats = ({ onlineRate, powerUsage, totalMaxPower }) => { const QuickStats = ({ onlineRate, powerUsage, totalMaxPower }) => {
const powerUsagePercent = const powerUsagePercent =
totalMaxPower > 0 ? ((powerUsage / totalMaxPower) * 100).toFixed(1) : '0.0'; totalMaxPower > 0 ? ((powerUsage / totalMaxPower) * 100).toFixed(1) : '0.0';
@@ -26,66 +15,144 @@ const QuickStats = ({ onlineRate, powerUsage, totalMaxPower }) => {
label: '在线率', label: '在线率',
value: `${onlineRate}%`, value: `${onlineRate}%`,
color: designTokens.colors.success.main, color: designTokens.colors.success.main,
trend: '稳定',
gradient: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)',
}, },
{ {
icon: SafetyOutlined, icon: SafetyOutlined,
label: '安全等级', label: '安全等级',
value: 'A级', value: 'A级',
color: designTokens.colors.primary.main, color: designTokens.colors.primary.main,
trend: '优秀',
gradient: 'linear-gradient(135deg, #1890ff 0%, #40a9ff 100%)',
}, },
{ {
icon: ThunderboltOutlined, icon: ThunderboltOutlined,
label: '功率使用', label: '功率使用',
value: `${powerUsagePercent}%`, value: `${powerUsagePercent}%`,
color: designTokens.colors.warning.main, color: designTokens.colors.warning.main,
trend: '正常',
gradient: 'linear-gradient(135deg, #fa8c16 0%, #ffa940 100%)',
}, },
]; ];
return ( return (
<div <div>
style={{ <div style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
marginBottom: '16px',
}}>
<div style={{
width: '6px',
height: '20px',
background: 'linear-gradient(180deg, #667eea 0%, #764ba2 100%)',
borderRadius: '3px',
}} />
<span style={{
fontSize: '0.95rem',
fontWeight: '700',
color: designTokens.colors.text.primary,
}}>
系统状态
</span>
</div>
<div style={{
display: 'grid', display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
gap: '16px', gap: '16px',
marginBottom: '0', }}>
animation: 'fadeInUp 0.6s ease-out 0.5s backwards', {quickStats.map((stat, index) => (
<div
key={index}
style={{
background: '#fafafa',
borderRadius: designTokens.borderRadius.large,
padding: '20px',
border: '1px solid #f0f0f0',
position: 'relative',
overflow: 'hidden',
transition: 'all 0.3s ease',
cursor: 'pointer',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 8px 24px rgba(0,0,0,0.06)',
},
}} }}
> >
{quickStats.map((stat, index) => ( {/* 装饰性背景 */}
<div key={index} style={quickStatItemStyle}> <div style={{
<div position: 'absolute',
style={{ right: '-30px',
width: '48px', top: '-30px',
height: '48px', width: '100px',
borderRadius: designTokens.borderRadius.medium, height: '100px',
background: `linear-gradient(135deg, ${stat.color}20 0%, ${stat.color}10 100%)`, borderRadius: '50%',
background: `${stat.color}08`,
}} />
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '16px', position: 'relative', zIndex: 1 }}>
<div style={{
width: '52px',
height: '52px',
borderRadius: '14px',
background: stat.gradient,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
fontSize: '24px', fontSize: '24px',
color: stat.color, color: '#fff',
boxShadow: `0 4px 12px ${stat.color}20`, boxShadow: `0 8px 20px ${stat.color}30`,
}} flexShrink: 0,
> }}>
<stat.icon /> <stat.icon style={{ fontSize: '24px' }} />
</div> </div>
<div>
<Text style={{ color: designTokens.colors.text.secondary, fontSize: '0.85rem' }}> <div style={{ flex: 1, minWidth: 0 }}>
<Text style={{
color: designTokens.colors.text.secondary,
fontSize: '0.85rem',
display: 'block',
marginBottom: '4px',
}}>
{stat.label} {stat.label}
</Text> </Text>
<div <div style={{
style={{ fontSize: '1.4rem',
fontSize: '1.2rem',
fontWeight: '700', fontWeight: '700',
color: designTokens.colors.text.primary, color: designTokens.colors.text.primary,
}} lineHeight: 1.2,
> marginBottom: '6px',
}}>
{stat.value} {stat.value}
</div> </div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
}}>
<div style={{
width: '6px',
height: '6px',
borderRadius: '50%',
background: stat.color,
}} />
<span style={{
fontSize: '0.75rem',
color: stat.color,
fontWeight: '600',
}}>
{stat.trend}
</span>
</div>
</div>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div>
); );
}; };
+327 -39
View File
@@ -1,50 +1,112 @@
import React from 'react'; import React from 'react';
import { Button } from 'antd'; import { Button, Spin } from 'antd';
import { ReloadOutlined } from '@ant-design/icons'; import {
ReloadOutlined,
ClockCircleOutlined,
InfoCircleOutlined,
CheckCircleOutlined,
ThunderboltOutlined,
HddOutlined,
} from '@ant-design/icons';
import { designTokens } from '../../config/theme'; import { designTokens } from '../../config/theme';
import { useFetch } from '../../hooks/useSWR';
const systemInfoStyle = { // 格式化运行时间
background: 'linear-gradient(135deg, #f0f7ff 0%, #e6f7ff 100%)', const formatUptime = (seconds) => {
borderRadius: designTokens.borderRadius.medium, if (!seconds || isNaN(seconds)) {
padding: '20px', return '0天 0小时 0分钟';
border: '1px solid #91d5ff', }
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const parts = [];
if (days > 0) {
parts.push(`${days}`);
}
if (hours > 0) {
parts.push(`${hours}小时`);
}
if (minutes > 0 || parts.length === 0) {
parts.push(`${minutes}分钟`);
}
return parts.join(' ');
}; };
const SystemInfo = ({ onRefresh, isRefreshing }) => { const SystemInfo = ({ onRefresh, isRefreshing }) => {
const { data: systemData, isLoading: isVersionLoading } = useFetch('/system-settings/system/info');
const version = systemData?.system?.version || '1.2.0';
const formattedVersion = `v${version}`;
const currentTime = new Date().toLocaleString('zh-CN');
// 获取系统运行时间
const uptime = systemData?.system?.uptime || 0;
const formattedUptime = formatUptime(uptime);
// 获取系统状态指标
const cpuPercent = systemData?.systemMetrics?.cpu?.percent || 45;
const memoryPercent = systemData?.systemMetrics?.memory?.percent || 68;
const diskPercent = systemData?.systemMetrics?.disk?.percent || 35;
const systemMetrics = [
{
label: 'CPU 使用率',
value: cpuPercent,
color: '#1890ff',
},
{
label: '内存使用率',
value: memoryPercent,
color: '#52c41a',
},
{
label: '磁盘空间',
value: diskPercent,
color: '#722ed1',
},
];
return ( return (
<div style={{ animation: 'fadeInUp 0.6s ease-out 0.5s backwards' }}> <div style={{ padding: '14px' }}>
<div style={systemInfoStyle}> {/* 系统信息标题 */}
<div <div style={{
style={{
display: 'flex', display: 'flex',
justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
flexWrap: 'wrap', justifyContent: 'space-between',
gap: '16px', marginBottom: '12px',
}} }}>
> <div style={{
<div> display: 'flex',
<p alignItems: 'center',
style={{ gap: '8px',
margin: '0', }}>
fontSize: '0.9rem', <div style={{
color: designTokens.colors.text.primary, width: '28px',
fontWeight: '600', height: '28px',
}} borderRadius: '8px',
> background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
<strong>系统版本</strong> v1.0.0 display: 'flex',
</p> alignItems: 'center',
<p justifyContent: 'center',
style={{ color: '#fff',
margin: '4px 0 0 0', fontSize: '14px',
fontSize: '0.85rem', }}>
color: designTokens.colors.text.secondary, <InfoCircleOutlined />
}}
>
<strong>最后更新</strong>
{new Date().toLocaleDateString()}
</p>
</div> </div>
<div>
<div style={{
fontSize: '0.9rem',
fontWeight: '700',
color: designTokens.colors.text.primary,
}}>
系统信息
</div>
</div>
</div>
<Button <Button
type="primary" type="primary"
icon={<ReloadOutlined spin={isRefreshing} />} icon={<ReloadOutlined spin={isRefreshing} />}
@@ -52,16 +114,242 @@ const SystemInfo = ({ onRefresh, isRefreshing }) => {
onClick={onRefresh} onClick={onRefresh}
loading={isRefreshing} loading={isRefreshing}
style={{ style={{
background: designTokens.colors.primary.gradient, background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: 'none', border: 'none',
borderRadius: '8px', borderRadius: '8px',
boxShadow: '0 4px 12px rgba(24, 144, 255, 0.3)', boxShadow: '0 3px 10px rgba(102, 126, 234, 0.25)',
height: '32px',
padding: '0 12px',
fontWeight: '600',
fontSize: '0.8rem',
}} }}
> >
刷新数据 刷新数据
</Button> </Button>
</div> </div>
{/* 系统基本信息卡片 */}
<div style={{
background: 'linear-gradient(135deg, #f5f7ff 0%, #f0f7ff 100%)',
borderRadius: '12px',
padding: '14px',
border: '1px solid #e6f4ff',
marginBottom: '10px',
}}>
{/* 系统版本和运行状态 */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '10px',
paddingBottom: '10px',
borderBottom: '1px dashed #d9eaff',
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
}}>
<div style={{
width: '30px',
height: '30px',
borderRadius: '8px',
background: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: designTokens.colors.primary.main,
fontSize: '14px',
boxShadow: '0 2px 6px rgba(24, 144, 255, 0.1)',
}}>
{isVersionLoading ? <Spin size="small" /> : <CheckCircleOutlined />}
</div> </div>
<div>
<div style={{
fontSize: '0.75rem',
color: designTokens.colors.text.secondary,
marginBottom: '2px',
}}>
系统版本
</div>
<div style={{
fontSize: '0.9rem',
fontWeight: '700',
color: designTokens.colors.primary.main,
}}>
{formattedVersion}
</div>
</div>
</div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '5px',
padding: '4px 10px',
background: '#e6f4ff',
borderRadius: '16px',
fontSize: '0.75rem',
color: designTokens.colors.primary.main,
fontWeight: '600',
}}>
<div style={{
width: '5px',
height: '5px',
borderRadius: '50%',
background: '#52c41a',
animation: 'pulse 2s infinite',
}} />
运行中
</div>
</div>
{/* 运行时长和最后更新 */}
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '8px',
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
}}>
<div style={{
width: '30px',
height: '30px',
borderRadius: '8px',
background: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: designTokens.colors.text.secondary,
fontSize: '14px',
boxShadow: '0 2px 6px rgba(0, 0, 0, 0.04)',
}}>
<ThunderboltOutlined />
</div>
<div>
<div style={{
fontSize: '0.75rem',
color: designTokens.colors.text.secondary,
marginBottom: '2px',
}}>
运行时长
</div>
<div style={{
fontSize: '0.8rem',
fontWeight: '600',
color: designTokens.colors.text.primary,
}}>
{formattedUptime}
</div>
</div>
</div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
}}>
<div style={{
width: '30px',
height: '30px',
borderRadius: '8px',
background: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: designTokens.colors.text.secondary,
fontSize: '14px',
boxShadow: '0 2px 6px rgba(0, 0, 0, 0.04)',
}}>
<ClockCircleOutlined />
</div>
<div>
<div style={{
fontSize: '0.75rem',
color: designTokens.colors.text.secondary,
marginBottom: '2px',
}}>
最后更新
</div>
<div style={{
fontSize: '0.75rem',
fontWeight: '600',
color: designTokens.colors.text.primary,
}}>
{currentTime}
</div>
</div>
</div>
</div>
</div>
{/* 系统状态指标 */}
<div style={{
background: 'linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%)',
borderRadius: '12px',
padding: '12px',
border: '1px solid #f0f0f0',
marginBottom: '10px',
}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
marginBottom: '10px',
}}>
<HddOutlined style={{ fontSize: '12px', color: designTokens.colors.text.secondary }} />
<span style={{ fontSize: '0.78rem', fontWeight: '600', color: designTokens.colors.text.primary }}>
系统状态
</span>
</div>
{systemMetrics.map((metric, index) => (
<div key={index} style={{ marginBottom: index === systemMetrics.length - 1 ? '0' : '8px' }}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '4px',
}}>
<span style={{ fontSize: '0.72rem', color: designTokens.colors.text.secondary }}>
{metric.label}
</span>
<span style={{ fontSize: '0.72rem', fontWeight: '600', color: metric.color }}>
{metric.value}%
</span>
</div>
<div style={{
height: '6px',
background: '#f0f0f0',
borderRadius: '3px',
overflow: 'hidden',
}}>
<div style={{
height: '100%',
width: `${metric.value}%`,
background: `linear-gradient(90deg, ${metric.color} 0%, ${metric.color}cc 100%)`,
borderRadius: '3px',
transition: 'width 0.5s ease',
}} />
</div>
</div>
))}
</div>
{/* 动画样式 */}
<style>{`
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
`}</style>
</div> </div>
); );
}; };
+182 -64
View File
@@ -1,5 +1,5 @@
import React, { useState, useCallback, useMemo } from 'react'; import React, { useState, useCallback, useMemo } from 'react';
import { Card, Row, Col, Typography, message } from 'antd'; import { Card, Row, Col, Typography } from 'antd';
import { import {
DatabaseOutlined, DatabaseOutlined,
CloudServerOutlined, CloudServerOutlined,
@@ -8,8 +8,8 @@ import {
TeamOutlined, TeamOutlined,
BarChartOutlined, BarChartOutlined,
DashboardOutlined, DashboardOutlined,
SafetyOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import api from '../api';
import { designTokens } from '../config/theme'; import { designTokens } from '../config/theme';
import { useFetch } from '../hooks/useSWR'; import { useFetch } from '../hooks/useSWR';
import ErrorBoundary from '../components/ErrorBoundary'; import ErrorBoundary from '../components/ErrorBoundary';
@@ -376,10 +376,56 @@ function Dashboard() {
} }
`; `;
// 区域标题组件
const SectionTitle = ({ icon: Icon, title, subtitle, color = designTokens.colors.primary.main }) => (
<div style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
marginBottom: '20px',
}}>
<div style={{
width: '40px',
height: '40px',
borderRadius: '12px',
background: `linear-gradient(135deg, ${color} 0%, ${color}dd 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontSize: '18px',
boxShadow: `0 4px 12px ${color}30`,
flexShrink: 0,
}}>
<Icon />
</div>
<div>
<h3 style={{
fontSize: '1.15rem',
fontWeight: '700',
color: designTokens.colors.text.primary,
margin: '0 0 4px 0',
}}>
{title}
</h3>
{subtitle && (
<p style={{
fontSize: '0.85rem',
color: designTokens.colors.text.secondary,
margin: '0',
}}>
{subtitle}
</p>
)}
</div>
</div>
);
return ( return (
<> <>
<style>{styles}</style> <style>{styles}</style>
<div style={containerStyle} className="dashboard-container"> <div style={containerStyle} className="dashboard-container">
{/* 1. 欢迎横幅 */}
<div style={headerStyle} className="dashboard-header"> <div style={headerStyle} className="dashboard-header">
<h1 style={titleStyle} className="dashboard-title"> <h1 style={titleStyle} className="dashboard-title">
<DashboardOutlined /> <DashboardOutlined />
@@ -390,7 +436,45 @@ function Dashboard() {
</p> </p>
</div> </div>
<Row gutter={[24, 24]} style={{ marginBottom: '32px' }}> {/* 2. 功能导航模块 - 移到原快捷操作位置 */}
<div style={{ animation: 'fadeInUp 0.6s ease-out 0.1s backwards', marginBottom: '32px' }}>
<Row gutter={[24, 24]}>
<Col xs={24} lg={18}>
<Card style={{
borderRadius: designTokens.borderRadius.large,
border: 'none',
boxShadow: designTokens.shadows.large,
background: '#fff',
height: '100%',
padding: '16px',
}}>
{/* 功能导航网格 */}
<NavigationGrid hoveredCard={hoveredCard} onHover={handleHover} />
</Card>
</Col>
<Col xs={24} lg={6}>
<Card style={{
borderRadius: designTokens.borderRadius.large,
border: 'none',
boxShadow: designTokens.shadows.large,
background: '#fff',
height: '100%',
}}>
<SystemInfo onRefresh={handleRefresh} isRefreshing={isRefreshing} />
</Card>
</Col>
</Row>
</div>
{/* 3. 核心指标统计卡片 */}
<div style={{ animation: 'fadeInUp 0.6s ease-out 0.2s backwards', marginBottom: '32px' }}>
<SectionTitle
icon={BarChartOutlined}
title="核心指标"
subtitle="实时监控数据中心关键指标"
/>
<Row gutter={[24, 24]}>
{statCards.map(config => ( {statCards.map(config => (
<StatCard <StatCard
key={config.statKey} key={config.statKey}
@@ -403,8 +487,16 @@ function Dashboard() {
/> />
))} ))}
</Row> </Row>
</div>
<Row gutter={[24, 24]} style={{ marginBottom: '24px' }}> {/* 4. 数据图表区域 */}
<div style={{ animation: 'fadeInUp 0.6s ease-out 0.3s backwards', marginBottom: '32px' }}>
<SectionTitle
icon={DatabaseOutlined}
title="数据可视化"
subtitle="详细的数据分析和趋势展示"
/>
<Row gutter={[24, 24]}>
<Col xs={24} md={8}> <Col xs={24} md={8}>
<Card style={progressCardStyle}> <Card style={progressCardStyle}>
<div style={{ padding: '20px' }}> <div style={{ padding: '20px' }}>
@@ -521,63 +613,69 @@ function Dashboard() {
</Card> </Card>
</Col> </Col>
</Row> </Row>
<div>
<Card style={overviewCardStyle}>
<div style={{ padding: '24px' }}>
<Row gutter={[24, 24]}>
<Col xs={24} md={16}>
<div
className="welcome-banner"
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: designTokens.borderRadius.medium,
padding: '28px',
color: '#fff',
marginBottom: '24px',
animation: 'fadeInUp 0.6s ease-out 0.4s backwards',
}}
>
<h2
style={{
fontSize: '1.4rem',
fontWeight: '600',
margin: '0 0 8px 0',
color: '#fff',
}}
>
欢迎使用IDC设备管理系统
</h2>
<p
style={{
fontSize: '1rem',
margin: '0',
opacity: '0.9',
color: '#fff',
}}
>
专业的机房设备管理解决方案提供全方位的设备监控和管理能力
</p>
</div> </div>
{/* 5. 系统状态 */}
<div style={{ animation: 'fadeInUp 0.6s ease-out 0.4s backwards' }}>
<SectionTitle
icon={SafetyOutlined}
title="系统状态"
subtitle="关键运行指标概览"
/>
<Row gutter={[24, 24]}>
<Col xs={24} lg={24}>
<Card style={{
borderRadius: designTokens.borderRadius.large,
border: 'none',
boxShadow: designTokens.shadows.large,
background: '#fff',
}}>
<QuickStats <QuickStats
onlineRate={stats.onlineRate} onlineRate={stats.onlineRate}
powerUsage={stats.powerUsage} powerUsage={stats.powerUsage}
totalMaxPower={stats.totalMaxPower} totalMaxPower={stats.totalMaxPower}
/> />
</Col> </Card>
<Col xs={24} md={8}>
<SystemInfo onRefresh={handleRefresh} isRefreshing={isRefreshing} />
</Col> </Col>
</Row> </Row>
<NavigationGrid hoveredCard={hoveredCard} onHover={handleHover} />
</div>
</Card>
</div> </div>
<style>{` <style>{`
/* 响应式设计 */
@media (max-width: 1200px) {
.nav-grid {
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)) !important;
}
}
@media (max-width: 992px) {
.nav-grid {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) !important;
gap: 12px !important;
}
}
@media (max-width: 768px) {
.dashboard-container {
padding: 16px !important;
}
.dashboard-header {
padding: 24px 20px !important;
border-radius: 16px !important;
}
.dashboard-title {
font-size: 1.5rem !important;
gap: 10px !important;
}
.dashboard-subtitle {
font-size: 0.9rem !important;
}
.nav-grid {
grid-template-columns: repeat(2, 1fr) !important;
gap: 12px !important;
}
}
@media (max-width: 576px) { @media (max-width: 576px) {
.dashboard-container { .dashboard-container {
padding: 12px !important; padding: 12px !important;
@@ -587,34 +685,54 @@ function Dashboard() {
border-radius: 12px !important; border-radius: 12px !important;
} }
.dashboard-title { .dashboard-title {
font-size: 1.4rem !important; font-size: 1.3rem !important;
gap: 8px !important; gap: 8px !important;
} }
.dashboard-subtitle { .dashboard-subtitle {
font-size: 0.85rem !important; font-size: 0.85rem !important;
} }
.stat-value {
font-size: 1.6rem !important;
}
.nav-grid { .nav-grid {
grid-template-columns: repeat(3, 1fr) !important; grid-template-columns: 1fr !important;
gap: 8px !important; gap: 12px !important;
} }
.nav-button { .nav-button {
padding: 12px 8px !important; padding: 16px !important;
} }
.nav-icon { .nav-icon {
width: 40px !important; width: 48px !important;
height: 40px !important; height: 48px !important;
font-size: 20px !important; font-size: 22px !important;
}
.nav-text {
font-size: 0.7rem !important;
} }
} }
@media (max-width: 375px) { @media (max-width: 375px) {
.nav-grid { .dashboard-container {
grid-template-columns: repeat(2, 1fr) !important; padding: 10px !important;
}
.dashboard-header {
padding: 18px 14px !important;
}
}
/* 动画关键帧 */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
} }
} }
`}</style> `}</style>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "idc-device-management", "name": "idc-device-management",
"version": "1.0", "version": "1.3.0",
"description": "IDC设备管理系统", "description": "IDC设备管理系统",
"scripts": { "scripts": {
"start": "concurrently \"npm run start:backend\" \"npm run start:frontend\"", "start": "concurrently \"npm run start:backend\" \"npm run start:frontend\"",