先出方案(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 拆又容易做出「中间那笔编译不过」的历史,所以合成一笔、在消息里分段。
175 lines
8.1 KiB
HTML
175 lines
8.1 KiB
HTML
{{define "content"}}
|
||
<div class="wrap">
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>迷雾导入预览</h1>
|
||
<div class="sub">这份快照<strong>还没有写入</strong>。先看清两种导入方式各自的后果,再决定用哪一种。</div>
|
||
</div>
|
||
<div class="row">
|
||
<a class="btn" href="/admin">← 返回后台</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>数量对比</h2>
|
||
<div class="tw">
|
||
<table class="tb tb-stack">
|
||
<thead>
|
||
<tr><th>状态</th><th class="num">块</th><th class="num">格</th><th class="num">面积</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>当前迷雾</td>
|
||
<td class="num" data-label="块">{{num .Diff.CurBlocks}}</td>
|
||
<td class="num" data-label="格">{{num .Diff.CurCells}}</td>
|
||
<td class="num" data-label="面积">{{printf "%.1f" .Diff.CurAreaKm2}} km²</td>
|
||
</tr>
|
||
<tr>
|
||
<td>这份快照</td>
|
||
<td class="num" data-label="块">{{num .Diff.IncBlocks}}</td>
|
||
<td class="num" data-label="格">{{num .Diff.IncCells}}</td>
|
||
<td class="num" data-label="面积">{{printf "%.1f" .Diff.IncAreaKm2}} km²</td>
|
||
</tr>
|
||
<tr>
|
||
<td>叠加导入后</td>
|
||
<td class="num" data-label="块">{{num .Diff.CurBlocks}}<span class="muted"> +{{num .Diff.GainBlocks}}</span></td>
|
||
<td class="num" data-label="格">{{num .Diff.CurCells}}<span class="muted"> +{{num .Diff.GainCells}}</span></td>
|
||
<td class="num" data-label="面积">{{printf "%.1f" .Diff.MergeAreaKm2}} km²</td>
|
||
</tr>
|
||
<tr>
|
||
<td>整体替换后</td>
|
||
<td class="num" data-label="块">{{num .Diff.IncBlocks}}</td>
|
||
<td class="num" data-label="格">{{num .Diff.IncCells}}</td>
|
||
<td class="num" data-label="面积">{{printf "%.1f" .Diff.CoverAreaKm2}} km²</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="card-sub" style="margin-top:10px">
|
||
两个块坐标相同就只按位合并,所以「叠加后」的块数不会等于两边相加——快照里有 {{num .Diff.OverlapBlocks}} 个块与当前迷雾落在同一块,
|
||
其中 {{num .Diff.SameBlocks}} 个一格都不会变。
|
||
</div>
|
||
</div>
|
||
|
||
{{if .HasBounds}}
|
||
<div class="card">
|
||
<h2>差异地图</h2>
|
||
<div class="card-sub">在真实底图上核对差异落在哪些区域,可以缩放平移。</div>
|
||
<div class="row" style="gap:16px;margin:2px 0 10px;font-size:var(--fs-sm);flex-wrap:wrap">
|
||
<span style="display:flex;align-items:center;gap:6px">
|
||
<i style="width:13px;height:13px;border-radius:3px;background:#24a45a;display:inline-block"></i>叠加会新增
|
||
</span>
|
||
<span style="display:flex;align-items:center;gap:6px">
|
||
<i style="width:13px;height:13px;border-radius:3px;background:#7e8692;display:inline-block"></i>两边都有
|
||
</span>
|
||
<span style="display:flex;align-items:center;gap:6px">
|
||
<i style="width:13px;height:13px;border-radius:3px;background:#d63838;display:inline-block"></i>整体替换会丢掉
|
||
</span>
|
||
</div>
|
||
<div id="fog-diff-map" style="height:400px;border-radius:10px;background:var(--line-soft)"
|
||
data-token="{{.Token}}"
|
||
data-min-lat="{{printf "%.6f" .MinLat}}" data-min-lon="{{printf "%.6f" .MinLon}}"
|
||
data-max-lat="{{printf "%.6f" .MaxLat}}" data-max-lon="{{printf "%.6f" .MaxLon}}"></div>
|
||
</div>
|
||
{{end}}
|
||
|
||
<div class="card">
|
||
<h2>叠加导入</h2>
|
||
{{if .Diff.Changed}}
|
||
<div class="alert ok">
|
||
会补上当前迷雾没有的 <strong>{{num .Diff.GainBlocks}}</strong> 块、<strong>{{num .Diff.GainCells}}</strong> 格
|
||
(约 {{printf "%.1f" .Diff.GainAreaKm2}} km²)。叠加只做按位合并,<strong>不会丢失任何已有数据</strong>。
|
||
</div>
|
||
{{else}}
|
||
<div class="alert err">
|
||
这份快照的点亮区域已经被当前迷雾<strong>完全覆盖</strong>——叠加导入不会带来任何变化,
|
||
多半是同一批数据又导出了一次。
|
||
</div>
|
||
{{end}}
|
||
<form method="post" action="/admin/fog?stash={{.Token}}"
|
||
onsubmit="this.querySelector('button').disabled=true">
|
||
<input type="hidden" name="mode" value="overlay">
|
||
<button class="btn primary" type="submit">叠加导入</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>整体替换</h2>
|
||
{{if gt .Diff.LostCells 0}}
|
||
<div class="alert err">
|
||
⚠️ 快照里<strong>没有</strong>当前迷雾的 <strong>{{num .Diff.LostBlocks}}</strong> 块、<strong>{{num .Diff.LostCells}}</strong> 格
|
||
(约 {{printf "%.1f" .Diff.LostAreaKm2}} km²)。整体替换后,这些区域会<strong>从点亮变回未探索</strong>,且无法恢复。
|
||
</div>
|
||
{{else}}
|
||
<div class="alert ok">
|
||
这份快照完整包含了当前迷雾的全部点亮区域,替换不会丢数据。
|
||
</div>
|
||
{{end}}
|
||
{{if and (eq .TildeLayers 0) (eq .HashLayers 0)}}
|
||
<div class="alert err">
|
||
⚠️ 快照里没有 <span class="mono">Model/~</span> 与 <span class="mono">Model/#</span> 两层数据,替换会把现有的这两层一并清掉。
|
||
</div>
|
||
{{else}}
|
||
<div class="muted" style="font-size:var(--fs-sm);margin-bottom:8px">
|
||
另外,站点不解析但会留存的 <span class="mono">Model/~</span>({{.TildeLayers}} 项)与
|
||
<span class="mono">Model/#</span>({{.HashLayers}} 项)也会一并换成这份快照里的{{if .OtherLayers}},另有 {{.OtherLayers}} 项其它条目{{end}}。
|
||
</div>
|
||
{{end}}
|
||
<form method="post" action="/admin/fog?stash={{.Token}}"
|
||
onsubmit="return confirm('整体替换会清空当前迷雾。{{if gt .Diff.LostCells 0}}快照里缺 {{num .Diff.LostCells}} 格,替换后这些区域会变回未探索,且不可恢复。{{end}}确认替换?')">
|
||
<input type="hidden" name="mode" value="cover">
|
||
<button class="btn danger" type="submit">整体替换</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
// Leaflet 由 layout 在 content 之后引入,脚本执行时 window.L 还没有,
|
||
// 必须等 load 之后再初始化,否则地图整个不出现(与迷雾遮罩页同一条约定)
|
||
function whenReady(cb) {
|
||
if (document.readyState === 'complete') { setTimeout(cb, 0); return; }
|
||
window.addEventListener('load', cb);
|
||
}
|
||
|
||
var el = document.getElementById('fog-diff-map');
|
||
if (!el) return;
|
||
|
||
// 底图走站点配置(与迷雾遮罩页同一套取法):带密钥的图层由服务端转发,
|
||
// 免密钥的是浏览器直连的 {s} 模板,不能硬写 /tiles/ 路由
|
||
var fallback = {
|
||
def: { url: 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', maxZoom: 18, attr: '© 高德地图', crs: 'gcj02' },
|
||
subs: ['1', '2', '3', '4']
|
||
};
|
||
|
||
function init(cfg) {
|
||
var def = cfg.def;
|
||
var map = L.map(el, { scrollWheelZoom: false });
|
||
L.tileLayer(def.url, { subdomains: cfg.subs, maxZoom: def.maxZoom || 18, minZoom: 3, attribution: def.attr || '' }).addTo(map);
|
||
// 差异瓦片必须和底图同坐标系:迷雾网格是 WGS-84,服务端按 crs 逐块纠偏
|
||
L.tileLayer('/admin/fog/preview/' + el.dataset.token + '/{z}/{x}/{y}?crs=' + (def.crs || 'gcj02'), {
|
||
minZoom: 3, maxZoom: 18, opacity: 0.95
|
||
}).addTo(map);
|
||
var b = [
|
||
[parseFloat(el.dataset.minLat), parseFloat(el.dataset.minLon)],
|
||
[parseFloat(el.dataset.maxLat), parseFloat(el.dataset.maxLon)]
|
||
];
|
||
// 只有一个块时两个角重合,不设上限会一路放大到最大级别
|
||
map.fitBounds(b, { padding: [24, 24], maxZoom: 13 });
|
||
L.control.scale({ imperial: false }).addTo(map);
|
||
}
|
||
|
||
whenReady(function () {
|
||
if (!window.L) return;
|
||
fetch('/api/map-config', { headers: { 'Accept': 'application/json' } })
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (c) {
|
||
var def = (c && c.base && c.base.length) ? c.base[0] : fallback.def;
|
||
init({ def: def, subs: (c && c.subs && c.subs.length) ? c.subs : fallback.subs });
|
||
})
|
||
.catch(function () { init(fallback); });
|
||
});
|
||
})();
|
||
</script>
|
||
{{end}}
|