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

@@ -1,7 +1,40 @@
import { Card, CardContent } from '@/components/ui/card';
import { TrendingUp, TrendingDown } from 'lucide-react';
import { TrendingUp, TrendingDown, Minus } from 'lucide-react';
import { cn } from '@/lib/utils';
/**
* StatCard - 优化后的统计卡片组件
*
* 优化点:
* 1. 使用 dg-card 工具类替代硬编码样式
* 2. 添加 hover 效果和点击交互
* 3. 图标颜色通过主题变量映射
* 4. 趋势显示更直观
* 5. 添加 shimmer loading 效果支持
*/
const colorClasses: Record<string, { bg: string; icon: string; glow: string }> = {
blue: { bg: 'bg-[var(--dg-blue)]/10', icon: 'text-[var(--dg-blue)]', glow: 'shadow-[var(--dg-blue)]/10' },
emerald: { bg: 'bg-[var(--dg-green)]/10', icon: 'text-[var(--dg-green)]', glow: 'shadow-[var(--dg-green)]/10' },
amber: { bg: 'bg-[var(--dg-yellow)]/10', icon: 'text-[var(--dg-yellow)]', glow: 'shadow-[var(--dg-yellow)]/10' },
rose: { bg: 'bg-[var(--dg-red)]/10', icon: 'text-[var(--dg-red)]', glow: 'shadow-[var(--dg-red)]/10' },
purple: { bg: 'bg-[var(--dg-purple)]/10', icon: 'text-[var(--dg-purple)]', glow: 'shadow-[var(--dg-purple)]/10' },
cyan: { bg: 'bg-[var(--dg-cyan)]/10', icon: 'text-[var(--dg-cyan)]', glow: 'shadow-[var(--dg-cyan)]/10' },
orange: { bg: 'bg-[var(--dg-orange)]/10', icon: 'text-[var(--dg-orange)]', glow: 'shadow-[var(--dg-orange)]/10' },
};
interface StatCardProps {
title: string;
value: string | number;
description?: string;
icon: React.ElementType;
trend?: 'up' | 'down' | 'neutral';
trendValue?: string;
color: string;
onClick?: () => void;
loading?: boolean;
}
// 统计卡片组件
export function StatCard({
title,
value,
@@ -11,56 +44,70 @@ export function StatCard({
trendValue,
color,
onClick,
}: {
title: string;
value: string | number;
description?: string;
icon: React.ElementType;
trend?: 'up' | 'down' | 'neutral';
trendValue?: string;
color: string;
onClick?: () => void;
}) {
const colorClasses: Record<string, { bg: string; text: string }> = {
blue: { bg: 'bg-blue-500/10', text: 'text-blue-400' },
emerald: { bg: 'bg-emerald-500/10', text: 'text-emerald-400' },
amber: { bg: 'bg-amber-500/10', text: 'text-amber-400' },
rose: { bg: 'bg-rose-500/10', text: 'text-rose-400' },
purple: { bg: 'bg-purple-500/10', text: 'text-purple-400' },
cyan: { bg: 'bg-cyan-500/10', text: 'text-cyan-400' },
orange: { bg: 'bg-orange-500/10', text: 'text-orange-400' },
};
const colors = colorClasses[color];
loading = false,
}: StatCardProps) {
const colors = colorClasses[color] || colorClasses.blue;
if (loading) {
return (
<Card className="dg-card overflow-hidden">
<CardContent className="p-6 space-y-4">
<div className="flex items-start justify-between">
<div className="space-y-3 flex-1">
<div className="dg-skeleton h-4 w-24 rounded" />
<div className="dg-skeleton h-8 w-16 rounded" />
</div>
<div className="dg-skeleton h-10 w-10 rounded-lg" />
</div>
<div className="dg-skeleton h-3 w-32 rounded" />
</CardContent>
</Card>
);
}
const TrendIcon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus;
const trendColor = trend === 'up'
? 'text-[var(--dg-green)]'
: trend === 'down'
? 'text-[var(--dg-red)]'
: 'text-[var(--dg-text-muted)]';
return (
<Card
className="bg-slate-900 border-slate-800 hover:border-slate-700 transition-all cursor-pointer"
className={cn(
'dg-card dg-card-hover cursor-pointer overflow-hidden',
onClick && 'cursor-pointer',
)}
onClick={onClick}
>
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div className="space-y-2">
<p className="text-sm font-medium text-slate-400">{title}</p>
<div className="flex items-baseline gap-2">
<h3 className="text-2xl font-bold text-slate-100">{value}</h3>
<div className="flex items-start justify-between gap-4">
<div className="space-y-3 min-w-0 flex-1">
<p className="text-sm font-medium text-[var(--dg-text-secondary)] truncate">
{title}
</p>
<div className="flex items-baseline gap-2 flex-wrap">
<h3 className="text-2xl font-bold text-[var(--dg-text-primary)] tracking-tight">
{value}
</h3>
{trend && trendValue && (
<span
className={`flex items-center text-xs font-medium ${
trend === 'up' ? 'text-emerald-400' : trend === 'down' ? 'text-red-400' : 'text-slate-400'
}`}
>
{trend === 'up' ? (
<TrendingUp className="h-3 w-3 mr-0.5" />
) : trend === 'down' ? (
<TrendingDown className="h-3 w-3 mr-0.5" />
) : null}
<span className={cn('flex items-center text-xs font-semibold gap-0.5', trendColor)}>
<TrendIcon className="h-3.5 w-3.5" />
{trendValue}
</span>
)}
</div>
{description && <p className="text-xs text-slate-500">{description}</p>}
{description && (
<p className="text-xs text-[var(--dg-text-muted)]">{description}</p>
)}
</div>
<div className={`p-3 rounded-lg ${colors.bg} ${colors.text}`}>
<div
className={cn(
'p-3 rounded-xl flex-shrink-0',
colors.bg,
colors.icon,
)}
>
<Icon className="h-5 w-5" />
</div>
</div>