排查:/admin/settings 渲染到一半就断
- 现象:页面在「统计脚本」那张卡片处截断,没有保存按钮、没有 </form></html>,而 HTTP 仍是 200。
- 根因:跑的是旧二进制(改动前编译),磁盘上的模板却是新的。debug=true 下 pageSet()
每次渲染都从磁盘重解析模板,于是新 settings.html 引用了旧 setting.Settings 里没有的
TrackEnabled —— 执行期报错,而 render 只把错误写进日志,客户端只看到半张页面。
- 判据:strings <二进制> | grep -c cleanTrackScript(旧 0 / 新 2)。别用 strings | grep
TrackScript —— config 里本来就有这个名字,旧二进制也命中,是个假信号。
启动自检(防复发)
- template_smoke.go:pageProbe 给 24 个页面各配一份最小可用数据,New() 里全部渲染一遍,
任一页失败就拒绝启动。pageProbe 与 pageFiles 双向校验,新加页面必须补探针,
漏配按失败处理(不是省事)。
- smokeOne 用 recover 兜住方法里的 panic,报「哪一页坏了」而不是崩栈。
- 实测:往 settings.html 插一个不存在的字段 → 启动即报
「模板自检未通过…admin/settings: can't evaluate field …」,退出码 1。
- 门禁 template_smoke_test.go:一条跑全量自检,一条变异验证(探针换成字段更少的类型
必须报错且点名该页)。
站长导航被页面开关误伤
- guardPage 只拦访客(站长始终能访问全部公开页),但导航写的是 {{if .Settings.HasPage "x"}},
没有 IsAdmin 分支 ⇒ 页面开关一关,站长自己也只剩「主页」一个入口 —— 能访问却点不到。
改成 {{if or .IsAdmin (.Settings.HasPage "x")}}(map/places/timeline/stats/report 五处)。
- 门禁 nav_visibility_test.go:三向不变量(站长仍有全部入口 / 访客一个都没有 /
全开放时访客也看得到),做过变异验证。
- 访客侧「只有主页」是按后台配置应有的状态,没有动数据。
统计脚本关口收紧(cleanTrackScript)
- src= 只在 <script …> 开标签内部找。原来对整段做 strings.Index,脚本体里一句
`img.src=u` 就被命中、整段被判危险而清空 —— 页面照样提示「设置已保存」,站长看不出没挂上。
- 开闭标签从「只拒 shut > open」改成严格配平:末尾未闭合的 <script> 会把页面收尾整段吞进脚本。
- 不再静默:签名改成 (script, warn),说明码经 ?warn= 回显成人话;判为危险时整段不采纳、
库里原脚本与开关都不动(把站长手上好用的脚本一并清掉更糟)。
- 门禁 TestCleanTrackScript 重写:逐条边界 + 定义性不变量(采纳了非空结果时,
里面开闭标签数必定相等)+ TestTrackWarnMessage(未识别码不得回显)。变异验证做过。
- 文案订正:settings.html 的「conf/app.ini 的 track_script 会作为这里的默认值」有歧义 ——
它不回填文本框,而是「这个框留空时、注入那一刻才回落到 ini」。
其余
- bin/ 目录删除(15 个 exe / 412MB),二进制只留仓库根 rond.exe;
技能 rond-web-verify 与 memory details 十八里 bin/rond.exe 的写法同步改掉。
- 验收:./scripts/verify.sh 全绿且 0 skip(PASS=346 FAIL=0 SKIP=0)。
360 lines
18 KiB
HTML
360 lines
18 KiB
HTML
{{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 也是这么放的),
|
||
导航必须跟着一起放行,否则关掉页面开关后站长自己也只剩「主页」一个入口,
|
||
明明能访问却点不到。 */}}
|
||
<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}}
|
||
{{if or .IsAdmin (.Settings.HasPage "timeline")}}<a href="/timeline" class="{{if eq .Nav "timeline"}}on{{end}}">时间线</a>{{end}}
|
||
{{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>
|
||
<div class="nav-right">
|
||
{{if .User}}
|
||
<span class="who">{{.User.DisplayName}}</span>
|
||
<a class="btn sm" href="/admin"><span class="hide-sm">后台</span><span class="only-sm">后台</span></a>
|
||
<form method="post" action="/admin/logout" style="display:inline"><button class="btn sm" type="submit">退出</button></form>
|
||
{{else}}
|
||
<a class="btn sm" href="/admin"><span class="hide-sm">登录后台</span><span class="only-sm">登录</span></a>
|
||
{{end}}
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
<script>
|
||
/* 手机上导航装不下这几项,可以横向滑动(滚动条已隐藏)——右侧加一层渐隐,提示「还能往右滑」;
|
||
滑到末端就收掉,免得让人以为后面还有。桌面端没有横向溢出,这段等于不做事。 */
|
||
(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);
|
||
})();
|
||
</script>
|
||
{{end}}
|
||
|
||
{{define "filters"}}
|
||
<form class="card filters-card" method="get" action="{{.FilterBase}}" id="filters-form">
|
||
{{/* 窄屏折叠开关,与地图页筛选面板同一套(.panel-fold):桌面端它 display:none、表单照旧常开;
|
||
手机上默认收成这一条,只留「筛选 + 当前范围 + 箭头」。收起只是 CSS 隐藏,
|
||
表单里的控件都还在,筛选条件照旧随提交带上(act/hw/sort 那几个 hidden 也在)。 */}}
|
||
<button class="panel-fold" id="filters-fold" type="button" aria-controls="filters-form" aria-expanded="true">
|
||
<span>筛选</span>
|
||
<span class="pf-n">{{.RangeLabel}}{{if .ActiveChips}} · {{len .ActiveChips}} 个条件{{end}}</span>
|
||
<span class="pf-c" id="filters-fold-caret" aria-hidden="true">▾</span>
|
||
</button>
|
||
<div class="chips" style="margin-bottom:12px">
|
||
{{range .Presets}}<a class="chip {{if .Active}}on{{end}}" href="{{$.RangeLink .From .To}}">{{.Label}}</a>{{end}}
|
||
<span class="chip" style="background:transparent;color:var(--text-3)">{{.RangeLabel}}</span>
|
||
{{if .IsAdmin}}
|
||
<a class="chip {{if .ShowHomeWork}}on{{end}}" href="{{.HWLink}}" title="仅登录后的你自己可见,访客看不到这两类记录">含家/工作</a>
|
||
{{end}}
|
||
</div>
|
||
|
||
{{/* 已选条件:以前只能靠「重置」全清,想撤掉其中一个得手动改回「全部」再点应用 */}}
|
||
{{$chips := .ActiveChips}}
|
||
{{if $chips}}
|
||
<div class="chips active-filters">
|
||
<span class="af-label">已选</span>
|
||
{{range $chips}}<a class="chip on" href="{{.RemoveURL}}" title="去掉这个条件">{{.Label}}<span class="af-x">×</span></a>{{end}}
|
||
<a class="chip af-clear" href="{{.FilterBase}}">全部清空</a>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{if .Activities}}
|
||
<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="muted">{{num $a.Count}}</span>
|
||
</a>
|
||
{{end}}{{end}}
|
||
</div>
|
||
{{if gt (len .Activities) 14}}
|
||
<details class="more-types">
|
||
<summary>展开其余 {{len .Activities}} 个类型</summary>
|
||
<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="muted">{{num $a.Count}}</span>
|
||
</a>
|
||
{{end}}{{end}}
|
||
</div>
|
||
</details>
|
||
{{end}}
|
||
{{end}}
|
||
|
||
<div class="row" style="align-items:flex-end">
|
||
<label class="f" style="flex:1 1 140px;margin-bottom:0"><span>起始日期</span><input type="date" name="from" value="{{.From}}"></label>
|
||
<label class="f" style="flex:1 1 140px;margin-bottom:0"><span>结束日期</span><input type="date" name="to" value="{{.To}}"></label>
|
||
<label class="f" style="flex:1 1 130px;margin-bottom:0"><span>城市</span>
|
||
<select name="city">
|
||
<option value="">全部城市</option>
|
||
{{range .CityList}}<option value="{{.}}" {{if eq . $.City}}selected{{end}}>{{.}}</option>{{end}}
|
||
</select>
|
||
</label>
|
||
<label class="f" style="flex:1 1 130px;margin-bottom:0"><span>标签</span>
|
||
<select name="tag">
|
||
<option value="0">全部标签</option>
|
||
{{range .TagOptions}}<option value="{{.ID}}" {{if eq .ID $.TagID}}selected{{end}}>{{.Name}}</option>{{end}}
|
||
</select>
|
||
</label>
|
||
<label class="f" style="flex:1 1 120px;margin-bottom:0"><span>最短停留</span>
|
||
<select name="min">
|
||
<option value="0" {{if eq .MinDwell 0}}selected{{end}}>不限</option>
|
||
<option value="5" {{if eq .MinDwell 5}}selected{{end}}>≥ 5 分钟</option>
|
||
<option value="15" {{if eq .MinDwell 15}}selected{{end}}>≥ 15 分钟</option>
|
||
<option value="30" {{if eq .MinDwell 30}}selected{{end}}>≥ 30 分钟</option>
|
||
<option value="60" {{if eq .MinDwell 60}}selected{{end}}>≥ 1 小时</option>
|
||
</select>
|
||
</label>
|
||
<label class="f" style="flex:2 1 180px;margin-bottom:0"><span>关键词</span><input type="text" name="q" value="{{.Keyword}}" placeholder="地点 / 城市 / 类型,回车即可"></label>
|
||
<div style="display:flex;gap:8px">
|
||
{{/* 下拉与日期改动即生效(见下面的脚本);这个按钮是给「关键词」和
|
||
关了 JS 的浏览器兜底的,所以不再是主按钮样式 */}}
|
||
<button class="btn" type="submit">查询</button>
|
||
<a class="btn" href="{{.FilterBase}}">重置</a>
|
||
</div>
|
||
</div>
|
||
{{range .ActivityIDs}}<input type="hidden" name="act" value="{{.}}">{{end}}
|
||
{{if .ShowHomeWork}}<input type="hidden" name="hw" value="1">{{end}}
|
||
{{if .Sort}}<input type="hidden" name="sort" value="{{.Sort}}">{{end}}
|
||
</form>
|
||
{{/* 窄屏折叠:脚本必须紧跟这个表单 —— 放到 body 末尾时,手机上会先按「全部展开」画一帧
|
||
再收起,看着就是闪一下整张大卡(地图页那块面板也是贴在面板后面)。收起态记在本地,
|
||
翻页/换页之后不该又自己弹开。 */}}
|
||
<script>
|
||
(function () {
|
||
var form = document.getElementById('filters-form');
|
||
var btn = document.getElementById('filters-fold');
|
||
if (!form || !btn) return;
|
||
var caret = document.getElementById('filters-fold-caret');
|
||
var KEY = 'rond-filters-fold';
|
||
var desk = window.matchMedia('(min-width: 621px)');
|
||
var saved = null;
|
||
try { saved = localStorage.getItem(KEY); } catch (e) { /* 隐私模式禁用了,忽略 */ }
|
||
|
||
function apply(collapsed) {
|
||
form.classList.toggle('collapsed', collapsed);
|
||
btn.setAttribute('aria-expanded', String(!collapsed));
|
||
btn.title = collapsed ? '展开筛选' : '收起筛选';
|
||
if (caret) caret.textContent = collapsed ? '▸' : '▾';
|
||
}
|
||
// 没记过状态时窄屏默认收起。桌面端这条 class 不生效(显示规则整条在窄屏媒体查询里),
|
||
// 所以同一个浏览器在手机上收起过、回桌面照样看得见筛选。
|
||
apply(saved === null ? !desk.matches : saved === '1');
|
||
|
||
btn.addEventListener('click', function () {
|
||
var collapsed = !form.classList.contains('collapsed');
|
||
apply(collapsed);
|
||
try { localStorage.setItem(KEY, collapsed ? '1' : '0'); } catch (e) { /* 同上 */ }
|
||
});
|
||
})();
|
||
</script>
|
||
{{end}}
|
||
|
||
{{define "emptystate"}}
|
||
<div class="card">
|
||
<div class="empty">
|
||
<div class="big">🗺️</div>
|
||
<div style="font-size:15px;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>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{define "pager"}}
|
||
{{if gt .PageCount 1}}
|
||
<div class="pager">
|
||
{{if gt .PageNo 1}}<a href="{{.Link .FilterBase "page" (printf "%d" (sub .PageNo 1))}}">上一页</a>{{end}}
|
||
<span class="cur">第 {{.PageNo}} / {{.PageCount}} 页</span>
|
||
{{if lt .PageNo .PageCount}}<a href="{{.Link .FilterBase "page" (printf "%d" (add .PageNo 1))}}">下一页</a>{{end}}
|
||
</div>
|
||
{{end}}
|
||
{{end}}
|
||
|
||
{{define "layout"}}<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
{{/* viewport-fit=cover 才会把刘海屏 / 底部 Home 指示条的安全区通过 env() 暴露出来,否则 CSS 里的 safe-area-inset 全是 0 */}}
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<title>{{.Title}} · {{.Settings.SiteTitle}}</title>
|
||
<script>window.__assetV="{{.AssetV}}"</script>
|
||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||
<link rel="manifest" href="/static/manifest.webmanifest">
|
||
<meta name="theme-color" content="{{.Settings.AccentColor}}">
|
||
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
|
||
{{if .HasMap}}
|
||
<link rel="stylesheet" href="/static/vendor/leaflet/leaflet.css?v={{.AssetV}}">
|
||
{{if or (eq .Nav "map") (eq .Nav "topics")}}
|
||
<link rel="stylesheet" href="/static/vendor/markercluster/MarkerCluster.css?v={{.AssetV}}">
|
||
<link rel="stylesheet" href="/static/vendor/markercluster/MarkerCluster.Default.css?v={{.AssetV}}">
|
||
{{end}}
|
||
{{end}}
|
||
{{if .Flatpickr}}
|
||
<link rel="stylesheet" href="/static/vendor/flatpickr/flatpickr.min.css?v={{.AssetV}}">
|
||
{{end}}
|
||
<link rel="stylesheet" href="/static/css/app.css?v={{.AssetV}}">
|
||
{{/* accent-base 是站长选的主色原值:暗色模式会用 color-mix 把它提亮,否则深色底上的链接会看不清(原来的深绿在深底上只有 3.3:1)。 */}}
|
||
<style>:root{--accent:{{.Settings.AccentColor}};--accent-soft:{{.Settings.AccentColor}}1a;--accent-base:{{.Settings.AccentColor}}}</style>
|
||
</head>
|
||
<body>
|
||
{{template "nav" .}}
|
||
{{template "content" .}}
|
||
{{if ne .Nav "map"}}
|
||
<footer class="ft">
|
||
{{.Settings.SiteTitle}}{{if .Settings.SiteSubtitle}} · {{.Settings.SiteSubtitle}}{{end}}
|
||
{{/* 到访被删光的数据集 last_visit_at 是 NULL,而 d 收的是 time.Time —— 少判这一层就会
|
||
nil 解引用、页脚之后整段不渲染(客户端还拿到 200)。 */}}
|
||
{{if .Dataset}}{{if .Dataset.Last}} · 数据截至 {{d .Dataset.Last}}{{end}}{{end}}
|
||
</footer>
|
||
{{end}}
|
||
{{if .HasMap}}
|
||
<script src="/static/vendor/leaflet/leaflet.js?v={{.AssetV}}"></script>
|
||
{{if or (eq .Nav "map") (eq .Nav "topics")}}<script src="/static/vendor/markercluster/leaflet.markercluster.js?v={{.AssetV}}"></script>{{end}}
|
||
<script src="/static/js/rond-map.js?v={{.AssetV}}"></script>
|
||
{{end}}
|
||
{{if .Flatpickr}}
|
||
<script src="/static/vendor/flatpickr/flatpickr.min.js?v={{.AssetV}}"></script>
|
||
<script src="/static/vendor/flatpickr/l10n/zh.js?v={{.AssetV}}"></script>
|
||
{{/* 时间框的解析器与输入引导。必须在 flatpickr 之后、页面内联脚本之前:
|
||
它不自己调 flatpickr,只提供 parseDate 与 attach,由各页 whenReady 调 attachPicker。 */}}
|
||
<script src="/static/js/datetime-input.js?v={{.AssetV}}"></script>
|
||
{{end}}
|
||
{{/* 输入弹层:替代 window.prompt(它会把提示文本与默认值写进地址栏)。
|
||
全站都可能用到(报告页复制链接、后台活动改名),所以无条件引入。 */}}
|
||
<script src="/static/js/prompt-box.js?v={{.AssetV}}"></script>
|
||
{{/* 第三方统计脚本:只在公开页面挂,后台/登录页不挂(免得把自己的操作也统计进去) */}}
|
||
{{if and .TrackScript (ne .Nav "admin")}}
|
||
{{.TrackScript}}
|
||
{{end}}
|
||
<script>
|
||
// 后台的表单提交 / 翻页 / 搜索都是整页刷新,浏览器会回到顶部,滚动位置就丢了。
|
||
// 这里在离开前把位置记进 sessionStorage,加载完再滚回去。
|
||
// 只在 /admin 下生效:前台页面(主页进详情之类)本来就该从头看。
|
||
(function () {
|
||
if (location.pathname.indexOf("/admin") !== 0) return;
|
||
var KEY = "rond:scroll:" + location.pathname;
|
||
function record() {
|
||
try {
|
||
sessionStorage.setItem(KEY, Math.round(window.scrollY || window.pageYOffset || 0) + ":" + Date.now());
|
||
} catch (e) {}
|
||
}
|
||
function restore() {
|
||
try {
|
||
var raw = sessionStorage.getItem(KEY);
|
||
if (!raw) return;
|
||
sessionStorage.removeItem(KEY);
|
||
var parts = raw.split(":"), y = parseInt(parts[0], 10), ts = parseInt(parts[1], 10);
|
||
// 10 秒内才认:确认框被取消时留下的记录不该在下次打开时把视图拽走
|
||
if (isFinite(y) && y > 0 && Date.now() - ts < 10000) window.scrollTo(0, y);
|
||
} catch (e) {}
|
||
}
|
||
// 等 load:解析阶段就滚的话文档可能还不够高,scrollTo 会被截断到 0
|
||
if (document.readyState === "complete") restore();
|
||
else window.addEventListener("load", restore);
|
||
document.addEventListener("submit", record, true);
|
||
// 翻页 / 重置这类是同路径的链接跳转,也要记(新开标签、锚点、跨页不记)
|
||
document.addEventListener("click", function (ev) {
|
||
var a = ev.target && ev.target.closest ? ev.target.closest("a") : null;
|
||
if (!a) return;
|
||
var href = a.getAttribute("href") || "";
|
||
if (!href || href.charAt(0) === "#" || a.target === "_blank") return;
|
||
var u;
|
||
try { u = new URL(a.href, location.href); } catch (e) { return; }
|
||
if (u.pathname === location.pathname) record();
|
||
}, true);
|
||
})();
|
||
</script>
|
||
|
||
{{/* 长列表(地点库 300+ 行、时间线 800+ 条)划到底后能一键回来。
|
||
地图页整页不滚动,所以这个按钮在那里不会出现。 */}}
|
||
{{/* 顶部细进度条:筛选是整页 GET,慢网下点了没反应会让人以为卡住 */}}
|
||
<div class="load-bar" id="load-bar" hidden></div>
|
||
<button class="to-top" id="to-top" type="button" aria-label="回到顶部" hidden>↑</button>
|
||
<script>
|
||
(function () {
|
||
/* ---------- 顶部加载进度条 ---------- */
|
||
var bar = document.getElementById('load-bar'), timer = null, safety = null, w = 0;
|
||
function start() {
|
||
bar.hidden = false;
|
||
w = 8;
|
||
bar.style.width = w + '%';
|
||
bar.style.opacity = '1';
|
||
clearInterval(timer);
|
||
timer = setInterval(function () {
|
||
w = Math.min(90, w + Math.max(0.4, (90 - w) / 14));
|
||
bar.style.width = w + '%';
|
||
}, 180);
|
||
// 兜底:万一这次「跳转」被阻止了(比如地图页是 AJAX 换数据),别让进度条一直挂着
|
||
clearTimeout(safety);
|
||
safety = setTimeout(finish, 8000);
|
||
}
|
||
function finish() {
|
||
clearInterval(timer);
|
||
clearTimeout(safety);
|
||
if (bar.hidden) return;
|
||
bar.style.width = '100%';
|
||
setTimeout(function () {
|
||
bar.style.opacity = '0';
|
||
setTimeout(function () { bar.hidden = true; bar.style.width = '0'; }, 260);
|
||
}, 140);
|
||
}
|
||
window.rondProgress = { start: start, finish: finish };
|
||
document.addEventListener('submit', start, true);
|
||
document.addEventListener('click', function (e) {
|
||
var a = e.target && e.target.closest ? e.target.closest('a') : null;
|
||
if (!a || a.target === '_blank' || a.hasAttribute('download')) return;
|
||
var u;
|
||
try { u = new URL(a.href, location.href); } catch (err) { return; }
|
||
// 只对「真的会跳走」的站内链接亮进度条
|
||
if (u.origin !== location.origin) return;
|
||
if (u.pathname === location.pathname && u.search === location.search) return;
|
||
start();
|
||
}, true);
|
||
window.addEventListener('pageshow', finish); // 从 bfcache 回来时也要收掉
|
||
|
||
/* ---------- 筛选:改动即生效 ---------- */
|
||
var form = document.getElementById('filters-form');
|
||
if (form) {
|
||
// 下拉与日期改了就提交,不用再点「查询」。
|
||
// 关键词不自动提交(每敲一个字都跳转会很难受)——回车即可。
|
||
function apply() {
|
||
// 用 requestSubmit 而不是 form.submit():前者会**触发 submit 事件**,
|
||
// 于是地图页自己那个「拦下提交、改走 AJAX」的处理器仍然生效;
|
||
// 用 submit() 会绕过它,把地图页的筛选变成整页刷新。
|
||
if (form.requestSubmit) form.requestSubmit();
|
||
else form.submit();
|
||
}
|
||
form.addEventListener('change', function (e) {
|
||
var el = e.target;
|
||
if (!el || !el.name) return;
|
||
if (el.tagName === 'SELECT' || el.type === 'date') apply();
|
||
});
|
||
}
|
||
|
||
var b = document.getElementById('to-top');
|
||
function sync() { b.hidden = window.scrollY < 600; }
|
||
window.addEventListener('scroll', sync, { passive: true });
|
||
sync();
|
||
b.addEventListener('click', function () {
|
||
// 尊重「减少动态效果」偏好:开了就别做平滑滚动
|
||
var smooth = !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||
window.scrollTo({ top: 0, behavior: smooth ? 'smooth' : 'auto' });
|
||
});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>{{end}}
|