Files
subtrack/internal/web/templates/_layout.html
T
chun_qiu 8b9c347649
build / build-and-test (push) Successful in 3m38s
feat(v1.10.0): 重建 Bootstrap 主题层,修复迁移遗留的明暗/缓存/布局问题
背景:v1.9.0 把界面迁到 Bootstrap 5 时保留了旧自研 CSS,v1.9.2 又叠加了一层
「墨绿渐变」覆盖层,形成三套令牌(--paper/--ink、prefers-color-scheme、
data-bs-theme)并存。系统偏好与 Cookie 不一致时,页面会出现半亮半暗、表单
标签/抽屉标题/订阅页签不可见、输入框变黑块等问题。

本次改动:
- 样式加载顺序改为 bootstrap → app.css;删除旧纸墨令牌与 v1.9.2 覆盖层,
  app.css 重写为单一主题层(:root/[data-bs-theme=light] 与 [data-bs-theme=dark]
  两套语义令牌)。组件只读 --app-*,解决 --ink-2 同时被当作「次级文字色」和
  「卡片底色」的语义冲突。
- 主题只认 <html data-bs-theme>:服务端按 Cookie 直出;没有 Cookie 时由 <head>
  内联脚本在首屏按系统偏好补齐,修好「跟随系统」并避免闪白。大屏同理。
- 修复 Bootstrap 覆盖自研设计的问题:正文 14px + 中文字体栈、22/16/14px 标题、
  按钮/输入框/焦点环/卡片回到设计值;普通按钮新增 .btn-soft,不再变透明文字。
- 布局修复:客户/套餐表单的 .grid-2 不再塞 3 个字段;包详情页把筛选独立成
  GET 表单(在搜索框回车不再误触批量开通);客户详情操作区与 meta 重排;
  抽屉时钟改为固定 46px 并用它对齐抽屉头的 sticky 偏移;追加面板在抽屉里
  两列月历、不再 1100px 竖排;手机端日历数字不再换行溢出,移动端表格隐藏
  低优先级列,不再被裁切。
- 大屏:KPI/面板去掉 Bootstrap utility,统一由 screen.css 控制;TOP/今日待打
  改为固定列宽 + grid-column 对齐;浅色主题的链接、热力、图例对比度补齐;
  热力格不再整体降透明度;当日浮层可滚动并支持点击/键盘打开。
- 配色与可读性:普通文本对比度全部达标(截图 lint 由 65 处降到 0);日历
  外月日期改为浅底深字,不再整格 opacity;archived 行改为中性底色而非降透明。
- 缓存:新增内嵌静态资源内容指纹 AssetVersion(12 位 sha256),所有 /static
  链接带 ?v=;带指纹的响应 public, max-age=31536000, immutable,不带指纹的
  回源 no-cache;HTML 继续 no-store。改样式重新编译即自动破缓存,不再依赖
  手改版本号(仍保留 v1.10.0 作为产品版本)。
- 认证页重做:登录/初始化页改用 .auth 卡片(品牌、主题切换、绿色焦点环),
  去掉 min-height:100vh 叠加 main padding 造成的 40px 纵向滚动。
- JS:修 toISOString 计算自然日差一天的时区 bug;主题切换读 data-bs-theme
  并同步 aria-pressed;全选支持半选;大屏切主题同时更新 data-bs-theme;
  提示条改为固定定位、淡出不再顶动页面。
- README 更新主题与缓存说明;版本号 1.9.5 → 1.10.0。

验证:gofmt / go vet / go test 全绿;1440、1100、390 三档,明暗两套,工作台/
客户/套餐/订阅/设置/日志/大屏/登录全部截图走查,无横向溢出与控制台错误。
2026-10-20 01:46:11 +08:00

71 lines
4.4 KiB
HTML

