前端改造:设计令牌 + 卡片层级 + 图表可读性 + 类型色收口 + 窄屏导航
先出方案(frontend-plan-2026-10-09.md)再动手,四批按顺序落地。方法上先存基线、再用同一份 探针在改动前后逐值 diff —— 这批里有 6 处问题只有「量」才看得见,看截图全是正常的。 A 设计令牌(app.css 为主) - 字阶收敛到 6 档 --fs-xs/sm/md/lg/xl/2xl = 11/13/15/21/26/34px。原来 app.css 有 19 种字号、 模板里另有 54 处内联字号,光 12~15px 之间就挤了 4 档 —— 层级挤不出来,只能靠加粗硬顶。 - 两个「字外档」不是漏的:--fs-control: 16px(窄屏表单控件必须 ≥16px,否则 iOS 聚焦放大整页, 属功能约束);--glyph-*(✕/箭头/空态图标按控件尺寸定,硬塞进字阶只会把按钮撑坏)。 - 字重收敛到 400/500/600(删掉 650 与 700)。 - 圆角 4 档 --r-*、阴影 3 档 --sh-1/2/3(暗色整套覆盖)、间距 --sp-1..5、 卡片内边距单列 --card-pad(与折叠卡片标题行的负 margin 绑在一起,改一个即改两处)。 - 门禁 TestNoBareFontSizePx:静态扫 app.css 与全部模板,出现 font-size: <数字>px 即红。 B 卡片分主次 - .card--hero / .card(默认)/ .card--quiet 三档。quiet 走「页面底色 + 细边 + 无阴影」—— 第一版拿 --bg-2 当底色,实测它与页面底 #f6f7f8 只差 4/255,看不出边界、像破了个洞。 - 首屏 KPI 从「4×2 等重白盒」改成 .kpi-lead(主数 + 一行上下文)+ 5 个常规指标,8 个数字一个没少。 C 图表说人话 - 月度分布:柱底加基线;峰值柱不透明拉满、柱顶标次数(.pv),副标题里不再重复写「最高 N 次」。 - 一天中的分布:把「0/6/12/18 四个数字」换成 6 小时一段的标尺(深夜/上午/下午/晚上), 用同一套 24 列网格,段边界与柱子严格对齐。 - 首页月度图同样加峰值锚点(给 HomeData 补 MaxMonthly,与统计页同口径 maxOf)。 - 空态没动:查下来已经统一(8 页共用 emptystate、局部走 .empty)。 D1 类型色语义化(结论与计划不同,按实测改了) - 先算对比度:白底上 yellow 1.87 / mint 2.12 / orange 2.20 / green 2.22 / cyan 2.54 / teal 2.57, 6 个色连图形对象 3:1 都到不了;暗底只有 indigo 2.90 不够。 - 所以**没有**把类型色用到 chips / 表格类型列 / KPI 强调 —— 那几处要的是文字色,只会更难读。 这也解释了全站为什么一直只用 8px 小圆点做身份标记。浅色那 6 个色一个没动: 它们是手机端与地图上的同一套身份色,改暗会破坏对应关系。 - 实际做的:色值收口成 --cat-* / --cat-*-soft(26 个变量);模板 colorof → csscolor / csssoft(19 处); 暗色只提亮 indigo(2.90 → 3.55:1);colorOf 成了死代码,删掉。 - ⚠️ 返回值必须是 template.CSS:html/template 的 CSS 过滤器不认识 var(),普通 string 会被静默 换成 ZgotmplZ —— 页面上所有圆点当场变透明,而且不报错、模板测试照过。挂了它等于关掉那道过滤, 所以数据侧色值先过严格校验(只有 # + 6 位 hex 放行),否则 #"><img… 能逃出 style 属性。 守卫 TestCSSColorSurvivesTemplateSanitizer 把两个方向都钉住(做过变异验证)。 D2 窄屏导航 - 改前 ≤620px 靠横向滚动兜底:390 溢 63px、320 溢 133px,「统计」被切成半个字紧贴后面的按钮, 渐隐提示实际看不出来。现在链接本体抽成 {{define "navitems"}} 渲染两遍:桌面 .nav-links 平铺、 窄屏 .nav-menu(原生 <details>,不靠 JS 也能开合);JS 只补点外面 / Esc / 点链接后收起。 - ⚠️ 顶栏 --z-nav 抬到 1200:菜单是 .nav 的子元素、自己抬不动层叠上下文,不抬就会被地图页的 --z-map-overlay(1100) 盖住。已在 /map 上用 elementFromPoint 实测。 - 守卫 TestNarrowNavCollapsesToMenu 钉「CSS 两半 + 模板两个类 + 渲染两遍」的成对关系。 途中发现并一并修掉的(都只有量 DOM 才看得见) - Leaflet 的 .leaflet-container 字号是 0.75rem、跟着根字号漂:根 14 → 13px 后地图署名/比例尺 掉到 9.8px(低于本站 11px 下限)、弹窗正文也跟着缩。已按自己的字阶钉死。 - h1..h4 原来只设了字重与 margin、没设字号,靠浏览器默认的 2em/1.5em/1.17em —— 同样跟着根字号漂: /stats 那两个 h2 从 21px 变成 19.5px,比同页卡片标题大一圈。已显式给出字号。 - <b>/<strong> 浏览器默认 700,比全站最高层级(标题 600)还重,已钉成 600。 回归 - ./scripts/verify.sh:PASS=348 FAIL=0 SKIP=0 - internal/fog 加 -race:PASS=22 FAIL=0 SKIP=0 - JS 单测 34/34 - 移动端 320 / 390 溢出全 0(改前 /admin 在 320 还溢 34px);桌面 1440 与改前一致(−15 是滚动条宽) - 全站 7 个页面都没有低于 11px 的文字 说明:A/B 与 C/D 本想拆两笔提交,但 app.css / layout.html / view.go 里两批改动交织,按文件拆不开、 按 hunk 拆又容易做出「中间那笔编译不过」的历史,所以合成一笔、在消息里分段。
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
{{define "nav"}}
|
||||
<nav class="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="brand" href="/" title="{{.Settings.SiteTitle}}"><span class="avatar">{{.Settings.OwnerAvatar}}</span><span class="t">{{.Settings.SiteTitle}}</span></a>
|
||||
<div class="nav-links">
|
||||
{{/* 页面开关只管访客:站长登录后始终能访问全部页面(guardPage 也是这么放的),
|
||||
导航必须跟着一起放行,否则关掉页面开关后站长自己也只剩「主页」一个入口,
|
||||
明明能访问却点不到。 */}}
|
||||
{{/* navitems 是导航链接本体,抽出来是因为它要渲染两遍:桌面上在 .nav-links 里平铺,
|
||||
窄屏整行一定放不下(实测 390 溢 63px、320 溢 133px,最深处的「统计」会被切成半个字),
|
||||
所以另收进 .nav-menu 那个折叠菜单。两份靠 display 二选一,DOM 里不会同时可见。
|
||||
|
||||
页面开关只管访客:站长登录后始终能访问全部页面(guardPage 也是这么放的),
|
||||
导航必须跟着一起放行,否则关掉页面开关后站长自己也只剩「主页」一个入口。 */}}
|
||||
{{define "navitems"}}
|
||||
<a href="/" class="{{if eq .Nav "home"}}on{{end}}">主页</a>
|
||||
{{if or .IsAdmin (.Settings.HasPage "map")}}<a href="/map" class="{{if eq .Nav "map"}}on{{end}}">地图</a>{{end}}
|
||||
{{if or .IsAdmin (.Settings.HasPage "places")}}<a href="/places" class="{{if eq .Nav "places"}}on{{end}}">地点</a>{{end}}
|
||||
@@ -13,7 +12,17 @@
|
||||
{{if or .IsAdmin (.Settings.HasPage "stats")}}<a href="/stats" class="{{if eq .Nav "stats"}}on{{end}}">统计</a>{{end}}
|
||||
{{if .HasTopics}}<a href="/topics" class="{{if eq .Nav "topics"}}on{{end}}">专题</a>{{end}}
|
||||
{{if or .IsAdmin (.Settings.HasPage "report")}}<a href="/report" class="{{if eq .Nav "report"}}on{{end}}">报告</a>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "nav"}}
|
||||
<nav class="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="brand" href="/" title="{{.Settings.SiteTitle}}"><span class="avatar">{{.Settings.OwnerAvatar}}</span><span class="t">{{.Settings.SiteTitle}}</span></a>
|
||||
<div class="nav-links">{{template "navitems" .}}</div>
|
||||
<details class="nav-menu">
|
||||
<summary aria-label="导航菜单">菜单</summary>
|
||||
<nav class="nav-menu-body">{{template "navitems" .}}</nav>
|
||||
</details>
|
||||
<div class="nav-right">
|
||||
{{if .User}}
|
||||
<span class="who">{{.User.DisplayName}}</span>
|
||||
@@ -26,17 +35,26 @@
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
/* 手机上导航装不下这几项,可以横向滑动(滚动条已隐藏)——右侧加一层渐隐,提示「还能往右滑」;
|
||||
滑到末端就收掉,免得让人以为后面还有。桌面端没有横向溢出,这段等于不做事。 */
|
||||
/* 1) 620px 以上:链接行万一还是放不下(以后加了入口),横向可滑 + 右缘渐隐提示。
|
||||
2) 620px 及以下:整行已经收进 .nav-menu,这里只管菜单开合——原生 <details> 不会
|
||||
点外面自动收、也不响应 Esc,补上;点链接后也收掉,免得盖着刚打开的页面。 */
|
||||
(function () {
|
||||
var el = document.querySelector('.nav-links');
|
||||
if (!el) return;
|
||||
var update = function () {
|
||||
el.classList.toggle('overflow-right', el.scrollWidth - el.clientWidth - el.scrollLeft > 2);
|
||||
};
|
||||
update();
|
||||
el.addEventListener('scroll', update, { passive: true });
|
||||
window.addEventListener('resize', update);
|
||||
if (el) {
|
||||
var update = function () {
|
||||
el.classList.toggle('overflow-right', el.scrollWidth - el.clientWidth - el.scrollLeft > 2);
|
||||
};
|
||||
update();
|
||||
el.addEventListener('scroll', update, { passive: true });
|
||||
window.addEventListener('resize', update);
|
||||
}
|
||||
var menu = document.querySelector('.nav-menu');
|
||||
if (!menu) return;
|
||||
document.addEventListener('click', function (e) { if (!menu.contains(e.target)) menu.open = false; });
|
||||
menu.addEventListener('click', function (e) {
|
||||
if (e.target.closest && e.target.closest('a')) menu.open = false;
|
||||
});
|
||||
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') menu.open = false; });
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -73,7 +91,7 @@
|
||||
<div class="chips" style="margin-bottom:{{if gt (len .Activities) 14}}8{{else}}12{{end}}px">
|
||||
{{range $i, $a := .Activities}}{{if or (lt $i 14) ($.IsAct $a.ID)}}
|
||||
<a class="chip {{if $.IsAct $a.ID}}on{{end}}" href="{{$.ActLink $a.ID}}" title="{{num $a.Count}} 次到访">
|
||||
<span class="dot" style="background:{{colorof $a.Color}}"></span>{{$a.Name}}
|
||||
<span class="dot" style="background:{{csscolor $a.Color}}"></span>{{$a.Name}}
|
||||
<span class="muted">{{num $a.Count}}</span>
|
||||
</a>
|
||||
{{end}}{{end}}
|
||||
@@ -84,7 +102,7 @@
|
||||
<div class="chips">
|
||||
{{range $i, $a := .Activities}}{{if and (ge $i 14) (not ($.IsAct $a.ID))}}
|
||||
<a class="chip" href="{{$.ActLink $a.ID}}">
|
||||
<span class="dot" style="background:{{colorof $a.Color}}"></span>{{$a.Name}}
|
||||
<span class="dot" style="background:{{csscolor $a.Color}}"></span>{{$a.Name}}
|
||||
<span class="muted">{{num $a.Count}}</span>
|
||||
</a>
|
||||
{{end}}{{end}}
|
||||
@@ -166,7 +184,7 @@
|
||||
<div class="card">
|
||||
<div class="empty">
|
||||
<div class="big">🗺️</div>
|
||||
<div style="font-size:15px;color:var(--text);margin-bottom:6px">还没有导入任何数据</div>
|
||||
<div style="font-size:var(--fs-md);color:var(--text);margin-bottom:6px">还没有导入任何数据</div>
|
||||
<div>登录后台,上传 rond 导出的 <span class="mono">.rondbackup</span> 备份文件即可生成足迹地图。</div>
|
||||
<div style="margin-top:14px"><a class="btn primary" href="/admin">前往后台导入</a></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user