refactor: P2 - redesign Products/Services/Solutions pages

- Products: Atlassian-style card grid with ProductCard
- Products detail: clean typography, consistent tokens
- Services: unified card layout, simplified interaction
- Services detail: consistent section styling
- Solutions: data-driven module rendering, clean borders
This commit is contained in:
张翔
2026-04-30 22:11:17 +08:00
parent f513d9da20
commit 307e0a654e
5 changed files with 352 additions and 896 deletions
+126 -242
View File
@@ -1,270 +1,154 @@
'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 { ArrowRight, Lightbulb, Cpu, Users, CheckCircle2 } from 'lucide-react';
import { MethodologySection } from '@/components/sections/methodology-section';
export default function SolutionsPage() {
const contentRef = useRef(null);
const isContentInView = useInView(contentRef, { once: true, margin: '-100px' });
const modules = [
{
icon: Lightbulb,
title: '数字化转型咨询 · 参谋伙伴',
subtitle: '帮您看清前路,迈对第一步',
paragraphs: [
'数字化转型最大的成本,是走错方向的成本。',
'我们用行业智慧帮您洞察趋势,用理性分析帮您避开陷阱。',
'不堆砌概念,只帮您想清楚:该不该做、做什么、怎么做。',
],
values: ['行业趋势洞察报告', '数字化转型成熟度评估', '个性化实施路径规划'],
cta: '预约一次免费诊断',
ctaVariant: 'default' as const,
},
{
icon: Cpu,
title: '信息技术解决方案 · 技术伙伴',
subtitle: '让技术真正为业务服务',
paragraphs: [
'我们不追逐"最火"的技术,只选择"最对"的技术。',
'将前沿技术深度融入您的业务场景,让每一行代码都产生业务价值。',
'您不必懂技术原理,只需要看见业务在增长。',
],
values: ['业务场景深度调研', '技术方案定制开发', '敏捷交付快速迭代'],
cta: '查看技术案例',
ctaVariant: 'outline' as const,
},
{
icon: Users,
title: '长期陪跑服务 · 同行伙伴',
subtitle: '交付只是开始,陪伴才是常态',
paragraphs: [
'项目上线那天,是我们真正成为伙伴的开始。',
'我们建立长效服务机制,定期回访、持续优化、随时响应。',
'在您需要的时候,我们始终在场。',
],
values: ['专属客户成功经理', '季度业务复盘会', '7×24小时响应通道'],
cta: '了解陪跑服务',
ctaVariant: 'default' as const,
},
];
export default function SolutionsPage() {
return (
<div className="min-h-screen bg-white">
<PageHeader
title="三种角色,一种身份——您的成长伙伴"
description="我们以伙伴的身份,陪您走过数字化转型的每一步"
/>
<div className="container-wide relative z-10 py-16" ref={contentRef}>
<div className="max-w-6xl mx-auto space-y-24">
<motion.section
<section className="pt-32 pb-16">
<div className="container-wide">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6 }}
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="max-w-3xl"
>
<div className="flex items-start gap-6 mb-8">
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
<Lightbulb className="w-8 h-8 text-white" />
</div>
<div>
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
·
</h2>
<p className="text-lg text-[#5C5C5C]">
</p>
</div>
</div>
<div className="space-y-6 mb-8">
<p className="text-lg text-[#1C1C1C] leading-relaxed">
</p>
<p className="text-lg text-[#1C1C1C] leading-relaxed">
</p>
<p className="text-lg text-[#1C1C1C] leading-relaxed">
</p>
</div>
<div className="mb-8">
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
</h3>
<div className="grid md:grid-cols-3 gap-4">
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]"></span>
</div>
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]"></span>
</div>
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]"></span>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
size="lg"
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
onClick={() => {
const element = document.getElementById('contact');
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}}
>
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</div>
</motion.section>
<motion.section
initial={{ opacity: 0, y: 20 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.2 }}
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
>
<div className="flex items-start gap-6 mb-8">
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
<Cpu className="w-8 h-8 text-white" />
</div>
<div>
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
·
</h2>
<p className="text-lg text-[#5C5C5C]">
</p>
</div>
</div>
<div className="space-y-6 mb-8">
<p className="text-lg text-[#1C1C1C] leading-relaxed">
&ldquo;&rdquo;&ldquo;&rdquo;
</p>
<p className="text-lg text-[#1C1C1C] leading-relaxed">
沿
</p>
<p className="text-lg text-[#1C1C1C] leading-relaxed">
</p>
</div>
<div className="mb-8">
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
</h3>
<div className="grid md:grid-cols-3 gap-4">
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]"></span>
</div>
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]"></span>
</div>
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]"></span>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
size="lg"
variant="outline"
className="border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A] hover:text-white"
onClick={() => {
const element = document.getElementById('cases');
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}}
>
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</div>
</motion.section>
<motion.section
initial={{ opacity: 0, y: 20 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.4 }}
className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-12 border border-[#C41E3A]/20"
>
<div className="flex items-start gap-6 mb-8">
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center flex-shrink-0">
<Users className="w-8 h-8 text-white" />
</div>
<div>
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-2">
·
</h2>
<p className="text-lg text-[#5C5C5C]">
</p>
</div>
</div>
<div className="space-y-6 mb-8">
<p className="text-lg text-[#1C1C1C] leading-relaxed">
线
</p>
<p className="text-lg text-[#1C1C1C] leading-relaxed">
访
</p>
<p className="text-lg text-[#1C1C1C] leading-relaxed">
</p>
</div>
<div className="mb-8">
<h3 className="text-xl font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
<CheckCircle2 className="w-6 h-6 text-[#C41E3A]" />
</h3>
<div className="grid md:grid-cols-3 gap-4">
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]"></span>
</div>
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]"></span>
</div>
<div className="flex items-start gap-3 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<div className="w-2 h-2 bg-[#C41E3A] rounded-full mt-2" />
<span className="text-[#1C1C1C]">7×24</span>
</div>
</div>
</div>
<div className="flex justify-center">
<Button
size="lg"
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
onClick={() => {
const element = document.getElementById('contact');
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}}
>
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</div>
</motion.section>
<p className="text-sm font-medium text-[#C41E3A] mb-4 tracking-wide uppercase">Solutions</p>
<h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6 tracking-tight">
</h1>
<p className="text-lg text-[#595959] leading-relaxed">
</p>
</motion.div>
</div>
</div>
</section>
<section className="pb-20">
<div className="container-wide">
<div className="max-w-6xl mx-auto space-y-8">
{modules.map((module, index) => (
<motion.section
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="p-8 md:p-12 rounded-xl border border-[#E5E5E5] bg-white hover:border-[#C41E3A]/30 transition-colors"
>
<div className="flex items-start gap-4 mb-6">
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center shrink-0">
<module.icon className="w-6 h-6 text-white" />
</div>
<div>
<h2 className="text-2xl font-bold text-[#1C1C1C] mb-1">
{['一', '二', '三'][index]}{module.title}
</h2>
<p className="text-[#595959]">{module.subtitle}</p>
</div>
</div>
<div className="space-y-4 mb-8">
{module.paragraphs.map((p, i) => (
<p key={i} className="text-[#1C1C1C] leading-relaxed">{p}</p>
))}
</div>
<div className="mb-8">
<h3 className="text-lg font-semibold text-[#1C1C1C] mb-4 flex items-center gap-2">
<CheckCircle2 className="w-5 h-5 text-[#C41E3A]" />
</h3>
<div className="grid md:grid-cols-3 gap-3">
{module.values.map((value, i) => (
<div key={i} className="flex items-start gap-2 p-3 bg-[#F5F5F5] rounded-lg">
<div className="w-1.5 h-1.5 bg-[#C41E3A] rounded-full mt-2 shrink-0" />
<span className="text-sm text-[#1C1C1C]">{value}</span>
</div>
))}
</div>
</div>
<div className="flex justify-center">
<Button
size="lg"
variant={module.ctaVariant}
className={module.ctaVariant === 'default' ? 'bg-[#C41E3A] hover:bg-[#A01830] text-white' : 'border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A] hover:text-white'}
asChild
>
<StaticLink href="/contact">
{module.cta}
<ArrowRight className="ml-2 w-4 h-4" />
</StaticLink>
</Button>
</div>
</motion.section>
))}
</div>
</div>
</section>
<MethodologySection />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isContentInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.6 }}
className="bg-[#F5F5F5] py-16"
>
<section className="bg-[#F5F5F5] py-20">
<div className="container-wide text-center">
<h2 className="text-3xl font-bold text-[#1C1C1C] mb-6">
</h2>
<p className="text-lg text-[#5C5C5C] mb-8 max-w-2xl mx-auto">
<p className="text-lg text-[#595959] mb-8 max-w-2xl mx-auto">
</p>
<div className="flex justify-center gap-4">
<Button
size="lg"
variant="outline"
asChild
>
<Button size="lg" variant="outline" asChild>
<StaticLink href="/contact"></StaticLink>
</Button>
<Button
size="lg"
className="bg-[#C41E3A] hover:bg-[#A01830] text-white"
asChild
>
<Button size="lg" className="bg-[#C41E3A] hover:bg-[#A01830] text-white" asChild>
<StaticLink href="/contact">
<ArrowRight className="ml-2 w-4 h-4" />
@@ -272,7 +156,7 @@ export default function SolutionsPage() {
</Button>
</div>
</div>
</motion.div>
</section>
</div>
);
}