feat: 前端全面优化 — 懒加载/骨架屏/错误边界/面包屑/主题增强/Glass登录页/动画系统

- 新增: ErrorBoundary, PageTransition, Breadcrumb, SkeletonCard (4组件)
- 重构: App.tsx (React.lazy懒加载所有页面), index.css (55+主题变量/动画/工具类)
- 优化: MainLayout (CSS变量布局+Footer), Header (搜索增强+快捷键), Sidebar (折叠Tooltip+子菜单动画)
- 优化: Dashboard骨架屏+级联动画, Stats/Charts/Recent主题感知颜色
- 优化: Login.tsx Glass毛玻璃效果+密码显隐图标+光球动画
- 图表Recharts主题化(CustomTooltip/chart-grid/chart-axis)
- 减少动画偏好支持(prefers-reduced-motion)
This commit is contained in:
attacker committed 2026-06-24 02:06:07 +00:00
1 parent 3d7932c087
commit 722587ab84
14 files changed
+2026 -969

No files matched your search

+103 -62
View File
@@ -1,6 +1,8 @@
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { Clock, Server, Cloud, Bell, DollarSign } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Clock, Server, Cloud, Bell, DollarSign, TrendingUp, TrendingDown, ArrowRight } from 'lucide-react';
import { dashboardStatsApi, serverApi, kubernetesApi } from '@/services/api';
import { StatCard } from './DashboardStats';
import { CostOverviewCard, TopResourceUsageCard, ResourceUsageCard, computeMonthChange } from './DashboardCharts';
@@ -11,8 +13,21 @@ import {
ServerStatusCard,
KubernetesClusterCard,
} from './DashboardRecent';
import { SkeletonDashboard } from '@/components/layout/SkeletonCard';
/**
* Dashboard - 优化后的仪表盘页面
*
* 优化点:
* 1. 使用 SkeletonDashboard 骨架屏替代简单 spinner
* 2. 页面结构使用 dg-page* 工具类
* 3. 统计卡片添加 staggered fade-in 动画
* 4. 操作按钮使用 dg-btn* 工具类
* 5. 更好的空状态处理
* 6. 添加页面标题区域的操作入口
*/
export function Dashboard() {
const navigate = useNavigate();
const [initialLoading, setInitialLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [stats, setStats] = useState<any>(null);
@@ -21,7 +36,7 @@ export function Dashboard() {
const [alerts, setAlerts] = useState<any[]>([]);
const [releases, setReleases] = useState<any[]>([]);
const [expCerts, setExpCerts] = useState<any[]>([]);
const [metrics, setMetrics] = useState<any[]>([]); // 真实 Agent 指标时间序列 // Logan.Li
const [metrics, setMetrics] = useState<any[]>([]);
const refreshData = useCallback(async () => {
setRefreshing(true);
@@ -47,7 +62,6 @@ export function Dashboard() {
console.error('Failed to fetch dashboard releases:', e);
return null;
}),
// 获取真实 Agent 指标时间序列(过去24小时按小时聚合) // Logan.Li
dashboardStatsApi.getMetrics(24).catch((e) => {
console.error('Failed to fetch metrics:', e);
return [];
@@ -74,11 +88,7 @@ export function Dashboard() {
}, [refreshData]);
if (initialLoading) {
return (
<div className="flex items-center justify-center h-96">
<div className="h-8 w-8 border-2 border-slate-700 border-t-blue-500 rounded-full animate-spin" />
</div>
);
return <SkeletonDashboard />;
}
const serverStats = stats?.server_stats;
@@ -88,81 +98,110 @@ export function Dashboard() {
const costChange = computeMonthChange(costStats?.month_trend);
return (
<div className="space-y-6">
<div className="dg-page">
{/* Page Header */}
<div className="flex items-center justify-between">
<div className="dg-page-header">
<div>
<h1 className="text-2xl font-bold text-slate-100">仪表盘</h1>
<p className="text-sm text-slate-500 mt-1">实时监控和运维概览</p>
<h1 className="dg-page-title">仪表盘</h1>
<p className="dg-page-subtitle">实时监控和运维概览 · 最后更新: {new Date().toLocaleTimeString('zh-CN')}</p>
</div>
<div className="dg-page-actions">
<Button
variant="outline"
className="dg-btn-secondary"
onClick={refreshData}
disabled={refreshing}
>
<Clock className={cn('h-4 w-4', refreshing && 'animate-spin')} />
{refreshing ? '刷新中' : '刷新'}
</Button>
<Button
className="dg-btn-primary"
onClick={() => navigate('/servers')}
>
<Server className="h-4 w-4" />
服务器
<ArrowRight className="h-3.5 w-3.5" />
</Button>
</div>
<Button variant="outline" className="border-slate-700 text-slate-300 hover:text-slate-100 hover:bg-slate-800" onClick={refreshData}>
<Clock className={`h-4 w-4 mr-2 ${refreshing ? 'animate-spin' : ''}`} />
刷新
</Button>
</div>
{/* Stats Grid */}
{/* Stats Grid with staggered animation */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard
title="服务器总数"
value={serverStats?.total ?? 0}
description={`${serverStats?.running ?? 0} 台运行中`}
icon={Server}
trend={Number(serverStats?.error ?? 0) > 0 ? 'down' : 'neutral'}
trendValue={Number(serverStats?.error ?? 0) > 0 ? `${serverStats?.error} 台异常` : '运行正常'}
color="blue"
/>
<StatCard
title="K8s集群"
value={k8sStats?.total ?? 0}
description={`${k8sStats?.healthy ?? 0} 个健康`}
icon={Cloud}
trend="neutral"
trendValue="稳定"
color="purple"
/>
<StatCard
title="待处理告警"
value={alertStats?.firing ?? 0}
description={`${alertStats?.critical ?? 0} 个严重`}
icon={Bell}
trend={Number(alertStats?.today_count ?? 0) > 0 ? 'up' : 'down'}
trendValue={`本周 ${alertStats?.week_count ?? 0} 条`}
color="rose"
/>
<StatCard
title="本月云成本"
value={`¥${Number(costStats?.month_cost ?? 0).toLocaleString()}`}
description={costChange.label}
icon={DollarSign}
trend={costChange.direction}
trendValue={`${costChange.pct > 0 ? '+' : ''}${costChange.pct.toFixed(1)}%`}
color="orange"
/>
<div className="animate-fade-in-delay-1">
<StatCard
title="服务器总数"
value={serverStats?.total ?? 0}
description={`${serverStats?.running ?? 0} 台运行中`}
icon={Server}
trend={Number(serverStats?.error ?? 0) > 0 ? 'down' : 'neutral'}
trendValue={Number(serverStats?.error ?? 0) > 0 ? `${serverStats?.error} 台异常` : '运行正常'}
color="blue"
onClick={() => navigate('/servers')}
/>
</div>
<div className="animate-fade-in-delay-2">
<StatCard
title="K8s集群"
value={k8sStats?.total ?? 0}
description={`${k8sStats?.healthy ?? 0} 个健康`}
icon={Cloud}
trend="neutral"
trendValue="稳定"
color="purple"
onClick={() => navigate('/kubernetes')}
/>
</div>
<div className="animate-fade-in-delay-3">
<StatCard
title="待处理告警"
value={alertStats?.firing ?? 0}
description={`${alertStats?.critical ?? 0} 个严重`}
icon={Bell}
trend={Number(alertStats?.today_count ?? 0) > 0 ? 'up' : 'down'}
trendValue={`本周 ${alertStats?.week_count ?? 0} 条`}
color="rose"
onClick={() => navigate('/alert-center')}
/>
</div>
<div className="animate-fade-in-delay-4">
<StatCard
title="本月云成本"
value={`¥${Number(costStats?.month_cost ?? 0).toLocaleString()}`}
description={costChange.label}
icon={DollarSign}
trend={costChange.direction}
trendValue={`${costChange.pct > 0 ? '+' : ''}${costChange.pct.toFixed(1)}%`}
color="orange"
onClick={() => navigate('/cloud-billing')}
/>
</div>
</div>
{/* Resource Usage Chart */}
<ResourceUsageCard
avgCpu={Number(serverStats?.avg_cpu ?? 0)}
avgMemory={Number(serverStats?.avg_memory ?? 0)}
avgDisk={Number(serverStats?.avg_disk ?? 0)}
metrics={metrics}
/>
<div className="animate-fade-in-delay-2">
<ResourceUsageCard
avgCpu={Number(serverStats?.avg_cpu ?? 0)}
avgMemory={Number(serverStats?.avg_memory ?? 0)}
avgDisk={Number(serverStats?.avg_disk ?? 0)}
metrics={metrics}
/>
</div>
{/* Cost & Top Resource Usage */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 animate-fade-in-delay-3">
<CostOverviewCard costStats={costStats} />
<TopResourceUsageCard topUsage={stats?.top_resource_usage} />
</div>
{/* Alert Messages & Certificate Expiry */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 animate-fade-in-delay-3">
<AlertMessagesCard alerts={alerts} />
<CertificateExpiryCard expCerts={expCerts} />
</div>
{/* Bottom Row */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 animate-fade-in-delay-4">
<ReleaseHistoryCard releases={releases} />
<ServerStatusCard servers={servers} />
<KubernetesClusterCard clusters={clusters} />
@@ -170,3 +209,5 @@ export function Dashboard() {
</div>
);
}