feat: 实现动态详情页面和性能优化

- 添加案例、新闻、产品详情页面的E2E测试
- 优化详情页面的客户端组件和页面逻辑
- 添加高性能Docker配置和Nginx配置
- 更新API服务和常量配置
- 添加性能优化文档和任务进度更新
- 修复ESLint错误和类型问题
This commit is contained in:
张翔
2026-03-26 12:53:58 +08:00
parent 498bb3a3c8
commit 14448af731
18 changed files with 2244 additions and 913 deletions
+45 -18
View File
@@ -2,20 +2,56 @@
import { motion } from 'framer-motion';
import { useInView } from 'framer-motion';
import { useRef } from 'react';
import { useRef, useState, useEffect } from 'react';
import Link from 'next/link';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { TouchSwipe } from '@/components/ui/touch-swipe';
import { CASES } from '@/lib/constants';
import { ArrowRight, Building2, TrendingUp } from 'lucide-react';
import { contentService } from '@/lib/api/services';
import { ArrowRight, Building2 } from 'lucide-react';
interface CaseItem {
id: string;
title: string;
excerpt: string;
category: string;
slug: string;
}
export function CasesSection() {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
const [cases, setCases] = useState<CaseItem[]>([]);
const [loading, setLoading] = useState(true);
const featuredCases = CASES.slice(0, 3);
useEffect(() => {
const fetchCases = async () => {
try {
const casesData = await contentService.getCases(3);
setCases(casesData);
} catch (error) {
console.error('Failed to fetch cases:', error);
} finally {
setLoading(false);
}
};
fetchCases();
}, []);
if (loading) {
return (
<section id="cases" role="region" aria-labelledby="cases-heading" className="py-24 bg-white relative overflow-hidden">
<div className="container-wide relative z-10">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#C41E3A] mx-auto mb-4" />
<p className="text-[#5C5C5C]">...</p>
</div>
</div>
</section>
);
}
return (
<section id="cases" role="region" aria-labelledby="cases-heading" className="py-24 bg-white relative overflow-hidden" ref={ref}>
@@ -39,49 +75,40 @@ export function CasesSection() {
<TouchSwipe
onSwipeLeft={() => {
// 切换到下一个案例
}}
onSwipeRight={() => {
// 切换到上一个案例
}}
className="md:hidden"
>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{featuredCases.map((caseItem, index) => (
{cases.map((caseItem, index) => (
<motion.div
key={caseItem.id}
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, delay: 0.1 + index * 0.1 }}
>
<Link href={`/cases/${caseItem.id}`}>
<Link href={`/cases/${caseItem.slug}`}>
<Card className="h-full group cursor-pointer border-[#E5E5E5] hover:border-[#C41E3A] transition-colors overflow-hidden">
<div className="relative h-40 bg-gradient-to-br from-[#F5F5F5] to-[#E5E5E5] flex items-center justify-center">
<Building2 className="w-16 h-16 text-[#C41E3A]/20 group-hover:scale-110 transition-transform duration-300" />
<div className="absolute top-4 right-4">
<Badge className="bg-white/90 text-[#1C1C1C] hover:bg-white">
{caseItem.industry}
{caseItem.category}
</Badge>
</div>
</div>
<CardContent className="p-6">
<div className="flex items-center gap-2 mb-3">
<Building2 className="w-4 h-4 text-[#C41E3A]" />
<span className="text-sm text-[#5C5C5C]">{caseItem.client}</span>
<span className="text-sm text-[#5C5C5C]"></span>
</div>
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-3 group-hover:text-[#C41E3A] transition-colors">
{caseItem.title}
</h3>
<p className="text-[#5C5C5C] text-sm line-clamp-2 mb-4">
{caseItem.description}
{caseItem.excerpt}
</p>
{caseItem.results.length > 0 && caseItem.results[0] && (
<div className="flex items-center gap-2 text-[#C41E3A]">
<TrendingUp className="w-4 h-4" />
<span className="text-sm font-medium">{caseItem.results[0].value}</span>
<span className="text-sm text-[#5C5C5C]">{caseItem.results[0].label}</span>
</div>
)}
</CardContent>
</Card>
</Link>