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:
1 parent
3d7932c087
commit
722587ab84
14 files changed
+2026
-969
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user