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:
@@ -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));
|
||||
|
||||
+6
-4
@@ -107,12 +107,14 @@ async function main() {
|
||||
const pageG = await browser.newPage({ viewport: { width: 1280, height: 900 } });
|
||||
await pageG.goto(`${BASE}/?source=api&view=table`, { waitUntil: 'domcontentloaded' });
|
||||
await pageG.waitForSelector('table.monitor-table tbody tr[data-status]', { timeout: 20000 });
|
||||
await pageG.waitForTimeout(1200);
|
||||
// 等首轮刷新结束(倒计时在刷新完成时才重置),否则读到的是加载中的旧值,偏差会被算进断言
|
||||
await pageG.waitForFunction(() => document.getElementById('refresh-btn').textContent === '刷新' && !document.getElementById('refresh-btn').disabled, null, { timeout: 45000 });
|
||||
const shownText = await pageG.evaluate(() => document.getElementById('countdown').textContent);
|
||||
const shownSec = Number((shownText.match(/(\d+)/) ?? [])[1]);
|
||||
// 期望值需在当前时刻重算:面板显示的是「服务端上次更新 + 一个周期 - 现在」
|
||||
const expectSec = Math.round((updatedMs + cadence * 1000 - Date.now()) / 1000);
|
||||
check('倒计时对齐服务端下次更新时刻', Math.abs(shownSec - expectSec) <= 4, `面板 ${shownText}(服务端还有约 ${expectSec}s,节奏 ${cadence}s)`);
|
||||
const shownSec = Number((shownText.match(/(\d+)/) ?? [])[1]);
|
||||
check('倒计时对齐服务端下次更新时刻', shownSec >= expectSec - 2 && shownSec <= expectSec + 4, `面板 ${shownText}(服务端还有约 ${expectSec}s,节奏 ${cadence}s)`);
|
||||
// 注:无头浏览器会冻结页面定时器(实测 performance.now() 会在采样间隔内跳跃十几秒),
|
||||
// 因此不断言「N 秒内递减」,只断言与公式一致、且不退化为整周期重计——这两条不依赖定时器
|
||||
check('倒计时不会退化为从整周期重新计时', shownSec <= Math.max(5, Math.min(cadence, Number(cfg.settings.refreshInterval) || cadence)), `显示 ${shownSec}s`);
|
||||
await pageG.close();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user