v2.4: UI 规范化(token体系) + 复制直播链接 + 设置页间距

- 建立完整 token 体系: 色板/字号阶梯(9档)/圆角阶梯/阴影/动效/间距
- 字号 42→9 种、圆角 20→8 种、正文硬编码色清零
- 补丁区(v2/v2.1/v2.2 追加段)并入主体系: 统一缩进 + 全用 token
- header「打开直播流」改为「复制直播流链接」(navigator.clipboard + 降级)
- header 图标按钮统一为胶囊形(20px 圆角)
- 设置页: .page.active 加 gap, section-body 内边距对齐, 缓解拥挤

测试: api 16/16, e2e 17/17, write 5/5 全绿
This commit is contained in:
Xiao-no-love committed 2026-09-24 22:20:55 +08:00
1 parent 5d5d5cce19
commit 16df0a7d08
4 files changed
+593 -509

No files matched your search

+48
View File
@@ -367,3 +367,51 @@ python test_enrich.py # 给 5 首补封面(已跑过,5/5 成功)
### 16.6 未做 / 可选后续
- 是否 `git commit`(工作区改动含本次 + 上轮 v2.2,均未提交)。
- 深色模式、缓存条颜色再打磨、其余「AI 味」细节。
---
## 17. v2.4 UI 规范化 / Token 体系(已完成 2026-09-24)
> 老板反馈「整体 UI 风格很撕裂」→ 全量重构 `style.css` 的 token 与取值体系。
### 17.1 问题诊断
`style.css` 原是两段拼接:**主体系**(1~1730,16 空格缩进、有分节) + **补丁区**(1731+,不缩进、硬编码色的 v2/v2.1/v2.2 追加)。具体失控:
- 字号 **42 种**(0.54~2.4rem 密密麻麻,还混用 `0.82rem`/`.82rem` 写法)
- 圆角 **约 20 种**(3/5/6/7/8/9/10/11/12/14/16/18/20/22/28px,定义了 `--radius-*` 却大半没用)
- 颜色硬编码:55 hex + 38 rgba,兜底色 `#1f6feb` 出现 8 次(带 `var(--accent,#1f6feb)` fallback)
### 17.2 重构内容
**建立完整 token 体系**(`:root` 重写):
- 色板:`--bg/surface/white/accent*/text*/border*/danger*/success*/warning*/overlay/log-*`
- 字号阶梯 9 档:`--fs-2xs/…/--fs-4xl`
- 圆角阶梯:`--radius-xs/sm/base/lg/xl/pill`
- 阴影:`--shadow-xs/sm/base/lg/accent`
- 动效:`--dur-fast/base/slow` + `--ease-out/spring`
- 间距:`--sp-1`~`--sp-6`
**全局收敛**:
- `font-size`:42 种 → **9 种**(67 处替换为 var)
- `border-radius`:20 种 → **8 种**(28 处替换为 var,余 50%/xl 特殊)
- 硬编码色:正文区 **清零**(全转 var)
- accent 阴影重复值收进 `--shadow-accent`;`#fff`→`--white`;`#fdd`→`--danger-hover`
- **补丁区整体并入主体系**:统一 12 空格缩进、改用 `--fs-*`/`--radius-*`/`--sp-*`、重写分节注释
### 17.3 关键取值变化(有意微调,贴近原值)
- `--radius-sm`: 12px→10px,`--radius`: 16px→14px,`--radius-lg`: 24px→20px,`--radius-xl`: 28px 保留
- 补丁区硬编码圆角 14/18/22px → 归入 `--radius/--radius-lg`
- live-cover-ph 大图标 2.4rem → `--fs-4xl`
### 17.4 验证
- 全文括号配平({} 360/360、( ) 581/581),无 var 残渣/嵌套。
- 回归:api 16/16、e2e 17/17、write 5/5 全绿。
- `node --check app.js` 通过。
### 17.5 待老板浏览器实测
- 整体观感是否统一(字号阶梯、圆角、配色)。
- 特别注意:卡片圆角、按钮胶囊、模态框圆角的和谐度。
### 17.6 保留项(非撕裂)
- 毛玻璃 `rgba(255,255,255,…)` 背景、阴影 rgba:正当设计值。
- `login.html` 独立暗色主题(自己的 `:root`)。
- `body` 字体栈、`--log-bg` 终端风格:保留。
+525 -503
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -39,8 +39,8 @@
<div class="header-actions">
<span class="cache-pill" id="cacheInfo"><i class="bi bi-database"></i> --</span>
<button class="channel-btn" id="btnStreamMgr" title="频道与切换"><i class="bi bi-broadcast"></i><span id="currentChannelName">主电台</span></button>
<a href="#" id="liveLink" class="btn-link-sm" target="_blank" title="打开直播流"
><i class="bi bi-link-45deg"></i></a
<button class="btn-link-sm" id="liveLink" title="复制直播流链接"
><i class="bi bi-link-45deg"></i></button
>
<button class="btn-link-sm" id="btnLogout" title="退出登录"
><i class="bi bi-box-arrow-right"></i></button>
+18 -4
View File
@@ -5,6 +5,7 @@
// ==================== 全局状态 ====================
let currentStreamId = "main";
let liveStreamUrl = "/hls/main/index.m3u8";
const state = { streams: [], resources: [], playlist: [] };
// ==================== Toast ====================
@@ -101,13 +102,26 @@ async function loadStreams() {
const cur = state.streams.find((s) => s.id === currentStreamId);
const nameEl = document.getElementById("currentChannelName");
if (nameEl) nameEl.textContent = cur ? (cur.name || cur.id) : "主电台";
document.getElementById("liveLink").href = cur ? cur.m3u8 : "/hls/main/index.m3u8";
liveStreamUrl = cur ? cur.m3u8 : "/hls/main/index.m3u8";
updateLiveUI();
} catch (e) { console.error("加载流失败", e); }
}
document.getElementById("liveLink").addEventListener("click", (e) => {
e.preventDefault();
window.open(e.currentTarget.href, "_blank");
document.getElementById("liveLink").addEventListener("click", async () => {
const url = new URL(liveStreamUrl, location.origin).href;
try {
await navigator.clipboard.writeText(url);
showToast("已复制直播流链接", "success");
} catch (err) {
const ta = document.createElement("textarea");
ta.value = url;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); showToast("已复制直播流链接", "success"); }
catch (e2) { showToast("复制失败,请手动复制: " + url, "error"); }
ta.remove();
}
});
document.getElementById("btnLogout").addEventListener("click", async () => {
await post("/api/auth/logout").catch(() => {});