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

@@ -0,0 +1,133 @@
import { Component, type ErrorInfo, type ReactNode } from 'react';
import { Button } from '@/components/ui/button';
import { AlertTriangle, RotateCcw, Home, Bug } from 'lucide-react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
}
/**
* ErrorBoundary - 全局错误边界组件
*
* 捕获 React 组件树中的错误,防止整个应用崩溃
* 提供友好的错误提示和恢复操作
*/
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error, errorInfo: null };
}
override componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('[ErrorBoundary] 捕获到错误:', error);
console.error('[ErrorBoundary] 错误信息:', errorInfo.componentStack);
this.setState({ error, errorInfo });
}
handleRetry = () => {
this.setState({ hasError: false, error: null, errorInfo: null });
};
handleGoHome = () => {
window.location.href = '/';
};
handleReport = () => {
const { error, errorInfo } = this.state;
const body = `## 错误报告\n\n**时间:** ${new Date().toISOString()}\n**URL:** ${window.location.href}\n**用户代理:** ${navigator.userAgent}\n\n### 错误信息\n\n\`\`\`\n${error?.name}: ${error?.message}\n\`\`\`\n\n### 组件堆栈\n\n\`\`\`\n${errorInfo?.componentStack}\n\`\`\`\n`;
navigator.clipboard.writeText(body).then(() => {
alert('错误信息已复制到剪贴板,请粘贴到 Issue 中');
});
};
override render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="min-h-[60vh] flex items-center justify-center p-6">
<div className="dg-card max-w-lg w-full p-8 text-center space-y-6">
{/* Error Icon */}
<div className="mx-auto w-16 h-16 rounded-2xl bg-red-500/10 flex items-center justify-center">
<AlertTriangle className="h-8 w-8 text-red-400" />
</div>
{/* Title */}
<div>
<h2 className="text-xl font-bold text-[var(--dg-text-primary)]">
页面出现错误
</h2>
<p className="text-sm text-[var(--dg-text-muted)] mt-2">
抱歉,该页面遇到了意外错误。您可以尝试重新加载或返回首页。
</p>
</div>
{/* Error Detail (collapsible) */}
{this.state.error && (
<div className="text-left">
<details className="group">
<summary className="cursor-pointer text-xs text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)] transition-colors flex items-center gap-1.5">
<Bug className="h-3.5 w-3.5" />
查看错误详情
</summary>
<div className="mt-2 p-3 rounded-lg bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] overflow-auto max-h-48">
<code className="text-xs text-red-400 font-mono block whitespace-pre-wrap">
{this.state.error.name}: {this.state.error.message}
</code>
{this.state.errorInfo && (
<pre className="text-xs text-[var(--dg-text-muted)] mt-2 whitespace-pre-wrap">
{this.state.errorInfo.componentStack}
</pre>
)}
</div>
</details>
</div>
)}
{/* Actions */}
<div className="flex items-center justify-center gap-3">
<Button
variant="outline"
className="dg-btn-secondary"
onClick={this.handleGoHome}
>
<Home className="h-4 w-4 mr-1.5" />
返回首页
</Button>
<Button
className="dg-btn-primary"
onClick={this.handleRetry}
>
<RotateCcw className="h-4 w-4 mr-1.5" />
重新加载
</Button>
<Button
variant="outline"
className="dg-btn-ghost"
onClick={this.handleReport}
>
<Bug className="h-4 w-4 mr-1.5" />
复制错误
</Button>
</div>
</div>
</div>
);
}
return this.props.children;
}
}