feat(perf,ux): implement performance and UX optimizations

Phase 2: Performance Optimizations
- Implement dynamic imports for non-critical sections
- Add loading skeletons for lazy-loaded components
- Optimize bundle size with code splitting
- Enable SSR for dynamic components

Phase 3: UX Optimizations
- Create ErrorBoundary component for graceful error handling
- Add Toast notification component for user feedback
- Implement success/error notifications in contact form
- Add error handling with user-friendly messages

Files modified:
- src/app/(marketing)/page.tsx: Dynamic imports for sections
- src/app/(marketing)/layout.tsx: Error boundary integration
- src/components/sections/contact-section.tsx: Toast notifications
- src/components/ui/error-boundary.tsx: New error boundary component
- src/components/ui/toast.tsx: New toast notification component

Impact:
- Reduced initial bundle size
- Faster page load times
- Better error handling
- Improved user feedback
- Enhanced user experience
This commit is contained in:
张翔
2026-02-24 00:44:40 +08:00
parent 016b7cfb91
commit 37a86bfaf7
5 changed files with 202 additions and 10 deletions
+66
View File
@@ -0,0 +1,66 @@
'use client';
import { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="flex items-center justify-center min-h-[400px] p-8">
<div className="text-center max-w-md">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg
className="w-8 h-8 text-red-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
</div>
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-4"></h2>
<p className="text-[#5C5C5C] mb-6">
</p>
<button
onClick={() => this.setState({ hasError: false, error: undefined })}
className="px-6 py-2.5 bg-[#C41E3A] text-white rounded-lg hover:bg-[#A01830] transition-colors"
>
</button>
</div>
</div>
);
}
return this.props.children;
}
}
+64
View File
@@ -0,0 +1,64 @@
'use client';
import { useEffect, useState } from 'react';
import { CheckCircle2, X, AlertCircle, Info } from 'lucide-react';
interface ToastProps {
message: string;
type?: 'success' | 'error' | 'info';
duration?: number;
onClose: () => void;
}
export function Toast({
message,
type = 'success',
duration = 3000,
onClose
}: ToastProps) {
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
const timer = setTimeout(() => {
setIsVisible(false);
setTimeout(onClose, 300);
}, duration);
return () => clearTimeout(timer);
}, [duration, onClose]);
const icons = {
success: <CheckCircle2 className="h-5 w-5 text-green-500" />,
error: <AlertCircle className="h-5 w-5 text-red-500" />,
info: <Info className="h-5 w-5 text-blue-500" />
};
const bgColors = {
success: 'bg-green-50 border-green-200',
error: 'bg-red-50 border-red-200',
info: 'bg-blue-50 border-blue-200'
};
return (
<div
className={`fixed bottom-4 right-4 z-50 flex items-center gap-3 px-4 py-3 bg-white rounded-lg shadow-lg border transition-all duration-300 ${
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'
} ${bgColors[type]}`}
role="alert"
aria-live="polite"
>
{icons[type]}
<p className="text-sm font-medium text-[#1C1C1C]">{message}</p>
<button
onClick={() => {
setIsVisible(false);
setTimeout(onClose, 300);
}}
className="ml-2 text-gray-400 hover:text-gray-600 transition-colors"
aria-label="关闭提示"
>
<X className="h-4 w-4" />
</button>
</div>
);
}