审计修复:前端(模板 / CSS / JS 与无障碍)

- 地图脚本:localStorage 读写走 lsGet/lsSet 兜底(隐私模式下裸访问会打断整段脚本);
  preview() 在 /api/map-config 还没回来时不丢点(previewQueue 在 if (live) 之前排队);
  异步回包一律发序号作废,取消后回来的包不再画
- photo-exif.js:EXIF 时间全 0 不再变成 1899-11-30 写进到访时间;读 count 前先按版本卡宽度
- 数据编辑页:三个筛选表单都把其余条件用隐藏域原样带上,提交不再把别的列表的筛选清掉
- 补全页:sessionStorage 的读写一律包 try(脏数据/隐私模式都会抛)
- 窄屏:删掉被后面同特异性规则盖掉的死内边距;宽表堆叠类名成对、判据扩到所有 tb 表
- 无障碍:图片删除按钮补 aria-label,访客画廊图片补 alt
- report.html 补上漏掉的 </tr>,fog-mask 的宽表带 tb-stack

本批不碰数据库与导出路径:往返包 823/323/8/30/791/557/2246 全等、
坐标偏差 0.000000000、时间偏差 0s、迷雾 8701 块一致。
This commit is contained in:
tamakyi
2026-09-24 20:37:57 +08:00
parent b4c2a8452f
commit d5f6dfa381
18 changed files with 675 additions and 77 deletions
+89
View File
@@ -0,0 +1,89 @@
package web
import (
"io/fs"
"strings"
"testing"
)
// funcBody 从 marker 起按大括号配平取出一段函数体(含 marker)。
// 这几段代码里的字符串字面量都不含大括号,直接数括号是安全的。
func funcBody(src, marker string) string {
i := strings.Index(src, marker)
if i < 0 {
return ""
}
j := strings.Index(src[i:], "{")
if j < 0 {
return ""
}
depth := 0
for k := i + j; k < len(src); k++ {
switch src[k] {
case '{':
depth++
case '}':
depth--
if depth == 0 {
return src[i : k+1]
}
}
}
return ""
}
// TestMapLayersDropStaleResponses 守住「开关关着图层还在画」这个竞态。
//
// 勾选 → 请求发出 → 用户立刻取消 → 请求回包。回包若直接 addTo(map),图层就画上去了。
// 修法是给每次调用发一个序号,回包时对不上就作废;关键是**序号必须在 `if (!on) return;`
// 之前就递增**,否则取消时压根不会作废在途的那次请求。
//
// 判据放在源码文本上:这是「两个异步分支的相对顺序」,用 CDP 造这个时序既慢又脆。
func TestMapLayersDropStaleResponses(t *testing.T) {
js, err := staticFS.ReadFile("static/js/rond-map.js")
if err != nil {
t.Fatalf("读取 rond-map.js 失败: %v", err)
}
src := string(js)
for _, c := range []struct{ setter, seq, draw string }{
{"window.rondMap.setGpx = function (on)", "gpxSeq", "addTo(map)"},
{"window.rondMap.setHeat = function (on)", "heatSeq", "addTo(map)"},
{"window.rondMap.setCityAgg = function (on)", "citySeq", "addTo(map)"},
} {
body := funcBody(src, c.setter)
if body == "" {
t.Fatalf("在 rond-map.js 里找不到 %s 的函数体(写法变了,这条守卫就失效了)", c.setter)
}
inc := strings.Index(body, "++"+c.seq)
off := strings.Index(body, "if (!on) return;")
guard := strings.Index(body, "seq !== "+c.seq)
draw := strings.Index(body, c.draw)
if inc < 0 || off < 0 || inc > off {
t.Errorf("%s:必须在 `if (!on) return;` 之前先递增序号(否则取消时作废不了在途回包)", c.setter)
}
if guard < 0 || draw < 0 || guard > draw {
t.Errorf("%s:回包在 %s 之前没有做 `seq !== %s` 作废检查", c.setter, c.draw, c.seq)
}
}
// 行程轨迹开关在模板里(map.html),症状一样:取消勾选后旧回包把轨迹画回来。
html, err := fs.ReadFile(tmplFS, "templates/map.html")
if err != nil {
t.Fatalf("读取 map.html 失败: %v", err)
}
body := funcBody(string(html), "function refreshTrack()")
if body == "" {
t.Fatal("在 map.html 里找不到 refreshTrack(写法变了,这条守卫就失效了)")
}
inc := strings.Index(body, "++trackSeq")
off := strings.Index(body, "if (!trackOn)")
guard := strings.Index(body, "seq !== trackSeq")
draw := strings.Index(body, "setTrack(d.segs")
if inc < 0 || off < 0 || inc > off {
t.Error("refreshTrack:必须在 `if (!trackOn)` 之前先递增序号")
}
if guard < 0 || draw < 0 || guard > draw {
t.Error("refreshTrack:回包在 setTrack(d.segs…) 之前没有做 `seq !== trackSeq` 作废检查")
}
}