feat: add testimonials section with testimonial cards
This commit is contained in:
@@ -12,6 +12,7 @@ import { AboutSection } from "@/components/sections/about-section";
|
|||||||
import { ServicesSection } from "@/components/sections/services-section";
|
import { ServicesSection } from "@/components/sections/services-section";
|
||||||
import { ProductsSection } from "@/components/sections/products-section";
|
import { ProductsSection } from "@/components/sections/products-section";
|
||||||
import { NewsSection } from "@/components/sections/news-section";
|
import { NewsSection } from "@/components/sections/news-section";
|
||||||
|
import { TestimonialsSection } from "@/components/sections/testimonials-section";
|
||||||
import { ContactSection } from "@/components/sections/contact-section";
|
import { ContactSection } from "@/components/sections/contact-section";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
@@ -28,6 +29,7 @@ export default function HomePage() {
|
|||||||
<ServicesSection />
|
<ServicesSection />
|
||||||
<ProductsSection />
|
<ProductsSection />
|
||||||
<NewsSection />
|
<NewsSection />
|
||||||
|
<TestimonialsSection />
|
||||||
<ContactSection />
|
<ContactSection />
|
||||||
<Footer />
|
<Footer />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState, useRef } from 'react';
|
||||||
|
import { TestimonialCard } from '@/components/ui/testimonial-card';
|
||||||
|
|
||||||
|
interface Testimonial {
|
||||||
|
id: string;
|
||||||
|
quote: string;
|
||||||
|
author: string;
|
||||||
|
position: string;
|
||||||
|
company: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
rating?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOCK_TESTIMONIALS: Testimonial[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
quote: '睿新致远的团队非常专业,他们不仅理解我们的业务需求,还能提供超出预期的技术解决方案。数字化转型后,我们的运营效率提升了40%。',
|
||||||
|
author: '张总',
|
||||||
|
position: '总经理',
|
||||||
|
company: '某制造企业',
|
||||||
|
avatarUrl: '/testimonials/avatar-1.jpg',
|
||||||
|
rating: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
quote: '选择睿新致远是我们最正确的决定。他们的数据中台解决方案帮助我们实现了数据资产的统一管理,决策效率大幅提升。',
|
||||||
|
author: '李经理',
|
||||||
|
position: '信息部经理',
|
||||||
|
company: '某零售集团',
|
||||||
|
avatarUrl: '/testimonials/avatar-2.jpg',
|
||||||
|
rating: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
quote: '从需求分析到系统上线,睿新致远的团队都表现出极高的专业素养。他们的ERP系统让我们的业务流程更加标准化、透明化。',
|
||||||
|
author: '王总监',
|
||||||
|
position: '运营总监',
|
||||||
|
company: '某物流企业',
|
||||||
|
avatarUrl: '/testimonials/avatar-3.jpg',
|
||||||
|
rating: 5,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function TestimonialsSection() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const sectionRef = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sectionRef.current) {
|
||||||
|
observer.observe(sectionRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="testimonials"
|
||||||
|
ref={sectionRef}
|
||||||
|
className="py-24 bg-white"
|
||||||
|
>
|
||||||
|
<div className="container-wide">
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
text-center mb-16
|
||||||
|
opacity-0 translate-y-4
|
||||||
|
${isVisible ? 'animate-fade-in-up' : ''}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-semibold text-[#171717] mb-4">
|
||||||
|
客户评价
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-[#737373] max-w-2xl mx-auto">
|
||||||
|
听听我们的客户怎么说
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8
|
||||||
|
opacity-0 translate-y-4
|
||||||
|
${isVisible ? 'animate-fade-in-up stagger-1' : ''}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{MOCK_TESTIMONIALS.map((testimonial) => (
|
||||||
|
<TestimonialCard key={testimonial.id} {...testimonial} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Quote } from 'lucide-react';
|
||||||
|
|
||||||
|
export interface TestimonialCardProps {
|
||||||
|
quote: string;
|
||||||
|
author: string;
|
||||||
|
position: string;
|
||||||
|
company: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
rating?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TestimonialCard({
|
||||||
|
quote,
|
||||||
|
author,
|
||||||
|
position,
|
||||||
|
company,
|
||||||
|
avatarUrl,
|
||||||
|
rating = 5,
|
||||||
|
}: TestimonialCardProps) {
|
||||||
|
return (
|
||||||
|
<div className="relative p-8 rounded-lg border border-[#E5E5E5]/50 bg-white hover:shadow-md transition-shadow">
|
||||||
|
<Quote className="absolute top-6 right-6 w-8 h-8 text-[#C41E3A]/10" />
|
||||||
|
|
||||||
|
{rating > 0 && (
|
||||||
|
<div className="flex gap-1 mb-4">
|
||||||
|
{Array.from({ length: rating }).map((_, i) => (
|
||||||
|
<svg
|
||||||
|
key={i}
|
||||||
|
className="w-4 h-4 text-[#C41E3A]"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||||
|
</svg>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<blockquote className="text-base text-[#171717] mb-6 leading-relaxed">
|
||||||
|
"{quote}"
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{avatarUrl && (
|
||||||
|
<img
|
||||||
|
src={avatarUrl}
|
||||||
|
alt={author}
|
||||||
|
className="w-12 h-12 rounded-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-[#171717]">{author}</div>
|
||||||
|
<div className="text-sm text-[#737373]">
|
||||||
|
{position} · {company}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user