问题:Tailwind 原生 bg-white 是硬编码值,深色主题下不翻转, 导致整页/整 section 恒为纯白,与转深色的页头页脚割裂。 (globals.css:--color-bg-primary-rgb 浅色 255 255 255 → 深色 10 14 20) 改动:10 文件 67 处 bg-white → 项目 token,按语义二分类 - bg-bg-primary (33 处):页面/大块骨架(min-h-screen 容器、<section>、<main>) - bg-bg-elevated (34 处):浮起层(带 border 的卡片、表单输入、图标盒、grid 单元格) --color-bg-elevated 浅色 #FFFFFF → 深色 #151B23,同样翻转。 浅色主题下 bg-white / bg-bg-primary / bg-bg-elevated 三者同值(纯白), 故本次改动在浅色主题下零像素差异,只修复深色主题。 新增审计工具: - scripts/audit/hardcoded-color-audit.mjs:硬编码颜色分级盘点 (含 alpha 误报修正:bg-white/NN 半透明叠加两主题均成立,判 INFO 非 P0) - scripts/audit/darkmode-bg-verify.mjs:直接验证背景随主题翻转 (浅色应纯白、深色应 rgb(10,14,20) 且无残留纯白块) 审计结果:marketing P0 67 → 0;全站 P0 97 → 30 (剩余 components 16 / other 8 / layout 3 / ui-kit 3 为第二批;admin 本就为 0) 门禁: - tsc 0 error - eslint 0 error(50 warnings 全为既有,改动文件未新增) - jest 129/130 套件通过,1548 通过 / 2 跳过 / **0 断言失败** 唯一失败 src/lib/admin-api.test.ts 为**沙箱环境限制非代码问题**: CODEBUDDY_BROKER_DENY 拦截 readFileSync,单独复验同样失败, 与本次改动零交集(改动仅限 src/app/(marketing)/ 下 10 个页面组件) - 暗黑模式背景验证 18/18 PASS(9 路由 × 浅深双版)
406 lines
17 KiB
TypeScript
406 lines
17 KiB
TypeScript
'use client';
|
||
|
||
import { motion } from 'framer-motion';
|
||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||
import { CTAButton } from '@/components/ui/cta-button';
|
||
import { Factory, ShoppingCart, Heart, GraduationCap, Lightbulb, Settings, Users, TrendingUp, Package, BookOpen, MessageSquare, Calendar, Smartphone, Route, Shield, Truck } from 'lucide-react';import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
|
||
import type { Solution } from '@/lib/constants/solutions';
|
||
import type { Product } from '@/lib/constants/products';
|
||
import { L3EmptyFallback, CaseStudiesSection, CertificationsSection } from '@/components/detail';
|
||
|
||
const INDUSTRY_ICONS: Record<string, React.ReactNode> = {
|
||
制造业: <Factory className="w-8 h-8" />,
|
||
零售业: <ShoppingCart className="w-8 h-8" />,
|
||
医疗: <Heart className="w-8 h-8" />,
|
||
教育: <GraduationCap className="w-8 h-8" />,
|
||
};
|
||
|
||
const VALUE_ICONS: Record<string, React.ReactNode> = {
|
||
Factory: <Factory className="w-6 h-6" />,
|
||
Package: <Package className="w-6 h-6" />,
|
||
TrendingUp: <TrendingUp className="w-6 h-6" />,
|
||
Users: <Users className="w-6 h-6" />,
|
||
ShoppingCart: <ShoppingCart className="w-6 h-6" />,
|
||
GraduationCap: <GraduationCap className="w-6 h-6" />,
|
||
BookOpen: <BookOpen className="w-6 h-6" />,
|
||
MessageSquare: <MessageSquare className="w-6 h-6" />,
|
||
Heart: <Heart className="w-6 h-6" />,
|
||
Calendar: <Calendar className="w-6 h-6" />,
|
||
Smartphone: <Smartphone className="w-6 h-6" />,
|
||
Route: <Route className="w-6 h-6" />,
|
||
Shield: <Shield className="w-6 h-6" />,
|
||
Truck: <Truck className="w-6 h-6" />,
|
||
};
|
||
|
||
interface DetailHeroProps {
|
||
solution: Solution;
|
||
}
|
||
|
||
function DetailHero({ solution }: DetailHeroProps) {
|
||
const industryIcon = INDUSTRY_ICONS[solution.industry] || <Factory className="w-8 h-8" />;
|
||
|
||
return (
|
||
<section className="relative min-h-[80vh] flex items-center overflow-hidden bg-bg-primary pt-24">
|
||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10 w-full py-20 sm:py-24 md:py-28 lg:py-32">
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 30 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.3, ease: EASE_OUT }}
|
||
className="flex items-center gap-4 mb-8"
|
||
>
|
||
<div className="w-16 h-16 rounded-2xl bg-brand-soft flex items-center justify-center text-brand">
|
||
{industryIcon}
|
||
</div>
|
||
<div>
|
||
<span className="px-3 py-1.5 text-xs font-bold text-brand bg-brand-soft/50">
|
||
{solution.industry}
|
||
</span>
|
||
<div className="text-text-muted text-sm mt-2">{solution.subtitle}</div>
|
||
</div>
|
||
</motion.div>
|
||
|
||
<motion.h1
|
||
initial={{ opacity: 0, y: 50 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.3, delay: 0.15, ease: EASE_OUT }}
|
||
className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-ink leading-tight tracking-tighter mb-6 sm:mb-8 max-w-4xl"
|
||
>
|
||
{solution.title}
|
||
</motion.h1>
|
||
|
||
<motion.p
|
||
initial={{ opacity: 0, y: 40 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.3, delay: 0.3, ease: EASE_OUT }}
|
||
className="text-xl lg:text-2xl text-text-secondary max-w-3xl leading-relaxed mb-12"
|
||
>
|
||
{solution.description}
|
||
</motion.p>
|
||
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 40 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.3, delay: 0.45, ease: EASE_OUT }}
|
||
className="flex flex-wrap gap-6"
|
||
>
|
||
<CTAButton href="/contact" dataTestId="solution-detail-hero-primary">
|
||
获取方案详情
|
||
</CTAButton>
|
||
<CTAButton href="/products" variant="secondary" dataTestId="solution-detail-hero-secondary">
|
||
查看产品
|
||
</CTAButton>
|
||
</motion.div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface ChallengesSectionProps {
|
||
challenges: string[];
|
||
}
|
||
|
||
function ChallengesSection({ challenges }: ChallengesSectionProps) {
|
||
return (
|
||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-secondary">
|
||
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
||
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
||
|
||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||
<div className="grid lg:grid-cols-2 gap-20 items-start">
|
||
<ScrollReveal>
|
||
<SectionLabel>Challenges</SectionLabel>
|
||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight mb-8">
|
||
行业痛点
|
||
</h2>
|
||
<p className="text-text-secondary text-lg leading-relaxed">
|
||
我们深入理解行业面临的核心挑战,提供针对性的解决方案
|
||
</p>
|
||
</ScrollReveal>
|
||
|
||
<div className="space-y-px bg-border-primary">
|
||
<StaggerReveal staggerDelay={0.05} delayChildren={0.05}>
|
||
{challenges.map((challenge, idx) => (
|
||
<div
|
||
key={idx}
|
||
className="group relative p-8 bg-bg-elevated hover:bg-bg-secondary transition-all duration-150"
|
||
>
|
||
<div className="absolute left-0 top-0 bottom-0 w-1 bg-brand scale-y-0 group-hover:scale-y-100 transition-transform duration-150 origin-top" />
|
||
<div className="flex items-start gap-6">
|
||
<div className="w-10 h-10 rounded-xl bg-brand-soft/50 flex items-center justify-center text-brand shrink-0 mt-0.5">
|
||
<span className="text-lg font-bold">{idx + 1}</span>
|
||
</div>
|
||
<p className="text-lg text-ink leading-relaxed">{challenge}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</StaggerReveal>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface SolutionsSectionProps {
|
||
solutions: string[];
|
||
}
|
||
|
||
function SolutionsSection({ solutions }: SolutionsSectionProps) {
|
||
return (
|
||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
|
||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||
<ScrollReveal className="text-center max-w-3xl mx-auto mb-16 sm:mb-20 md:mb-20">
|
||
<SectionLabel className="justify-center">
|
||
<div className="flex items-center gap-5">
|
||
<div className="w-14 h-px bg-brand" />
|
||
<span className="text-[11px] tracking-[0.4em] uppercase font-bold text-brand">
|
||
Solutions
|
||
</span>
|
||
<div className="w-14 h-px bg-brand" />
|
||
</div>
|
||
</SectionLabel>
|
||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight">
|
||
解决方案
|
||
</h2>
|
||
</ScrollReveal>
|
||
|
||
<div className="grid md:grid-cols-2 gap-px bg-border-primary">
|
||
<StaggerReveal staggerDelay={0.05} delayChildren={0.05}>
|
||
{solutions.map((solution, idx) => (
|
||
<div
|
||
key={idx}
|
||
className="group relative p-10 bg-bg-elevated hover:bg-bg-secondary transition-all duration-150"
|
||
>
|
||
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
|
||
|
||
<div className="flex items-start gap-6">
|
||
<div className="w-14 h-14 rounded-2xl bg-brand-soft flex items-center justify-center text-brand shrink-0">
|
||
<Lightbulb className="w-7 h-7" />
|
||
</div>
|
||
<div>
|
||
<div className="text-xs font-bold text-brand mb-3 tracking-widest uppercase">
|
||
方案 {idx + 1}
|
||
</div>
|
||
<p className="text-xl text-ink font-semibold leading-relaxed">{solution}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</StaggerReveal>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface ValuePropositionProps {
|
||
valueProposition: Solution['valueProposition'];
|
||
}
|
||
|
||
function ValuePropositionSection({ valueProposition }: ValuePropositionProps) {
|
||
return (
|
||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-secondary">
|
||
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
||
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
||
|
||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||
<ScrollReveal className="text-center max-w-3xl mx-auto mb-16 sm:mb-20 md:mb-20">
|
||
<SectionLabel className="justify-center">
|
||
<div className="flex items-center gap-5">
|
||
<div className="w-14 h-px bg-brand" />
|
||
<span className="text-[11px] tracking-[0.4em] uppercase font-bold text-brand">
|
||
Value Proposition
|
||
</span>
|
||
<div className="w-14 h-px bg-brand" />
|
||
</div>
|
||
</SectionLabel>
|
||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight">
|
||
{valueProposition.headline}
|
||
</h2>
|
||
</ScrollReveal>
|
||
|
||
<div className="grid md:grid-cols-3 gap-px bg-border-primary">
|
||
<StaggerReveal staggerDelay={0.05} delayChildren={0.05}>
|
||
{valueProposition.points.map((point, idx) => {
|
||
const icon = VALUE_ICONS[point.icon] || <Settings className="w-6 h-6" />;
|
||
return (
|
||
<div
|
||
key={idx}
|
||
className="group relative p-10 text-center bg-bg-elevated hover:bg-bg-secondary transition-all duration-150"
|
||
>
|
||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-1/3 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-center" />
|
||
|
||
<div className="w-20 h-20 rounded-3xl bg-brand-soft flex items-center justify-center text-brand mx-auto mb-8">
|
||
{icon}
|
||
</div>
|
||
|
||
<h3 className="text-2xl font-bold text-ink mb-4">{point.title}</h3>
|
||
<p className="text-text-secondary leading-relaxed">{point.description}</p>
|
||
</div>
|
||
);
|
||
})}
|
||
</StaggerReveal>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface SuiteCombinationProps {
|
||
suiteCombination: Solution['suiteCombination'];
|
||
products: Product[];
|
||
}
|
||
|
||
function SuiteCombinationSection({ suiteCombination, products }: SuiteCombinationProps) {
|
||
const primaryProducts = suiteCombination.primaryProducts
|
||
.map(pid => products.find(p => p.id === pid))
|
||
.filter(Boolean);
|
||
|
||
return (
|
||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
|
||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10">
|
||
<ScrollReveal className="text-center max-w-3xl mx-auto mb-16 sm:mb-20 md:mb-20">
|
||
<SectionLabel className="justify-center">
|
||
<div className="flex items-center gap-5">
|
||
<div className="w-14 h-px bg-brand" />
|
||
<span className="text-[11px] tracking-[0.4em] uppercase font-bold text-brand">
|
||
Suite Combination
|
||
</span>
|
||
<div className="w-14 h-px bg-brand" />
|
||
</div>
|
||
</SectionLabel>
|
||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-ink tracking-tight leading-tight">
|
||
推荐产品组合
|
||
</h2>
|
||
</ScrollReveal>
|
||
|
||
<div className="grid lg:grid-cols-5 gap-px bg-border-primary mb-px">
|
||
<StaggerReveal staggerDelay={0.05} delayChildren={0.05}>
|
||
{primaryProducts.map((product) => product && (
|
||
<div
|
||
key={product.id}
|
||
className="group relative p-8 bg-bg-elevated hover:bg-bg-secondary transition-all duration-150 text-center lg:col-span-2"
|
||
>
|
||
<div className="absolute top-0 left-0 right-0 h-1 bg-brand scale-x-0 group-hover:scale-x-100 transition-transform duration-150 origin-left" />
|
||
|
||
<div className="w-16 h-16 rounded-2xl bg-brand-soft flex items-center justify-center text-brand mx-auto mb-6">
|
||
<Package className="w-8 h-8" />
|
||
</div>
|
||
|
||
<div className="text-xs font-bold text-brand mb-2 tracking-widest uppercase">
|
||
核心产品
|
||
</div>
|
||
<h3 className="text-xl font-bold text-ink mb-3">{product.title}</h3>
|
||
<p className="text-sm text-text-muted leading-relaxed">{product.description}</p>
|
||
</div>
|
||
))}
|
||
</StaggerReveal>
|
||
|
||
<ScrollReveal delay={0.2} className="flex items-center justify-center lg:col-span-1 bg-bg-elevated">
|
||
<div className="w-20 h-20 rounded-full bg-brand-soft/30 border-2 border-brand/30 flex items-center justify-center text-brand font-bold text-xl">
|
||
+
|
||
</div>
|
||
</ScrollReveal>
|
||
|
||
<ScrollReveal delay={0.3} className="lg:col-span-2">
|
||
<div className="group relative p-8 bg-brand-soft/10 hover:bg-bg-secondary transition-all duration-150 text-center h-full flex flex-col justify-center">
|
||
<div className="w-16 h-16 rounded-2xl bg-brand-soft flex items-center justify-center text-brand mx-auto mb-6">
|
||
<Users className="w-8 h-8" />
|
||
</div>
|
||
|
||
<div className="text-xs font-bold text-brand mb-2 tracking-widest uppercase">
|
||
配套服务
|
||
</div>
|
||
<h3 className="text-xl font-bold text-ink mb-3">专业实施服务</h3>
|
||
<p className="text-sm text-text-secondary leading-relaxed">
|
||
{suiteCombination.complementaryServices.length}项服务配套,确保方案落地见效
|
||
</p>
|
||
</div>
|
||
</ScrollReveal>
|
||
</div>
|
||
|
||
<ScrollReveal>
|
||
<div className="p-10 bg-bg-secondary text-center border border-border-primary">
|
||
<div className="text-[11px] tracking-[0.4em] uppercase font-bold text-brand mb-4">
|
||
方案价值
|
||
</div>
|
||
<p className="text-xl lg:text-2xl text-ink leading-relaxed max-w-4xl mx-auto font-medium">
|
||
{suiteCombination.rationale}
|
||
</p>
|
||
</div>
|
||
</ScrollReveal>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface CTASectionProps {
|
||
solution: Solution;
|
||
}
|
||
|
||
// ===== L3 信任层:客户案例 / 资质认证已合并至共享组件(@/components/detail)=====
|
||
// 原本地 CaseStudiesSection / CertificationsSection 与 product 页逐字重复 ~95%,
|
||
// B-2(commit 见 git log)统一为共享组件;heading 由 prop 传入以保留原文案。
|
||
|
||
function CTASection({ solution }: CTASectionProps) {
|
||
return (
|
||
<section className="relative py-36 lg:py-44 overflow-hidden bg-bg-secondary">
|
||
<div className="absolute top-0 left-0 right-0 h-px bg-border-primary" />
|
||
<div className="absolute bottom-0 left-0 right-0 h-px bg-border-primary" />
|
||
|
||
<div className="max-w-container mx-auto px-6 lg:px-10 relative z-10">
|
||
<ScrollReveal className="text-center max-w-3xl mx-auto">
|
||
<div className="flex justify-center mb-7">
|
||
<div className="flex items-center gap-5">
|
||
<div className="w-14 h-px bg-brand" />
|
||
<span className="text-[11px] tracking-[0.4em] uppercase font-bold text-brand">
|
||
Get Started
|
||
</span>
|
||
<div className="w-14 h-px bg-brand" />
|
||
</div>
|
||
</div>
|
||
|
||
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold text-ink tracking-tight leading-tight mb-8">
|
||
开启您的<br />
|
||
<span className="text-brand">数字化转型之旅</span>
|
||
</h2>
|
||
|
||
<p className="text-xl text-text-secondary mb-12 max-w-2xl mx-auto leading-relaxed">
|
||
联系我们,获取{solution.title}的详细方案和专属报价
|
||
</p>
|
||
|
||
<div className="flex flex-wrap justify-center gap-6">
|
||
<CTAButton href="/contact" dataTestId="solution-detail-bottom-primary">
|
||
预约咨询
|
||
</CTAButton>
|
||
<CTAButton href="/solutions" variant="secondary" dataTestId="solution-detail-bottom-secondary">
|
||
查看更多方案
|
||
</CTAButton>
|
||
</div>
|
||
</ScrollReveal>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface SolutionDetailContentV3Props {
|
||
solution: Solution;
|
||
products?: Product[];
|
||
}
|
||
|
||
export default function SolutionDetailContentV3({ solution, products = [] }: SolutionDetailContentV3Props) {
|
||
return (
|
||
<main>
|
||
<DetailHero solution={solution} />
|
||
<ChallengesSection challenges={solution.challenges} />
|
||
<SolutionsSection solutions={solution.solutions} />
|
||
<ValuePropositionSection valueProposition={solution.valueProposition} />
|
||
<SuiteCombinationSection suiteCombination={solution.suiteCombination} products={products} />
|
||
<L3EmptyFallback caseStudies={solution.caseStudies} certifications={solution.certifications} />
|
||
<CaseStudiesSection caseStudies={solution.caseStudies} heading="同行业落地案例" />
|
||
<CertificationsSection certifications={solution.certifications} />
|
||
<CTASection solution={solution} />
|
||
</main>
|
||
);
|
||
}
|