dev #8
@@ -9,41 +9,57 @@ import type { ReactNode } from 'react';
|
||||
|
||||
const ServicesSection = dynamic(
|
||||
() => import('@/components/sections/services-section').then(mod => ({ default: mod.ServicesSection })),
|
||||
{
|
||||
{
|
||||
loading: () => <SectionSkeleton />,
|
||||
ssr: false
|
||||
ssr: false
|
||||
}
|
||||
);
|
||||
|
||||
const HomeSolutionsSection = dynamic(
|
||||
() => import('@/components/sections/home-solutions-section').then(mod => ({ default: mod.HomeSolutionsSection })),
|
||||
{
|
||||
loading: () => <SectionSkeleton />,
|
||||
ssr: false
|
||||
}
|
||||
);
|
||||
|
||||
const ProductsSection = dynamic(
|
||||
() => import('@/components/sections/products-section').then(mod => ({ default: mod.ProductsSection })),
|
||||
{
|
||||
{
|
||||
loading: () => <SectionSkeleton />,
|
||||
ssr: false
|
||||
ssr: false
|
||||
}
|
||||
);
|
||||
|
||||
const CasesSection = dynamic(
|
||||
() => import('@/components/sections/cases-section').then(mod => ({ default: mod.CasesSection })),
|
||||
{
|
||||
{
|
||||
loading: () => <SectionSkeleton />,
|
||||
ssr: false
|
||||
ssr: false
|
||||
}
|
||||
);
|
||||
|
||||
const AboutSection = dynamic(
|
||||
() => import('@/components/sections/about-section').then(mod => ({ default: mod.AboutSection })),
|
||||
{
|
||||
{
|
||||
loading: () => <SectionSkeleton />,
|
||||
ssr: false
|
||||
ssr: false
|
||||
}
|
||||
);
|
||||
|
||||
const TeamSection = dynamic(
|
||||
() => import('@/components/sections/team-section').then(mod => ({ default: mod.TeamSection })),
|
||||
{
|
||||
loading: () => <SectionSkeleton />,
|
||||
ssr: false
|
||||
}
|
||||
);
|
||||
|
||||
const NewsSection = dynamic(
|
||||
() => import('@/components/sections/news-section').then(mod => ({ default: mod.NewsSection })),
|
||||
{
|
||||
{
|
||||
loading: () => <SectionSkeleton />,
|
||||
ssr: false
|
||||
ssr: false
|
||||
}
|
||||
);
|
||||
|
||||
@@ -83,9 +99,11 @@ function HomeContent({ heroStats }: { heroStats: ReactNode }) {
|
||||
<main className="min-h-screen bg-white dark:bg-(--color-bg-primary)">
|
||||
<HeroSection heroStats={heroStats} />
|
||||
<ServicesSection />
|
||||
<HomeSolutionsSection />
|
||||
<ProductsSection />
|
||||
<CasesSection />
|
||||
<AboutSection />
|
||||
<TeamSection />
|
||||
<NewsSection />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PageHeader } from '@/components/ui/page-header';
|
||||
import { ArrowRight, Lightbulb, Cpu, Users, CheckCircle2 } from 'lucide-react';
|
||||
import { MethodologySection } from '@/components/sections/methodology-section';
|
||||
|
||||
export default function SolutionsPage() {
|
||||
const contentRef = useRef(null);
|
||||
@@ -236,6 +237,8 @@ export default function SolutionsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MethodologySection />
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PageHeader } from '@/components/ui/page-header';
|
||||
import { Shield, Building2, Users, Code, Target, ArrowRight } from 'lucide-react';
|
||||
|
||||
const TEAM_PILLARS = [
|
||||
{
|
||||
icon: Shield,
|
||||
title: '12年+ 行业深耕',
|
||||
description: '核心团队长期从事技术咨询、企业数字化等领域,服务覆盖金融、制造、零售、政务、农业等多个行业,积累了丰富的跨行业经验和最佳实践。',
|
||||
},
|
||||
{
|
||||
icon: Building2,
|
||||
title: '大型 IT 企业背景',
|
||||
description: '开发团队成员来自多个大型传统 IT 企业,具备扎实的工程能力、规范化的交付流程和严格的质量意识,确保每一个项目都经得起考验。',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: '复合型技术团队',
|
||||
description: '我们的团队成员既懂技术又懂业务,能够深入理解客户的真实场景和痛点,提供真正可落地的解决方案,而非纸上谈兵。',
|
||||
},
|
||||
{
|
||||
icon: Code,
|
||||
title: '全栈技术能力',
|
||||
description: '团队掌握从前端到后端、从云原生到数据智能、从移动端到物联网的全栈技术能力,能够应对各种复杂的技术挑战。',
|
||||
},
|
||||
{
|
||||
icon: Target,
|
||||
title: '结果导向交付',
|
||||
description: '我们不以"项目上线"为终点,而是以"客户业务是否真正改善"为衡量标准。每一个交付成果,都追求可量化的业务价值。',
|
||||
},
|
||||
];
|
||||
|
||||
export function TeamClient() {
|
||||
const contentRef = useRef(null);
|
||||
const isContentInView = useInView(contentRef, { once: true, margin: '-100px' });
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<PageHeader
|
||||
title="核心团队"
|
||||
description="核心团队从事技术咨询、企业数字化等行业 12 年+,开发团队成员来自于多个大型传统 IT 企业"
|
||||
/>
|
||||
|
||||
<div ref={contentRef} className="container-wide py-12 md:py-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="max-w-5xl mx-auto"
|
||||
>
|
||||
{/* 团队概述 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="bg-[#FFFBF5] rounded-2xl p-8 md:p-12 border border-[#E5E5E5] mb-16"
|
||||
>
|
||||
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-6 text-center">关于我们的团队</h2>
|
||||
<div className="space-y-4 max-w-3xl mx-auto text-center">
|
||||
<p className="text-[#5C5C5C] leading-relaxed">
|
||||
我们的核心团队长期从事<span className="text-[#C41E3A] font-medium">技术咨询</span>、<span className="text-[#C41E3A] font-medium">企业数字化</span>等行业,拥有 12 年以上的深厚积累。
|
||||
</p>
|
||||
<p className="text-[#5C5C5C] leading-relaxed">
|
||||
开发团队成员来自于多个<span className="text-[#C41E3A] font-medium">大型传统 IT 企业</span>,具备扎实的工程能力和规范化的交付经验。
|
||||
</p>
|
||||
<p className="text-[#5C5C5C] leading-relaxed">
|
||||
我们相信,优秀的技术咨询不仅需要过硬的技术能力,更需要深入理解客户的业务场景和真实需求。
|
||||
每一位成员都是既懂技术又懂业务的复合型人才。
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 团队优势 */}
|
||||
<div className="mb-16">
|
||||
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-8 text-center">团队优势</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{TEAM_PILLARS.map((item, idx) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<motion.div
|
||||
key={item.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: 0.2 + idx * 0.1 }}
|
||||
className={idx >= 3 ? 'md:col-span-1 lg:col-start-1' : ''}
|
||||
>
|
||||
<div className="bg-white rounded-xl p-6 border border-[#E5E5E5] hover:border-[#C41E3A]/30 hover:shadow-md transition-all duration-300 h-full">
|
||||
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center mb-4">
|
||||
<Icon className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-[#1C1C1C] mb-2">{item.title}</h3>
|
||||
<p className="text-sm text-[#5C5C5C] leading-relaxed">{item.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
className="text-center"
|
||||
>
|
||||
<p className="text-lg text-[#5C5C5C] mb-6">想与我们的团队交流?</p>
|
||||
<Button size="lg" asChild>
|
||||
<StaticLink href="/contact">
|
||||
联系我们
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { TeamClient } from './client';
|
||||
|
||||
export const metadata = {
|
||||
title: `核心团队 - ${COMPANY_INFO.name}`,
|
||||
description: `了解${COMPANY_INFO.name}的核心团队。我们的团队成员拥有丰富的行业经验和技术专长,致力于为客户提供专业的数字化转型服务。`,
|
||||
};
|
||||
|
||||
export default function TeamPage() {
|
||||
return <TeamClient />;
|
||||
}
|
||||
@@ -36,7 +36,7 @@ function HeaderContent() {
|
||||
|
||||
if (pathname === '/' && !isScrollingRef.current) {
|
||||
const scrollPosition = window.scrollY + 100;
|
||||
const sections = ['home', 'services', 'products', 'cases', 'about', 'news'];
|
||||
const sections = ['home', 'services', 'solutions', 'products', 'cases', 'about', 'news'];
|
||||
|
||||
for (const sectionId of sections) {
|
||||
const element = document.getElementById(sectionId);
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowRight, Lightbulb, Cpu, Users } from 'lucide-react';
|
||||
|
||||
const SOLUTIONS_OVERVIEW = [
|
||||
{
|
||||
icon: Lightbulb,
|
||||
title: '参谋伙伴',
|
||||
subtitle: '数字化转型咨询',
|
||||
description: '帮您看清前路,迈对第一步。用行业智慧洞察趋势,用理性分析避开陷阱。',
|
||||
points: ['行业趋势洞察报告', '成熟度评估', '实施路径规划'],
|
||||
},
|
||||
{
|
||||
icon: Cpu,
|
||||
title: '技术伙伴',
|
||||
subtitle: '信息技术解决方案',
|
||||
description: '让技术真正为业务服务。不追逐"最火"的技术,只选择"最对"的技术。',
|
||||
points: ['业务场景调研', '技术方案定制', '敏捷交付迭代'],
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: '同行伙伴',
|
||||
subtitle: '长期陪跑服务',
|
||||
description: '交付只是开始,陪伴才是常态。项目上线那天,是我们真正成为伙伴的开始。',
|
||||
points: ['专属客户成功经理', '季度业务复盘', '7×24小时响应'],
|
||||
},
|
||||
];
|
||||
|
||||
export function HomeSolutionsSection() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: '-100px' });
|
||||
|
||||
return (
|
||||
<section id="solutions" role="region" aria-labelledby="solutions-heading" className="py-24 bg-[#F5F7FA] relative overflow-hidden" ref={ref}>
|
||||
<div className="absolute top-1/2 right-0 w-[400px] h-[400px] bg-[rgba(196,30,58,0.02)] rounded-full blur-3xl" />
|
||||
<div className="container-wide relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<h2 id="solutions-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
三种角色,一种<span className="text-[#C41E3A] font-calligraphy">身份</span>
|
||||
</h2>
|
||||
<p className="text-lg text-[#5C5C5C]">
|
||||
您的数字化转型成长伙伴——从战略咨询到技术落地,再到长期陪跑
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{SOLUTIONS_OVERVIEW.map((item, idx) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<motion.div
|
||||
key={item.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: 0.1 + idx * 0.15 }}
|
||||
>
|
||||
<div className="bg-white rounded-2xl p-8 border border-[#E5E5E5] hover:border-[#C41E3A]/30 hover:shadow-lg transition-all duration-300 h-full flex flex-col">
|
||||
<div className="w-14 h-14 bg-[#C41E3A] rounded-2xl flex items-center justify-center mb-6">
|
||||
<Icon className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-[#1C1C1C] mb-1">{item.title}</h3>
|
||||
<p className="text-sm text-[#C41E3A] font-medium mb-3">{item.subtitle}</p>
|
||||
<p className="text-sm text-[#5C5C5C] leading-relaxed mb-6 flex-1">{item.description}</p>
|
||||
<ul className="space-y-2">
|
||||
{item.points.map((point, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-sm text-[#1C1C1C]">
|
||||
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-1.5 shrink-0" />
|
||||
{point}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
className="mt-12 text-center"
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/solutions">
|
||||
了解完整解决方案
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { InsightsSection } from './insights-section';
|
||||
|
||||
jest.mock('@/components/ui/insight-card', () => ({
|
||||
InsightCard: ({ title, category }: any) => (
|
||||
<div data-testid="insight-card">
|
||||
<div>{title}</div>
|
||||
<div>{category}</div>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('InsightsSection', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render insights section', () => {
|
||||
render(<InsightsSection />);
|
||||
const section = document.querySelector('section#insights');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render section heading', () => {
|
||||
render(<InsightsSection />);
|
||||
expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render section description', () => {
|
||||
render(<InsightsSection />);
|
||||
expect(screen.getByText(/分享前沿技术趋势/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render insight cards', () => {
|
||||
render(<InsightsSection />);
|
||||
const cards = screen.getAllByTestId('insight-card');
|
||||
expect(cards.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should render insight titles', () => {
|
||||
render(<InsightsSection />);
|
||||
expect(screen.getByText('2025年技术趋势:AI驱动的数字化转型')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render insight categories', () => {
|
||||
render(<InsightsSection />);
|
||||
expect(screen.getByText('技术趋势')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render view all button', () => {
|
||||
render(<InsightsSection />);
|
||||
expect(screen.getByText('查看全部洞察')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have section id', () => {
|
||||
render(<InsightsSection />);
|
||||
const section = document.querySelector('section#insights');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should have correct background', () => {
|
||||
render(<InsightsSection />);
|
||||
const section = document.querySelector('section.bg-\\[\\#FAFAFA\\]');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have container', () => {
|
||||
render(<InsightsSection />);
|
||||
const container = document.querySelector('.container-wide');
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have grid layout', () => {
|
||||
render(<InsightsSection />);
|
||||
const grid = document.querySelector('.grid');
|
||||
expect(grid).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,129 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { InsightCard } from '@/components/ui/insight-card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
|
||||
interface Insight {
|
||||
id: string;
|
||||
title: string;
|
||||
excerpt: string;
|
||||
category: string;
|
||||
readTime: string;
|
||||
publishedAt: string;
|
||||
imageUrl?: string;
|
||||
href: string;
|
||||
featured?: boolean;
|
||||
}
|
||||
|
||||
const MOCK_INSIGHTS: Insight[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: '2025年技术趋势:AI驱动的数字化转型',
|
||||
excerpt: '深入探讨人工智能如何重塑企业技术架构,以及如何把握AI时代的机遇',
|
||||
category: '技术趋势',
|
||||
readTime: '8 分钟',
|
||||
publishedAt: '2026-02-10',
|
||||
imageUrl: '/insights/ai-trend.jpg',
|
||||
href: '/insights/ai-trend-2025',
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '微服务架构最佳实践指南',
|
||||
excerpt: '从单体应用到微服务的演进之路,包含实战案例和避坑指南',
|
||||
category: '架构设计',
|
||||
readTime: '12 分钟',
|
||||
publishedAt: '2026-02-08',
|
||||
imageUrl: '/insights/microservices.jpg',
|
||||
href: '/insights/microservices-best-practices',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '云原生技术栈选型指南',
|
||||
excerpt: 'Kubernetes、Docker、Service Mesh等技术栈的深度对比与选型建议',
|
||||
category: '云原生',
|
||||
readTime: '10 分钟',
|
||||
publishedAt: '2026-02-05',
|
||||
imageUrl: '/insights/cloud-native.jpg',
|
||||
href: '/insights/cloud-native-stack-guide',
|
||||
},
|
||||
];
|
||||
|
||||
export function InsightsSection() {
|
||||
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="insights"
|
||||
ref={sectionRef}
|
||||
className="py-24 bg-[#FAFAFA]"
|
||||
>
|
||||
<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-6 mb-12
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-1' : ''}
|
||||
`}
|
||||
>
|
||||
{MOCK_INSIGHTS.map((insight) => (
|
||||
<InsightCard key={insight.id} {...insight} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
text-center
|
||||
opacity-0 translate-y-4
|
||||
${isVisible ? 'animate-fade-in-up stagger-2' : ''}
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
className="group"
|
||||
onClick={() => window.location.href = '/insights'}
|
||||
>
|
||||
查看全部洞察
|
||||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { METHODOLOGY } from '@/lib/constants/methodology';
|
||||
import { CheckCircle2 } from 'lucide-react';
|
||||
|
||||
export function MethodologySection() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: '-100px' });
|
||||
|
||||
return (
|
||||
<section id="methodology" role="region" aria-labelledby="methodology-heading" className="py-24 bg-white relative overflow-hidden" ref={ref}>
|
||||
<div className="container-wide relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<h2 id="methodology-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
实施<span className="text-[#C41E3A] font-calligraphy">方法论</span>
|
||||
</h2>
|
||||
<p className="text-lg text-[#5C5C5C]">
|
||||
经过多年实践验证的四阶段模型,确保每个项目都能科学推进、高效落地
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="relative">
|
||||
{/* 连接线 */}
|
||||
<div className="hidden lg:block absolute top-24 left-[12.5%] right-[12.5%] h-0.5 bg-gradient-to-r from-[#C41E3A]/20 via-[#C41E3A]/40 to-[#C41E3A]/20" />
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{METHODOLOGY.map((phase, idx) => (
|
||||
<motion.div
|
||||
key={phase.id}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: 0.2 + idx * 0.15 }}
|
||||
>
|
||||
<div className="relative bg-[#FFFBF5] rounded-2xl p-8 border border-[#E5E5E5] hover:border-[#C41E3A]/30 hover:shadow-lg transition-all duration-300 h-full">
|
||||
{/* 阶段编号 */}
|
||||
<div className="w-12 h-12 bg-[#C41E3A] rounded-full flex items-center justify-center mb-6 text-white font-bold text-xl">
|
||||
{phase.number}
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-bold text-[#1C1C1C] mb-1">{phase.title}</h3>
|
||||
<p className="text-sm text-[#C41E3A] font-medium mb-3">{phase.subtitle}</p>
|
||||
<p className="text-sm text-[#5C5C5C] leading-relaxed mb-6">{phase.description}</p>
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="text-xs font-semibold text-[#1C1C1C] mb-2 uppercase tracking-wide">核心活动</p>
|
||||
<ul className="space-y-1.5">
|
||||
{phase.activities.map((activity, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-xs text-[#3D3D3D]">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-[#C41E3A] mt-0.5 shrink-0" />
|
||||
{activity}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-[#1C1C1C] mb-2 uppercase tracking-wide">交付物</p>
|
||||
<ul className="space-y-1.5">
|
||||
{phase.deliverables.map((deliverable, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-xs text-[#5C5C5C]">
|
||||
<span className="w-1.5 h-1.5 bg-[#C41E3A]/60 rounded-full mt-1.5 shrink-0" />
|
||||
{deliverable}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -85,19 +85,6 @@ export function ProductsSection() {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{product.pricing && (
|
||||
<div className="mb-4 p-3 bg-[#F5F7FA] rounded-lg">
|
||||
<p className="text-sm font-medium text-[#1C1C1C] mb-2">价格方案</p>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(product.pricing).map(([key, value]) => (
|
||||
<p key={key} className="text-xs text-[#5C5C5C]">
|
||||
{value}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button variant="outline" className="w-full mt-auto group-hover:bg-[#A01830] group-hover:text-white group-hover:border-[#A01830] transition-colors">
|
||||
了解详情
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { StaticLink } from '@/components/ui/static-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowRight, Shield, Building2, Users } from 'lucide-react';
|
||||
|
||||
const TEAM_HIGHLIGHTS = [
|
||||
{
|
||||
icon: Shield,
|
||||
title: '12年+ 行业深耕',
|
||||
description: '核心团队长期从事技术咨询、企业数字化等领域,积累了丰富的行业经验和最佳实践。',
|
||||
},
|
||||
{
|
||||
icon: Building2,
|
||||
title: '大型 IT 企业背景',
|
||||
description: '开发团队成员来自多个大型传统 IT 企业,具备扎实的工程能力和规范化交付经验。',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: '复合型技术团队',
|
||||
description: '既懂技术又懂业务,能深入理解客户场景,提供真正落地的解决方案。',
|
||||
},
|
||||
];
|
||||
|
||||
export function TeamSection() {
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: '-100px' });
|
||||
|
||||
return (
|
||||
<section id="team" role="region" aria-labelledby="team-heading" className="py-24 bg-[#FAFAFA] relative overflow-hidden" ref={ref}>
|
||||
<div className="container-wide relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center max-w-3xl mx-auto mb-16"
|
||||
>
|
||||
<h2 id="team-heading" className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6">
|
||||
核心<span className="text-[#C41E3A] font-calligraphy">团队</span>
|
||||
</h2>
|
||||
<p className="text-lg text-[#5C5C5C] leading-relaxed">
|
||||
核心团队从事技术咨询、企业数字化等行业 12 年+,开发团队成员来自于多个大型传统 IT 企业,具备扎实的工程能力和丰富的行业经验。
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto mb-12">
|
||||
{TEAM_HIGHLIGHTS.map((item, idx) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<motion.div
|
||||
key={item.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.5, delay: 0.1 + idx * 0.15 }}
|
||||
>
|
||||
<div className="bg-white rounded-2xl p-8 border border-[#E5E5E5] hover:border-[#C41E3A]/30 hover:shadow-lg transition-all duration-300 h-full text-center">
|
||||
<div className="w-14 h-14 bg-[#C41E3A] rounded-2xl flex items-center justify-center mb-6 mx-auto">
|
||||
<Icon className="w-7 h-7 text-white" />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-[#1C1C1C] mb-3">{item.title}</h3>
|
||||
<p className="text-sm text-[#5C5C5C] leading-relaxed">{item.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
className="text-center"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
asChild
|
||||
>
|
||||
<StaticLink href="/team">
|
||||
了解更多
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</StaticLink>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { TestimonialsSection } from './testimonials-section';
|
||||
|
||||
jest.mock('@/components/ui/testimonial-card', () => ({
|
||||
TestimonialCard: ({ author, quote }: any) => (
|
||||
<div data-testid="testimonial-card">
|
||||
<div>{author}</div>
|
||||
<div>{quote}</div>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('TestimonialsSection', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render testimonials section', () => {
|
||||
render(<TestimonialsSection />);
|
||||
const section = document.querySelector('section#testimonials');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render section heading', () => {
|
||||
render(<TestimonialsSection />);
|
||||
expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render section description', () => {
|
||||
render(<TestimonialsSection />);
|
||||
expect(screen.getByText(/听听我们的客户怎么说/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render testimonial cards', () => {
|
||||
render(<TestimonialsSection />);
|
||||
const cards = screen.getAllByTestId('testimonial-card');
|
||||
expect(cards.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should render testimonial authors', () => {
|
||||
render(<TestimonialsSection />);
|
||||
expect(screen.getByText('张总')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render testimonial quotes', () => {
|
||||
render(<TestimonialsSection />);
|
||||
expect(screen.getByText(/睿新致远的团队非常专业/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have section id', () => {
|
||||
render(<TestimonialsSection />);
|
||||
const section = document.querySelector('section#testimonials');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should have correct background', () => {
|
||||
render(<TestimonialsSection />);
|
||||
const section = document.querySelector('section.bg-white');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have container', () => {
|
||||
render(<TestimonialsSection />);
|
||||
const container = document.querySelector('.container-wide');
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have grid layout', () => {
|
||||
render(<TestimonialsSection />);
|
||||
const grid = document.querySelector('.grid');
|
||||
expect(grid).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,103 +0,0 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export interface NavigationItem {
|
||||
export const NAVIGATION: NavigationItem[] = [
|
||||
{ id: 'home', label: '首页', href: '/' },
|
||||
{ id: 'services', label: '核心业务', href: '/' },
|
||||
{ id: 'solutions', label: '解决方案', href: '/' },
|
||||
{ id: 'products', label: '产品服务', href: '/' },
|
||||
{ id: 'cases', label: '成功案例', href: '/' },
|
||||
{ id: 'about', label: '关于我们', href: '/' },
|
||||
|
||||
Reference in New Issue
Block a user