{{define "layout"}}<!doctype html>
<html lang="zh-CN"{{if .Theme}} data-bs-theme="{{.Theme}}"{{end}}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>{{.Title}} · 代打台</title>
<link rel="icon" href="/static/favicon.svg?v={{.AssetVersion}}" type="image/svg+xml">
<script>
/* 首屏主题:服务端没有写 data-bs-theme 时(还没选过主题)跟随系统,避免闪白 */
(function () {
var d = document.documentElement;
if (d.hasAttribute('data-bs-theme')) return;
var dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
d.setAttribute('data-bs-theme', dark ? 'dark' : 'light');
})();
</script>
<link rel="stylesheet" href="/static/vendor/bootstrap.min.css?v={{.AssetVersion}}">
<link rel="stylesheet" href="/static/app.css?v={{.AssetVersion}}">
</head>
<body>
{{if .User}}
<nav class="navbar navbar-expand-xl app-navbar sticky-top">
<div class="container-fluid">
<a class="navbar-brand" href="/calendar"><span class="brand-mark" aria-hidden="true">代</span><span>代打台</span></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="展开导航"><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav me-auto">
<li class="nav-item"><a class="nav-link{{if eq .Nav "calendar"}} active{{end}}" href="/calendar">日历</a></li>
<li class="nav-item"><a class="nav-link{{if eq .Nav "customers"}} active{{end}}" href="/customers">客户</a></li>
<li class="nav-item"><a class="nav-link{{if eq .Nav "subscriptions"}} active{{end}}" href="/subscriptions">订阅</a></li>
<li class="nav-item"><a class="nav-link{{if eq .Nav "packages"}} active{{end}}" href="/packages">套餐</a></li>
<li class="nav-item"><a class="nav-link nav-screen" href="/screen" target="_blank" rel="noopener">大屏 ↗</a></li>
</ul>
<form class="nav-search" role="search" action="/customers" method="get">
<input class="form-control form-control-sm" type="search" name="q" value="{{.Query}}" placeholder="手机尾号 / 姓名 / 游戏名" aria-label="搜索客户" data-search>
<kbd aria-hidden="true">/</kbd>
</form>
<div class="nav-side">
<a class="badge-due" href="/subscriptions?state=due_today" title="今天到期">今日到期 <b>{{.DueToday}}</b></a>
<a class="badge-warn" href="/subscriptions?state=due3" title="3 天内到期">3 天内 <b>{{.Due3}}</b></a>
<a class="badge-muted" href="/subscriptions?state=due7" title="7 天内到期">7 天内 <b>{{.Due7}}</b></a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="切换亮色/暗色" aria-pressed="false"><span class="ic-moon" aria-hidden="true">☾</span><span class="ic-sun" aria-hidden="true">☀</span></button>
<div class="dropdown">
<button class="btn btn-soft btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">{{if .User.DisplayName}}{{.User.DisplayName}}{{else}}{{.User.Username}}{{end}}</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/settings">设置</a></li>
<li><a class="dropdown-item" href="/logs">操作日志</a></li>
<li><hr class="dropdown-divider"></li>
<li><form method="post" action="/logout"><button class="dropdown-item text-danger" type="submit">退出登录</button></form></li>
</ul>
</div>
</div>
</div>
</div>
</nav>
{{end}}
{{if .Flash}}<div class="flash" role="status" data-flash>{{.Flash}}</div>{{end}}
{{if .Error}}<div class="notice notice-danger" role="alert">{{.Error}}</div>{{end}}
<main class="{{if .User}}app-main{{else}}auth-main{{end}}">{{template "content" .}}</main>
{{if .User}}<footer class="app-foot">
<span>代打台 <span class="foot-version">v{{.Version}}</span></span>
<a href="/logs">操作日志</a>
<a href="/settings">设置</a>
<a href="/screen" target="_blank" rel="noopener">大屏</a>
</footer>{{end}}
<script src="/static/vendor/bootstrap.bundle.min.js?v={{.AssetVersion}}" defer></script>
<script src="/static/app.js?v={{.AssetVersion}}" defer></script>
</body>
</html>{{end}}