feat: 升级系统版本至1.2.0并优化仪表盘UI
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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,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"
|
display: 'grid',
|
||||||
style={{
|
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||||
display: 'grid',
|
gap: '10px',
|
||||||
gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))',
|
marginBottom: '10px',
|
||||||
gap: 'clamp(8px, 2vw, 16px)',
|
}}>
|
||||||
marginBottom: '24px',
|
{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 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}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 描述文本 */}
|
||||||
|
<div style={{
|
||||||
|
fontSize: '0.78rem',
|
||||||
|
color: designTokens.colors.text.secondary,
|
||||||
|
lineHeight: '1.4',
|
||||||
|
fontWeight: '400',
|
||||||
|
}}>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="nav-text" style={navTextStyle}>
|
|
||||||
{text}
|
|
||||||
</span>
|
|
||||||
</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);
|
||||||
|
|||||||
@@ -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,65 +15,143 @@ 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}
|
||||||
{quickStats.map((stat, index) => (
|
|
||||||
<div key={index} style={quickStatItemStyle}>
|
|
||||||
<div
|
|
||||||
style={{
|
style={{
|
||||||
width: '48px',
|
background: '#fafafa',
|
||||||
height: '48px',
|
borderRadius: designTokens.borderRadius.large,
|
||||||
borderRadius: designTokens.borderRadius.medium,
|
padding: '20px',
|
||||||
background: `linear-gradient(135deg, ${stat.color}20 0%, ${stat.color}10 100%)`,
|
border: '1px solid #f0f0f0',
|
||||||
display: 'flex',
|
position: 'relative',
|
||||||
alignItems: 'center',
|
overflow: 'hidden',
|
||||||
justifyContent: 'center',
|
transition: 'all 0.3s ease',
|
||||||
fontSize: '24px',
|
cursor: 'pointer',
|
||||||
color: stat.color,
|
'&:hover': {
|
||||||
boxShadow: `0 4px 12px ${stat.color}20`,
|
transform: 'translateY(-2px)',
|
||||||
|
boxShadow: '0 8px 24px rgba(0,0,0,0.06)',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<stat.icon />
|
{/* 装饰性背景 */}
|
||||||
</div>
|
<div style={{
|
||||||
<div>
|
position: 'absolute',
|
||||||
<Text style={{ color: designTokens.colors.text.secondary, fontSize: '0.85rem' }}>
|
right: '-30px',
|
||||||
{stat.label}
|
top: '-30px',
|
||||||
</Text>
|
width: '100px',
|
||||||
<div
|
height: '100px',
|
||||||
style={{
|
borderRadius: '50%',
|
||||||
fontSize: '1.2rem',
|
background: `${stat.color}08`,
|
||||||
fontWeight: '700',
|
}} />
|
||||||
color: designTokens.colors.text.primary,
|
|
||||||
}}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '16px', position: 'relative', zIndex: 1 }}>
|
||||||
>
|
<div style={{
|
||||||
{stat.value}
|
width: '52px',
|
||||||
|
height: '52px',
|
||||||
|
borderRadius: '14px',
|
||||||
|
background: stat.gradient,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: '24px',
|
||||||
|
color: '#fff',
|
||||||
|
boxShadow: `0 8px 20px ${stat.color}30`,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
<stat.icon style={{ fontSize: '24px' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<Text style={{
|
||||||
|
color: designTokens.colors.text.secondary,
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '4px',
|
||||||
|
}}>
|
||||||
|
{stat.label}
|
||||||
|
</Text>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '1.4rem',
|
||||||
|
fontWeight: '700',
|
||||||
|
color: designTokens.colors.text.primary,
|
||||||
|
lineHeight: 1.2,
|
||||||
|
marginBottom: '6px',
|
||||||
|
}}>
|
||||||
|
{stat.value}
|
||||||
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,67 +1,355 @@
|
|||||||
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',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: '12px',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: '28px',
|
||||||
|
height: '28px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexWrap: 'wrap',
|
justifyContent: 'center',
|
||||||
gap: '16px',
|
color: '#fff',
|
||||||
|
fontSize: '14px',
|
||||||
|
}}>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
fontWeight: '700',
|
||||||
|
color: designTokens.colors.text.primary,
|
||||||
|
}}>
|
||||||
|
系统信息
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<ReloadOutlined spin={isRefreshing} />}
|
||||||
|
size="small"
|
||||||
|
onClick={onRefresh}
|
||||||
|
loading={isRefreshing}
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 3px 10px rgba(102, 126, 234, 0.25)',
|
||||||
|
height: '32px',
|
||||||
|
padding: '0 12px',
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: '0.8rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
刷新数据
|
||||||
<p
|
</Button>
|
||||||
style={{
|
</div>
|
||||||
margin: '0',
|
|
||||||
fontSize: '0.9rem',
|
{/* 系统基本信息卡片 */}
|
||||||
color: designTokens.colors.text.primary,
|
<div style={{
|
||||||
fontWeight: '600',
|
background: 'linear-gradient(135deg, #f5f7ff 0%, #f0f7ff 100%)',
|
||||||
}}
|
borderRadius: '12px',
|
||||||
>
|
padding: '14px',
|
||||||
<strong>系统版本:</strong> v1.0.0
|
border: '1px solid #e6f4ff',
|
||||||
</p>
|
marginBottom: '10px',
|
||||||
<p
|
}}>
|
||||||
style={{
|
{/* 系统版本和运行状态 */}
|
||||||
margin: '4px 0 0 0',
|
<div style={{
|
||||||
fontSize: '0.85rem',
|
display: 'flex',
|
||||||
color: designTokens.colors.text.secondary,
|
justifyContent: 'space-between',
|
||||||
}}
|
alignItems: 'center',
|
||||||
>
|
marginBottom: '10px',
|
||||||
<strong>最后更新:</strong>
|
paddingBottom: '10px',
|
||||||
{new Date().toLocaleDateString()}
|
borderBottom: '1px dashed #d9eaff',
|
||||||
</p>
|
}}>
|
||||||
</div>
|
<div style={{
|
||||||
<Button
|
display: 'flex',
|
||||||
type="primary"
|
alignItems: 'center',
|
||||||
icon={<ReloadOutlined spin={isRefreshing} />}
|
gap: '8px',
|
||||||
size="small"
|
}}>
|
||||||
onClick={onRefresh}
|
<div style={{
|
||||||
loading={isRefreshing}
|
width: '30px',
|
||||||
style={{
|
height: '30px',
|
||||||
background: designTokens.colors.primary.gradient,
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
boxShadow: '0 4px 12px rgba(24, 144, 255, 0.3)',
|
background: '#fff',
|
||||||
}}
|
display: 'flex',
|
||||||
>
|
alignItems: 'center',
|
||||||
刷新数据
|
justifyContent: 'center',
|
||||||
</Button>
|
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 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>
|
</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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+328
-210
@@ -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,234 +436,306 @@ function Dashboard() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Row gutter={[24, 24]} style={{ marginBottom: '32px' }}>
|
{/* 2. 功能导航模块 - 移到原快捷操作位置 */}
|
||||||
{statCards.map(config => (
|
<div style={{ animation: 'fadeInUp 0.6s ease-out 0.1s backwards', marginBottom: '32px' }}>
|
||||||
<StatCard
|
<Row gutter={[24, 24]}>
|
||||||
key={config.statKey}
|
<Col xs={24} lg={18}>
|
||||||
config={config}
|
<Card style={{
|
||||||
stats={stats}
|
borderRadius: designTokens.borderRadius.large,
|
||||||
loading={loading}
|
border: 'none',
|
||||||
animatedKey={animatedKey}
|
boxShadow: designTokens.shadows.large,
|
||||||
hoveredCard={hoveredCard}
|
background: '#fff',
|
||||||
onHover={handleHover}
|
height: '100%',
|
||||||
/>
|
padding: '16px',
|
||||||
))}
|
}}>
|
||||||
</Row>
|
{/* 功能导航网格 */}
|
||||||
|
<NavigationGrid hoveredCard={hoveredCard} onHover={handleHover} />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Row gutter={[24, 24]} style={{ marginBottom: '24px' }}>
|
<Col xs={24} lg={6}>
|
||||||
<Col xs={24} md={8}>
|
<Card style={{
|
||||||
<Card style={progressCardStyle}>
|
borderRadius: designTokens.borderRadius.large,
|
||||||
<div style={{ padding: '20px' }}>
|
border: 'none',
|
||||||
<Title
|
boxShadow: designTokens.shadows.large,
|
||||||
level={5}
|
background: '#fff',
|
||||||
style={{ margin: '0 0 20px 0', color: designTokens.colors.text.primary }}
|
height: '100%',
|
||||||
>
|
}}>
|
||||||
设备状态分布
|
<SystemInfo onRefresh={handleRefresh} isRefreshing={isRefreshing} />
|
||||||
</Title>
|
</Card>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
</Col>
|
||||||
<div style={pieChartStyle}>
|
</Row>
|
||||||
<div style={pieChartInner}>
|
</div>
|
||||||
<span
|
|
||||||
style={{
|
{/* 3. 核心指标统计卡片 */}
|
||||||
fontSize: '1.5rem',
|
<div style={{ animation: 'fadeInUp 0.6s ease-out 0.2s backwards', marginBottom: '32px' }}>
|
||||||
fontWeight: '700',
|
<SectionTitle
|
||||||
color: designTokens.colors.text.primary,
|
icon={BarChartOutlined}
|
||||||
}}
|
title="核心指标"
|
||||||
>
|
subtitle="实时监控数据中心关键指标"
|
||||||
{stats.totalDevices}
|
/>
|
||||||
</span>
|
<Row gutter={[24, 24]}>
|
||||||
<span
|
{statCards.map(config => (
|
||||||
style={{ fontSize: '0.75rem', color: designTokens.colors.text.secondary }}
|
<StatCard
|
||||||
>
|
key={config.statKey}
|
||||||
设备总数
|
config={config}
|
||||||
</span>
|
stats={stats}
|
||||||
|
loading={loading}
|
||||||
|
animatedKey={animatedKey}
|
||||||
|
hoveredCard={hoveredCard}
|
||||||
|
onHover={handleHover}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 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}>
|
||||||
|
<Card style={progressCardStyle}>
|
||||||
|
<div style={{ padding: '20px' }}>
|
||||||
|
<Title
|
||||||
|
level={5}
|
||||||
|
style={{ margin: '0 0 20px 0', color: designTokens.colors.text.primary }}
|
||||||
|
>
|
||||||
|
设备状态分布
|
||||||
|
</Title>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||||
|
<div style={pieChartStyle}>
|
||||||
|
<div style={pieChartInner}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: '1.5rem',
|
||||||
|
fontWeight: '700',
|
||||||
|
color: designTokens.colors.text.primary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stats.totalDevices}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{ fontSize: '0.75rem', color: designTokens.colors.text.secondary }}
|
||||||
|
>
|
||||||
|
设备总数
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<StatusLegend deviceStatusPercentages={deviceStatusPercentages} />
|
||||||
</div>
|
</div>
|
||||||
<StatusLegend deviceStatusPercentages={deviceStatusPercentages} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</Card>
|
</Col>
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col xs={24} md={8}>
|
<Col xs={24} md={8}>
|
||||||
<Card style={progressCardStyle}>
|
<Card style={progressCardStyle}>
|
||||||
<div style={{ padding: '20px' }}>
|
<div style={{ padding: '20px' }}>
|
||||||
<Title
|
<Title
|
||||||
level={5}
|
level={5}
|
||||||
style={{ margin: '0 0 20px 0', color: designTokens.colors.text.primary }}
|
style={{ margin: '0 0 20px 0', color: designTokens.colors.text.primary }}
|
||||||
>
|
|
||||||
系统健康指标
|
|
||||||
</Title>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '24px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ErrorBoundary
|
|
||||||
fallback={
|
|
||||||
<div style={{ padding: '20px', textAlign: 'center', color: '#999' }}>
|
|
||||||
在线率图表加载失败
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<CircularProgress
|
系统健康指标
|
||||||
percentage={parseFloat(stats.onlineRate)}
|
</Title>
|
||||||
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 }}
|
|
||||||
>
|
|
||||||
周设备趋势
|
|
||||||
</Title>
|
|
||||||
<div style={chartContainerStyle}>
|
|
||||||
<ErrorBoundary
|
|
||||||
fallback={
|
|
||||||
<div style={{ padding: '20px', textAlign: 'center', color: '#999' }}>
|
|
||||||
趋势图表加载失败
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeviceTrendChart data={deviceTrendData} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: '16px',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: '16px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ fontSize: '0.75rem', color: designTokens.colors.text.tertiary }}>
|
|
||||||
周一 至 周日 设备变化趋势
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Card style={overviewCardStyle}>
|
|
||||||
<div style={{ padding: '24px' }}>
|
|
||||||
<Row gutter={[24, 24]}>
|
|
||||||
<Col xs={24} md={16}>
|
|
||||||
<div
|
<div
|
||||||
className="welcome-banner"
|
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
display: 'flex',
|
||||||
borderRadius: designTokens.borderRadius.medium,
|
flexDirection: 'column',
|
||||||
padding: '28px',
|
alignItems: 'center',
|
||||||
color: '#fff',
|
gap: '24px',
|
||||||
marginBottom: '24px',
|
|
||||||
animation: 'fadeInUp 0.6s ease-out 0.4s backwards',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2
|
<ErrorBoundary
|
||||||
style={{
|
fallback={
|
||||||
fontSize: '1.4rem',
|
<div style={{ padding: '20px', textAlign: 'center', color: '#999' }}>
|
||||||
fontWeight: '600',
|
在线率图表加载失败
|
||||||
margin: '0 0 8px 0',
|
</div>
|
||||||
color: '#fff',
|
}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
欢迎使用IDC设备管理系统
|
<CircularProgress
|
||||||
</h2>
|
percentage={parseFloat(stats.onlineRate)}
|
||||||
<p
|
size={140}
|
||||||
style={{
|
strokeWidth={12}
|
||||||
fontSize: '1rem',
|
color={designTokens.colors.success.main}
|
||||||
margin: '0',
|
label="在线率"
|
||||||
opacity: '0.9',
|
/>
|
||||||
color: '#fff',
|
</ErrorBoundary>
|
||||||
}}
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<div style={{ padding: '20px', textAlign: 'center', color: '#999' }}>
|
||||||
|
功率仪表加载失败
|
||||||
|
</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
专业的机房设备管理解决方案,提供全方位的设备监控和管理能力
|
<PowerGauge value={stats.powerUsage} maxValue={stats.totalMaxPower} />
|
||||||
</p>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
<QuickStats
|
<Col xs={24} md={8}>
|
||||||
onlineRate={stats.onlineRate}
|
<Card style={progressCardStyle}>
|
||||||
powerUsage={stats.powerUsage}
|
<div style={{ padding: '20px' }}>
|
||||||
totalMaxPower={stats.totalMaxPower}
|
<Title
|
||||||
/>
|
level={5}
|
||||||
</Col>
|
style={{ margin: '0 0 20px 0', color: designTokens.colors.text.primary }}
|
||||||
|
>
|
||||||
|
周设备趋势
|
||||||
|
</Title>
|
||||||
|
<div style={chartContainerStyle}>
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={
|
||||||
|
<div style={{ padding: '20px', textAlign: 'center', color: '#999' }}>
|
||||||
|
趋势图表加载失败
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DeviceTrendChart data={deviceTrendData} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: '16px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: '0.75rem', color: designTokens.colors.text.tertiary }}>
|
||||||
|
周一 至 周日 设备变化趋势
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Col xs={24} md={8}>
|
{/* 5. 系统状态 */}
|
||||||
<SystemInfo onRefresh={handleRefresh} isRefreshing={isRefreshing} />
|
<div style={{ animation: 'fadeInUp 0.6s ease-out 0.4s backwards' }}>
|
||||||
</Col>
|
<SectionTitle
|
||||||
</Row>
|
icon={SafetyOutlined}
|
||||||
|
title="系统状态"
|
||||||
<NavigationGrid hoveredCard={hoveredCard} onHover={handleHover} />
|
subtitle="关键运行指标概览"
|
||||||
</div>
|
/>
|
||||||
</Card>
|
<Row gutter={[24, 24]}>
|
||||||
|
<Col xs={24} lg={24}>
|
||||||
|
<Card style={{
|
||||||
|
borderRadius: designTokens.borderRadius.large,
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: designTokens.shadows.large,
|
||||||
|
background: '#fff',
|
||||||
|
}}>
|
||||||
|
<QuickStats
|
||||||
|
onlineRate={stats.onlineRate}
|
||||||
|
powerUsage={stats.powerUsage}
|
||||||
|
totalMaxPower={stats.totalMaxPower}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>{`
|
<style>{`
|
||||||
@media (max-width: 576px) {
|
/* 响应式设计 */
|
||||||
.dashboard-container {
|
@media (max-width: 1200px) {
|
||||||
padding: 12px !important;
|
.nav-grid {
|
||||||
}
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)) !important;
|
||||||
.dashboard-header {
|
|
||||||
padding: 20px 16px !important;
|
|
||||||
border-radius: 12px !important;
|
|
||||||
}
|
|
||||||
.dashboard-title {
|
|
||||||
font-size: 1.4rem !important;
|
|
||||||
gap: 8px !important;
|
|
||||||
}
|
|
||||||
.dashboard-subtitle {
|
|
||||||
font-size: 0.85rem !important;
|
|
||||||
}
|
|
||||||
.stat-value {
|
|
||||||
font-size: 1.6rem !important;
|
|
||||||
}
|
|
||||||
.nav-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr) !important;
|
|
||||||
gap: 8px !important;
|
|
||||||
}
|
|
||||||
.nav-button {
|
|
||||||
padding: 12px 8px !important;
|
|
||||||
}
|
|
||||||
.nav-icon {
|
|
||||||
width: 40px !important;
|
|
||||||
height: 40px !important;
|
|
||||||
font-size: 20px !important;
|
|
||||||
}
|
|
||||||
.nav-text {
|
|
||||||
font-size: 0.7rem !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@media (max-width: 375px) {
|
}
|
||||||
.nav-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr) !important;
|
@media (max-width: 992px) {
|
||||||
}
|
.nav-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) !important;
|
||||||
|
gap: 12px !important;
|
||||||
}
|
}
|
||||||
`}</style>
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -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\"",
|
||||||
|
|||||||
Reference in New Issue
Block a user