Files
shiroko-monitor/public/assets/style.css
T
tamakyi a3a683ff0e fix(ui): 表格行高统一 + 事件区块改为与表格同一套样式
1) 行高不一致(「狼的评论系统」明显矮一截)
   实测在线行 58px / 离线行 43px。根因是可用率列的 90 格 mini 条:
   它是 inline-flex,参与行内布局时基线把行盒抬高约 15px,于是「有可用率
   数据的行」比不带条的行高出一截。
   修复:单元格内容统一 vertical-align: middle + mini 条容器改 flex;
   实测 6 行全部 43px。

2) 「最近事件」与表格样式不统一
   去掉独立的时间线卡片风格,改为与监控表格同构的表格(相同容器圆角、
   表头、行分隔、hover、状态底色);故障行淡红、证书行淡琥珀、恢复行不染。
   刻意不共用 .monitor-table 类名 —— 共用时面板条目统计被多算 1~8 个
   (测试当场抓到),改为独立 .events-table 并显式声明所需样式。

说明:内容长度相当时保证等高;名称/地址特别长时允许换行(上一版按用户
要求做的「完整显示」),此时行更高属于数据本身需要,不再用截断换整齐。

测试:verify-views 新增「内容等长时各行等高」「事件表与监控表同构」
「事件行复用状态底色」断言(并修正了断言脚本改错文件的问题 —— 名称来自
monitors.json 而非 status.json)。六套 171 项全通过。
2026-10-14 10:49:01 +08:00

