feat(cms): 添加 CMS 内容管理系统与 Admin 管理后台
- 新增 Prisma + SQLite 数据库模型 (Category, Content, Media, User 等) - 新增 Admin 管理后台 (认证、内容管理、媒体管理) - 新增 CMS API 路由 (CRUD, 草稿/发布, 重新验证) - 新增 CMS 内容版本的历史归档页面 - 新增 components/cms 内容渲染组件 - 新增 components/admin 管理后台 UI 组件 - 更新 Contact API 路由
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
'use client';
|
||||
|
||||
import { notFound } from 'next/navigation';
|
||||
import CaseDetailPage from '@/components/sections/case-detail-page';
|
||||
import { ScrollProgress } from '@/components/ui/scroll-progress';
|
||||
import type { ContentItem } from '@/lib/cms/types';
|
||||
import type { CaseStudyData } from '@/lib/cms/mock-data';
|
||||
|
||||
interface CaseDetailClientProps {
|
||||
item: ContentItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 案例详情客户端组件(CMS 化版本)
|
||||
* 数据源:CMS ContentItem(由 server 端从 mock-data.ts 同步预取)
|
||||
* 类型系统:使用 CMS 类型 ContentItem,未来切换真实 API 时无需改业务代码
|
||||
*/
|
||||
export function CaseDetailClient({ item }: CaseDetailClientProps) {
|
||||
if (!item) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const data = item.data as unknown as CaseStudyData;
|
||||
|
||||
const detailData = {
|
||||
id: item.id,
|
||||
client: data.client,
|
||||
industry: data.industry,
|
||||
title: item.title,
|
||||
subtitle: data.subtitle,
|
||||
challenge: data.challenge,
|
||||
solution: data.solution,
|
||||
result: data.result,
|
||||
metrics: data.metrics,
|
||||
timeline: data.timeline,
|
||||
services: data.services,
|
||||
testimonial: data.testimonial
|
||||
? {
|
||||
quote: data.testimonial.quote,
|
||||
author: data.testimonial.author,
|
||||
role: `${data.testimonial.role},${data.testimonial.company}`,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollProgress />
|
||||
<CaseDetailPage data={detailData} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { CaseDetailClient } from './client';
|
||||
import { getPublishedItemBySlug, getAllPublishedSlugs } from '@/lib/cms/data-server';
|
||||
import { COMPANY_INFO } from '@/lib/constants/company';
|
||||
import type { CaseStudyData } from '@/lib/cms/mock-data';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
interface CaseDetailPageProps {
|
||||
params: Promise<{ slug: string }>;
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const slugs = await getAllPublishedSlugs('case-study');
|
||||
return slugs.map((s) => ({ slug: s.slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: CaseDetailPageProps): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
const item = await getPublishedItemBySlug('case-study', slug);
|
||||
|
||||
if (!item) {
|
||||
return { title: `案例未找到 - ${COMPANY_INFO.displayName}` };
|
||||
}
|
||||
|
||||
const data = item.data as unknown as CaseStudyData;
|
||||
|
||||
return {
|
||||
title: `${item.title} - ${data.industry}案例 - ${COMPANY_INFO.displayName}`,
|
||||
description: `${data.client}数字化转型案例:${data.challenge.substring(0, 80)}... ${data.metrics?.map((m: { value: string; label: string }) => `${m.value} ${m.label}`).join(',')}`,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function CaseDetailPage({ params }: CaseDetailPageProps) {
|
||||
const { slug } = await params;
|
||||
const item = await getPublishedItemBySlug('case-study', slug);
|
||||
|
||||
if (!item) { notFound(); }
|
||||
|
||||
return <CaseDetailClient item={item} />;
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import {
|
||||
Building2, ArrowRight, TrendingUp, Target, Lightbulb,
|
||||
Filter,
|
||||
} from 'lucide-react';
|
||||
import { ScrollReveal } from '@/components/ui/scroll-reveal';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { MetricCard } from '@/components/ui/metric-card';
|
||||
import {
|
||||
CASE_INDUSTRIES,
|
||||
getCasesByIndustry,
|
||||
type CaseStudy,
|
||||
} from '@/lib/constants/cases';
|
||||
import {
|
||||
GrainOverlay,
|
||||
FloatingInkParticles,
|
||||
SectionLabel,
|
||||
} from '@/components/ui/page-decoration';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const colorMap: Record<CaseStudy['color'], string> = {
|
||||
brand: 'bg-brand text-brand',
|
||||
blue: 'bg-accent-blue text-accent-blue',
|
||||
teal: 'bg-accent-teal text-accent-teal',
|
||||
amber: 'bg-accent-amber text-accent-amber',
|
||||
purple: 'bg-accent-purple text-accent-purple',
|
||||
};
|
||||
|
||||
function CaseCard({ caseItem }: { caseItem: CaseStudy; index?: number }) {
|
||||
const colorStyle = colorMap[caseItem.color];
|
||||
const [bgClass, textClass] = colorStyle.split(' ');
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`/cases/${caseItem.slug}`}
|
||||
className="group relative block overflow-hidden border border-white/[0.08] hover:border-white/20 bg-ink transition-all duration-500 hover:-translate-y-1 flex flex-col h-full"
|
||||
>
|
||||
<div className={cn(
|
||||
'absolute top-0 left-0 right-0 h-1 scale-x-0 group-hover:scale-x-100 transition-transform duration-700 origin-left',
|
||||
bgClass
|
||||
)} />
|
||||
|
||||
<div className="p-6 sm:p-7 lg:p-8 flex flex-col flex-1">
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<Badge variant="outline" className={cn('border-transparent', bgClass, 'text-white')}>
|
||||
{caseItem.industry}
|
||||
</Badge>
|
||||
<span className="text-[10px] tracking-[0.25em] uppercase text-white/30 font-semibold">
|
||||
{caseItem.companySize}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg sm:text-xl font-bold text-white mb-4 leading-tight group-hover:text-brand transition-colors duration-300">
|
||||
{caseItem.title}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4 mb-6 flex-1">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Target className={cn('w-3.5 h-3.5', textClass)} />
|
||||
<span className={cn('text-[11px] tracking-wide uppercase font-bold', textClass)}>挑战</span>
|
||||
</div>
|
||||
<p className="text-sm text-white/50 leading-relaxed line-clamp-2 pl-5.5">
|
||||
{caseItem.challenge}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Lightbulb className={cn('w-3.5 h-3.5', textClass)} />
|
||||
<span className={cn('text-[11px] tracking-wide uppercase font-bold', textClass)}>方案</span>
|
||||
</div>
|
||||
<p className="text-sm text-white/50 leading-relaxed line-clamp-2 pl-5.5">
|
||||
{caseItem.solution}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-5 border-t border-white/[0.08] mb-5">
|
||||
{caseItem.metrics.slice(0, 2).map((m, i) => (
|
||||
<div key={i}>
|
||||
<div className="text-2xl sm:text-3xl font-bold text-white mb-1 tabular-nums">
|
||||
{m.value}
|
||||
</div>
|
||||
<div className="text-xs text-white/40">{m.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4 mt-auto border-t border-white/[0.06]">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="w-4 h-4 text-white/30" />
|
||||
<span className="text-xs text-white/40">{caseItem.client}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-brand text-sm font-medium">
|
||||
查看详情
|
||||
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const PAGE_METRICS = [
|
||||
{
|
||||
icon: <Building2 className="w-5 h-5" />,
|
||||
label: '服务客户',
|
||||
value: 500,
|
||||
suffix: '+',
|
||||
description: '覆盖六大行业,500+企业客户的共同选择',
|
||||
trend: { value: '23% YoY', direction: 'up' as const, label: '增长' },
|
||||
accentColor: 'brand' as const,
|
||||
},
|
||||
{
|
||||
icon: <TrendingUp className="w-5 h-5" />,
|
||||
label: '成功率',
|
||||
value: 98,
|
||||
suffix: '%',
|
||||
description: '项目一次性上线成功率,持续稳定交付',
|
||||
trend: { value: '97%续约率', direction: 'up' as const, label: '' },
|
||||
accentColor: 'teal' as const,
|
||||
},
|
||||
{
|
||||
icon: <Target className="w-5 h-5" />,
|
||||
label: '平均效率提升',
|
||||
value: 35,
|
||||
suffix: '%',
|
||||
description: '客户核心业务流程效率平均提升幅度',
|
||||
trend: { value: '基于 200+ 案例', direction: 'neutral' as const, label: '' },
|
||||
accentColor: 'blue' as const,
|
||||
},
|
||||
{
|
||||
icon: <Lightbulb className="w-5 h-5" />,
|
||||
label: '行业覆盖',
|
||||
value: 6,
|
||||
suffix: '大',
|
||||
description: '制造、零售、医疗、教育、金融、物流六大行业',
|
||||
trend: { value: '持续扩展中', direction: 'up' as const, label: '' },
|
||||
accentColor: 'amber' as const,
|
||||
},
|
||||
];
|
||||
|
||||
export default function CasesContentV1() {
|
||||
const [selectedIndustry, setSelectedIndustry] = useState('all');
|
||||
|
||||
const filteredCases = useMemo(() => {
|
||||
return getCasesByIndustry(selectedIndustry);
|
||||
}, [selectedIndustry]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-ink text-white overflow-x-hidden" data-theme="dark">
|
||||
{/* Hero Section */}
|
||||
<section className="relative min-h-[70vh] flex items-center overflow-hidden bg-ink">
|
||||
<GrainOverlay />
|
||||
<FloatingInkParticles />
|
||||
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-ink-light/30 via-transparent to-ink pointer-events-none" />
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-40">
|
||||
<div className="max-w-4xl">
|
||||
<ScrollReveal>
|
||||
<SectionLabel>Case Studies</SectionLabel>
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.1}>
|
||||
<h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold leading-[0.95] tracking-tighter mb-8 sm:mb-10">
|
||||
真实案例,
|
||||
<span className="block mt-3 text-brand">可衡量的成果</span>
|
||||
</h1>
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.2}>
|
||||
<p className="text-lg md:text-xl text-white/60 max-w-2xl leading-relaxed mb-10">
|
||||
每一个案例,都是我们与客户共同创造的价值故事。从挑战到方案,从过程到成果——用数据说话,用结果证明。
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.3}>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Badge variant="outline" className="border-brand/30 text-brand px-4 py-1.5 text-xs">
|
||||
500+ 企业客户
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-white/20 text-white/70 px-4 py-1.5 text-xs">
|
||||
98% 项目成功率
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-white/20 text-white/70 px-4 py-1.5 text-xs">
|
||||
6 大行业覆盖
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-white/20 text-white/70 px-4 py-1.5 text-xs">
|
||||
97% 客户续约率
|
||||
</Badge>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Key Metrics Section */}
|
||||
<section className="relative py-16 sm:py-20 md:py-24 overflow-hidden bg-ink-light">
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-white/10 to-transparent" />
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-white/10 to-transparent" />
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6">
|
||||
{PAGE_METRICS.map((metric, i) => (
|
||||
<ScrollReveal key={metric.label} delay={i * 0.08}>
|
||||
<div className="h-full">
|
||||
<MetricCard
|
||||
icon={metric.icon}
|
||||
label={metric.label}
|
||||
value={metric.value}
|
||||
suffix={metric.suffix}
|
||||
description={metric.description}
|
||||
trend={metric.trend}
|
||||
accentColor={metric.accentColor}
|
||||
theme="dark"
|
||||
className="h-full"
|
||||
/>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Cases List Section */}
|
||||
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-ink">
|
||||
<GrainOverlay />
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-8 mb-12 sm:mb-16 md:mb-20">
|
||||
<ScrollReveal className="lg:max-w-2xl">
|
||||
<SectionLabel>All Cases</SectionLabel>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight leading-tight mt-4">
|
||||
全部案例
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-white/60 leading-relaxed">
|
||||
按行业筛选,找到与您业务最相关的成功故事
|
||||
</p>
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.1}>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Filter className="w-4 h-4 text-white/40" />
|
||||
{CASE_INDUSTRIES.map((industry) => (
|
||||
<button
|
||||
key={industry.id}
|
||||
onClick={() => setSelectedIndustry(industry.id)}
|
||||
className={cn(
|
||||
'px-4 py-2 text-xs font-medium transition-all duration-300',
|
||||
selectedIndustry === industry.id
|
||||
? 'bg-brand text-white'
|
||||
: 'bg-white/[0.05] text-white/60 hover:bg-white/[0.1] hover:text-white'
|
||||
)}
|
||||
>
|
||||
{industry.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
||||
{filteredCases.length > 0 ? (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
|
||||
{filteredCases.map((caseItem, i) => (
|
||||
<ScrollReveal key={caseItem.id} delay={i * 0.08}>
|
||||
<div className="h-full">
|
||||
<CaseCard caseItem={caseItem} index={i} />
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-white/50 text-lg">暂无该行业的案例</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-ink-light">
|
||||
<GrainOverlay />
|
||||
<FloatingInkParticles />
|
||||
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-ink-light/50 to-ink pointer-events-none" />
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<ScrollReveal className="text-center max-w-3xl mx-auto">
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight leading-tight">
|
||||
您的成功故事,
|
||||
<span className="text-brand">从这里开始</span>
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-white/60 leading-relaxed">
|
||||
无论您处于数字化转型的哪个阶段,我们都有相应的经验和能力帮助您。
|
||||
让我们聊聊您的挑战,看看能创造什么样的成果。
|
||||
</p>
|
||||
<div className="mt-12 flex flex-col sm:flex-row justify-center gap-4 sm:gap-6">
|
||||
<a
|
||||
href="/contact"
|
||||
className="group inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base bg-brand text-white transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
||||
>
|
||||
预约咨询
|
||||
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
||||
</a>
|
||||
<a
|
||||
href="/services"
|
||||
className="inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base text-white border border-white/20 hover:border-white/40 hover:bg-white/[0.03] transition-all duration-300 min-h-[52px] touch-manipulation"
|
||||
>
|
||||
了解服务
|
||||
</a>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Building2, ArrowUpRight, Target, Lightbulb, Filter } from 'lucide-react';
|
||||
import {
|
||||
CASE_INDUSTRIES,
|
||||
getCasesByIndustry,
|
||||
type CaseStudy,
|
||||
} from '@/lib/constants/cases';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
|
||||
|
||||
const PAGE_METRICS = [
|
||||
{ value: '500+', label: '服务客户', sub: '覆盖六大行业' },
|
||||
{ value: '98%', label: '项目成功率', sub: '持续稳定交付' },
|
||||
{ value: '35%', label: '平均效率提升', sub: '核心业务流程' },
|
||||
{ value: '6大', label: '行业覆盖', sub: '制造零售医疗等' },
|
||||
];
|
||||
|
||||
function CaseCard({ caseItem, index }: { caseItem: CaseStudy; index: number }) {
|
||||
return (
|
||||
<motion.a
|
||||
href={`/cases/${caseItem.slug}`}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.6, delay: index * 0.06, ease: EASE_OUT }}
|
||||
className="group relative block overflow-hidden border border-border-primary hover:border-border-secondary bg-white hover:bg-bg-secondary transition-all duration-500 flex flex-col h-full"
|
||||
>
|
||||
<div className="absolute top-0 left-0 h-0 w-[2px] group-hover:h-full transition-all duration-500 origin-top bg-brand" />
|
||||
|
||||
<div className="p-6 sm:p-7 lg:p-8 flex flex-col flex-1 relative z-10">
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<span className="inline-block px-3 py-1 text-[11px] font-bold text-brand border border-brand/20 bg-brand/[0.05]">
|
||||
{caseItem.industry}
|
||||
</span>
|
||||
<span className="text-[10px] tracking-[0.25em] uppercase text-text-muted font-semibold">
|
||||
{caseItem.companySize}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg sm:text-xl font-bold text-ink mb-4 leading-tight group-hover:text-brand transition-colors duration-300">
|
||||
{caseItem.title}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4 mb-6 flex-1">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Target className="w-3.5 h-3.5 text-brand" />
|
||||
<span className="text-[11px] tracking-wide uppercase font-bold text-brand">挑战</span>
|
||||
</div>
|
||||
<p className="text-sm text-text-secondary leading-relaxed line-clamp-2 pl-5.5">
|
||||
{caseItem.challenge}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Lightbulb className="w-3.5 h-3.5 text-text-muted" />
|
||||
<span className="text-[11px] tracking-wide uppercase font-bold text-text-secondary">方案</span>
|
||||
</div>
|
||||
<p className="text-sm text-text-secondary leading-relaxed line-clamp-2 pl-5.5">
|
||||
{caseItem.solution}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-5 border-t border-border-primary mb-5">
|
||||
{caseItem.metrics.slice(0, 2).map((m, i) => (
|
||||
<div key={i}>
|
||||
<div className={`text-2xl sm:text-3xl font-black mb-1 tabular-nums tracking-tight ${
|
||||
i === 0 ? 'text-brand' : 'text-ink'
|
||||
}`}>
|
||||
{m.value}
|
||||
</div>
|
||||
<div className="text-xs text-text-muted">{m.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4 mt-auto border-t border-border-primary">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="w-4 h-4 text-text-muted" />
|
||||
<span className="text-xs text-text-muted">{caseItem.client}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-brand text-sm font-semibold">
|
||||
查看详情
|
||||
<ArrowUpRight className="w-4 h-4 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.a>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CasesContentV2({
|
||||
cases: casesProp,
|
||||
industries: industriesProp,
|
||||
}: {
|
||||
cases?: CaseStudy[];
|
||||
industries?: { id: string; label: string }[];
|
||||
}) {
|
||||
const [selectedIndustry, setSelectedIndustry] = useState('all');
|
||||
|
||||
const industries = industriesProp ?? CASE_INDUSTRIES;
|
||||
|
||||
const filteredCases = useMemo(() => {
|
||||
if (casesProp) return casesProp;
|
||||
return getCasesByIndustry(selectedIndustry);
|
||||
}, [selectedIndustry, casesProp]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
|
||||
{/* Hero Section */}
|
||||
<section className="relative min-h-[70vh] flex items-center overflow-hidden bg-white">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-40">
|
||||
<div className="max-w-4xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.1, ease: EASE_OUT }}
|
||||
className="text-sm font-mono tracking-[0.2em] text-text-muted uppercase mb-8"
|
||||
>
|
||||
Case Studies
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, delay: 0.2, ease: EASE_OUT }}
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-black text-ink leading-[0.88] tracking-tightest mb-10 sm:mb-12 md:mb-16"
|
||||
>
|
||||
<div className="block">真实案例,</div>
|
||||
<div className="block mt-4 sm:mt-6">
|
||||
<span className="relative inline-block">
|
||||
<span className="relative text-brand font-black">
|
||||
可衡量的成果
|
||||
<motion.span
|
||||
className="absolute -bottom-2 left-0 w-full h-[3px] bg-brand"
|
||||
style={{ transformOrigin: 'left' }}
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.8, delay: 1.2, ease: EASE_OUT }}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.5, ease: EASE_OUT }}
|
||||
className="text-lg md:text-xl text-text-secondary max-w-2xl leading-relaxed mb-12"
|
||||
>
|
||||
每一个案例,都是我们与客户共同创造的价值故事。
|
||||
<br className="hidden sm:block" />
|
||||
从挑战到方案,从过程到成果——用数据说话,用结果证明。
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.7, ease: EASE_OUT }}
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
<span className="px-4 py-2 text-xs font-bold text-brand border border-brand/20 bg-brand/[0.05]">
|
||||
500+ 企业客户
|
||||
</span>
|
||||
<span className="px-4 py-2 text-xs font-medium text-text-secondary border border-border-primary">
|
||||
98% 项目成功率
|
||||
</span>
|
||||
<span className="px-4 py-2 text-xs font-medium text-text-secondary border border-border-primary">
|
||||
6 大行业覆盖
|
||||
</span>
|
||||
<span className="px-4 py-2 text-xs font-medium text-text-secondary border border-border-primary">
|
||||
97% 客户续约率
|
||||
</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Metrics Strip */}
|
||||
<section className="relative py-16 sm:py-20 md:py-24 overflow-hidden bg-bg-secondary">
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8 md:gap-12">
|
||||
{PAGE_METRICS.map((metric, i) => (
|
||||
<motion.div
|
||||
key={metric.label}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.6, delay: i * 0.08, ease: EASE_OUT }}
|
||||
className="text-center lg:text-left"
|
||||
>
|
||||
<div className={`text-3xl sm:text-4xl md:text-5xl font-black tracking-tight mb-2 ${
|
||||
i === 0 ? 'text-brand' : 'text-ink'
|
||||
}`}>
|
||||
{metric.value}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-text-secondary mb-1">{metric.label}</div>
|
||||
<div className="text-xs text-text-muted">{metric.sub}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Cases List Section */}
|
||||
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-8 mb-12 sm:mb-16 md:mb-20">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.8, ease: EASE_OUT }}
|
||||
className="lg:max-w-2xl"
|
||||
>
|
||||
<div className="text-sm font-mono tracking-[0.2em] text-text-muted uppercase mb-6">
|
||||
All Cases
|
||||
</div>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-extrabold text-ink tracking-tight leading-[0.95]">
|
||||
全部案例
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-text-secondary leading-relaxed">
|
||||
按行业筛选,找到与您业务最相关的成功故事
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.8, delay: 0.15, ease: EASE_OUT }}
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Filter className="w-4 h-4 text-text-muted" />
|
||||
{industries.map((industry) => (
|
||||
<button
|
||||
key={industry.id}
|
||||
onClick={() => setSelectedIndustry(industry.id)}
|
||||
className={cn(
|
||||
'px-4 py-2 text-xs font-medium transition-all duration-300',
|
||||
selectedIndustry === industry.id
|
||||
? 'bg-brand text-white'
|
||||
: 'bg-bg-secondary text-text-secondary hover:bg-bg-tertiary hover:text-ink'
|
||||
)}
|
||||
>
|
||||
{industry.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{filteredCases.length > 0 ? (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-px bg-border-primary">
|
||||
{filteredCases.map((caseItem, i) => (
|
||||
<CaseCard key={caseItem.id} caseItem={caseItem} index={i} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-text-secondary text-lg">暂无该行业的案例</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-secondary">
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.8, ease: EASE_OUT }}
|
||||
className="max-w-3xl"
|
||||
>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-extrabold text-ink tracking-tight leading-[0.95] mb-8">
|
||||
您的成功故事,<br className="sm:hidden" />从这里开始
|
||||
</h2>
|
||||
<p className="text-lg text-text-secondary leading-relaxed mb-10">
|
||||
无论您处于数字化转型的哪个阶段,我们都有相应的经验和能力帮助您。
|
||||
<br className="hidden sm:block" />
|
||||
让我们聊聊您的挑战,看看能创造什么样的成果。
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||
<a
|
||||
href="/contact"
|
||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
||||
>
|
||||
预约咨询
|
||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
||||
</a>
|
||||
<a
|
||||
href="/services"
|
||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary hover:border-border-secondary hover:bg-white transition-all duration-300 min-h-[52px] touch-manipulation"
|
||||
>
|
||||
了解服务
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Building2, ArrowUpRight, Target, Lightbulb, Filter } from 'lucide-react';
|
||||
import {
|
||||
type CaseStudy,
|
||||
} from '@/lib/constants/cases';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
|
||||
|
||||
const PAGE_METRICS = [
|
||||
{ value: '500+', label: '服务客户', sub: '覆盖六大行业' },
|
||||
{ value: '98%', label: '项目成功率', sub: '持续稳定交付' },
|
||||
{ value: '35%', label: '平均效率提升', sub: '核心业务流程' },
|
||||
{ value: '6大', label: '行业覆盖', sub: '制造零售医疗等' },
|
||||
];
|
||||
|
||||
function CaseCard({ caseItem, index }: { caseItem: CaseStudy; index: number }) {
|
||||
return (
|
||||
<motion.a
|
||||
href={`/cases/${caseItem.slug}`}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{ duration: 0.6, delay: index * 0.06, ease: EASE_OUT }}
|
||||
className="group relative block overflow-hidden border border-border-primary hover:border-border-secondary bg-white hover:bg-bg-secondary transition-all duration-500 flex flex-col h-full"
|
||||
>
|
||||
<div className="absolute top-0 left-0 h-0 w-[2px] group-hover:h-full transition-all duration-500 origin-top bg-brand" />
|
||||
|
||||
<div className="p-6 sm:p-7 lg:p-8 flex flex-col flex-1 relative z-10">
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<span className="inline-block px-3 py-1 text-[11px] font-bold text-brand border border-brand/20 bg-brand/[0.05]">
|
||||
{caseItem.industry}
|
||||
</span>
|
||||
<span className="text-[10px] tracking-[0.25em] uppercase text-text-muted font-semibold">
|
||||
{caseItem.companySize}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg sm:text-xl font-bold text-ink mb-4 leading-tight group-hover:text-brand transition-colors duration-300">
|
||||
{caseItem.title}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4 mb-6 flex-1">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Target className="w-3.5 h-3.5 text-brand" />
|
||||
<span className="text-[11px] tracking-wide uppercase font-bold text-brand">挑战</span>
|
||||
</div>
|
||||
<p className="text-sm text-text-secondary leading-relaxed line-clamp-2 pl-5.5">
|
||||
{caseItem.challenge}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Lightbulb className="w-3.5 h-3.5 text-text-muted" />
|
||||
<span className="text-[11px] tracking-wide uppercase font-bold text-text-secondary">方案</span>
|
||||
</div>
|
||||
<p className="text-sm text-text-secondary leading-relaxed line-clamp-2 pl-5.5">
|
||||
{caseItem.solution}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-5 border-t border-border-primary mb-5">
|
||||
{caseItem.metrics.slice(0, 2).map((m, i) => (
|
||||
<div key={i}>
|
||||
<div className={`text-2xl sm:text-3xl font-black mb-1 tabular-nums tracking-tight ${
|
||||
i === 0 ? 'text-brand' : 'text-ink'
|
||||
}`}>
|
||||
{m.value}
|
||||
</div>
|
||||
<div className="text-xs text-text-muted">{m.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4 mt-auto border-t border-border-primary">
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="w-4 h-4 text-text-muted" />
|
||||
<span className="text-xs text-text-muted">{caseItem.client}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-brand text-sm font-semibold">
|
||||
查看详情
|
||||
<ArrowUpRight className="w-4 h-4 group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.a>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CasesContentV3({
|
||||
cases: casesProp,
|
||||
industries: industriesProp,
|
||||
}: {
|
||||
cases?: CaseStudy[];
|
||||
industries?: { id: string; label: string }[];
|
||||
}) {
|
||||
const [selectedIndustry, setSelectedIndustry] = useState('all');
|
||||
|
||||
const industries = industriesProp ?? [];
|
||||
|
||||
const filteredCases = useMemo(() => {
|
||||
const allCases = casesProp ?? [];
|
||||
if (selectedIndustry === 'all') return allCases;
|
||||
return allCases.filter(c => c.industry === selectedIndustry);
|
||||
}, [selectedIndustry, casesProp]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white text-ink overflow-x-hidden">
|
||||
{/* Hero Section */}
|
||||
<section className="relative min-h-[70vh] flex items-center overflow-hidden bg-white">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-28 sm:py-36 md:py-40">
|
||||
<div className="max-w-4xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.1, ease: EASE_OUT }}
|
||||
className="text-sm font-mono tracking-[0.2em] text-text-muted uppercase mb-8"
|
||||
>
|
||||
Case Studies
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, delay: 0.2, ease: EASE_OUT }}
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-black text-ink leading-[0.88] tracking-tightest mb-10 sm:mb-12 md:mb-16"
|
||||
>
|
||||
<div className="block">真实案例,</div>
|
||||
<div className="block mt-4 sm:mt-6">
|
||||
<span className="relative inline-block">
|
||||
<span className="relative text-brand font-black">
|
||||
可衡量的成果
|
||||
<motion.span
|
||||
className="absolute -bottom-2 left-0 w-full h-[3px] bg-brand"
|
||||
style={{ transformOrigin: 'left' }}
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.8, delay: 1.2, ease: EASE_OUT }}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.5, ease: EASE_OUT }}
|
||||
className="text-lg md:text-xl text-text-secondary max-w-2xl leading-relaxed mb-12"
|
||||
>
|
||||
每一个案例,都是我们与客户共同创造的价值故事。
|
||||
<br className="hidden sm:block" />
|
||||
从挑战到方案,从过程到成果——用数据说话,用结果证明。
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.7, ease: EASE_OUT }}
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
<span className="px-4 py-2 text-xs font-bold text-brand border border-brand/20 bg-brand/[0.05]">
|
||||
500+ 企业客户
|
||||
</span>
|
||||
<span className="px-4 py-2 text-xs font-medium text-text-secondary border border-border-primary">
|
||||
98% 项目成功率
|
||||
</span>
|
||||
<span className="px-4 py-2 text-xs font-medium text-text-secondary border border-border-primary">
|
||||
6 大行业覆盖
|
||||
</span>
|
||||
<span className="px-4 py-2 text-xs font-medium text-text-secondary border border-border-primary">
|
||||
97% 客户续约率
|
||||
</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Metrics Strip */}
|
||||
<section className="relative py-16 sm:py-20 md:py-24 overflow-hidden bg-bg-secondary">
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8 md:gap-12">
|
||||
{PAGE_METRICS.map((metric, i) => (
|
||||
<motion.div
|
||||
key={metric.label}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.6, delay: i * 0.08, ease: EASE_OUT }}
|
||||
className="text-center lg:text-left"
|
||||
>
|
||||
<div className={`text-3xl sm:text-4xl md:text-5xl font-black tracking-tight mb-2 ${
|
||||
i === 0 ? 'text-brand' : 'text-ink'
|
||||
}`}>
|
||||
{metric.value}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-text-secondary mb-1">{metric.label}</div>
|
||||
<div className="text-xs text-text-muted">{metric.sub}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Cases List Section */}
|
||||
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-white">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-8 mb-12 sm:mb-16 md:mb-20">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.8, ease: EASE_OUT }}
|
||||
className="lg:max-w-2xl"
|
||||
>
|
||||
<div className="text-sm font-mono tracking-[0.2em] text-text-muted uppercase mb-6">
|
||||
All Cases
|
||||
</div>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-extrabold text-ink tracking-tight leading-[0.95]">
|
||||
全部案例
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-text-secondary leading-relaxed">
|
||||
按行业筛选,找到与您业务最相关的成功故事
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.8, delay: 0.15, ease: EASE_OUT }}
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Filter className="w-4 h-4 text-text-muted" />
|
||||
{industries.map((industry) => (
|
||||
<button
|
||||
key={industry.id}
|
||||
onClick={() => setSelectedIndustry(industry.id)}
|
||||
className={cn(
|
||||
'px-4 py-2 text-xs font-medium transition-all duration-300',
|
||||
selectedIndustry === industry.id
|
||||
? 'bg-brand text-white'
|
||||
: 'bg-bg-secondary text-text-secondary hover:bg-bg-tertiary hover:text-ink'
|
||||
)}
|
||||
>
|
||||
{industry.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{filteredCases.length > 0 ? (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-px bg-border-primary">
|
||||
{filteredCases.map((caseItem, i) => (
|
||||
<CaseCard key={caseItem.id} caseItem={caseItem} index={i} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-text-secondary text-lg">暂无该行业的案例</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="relative py-20 sm:py-28 md:py-36 lg:py-44 overflow-hidden bg-bg-secondary">
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
||||
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-80px' }}
|
||||
transition={{ duration: 0.8, ease: EASE_OUT }}
|
||||
className="max-w-3xl"
|
||||
>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-extrabold text-ink tracking-tight leading-[0.95] mb-8">
|
||||
您的成功故事,<br className="sm:hidden" />从这里开始
|
||||
</h2>
|
||||
<p className="text-lg text-text-secondary leading-relaxed mb-10">
|
||||
无论您处于数字化转型的哪个阶段,我们都有相应的经验和能力帮助您。
|
||||
<br className="hidden sm:block" />
|
||||
让我们聊聊您的挑战,看看能创造什么样的成果。
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||
<a
|
||||
href="/contact"
|
||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
||||
>
|
||||
预约咨询
|
||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
||||
</a>
|
||||
<a
|
||||
href="/services"
|
||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary hover:border-border-secondary hover:bg-white transition-all duration-300 min-h-[52px] touch-manipulation"
|
||||
>
|
||||
了解服务
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import type { CaseStudy } from '@/lib/constants/cases';
|
||||
import CasesContentV3 from './cases-content-v3';
|
||||
|
||||
export function CasesClient({ cases }: { cases?: CaseStudy[] }) {
|
||||
return <CasesContentV3 cases={cases} />;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Metadata } from 'next';
|
||||
import { COMPANY_INFO } from '@/lib/constants';
|
||||
import { getPublishedItems } from '@/lib/cms/data-server';
|
||||
import type { CaseStudy } from '@/lib/constants/cases';
|
||||
import { CasesClient } from './client';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `客户案例 - ${COMPANY_INFO.displayName}`,
|
||||
description: `500+企业客户的成功案例,覆盖制造、零售、医疗、教育、金融、物流六大行业。真实数据,可衡量的成果——见证数字化转型的价值。`,
|
||||
};
|
||||
|
||||
export default async function CasesPage() {
|
||||
const items = await getPublishedItems('case-study');
|
||||
const cases = items.map((item) => ({ ...item.data, title: item.data.title || item.title } as unknown as CaseStudy));
|
||||
return <CasesClient cases={cases} />;
|
||||
}
|
||||
Reference in New Issue
Block a user