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
+16 -2
View File
@@ -1,4 +1,4 @@
const VERSION = '0.8.0';
const VERSION = '0.8.1';
const HB_LIMIT = 48;
const UPTIME_DAYS = 90;
const BROWSER_CONCURRENCY = 6;
@@ -507,14 +507,23 @@ function renderViewSwitch() {
}
}
// 窄屏(手机)判定:表格在最窄的屏幕上必须横向拖动,不如默认给卡片视图
function isNarrowScreen() {
return window.matchMedia('(max-width: 700px)').matches;
}
function resolveView() {
const q = new URLSearchParams(location.search).get('view');
if (q && VIEWS[q]) return q;
let saved = null;
try {
saved = localStorage.getItem('shiroko.view');
} catch { /* 隐私模式下 localStorage 不可用 */ }
const cfg = state.config?.settings?.view;
return [q, saved, cfg, DEFAULT_VIEW].find((v) => v && VIEWS[v]) ?? DEFAULT_VIEW;
const resolved = [saved, cfg, DEFAULT_VIEW].find((v) => v && VIEWS[v]) ?? DEFAULT_VIEW;
// 用户没在这个设备上显式选过视图时,窄屏自动落到卡片(表格在手机上要横向拖)
if (!saved && resolved === 'table' && isNarrowScreen()) return 'cards';
return resolved;
}
function setView(view, { persist = true } = {}) {
@@ -802,6 +811,11 @@ function bindEvents() {
// 只有手动点「刷新」才会触发一次重新检测;自动刷新是只读的
$('refresh-btn').addEventListener('click', () => refresh({ force: true }));
$('theme-btn')?.addEventListener('click', () => applyTheme(state.theme === 'shiroko' ? 'default' : 'shiroko'));
// 横竖屏切换 / 窗口缩放时重算视图(不覆盖用户显式选择,故 persist: false)
window.addEventListener('resize', () => {
const next = resolveView();
if (next !== state.view) setView(next, { persist: false });
});
}
/* ---------- 启动 ---------- */
+15 -2
View File
@@ -91,6 +91,8 @@ body {
.brand-text p { margin: 0; font-size: 12px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.summary { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
.stats { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; }
.controls { display: flex; align-items: center; gap: 8px; }
.pill {
padding: 4px 12px;
@@ -323,9 +325,20 @@ body {
.demo-chip { padding: 1px 8px; border-radius: 999px; background: var(--pending-bg); color: var(--pending); font-weight: 600; }
.warn-chip { padding: 1px 8px; border-radius: 999px; background: var(--down-bg); color: var(--down); font-weight: 600; }
/* 窄屏(手机 / 竖屏平板):顶栏改为三行——标题 / 统计 / 控件,避免挤成一团 */
@media (max-width: 820px) {
.topbar { flex-direction: column; align-items: stretch; gap: 10px; padding: 12px 14px 14px; }
.summary { flex-direction: column; align-items: stretch; gap: 8px; }
.stats { justify-content: space-between; gap: 4px; }
.stat { min-width: 0; flex: 1 1 0; }
.stat b { font-size: 15px; }
.stat label { font-size: 10px; }
.controls { justify-content: flex-end; gap: 10px; }
.footer { flex-direction: column; align-items: flex-start; gap: 4px; padding-bottom: 20px; }
.footer .version { margin-left: 0; }
}
@media (max-width: 640px) {
.topbar { padding: 10px 14px; }
.summary { gap: 6px 12px; }
.grid { grid-template-columns: 1fr; padding: 12px 14px 8px; }
.group-tabs { padding: 12px 14px 0; }
.footer { padding: 8px 14px 24px; }
+8 -1
View File
@@ -215,7 +215,14 @@ body[data-theme="shiroko"] .artwork {
user-select: none;
filter: drop-shadow(0 6px 18px rgb(23 60 100 / 15%));
}
@media (max-width: 1100px) {
/* 窄屏:立绘与固定装饰全部撤掉,避免压住内容或溢出(手机只保留配色与围巾飘带) */
@media (max-width: 820px) {
body[data-theme="shiroko"] .artwork,
body[data-theme="shiroko"]::before,
body[data-theme="shiroko"]::after { display: none; }
}
/* 横屏手机:可用高度很小,立绘同样撤掉 */
@media (max-height: 520px) {
body[data-theme="shiroko"] .artwork { display: none; }
}
/* 卡片视图行数多、卡片更靠右下,立绘再收一档,尽量让它落在卡片之外的角落 */
+22 -18
View File
@@ -20,23 +20,27 @@
</div>
<div class="summary">
<span id="overall-pill" class="pill pending">检测中…</span>
<div class="stat"><b id="stat-total">–</b><label>监控数</label></div>
<div class="stat stat-up"><b id="stat-up">–</b><label>在线</label></div>
<div class="stat stat-down"><b id="stat-down">–</b><label>离线</label></div>
<div class="stat"><b id="stat-latency">–</b><label>平均延迟</label></div>
<div class="stat"><b id="stat-uptime">–</b><label>90天可用率</label></div>
<div class="view-switch" id="view-switch" role="group" aria-label="视图切换"></div>
<button id="theme-btn" type="button" class="icon-btn" title="切换主题">
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle cx="50" cy="50" r="31" fill="none" stroke="currentColor" stroke-width="8"/>
<circle cx="50" cy="50" r="15" fill="none" stroke="currentColor" stroke-width="4" opacity="0.7"/>
<polygon points="46.4,4 53.6,4 51.2,15 48.8,15" fill="currentColor"/>
<polygon points="46.4,96 53.6,96 51.2,85 48.8,85" fill="currentColor"/>
<polygon points="4,46.4 4,53.6 15,51.2 15,48.8" fill="currentColor"/>
<polygon points="96,46.4 96,53.6 85,51.2 85,48.8" fill="currentColor"/>
</svg>
</button>
<button id="refresh-btn" type="button">刷新</button>
<div class="stats">
<div class="stat"><b id="stat-total">–</b><label>监控数</label></div>
<div class="stat stat-up"><b id="stat-up">–</b><label>在线</label></div>
<div class="stat stat-down"><b id="stat-down">–</b><label>离线</label></div>
<div class="stat"><b id="stat-latency">–</b><label>平均延迟</label></div>
<div class="stat"><b id="stat-uptime">–</b><label>90天可用率</label></div>
</div>
<div class="controls">
<div class="view-switch" id="view-switch" role="group" aria-label="视图切换"></div>
<button id="theme-btn" type="button" class="icon-btn" title="切换主题">
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle cx="50" cy="50" r="31" fill="none" stroke="currentColor" stroke-width="8"/>
<circle cx="50" cy="50" r="15" fill="none" stroke="currentColor" stroke-width="4" opacity="0.7"/>
<polygon points="46.4,4 53.6,4 51.2,15 48.8,15" fill="currentColor"/>
<polygon points="46.4,96 53.6,96 51.2,85 48.8,85" fill="currentColor"/>
<polygon points="4,46.4 4,53.6 15,51.2 15,48.8" fill="currentColor"/>
<polygon points="96,46.4 96,53.6 85,51.2 85,48.8" fill="currentColor"/>
</svg>
</button>
<button id="refresh-btn" type="button">刷新</button>
</div>
</div>
</header>
@@ -52,7 +56,7 @@
<span id="ur-chip" class="footer-item warn-chip" hidden>UptimeRobot 同步异常</span>
<span id="updated-at" class="footer-item">–</span>
<span id="countdown" class="footer-item">–</span>
<span class="footer-item version">shiroko-monitor v0.8.0</span>
<span class="footer-item version">shiroko-monitor v0.8.1</span>
</footer>
<script type="module" src="assets/app.js"></script>
+408 -413
View File
File diff suppressed because it is too large Load Diff