fix(mobile): 修移动端顶栏塌陷,窄屏默认卡片视图

问题(用户报告移动端异常):窄屏下顶栏统计挤成竖排并与标题重叠,
表格在手机上必须横向拖动,固定装饰在小屏上可能溢出或压住内容。

修复:
- 顶栏结构拆为「标题 / 统计(.stats) / 控件(.controls)」三段;
  ≤820px 改为纵向三行,统计均分视口宽度,页脚改纵向堆叠
- ≤700px 且用户未显式选择视图时自动用卡片(横竖屏切换时重算,
  显式选择仍尊重 localStorage)
- ≤820px(及横屏手机)撤掉立绘、公路车、光环水印等固定装饰

验证:
- 390x844 / 360x640 / 414x896 / 844x390 四种视口:0 横向溢出,顶栏三行
- verify-views 新增移动端 7 条断言(22/22 通过)
- 四套合计 81 项全通过(17+22+20+22)
This commit is contained in:
tamakyi
2026-10-13 20:38:39 +08:00
parent 5b417c84f4
commit f0e5d91171
15 changed files with 523 additions and 442 deletions
+37
View File
@@ -138,6 +138,43 @@ async function main() {
fs.writeFileSync(CONFIG, raw); // 还原
}
// ---------- H. 移动端(窄屏) ----------
console.log('\n[H] 移动端窄屏');
const mCtx = await browser.newContext({ viewport: { width: 390, height: 844 }, isMobile: true, deviceScaleFactor: 2 });
const mp = await mCtx.newPage();
const mErrors = [];
mp.on('pageerror', (e) => mErrors.push(e.message));
// 不带 ?view=:窄屏下应自动落到卡片视图(表格在手机上必须横向拖动)
await mp.goto(`${BASE}/?source=api`, { waitUntil: 'domcontentloaded' });
await mp.waitForSelector('.card', { timeout: 20000 });
await mp.waitForTimeout(1500);
const mob = await mp.evaluate(() => {
const top = (sel) => { const el = document.querySelector(sel); return el ? Math.round(el.getBoundingClientRect().top) : -1; };
const statsW = Math.round(document.querySelector('.stats')?.getBoundingClientRect().width ?? 0);
const lastFooter = [...document.querySelectorAll('.footer-item')].pop();
return {
view: document.body.dataset.view,
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
brandTop: top('.brand'), statsTop: top('.stats'), controlsTop: top('.controls'),
statsW, vw: window.innerWidth,
artwork: getComputedStyle(document.querySelector('.artwork') ?? document.body).display,
beforeDisplay: getComputedStyle(document.body, '::before').display,
afterDisplay: getComputedStyle(document.body, '::after').display,
footerRight: lastFooter ? Math.round(lastFooter.getBoundingClientRect().right) : -1,
cards: document.querySelectorAll('.card').length,
tabsWrapped: getComputedStyle(document.querySelector('.topbar')).flexDirection,
};
});
check('窄屏默认使用卡片视图', mob.view === 'cards', `data-view=${mob.view}`);
check('窄屏无横向溢出', mob.overflow <= 0, `溢出 ${mob.overflow}px`);
check('顶栏在窄屏改为纵向三行(标题/统计/控件)', mob.brandTop < mob.statsTop && mob.statsTop < mob.controlsTop && mob.tabsWrapped === 'column', `top: ${mob.brandTop}/${mob.statsTop}/${mob.controlsTop}, flex=${mob.tabsWrapped}`);
check('统计行铺满视口宽度', Math.abs(mob.statsW - mob.vw) <= 32, `stats=${mob.statsW} 视口=${mob.vw}`);
check('窄屏撤掉立绘与固定装饰', mob.artwork === 'none' && mob.beforeDisplay === 'none' && mob.afterDisplay === 'none', `立绘=${mob.artwork} 左装饰=${mob.beforeDisplay} 光环水印=${mob.afterDisplay}`);
check('页脚内容不被裁切', mob.footerRight > 0 && mob.footerRight <= mob.vw, `最后一项右边界 ${mob.footerRight} / 视口 ${mob.vw}`);
check('窄屏渲染无 JS 错误', mErrors.length === 0, mErrors.join(' | ') || '无');
await mp.screenshot({ path: path.join(__dirname, '..', 'docs', 'screenshot-mobile.png'), fullPage: true });
await mCtx.close();
// 429 是刷新限流保护在起作用(快速连续导航各触发一次强制探测),面板已优雅降级为只读数据;
// 这里只把 429 以外的错误视为失败
const hardErrors = errors.filter((e) => !/429/.test(e));