From e1367267f8f1488e739da50666a21c886830d53a Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Mon, 27 Apr 2026 13:56:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8D=87=E7=BA=A7=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E8=87=B31.2.0=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=AA=E8=A1=A8=E7=9B=98UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 +- backend/routes/systemSettings.js | 60 +- frontend/package.json | 2 +- .../components/dashboard/NavigationGrid.jsx | 342 ++++++++--- .../src/components/dashboard/QuickStats.jsx | 161 ++++-- .../src/components/dashboard/SystemInfo.jsx | 388 +++++++++++-- frontend/src/pages/Dashboard.jsx | 538 +++++++++++------- package.json | 2 +- 8 files changed, 1112 insertions(+), 383 deletions(-) diff --git a/backend/package.json b/backend/package.json index ef52abd..2e91892 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "idc-backend", - "version": "1.0.0", + "version": "1.2.0", "description": "IDC设备管理系统后端", "scripts": { "start": "node server.js", diff --git a/backend/routes/systemSettings.js b/backend/routes/systemSettings.js index 35c90a1..49a3202 100644 --- a/backend/routes/systemSettings.js +++ b/backend/routes/systemSettings.js @@ -5,6 +5,11 @@ const path = require('path'); const { Op } = require('sequelize'); 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 匹配,兼容子路由挂载 const publicRoutes = ['/system/info']; @@ -149,7 +154,7 @@ const initDefaultSettings = async () => { // 关于页面 { settingKey: 'app_version', - settingValue: JSON.stringify('1.0.0'), + settingValue: JSON.stringify('1.2.0'), settingType: 'string', category: 'about', description: '应用版本', @@ -516,7 +521,7 @@ router.post('/backup', async (req, res) => { const backupData = { timestamp: new Date().toISOString(), - version: '1.0.0', + version: APP_VERSION, data: { devices: await Device.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 Room = require('../models/Room'); 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([ Device.count(), @@ -716,10 +735,30 @@ router.get('/system/info', async (req, res) => { 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({ system: { name: '机柜管理系统', - version: '1.0.0', + version: APP_VERSION, uptime: process.uptime(), nodeVersion: process.version, platform: process.platform, @@ -727,6 +766,21 @@ router.get('/system/info', async (req, res) => { memoryUsage: process.memoryUsage(), 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: { devices: deviceCount, racks: rackCount, diff --git a/frontend/package.json b/frontend/package.json index cd36079..46dd794 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "idc-frontend", - "version": "1.0.0", + "version": "1.2.0", "description": "IDC设备管理系统前端", "scripts": { "start": "vite", diff --git a/frontend/src/components/dashboard/NavigationGrid.jsx b/frontend/src/components/dashboard/NavigationGrid.jsx index 3f28900..ee88021 100644 --- a/frontend/src/components/dashboard/NavigationGrid.jsx +++ b/frontend/src/components/dashboard/NavigationGrid.jsx @@ -7,6 +7,9 @@ import { BarChartOutlined, AppstoreOutlined, SettingOutlined, + ApartmentOutlined, + LinkOutlined, + ArrowRightOutlined, } from '@ant-design/icons'; import { designTokens } from '../../config/theme'; @@ -17,6 +20,7 @@ const NAV_BUTTONS_DATA = [ text: '设备管理', path: '/devices', color: designTokens.colors.primary.main, + description: '设备全生命周期管理', }, { key: 'racks', @@ -24,20 +28,23 @@ const NAV_BUTTONS_DATA = [ text: '资源规划', path: '/racks', color: '#722ed1', - }, - { - key: 'faults', - icon: WarningOutlined, - text: '故障监控', - path: '/faults', - color: designTokens.colors.warning.main, + description: '机柜和机房管理', }, { key: 'tickets', - icon: BarChartOutlined, - text: '工单管理', + icon: WarningOutlined, + text: '故障监控', path: '/tickets', + color: designTokens.colors.warning.main, + description: '工单和故障处理', + }, + { + key: 'analytics', + icon: BarChartOutlined, + text: '数据分析', + path: '/', color: '#13c2c2', + description: '数据统计和分析', }, { key: 'consumables', @@ -45,95 +52,290 @@ const NAV_BUTTONS_DATA = [ text: '耗材管理', path: '/consumables', 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', icon: SettingOutlined, text: '系统配置', 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 navigate = useNavigate(); + const firstRow = NAV_BUTTONS_DATA.slice(0, 4); + const secondRow = NAV_BUTTONS_DATA.slice(4, 8); - return ( -
- {NAV_BUTTONS_DATA.map(({ key, icon: Icon, text, color }) => { + const renderRow = (items) => ( +
+ {items.map(({ key, icon: Icon, text, color, description, path }) => { const isHovered = hoveredCard === `nav-${key}`; return (
b.key === key) * 0.1}s`, + padding: '14px 12px', + 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}`)} onMouseLeave={() => onHover(null)} - onClick={() => navigate(`/${key}`)} + onClick={() => navigate(path)} > -
- + {/* 装饰性背景元素 */} +
+ + {/* 次要装饰元素 */} +
+ +
+ {/* 顶部区域:图标和箭头 */} +
+ {/* 图标容器 */} +
+ +
+ + {/* 箭头指示器 */} +
+ +
+
+ + {/* 文本内容区域 */} +
+ {/* 标题 */} +
+ {text} +
+ + {/* 描述文本 */} +
+ {description} +
+
- - {text} -
); })}
); + + return ( +
+ {/* 区域标题 */} +
+
+
+ +
+
+

+ 功能导航 +

+

+ 快速访问系统核心功能模块 +

+
+
+
+ + {/* 导航网格 - 第一排 */} + {renderRow(firstRow)} + + {/* 导航网格 - 第二排 */} + {renderRow(secondRow)} + + {/* 响应式样式 */} + +
+ ); }; export default React.memo(NavigationGrid); diff --git a/frontend/src/components/dashboard/QuickStats.jsx b/frontend/src/components/dashboard/QuickStats.jsx index f84fd1b..9aca741 100644 --- a/frontend/src/components/dashboard/QuickStats.jsx +++ b/frontend/src/components/dashboard/QuickStats.jsx @@ -1,21 +1,10 @@ import React from 'react'; -import { Card, Typography } from 'antd'; +import { Typography } from 'antd'; import { LineChartOutlined, SafetyOutlined, ThunderboltOutlined } from '@ant-design/icons'; import { designTokens } from '../../config/theme'; 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 powerUsagePercent = totalMaxPower > 0 ? ((powerUsage / totalMaxPower) * 100).toFixed(1) : '0.0'; @@ -26,65 +15,143 @@ const QuickStats = ({ onlineRate, powerUsage, totalMaxPower }) => { label: '在线率', value: `${onlineRate}%`, color: designTokens.colors.success.main, + trend: '稳定', + gradient: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)', }, { icon: SafetyOutlined, label: '安全等级', value: 'A级', color: designTokens.colors.primary.main, + trend: '优秀', + gradient: 'linear-gradient(135deg, #1890ff 0%, #40a9ff 100%)', }, { icon: ThunderboltOutlined, label: '功率使用', value: `${powerUsagePercent}%`, color: designTokens.colors.warning.main, + trend: '正常', + gradient: 'linear-gradient(135deg, #fa8c16 0%, #ffa940 100%)', }, ]; return ( -
+
+
+ + 系统状态 + +
+ +
- {quickStats.map((stat, index) => ( -
-
+ {quickStats.map((stat, index) => ( +
- -
-
- - {stat.label} - -
- {stat.value} + {/* 装饰性背景 */} +
+ +
+
+ +
+ +
+ + {stat.label} + +
+ {stat.value} +
+
+
+ + {stat.trend} + +
+
-
- ))} + ))} +
); }; diff --git a/frontend/src/components/dashboard/SystemInfo.jsx b/frontend/src/components/dashboard/SystemInfo.jsx index 19de2df..b5af712 100644 --- a/frontend/src/components/dashboard/SystemInfo.jsx +++ b/frontend/src/components/dashboard/SystemInfo.jsx @@ -1,67 +1,355 @@ import React from 'react'; -import { Button } from 'antd'; -import { ReloadOutlined } from '@ant-design/icons'; +import { Button, Spin } from 'antd'; +import { + ReloadOutlined, + ClockCircleOutlined, + InfoCircleOutlined, + CheckCircleOutlined, + ThunderboltOutlined, + HddOutlined, +} from '@ant-design/icons'; import { designTokens } from '../../config/theme'; +import { useFetch } from '../../hooks/useSWR'; -const systemInfoStyle = { - background: 'linear-gradient(135deg, #f0f7ff 0%, #e6f7ff 100%)', - borderRadius: designTokens.borderRadius.medium, - padding: '20px', - border: '1px solid #91d5ff', +// 格式化运行时间 +const formatUptime = (seconds) => { + if (!seconds || isNaN(seconds)) { + return '0天 0小时 0分钟'; + } + + 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 { 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 ( -
-
-
+ {/* 系统信息标题 */} +
+
+
+ +
+
+
+ 系统信息 +
+
+
+ + +
+ + {/* 系统基本信息卡片 */} +
+ {/* 系统版本和运行状态 */} +
+
+
- 刷新数据 - + 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 ? : } +
+
+
+ 系统版本 +
+
+ {formattedVersion} +
+
+
+ +
+
+ 运行中 +
+
+ + {/* 运行时长和最后更新 */} +
+
+
+ +
+
+
+ 运行时长 +
+
+ {formattedUptime} +
+
+
+ +
+
+ +
+
+
+ 最后更新 +
+
+ {currentTime} +
+
+
+ + {/* 系统状态指标 */} +
+
+ + + 系统状态 + +
+ + {systemMetrics.map((metric, index) => ( +
+
+ + {metric.label} + + + {metric.value}% + +
+
+
+
+
+ ))} +
+ + {/* 动画样式 */} +
); }; diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 0fe93bd..080909d 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -1,5 +1,5 @@ import React, { useState, useCallback, useMemo } from 'react'; -import { Card, Row, Col, Typography, message } from 'antd'; +import { Card, Row, Col, Typography } from 'antd'; import { DatabaseOutlined, CloudServerOutlined, @@ -8,8 +8,8 @@ import { TeamOutlined, BarChartOutlined, DashboardOutlined, + SafetyOutlined, } from '@ant-design/icons'; -import api from '../api'; import { designTokens } from '../config/theme'; import { useFetch } from '../hooks/useSWR'; import ErrorBoundary from '../components/ErrorBoundary'; @@ -376,10 +376,56 @@ function Dashboard() { } `; + // 区域标题组件 + const SectionTitle = ({ icon: Icon, title, subtitle, color = designTokens.colors.primary.main }) => ( +
+
+ +
+
+

+ {title} +

+ {subtitle && ( +

+ {subtitle} +

+ )} +
+
+ ); + return ( <>
+ {/* 1. 欢迎横幅 */}

@@ -390,234 +436,306 @@ function Dashboard() {

- - {statCards.map(config => ( - - ))} - + {/* 2. 功能导航模块 - 移到原快捷操作位置 */} +
+ + + + {/* 功能导航网格 */} + + + - - - -
- - 设备状态分布 - -
-
-
- - {stats.totalDevices} - - - 设备总数 - + + + + + + +
+ + {/* 3. 核心指标统计卡片 */} +
+ + + {statCards.map(config => ( + + ))} + +
+ + {/* 4. 数据图表区域 */} +
+ + + + +
+ + 设备状态分布 + +
+
+
+ + {stats.totalDevices} + + + 设备总数 + +
+
-
-
- - + + - - -
- - 系统健康指标 - -
- - 在线率图表加载失败 -
- } + + +
+ - <CircularProgress - percentage={parseFloat(stats.onlineRate)} - size={140} - strokeWidth={12} - color={designTokens.colors.success.main} - label="在线率" - /> - </ErrorBoundary> - <ErrorBoundary - fallback={ - <div style={{ padding: '20px', textAlign: 'center', color: '#999' }}> - 功率仪表加载失败 - </div> - } - > - <PowerGauge value={stats.powerUsage} maxValue={stats.totalMaxPower} /> - </ErrorBoundary> - </div> - </div> - </Card> - </Col> - - <Col xs={24} md={8}> - <Card style={progressCardStyle}> - <div style={{ padding: '20px' }}> - <Title - level={5} - style={{ margin: '0 0 20px 0', color: designTokens.colors.text.primary }} - > - 周设备趋势 - -
- - 趋势图表加载失败 -
- } - > - - -
-
- - 周一 至 周日 设备变化趋势 - -
-
-
- - - -
- -
- - + 系统健康指标 +
-

+ 在线率图表加载失败 +

+ } > - 欢迎使用IDC设备管理系统 - -

+ + + 功率仪表加载失败 +

+ } > - 专业的机房设备管理解决方案,提供全方位的设备监控和管理能力 -

+ +
+
+ + - - + + +
+ + 周设备趋势 + +
+ + 趋势图表加载失败 +
+ } + > + + +
+
+ + 周一 至 周日 设备变化趋势 + +
+
+ + + +
- - - -
- - -
- + {/* 5. 系统状态 */} +
+ + + + + + + +
+ } + + @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) { + .dashboard-container { + padding: 12px !important; + } + .dashboard-header { + padding: 20px 16px !important; + border-radius: 12px !important; + } + .dashboard-title { + font-size: 1.3rem !important; + gap: 8px !important; + } + .dashboard-subtitle { + font-size: 0.85rem !important; + } + .nav-grid { + grid-template-columns: 1fr !important; + gap: 12px !important; + } + .nav-button { + padding: 16px !important; + } + .nav-icon { + width: 48px !important; + height: 48px !important; + font-size: 22px !important; + } + } + + @media (max-width: 375px) { + .dashboard-container { + 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); + } + } + `}
); diff --git a/package.json b/package.json index 2494f88..c001630 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "idc-device-management", - "version": "1.0", + "version": "1.3.0", "description": "IDC设备管理系统", "scripts": { "start": "concurrently \"npm run start:backend\" \"npm run start:frontend\"",