feat(detail): Layer 3 信任层空数据兜底骨架(L3SignalsSlot + L3EmptyFallback + EarlyAccessNotice)

- 新增 L3SignalsSlot:4 类可验证替代信号卡(方法论/团队/历程/服务承诺),
  服务承诺卡 disabled 占位待业务确认;零编造约束下替代真实案例/认证
- 新增 EarlyAccessNotice:首批客户共创状态条(单一真源 company.ts EARLY_ACCESS)
- 新增 L3EmptyFallback:caseStudies/certifications/dataProofs 三者全空时渲染兜底,
  任一非空 return null 保留本地真实 section 视觉
- 接入 5 处详情页:solution v3 / service v4 / product v3 / erp-upgrade-v3 / standalone/[id]
- 修复 StaggerReveal 网格布局:grid 类须挂在 StaggerReveal 自身(否则卡片堆单列)
- 更新 solution 测试至新行为(空数据→兜底渲染)+ 补 lucide 图标 mock
- 修复 header.test.tsx 双 logo 断言(2593599 遗留)
- 新增 scripts/audit/l3-fallback-verify.mjs 运行时验证(浅/深双版 PASS)
  注:必须用 localhost 而非 127.0.0.1 —— Next dev allowedDevOrigins 默认白名单
  不含 127.0.0.1,chunk 请求带该 Origin 被 403 → hydration 不发生 → 动画全冻结

验证:type-check 0 错 · lint 0 错(127 既有 warnings) · 单测 129 套件/1628 通过 ·
L3 兜底浅深双版渲染截图 PASS
This commit is contained in:
2026-09-02 12:26:07 +08:00
parent 2a8b81f042
commit 3091035c56
12 changed files with 446 additions and 3 deletions
@@ -6,6 +6,7 @@ import { DetailHero } from '@/components/detail/detail-hero';
import { ProductValueSection } from '@/components/detail/detail-product-value';
import { DetailTrustSection } from '@/components/detail/detail-trust-section';
import { DetailCTASection } from '@/components/detail/detail-cta-section';
import { L3EmptyFallback } from '@/components/detail';
import type { HeroTheme } from '@/lib/constants/hero-themes';
import type { Product, CaseStudy, DataProof, Certification } from '@/lib/constants/products';
@@ -70,6 +71,7 @@ export default async function ERPUpgradeV3Page() {
<ProductValueSection product={data as unknown as Product} />
<L3EmptyFallback caseStudies={caseStudies} dataProofs={dataProofs} certifications={certifications} />
<DetailTrustSection
caseStudies={caseStudies}
dataProofs={dataProofs}
@@ -8,6 +8,7 @@ import { ArrowRight, Package, Check, Settings, Users, FileText, Cpu, Shield, Tre
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
import type { Product } from '@/lib/constants/products';
import { MethodologyFramework, TechStackShowcase, FAQSection, DataProofSection } from '@/components/content/sections';
import { L3EmptyFallback } from '@/components/detail';
const PRODUCT_ICONS: Record<string, React.ReactNode> = {
'睿新ERP管理系统': <Package className="w-8 h-8" />,
@@ -453,6 +454,7 @@ export default function ProductDetailContentV3({ product }: ProductDetailContent
categories={product.techStack}
/>
)}
<L3EmptyFallback caseStudies={product.caseStudies} certifications={product.certifications} dataProofs={product.dataProofs} />
{product.dataProofs && product.dataProofs.length > 0 && (
<DataProofSection
title="可衡量的成果"
@@ -5,6 +5,7 @@ import { CTAButton } from '@/components/ui/cta-button';
import { DetailHero } from '@/components/detail/detail-hero';
import { DetailTrustSection } from '@/components/detail/detail-trust-section';
import { DetailCTASection } from '@/components/detail/detail-cta-section';
import { L3EmptyFallback } from '@/components/detail';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { motion } from 'framer-motion';
import { CheckCircle2, Shield, Lock, Cpu, HardDrive, FileCheck, Gauge } from 'lucide-react';
@@ -257,6 +258,7 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
</section>
)}
<L3EmptyFallback caseStudies={caseStudies} dataProofs={dataProofs} certifications={certifications} />
<DetailTrustSection
caseStudies={caseStudies}
dataProofs={dataProofs}
@@ -5,6 +5,7 @@ import { CTAButton } from '@/components/ui/cta-button';
import { ArrowUpRight, CheckCircle2, Zap, Clock, Users, Award, TrendingUp, Shield } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import type { Service, CaseStudy } from '@/lib/constants/services';
import { L3EmptyFallback } from '@/components/detail';
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
@@ -350,6 +351,7 @@ export default function ServiceDetailContentV4({ service }: { service: Service }
<OverviewSection service={service} />
<FeaturesSection service={service} />
<ProcessSection service={service} />
<L3EmptyFallback caseStudies={service.caseStudies} />
<CaseStudiesSection caseStudies={service.caseStudies} />
<CTASection />
</main>
@@ -63,6 +63,11 @@ jest.mock('lucide-react', () => {
Route: mockIcon('route'),
Shield: mockIcon('shield'),
Truck: mockIcon('truck'),
// L3SignalsSlot(L3 空数据兜底,commit: L3 骨架)用到的图标
ArrowUpRight: mockIcon('arrow-up-right'),
Compass: mockIcon('compass'),
Milestone: mockIcon('milestone'),
ShieldCheck: mockIcon('shield-check'),
};
});
@@ -87,11 +92,24 @@ const baseSolution = {
} as Solution;
describe('SolutionDetailContentV3 - L3 信任层三档策略', () => {
it('does not render L3 sections when no case/certification data exists', () => {
it('renders L3 fallback (signals + early-access notice) when no case/certification data exists', () => {
// 修订方案 B'(2026-09-02):空数据不再是「整段消失 = 信任真空」,
// 而是由 L3EmptyFallback 渲染 4 类替代信号卡 + 首批客户共创占位。
render(<SolutionDetailContentV3 solution={baseSolution} />);
// 真实数据 section 仍不渲染
expect(screen.queryByText('同行业落地案例')).not.toBeInTheDocument();
expect(screen.queryByText('资质认证')).not.toBeInTheDocument();
// 兜底:信任锚点信号区 + 共创占位
expect(screen.getByText('在数据之外,我们用透明度建立信任')).toBeInTheDocument();
expect(screen.getByRole('link', { name: /交付方法论/ })).toHaveAttribute('href', '/methodology');
expect(screen.getByRole('link', { name: /团队资历/ })).toHaveAttribute('href', '/team');
expect(screen.getByRole('link', { name: /发展历程/ })).toHaveAttribute('href', '/about/brand');
// 服务承诺卡:disabled 占位,不可点击
expect(screen.getByText(/服务承诺/)).toBeInTheDocument();
expect(screen.getByText('待业务确认')).toBeInTheDocument();
expect(screen.getByText('首批客户共创中')).toBeInTheDocument();
});
it('renders case studies when data is available', () => {
@@ -6,6 +6,7 @@ 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 } from '@/components/detail';
const INDUSTRY_ICONS: Record<string, React.ReactNode> = {
制造业: <Factory className="w-8 h-8" />,
@@ -514,6 +515,7 @@ export default function SolutionDetailContentV3({ solution, products = [] }: Sol
<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} />
<CertificationsSection certifications={solution.certifications} />
<CTASection solution={solution} />