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 通过
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
'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 null(L3EmptyFallback 接管兜底渲染)。
|
||||
*/
|
||||
|
||||
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;
|
||||
/** 锚点 id(service 页 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user