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,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>
|
||||
|
||||
Reference in new issue
Block a user