Files
novalon-website/src/components/detail/detail-case-studies-section.tsx
T
zhangxiang 10160fb7f2 refactor(detail): 合并 L3 客户案例/资质认证区块为共享组件
- 新建 CaseStudiesSection / CertificationsSection 共享组件(src/components/detail),
  合并 solution/product 逐字重复的本地实现与 service 的异类实现(卡片 motion.a
  链 /cases/<slug> 依赖尚不存在数据 → 404,违反零编造)
- bg-white → bg-bg-primary token 化(浅色零视觉差异,深色模式正确反色,对齐暗黑 Phase 2)
- 数据闸门保留:caseStudies/certifications 空时 return null,由 L3EmptyFallback 接管
- grid/flex 类直接挂 StaggerReveal,修复合并前的单列布局 bug
- 动效 0.8s → 0.7s(detail-cta-section + service CTASection),对齐 ≤700ms 约束
- 附 scripts/audit/l3-b2-verify.mjs 运行时冒烟(product 页浅深双版,0 报错,hydration 通过)

验证:type-check 0 错 / lint 0 错(127 既有 warnings) / 单测 129 套件 1628 通过
2026-09-02 12:51:03 +08:00

113 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
/**
* CaseStudiesSection — L3 信任层「客户案例」共享区块(B-2 合并自 3 份本地实现)
*
* 合并来源与差异消解(2026-09-02,修订方案 B' 第二步):
* - solution-detail-content-v3 / product-detail-content-v3 的本地实现原本 ~95% 相同,
* 仅 section 底色(bg-white vs bg-bg-primary)与 h2 文案不同 → 统一为 token 底色
* bg-bg-primary:浅色与 bg-white 视觉零差异,深色模式正确反色),
* h2 文案改由 heading prop 传入(solution 传「同行业落地案例」,product 用默认「客户案例」)。
* - service-detail-content-v4 的本地实现是异类(motion.a 链接 /cases/<slug>、无 SectionLabel、
* 动效 0.8s 违规)→ 统一到本组件:卡片不带链接(/cases/<slug> 依赖尚不存在的案例数据,
* 链向 404 违反零编造);如需锚点(service hero 的 #cases CTA)用 id prop。
*
* 布局要点:grid 类必须挂在 StaggerReveal 自身——它会给每个子项包一层 motion.div
* 放外层 div 时包装层成为 grid 唯一子项,卡片会堆成单列(commit 3091035 踩坑记录)。
*
* 数据闸门:caseStudies 为空时 return nullL3EmptyFallback 接管兜底渲染)。
*/
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
import { SectionLabel } from '@/components/ui/page-decoration';
/** 三处数据源(cases.ts / services.ts / products.ts)的结构公共子集 */
export interface CaseStudyItem {
client: string;
industry: string;
challenge: string;
solution?: string;
result: string;
}
interface CaseStudiesSectionProps {
caseStudies?: readonly CaseStudyItem[];
/** h2 标题文案(solution 页传「同行业落地案例」,其余用默认) */
heading?: string;
/** 锚点 idservice 页 hero 的 #cases CTA 依赖) */
id?: string;
}
export function CaseStudiesSection({
caseStudies,
heading = '客户案例',
id,
}: CaseStudiesSectionProps) {
if (!caseStudies || caseStudies.length === 0) return null;
return (
<section id={id} className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
<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">
客户案例
</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">
{heading}
</h2>
</ScrollReveal>
{/* grid 直接挂 StaggerReveal(见文件头注释),gap-px 露出容器底色形成发丝分隔线 */}
<StaggerReveal
staggerDelay={0.1}
delayChildren={0.05}
className="grid md:grid-cols-2 lg:grid-cols-3 gap-px bg-border-primary"
>
{caseStudies.map((study) => (
<div
key={study.client}
className="group relative block h-full overflow-hidden bg-bg-primary transition-all duration-300 hover:bg-bg-secondary"
>
<div className="aspect-[4/3] bg-gradient-to-br from-bg-secondary to-bg-tertiary relative overflow-hidden">
<div className="absolute inset-0 flex items-center justify-center">
{/* 客户名首字作占位图形(无真实配图时),纯装饰 → aria-hidden 豁免对比度 */}
<span aria-hidden="true" className="text-6xl font-bold text-text-muted/10">
{study.client.charAt(0)}
</span>
</div>
<div className="absolute top-5 left-5">
<span className="px-3 py-1.5 text-xs font-bold text-brand bg-brand-soft/80 backdrop-blur-sm">
{study.industry}
</span>
</div>
</div>
<div className="p-8">
<h3 className="text-xl font-bold text-ink mb-3 group-hover:translate-x-2 transition-transform duration-300">
{study.client}
</h3>
<p className="text-text-secondary text-sm leading-relaxed mb-6">
{study.challenge}
</p>
<div className="pt-6 border-t border-border-primary">
<div className="text-2xl font-bold text-brand mb-1">{study.result}</div>
<div className="text-xs text-text-muted">核心成果</div>
</div>
</div>
</div>
))}
</StaggerReveal>
</div>
</section>
);
}