Files
yunrui_asset/frontend/src/pages/Dashboard.jsx
T

548 lines
16 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useCallback, useMemo } from 'react';
import { Card, Row, Col, Typography, message } from 'antd';
import { DatabaseOutlined, CloudServerOutlined, WarningOutlined, HomeOutlined, TeamOutlined, BarChartOutlined, DashboardOutlined } from '@ant-design/icons';
import api from '../api';
import { designTokens } from '../config/theme';
import { useFetch } from '../hooks/useSWR';
import ErrorBoundary from '../components/ErrorBoundary';
import {
AnimatedCounter,
CircularProgress,
PowerGauge,
DeviceTrendChart,
StatusLegend,
StatCard,
NavigationGrid,
QuickStats,
SystemInfo,
} from '../components/dashboard';
const { Title } = Typography;
const containerStyle = {
minHeight: '100vh',
background: 'linear-gradient(180deg, #f5f7fa 0%, #e8ecf1 100%)',
padding: 'clamp(12px, 3vw, 20px)',
};
const headerStyle = {
textAlign: 'center',
marginBottom: '32px',
padding: 'clamp(20px, 5vw, 32px) clamp(16px, 4vw, 48px)',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: 'clamp(12px, 3vw, 20px)',
color: '#fff',
boxShadow: '0 8px 32px rgba(102, 126, 234, 0.3)',
animation: 'fadeInDown 0.6s ease-out',
};
const titleStyle = {
fontSize: 'clamp(1.4rem, 4vw, 2.2rem)',
fontWeight: '700',
color: '#fff',
margin: '0 0 8px 0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 'clamp(8px, 2vw, 16px)',
flexWrap: 'wrap',
textAlign: 'center',
};
const subtitleStyle = {
fontSize: 'clamp(0.85rem, 2.5vw, 1.1rem)',
color: 'rgba(255, 255, 255, 0.85)',
margin: '0',
textAlign: 'center',
};
const progressCardStyle = {
borderRadius: designTokens.borderRadius.large,
border: 'none',
boxShadow: designTokens.shadows.medium,
background: '#fff',
height: '100%',
animation: 'fadeInUp 0.6s ease-out 0.3s backwards',
};
const chartContainerStyle = {
padding: '20px',
borderRadius: designTokens.borderRadius.medium,
background: 'linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%)',
border: '1px solid #f0f0f0',
};
const PIE_CHART_COLORS = {
success: designTokens.colors.success.main,
warning: designTokens.colors.warning.main,
error: designTokens.colors.error.main,
primary: designTokens.colors.primary.main,
};
const pieChartStyle = {
width: '180px',
height: '180px',
borderRadius: '50%',
background: `conic-gradient(
${PIE_CHART_COLORS.success} 0deg 216deg,
${PIE_CHART_COLORS.warning} 216deg 288deg,
${PIE_CHART_COLORS.error} 288deg 324deg,
${PIE_CHART_COLORS.primary} 324deg 360deg
)`,
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
};
const pieChartInner = {
width: '120px',
height: '120px',
borderRadius: '50%',
background: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
};
const overviewCardStyle = {
borderRadius: designTokens.borderRadius.large,
border: 'none',
boxShadow: designTokens.shadows.medium,
background: '#fff',
animation: 'fadeInUp 0.6s ease-out 0.2s backwards',
};
function Dashboard() {
const [hoveredCard, setHoveredCard] = useState(null);
const [animatedKey, setAnimatedKey] = useState(0);
const { data: devicesData, isLoading: devicesLoading, mutate: mutateDevices } = useFetch(
'/devices?pageSize=1'
);
const { data: racksData, isLoading: racksLoading } = useFetch('/racks?pageSize=1');
const { data: roomsData, isLoading: roomsLoading } = useFetch('/rooms');
const { data: usersData, isLoading: usersLoading } = useFetch('/users?pageSize=1');
const { data: ticketsData, isLoading: ticketsLoading } = useFetch('/tickets?pageSize=1&status=open');
const { data: faultData, isLoading: faultLoading } = useFetch('/devices?status=fault&pageSize=1');
const loading = devicesLoading || racksLoading || roomsLoading || usersLoading || ticketsLoading;
const isRefreshing = devicesLoading && devicesData;
const stats = useMemo(() => {
const totalDevices = devicesData?.total || 0;
const totalRacks = racksData?.total || 0;
const rooms = roomsData || [];
const totalRooms = Array.isArray(rooms) ? rooms.length : 0;
const totalUsers = usersData?.total || 0;
const activeTickets = ticketsData?.total || 0;
const faultDevices = faultData?.total || 0;
return {
totalDevices,
totalRacks,
totalRooms,
totalUsers,
activeTickets,
faultDevices,
deviceGrowth: 2.5,
faultTrend: -12.3,
onlineRate:
totalDevices > 0
? (((totalDevices - faultDevices) / totalDevices) * 100).toFixed(1)
: 100,
powerUsage: Math.floor(Math.random() * 5000) + 2000,
};
}, [devicesData, racksData, roomsData, usersData, ticketsData, faultData]);
const handleRefresh = useCallback(async () => {
setAnimatedKey((prev) => prev + 1);
await Promise.all([
mutateDevices(),
]);
}, [mutateDevices]);
const handleHover = useCallback((key) => {
setHoveredCard(key);
}, []);
const statCards = useMemo(
() => [
{
key: 'devices',
xs: 12,
sm: 12,
md: 8,
lg: 6,
xl: 4,
icon: CloudServerOutlined,
color: designTokens.colors.primary.main,
statKey: 'totalDevices',
title: '总设备数',
trend: stats.deviceGrowth,
tagColor: 'blue',
delay: 0,
},
{
key: 'racks',
xs: 12,
sm: 12,
md: 8,
lg: 6,
xl: 4,
icon: DatabaseOutlined,
color: '#722ed1',
statKey: 'totalRacks',
title: '总机柜数',
trend: 0,
tagColor: 'green',
customStatus: true,
delay: 1,
},
{
key: 'rooms',
xs: 12,
sm: 12,
md: 8,
lg: 6,
xl: 4,
icon: HomeOutlined,
color: designTokens.colors.success.main,
statKey: 'totalRooms',
title: '总机房数',
trend: 0,
tagColor: 'green',
customStatus: true,
delay: 2,
},
{
key: 'faults',
xs: 12,
sm: 12,
md: 8,
lg: 6,
xl: 4,
icon: WarningOutlined,
color: designTokens.colors.error.main,
statKey: 'faultDevices',
title: '故障设备',
trend: stats.faultTrend,
tagColor: 'red',
delay: 3,
},
{
key: 'users',
xs: 12,
sm: 12,
md: 8,
lg: 6,
xl: 4,
icon: TeamOutlined,
color: '#13c2c2',
statKey: 'totalUsers',
title: '用户总数',
trend: 5.2,
tagColor: 'cyan',
delay: 4,
},
{
key: 'tickets',
xs: 12,
sm: 12,
md: 8,
lg: 6,
xl: 4,
icon: BarChartOutlined,
color: '#fa8c16',
statKey: 'activeTickets',
title: '待处理工单',
trend: -8.5,
tagColor: 'orange',
delay: 5,
},
],
[stats.deviceGrowth, stats.faultTrend]
);
const deviceTrendData = useMemo(
() => [
{ label: '周一', value: 45, color: designTokens.colors.primary.main },
{ label: '周二', value: 52, color: designTokens.colors.primary.main },
{ label: '周三', value: 48, color: designTokens.colors.primary.main },
{ label: '周四', value: 60, color: designTokens.colors.success.main },
{ label: '周五', value: 55, color: designTokens.colors.success.main },
{ label: '周六', value: 42, color: designTokens.colors.warning.main },
{ label: '周日', value: 58, color: designTokens.colors.primary.main },
],
[]
);
const styles = `
@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);
}
}
`;
return (
<>
<style>{styles}</style>
<div style={containerStyle} className="dashboard-container">
<div style={headerStyle} className="dashboard-header">
<h1 style={titleStyle} className="dashboard-title">
<DashboardOutlined />
IDC设备管理系统
</h1>
<p style={subtitleStyle} className="dashboard-subtitle">
实时监控 · 智能管理 · 高效运维
</p>
</div>
<Row gutter={[24, 24]} style={{ marginBottom: '32px' }}>
{statCards.map((config) => (
<StatCard
key={config.statKey}
config={config}
stats={stats}
loading={loading}
animatedKey={animatedKey}
hoveredCard={hoveredCard}
onHover={handleHover}
/>
))}
</Row>
<Row gutter={[24, 24]} style={{ marginBottom: '24px' }}>
<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>
<StatusLegend />
</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={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '24px',
}}
>
<ErrorBoundary
fallback={
<div style={{ padding: '20px', textAlign: 'center', color: '#999' }}>
在线率图表加载失败
</div>
}
>
<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={10000} />
</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
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>
<QuickStats onlineRate={stats.onlineRate} powerUsage={stats.powerUsage} />
</Col>
<Col xs={24} md={8}>
<SystemInfo onRefresh={handleRefresh} isRefreshing={isRefreshing} />
</Col>
</Row>
<NavigationGrid hoveredCard={hoveredCard} onHover={handleHover} />
</div>
</Card>
</div>
<style>{`
@media (max-width: 576px) {
.dashboard-container {
padding: 12px !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;
}
}
`}</style>
</div>
</>
);
}
export default React.memo(Dashboard);