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,70 @@
|
||||
// B-2 运行时冒烟:确认合并后的共享 L3 组件(CaseStudiesSection /
|
||||
// CertificationsSection)接入详情页后不破坏页面、hydration 成功、无控制台报错。
|
||||
//
|
||||
// 关键约束(2026-09-02):当前所有 product/service/solution 的 caseStudies /
|
||||
// certifications 均为空数组(零编造前提,刻意留空),dataProofs 有填充。
|
||||
// 因此 product 页 L3 整段不渲染(L3EmptyFallback 因 dataProofs 非空而休眠,
|
||||
// CaseStudiesSection/CertificationsSection 因 caseStudies/certs 空而 return null)。
|
||||
// 本脚本不验证「填充态」渲染(无真实数据,禁止编造),只验证:
|
||||
// 1) 页面正常加载、hero 渲染(非空白)
|
||||
// 2) hydration 完成(framer-motion 元素 opacity 达到 1)
|
||||
// 3) 无未捕获控制台错误(合并 import 未破坏模块图)
|
||||
// 4) L3 区块按数据闸门正确缺席(无「客户案例」h2 = CaseStudiesSection 休眠)
|
||||
// 浅色 + 深色双版截图留证。
|
||||
|
||||
import { chromium } from 'playwright';
|
||||
import { writeFileSync, mkdirSync } from 'node:fs';
|
||||
|
||||
const BASE = 'http://localhost:3000';
|
||||
const OUT = 'dogfood-b2-verify';
|
||||
mkdirSync(OUT, { recursive: true });
|
||||
|
||||
const browser = await chromium.launch({ args: ['--no-proxy-server'] });
|
||||
|
||||
async function check(path, theme) {
|
||||
const ctx = await browser.newContext();
|
||||
const page = await ctx.newPage();
|
||||
if (theme === 'dark') {
|
||||
await page.addInitScript(() => {
|
||||
try { localStorage.setItem('novalon-theme', 'dark'); } catch {}
|
||||
});
|
||||
}
|
||||
const errors = [];
|
||||
page.on('console', (m) => { if (m.type() === 'error') errors.push(m.text()); });
|
||||
page.on('pageerror', (e) => errors.push('PAGEERROR: ' + e.message));
|
||||
|
||||
await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 30000 });
|
||||
|
||||
// hydration 探测:轮询 framer-motion 元素 opacity === '1'
|
||||
let hydrated = false;
|
||||
try {
|
||||
await page.waitForFunction(() => {
|
||||
const els = Array.from(document.querySelectorAll('div[style*="opacity"]'));
|
||||
return els.some((el) => getComputedStyle(el).opacity === '1');
|
||||
}, { timeout: 10000 });
|
||||
hydrated = true;
|
||||
} catch { hydrated = false; }
|
||||
|
||||
const heroText = await page.locator('h1').first().innerText().catch(() => '(no h1)');
|
||||
const hasCaseStudiesH2 = await page.locator("h2:has-text('客户案例')").count();
|
||||
const l3Signals = await page.locator("#l3-signals-heading").count();
|
||||
|
||||
const file = `${OUT}/erp-${theme}.png`;
|
||||
await page.screenshot({ path: file, fullPage: true });
|
||||
await ctx.close();
|
||||
|
||||
return {
|
||||
theme, path, hydrated, heroText: heroText.slice(0, 40),
|
||||
hasCaseStudiesH2, l3Signals, errors, file,
|
||||
};
|
||||
}
|
||||
|
||||
const results = [];
|
||||
results.push(await check('/products/erp', 'light'));
|
||||
results.push(await check('/products/erp', 'dark'));
|
||||
|
||||
await browser.close();
|
||||
|
||||
const report = { generatedAt: new Date().toISOString(), results };
|
||||
writeFileSync(`${OUT}/report.json`, JSON.stringify(report, null, 2));
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
Reference in New Issue
Block a user