style(admin): polish dashboard layout and visuals

This commit is contained in:
caoqianming 2026-08-28 13:40:02 +08:00
parent b6d0e34b51
commit a7c3ee8b16
2 changed files with 120 additions and 52 deletions

View File

@ -6,59 +6,94 @@
<title>zcbot 管理后台</title>
<style>
:root {
--bg: #f7f7f7; --panel: #ffffff; --border: #e3e3e3; --border-soft: #ececec;
--text: #222; --muted: #888; --accent: #c0392b; --accent-soft: #fde9e7;
--ok: #2e7d32; --warn: #c87f0a; --danger: #c0392b;
--r-md: 4px; --r-lg: 8px;
color-scheme: light;
--bg: #f4f6f8; --panel: #ffffff; --panel-soft: #f8fafb;
--border: #dfe3e7; --border-soft: #edf0f2;
--text: #202428; --text-strong: #121619; --muted: #70777f;
--accent: #b83a31; --accent-hover: #a9322a; --accent-soft: #fbecea;
--accent-ring: rgba(184,58,49,.16);
--ok: #238653; --warn: #b66a16; --danger: #b83a31;
--r-md: 6px; --r-lg: 12px;
--shadow-soft: 0 1px 2px rgba(18,24,30,.04), 0 8px 24px rgba(18,24,30,.035);
--mono: ui-monospace, "Cascadia Code", "SF Mono", Consolas, monospace;
}
* { box-sizing: border-box; }
body {
margin: 0; font-family: system-ui, -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
color: var(--text); background: var(--bg); font-size: 13px; line-height: 1.5;
color: var(--text); background: var(--bg); font-size: 13px; line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
header {
position: sticky; top: 0; z-index: 10; background: #fff;
border-bottom: 1px solid var(--border); box-shadow: 0 1px 2px rgba(0,0,0,.03);
display: flex; align-items: center; gap: 12px; padding: 10px 16px; flex-wrap: wrap;
position: sticky; top: 0; z-index: 10; min-height: 64px;
background: rgba(255,255,255,.94); backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border); box-shadow: 0 1px 0 rgba(18,24,30,.02);
display: flex; align-items: center; gap: 16px; padding: 10px max(20px, calc((100vw - 1480px) / 2));
}
header .brand { display: flex; align-items: center; gap: 11px; min-width: 0; }
header .logo {
width: 24px; height: 24px; border-radius: var(--r-md);
width: 34px; height: 34px; flex: 0 0 auto; border-radius: 9px;
background: linear-gradient(135deg, var(--accent), #8e2a20);
color: #fff; font-weight: 700; font-size: 13px;
box-shadow: 0 5px 14px rgba(184,58,49,.22);
color: #fff; font-weight: 750; font-size: 15px;
display: flex; align-items: center; justify-content: center;
}
header .title { font-weight: 600; font-size: 15px; }
header .brand-copy { min-width: 0; }
header .title { color: var(--text-strong); font-weight: 650; font-size: 15px; line-height: 1.25; }
header .eyebrow { margin-top: 1px; color: var(--muted); font-size: 10px; letter-spacing: .7px; text-transform: uppercase; }
header .spacer { flex: 1; }
header .meta { color: var(--muted); font-size: 12px; font-family: var(--mono); }
header .actions { display: flex; align-items: center; gap: 7px; }
header a, header button {
font-size: 12px; color: var(--accent); text-decoration: none;
padding: 4px 10px; border: 1px solid var(--accent-soft); border-radius: var(--r-md);
background: #fff; cursor: pointer;
min-height: 32px; display: inline-flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 550; color: var(--text); text-decoration: none;
padding: 5px 11px; border: 1px solid var(--border); border-radius: var(--r-md);
background: #fff; cursor: pointer; transition: .15s ease;
}
header a:hover, header button:hover { background: var(--accent-soft); }
header label.auto { color: var(--muted); display: flex; align-items: center; gap: 4px; cursor: pointer; }
header a:hover, header button:hover { border-color: #c8cdd2; background: var(--panel-soft); color: var(--accent); }
header a { color: var(--accent); border-color: #edcfcc; }
header label.auto { color: var(--muted); display: flex; align-items: center; gap: 6px; padding: 0 4px; cursor: pointer; white-space: nowrap; }
input[type="checkbox"] { accent-color: var(--accent); }
main { padding: 16px; max-width: 1200px; margin: 0 auto; }
main { padding: 28px 24px 48px; max-width: 1480px; margin: 0 auto; }
.msg { padding: 40px 16px; text-align: center; color: var(--muted); }
.msg a { color: var(--accent); }
/* 目录 + 内容两栏;目录 sticky 跟随滚动 */
#layout { display: grid; grid-template-columns: 132px 1fr; gap: 18px; align-items: start; }
#toc { position: sticky; top: 64px; display: flex; flex-direction: column; gap: 2px; }
#toc a {
color: var(--muted); text-decoration: none; font-size: 13px; padding: 6px 10px;
border-radius: var(--r-md); border-left: 2px solid transparent;
#layout { display: grid; grid-template-columns: 196px minmax(0, 1fr); gap: 24px; align-items: start; }
#toc {
position: sticky; top: 88px; display: flex; flex-direction: column; gap: 3px;
padding: 10px; background: rgba(255,255,255,.76); border: 1px solid var(--border);
border-radius: var(--r-lg); box-shadow: var(--shadow-soft);
}
#toc a:hover { background: var(--accent-soft); color: var(--accent); }
#toc a.active { color: var(--accent); border-left-color: var(--accent); background: var(--accent-soft); font-weight: 600; }
.anchor { scroll-margin-top: 64px; } /* 滚动定位避开 sticky 顶栏 */
#toc .toc-label { padding: 5px 10px 8px; color: #959ba1; font-size: 10px; font-weight: 700; letter-spacing: 1px; }
#toc a {
display: flex; align-items: center; gap: 9px; min-height: 36px;
color: var(--muted); text-decoration: none; font-size: 12px; padding: 7px 10px;
border-radius: 7px; border: 1px solid transparent; transition: .15s ease;
}
#toc .nav-index { width: 20px; color: #a0a6ac; font: 10px/1 var(--mono); }
#toc a:hover { background: var(--panel-soft); color: var(--text-strong); }
#toc a.active { color: var(--accent); border-color: #f0d5d2; background: var(--accent-soft); font-weight: 650; }
#toc a.active .nav-index { color: var(--accent); }
#content { min-width: 0; }
.page-intro {
position: relative; overflow: hidden; margin-bottom: 18px; padding: 22px 24px;
border: 1px solid #e6d9d7; border-radius: var(--r-lg);
background: linear-gradient(120deg, #fff 0%, #fff 54%, #fdf4f2 100%); box-shadow: var(--shadow-soft);
}
.page-intro::after {
content: ""; position: absolute; right: -70px; top: -90px; width: 220px; height: 220px;
border: 42px solid rgba(184,58,49,.055); border-radius: 50%; pointer-events: none;
}
.page-intro h1 { position: relative; margin: 0; color: var(--text-strong); font-size: 20px; line-height: 1.35; }
.page-intro p { position: relative; margin: 6px 0 0; color: var(--muted); font-size: 12px; }
.anchor { scroll-margin-top: 82px; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.card-head h2 { margin: 0; }
.ctrl { display: flex; gap: 6px; }
.ctrl select {
font-size: 12px; padding: 3px 6px; border: 1px solid var(--border);
min-height: 32px; font-size: 12px; padding: 4px 28px 4px 9px; border: 1px solid var(--border);
border-radius: var(--r-md); background: #fff; color: var(--text);
}
.sublabel { color: var(--muted); font-size: 11px; margin-bottom: 4px; }
@ -66,18 +101,21 @@
.grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.card {
background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
padding: 14px 16px; margin-bottom: 14px;
padding: 18px 20px; margin-bottom: 16px; box-shadow: var(--shadow-soft);
}
.card h2 { margin: 0 0 10px; font-size: 13px; color: var(--muted); font-weight: 600; letter-spacing: .3px; }
.card h2 { margin: 0 0 13px; font-size: 13px; color: var(--text-strong); font-weight: 650; letter-spacing: .15px; }
/* 大数字 stat 块 */
.stat { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 12px 14px; }
.stat { position: relative; overflow: hidden; min-height: 102px; background: linear-gradient(145deg,#fff,var(--panel-soft)); border: 1px solid var(--border-soft); border-radius: 10px; padding: 14px 16px; }
.stat::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: #d9dde1; }
.stat .k { color: var(--muted); font-size: 12px; }
.stat .v { font-size: 22px; font-weight: 600; font-family: var(--mono); margin-top: 4px; }
.stat .v { color: var(--text-strong); font-size: 23px; font-weight: 650; font-family: var(--mono); margin-top: 7px; letter-spacing: -.5px; }
.stat .sub { color: var(--muted); font-size: 11px; margin-top: 2px; }
.stat.warn { border-color: var(--warn); background: #fff8ec; }
.stat.warn { border-color: #efd8b7; background: #fffaf2; }
.stat.warn::before { background: var(--warn); }
.stat.warn .v { color: var(--warn); }
.stat.danger { border-color: var(--danger); background: var(--accent-soft); }
.stat.danger { border-color: #edc1bd; background: var(--accent-soft); }
.stat.danger::before { background: var(--danger); }
.stat.danger .v { color: var(--danger); }
/* chips(状态分布) */
@ -91,10 +129,14 @@
.chip.run { background: #e7f3ff; color: #1565c0; }
.chip.ok { background: #e8f5e9; color: var(--ok); }
table { width: 100%; border-collapse: collapse; font-size: 12px; }
th, td { padding: 6px 8px; text-align: right; border-bottom: 1px solid var(--border-soft); white-space: nowrap; }
table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; }
th, td { padding: 9px 10px; text-align: right; border-bottom: 1px solid var(--border-soft); white-space: nowrap; }
th:first-child, td:first-child { text-align: left; }
th { color: var(--muted); font-weight: 600; }
th { color: var(--muted); background: var(--panel-soft); font-weight: 600; }
thead th:first-child { border-radius: 7px 0 0 7px; }
thead th:last-child { border-radius: 0 7px 7px 0; }
tbody tr { transition: background-color .12s ease; }
tbody tr:hover { background: #fafbfc; }
td.num { font-family: var(--mono); }
td.num.hot { color: var(--danger); font-weight: 700; }
td.trend { font-family: var(--mono); letter-spacing: 1px; color: var(--accent); }
@ -102,7 +144,7 @@
td.email { font-family: var(--mono); max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
.bar-cell { position: relative; }
tfoot .total-row td { border-top: 2px solid var(--border); border-bottom: none; font-weight: 700; }
.scroll-x { overflow-x: auto; }
.scroll-x { overflow-x: auto; scrollbar-color: #cbd1d6 transparent; scrollbar-width: thin; }
.empty { color: var(--muted); padding: 8px; text-align: center; }
#ext-admin-form label { display: grid; gap: 3px; color: var(--muted); font-size: 12px; }
#ext-admin-form [hidden] { display: none; }
@ -236,22 +278,31 @@
.pager { display: flex; align-items: center; gap: 12px; justify-content: flex-end; margin-top: 10px; }
.pager button {
font-size: 12px; padding: 4px 12px; border: 1px solid var(--border); border-radius: var(--r-md);
min-height: 32px; font-size: 12px; padding: 4px 12px; border: 1px solid var(--border); border-radius: var(--r-md);
background: #fff; cursor: pointer; color: var(--text);
}
.pager button:hover:not(:disabled) { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.pager button:disabled { opacity: .45; cursor: default; }
.pager .pginfo { color: var(--muted); font-size: 12px; font-family: var(--mono); }
@media (max-width: 640px) {
button, select, input, textarea { font: inherit; }
button:focus-visible, a:focus-visible, select:focus-visible, input:focus-visible, textarea:focus-visible {
outline: 2px solid var(--accent); outline-offset: 2px;
}
select:focus, input:focus, textarea:focus { border-color: var(--accent) !important; box-shadow: 0 0 0 3px var(--accent-ring); outline: none; }
@media (max-width: 820px) {
main { padding: 10px; }
.grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.stat .v { font-size: 18px; }
/* header 紧凑化:缩 padding/gap、字号,gen-at 时间戳截断不撑宽 */
header { padding: 8px 10px; gap: 8px; }
header { padding: 8px 10px; gap: 8px; flex-wrap: wrap; }
header .title { font-size: 14px; }
header .eyebrow { display: none; }
header .meta { font-size: 11px; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
header a, header button { padding: 4px 8px; }
header .actions { order: 3; width: 100%; overflow-x: auto; padding-bottom: 1px; }
header .actions > * { flex: 0 0 auto; }
/* card-head 允许换行,标题长 + 下拉不再撑出横向溢出 */
.card-head { flex-wrap: wrap; }
.ctrl { flex-wrap: wrap; }
@ -262,12 +313,21 @@
/* 窄屏:目录变顶部横向 chip 条 */
#layout { grid-template-columns: 1fr; gap: 10px; }
#toc {
flex-direction: row; overflow-x: auto; gap: 6px; top: 52px;
background: var(--bg); padding: 6px 0; z-index: 5;
flex-direction: row; overflow-x: auto; gap: 6px; top: 109px;
background: rgba(244,246,248,.94); backdrop-filter: blur(8px); padding: 6px 0; z-index: 5;
border: 0; border-radius: 0; box-shadow: none;
}
#toc a { white-space: nowrap; border-left: none; border: 1px solid var(--border); }
#toc .toc-label, #toc .nav-index { display: none; }
#toc a { min-height: 32px; white-space: nowrap; border: 1px solid var(--border); background: #fff; }
#toc a.active { border-color: var(--accent); }
.anchor { scroll-margin-top: 96px; }
.page-intro { padding: 18px; }
.page-intro h1 { font-size: 18px; }
.anchor { scroll-margin-top: 150px; }
.card { padding: 15px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }
}
/* 屏幕上隐藏打印报告;打印时只显它 */
@ -290,14 +350,21 @@
</head>
<body>
<header>
<div class="brand">
<div class="logo">Z</div>
<div class="brand-copy">
<div class="title">zcbot 管理后台</div>
<div class="eyebrow">Operations Console</div>
</div>
</div>
<span class="meta" id="gen-at"></span>
<div class="spacer"></div>
<div class="actions">
<label class="auto"><input type="checkbox" id="auto-refresh" checked /> 自动刷新</label>
<button id="refresh">刷新</button>
<button id="refresh">刷新数据</button>
<button id="export">导出 PDF</button>
<a href="/static/dev.html">← 返回控制台</a>
<a href="/static/dev.html">返回控制台</a>
</div>
</header>
<main id="main">
<div class="msg" id="boot">加载中…</div>

View File

@ -899,9 +899,10 @@ function wirePager(prefix, page, maxPage, go) {
function ensureSkeleton() {
if ($("layout")) return;
$("main").innerHTML = `<div id="layout">`
+ `<nav id="toc">` + SECTIONS.map(([id, label]) =>
`<a href="#${id}" data-target="${id}">${label}</a>`).join("") + `</nav>`
+ `<div id="content">` + SECTIONS.map(([id]) =>
+ `<nav id="toc" aria-label="管理后台导航"><div class="toc-label">管理模块</div>` + SECTIONS.map(([id, label], index) =>
`<a href="#${id}" data-target="${id}"><span class="nav-index">${String(index + 1).padStart(2, "0")}</span><span>${label}</span></a>`).join("") + `</nav>`
+ `<div id="content"><section class="page-intro"><h1>系统运行概览</h1>`
+ `<p>集中查看服务状态、资源用量与外部能力,数据默认每 10 秒自动更新。</p></section>` + SECTIONS.map(([id]) =>
`<div id="${id}" class="anchor"></div>`).join("") + `</div>`
+ `</div>`;
document.querySelectorAll("#toc a").forEach(a => {