523 lines
18 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:root {
--bg: #f5f6f8;
--surface: #ffffff;
--border: #e5e7eb;
--text: #111827;
--text-2: #6b7280;
--up: #16a34a;
--up-text: #15803d;
--up-bg: #f0fdf4;
--down: #dc2626;
--down-text: #b91c1c;
--down-bg: #fef2f2;
--pending: #d97706;
--pending-text: #92400e;
--pending-bg: #fffbeb;
--accent: #2563eb;
--uptime-ok: #16a34a;
--uptime-warn: #f59e0b;
--uptime-bad: #dc2626;
--uptime-none: #e5e7eb;
--mono: ui-monospace, SFMono-Regular, "Cascadia Code", Consolas, "Courier New", monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--shadow: 0 1px 2px rgb(0 0 0 / 0.04);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f1115;
--surface: #171a21;
--border: #2a2f3a;
--text: #e5e7eb;
--text-2: #9ca3af;
--up: #4ade80;
--up-text: #4ade80;
--up-bg: #10281c;
--down: #f87171;
--down-text: #f87171;
--down-bg: #2a1517;
--pending: #fbbf24;
--pending-text: #fbbf24;
--pending-bg: #2a2113;
--accent: #60a5fa;
--uptime-none: #2a2f3a;
--shadow: none;
}
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--sans);
font-size: 14px;
line-height: 1.5;
}
/* ---------- 顶栏 ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px 20px;
padding: 12px 20px;
background: var(--surface);
border-bottom: 1px solid var(--border);
box-shadow: var(--shadow);
}
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.logo {
width: 26px;
height: 26px;
flex: none;
border-radius: 50%;
background: var(--up);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--up) 18%, transparent);
animation: pulse 2.4s ease-out infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--up) 30%, transparent); }
70% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--up) 0%, transparent); }
100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--up) 0%, transparent); }
}
.brand-text { min-width: 0; }
.brand-text h1 { margin: 0; font-size: 15px; font-weight: 650; white-space: nowrap; }
.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;
border-radius: 999px;
font-size: 13px;
font-weight: 650;
white-space: nowrap;
}
.pill.ok { background: var(--up-bg); color: var(--up-text); }
.pill.bad { background: var(--down-bg); color: var(--down-text); }
.pill.pending { background: var(--pending-bg); color: var(--pending-text); }
.stat { text-align: center; min-width: 44px; }
.stat b { display: block; font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.stat label { font-size: 11px; color: var(--text-2); }
.stat-up b { color: var(--up-text); }
.stat-down b { color: var(--down-text); }
#refresh-btn {
display: inline-flex;
align-items: center;
padding: 6px 14px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface);
color: var(--text);
font-size: 13px;
font-family: var(--sans);
cursor: pointer;
transition: background 0.15s;
}
#refresh-btn:hover { background: var(--bg); }
#refresh-btn:disabled { opacity: 0.6; cursor: default; }
/* 加载态用的车轮图标(砂狼白子骑行意象) */
.wheel { width: 14px; height: 14px; margin-right: 6px; animation: spin 0.9s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* 图标按钮(主题切换) */
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
padding: 0;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface);
color: var(--text-2);
cursor: pointer;
transition: color 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.icon-btn svg { width: 16px; height: 16px; }
.icon-btn:hover { color: var(--accent); border-color: var(--accent); }
.icon-btn.active { color: var(--accent); border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent); }
/* ---------- 视图切换 ---------- */
.view-switch { display: flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.view-btn {
padding: 5px 10px;
border: 0;
background: var(--surface);
color: var(--text-2);
font-size: 12px;
font-family: var(--sans);
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.view-btn + .view-btn { border-left: 1px solid var(--border); }
.view-btn:hover { color: var(--text); }
.view-btn.active { background: var(--accent); color: #fff; }
/* ---------- 分组页签 ---------- */
.group-tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
max-width: 1200px;
margin: 0 auto;
padding: 16px 20px 0;
}
.tab {
padding: 5px 14px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface);
color: var(--text-2);
font-size: 13px;
font-family: var(--sans);
cursor: pointer;
transition: all 0.15s;
}
.tab:hover { color: var(--text); }
.tab.active { background: var(--accent); border-color: var(--accent); color: #fff; }
/* ---------- 卡片网格 ---------- */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
gap: 14px;
max-width: 1200px;
margin: 0 auto;
padding: 16px 20px 8px;
}
.group-title {
grid-column: 1 / -1;
margin: 8px 0 0;
font-size: 13px;
font-weight: 650;
color: var(--text-2);
}
.card {
display: flex;
flex-direction: column;
gap: 10px;
/* 网格项默认 min-width:auto,长 URL/名称会把卡片撑破容器 —— 显式允许收缩并裁剪 */
min-width: 0;
overflow: hidden;
padding: 14px 16px;
background: var(--surface);
border: 1px solid var(--border);
border-left: 3px solid var(--pending);
border-radius: 12px;
box-shadow: var(--shadow);
}
.card[data-status="up"] { border-left-color: var(--up); }
.card[data-status="down"] { border-left-color: var(--down); }
.card-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.card-head h3 { flex: 1; margin: 0; font-size: 14px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dot { width: 10px; height: 10px; flex: none; border-radius: 50%; background: var(--pending); }
/* 通用着色:卡片 / 表格行 / 极简列表 都以 data-status 标记状态 */
[data-status="up"] .dot { background: var(--up); }
[data-status="down"] .dot { background: var(--down); }
[data-status="pending"] .dot { background: var(--pending); }
.card[data-status="up"] .dot { animation: pulse-dot 2.4s ease-out infinite; }
@keyframes pulse-dot {
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--up) 35%, transparent); }
70% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--up) 0%, transparent); }
100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--up) 0%, transparent); }
}
.type-badge {
flex: none;
padding: 1px 7px;
border: 1px solid var(--border);
border-radius: 6px;
font-family: var(--mono);
font-size: 11px;
color: var(--text-2);
}
.card-target {
font-family: var(--mono);
font-size: 12px;
color: var(--text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.metrics { display: flex; gap: 8px; }
.metric { flex: 1; min-width: 0; }
.metric b { display: block; font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.metric label { font-size: 11px; color: var(--text-2); }
.heartbeat { height: 32px; }
.heartbeat svg { display: block; width: 100%; height: 100%; }
.hb-empty { display: flex; align-items: center; height: 100%; font-size: 12px; color: var(--text-2); }
/* 90 天可用率 */
.uptime { display: flex; align-items: center; gap: 8px; }
.uptime-bar { display: flex; gap: 1px; flex: 1; min-width: 0; height: 10px; }
.uptime-cell { flex: 1; min-width: 0; border-radius: 1px; background: var(--uptime-none); }
.uptime-cell.lv-ok { background: var(--uptime-ok); }
.uptime-cell.lv-warn { background: var(--uptime-warn); }
.uptime-cell.lv-bad { background: var(--uptime-bad); }
.uptime-label { font-size: 11px; color: var(--text-2); white-space: nowrap; font-variant-numeric: tabular-nums; }
.card-detail {
font-family: var(--mono);
font-size: 12px;
color: var(--text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card[data-status="down"] .card-detail { color: var(--down-text); }
.empty {
grid-column: 1 / -1;
padding: 60px 0;
text-align: center;
color: var(--text-2);
}
/* 过滤口径不一致时的提示条 */
.notice {
grid-column: 1 / -1;
margin-bottom: 10px;
padding: 10px 14px;
border: 1px solid color-mix(in srgb, var(--pending) 45%, var(--border));
border-radius: 10px;
background: var(--pending-bg);
color: var(--pending);
font-size: 12px;
line-height: 1.7;
}
.notice code { font-family: var(--mono); background: color-mix(in srgb, var(--pending) 14%, transparent); padding: 0 4px; border-radius: 4px; }
/* ---------- 底栏 ---------- */
.footer {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px 16px;
max-width: 1200px;
margin: 0 auto;
padding: 8px 20px 28px;
font-size: 12px;
color: var(--text-2);
}
.footer .version { margin-left: auto; }
.demo-chip { padding: 1px 8px; border-radius: 999px; background: var(--pending-bg); color: var(--pending-text); font-weight: 600; }
.warn-chip { padding: 1px 8px; border-radius: 999px; background: var(--down-bg); color: var(--down-text); 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) {
.grid { grid-template-columns: 1fr; padding: 12px 14px 8px; }
.group-tabs { padding: 12px 14px 0; }
.footer { padding: 8px 14px 24px; }
}
/* ================= 视图二:表格(高密度列表) ================= */
body[data-view="table"] .grid { display: block; }
.table-wrap {
overflow-x: auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--shadow);
}
.monitor-table {
width: 100%;
/* 列宽按百分比分配 + 固定表格算法:表格永远恰好铺满容器宽度,不会撑出横向滚动 */
table-layout: fixed;
border-collapse: collapse;
font-size: 13px;
}
/* 表格列宽:状态 / 服务 / 目标 / 延迟 / 结果 / 最近检测 / 心率 / 90天可用率 = 100%
地址与结果列给足比例,避免长 URL、长名称被截断 */
.monitor-table th:nth-child(1) { width: 7%; }
.monitor-table th:nth-child(2) { width: 15%; }
.monitor-table th:nth-child(3) { width: 24%; }
.monitor-table th:nth-child(4) { width: 7%; }
.monitor-table th:nth-child(5) { width: 9%; }
.monitor-table th:nth-child(6) { width: 10%; }
.monitor-table th:nth-child(7) { width: 15%; }
.monitor-table th:nth-child(8) { width: 13%; }
.monitor-table th {
padding: 10px 12px;
text-align: left;
font-size: 12px;
font-weight: 600;
color: var(--text-2);
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
background: var(--bg);
border-bottom: 1px solid var(--border);
}
.monitor-table td { padding: 9px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; overflow: hidden; line-height: 1.4; }
/* 所有行统一基线:可用率的 mini 条是 inline-flex,其基线会把行盒撑高十几像素,
导致带条的行比不带的行高出一截(实测 58px vs 43px) */
.monitor-table td > * { vertical-align: middle; }
.u-mini { display: inline-flex; align-items: center; gap: 6px; vertical-align: middle; }
.monitor-table tbody tr[data-status="pending"] { background: color-mix(in srgb, var(--pending) 6%, transparent); }
.monitor-table tbody tr:last-child td { border-bottom: 0; }
.monitor-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
.monitor-table tbody tr[data-status="down"] { background: color-mix(in srgb, var(--down) 6%, transparent); }
.group-row td { padding: 7px 12px; font-size: 12px; font-weight: 650; color: var(--text-2); background: var(--bg); }
.t-state { white-space: nowrap; }
.t-state .dot { display: inline-block; margin-right: 6px; vertical-align: middle; }
tr[data-status="up"] .t-state { color: var(--up-text); }
tr[data-status="down"] .t-state { color: var(--down-text); }
tr[data-status="pending"] .t-state { color: var(--pending-text); }
.t-name { white-space: normal; overflow-wrap: anywhere; }
.t-name .type-badge { margin-left: 8px; }
.t-target {
font-family: var(--mono);
font-size: 11.5px;
line-height: 1.45;
color: var(--text-2);
white-space: normal;
overflow-wrap: anywhere;
word-break: break-all;
}
.t-num { font-variant-numeric: tabular-nums; white-space: normal; overflow-wrap: anywhere; }
/* 证书 / 维护徽标:出现在名称旁,不占独立列 */
.tls-badge, .maint-badge {
margin-left: 8px;
padding: 0 6px;
border-radius: 999px;
font-size: 10px;
font-weight: 650;
vertical-align: middle;
white-space: nowrap;
cursor: help;
}
.tls-badge.warn { background: var(--pending-bg); color: var(--pending-text); }
.tls-badge.danger { background: var(--down-bg); color: var(--down-text); }
.maint-badge { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }
.card-head .tls-badge, .card-head .maint-badge { flex: none; }
.t-hb svg { display: block; width: 100%; max-width: 150px; height: 24px; }
.t-uptime { white-space: normal; }
/* 可用率条随视口伸缩;极窄时只留百分比,避免挤压其他列 */
.uptime-bar-mini { display: flex; flex: none; width: clamp(32px, 6vw, 84px); height: 8px; margin-right: 0; vertical-align: middle; }
.t-pct { font-size: 12px; color: var(--text-2); font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
.uptime-bar-mini { display: none; }
}
/* ---------- 最近事件:时间线风格(故障 / 恢复 / 证书提醒) ---------- */
.events {
max-width: 1200px;
margin: 18px auto 0;
padding: 0 20px;
}
.events-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.events-head h2 { margin: 0; font-size: 13px; font-weight: 650; color: var(--text); letter-spacing: 0.02em; }
.events-count { font-size: 11px; color: var(--text-2); }
.events-more {
margin-left: auto;
padding: 3px 10px;
font-size: 11px;
color: var(--accent);
background: color-mix(in srgb, var(--accent) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
border-radius: 999px;
cursor: pointer;
}
.events-more:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
/* 事件表:直接复用监控表格的外观(圆角、边框、行分隔、hover、状态底色),
只额外定义列宽与类型徽标,保证两处视觉完全一致 */
/* 事件表:显式声明与监控表格相同的观感(不共用 .monitor-table 类,
避免被其他按类名统计/定位的逻辑误算),列宽与类型徽标单独定义 */
.events-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 13px; }
.events-table th {
padding: 10px 12px;
text-align: left;
font-size: 12px;
font-weight: 600;
color: var(--text-2);
background: var(--bg);
border-bottom: 1px solid var(--border);
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.events-table td { padding: 11px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; line-height: 1.4; overflow: hidden; }
.events-table tbody tr:last-child td { border-bottom: 0; }
.events-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
.events-table tbody tr[data-status="down"] { background: color-mix(in srgb, var(--down) 6%, transparent); }
.events-table tbody tr[data-status="pending"] { background: color-mix(in srgb, var(--pending) 6%, transparent); }
.events-wrap { margin-top: 0; }
.events-table th:nth-child(1) { width: 11%; }
.events-table th:nth-child(2) { width: 9%; }
.events-table th:nth-child(3) { width: 26%; }
.events-table th:nth-child(4) { width: 54%; }
.ev-time { color: var(--text-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ev-badge {
display: inline-block;
padding: 1px 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 650;
}
tr[data-status="down"] .ev-badge { background: var(--down-bg); color: var(--down-text); }
tr[data-status="up"] .ev-badge { background: var(--up-bg); color: var(--up-text); }
tr[data-status="pending"] .ev-badge { background: var(--pending-bg); color: var(--pending-text); }
.ev-maint { margin-left: 6px; border-radius: 999px; padding: 0 6px; font-size: 10px; font-weight: 650; background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }
.ev-name { font-weight: 600; }
.ev-detail { color: var(--text-2); }
.events-empty { margin: 0; padding: 12px; font-size: 12px; color: var(--text-2); background: var(--surface); border: 1px dashed var(--border); border-radius: 12px; }
@media (max-width: 640px) {
.events { padding: 0 14px; }
.events-table th:nth-child(4), .ev-detail { display: none; }
}
/* ---------- 卡片:90 天日均延迟趋势 ---------- */
.lat-trend { display: flex; align-items: center; gap: 8px; margin-top: 2px; }
.lat-label { font-size: 11px; color: var(--text-2); flex: none; }
.lat-bars { display: flex; align-items: flex-end; gap: 1px; height: 20px; flex: 1; min-width: 0; }
.lat-cell { flex: 1; min-width: 2px; background: color-mix(in srgb, var(--accent) 50%, transparent); border-radius: 1px; }
.lat-cell:hover { background: var(--accent); }
.lat-avg { font-size: 11px; color: var(--text-2); font-variant-numeric: tabular-nums; flex: none; }