feat: 营销页面HSI架构改造与四层叙事对齐

- 产品页:双区展示(企业套装+专业产品),分类筛选,统计数据更新
- 方案页:删除服务方式区域,替换为轻量关联推荐卡片
- 方案详情页:添加L3信任层条件渲染,使用V3 Hero+V2 CTA
- 独立产品页:从V1组件迁移到V2/V3
- 服务页:视觉升级与组件更新
- 首页:产品矩阵和CTA区域优化
This commit is contained in:
张翔
2026-06-07 16:21:15 +08:00
parent 724a00f582
commit 38ae991ea7
13 changed files with 740 additions and 727 deletions
+11 -5
View File
@@ -4,11 +4,6 @@ import dynamic from 'next/dynamic';
import { HeroSectionV2 } from '@/components/sections/hero-section-v2';
import { SectionSkeleton } from '@/components/ui/loading-skeleton';
const SocialProofSection = dynamic(
() => import('@/components/sections/social-proof-section').then(mod => ({ default: mod.SocialProofSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
const ProductMatrixSection = dynamic(
() => import('@/components/sections/product-matrix-section').then(mod => ({ default: mod.ProductMatrixSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
@@ -19,6 +14,16 @@ const ChallengeSection = dynamic(
{ loading: () => <SectionSkeleton />, ssr: false }
);
const SocialProofSection = dynamic(
() => import('@/components/sections/social-proof-section').then(mod => ({ default: mod.SocialProofSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
const WhyUsSection = dynamic(
() => import('@/components/sections/why-us-section').then(mod => ({ default: mod.WhyUsSection })),
{ loading: () => <SectionSkeleton />, ssr: false }
);
const CTASection = dynamic(
() => import('@/components/sections/cta-section').then(mod => ({ default: mod.CTASection })),
{ loading: () => <SectionSkeleton />, ssr: false }
@@ -31,6 +36,7 @@ function HomeContentV2() {
<ProductMatrixSection />
<ChallengeSection />
<SocialProofSection />
<WhyUsSection />
<CTASection />
</main>
);
@@ -27,7 +27,9 @@ jest.mock('@/lib/constants', () => ({
{
id: 'test-product',
title: '测试产品',
category: '企业软件',
category: '企业旗舰系列',
categoryId: 'enterprise',
tags: ['测试', '示例'],
status: '研发中',
description: '这是测试产品描述',
overview: '这是测试产品概述',
@@ -37,6 +39,13 @@ jest.mock('@/lib/constants', () => ({
specs: ['规格1', '规格2'],
},
],
PRODUCT_CATEGORIES: [
{
id: 'enterprise',
title: '企业旗舰系列',
description: '测试分类描述',
},
],
}));
describe('ProductDetailPage', () => {
@@ -66,7 +75,7 @@ describe('ProductDetailPage', () => {
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
render(page);
const category = screen.getByText('企业软件');
const category = screen.getByText('企业旗舰系列');
expect(category).toBeInTheDocument();
});
+39 -142
View File
@@ -1,10 +1,13 @@
import { notFound } from 'next/navigation';
import { StaticLink } from '@/components/ui/static-link';
import { PRODUCTS, COMPANY_INFO } from '@/lib/constants';
import { Button } from '@/components/ui/button';
import { PageNav } from '@/components/layout/page-nav';
import { getHeroTheme } from '@/lib/constants/hero-themes';
import { getProductCrossRefs } from '@/lib/constants/cross-references';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { CheckCircle2, Zap, Target, Layers, ArrowRight, FlaskConical } from 'lucide-react';
import { DetailHeroV3 } from '@/components/detail-v2/detail-hero-v3';
import { ProductValueSectionV3 } from '@/components/detail-v2/detail-product-value-v3';
import { DetailTrustSectionV2 } from '@/components/detail-v2/detail-trust-section-v2';
import { DetailCTASectionV2 } from '@/components/detail-v2/detail-cta-section-v2';
import { CrossRecommendGrid } from '@/components/detail/detail-cross-recommend';
export async function generateStaticParams() {
return PRODUCTS.map((product) => ({
@@ -22,7 +25,7 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin
return {
title: `${product.title} - ${COMPANY_INFO.displayName}`,
description: product.description,
description: `${product.description} | 现代东方美学设计`,
};
}
@@ -34,151 +37,45 @@ export default async function ProductDetailPage({ params }: { params: Promise<{
notFound();
}
const theme = getHeroTheme(product.heroThemeId);
const crossRefs = getProductCrossRefs(product.id);
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<DetailSwipeNav type="product" currentId={product.id} />
<div className="pt-32 pb-16">
<div className="container-wide">
<PageNav items={[{ label: '产品', href: '/products' }, { label: product.title }]} />
<div className="max-w-4xl mt-8">
<div className="flex items-center gap-3 mb-4">
<p className="text-sm font-medium text-[var(--color-brand-primary)] tracking-wide uppercase">{product.category}</p>
<span
className="text-xs font-medium px-3 py-1 rounded-full border"
style={{
backgroundColor: 'rgba(var(--color-brand-primary-rgb), 0.08)',
color: 'var(--color-brand-primary)',
borderColor: 'rgba(var(--color-brand-primary-rgb), 0.15)',
}}
>
{product.status}
</span>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-[var(--color-text-primary)] mb-6 tracking-tight">
{product.title}
</h1>
<p className="text-xl text-[var(--color-text-muted)] leading-relaxed">
{product.description}
</p>
</div>
</div>
</div>
<div className="container-wide pb-20">
<div className="max-w-4xl space-y-16">
<section aria-labelledby="product-overview">
<h2 id="product-overview" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6"></h2>
<p className="text-lg text-[var(--color-text-muted)] leading-relaxed">
{product.overview}
</p>
</section>
{/* L1 - Hero 情感入口 (V3) */}
<DetailHeroV3
theme={theme}
badge={product.category}
title={product.title}
subtitle={product.description}
description={product.overview}
primaryAction={{ label: '预约体验', href: '/contact' }}
secondaryAction={{ label: '查看方案', href: '/solutions' }}
/>
<section aria-labelledby="product-features">
<h2 id="product-features" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3">
<Zap className="w-6 h-6 text-[var(--color-brand-primary)]" />
</h2>
<div className="grid md:grid-cols-2 gap-4">
{product.features.map((feature, index) => (
<div
key={index}
className="flex items-start gap-3 p-4 bg-[var(--color-bg-tertiary)] rounded-lg hover:bg-[var(--color-brand-primary-bg)] transition-colors"
>
<CheckCircle2 className="w-5 h-5 text-[var(--color-brand-primary)] mt-0.5 shrink-0" />
<span className="text-[var(--color-text-primary)] text-sm">{feature}</span>
</div>
))}
</div>
</section>
{/* L2 - 价值理性支撑 (V3) */}
<ProductValueSectionV3 product={product} />
<section aria-labelledby="product-benefits">
<h2 id="product-benefits" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3">
<Target className="w-6 h-6 text-[var(--color-brand-primary)]" />
</h2>
<div className="space-y-3">
{product.benefits.map((benefit, index) => (
<div
key={index}
className="flex items-start gap-3 p-4 rounded-lg bg-[var(--color-brand-primary-bg)]"
>
<div className="w-1.5 h-1.5 bg-[var(--color-brand-primary)] rounded-full mt-2 shrink-0" />
<span className="text-[var(--color-text-primary)] font-medium text-sm">{benefit}</span>
</div>
))}
</div>
</section>
{/* L3 - 信任证明 (V2) */}
<DetailTrustSectionV2
caseStudies={product.caseStudies}
dataProofs={product.dataProofs}
certifications={product.certifications}
accentColor={theme.accentColor}
/>
<section aria-labelledby="product-process">
<h2 id="product-process" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3">
<Layers className="w-6 h-6 text-[var(--color-brand-primary)]" />
</h2>
<div className="space-y-4">
{product.process.map((step, index) => (
<div key={index} className="flex items-start gap-4">
<div className="w-8 h-8 bg-[var(--color-brand-primary)] rounded-full flex items-center justify-center shrink-0 text-white text-sm font-bold">
{index + 1}
</div>
<p className="text-[var(--color-text-primary)] pt-1">{step}</p>
</div>
))}
</div>
</section>
{/* L4 - CTA + 交叉推荐 (V2) */}
<DetailCTASectionV2
theme={theme}
title={`探索 ${product.title} 的无限可能`}
description="立即联系我们,获取专属方案和报价"
primaryAction={{ label: '预约体验', href: '/contact' }}
secondaryAction={{ label: '下载产品手册', href: '#' }}
/>
<section aria-labelledby="product-specs">
<h2 id="product-specs" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6"></h2>
<div className="grid md:grid-cols-2 gap-3">
{product.specs.map((spec, index) => (
<div
key={index}
className="flex items-center gap-3 p-3 bg-[var(--color-bg-tertiary)] rounded-lg"
>
<div className="w-1.5 h-1.5 bg-[var(--color-brand-primary)] rounded-full" />
<span className="text-[var(--color-text-primary)] text-sm">{spec}</span>
</div>
))}
</div>
</section>
<section className="bg-[var(--color-brand-primary-bg)] rounded-2xl p-8 md:p-10" aria-labelledby="product-pricing">
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-[var(--color-brand-primary)] rounded-xl flex items-center justify-center shrink-0">
<FlaskConical className="w-6 h-6 text-white" />
</div>
<div>
<h2 id="product-pricing" className="text-2xl font-bold text-[var(--color-text-primary)] mb-3"></h2>
<p className="text-[var(--color-text-muted)] leading-relaxed mb-6">
</p>
<div className="flex flex-wrap gap-3">
<Button size="lg" className="bg-[var(--color-brand-primary)] hover:bg-[var(--color-brand-primary-hover)] text-white" asChild>
<StaticLink href="/contact">
<ArrowRight className="ml-2 w-4 h-4" />
</StaticLink>
</Button>
<Button variant="outline" size="lg" asChild>
<StaticLink href="/contact"></StaticLink>
</Button>
</div>
</div>
</div>
</section>
<div className="flex justify-center gap-4 pt-8 border-t border-[var(--color-border-primary)]">
<Button variant="outline" size="lg" asChild>
<StaticLink href="/contact"></StaticLink>
</Button>
<Button size="lg" className="bg-[var(--color-brand-primary)] hover:bg-[var(--color-brand-primary-hover)] text-white" asChild>
<StaticLink href="/contact">
<ArrowRight className="ml-2 w-4 h-4" />
</StaticLink>
</Button>
</div>
</div>
</div>
<CrossRecommendGrid items={crossRefs} title={`${product.title} 相关的推荐`} />
</div>
);
}
@@ -0,0 +1,57 @@
import { Metadata } from 'next';
import { PRODUCTS, COMPANY_INFO } from '@/lib/constants';
import { getHeroTheme } from '@/lib/constants/hero-themes';
import { getProductCrossRefs } from '@/lib/constants/cross-references';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { DetailHeroV3 } from '@/components/detail-v2/detail-hero-v3';
import { ProductValueSectionV3 } from '@/components/detail-v2/detail-product-value-v3';
import { DetailTrustSectionV2 } from '@/components/detail-v2/detail-trust-section-v2';
import { DetailCTASectionV2 } from '@/components/detail-v2/detail-cta-section-v2';
import { CrossRecommendGrid } from '@/components/detail/detail-cross-recommend';
export const metadata: Metadata = {
title: `睿新ERP管理系统(V3深度精细化版) - ${COMPANY_INFO.displayName}`,
description: 'ERP产品页V3版本 - 现代东方美学设计系统 + L1-L5全层次精细化打磨',
};
export default function ERPUpgradeV3Page() {
const product = PRODUCTS.find((p) => p.id === 'erp')!;
const theme = getHeroTheme(product.heroThemeId);
const crossRefs = getProductCrossRefs(product.id);
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<DetailSwipeNav type="product" currentId={product.id} />
<DetailHeroV3
theme={theme}
badge="企业旗舰系列"
title={product.title}
subtitle={product.description}
description={product.overview}
primaryAction={{ label: '预约体验', href: '/contact' }}
secondaryAction={{ label: '查看方案', href: '/solutions' }}
/>
<ProductValueSectionV3 product={product} />
<DetailTrustSectionV2
caseStudies={product.caseStudies}
dataProofs={product.dataProofs}
certifications={product.certifications}
accentColor={theme.accentColor}
/>
<DetailCTASectionV2
theme={theme}
title={`探索 ${product.title} 的无限可能`}
description="立即联系我们,获取专属方案和报价"
primaryAction={{ label: '预约体验', href: '/contact' }}
secondaryAction={{ label: '下载产品手册', href: '#' }}
/>
<CrossRecommendGrid items={crossRefs} title={`${product.title} 相关的推荐`} />
</div>
);
}
@@ -0,0 +1,57 @@
import { Metadata } from 'next';
import { PRODUCTS, COMPANY_INFO } from '@/lib/constants';
import { getHeroTheme } from '@/lib/constants/hero-themes';
import { getProductCrossRefs } from '@/lib/constants/cross-references';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { DetailHeroV2 } from '@/components/detail-v2/detail-hero-v2';
import { ProductValueSectionV2 } from '@/components/detail-v2/detail-product-value-v2';
import { DetailTrustSectionV2 } from '@/components/detail-v2/detail-trust-section-v2';
import { DetailCTASectionV2 } from '@/components/detail-v2/detail-cta-section-v2';
import { CrossRecommendGrid } from '@/components/detail/detail-cross-recommend';
export const metadata: Metadata = {
title: `睿新ERP管理系统(升级版原型) - ${COMPANY_INFO.displayName}`,
description: '升级版ERP产品页 - 现代东方美学设计系统',
};
export default function ERPUpgradePage() {
const product = PRODUCTS.find((p) => p.id === 'erp')!;
const theme = getHeroTheme(product.heroThemeId);
const crossRefs = getProductCrossRefs(product.id);
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<DetailSwipeNav type="product" currentId={product.id} />
<DetailHeroV2
theme={theme}
badge="企业旗舰系列"
title={product.title}
subtitle={product.description}
description={product.overview}
primaryAction={{ label: '预约体验', href: '/contact' }}
secondaryAction={{ label: '查看方案', href: '/solutions' }}
/>
<ProductValueSectionV2 product={product} />
<DetailTrustSectionV2
caseStudies={product.caseStudies}
dataProofs={product.dataProofs}
certifications={product.certifications}
accentColor={theme.accentColor}
/>
<DetailCTASectionV2
theme={theme}
title={`探索 ${product.title} 的无限可能`}
description="立即联系我们,获取专属方案和报价"
primaryAction={{ label: '预约体验', href: '/contact' }}
secondaryAction={{ label: '下载产品手册', href: '#' }}
/>
<CrossRecommendGrid items={crossRefs} title={`${product.title} 相关的推荐`} />
</div>
);
}
+137 -31
View File
@@ -1,63 +1,168 @@
'use client';
import { useState, useEffect } from 'react';
import { PRODUCTS } from '@/lib/constants';
import { ProductCard } from '@/components/ui/product-card';
import { useState, useEffect, useMemo } from 'react';
import { PRODUCTS, PRODUCT_CATEGORIES, type ProductCategory as ProductCategoryType } from '@/lib/constants';
import { motion } from 'framer-motion';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
import { PageNav } from '@/components/layout/page-nav';
import { CTASection } from '@/components/sections/cta-section';
import { BreadcrumbSchema } from '@/components/seo/structured-data';
import { LoadingState } from '@/components/ui/loading-state';
import { ListPageHeroV3 } from '@/components/detail-v2/list-page-hero-v3';
import { ProductCardV3 } from '@/components/detail-v2/product-card-v3';
import { Package, Lock, Cpu, ArrowRight } from 'lucide-react';
const enterpriseProducts = PRODUCTS.filter(p => p.categoryId === 'enterprise');
const specializedProducts = PRODUCTS.filter(p => p.categoryId === 'specialized');
export default function ProductsPage() {
const shouldReduceMotion = useReducedMotion();
const fadeUp = shouldReduceMotion ? {} : { initial: { opacity: 0, y: 20 } };
const [isLoading, setIsLoading] = useState(true);
const [activeCategory, setActiveCategory] = useState<string>('all');
useEffect(() => {
const timer = setTimeout(() => setIsLoading(false), 500);
return () => clearTimeout(timer);
}, []);
const filteredProducts = useMemo(() => {
if (activeCategory === 'all') return PRODUCTS;
return PRODUCTS.filter(product => product.categoryId === activeCategory);
}, [activeCategory]);
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<BreadcrumbSchema items={[{ name: '首页', href: '/' }, { name: '产品', href: '/products' }]} />
<section className="pt-32 pb-16">
{/* Hero 区域 (V3) */}
<ListPageHeroV3
title="企业软件产品矩阵"
subtitle="从ERP到BI,覆盖企业数字化全场景"
description="六大核心产品互为补充,可根据业务需求灵活组合"
stats={[
{ value: '6', label: '核心产品' },
{ value: '2', label: '产品分区' },
{ value: '99%', label: '自主可控' },
]}
/>
{/* 双区展示:企业套装 + 专业产品 */}
<section className="pb-16">
<div className="container-wide">
<PageNav items={[{ label: '产品' }]} />
{/* 企业套装区域 */}
<motion.div
{...fadeUp}
animate={shouldReduceMotion ? {} : { opacity: 1, y: 0 }}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="max-w-3xl"
className="mb-16"
>
<p className="text-sm font-medium text-[var(--color-brand-primary)] mb-4 tracking-wide uppercase">Products</p>
<h1 className="text-4xl md:text-5xl font-bold text-[var(--color-text-primary)] mb-6 tracking-tight">
</h1>
<p className="text-lg text-[var(--color-text-muted)] leading-relaxed">
<div className="mb-8 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-50">
<Package className="h-5 w-5 text-blue-600" />
</div>
<div>
<h2 className="text-xl font-bold text-gray-900"></h2>
<p className="text-sm text-gray-500">6 </p>
</div>
</div>
<LoadingState isLoading={isLoading} variant="card">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{enterpriseProducts.map((product) => (
<ProductCardV3 key={product.id} product={product} />
))}
</div>
</LoadingState>
</motion.div>
{/* 专业产品区域 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.15 }}
>
<div className="mb-8 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-orange-50">
<Cpu className="h-5 w-5 text-orange-600" />
</div>
<div>
<h2 className="text-xl font-bold text-gray-900"></h2>
<p className="text-sm text-gray-500">线</p>
</div>
</div>
{specializedProducts.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{specializedProducts.map((product) => (
<ProductCardV3 key={product.id} product={product} />
))}
</div>
) : (
<div className="rounded-2xl border-2 border-dashed border-gray-200 bg-gray-50/50 p-12 text-center">
<Lock className="mx-auto mb-4 h-12 w-12 text-gray-300" />
<h3 className="mb-2 text-lg font-semibold text-gray-700">线</h3>
<p className="mb-6 max-w-md mx-auto text-sm text-gray-500">
线
</p>
<a
href="/contact"
className="inline-flex items-center gap-2 rounded-lg bg-[#C41E3A] px-6 py-3 text-sm font-semibold text-white shadow-lg transition-all hover:bg-[#a01829] hover:shadow-xl"
>
<ArrowRight className="h-4 w-4" />
</a>
</div>
)}
</motion.div>
</div>
</section>
<section className="pb-20">
{/* 分类标签导航(保留作为辅助筛选) */}
<section className="border-t border-gray-100 bg-gray-50/50 py-12">
<div className="container-wide">
<LoadingState isLoading={isLoading} variant="card">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{PRODUCTS.map((product, index) => (
<ProductCard
key={product.id}
title={product.title}
description={product.description}
href={`/products/${product.id}`}
index={index}
status={product.status}
/>
<h3 className="mb-6 text-center text-lg font-semibold text-gray-900"></h3>
<div className="flex flex-wrap justify-center gap-2">
<button
onClick={() => setActiveCategory('all')}
className={`px-5 py-2.5 text-sm font-medium rounded-full transition-all ${
activeCategory === 'all'
? 'bg-[#C41E3A] text-white shadow-md'
: 'bg-white text-gray-600 border border-gray-200 hover:border-gray-300'
}`}
>
({PRODUCTS.length})
</button>
{PRODUCT_CATEGORIES.map((category: ProductCategoryType) => {
const count = PRODUCTS.filter(p => p.categoryId === category.id).length;
return (
<button
key={category.id}
onClick={() => setActiveCategory(category.id)}
className={`px-5 py-2.5 text-sm font-medium rounded-full transition-all ${
activeCategory === category.id
? 'bg-[#C41E3A] text-white shadow-md'
: 'bg-white text-gray-600 border border-gray-200 hover:border-gray-300'
}`}
>
{category.title} ({count})
</button>
);
})}
</div>
{activeCategory !== 'all' && filteredProducts.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-8"
>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredProducts.map((product) => (
<ProductCardV3 key={product.id} product={product} />
))}
</div>
</LoadingState>
</motion.div>
)}
</div>
</section>
@@ -72,3 +177,4 @@ export default function ProductsPage() {
</div>
);
}
@@ -0,0 +1,179 @@
'use client';
import { getHeroTheme } from '@/lib/constants/hero-themes';
import { DetailHeroV3 } from '@/components/detail-v2/detail-hero-v3';
import { DetailTrustSectionV2 } from '@/components/detail-v2/detail-trust-section-v2';
import { DetailCTASectionV2 } from '@/components/detail-v2/detail-cta-section-v2';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { motion } from 'framer-motion';
import { CheckCircle2, Clock, Shield, Lock, Cpu, HardDrive, ArrowRight } from 'lucide-react';
import type { StandaloneProduct } from '@/lib/constants/standalone-products';
interface StandaloneProductClientProps {
product: StandaloneProduct;
}
const categoryIcons: Record<string, typeof Shield> = {
security: Shield,
'specialized-software': Cpu,
hardware: HardDrive,
};
export function StandaloneProductClient({ product }: StandaloneProductClientProps) {
const theme = getHeroTheme(product.heroThemeId);
const Icon = categoryIcons[product.category] || Shield;
return (
<div className="min-h-screen">
<DetailSwipeNav type="product" currentId={product.id} />
<DetailHeroV3
theme={theme}
badge={product.status === 'coming-soon' ? '敬请期待' : product.badge}
title={product.title}
subtitle={product.description}
primaryAction={
product.status === 'coming-soon'
? undefined
: { label: '了解详情', href: '/contact' }
}
secondaryAction={{ label: '返回产品列表', href: '/products' }}
/>
{product.status === 'coming-soon' ? (
<section className="bg-white py-16 lg:py-20">
<div className="container-wide">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="max-w-3xl mx-auto text-center"
>
<div className="mb-6 flex justify-center">
<div className="flex h-20 w-20 items-center justify-center rounded-2xl bg-gray-100 text-gray-400">
<Lock className="h-10 w-10" />
</div>
</div>
<h2 className="mb-4 text-2xl font-bold text-gray-900">线</h2>
<p className="mb-8 text-gray-600 leading-relaxed">
{product.title}{product.category === 'security' ? '安全防护' : product.category === 'specialized-software' ? '行业解决方案' : '硬件产品'}
</p>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 mb-10">
{product.previewFeatures.map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: index * 0.08 }}
className="rounded-lg border border-gray-200 bg-gray-50 p-4"
>
<div className="mb-2 flex h-8 w-8 items-center justify-center rounded-lg bg-white text-gray-400">
<Icon className="h-4 w-4" />
</div>
<h3 className="mb-1 text-sm font-semibold text-gray-900">{feature.title}</h3>
<p className="text-xs leading-relaxed text-gray-500">{feature.description}</p>
</motion.div>
))}
</div>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<a
href="/contact"
className="inline-flex items-center gap-2 rounded-lg bg-[#C41E3A] px-8 py-3 text-sm font-semibold text-white shadow-md transition-all hover:bg-[#C41E3A]/90 hover:shadow-lg"
>
线
<ArrowRight className="h-4 w-4" />
</a>
<a
href="/products"
className="inline-flex items-center rounded-lg border border-gray-300 px-6 py-3 text-sm font-semibold text-gray-700 transition-all hover:border-gray-400 hover:bg-gray-50"
>
</a>
</div>
{product.expectedLaunch && (
<p className="mt-6 text-sm text-gray-500">
线<span className="font-medium text-gray-700">{product.expectedLaunch}</span>
</p>
)}
</motion.div>
</div>
</section>
) : (
<>
<section className="bg-white py-16 lg:py-20">
<div className="container-wide">
<div className="grid grid-cols-1 gap-12 lg:grid-cols-2">
<motion.div
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<h2 className="mb-6 text-2xl font-bold text-gray-900"></h2>
<ul className="space-y-3">
{(product.features ?? []).map((feature, index) => (
<motion.li
key={index}
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: index * 0.05 }}
className="flex items-start gap-3"
>
<CheckCircle2 className="mt-0.5 h-5 w-5 shrink-0 text-[#C41E3A]" />
<span className="text-sm leading-relaxed text-gray-700">{feature}</span>
</motion.li>
))}
</ul>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.15 }}
>
<h2 className="mb-6 flex items-center gap-2 text-2xl font-bold text-gray-900">
<Clock className="h-5 w-5 text-[#C41E3A]" />
</h2>
<div className="space-y-4">
{(product.useCases ?? []).map((useCase, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: index * 0.06 }}
className="rounded-lg border border-red-50 bg-red-50/30 p-4"
>
<h3 className="mb-1 text-sm font-semibold text-gray-900">{useCase.title}</h3>
<p className="text-xs leading-relaxed text-gray-600">{useCase.description}</p>
</motion.div>
))}
</div>
</motion.div>
</div>
</div>
</section>
<DetailTrustSectionV2 accentColor={theme.accentColor} />
<DetailCTASectionV2
theme={theme}
title={`了解更多关于${product.title}`}
description="联系我们,获取详细的产品资料和方案报价"
primaryAction={{ label: '预约演示', href: '/contact' }}
secondaryAction={{ label: '下载产品手册', href: '#' }}
/>
</>
)}
</div>
);
}
@@ -0,0 +1,37 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { STANDALONE_PRODUCTS } from '@/lib/constants/standalone-products';
import { StandaloneProductClient } from './client';
export async function generateStaticParams() {
return STANDALONE_PRODUCTS.map((product) => ({
id: product.id,
}));
}
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
const { id } = await params;
const product = STANDALONE_PRODUCTS.find((p) => p.id === id);
if (!product) {
return {
title: '产品未找到',
};
}
return {
title: `${product.title} - ${process.env.NEXT_PUBLIC_COMPANY_NAME || 'Novalon'}`,
description: product.description,
};
}
export default async function StandaloneProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = STANDALONE_PRODUCTS.find((p) => p.id === id);
if (!product) {
notFound();
}
return <StandaloneProductClient product={JSON.parse(JSON.stringify(product))} />;
}
+37 -176
View File
@@ -1,196 +1,57 @@
'use client';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
import { PageNav } from '@/components/layout/page-nav';
import { getHeroTheme } from '@/lib/constants/hero-themes';
import { getServiceCrossRefs } from '@/lib/constants/cross-references';
import { DetailHeroV3 } from '@/components/detail-v2/detail-hero-v3';
import { ServiceValueSectionV3 } from '@/components/detail-v2/service-value-v3';
import { DetailTrustSectionV2 } from '@/components/detail-v2/detail-trust-section-v2';
import { DetailCTASectionV2 } from '@/components/detail-v2/detail-cta-section-v2';
import { CrossRecommendGrid } from '@/components/detail/detail-cross-recommend';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import {
CheckCircle2,
TrendingUp,
Target,
Clock,
MessageCircle,
ArrowRight,
} from 'lucide-react';
import { SERVICES } from '@/lib/constants';
import type { Service } from '@/lib/constants/services';
interface ServiceDetailClientProps {
service: typeof SERVICES[number];
service: Service;
}
const challenges: Record<string, { title: string; description: string }[]> = {
software: [
{ title: '需求不明确', description: '业务部门提不出清晰需求,开发团队反复返工' },
{ title: '技术选型困难', description: '技术栈更新快,不知道该选什么技术方案' },
{ title: '项目延期', description: '开发进度难以把控,上线时间一拖再拖' },
{ title: '维护成本高', description: '系统上线后问题不断,运维压力巨大' },
],
data: [
{ title: '数据孤岛', description: '各系统数据分散,无法整合分析' },
{ title: '决策盲区', description: '缺乏数据支撑,决策凭感觉' },
{ title: '报表滞后', description: '手工制作报表,时效性差' },
{ title: '价值难挖', description: '数据很多,但不知道怎么用' },
],
consulting: [
{ title: '方向不明', description: '数字化转型不知道从哪里入手' },
{ title: '技术债务', description: '历史系统包袱重,新技术难以引入' },
{ title: '人才短缺', description: '缺乏专业的技术规划和架构人才' },
{ title: '投入浪费', description: 'IT投入不少,但看不到明显效果' },
],
solutions: [
{ title: '行业壁垒', description: '不了解行业最佳实践,走弯路' },
{ title: '方案碎片化', description: '各系统各自为政,无法协同' },
{ title: '实施风险', description: '大型项目实施失败率高' },
{ title: '效果难量化', description: '投入产出比不清晰,难以评估' },
],
};
const outcomes: Record<string, { value: string; label: string }[]> = {
software: [
{ value: '30%+', label: '预期开发效率提升' },
{ value: '50%+', label: '预期返工率降低' },
{ value: '严格', label: '交付质量把控' },
],
data: [
{ value: '实时', label: '数据更新' },
{ value: '自助式', label: '分析模式' },
{ value: '多维度', label: '可视化报表' },
],
consulting: [
{ value: '清晰', label: '转型方向规划' },
{ value: '系统化', label: '技术选型评估' },
{ value: '可落地', label: '实施路径设计' },
],
solutions: [
{ value: '标准化', label: '行业方案交付' },
{ value: '端到端', label: '全流程实施' },
{ value: '持续化', label: '运维优化支持' },
],
};
export function ServiceDetailClient({ service }: ServiceDetailClientProps) {
const serviceChallenges = challenges[service.id] ?? [];
const serviceOutcomes = outcomes[service.id] ?? [];
const theme = getHeroTheme(service.heroThemeId);
const crossRefs = getServiceCrossRefs(service.id);
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<DetailSwipeNav type="service" currentId={service.id} />
<div className="pt-32 pb-16">
<div className="container-wide">
<PageNav items={[{ label: '服务', href: '/services' }, { label: service.title }]} />
<div className="max-w-4xl mt-8">
<p className="text-sm font-medium text-[var(--color-brand-primary)] mb-4 tracking-wide uppercase">Services</p>
<h1 className="text-4xl md:text-5xl font-bold text-[var(--color-text-primary)] mb-6 tracking-tight">
{service.title}
</h1>
<p className="text-xl text-[var(--color-text-muted)] leading-relaxed">
{service.description}
</p>
</div>
</div>
</div>
<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-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center">
<MessageCircle className="w-5 h-5 text-[var(--color-brand-primary)]" />
</div>
<h2 className="text-2xl font-bold text-[var(--color-text-primary)]"></h2>
</div>
<div className="grid md:grid-cols-2 gap-4">
{serviceChallenges.map((challenge, index) => (
<div
key={index}
className="p-4 bg-[var(--color-bg-tertiary)] rounded-lg border border-[var(--color-border-primary)] hover:border-[var(--color-brand-primary)]/30 hover:bg-[var(--color-brand-primary-bg)] transition-colors"
>
<h3 className="font-semibold text-[var(--color-text-primary)] mb-1 text-sm">{challenge.title}</h3>
<p className="text-sm text-[var(--color-text-muted)]">{challenge.description}</p>
</div>
))}
</div>
</section>
{/* L1 - Hero 情感入口 (V3) */}
<DetailHeroV3
theme={theme}
badge="专业服务"
title={service.title}
subtitle={service.description}
primaryAction={{ label: '立即咨询', href: '/contact' }}
secondaryAction={{ label: '查看案例', href: '/cases' }}
/>
<section>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center">
<Target className="w-5 h-5 text-[var(--color-brand-primary)]" />
</div>
<h2 className="text-2xl font-bold text-[var(--color-text-primary)]"></h2>
</div>
<p className="text-lg text-[var(--color-text-muted)] 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-[var(--color-brand-primary)] mt-0.5 shrink-0" />
<span className="text-[var(--color-text-primary)] text-sm">{feature}</span>
</div>
))}
</div>
</section>
{/* L2 - 价值理性支撑 (V3) */}
<ServiceValueSectionV3 service={service} />
<section>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center">
<Clock className="w-5 h-5 text-[var(--color-brand-primary)]" />
</div>
<h2 className="text-2xl font-bold text-[var(--color-text-primary)]"></h2>
</div>
<div className="space-y-4">
{service.process.map((step, index) => (
<div key={index} className="flex items-start gap-4">
<div className="w-8 h-8 bg-[var(--color-brand-primary)] rounded-full flex items-center justify-center shrink-0 text-white text-sm font-bold">
{index + 1}
</div>
<p className="text-[var(--color-text-primary)] pt-1">{step}</p>
</div>
))}
</div>
</section>
{/* L3 - 信任证明 (V2) */}
<DetailTrustSectionV2
caseStudies={service.caseStudies}
dataProofs={service.dataProofs}
accentColor={theme.accentColor}
/>
<section>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center">
<TrendingUp className="w-5 h-5 text-[var(--color-brand-primary)]" />
</div>
<h2 className="text-2xl font-bold text-[var(--color-text-primary)]"></h2>
</div>
<div className="grid sm:grid-cols-3 gap-4">
{serviceOutcomes.map((outcome, index) => (
<div
key={index}
className="p-6 bg-[var(--color-bg-tertiary)] rounded-lg text-center hover:bg-[var(--color-brand-primary-bg)] transition-colors"
>
<div className="text-3xl font-bold text-[var(--color-brand-primary)] mb-2">
{outcome.value}
</div>
<div className="text-sm text-[var(--color-text-muted)]">{outcome.label}</div>
</div>
))}
</div>
<div className="mt-4 p-4 bg-[var(--color-bg-tertiary)] rounded-lg">
<p className="text-sm text-[var(--color-text-muted)]">
{service.benefits.join('')}
</p>
</div>
</section>
{/* L4 - CTA转化 (V2) */}
<DetailCTASectionV2
theme={theme}
title={`准备好开始${service.title}了吗?`}
description="联系我们,获取专属方案和报价"
primaryAction={{ label: '预约咨询', href: '/contact' }}
secondaryAction={{ label: '了解更多服务', href: '/services' }}
/>
<div className="flex justify-center gap-4 pt-8 border-t border-[var(--color-border-primary)]">
<StaticLink href="/services">
<Button variant="outline" size="lg"></Button>
</StaticLink>
<StaticLink href="/contact">
<Button size="lg" className="bg-[var(--color-brand-primary)] hover:bg-[var(--color-brand-primary-hover)] text-white">
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</StaticLink>
</div>
</div>
</div>
<CrossRecommendGrid items={crossRefs} title="相关解决方案" />
</div>
);
}
+58 -60
View File
@@ -4,11 +4,12 @@ import { useState, useEffect } from 'react';
import { SERVICES } from '@/lib/constants';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
import { ArrowRight, ArrowUpRight } from 'lucide-react';
import { ArrowRight } from 'lucide-react';
import { motion } from 'framer-motion';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
import { PageNav } from '@/components/layout/page-nav';
import { LoadingState } from '@/components/ui/loading-state';
import { ListPageHeroV3 } from '@/components/detail-v2/list-page-hero-v3';
import { ServiceCardV3 } from '@/components/detail-v2/solution-service-card-v3';
export default function ServicesPage() {
const shouldReduceMotion = useReducedMotion();
@@ -19,75 +20,72 @@ export default function ServicesPage() {
const timer = setTimeout(() => setIsLoading(false), 500);
return () => clearTimeout(timer);
}, []);
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<section className="pt-32 pb-16">
<div className="container-wide">
<PageNav items={[{ label: '服务' }]} />
<motion.div
{...fadeUp}
animate={shouldReduceMotion ? {} : { opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="max-w-3xl"
>
<p className="text-sm font-medium text-[var(--color-brand-primary)] mb-4 tracking-wide uppercase">Services</p>
<h1 className="text-4xl md:text-5xl font-bold text-[var(--color-text-primary)] mb-6 tracking-tight">
</h1>
<p className="text-lg text-[var(--color-text-muted)] leading-relaxed">
</p>
</motion.div>
</div>
</section>
{/* Hero 区域 (V3) */}
<ListPageHeroV3
title="专业服务"
subtitle="从咨询规划到落地实施,全生命周期数字化支持"
description="提供咨询、开发、数据、解决方案等全方位服务"
stats={[
{ value: '4', label: '服务类型' },
{ value: '7×24', label: '响应时间' },
{ value: '100%', label: '客户满意' },
]}
/>
<section className="pb-20">
<section className="pb-16">
<div className="container-wide">
<LoadingState isLoading={isLoading} variant="card">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{SERVICES.map((service, index) => (
<motion.div
key={service.id}
{...fadeUp}
whileInView={shouldReduceMotion ? {} : { opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: index * 0.1, ease: [0.16, 1, 0.3, 1] }}
>
<StaticLink
href={`/services/${service.id}`}
className="group block p-6 rounded-xl border border-[var(--color-border-primary)] bg-[var(--color-bg-primary)] hover:border-[var(--color-brand-primary)]/40 hover:shadow-lg transition-all duration-300 h-full"
>
<div className="flex items-start justify-between mb-4">
<span className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[var(--color-brand-primary-bg)] text-[var(--color-brand-primary)] text-sm font-bold">
{String(index + 1).padStart(2, '0')}
</span>
<ArrowUpRight className="w-5 h-5 text-[var(--color-text-muted)] group-hover:text-[var(--color-brand-primary)] transition-colors" />
</div>
<h3 className="text-lg font-semibold text-[var(--color-text-primary)] mb-2 group-hover:text-[var(--color-brand-primary)] transition-colors">
{service.title}
</h3>
<p className="text-sm text-[var(--color-text-muted)] leading-relaxed mb-4">
{service.description}
</p>
<div className="flex flex-wrap gap-1.5">
{service.features.slice(0, 3).map((feature, idx) => (
<span
key={idx}
className="inline-flex items-center text-xs px-2 py-1 bg-[var(--color-bg-tertiary)] text-[var(--color-text-muted)] rounded"
>
{feature.split('')[0]}
</span>
))}
</div>
</StaticLink>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{SERVICES.map((service) => (
<ServiceCardV3 key={service.id} service={service} />
))}
</div>
</LoadingState>
</div>
</section>
<section className="bg-[var(--color-bg-tertiary)] py-20">
{/* 服务流程概览 */}
<section className="bg-gray-50/50 py-16">
<div className="container-wide">
<motion.h2
{...fadeUp}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="mb-10 max-w-2xl text-center text-2xl font-bold text-gray-900 lg:text-3xl"
>
</motion.h2>
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-4">
{[
{ step: '01', title: '需求分析', desc: '深入了解业务场景与痛点' },
{ step: '02', title: '方案设计', desc: '量身定制技术解决方案' },
{ step: '03', title: '敏捷交付', desc: '快速迭代,持续优化' },
{ step: '04', title: '持续支持', desc: '长期陪伴,随时响应' },
].map((item, index) => (
<motion.div
key={item.step}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: index * 0.1 }}
className="text-center"
>
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-[#C41E3A] text-lg font-bold text-white shadow-md">
{item.step}
</div>
<h3 className="mb-2 text-base font-semibold text-gray-900">{item.title}</h3>
<p className="text-sm text-gray-500">{item.desc}</p>
</motion.div>
))}
</div>
</div>
</section>
<section className="py-20">
<div className="container-wide text-center">
<h2 className="text-3xl font-bold text-[var(--color-text-primary)] mb-6">
+42 -134
View File
@@ -1,151 +1,59 @@
'use client';
import { motion } from 'framer-motion';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
import { PageNav } from '@/components/layout/page-nav';
import { ArrowRight, AlertTriangle, CheckCircle2, Package } from 'lucide-react';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { DetailHeroV3 } from '@/components/detail-v2/detail-hero-v3';
import { SolutionValueSectionV3 } from '@/components/detail-v2/solution-value-v3';
import { DetailTrustSectionV2 } from '@/components/detail-v2/detail-trust-section-v2';
import { DetailCTASectionV2 } from '@/components/detail-v2/detail-cta-section-v2';
import { CrossRecommendGrid } from '@/components/detail/detail-cross-recommend';
import { getHeroTheme } from '@/lib/constants/hero-themes';
import { getSolutionCrossRefs } from '@/lib/constants/cross-references';
import type { Solution } from '@/lib/constants/solutions';
import type { Product } from '@/lib/constants/products';
interface SolutionDetailClientProps {
solution: Solution;
relatedProducts: Product[];
}
export function SolutionDetailClient({ solution, relatedProducts }: SolutionDetailClientProps) {
const shouldReduceMotion = useReducedMotion();
const fadeUp = shouldReduceMotion ? {} : { initial: { opacity: 0, y: 20 } };
export function SolutionDetailClient({ solution }: SolutionDetailClientProps) {
const theme = getHeroTheme(solution.heroThemeId || 'solution');
const crossRefs = getSolutionCrossRefs(solution.id);
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<div className="pt-32 pb-16">
<div className="container-wide">
<PageNav items={[{ label: '解决方案', href: '/solutions' }, { label: solution.title }]} />
<motion.div
{...fadeUp}
animate={shouldReduceMotion ? {} : { opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="max-w-4xl mt-8"
>
<p className="text-sm font-medium text-[var(--color-brand-primary)] mb-4 tracking-wide uppercase">
{solution.industry}
</p>
<h1 className="text-4xl md:text-5xl font-bold text-[var(--color-text-primary)] mb-4 tracking-tight">
{solution.title}
</h1>
<p className="text-xl text-[var(--color-text-muted)] mb-2">{solution.subtitle}</p>
<p className="text-lg text-[var(--color-text-muted)] leading-relaxed">
{solution.description}
</p>
</motion.div>
</div>
</div>
<DetailSwipeNav type="solution" currentId={solution.id} />
<div className="container-wide pb-20">
<div className="max-w-4xl space-y-16">
<motion.section
{...fadeUp}
whileInView={shouldReduceMotion ? {} : { opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3">
<AlertTriangle className="w-6 h-6 text-[var(--color-brand-primary)]" />
</h2>
<div className="space-y-3">
{solution.challenges.map((challenge, index) => (
<div
key={index}
className="flex items-start gap-3 p-4 rounded-lg bg-[var(--color-brand-primary-bg)]"
>
<AlertTriangle className="w-4 h-4 text-[var(--color-brand-primary)] mt-0.5 shrink-0" />
<span className="text-[var(--color-text-primary)] text-sm">{challenge}</span>
</div>
))}
</div>
</motion.section>
{/* L1 - Hero 情感入口 (V3) */}
<DetailHeroV3
theme={theme}
badge={`${solution.industry}解决方案`}
title={solution.title}
subtitle={solution.subtitle || solution.description}
description={solution.description}
primaryAction={{ label: '立即咨询', href: '/contact' }}
secondaryAction={{ label: '查看案例', href: '/cases' }}
/>
<motion.section
{...fadeUp}
whileInView={shouldReduceMotion ? {} : { opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3">
<CheckCircle2 className="w-6 h-6 text-[var(--color-brand-primary)]" />
</h2>
<div className="space-y-4">
{solution.solutions.map((sol, index) => (
<div key={index} className="flex items-start gap-4">
<div className="w-8 h-8 bg-[var(--color-brand-primary)] rounded-full flex items-center justify-center shrink-0 text-white text-sm font-bold">
{index + 1}
</div>
<p className="text-[var(--color-text-primary)] pt-1 leading-relaxed">{sol}</p>
</div>
))}
</div>
</motion.section>
{/* L2 - 价值理性支撑 (V3) */}
<SolutionValueSectionV3 solution={solution} />
{relatedProducts.length > 0 && (
<motion.section
{...fadeUp}
whileInView={shouldReduceMotion ? {} : { opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3">
<Package className="w-6 h-6 text-[var(--color-brand-primary)]" />
</h2>
<div className="grid md:grid-cols-2 gap-4">
{relatedProducts.map((product) => (
<StaticLink
key={product.id}
href={`/products/${product.id}`}
className="group block p-6 rounded-xl border border-[var(--color-border-primary)] bg-[var(--color-bg-primary)] hover:border-[var(--color-brand-primary)]/40 hover:shadow-lg transition-all duration-300"
>
<div className="flex items-center gap-2 mb-2">
<h3 className="text-lg font-semibold text-[var(--color-text-primary)] group-hover:text-[var(--color-brand-primary)] transition-colors">
{product.title}
</h3>
{product.status && (
<span
className="text-xs font-medium px-3 py-1 rounded-full border shrink-0"
style={{
backgroundColor: product.status === '研发中' ? 'rgba(var(--color-brand-primary-rgb), 0.08)' : product.status === '内测中' ? 'rgba(var(--color-warning-rgb), 0.08)' : 'rgba(var(--color-success-rgb), 0.08)',
color: product.status === '研发中' ? 'var(--color-brand-primary)' : product.status === '内测中' ? 'var(--color-warning)' : 'var(--color-success)',
borderColor: product.status === '研发中' ? 'rgba(var(--color-brand-primary-rgb), 0.15)' : product.status === '内测中' ? 'rgba(var(--color-warning-rgb), 0.15)' : 'rgba(var(--color-success-rgb), 0.15)',
}}
>
{product.status}
</span>
)}
</div>
<p className="text-sm text-[var(--color-text-muted)] leading-relaxed">
{product.description}
</p>
</StaticLink>
))}
</div>
</motion.section>
)}
{/* L3 - 信任证明 (V2) — 条件渲染:有案例/数据/资质时才显示 */}
<DetailTrustSectionV2
caseStudies={solution.caseStudies}
dataProofs={solution.dataProofs}
certifications={solution.certifications}
accentColor={theme.accentColor}
/>
<div className="flex justify-center gap-4 pt-8 border-t border-[var(--color-border-primary)]">
<Button variant="outline" size="lg" asChild>
<StaticLink href="/contact"></StaticLink>
</Button>
<Button size="lg" className="bg-[var(--color-brand-primary)] hover:bg-[var(--color-brand-primary-hover)] text-white" asChild>
<StaticLink href="/contact">
<ArrowRight className="ml-2 w-4 h-4" />
</StaticLink>
</Button>
</div>
</div>
</div>
{/* L4 - CTA转化 (V2) */}
<DetailCTASectionV2
theme={theme}
title={`探索 ${solution.title} 的无限可能`}
description="联系我们,获取专属方案和报价"
primaryAction={{ label: '立即咨询', href: '/contact' }}
secondaryAction={{ label: '下载方案白皮书', href: '#' }}
/>
<CrossRecommendGrid items={crossRefs} title="相关产品与服务" />
</div>
);
}
+1 -4
View File
@@ -1,7 +1,6 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { SOLUTIONS } from '@/lib/constants/solutions';
import { PRODUCTS } from '@/lib/constants/products';
import { COMPANY_INFO } from '@/lib/constants';
import { SolutionDetailClient } from './client';
@@ -33,7 +32,5 @@ export default async function SolutionDetailPage({ params }: { params: Promise<{
notFound();
}
const relatedProducts = PRODUCTS.filter((p) => solution.relatedProducts.includes(p.id));
return <SolutionDetailClient solution={JSON.parse(JSON.stringify(solution))} relatedProducts={JSON.parse(JSON.stringify(relatedProducts))} />;
return <SolutionDetailClient solution={JSON.parse(JSON.stringify(solution))} />;
}
+69 -168
View File
@@ -1,83 +1,25 @@
'use client';
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { motion } from 'framer-motion';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
import { StaticLink } from '@/components/ui/static-link';
import { ArrowRight, Lightbulb, Cpu, Users, CheckCircle2, ChevronDown } from 'lucide-react';
import {
Lightbulb,
Cpu,
Users,
} from 'lucide-react';
import { MethodologySection } from '@/components/sections/methodology-section';
import { CTASection } from '@/components/sections/cta-section';
import { PageNav } from '@/components/layout/page-nav';
import { BreadcrumbSchema } from '@/components/seo/structured-data';
import { SwipeNavigation } from '@/hooks/use-swipe-gesture';
const modules = [
{
icon: Lightbulb,
title: '数字化转型咨询',
subtitle: '参谋伙伴 — 帮您看清前路,迈对第一步',
paragraphs: [
'数字化转型最大的成本,是走错方向的成本。',
'我们用行业智慧帮您洞察趋势,用理性分析帮您避开陷阱。',
'不堆砌概念,只帮您想清楚:该不该做、做什么、怎么做。',
],
values: ['行业趋势洞察报告', '数字化转型成熟度评估', '个性化实施路径规划'],
cta: '预约一次免费诊断',
ctaHref: '/contact',
accentColor: 'var(--color-brand-primary)',
accentBg: 'var(--color-brand-primary-bg)',
accentBorder: 'border-l-[var(--color-brand-primary)]',
},
{
icon: Cpu,
title: '信息技术解决方案',
subtitle: '技术伙伴 — 让技术真正为业务服务',
paragraphs: [
'我们不追逐"最火"的技术,只选择"最对"的技术。',
'将前沿技术深度融入您的业务场景,让每一行代码都产生业务价值。',
'您不必懂技术原理,只需要看见业务在增长。',
],
values: ['业务场景深度调研', '技术方案定制开发', '敏捷交付快速迭代'],
cta: '了解技术方案',
ctaHref: '/products',
accentColor: 'var(--color-accent-blue)',
accentBg: 'rgba(37, 99, 235, 0.08)',
accentBorder: 'border-l-[var(--color-accent-blue)]',
},
{
icon: Users,
title: '长期陪跑服务',
subtitle: '同行伙伴 — 从需求到落地,持续陪伴',
paragraphs: [
'当产品真正为您所用那天,才是我们成为伙伴的开始。',
'我们建立长效服务机制,定期回访、持续优化、随时响应。',
'在您需要的时候,我们始终在场。',
],
values: ['专属客户成功经理', '季度业务复盘会', '7×24小时响应通道'],
cta: '了解陪跑服务',
ctaHref: '/services',
accentColor: 'var(--color-success)',
accentBg: 'rgba(22, 163, 74, 0.08)',
accentBorder: 'border-l-[var(--color-success)]',
},
];
import { ListPageHeroV3 } from '@/components/detail-v2/list-page-hero-v3';
import { SolutionCardV3 } from '@/components/detail-v2/solution-service-card-v3';
import { SOLUTIONS } from '@/lib/constants/solutions';
export default function SolutionsPage() {
const shouldReduceMotion = useReducedMotion();
const fadeUp = shouldReduceMotion ? {} : { initial: { opacity: 0, y: 20 } };
const [expandedModules, setExpandedModules] = useState<Set<number>>(new Set([0]));
const toggleModule = (index: number) => {
setExpandedModules(prev => {
const next = new Set(prev);
if (next.has(index)) {
next.delete(index);
} else {
next.add(index);
}
return next;
});
};
return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<BreadcrumbSchema items={[{ name: '首页', href: '/' }, { name: '解决方案', href: '/solutions' }]} />
@@ -87,118 +29,77 @@ export default function SolutionsPage() {
prevLabel="产品方案"
nextLabel="服务支持"
/>
<section className="pt-32 pb-16">
{/* Hero 区域 (V3) */}
<ListPageHeroV3
title="行业解决方案"
subtitle="针对不同行业的核心痛点,提供端到端数字化路径"
description="基于企业套装产品的行业化组合方案"
stats={[
{ value: '4', label: '行业方案' },
{ value: '6+', label: '核心产品' },
{ value: '100%', label: '自主可控' },
]}
/>
{/* 行业解决方案网格 */}
<section className="pb-16">
<div className="container-wide">
<PageNav items={[{ label: '解决方案' }]} />
<motion.div
{...fadeUp}
animate={shouldReduceMotion ? {} : { opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="max-w-3xl"
>
<p className="text-sm font-medium text-[var(--color-brand-primary)] mb-4 tracking-wide uppercase">Solutions</p>
<h1 className="text-4xl md:text-5xl font-bold text-[var(--color-text-primary)] mb-6 tracking-tight">
</h1>
<p className="text-lg text-[var(--color-text-muted)] leading-relaxed">
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{SOLUTIONS.map((solution) => (
<SolutionCardV3 key={solution.id} solution={solution} />
))}
</div>
</div>
</section>
<section className="pb-20">
{/* 方案→服务关联推荐 */}
<section className="py-16">
<div className="container-wide">
<div className="max-w-6xl mx-auto space-y-8">
{modules.map((module, index) => {
const anchorIds = ['consulting', 'tech', 'accompany'];
return (
<motion.section
key={index}
id={anchorIds[index]}
{...fadeUp}
whileInView={shouldReduceMotion ? {} : { 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-[var(--color-border-primary)] bg-[var(--color-bg-primary)] hover:border-[var(--color-border-primary)] transition-colors`}
>
<div className="flex items-start gap-4 mb-6">
<div className="w-12 h-12 rounded-xl flex items-center justify-center shrink-0" style={{ backgroundColor: module.accentBg }}>
<module.icon className="w-6 h-6" style={{ color: module.accentColor }} />
</div>
<div>
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-1">
{['一', '二', '三'][index]}{module.title}
</h2>
<p className="text-[var(--color-text-muted)]">{module.subtitle}</p>
</div>
</div>
<div className="space-y-4 mb-8">
<button
onClick={() => toggleModule(index)}
className="flex items-center gap-2 text-sm font-medium text-[var(--color-brand-primary)] hover:text-[var(--color-brand-primary-hover)] transition-colors group"
aria-expanded={expandedModules.has(index)}
>
<ChevronDown className={`w-4 h-4 transition-transform duration-200 ${expandedModules.has(index) ? 'rotate-180' : ''}`} />
{expandedModules.has(index) ? '收起详情' : '展开了解详情'}
</button>
<AnimatePresence>
{expandedModules.has(index) && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
className="overflow-hidden"
{...fadeUp}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
className="text-center mb-10"
>
{module.paragraphs.map((p, i) => (
<p key={i} className="text-[var(--color-text-primary)] leading-relaxed mb-3 last:mb-0">{p}</p>
))}
<h2 className="text-2xl font-bold text-gray-900 lg:text-3xl mb-3">
</h2>
<p className="text-gray-600 max-w-2xl mx-auto">
</p>
</motion.div>
)}
</AnimatePresence>
</div>
<div className="mb-8">
<h3 className="text-lg font-semibold text-[var(--color-text-primary)] mb-4 flex items-center gap-2">
<CheckCircle2 className="w-5 h-5" style={{ color: module.accentColor }} />
</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 rounded-lg" style={{ backgroundColor: module.accentBg }}>
<div className="w-1.5 h-1.5 rounded-full mt-2 shrink-0" style={{ backgroundColor: module.accentColor }} />
<span className="text-sm text-[var(--color-text-primary)]">{value}</span>
</div>
))}
</div>
</div>
<div className="flex justify-center">
<StaticLink
href={module.ctaHref}
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg border-2 font-medium transition-all duration-200 hover:text-white"
style={{
borderColor: module.accentColor,
color: module.accentColor,
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = module.accentColor;
e.currentTarget.style.color = '#fff';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'transparent';
e.currentTarget.style.color = module.accentColor;
}}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto">
{[
{ icon: Lightbulb, title: '数字化转型咨询', desc: '帮您看清前路,迈对第一步', href: '/services', bgColor: 'rgba(196,30,58,0.06)', color: '#C41E3A' },
{ icon: Cpu, title: '信息技术解决方案', desc: '让技术真正为业务服务', href: '/products', bgColor: 'rgba(37,99,235,0.06)', color: '#2563eb' },
{ icon: Users, title: '长期陪跑服务', desc: '从需求到落地,持续陪伴', href: '/services', bgColor: 'rgba(22,163,74,0.06)', color: '#16a34a' },
].map((item, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1 }}
>
{module.cta}
<ArrowRight className="w-4 h-4" />
</StaticLink>
<StaticLink
href={item.href}
className="block p-6 rounded-xl bg-white border border-gray-100 hover:border-gray-200 hover:shadow-md transition-all group"
>
<div
className="w-10 h-10 rounded-lg flex items-center justify-center mb-4"
style={{ backgroundColor: item.bgColor }}
>
<item.icon className="w-5 h-5" style={{ color: item.color }} />
</div>
</motion.section>
);
})}
<h3 className="font-semibold text-gray-900 mb-1 group-hover:text-[#C41E3A] transition-colors">
{item.title}
</h3>
<p className="text-sm text-gray-500">{item.desc}</p>
</StaticLink>
</motion.div>
))}
</div>
</div>
</section>