import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { Card, Row, Col, Statistic, message, Button, Tag, Typography, Progress, Spin } from 'antd'; import { DatabaseOutlined, CloudServerOutlined, WarningOutlined, HomeOutlined, SettingOutlined, ArrowUpOutlined, ArrowDownOutlined, DashboardOutlined, ReloadOutlined, EnvironmentOutlined, LineChartOutlined, SafetyOutlined, TeamOutlined, ThunderboltOutlined, AppstoreOutlined, BarChartOutlined } from '@ant-design/icons'; import api from '../api'; const { Title, Text } = Typography; const designTokens = { colors: { primary: { main: '#1890ff', light: '#40a9ff', dark: '#096dd9', gradient: 'linear-gradient(135deg, #1890ff 0%, #096dd9 100%)', bgGradient: 'linear-gradient(135deg, #1890ff15 0%, #096dd908 100%)' }, success: { main: '#52c41a', light: '#73d13d', dark: '#389e0d', gradient: 'linear-gradient(135deg, #52c41a 0%, #389e0d 100%)', bgGradient: 'linear-gradient(135deg, #52c41a15 0%, #389e0d08 100%)' }, warning: { main: '#faad14', light: '#ffc53d', dark: '#d48806', gradient: 'linear-gradient(135deg, #faad14 0%, #d48806 100%)', bgGradient: 'linear-gradient(135deg, #faad1415 0%, #d4880608 100%)' }, error: { main: '#ff4d4f', light: '#ff7875', dark: '#cf1322', gradient: 'linear-gradient(135deg, #ff4d4f 0%, #cf1322 100%)', bgGradient: 'linear-gradient(135deg, #ff4d4f15 0%, #cf132208 100%)' }, purple: { main: '#722ed1', light: '#9254de', dark: '#531dab', gradient: 'linear-gradient(135deg, #722ed1 0%, #531dab 100%)', bgGradient: 'linear-gradient(135deg, #722ed115 0%, #531dab08 100%)' }, cyan: { main: '#13c2c2', light: '#36cfc9', dark: '#08979c', gradient: 'linear-gradient(135deg, #13c2c2 0%, #08979c 100%)', bgGradient: 'linear-gradient(135deg, #13c2c215 0%, #08979c08 100%)' }, text: { primary: '#262626', secondary: '#8c8c8c', tertiary: '#bfbfbf' } }, shadows: { small: '0 2px 8px rgba(0, 0, 0, 0.06)', medium: '0 4px 16px rgba(0, 0, 0, 0.08)', large: '0 8px 24px rgba(0, 0, 0, 0.12)', hover: '0 12px 32px rgba(0, 0, 0, 0.15)' }, borderRadius: { small: '8px', medium: '12px', large: '16px', xl: '20px' }, transitions: { fast: '0.15s ease', normal: '0.3s cubic-bezier(0.4, 0, 0.2, 1)', slow: '0.5s cubic-bezier(0.4, 0, 0.2, 1)' } }; const responsiveConfig = { xs: { span: 24 }, sm: { span: 12 }, md: { span: 8 }, lg: { span: 6 }, xl: { span: 5 }, xxl: { span: 4 } }; 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 trendItemStyle = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 0', borderBottom: '1px solid #f0f0f0' }; const STAT_CARD_BASE_STYLE = { borderRadius: designTokens.borderRadius.large, border: 'none', boxShadow: designTokens.shadows.medium, background: '#fff', transition: `all ${designTokens.transitions.normal}`, overflow: 'hidden', cursor: 'pointer', height: '100%', animation: 'fadeInUp 0.6s ease-out backwards', borderLeft: '4px solid transparent' }; const STAT_ICON_CONTAINER_BASE = { width: 'clamp(40px, 8vw, 64px)', height: 'clamp(40px, 8vw, 64px)', borderRadius: designTokens.borderRadius.medium, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'clamp(20px, 4vw, 32px)', transition: `all ${designTokens.transitions.normal}`, flexShrink: 0 }; const NAV_BUTTON_BASE = { height: 'auto', padding: 'clamp(16px, 4vw, 24px) clamp(12px, 3vw, 20px)', borderRadius: designTokens.borderRadius.medium, border: '2px solid #f0f0f0', background: '#fff', transition: `all ${designTokens.transitions.normal}`, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'clamp(8px, 2vw, 12px)', cursor: 'pointer', boxShadow: designTokens.shadows.small, minWidth: '0' }; const NAV_ICON_CONTAINER_BASE = { width: 'clamp(44px, 10vw, 60px)', height: 'clamp(44px, 10vw, 60px)', borderRadius: designTokens.borderRadius.medium, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'clamp(20px, 5vw, 28px)', transition: `all ${designTokens.transitions.normal}`, flexShrink: 0 }; const createStatCardStyle = (color) => ({ ...STAT_CARD_BASE_STYLE, borderLeftColor: color }); const createStatIconContainer = (color) => ({ ...STAT_ICON_CONTAINER_BASE, background: `linear-gradient(135deg, ${color}20 0%, ${color}10 100%)` }); const createNavButtonStyle = (color) => ({ ...NAV_BUTTON_BASE, borderColor: color }); const createNavIconContainer = (color) => ({ ...NAV_ICON_CONTAINER_BASE, background: `linear-gradient(135deg, ${color}20 0%, ${color}10 100%)` }); const overviewCardStyle = { borderRadius: designTokens.borderRadius.large, border: 'none', boxShadow: designTokens.shadows.medium, background: '#fff', animation: 'fadeInUp 0.6s ease-out 0.2s backwards' }; const navigationGridStyle = { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))', gap: 'clamp(8px, 2vw, 16px)', marginBottom: '24px' }; const navButtonStyle = (color) => ({ height: 'auto', padding: '24px 20px', borderRadius: designTokens.borderRadius.medium, border: '2px solid #f0f0f0', background: '#fff', transition: `all ${designTokens.transitions.normal}`, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '12px', cursor: 'pointer', boxShadow: designTokens.shadows.small }); const navIconContainer = (color) => ({ width: '60px', height: '60px', borderRadius: designTokens.borderRadius.medium, display: 'flex', alignItems: 'center', justifyContent: 'center', background: `linear-gradient(135deg, ${color}20 0%, ${color}10 100%)`, fontSize: '28px', transition: `all ${designTokens.transitions.normal}` }); 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 systemInfoStyle = { background: 'linear-gradient(135deg, #f0f7ff 0%, #e6f7ff 100%)', borderRadius: designTokens.borderRadius.medium, padding: '20px', border: '1px solid #91d5ff' }; const quickStatsStyle = { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: '16px', marginBottom: '24px' }; 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 AnimatedCounter = ({ value, duration = 1500 }) => { const [displayValue, setDisplayValue] = useState(0); const animationRef = useRef(null); const startTimeRef = useRef(null); useEffect(() => { const animate = (currentTime) => { if (!startTimeRef.current) { startTimeRef.current = currentTime; } const elapsed = currentTime - startTimeRef.current; const progress = Math.min(elapsed / duration, 1); const easeOutQuart = 1 - Math.pow(1 - progress, 4); const currentValue = Math.floor(easeOutQuart * value); setDisplayValue(currentValue); if (progress < 1) { animationRef.current = requestAnimationFrame(animate); } }; animationRef.current = requestAnimationFrame(animate); return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [value, duration]); return {displayValue}; }; const CircularProgress = ({ percentage, size = 120, strokeWidth = 10, color, label }) => { const circumference = 2 * Math.PI * ((size - strokeWidth) / 2); const offset = circumference - (percentage / 100) * circumference; return (
{percentage}%
{label}
); }; const PowerGauge = ({ value, maxValue }) => { const percentage = Math.min((value / maxValue) * 100, 100); const getColor = () => { if (percentage >= 80) return designTokens.colors.error.main; if (percentage >= 60) return designTokens.colors.warning.main; return designTokens.colors.success.main; }; return (
功率使用率 {percentage.toFixed(1)}%
{value}W {maxValue}W
); }; const DeviceTrendChart = ({ data }) => { const maxValue = Math.max(...data.map(d => d.value)); const chartHeight = 120; return (
{data.map((item, index) => (
{item.label}
))}
); }; const StatusLegend = () => { const legends = [ { color: designTokens.colors.success.main, label: '运行中', percent: 60 }, { color: designTokens.colors.warning.main, label: '维护中', percent: 20 }, { color: designTokens.colors.error.main, label: '故障', percent: 10 }, { color: designTokens.colors.primary.main, label: '离线', percent: 10 } ]; return (
{legends.map((item, index) => (
{item.label}
{item.percent}%
))}
); }; const navButtonsData = [ { key: 'devices', icon: CloudServerOutlined, text: '设备管理', path: '/devices', color: designTokens.colors.primary.main }, { key: 'racks', icon: DatabaseOutlined, text: '资源规划', path: '/racks', color: designTokens.colors.purple.main }, { key: 'faults', icon: WarningOutlined, text: '故障监控', path: '/faults', color: designTokens.colors.warning.main }, { key: 'tickets', icon: BarChartOutlined, text: '工单管理', path: '/tickets', color: designTokens.colors.cyan.main }, { key: 'consumables', icon: AppstoreOutlined, text: '耗材管理', path: '/consumables', color: '#fa8c16' }, { key: 'settings', icon: SettingOutlined, text: '系统配置', path: '/settings', color: designTokens.colors.success.main } ]; function Dashboard() { const [stats, setStats] = useState({ totalDevices: 0, totalRacks: 0, totalRooms: 0, faultDevices: 0, deviceGrowth: 2.5, faultTrend: -12.3, onlineRate: 98.5, powerUsage: 0, totalUsers: 0, activeTickets: 0 }); const [loading, setLoading] = useState(true); const [hoveredCard, setHoveredCard] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); const [animatedKey, setAnimatedKey] = useState(0); const fetchStats = useCallback(async () => { try { setIsRefreshing(true); const [devicesRes, racksRes, roomsRes, usersRes, ticketsRes] = await Promise.all([ api.get('/devices', { params: { pageSize: 1 } }), api.get('/racks', { params: { pageSize: 1 } }), api.get('/rooms'), api.get('/users', { params: { pageSize: 1 } }), api.get('/tickets', { params: { pageSize: 1, status: 'open' } }) ]); const totalDevices = devicesRes.total || 0; const totalRacks = racksRes.total || 0; const rooms = roomsRes || []; const totalRooms = rooms.length; const totalUsers = usersRes.total || 0; const activeTickets = ticketsRes.total || 0; let faultDevices = 0; if (totalDevices > 0) { try { const faultRes = await api.get('/devices', { params: { status: 'fault', pageSize: 1 } }); faultDevices = faultRes.total || 0; } catch (error) { message.warning('获取故障设备数失败,使用默认值'); console.error('获取故障设备数失败:', error); faultDevices = 0; } } setStats({ 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 }); setAnimatedKey(prev => prev + 1); } catch (error) { message.error(`获取统计数据失败: ${error}`); console.error('获取统计数据失败:', error); } finally { setLoading(false); setIsRefreshing(false); } }, []); useEffect(() => { fetchStats(); }, [fetchStats]); const handleNavHover = (e, isEnter, buttonKey) => { if (isEnter) { setHoveredCard(buttonKey); } else { setHoveredCard(null); } }; const handleRefresh = useCallback(() => { fetchStats(); }, [fetchStats]); 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: designTokens.colors.purple.main, 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: designTokens.colors.cyan.main, 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 renderStatCard = useCallback((config) => { const { icon: Icon, color, statKey, title, trend, tagColor, customStatus, xs, sm, lg, xl, delay } = config; const colProps = { xs, sm, lg, xl }; const cardStyle = { ...createStatCardStyle(color), ...(hoveredCard === statKey ? { transform: 'translateY(-6px)', boxShadow: designTokens.shadows.hover } : {}), animationDelay: `${delay * 0.1}s` }; return ( setHoveredCard(statKey)} onMouseLeave={() => setHoveredCard(null)} styles={{ body: { padding: 'clamp(16px, 3vw, 24px)' } }} >
{title}
{loading ? ( ) : ( )}
{customStatus ? ( statKey === 'totalRacks' ? (
正常运行中
) : (
全部在线
) ) : (
0 ? designTokens.colors.success.main : designTokens.colors.error.main, flexWrap: 'wrap', gap: '4px' }}> {trend > 0 ? : } {Math.abs(trend)}% 环比
)}
); }, [stats, loading, hoveredCard, animatedKey]); const navButtons = useMemo(() => navButtonsData.map(({ key, icon: Icon, text, color }) => (
b.key === key) * 0.1}s` }} onMouseEnter={(e) => handleNavHover(e, true, `nav-${key}`)} onMouseLeave={(e) => handleNavHover(e, false, `nav-${key}`)} >
{text}
)), [hoveredCard]); const quickStats = useMemo(() => [ { icon: LineChartOutlined, label: '在线率', value: `${stats.onlineRate}%`, color: designTokens.colors.success.main }, { icon: SafetyOutlined, label: '安全等级', value: 'A级', color: designTokens.colors.primary.main }, { icon: ThunderboltOutlined, label: '功率使用', value: `${stats.powerUsage}W`, color: designTokens.colors.warning.main } ], [stats.onlineRate, stats.powerUsage]); 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 systemInfo = useMemo(() => (

系统版本: v1.0.0

最后更新:{new Date().toLocaleDateString()}

), [handleRefresh, isRefreshing]); 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 ( <>

IDC设备管理系统

实时监控 · 智能管理 · 高效运维

{statCards.map(renderStatCard)}
设备状态分布
{stats.totalDevices} 设备总数
系统健康指标
周设备趋势
周一 至 周日 设备变化趋势

欢迎使用IDC设备管理系统

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

{quickStats.map((stat, index) => (
{stat.label}
{stat.value}
))}
{systemInfo}
{navButtons}
); } export default React.memo(Dashboard);