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
+60 -115
View File
@@ -1,18 +1,16 @@
'use client';
import { useRef } from 'react';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { BackButton } from '@/components/ui/back-button';
import {
CheckCircle2,
TrendingUp,
Users,
Target,
Clock,
import {
CheckCircle2,
TrendingUp,
Users,
Target,
Clock,
MessageCircle,
ArrowRight
ArrowRight,
} from 'lucide-react';
import { SERVICES, CASES } from '@/lib/constants';
@@ -20,30 +18,7 @@ interface ServiceDetailClientProps {
service: typeof SERVICES[number];
}
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
Code: () => (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
),
BarChart3: () => (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
),
Lightbulb: () => (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
),
Puzzle: () => (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z" />
</svg>
),
};
const challenges = {
const challenges: Record<string, { title: string; description: string }[]> = {
software: [
{ title: '需求不明确', description: '业务部门提不出清晰需求,开发团队反复返工' },
{ title: '技术选型困难', description: '技术栈更新快,不知道该选什么技术方案' },
@@ -70,7 +45,7 @@ const challenges = {
],
};
const outcomes = {
const outcomes: Record<string, { value: string; label: string }[]> = {
software: [
{ value: '30%', label: '开发效率提升' },
{ value: '50%', label: '返工率降低' },
@@ -94,162 +69,134 @@ const outcomes = {
};
export function ServiceDetailClient({ service }: ServiceDetailClientProps) {
const contentRef = useRef<HTMLDivElement>(null);
const serviceChallenges = challenges[service.id as keyof typeof challenges] ?? [];
const serviceOutcomes = outcomes[service.id as keyof typeof outcomes] ?? [];
const serviceChallenges = challenges[service.id] ?? [];
const serviceOutcomes = outcomes[service.id] ?? [];
const relatedCases = CASES.slice(0, 2);
const Icon = iconMap[service.icon];
return (
<div className="min-h-screen bg-white">
<div className="relative overflow-hidden bg-gradient-to-b from-[#FAFAFA] to-white">
<div className="container-wide relative z-10 pt-32 pb-20">
<div className="pt-32 pb-16">
<div className="container-wide">
<BackButton />
<div className="max-w-4xl mt-8">
<div className="flex items-center gap-4 mb-6">
<div className="w-16 h-16 bg-[#C41E3A] rounded-2xl flex items-center justify-center text-white">
{Icon && <Icon />}
</div>
<div>
<Badge className="mb-2 bg-[#C41E3A]/10 text-[#C41E3A] hover:bg-[#C41E3A]/20">
</Badge>
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-semibold text-[#1C1C1C]">
{service.title}
</h1>
</div>
</div>
<p className="text-lg text-[#5C5C5C] leading-relaxed">
<p className="text-sm font-medium text-[#C41E3A] mb-4 tracking-wide uppercase"></p>
<h1 className="text-4xl md:text-5xl font-bold text-[#1C1C1C] mb-6 tracking-tight">
{service.title}
</h1>
<p className="text-xl text-[#595959] leading-relaxed">
{service.description}
</p>
</div>
</div>
</div>
<div ref={contentRef} className="container-wide py-12 md:py-16">
<div className="max-w-4xl mx-auto space-y-16">
<section className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-8 border border-[#C41E3A]/20">
<div className="container-wide pb-20">
<div className="max-w-4xl space-y-16">
<section>
<div className="flex items-center gap-3 mb-6">
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
<MessageCircle className="w-6 h-6 text-white" />
<div className="w-10 h-10 bg-[#FEF2F4] rounded-lg flex items-center justify-center">
<MessageCircle className="w-5 h-5 text-[#C41E3A]" />
</div>
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
</h2>
<h2 className="text-2xl font-bold text-[#1C1C1C]"></h2>
</div>
<div className="grid md:grid-cols-2 gap-4">
{serviceChallenges.map((challenge, index) => (
<div
<div
key={index}
className="p-4 bg-white rounded-lg border border-[#E5E5E5] hover:border-[#C41E3A] transition-colors"
className="p-4 bg-[#F5F5F5] rounded-lg hover:bg-[#FEF2F4] transition-colors"
>
<h3 className="font-semibold text-[#1C1C1C] mb-2">{challenge.title}</h3>
<p className="text-sm text-[#5C5C5C]">{challenge.description}</p>
<h3 className="font-semibold text-[#1C1C1C] mb-1 text-sm">{challenge.title}</h3>
<p className="text-sm text-[#595959]">{challenge.description}</p>
</div>
))}
</div>
</section>
<section className="bg-gradient-to-br from-[#F5F7FA] to-white rounded-2xl p-8 border border-[#E5E5E5]">
<section>
<div className="flex items-center gap-3 mb-6">
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
<Target className="w-6 h-6 text-white" />
<div className="w-10 h-10 bg-[#FEF2F4] rounded-lg flex items-center justify-center">
<Target className="w-5 h-5 text-[#C41E3A]" />
</div>
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
</h2>
<h2 className="text-2xl font-bold text-[#1C1C1C]"></h2>
</div>
<p className="text-lg text-[#5C5C5C] leading-relaxed mb-6">
<p className="text-lg text-[#595959] leading-relaxed mb-6">
{service.overview}
</p>
<div className="space-y-3">
{service.features.map((feature, index) => (
<div key={index} className="flex items-start gap-3">
<CheckCircle2 className="w-5 h-5 text-[#C41E3A] mt-0.5 flex-shrink-0" />
<span className="text-[#1C1C1C]">{feature}</span>
<CheckCircle2 className="w-5 h-5 text-[#C41E3A] mt-0.5 shrink-0" />
<span className="text-[#1C1C1C] text-sm">{feature}</span>
</div>
))}
</div>
</section>
<section className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-8 border border-[#C41E3A]/20">
<section>
<div className="flex items-center gap-3 mb-6">
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
<Clock className="w-6 h-6 text-white" />
<div className="w-10 h-10 bg-[#FEF2F4] rounded-lg flex items-center justify-center">
<Clock className="w-5 h-5 text-[#C41E3A]" />
</div>
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
</h2>
<h2 className="text-2xl font-bold text-[#1C1C1C]"></h2>
</div>
<div className="space-y-4">
{service.process.map((step, index) => (
<div key={index} className="flex items-start gap-4">
<div className="w-10 h-10 bg-[#C41E3A] rounded-full flex items-center justify-center flex-shrink-0 text-white font-semibold">
<div className="w-8 h-8 bg-[#C41E3A] rounded-full flex items-center justify-center shrink-0 text-white text-sm font-bold">
{index + 1}
</div>
<div className="flex-1 pb-4">
<p className="text-[#1C1C1C] font-medium">{step}</p>
</div>
<p className="text-[#1C1C1C] pt-1">{step}</p>
</div>
))}
</div>
</section>
<section className="bg-gradient-to-br from-[#F5F7FA] to-white rounded-2xl p-8 border border-[#E5E5E5]">
<section>
<div className="flex items-center gap-3 mb-6">
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
<TrendingUp className="w-6 h-6 text-white" />
<div className="w-10 h-10 bg-[#FEF2F4] rounded-lg flex items-center justify-center">
<TrendingUp className="w-5 h-5 text-[#C41E3A]" />
</div>
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
</h2>
<h2 className="text-2xl font-bold text-[#1C1C1C]"></h2>
</div>
<div className="grid sm:grid-cols-3 gap-4">
{serviceOutcomes.map((outcome, index) => (
<div
<div
key={index}
className="p-6 bg-white rounded-lg border border-[#E5E5E5] hover:border-[#C41E3A] transition-colors text-center"
className="p-6 bg-[#F5F5F5] rounded-lg text-center hover:bg-[#FEF2F4] transition-colors"
>
<div className="text-3xl font-bold text-[#C41E3A] mb-2">
{outcome.value}
</div>
<div className="text-sm text-[#5C5C5C]">{outcome.label}</div>
<div className="text-sm text-[#595959]">{outcome.label}</div>
</div>
))}
</div>
<div className="mt-6 p-4 bg-white rounded-lg border border-[#E5E5E5]">
<p className="text-sm text-[#5C5C5C]">
{service.benefits.map(b => b).join('')}
<div className="mt-4 p-4 bg-[#F5F5F5] rounded-lg">
<p className="text-sm text-[#595959]">
{service.benefits.join('')}
</p>
</div>
</section>
<section className="bg-gradient-to-br from-[#FFFBF5] to-white rounded-2xl p-8 border border-[#C41E3A]/20">
<section>
<div className="flex items-center gap-3 mb-6">
<div className="w-12 h-12 bg-[#C41E3A] rounded-xl flex items-center justify-center">
<Users className="w-6 h-6 text-white" />
<div className="w-10 h-10 bg-[#FEF2F4] rounded-lg flex items-center justify-center">
<Users className="w-5 h-5 text-[#C41E3A]" />
</div>
<h2 className="text-2xl font-semibold text-[#1C1C1C]">
</h2>
<h2 className="text-2xl font-bold text-[#1C1C1C]"></h2>
</div>
<div className="grid md:grid-cols-2 gap-4">
{relatedCases.map((caseItem) => (
<StaticLink
key={caseItem.id}
href={`/cases/${caseItem.id}`}
className="group p-4 bg-white rounded-lg border border-[#E5E5E5] hover:border-[#C41E3A] transition-colors"
className="group p-4 bg-[#F5F5F5] rounded-lg hover:bg-[#FEF2F4] transition-colors"
>
<Badge variant="secondary" className="mb-2">
{caseItem.industry}
</Badge>
<h3 className="font-semibold text-[#1C1C1C] group-hover:text-[#C41E3A] transition-colors">
<p className="text-xs text-[#C41E3A] font-medium mb-1">{caseItem.industry}</p>
<h3 className="font-semibold text-[#1C1C1C] group-hover:text-[#C41E3A] transition-colors text-sm">
{caseItem.title}
</h3>
<p className="text-sm text-[#5C5C5C] mt-2 line-clamp-2">
<p className="text-sm text-[#595959] mt-2 line-clamp-2">
{caseItem.description}
</p>
</StaticLink>
@@ -259,9 +206,7 @@ export function ServiceDetailClient({ service }: ServiceDetailClientProps) {
<div className="flex justify-center gap-4 pt-8 border-t border-[#E5E5E5]">
<StaticLink href="/services">
<Button variant="outline" size="lg">
</Button>
<Button variant="outline" size="lg"></Button>
</StaticLink>
<StaticLink href="/contact">
<Button size="lg" className="bg-[#C41E3A] hover:bg-[#A01830] text-white">