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 { HeroSectionV2 } from '@/components/sections/hero-section-v2';
import { SectionSkeleton } from '@/components/ui/loading-skeleton'; 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( const ProductMatrixSection = dynamic(
() => import('@/components/sections/product-matrix-section').then(mod => ({ default: mod.ProductMatrixSection })), () => import('@/components/sections/product-matrix-section').then(mod => ({ default: mod.ProductMatrixSection })),
{ loading: () => <SectionSkeleton />, ssr: false } { loading: () => <SectionSkeleton />, ssr: false }
@@ -19,6 +14,16 @@ const ChallengeSection = dynamic(
{ loading: () => <SectionSkeleton />, ssr: false } { 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( const CTASection = dynamic(
() => import('@/components/sections/cta-section').then(mod => ({ default: mod.CTASection })), () => import('@/components/sections/cta-section').then(mod => ({ default: mod.CTASection })),
{ loading: () => <SectionSkeleton />, ssr: false } { loading: () => <SectionSkeleton />, ssr: false }
@@ -31,6 +36,7 @@ function HomeContentV2() {
<ProductMatrixSection /> <ProductMatrixSection />
<ChallengeSection /> <ChallengeSection />
<SocialProofSection /> <SocialProofSection />
<WhyUsSection />
<CTASection /> <CTASection />
</main> </main>
); );
@@ -27,7 +27,9 @@ jest.mock('@/lib/constants', () => ({
{ {
id: 'test-product', id: 'test-product',
title: '测试产品', title: '测试产品',
category: '企业软件', category: '企业旗舰系列',
categoryId: 'enterprise',
tags: ['测试', '示例'],
status: '研发中', status: '研发中',
description: '这是测试产品描述', description: '这是测试产品描述',
overview: '这是测试产品概述', overview: '这是测试产品概述',
@@ -37,6 +39,13 @@ jest.mock('@/lib/constants', () => ({
specs: ['规格1', '规格2'], specs: ['规格1', '规格2'],
}, },
], ],
PRODUCT_CATEGORIES: [
{
id: 'enterprise',
title: '企业旗舰系列',
description: '测试分类描述',
},
],
})); }));
describe('ProductDetailPage', () => { describe('ProductDetailPage', () => {
@@ -66,7 +75,7 @@ describe('ProductDetailPage', () => {
const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) }); const page = await ProductDetailPage({ params: Promise.resolve({ id: 'test-product' }) });
render(page); render(page);
const category = screen.getByText('企业软件'); const category = screen.getByText('企业旗舰系列');
expect(category).toBeInTheDocument(); expect(category).toBeInTheDocument();
}); });
+39 -142
View File
@@ -1,10 +1,13 @@
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { StaticLink } from '@/components/ui/static-link';
import { PRODUCTS, COMPANY_INFO } from '@/lib/constants'; import { PRODUCTS, COMPANY_INFO } from '@/lib/constants';
import { Button } from '@/components/ui/button'; import { getHeroTheme } from '@/lib/constants/hero-themes';
import { PageNav } from '@/components/layout/page-nav'; import { getProductCrossRefs } from '@/lib/constants/cross-references';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav'; 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() { export async function generateStaticParams() {
return PRODUCTS.map((product) => ({ return PRODUCTS.map((product) => ({
@@ -22,7 +25,7 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin
return { return {
title: `${product.title} - ${COMPANY_INFO.displayName}`, 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(); notFound();
} }
const theme = getHeroTheme(product.heroThemeId);
const crossRefs = getProductCrossRefs(product.id);
return ( return (
<div className="min-h-screen bg-[var(--color-bg-primary)]"> <div className="min-h-screen bg-[var(--color-bg-primary)]">
<DetailSwipeNav type="product" currentId={product.id} /> <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"> {/* L1 - Hero 情感入口 (V3) */}
<div className="max-w-4xl space-y-16"> <DetailHeroV3
<section aria-labelledby="product-overview"> theme={theme}
<h2 id="product-overview" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6"></h2> badge={product.category}
<p className="text-lg text-[var(--color-text-muted)] leading-relaxed"> title={product.title}
{product.overview} subtitle={product.description}
</p> description={product.overview}
</section> primaryAction={{ label: '预约体验', href: '/contact' }}
secondaryAction={{ label: '查看方案', href: '/solutions' }}
/>
<section aria-labelledby="product-features"> {/* L2 - 价值理性支撑 (V3) */}
<h2 id="product-features" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3"> <ProductValueSectionV3 product={product} />
<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>
<section aria-labelledby="product-benefits"> {/* L3 - 信任证明 (V2) */}
<h2 id="product-benefits" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3"> <DetailTrustSectionV2
<Target className="w-6 h-6 text-[var(--color-brand-primary)]" /> caseStudies={product.caseStudies}
dataProofs={product.dataProofs}
</h2> certifications={product.certifications}
<div className="space-y-3"> accentColor={theme.accentColor}
{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>
<section aria-labelledby="product-process"> {/* L4 - CTA + 交叉推荐 (V2) */}
<h2 id="product-process" className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3"> <DetailCTASectionV2
<Layers className="w-6 h-6 text-[var(--color-brand-primary)]" /> theme={theme}
title={`探索 ${product.title} 的无限可能`}
</h2> description="立即联系我们,获取专属方案和报价"
<div className="space-y-4"> primaryAction={{ label: '预约体验', href: '/contact' }}
{product.process.map((step, index) => ( secondaryAction={{ label: '下载产品手册', href: '#' }}
<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>
<section aria-labelledby="product-specs"> <CrossRecommendGrid items={crossRefs} title={`${product.title} 相关的推荐`} />
<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>
</div> </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'; 'use client';
import { useState, useEffect } from 'react'; import { useState, useEffect, useMemo } from 'react';
import { PRODUCTS } from '@/lib/constants'; import { PRODUCTS, PRODUCT_CATEGORIES, type ProductCategory as ProductCategoryType } from '@/lib/constants';
import { ProductCard } from '@/components/ui/product-card';
import { motion } from 'framer-motion'; 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 { CTASection } from '@/components/sections/cta-section';
import { BreadcrumbSchema } from '@/components/seo/structured-data'; import { BreadcrumbSchema } from '@/components/seo/structured-data';
import { LoadingState } from '@/components/ui/loading-state'; 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() { export default function ProductsPage() {
const shouldReduceMotion = useReducedMotion();
const fadeUp = shouldReduceMotion ? {} : { initial: { opacity: 0, y: 20 } };
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [activeCategory, setActiveCategory] = useState<string>('all');
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => setIsLoading(false), 500); const timer = setTimeout(() => setIsLoading(false), 500);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, []); }, []);
const filteredProducts = useMemo(() => {
if (activeCategory === 'all') return PRODUCTS;
return PRODUCTS.filter(product => product.categoryId === activeCategory);
}, [activeCategory]);
return ( return (
<div className="min-h-screen bg-[var(--color-bg-primary)]"> <div className="min-h-screen bg-[var(--color-bg-primary)]">
<BreadcrumbSchema items={[{ name: '首页', href: '/' }, { name: '产品', href: '/products' }]} /> <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"> <div className="container-wide">
<PageNav items={[{ label: '产品' }]} /> {/* 企业套装区域 */}
<motion.div <motion.div
{...fadeUp} initial={{ opacity: 0, y: 20 }}
animate={shouldReduceMotion ? {} : { opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }} 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> <div className="mb-8 flex items-center gap-3">
<h1 className="text-4xl md:text-5xl font-bold text-[var(--color-text-primary)] mb-6 tracking-tight"> <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" />
</h1> </div>
<p className="text-lg text-[var(--color-text-muted)] leading-relaxed"> <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> </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> </motion.div>
</div> </div>
</section> </section>
<section className="pb-20"> {/* 分类标签导航(保留作为辅助筛选) */}
<section className="border-t border-gray-100 bg-gray-50/50 py-12">
<div className="container-wide"> <div className="container-wide">
<LoadingState isLoading={isLoading} variant="card"> <h3 className="mb-6 text-center text-lg font-semibold text-gray-900"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="flex flex-wrap justify-center gap-2">
{PRODUCTS.map((product, index) => ( <button
<ProductCard onClick={() => setActiveCategory('all')}
key={product.id} className={`px-5 py-2.5 text-sm font-medium rounded-full transition-all ${
title={product.title} activeCategory === 'all'
description={product.description} ? 'bg-[#C41E3A] text-white shadow-md'
href={`/products/${product.id}`} : 'bg-white text-gray-600 border border-gray-200 hover:border-gray-300'
index={index} }`}
status={product.status} >
/> ({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> </div>
</LoadingState> </motion.div>
)}
</div> </div>
</section> </section>
@@ -72,3 +177,4 @@ export default function ProductsPage() {
</div> </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'; 'use client';
import { StaticLink } from '@/components/ui/static-link'; import { getHeroTheme } from '@/lib/constants/hero-themes';
import { Button } from '@/components/ui/button'; import { getServiceCrossRefs } from '@/lib/constants/cross-references';
import { PageNav } from '@/components/layout/page-nav'; 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 { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { import type { Service } from '@/lib/constants/services';
CheckCircle2,
TrendingUp,
Target,
Clock,
MessageCircle,
ArrowRight,
} from 'lucide-react';
import { SERVICES } from '@/lib/constants';
interface ServiceDetailClientProps { 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) { export function ServiceDetailClient({ service }: ServiceDetailClientProps) {
const serviceChallenges = challenges[service.id] ?? []; const theme = getHeroTheme(service.heroThemeId);
const serviceOutcomes = outcomes[service.id] ?? []; const crossRefs = getServiceCrossRefs(service.id);
return ( return (
<div className="min-h-screen bg-[var(--color-bg-primary)]"> <div className="min-h-screen bg-[var(--color-bg-primary)]">
<DetailSwipeNav type="service" currentId={service.id} /> <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"> {/* L1 - Hero 情感入口 (V3) */}
<div className="max-w-4xl space-y-16"> <DetailHeroV3
<section> theme={theme}
<div className="flex items-center gap-3 mb-6"> badge="专业服务"
<div className="w-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center"> title={service.title}
<MessageCircle className="w-5 h-5 text-[var(--color-brand-primary)]" /> subtitle={service.description}
</div> primaryAction={{ label: '立即咨询', href: '/contact' }}
<h2 className="text-2xl font-bold text-[var(--color-text-primary)]"></h2> secondaryAction={{ label: '查看案例', href: '/cases' }}
</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>
<section> {/* L2 - 价值理性支撑 (V3) */}
<div className="flex items-center gap-3 mb-6"> <ServiceValueSectionV3 service={service} />
<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>
<section> {/* L3 - 信任证明 (V2) */}
<div className="flex items-center gap-3 mb-6"> <DetailTrustSectionV2
<div className="w-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center"> caseStudies={service.caseStudies}
<Clock className="w-5 h-5 text-[var(--color-brand-primary)]" /> dataProofs={service.dataProofs}
</div> accentColor={theme.accentColor}
<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>
<section> {/* L4 - CTA转化 (V2) */}
<div className="flex items-center gap-3 mb-6"> <DetailCTASectionV2
<div className="w-10 h-10 bg-[var(--color-brand-primary-bg)] rounded-lg flex items-center justify-center"> theme={theme}
<TrendingUp className="w-5 h-5 text-[var(--color-brand-primary)]" /> title={`准备好开始${service.title}了吗?`}
</div> description="联系我们,获取专属方案和报价"
<h2 className="text-2xl font-bold text-[var(--color-text-primary)]"></h2> primaryAction={{ label: '预约咨询', href: '/contact' }}
</div> secondaryAction={{ label: '了解更多服务', href: '/services' }}
<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>
<div className="flex justify-center gap-4 pt-8 border-t border-[var(--color-border-primary)]"> <CrossRecommendGrid items={crossRefs} title="相关解决方案" />
<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>
</div> </div>
); );
} }
+58 -60
View File
@@ -4,11 +4,12 @@ import { useState, useEffect } from 'react';
import { SERVICES } from '@/lib/constants'; import { SERVICES } from '@/lib/constants';
import { StaticLink } from '@/components/ui/static-link'; import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ArrowRight, ArrowUpRight } from 'lucide-react'; import { ArrowRight } from 'lucide-react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useReducedMotion } from '@/hooks/use-reduced-motion'; import { useReducedMotion } from '@/hooks/use-reduced-motion';
import { PageNav } from '@/components/layout/page-nav';
import { LoadingState } from '@/components/ui/loading-state'; 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() { export default function ServicesPage() {
const shouldReduceMotion = useReducedMotion(); const shouldReduceMotion = useReducedMotion();
@@ -19,75 +20,72 @@ export default function ServicesPage() {
const timer = setTimeout(() => setIsLoading(false), 500); const timer = setTimeout(() => setIsLoading(false), 500);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, []); }, []);
return ( return (
<div className="min-h-screen bg-[var(--color-bg-primary)]"> <div className="min-h-screen bg-[var(--color-bg-primary)]">
<section className="pt-32 pb-16"> {/* Hero 区域 (V3) */}
<div className="container-wide"> <ListPageHeroV3
<PageNav items={[{ label: '服务' }]} /> title="专业服务"
<motion.div subtitle="从咨询规划到落地实施,全生命周期数字化支持"
{...fadeUp} description="提供咨询、开发、数据、解决方案等全方位服务"
animate={shouldReduceMotion ? {} : { opacity: 1, y: 0 }} stats={[
transition={{ duration: 0.5 }} { value: '4', label: '服务类型' },
className="max-w-3xl" { value: '7×24', label: '响应时间' },
> { value: '100%', label: '客户满意' },
<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>
<section className="pb-20"> <section className="pb-16">
<div className="container-wide"> <div className="container-wide">
<LoadingState isLoading={isLoading} variant="card"> <LoadingState isLoading={isLoading} variant="card">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{SERVICES.map((service, index) => ( {SERVICES.map((service) => (
<motion.div <ServiceCardV3 key={service.id} service={service} />
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> </div>
</LoadingState> </LoadingState>
</div> </div>
</section> </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"> <div className="container-wide text-center">
<h2 className="text-3xl font-bold text-[var(--color-text-primary)] mb-6"> <h2 className="text-3xl font-bold text-[var(--color-text-primary)] mb-6">
+42 -134
View File
@@ -1,151 +1,59 @@
'use client'; 'use client';
import { motion } from 'framer-motion'; import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { useReducedMotion } from '@/hooks/use-reduced-motion'; import { DetailHeroV3 } from '@/components/detail-v2/detail-hero-v3';
import { StaticLink } from '@/components/ui/static-link'; import { SolutionValueSectionV3 } from '@/components/detail-v2/solution-value-v3';
import { Button } from '@/components/ui/button'; import { DetailTrustSectionV2 } from '@/components/detail-v2/detail-trust-section-v2';
import { PageNav } from '@/components/layout/page-nav'; import { DetailCTASectionV2 } from '@/components/detail-v2/detail-cta-section-v2';
import { ArrowRight, AlertTriangle, CheckCircle2, Package } from 'lucide-react'; 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 { Solution } from '@/lib/constants/solutions';
import type { Product } from '@/lib/constants/products';
interface SolutionDetailClientProps { interface SolutionDetailClientProps {
solution: Solution; solution: Solution;
relatedProducts: Product[];
} }
export function SolutionDetailClient({ solution, relatedProducts }: SolutionDetailClientProps) { export function SolutionDetailClient({ solution }: SolutionDetailClientProps) {
const shouldReduceMotion = useReducedMotion(); const theme = getHeroTheme(solution.heroThemeId || 'solution');
const fadeUp = shouldReduceMotion ? {} : { initial: { opacity: 0, y: 20 } }; const crossRefs = getSolutionCrossRefs(solution.id);
return ( return (
<div className="min-h-screen bg-[var(--color-bg-primary)]"> <div className="min-h-screen bg-[var(--color-bg-primary)]">
<div className="pt-32 pb-16"> <DetailSwipeNav type="solution" currentId={solution.id} />
<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>
<div className="container-wide pb-20"> {/* L1 - Hero 情感入口 (V3) */}
<div className="max-w-4xl space-y-16"> <DetailHeroV3
<motion.section theme={theme}
{...fadeUp} badge={`${solution.industry}解决方案`}
whileInView={shouldReduceMotion ? {} : { opacity: 1, y: 0 }} title={solution.title}
viewport={{ once: true }} subtitle={solution.subtitle || solution.description}
transition={{ duration: 0.5 }} description={solution.description}
> primaryAction={{ label: '立即咨询', href: '/contact' }}
<h2 className="text-2xl font-bold text-[var(--color-text-primary)] mb-6 flex items-center gap-3"> secondaryAction={{ label: '查看案例', href: '/cases' }}
<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>
<motion.section {/* L2 - 价值理性支撑 (V3) */}
{...fadeUp} <SolutionValueSectionV3 solution={solution} />
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>
{relatedProducts.length > 0 && ( {/* L3 - 信任证明 (V2) — 条件渲染:有案例/数据/资质时才显示 */}
<motion.section <DetailTrustSectionV2
{...fadeUp} caseStudies={solution.caseStudies}
whileInView={shouldReduceMotion ? {} : { opacity: 1, y: 0 }} dataProofs={solution.dataProofs}
viewport={{ once: true }} certifications={solution.certifications}
transition={{ duration: 0.5, delay: 0.2 }} accentColor={theme.accentColor}
> />
<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>
)}
<div className="flex justify-center gap-4 pt-8 border-t border-[var(--color-border-primary)]"> {/* L4 - CTA转化 (V2) */}
<Button variant="outline" size="lg" asChild> <DetailCTASectionV2
<StaticLink href="/contact"></StaticLink> theme={theme}
</Button> title={`探索 ${solution.title} 的无限可能`}
<Button size="lg" className="bg-[var(--color-brand-primary)] hover:bg-[var(--color-brand-primary-hover)] text-white" asChild> description="联系我们,获取专属方案和报价"
<StaticLink href="/contact"> primaryAction={{ label: '立即咨询', href: '/contact' }}
secondaryAction={{ label: '下载方案白皮书', href: '#' }}
<ArrowRight className="ml-2 w-4 h-4" /> />
</StaticLink>
</Button> <CrossRecommendGrid items={crossRefs} title="相关产品与服务" />
</div>
</div>
</div>
</div> </div>
); );
} }
+1 -4
View File
@@ -1,7 +1,6 @@
import { Metadata } from 'next'; import { Metadata } from 'next';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { SOLUTIONS } from '@/lib/constants/solutions'; import { SOLUTIONS } from '@/lib/constants/solutions';
import { PRODUCTS } from '@/lib/constants/products';
import { COMPANY_INFO } from '@/lib/constants'; import { COMPANY_INFO } from '@/lib/constants';
import { SolutionDetailClient } from './client'; import { SolutionDetailClient } from './client';
@@ -33,7 +32,5 @@ export default async function SolutionDetailPage({ params }: { params: Promise<{
notFound(); notFound();
} }
const relatedProducts = PRODUCTS.filter((p) => solution.relatedProducts.includes(p.id)); return <SolutionDetailClient solution={JSON.parse(JSON.stringify(solution))} />;
return <SolutionDetailClient solution={JSON.parse(JSON.stringify(solution))} relatedProducts={JSON.parse(JSON.stringify(relatedProducts))} />;
} }
+69 -168
View File
@@ -1,83 +1,25 @@
'use client'; 'use client';
import { useState } from 'react'; import { motion } from 'framer-motion';
import { motion, AnimatePresence } from 'framer-motion';
import { useReducedMotion } from '@/hooks/use-reduced-motion'; import { useReducedMotion } from '@/hooks/use-reduced-motion';
import { StaticLink } from '@/components/ui/static-link'; 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 { MethodologySection } from '@/components/sections/methodology-section';
import { CTASection } from '@/components/sections/cta-section'; import { CTASection } from '@/components/sections/cta-section';
import { PageNav } from '@/components/layout/page-nav';
import { BreadcrumbSchema } from '@/components/seo/structured-data'; import { BreadcrumbSchema } from '@/components/seo/structured-data';
import { SwipeNavigation } from '@/hooks/use-swipe-gesture'; import { SwipeNavigation } from '@/hooks/use-swipe-gesture';
import { ListPageHeroV3 } from '@/components/detail-v2/list-page-hero-v3';
const modules = [ import { SolutionCardV3 } from '@/components/detail-v2/solution-service-card-v3';
{ import { SOLUTIONS } from '@/lib/constants/solutions';
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)]',
},
];
export default function SolutionsPage() { export default function SolutionsPage() {
const shouldReduceMotion = useReducedMotion(); const shouldReduceMotion = useReducedMotion();
const fadeUp = shouldReduceMotion ? {} : { initial: { opacity: 0, y: 20 } }; 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 ( return (
<div className="min-h-screen bg-[var(--color-bg-primary)]"> <div className="min-h-screen bg-[var(--color-bg-primary)]">
<BreadcrumbSchema items={[{ name: '首页', href: '/' }, { name: '解决方案', href: '/solutions' }]} /> <BreadcrumbSchema items={[{ name: '首页', href: '/' }, { name: '解决方案', href: '/solutions' }]} />
@@ -87,118 +29,77 @@ export default function SolutionsPage() {
prevLabel="产品方案" prevLabel="产品方案"
nextLabel="服务支持" 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"> <div className="container-wide">
<PageNav items={[{ label: '解决方案' }]} /> <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<motion.div {SOLUTIONS.map((solution) => (
{...fadeUp} <SolutionCardV3 key={solution.id} solution={solution} />
animate={shouldReduceMotion ? {} : { opacity: 1, y: 0 }} ))}
transition={{ duration: 0.5 }} </div>
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> </div>
</section> </section>
<section className="pb-20"> {/* 方案→服务关联推荐 */}
<section className="py-16">
<div className="container-wide"> <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 <motion.div
initial={{ opacity: 0, height: 0 }} {...fadeUp}
animate={{ opacity: 1, height: 'auto' }} whileInView={{ opacity: 1 }}
exit={{ opacity: 0, height: 0 }} viewport={{ once: true }}
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }} className="text-center mb-10"
className="overflow-hidden"
> >
{module.paragraphs.map((p, i) => ( <h2 className="text-2xl font-bold text-gray-900 lg:text-3xl mb-3">
<p key={i} className="text-[var(--color-text-primary)] leading-relaxed mb-3 last:mb-0">{p}</p>
))} </h2>
<p className="text-gray-600 max-w-2xl mx-auto">
</p>
</motion.div> </motion.div>
)}
</AnimatePresence>
</div>
<div className="mb-8"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto">
<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 }} /> { 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' },
</h3> { icon: Users, title: '长期陪跑服务', desc: '从需求到落地,持续陪伴', href: '/services', bgColor: 'rgba(22,163,74,0.06)', color: '#16a34a' },
<div className="grid md:grid-cols-3 gap-3"> ].map((item, i) => (
{module.values.map((value, i) => ( <motion.div
<div key={i} className="flex items-start gap-2 p-3 rounded-lg" style={{ backgroundColor: module.accentBg }}> key={i}
<div className="w-1.5 h-1.5 rounded-full mt-2 shrink-0" style={{ backgroundColor: module.accentColor }} /> initial={{ opacity: 0, y: 20 }}
<span className="text-sm text-[var(--color-text-primary)]">{value}</span> whileInView={{ opacity: 1, y: 0 }}
</div> viewport={{ once: true }}
))} transition={{ delay: i * 0.1 }}
</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;
}}
> >
{module.cta} <StaticLink
<ArrowRight className="w-4 h-4" /> href={item.href}
</StaticLink> 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> </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>
</div> </div>
</section> </section>