审计修复:前端(模板 / 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:
@@ -205,6 +205,13 @@
|
||||
<form method="get" action="/admin/edit" class="filter-row" style="margin-top:6px">
|
||||
{{/* back 要跟着走:不然一筛选,「返回地点库」就没了 */}}
|
||||
{{if .Back}}<input type="hidden" name="back" value="{{.BackRaw}}">{{end}}
|
||||
{{/* 三个表单各自只带自己那几个条件,其余条件必须原样带上:
|
||||
否则搜一下就把别的列表的筛选悄悄清掉了。 */}}
|
||||
{{if .VisitQuery}}<input type="hidden" name="vq" value="{{.VisitQuery}}">{{end}}
|
||||
{{if .MovementFilter.Transport}}<input type="hidden" name="mt" value="{{.MovementFilter.Transport}}">{{end}}
|
||||
{{if .MovementFilter.PlaceID}}<input type="hidden" name="mp" value="{{.MovementFilter.PlaceID}}">{{end}}
|
||||
{{if .MovementFilter.From}}<input type="hidden" name="mfrom" value="{{.MovementFilter.From}}">{{end}}
|
||||
{{if .MovementFilter.To}}<input type="hidden" name="mto" value="{{.MovementFilter.To}}">{{end}}
|
||||
<label class="f" style="flex:1 1 200px"><span>搜索</span><input type="text" name="q" value="{{.Query}}" placeholder="名称 / 城市 / 区县"></label>
|
||||
<label class="f" style="flex:0 1 170px"><span>坐标来源</span>
|
||||
<select name="src">
|
||||
@@ -343,6 +350,15 @@
|
||||
<h2>到访记录</h2>
|
||||
<form method="get" action="/admin/edit" class="search-row" style="margin-top:6px">
|
||||
{{if .Back}}<input type="hidden" name="back" value="{{.BackRaw}}">{{end}}
|
||||
{{/* 只搜到访不能把别的列表的筛选清掉(原来只带 vq+back,一搜 q/src/sus
|
||||
和行程的 mt/mp/mfrom/mto 全丢了)。 */}}
|
||||
{{if .Query}}<input type="hidden" name="q" value="{{.Query}}">{{end}}
|
||||
{{if .SourceFilter}}<input type="hidden" name="src" value="{{.SourceFilter}}">{{end}}
|
||||
{{if .SuspectOnly}}<input type="hidden" name="sus" value="1">{{end}}
|
||||
{{if .MovementFilter.Transport}}<input type="hidden" name="mt" value="{{.MovementFilter.Transport}}">{{end}}
|
||||
{{if .MovementFilter.PlaceID}}<input type="hidden" name="mp" value="{{.MovementFilter.PlaceID}}">{{end}}
|
||||
{{if .MovementFilter.From}}<input type="hidden" name="mfrom" value="{{.MovementFilter.From}}">{{end}}
|
||||
{{if .MovementFilter.To}}<input type="hidden" name="mto" value="{{.MovementFilter.To}}">{{end}}
|
||||
<label class="f"><span>搜索</span><input type="text" name="vq" value="{{.VisitQuery}}" placeholder="地点 / 城市 / 备注 / 活动 / 日期 / #编号"></label>
|
||||
<div class="act"><button class="btn" type="submit">搜索</button><a class="btn" href="{{.EditLink "vq"}}">重置</a></div>
|
||||
</form>
|
||||
@@ -424,6 +440,8 @@
|
||||
{{if .Back}}<input type="hidden" name="back" value="{{.BackRaw}}">{{end}}
|
||||
{{if .Query}}<input type="hidden" name="q" value="{{.Query}}">{{end}}
|
||||
{{if .VisitQuery}}<input type="hidden" name="vq" value="{{.VisitQuery}}">{{end}}
|
||||
{{if .SourceFilter}}<input type="hidden" name="src" value="{{.SourceFilter}}">{{end}}
|
||||
{{if .SuspectOnly}}<input type="hidden" name="sus" value="1">{{end}}
|
||||
<label class="f" style="flex:0 1 150px"><span>交通方式</span>
|
||||
<select name="mt">
|
||||
<option value="">全部</option>
|
||||
|
||||
@@ -40,14 +40,15 @@
|
||||
<div class="card">
|
||||
<h2>已有遮罩区({{len .Masks}})</h2>
|
||||
{{if .Masks}}
|
||||
<table class="tb">
|
||||
<div class="tw">
|
||||
<table class="tb tb-stack">
|
||||
<thead><tr><th>名称</th><th>圆心(纬度, 经度)</th><th>半径</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Masks}}
|
||||
<tr>
|
||||
<td>{{if .Name}}{{.Name}}{{else}}<span class="muted">(未命名)</span>{{end}}</td>
|
||||
<td class="mono">{{printf "%.5f" .Lat}}, {{printf "%.5f" .Lon}}</td>
|
||||
<td>{{printf "%.0f" .RadiusM}} 米</td>
|
||||
<td class="mono" data-label="圆心">{{printf "%.5f" .Lat}}, {{printf "%.5f" .Lon}}</td>
|
||||
<td data-label="半径">{{printf "%.0f" .RadiusM}} 米</td>
|
||||
<td>
|
||||
<button class="btn sm" type="button" data-edit
|
||||
data-id="{{.ID}}" data-name="{{.Name}}"
|
||||
@@ -62,6 +63,7 @@
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="muted">还没有遮罩区。在地图上点选圆心、设置半径后保存即可。</div>
|
||||
{{end}}
|
||||
|
||||
@@ -520,7 +520,11 @@
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ coord: coordSel(), points: coords })
|
||||
}).then(function (r) { return r.json(); }).then(function (d) {
|
||||
}).then(function (r) {
|
||||
// 500 时回的是 HTML,直接 r.json() 会抛 SyntaxError(英文),先判状态码
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
}).then(function (d) {
|
||||
if (d.error) throw new Error(d.error);
|
||||
return d.items || [];
|
||||
});
|
||||
@@ -671,29 +675,24 @@
|
||||
|
||||
// ---- 单个点位的地图弹窗:只建一个 Leaflet 实例,之后靠 setPoints/setView 复用 ----
|
||||
var modal = document.getElementById('point-modal');
|
||||
var modalInited = false;
|
||||
function openPointMap(lat, lon, name, addr) {
|
||||
document.getElementById('point-modal-title').textContent = name || '点位';
|
||||
document.getElementById('point-modal-meta').textContent =
|
||||
(addr && addr !== '—' ? addr + ' · ' : '') + lat.toFixed(6) + ', ' + lon.toFixed(6);
|
||||
modal.style.display = 'flex';
|
||||
var pts = [{ lat: lat, lon: lon, n: name || '', v: 1 }];
|
||||
if (!modalInited) {
|
||||
if (!window.rondMap) { document.getElementById('point-modal-meta').textContent += '(地图脚本未就绪)'; return; }
|
||||
window.rondMap.preview({ id: 'point-modal-map', points: pts });
|
||||
modalInited = true;
|
||||
// 单个点 fitBounds 会顶到最大缩放,等底图就绪后固定到街区级
|
||||
var pin = function () {
|
||||
var st = window.rondMap._state['point-modal-map'];
|
||||
if (st && st.map) { st.map.invalidateSize(); st.map.setView([lat, lon], 16); }
|
||||
};
|
||||
setTimeout(pin, 400);
|
||||
setTimeout(pin, 1200);
|
||||
} else {
|
||||
if (!window.rondMap) { document.getElementById('point-modal-meta').textContent += '(地图脚本未就绪)'; return; }
|
||||
// 统一交给 preview:它自己区分「首次建图」与「复用换点」,配置还没回来时也会把点排队补画。
|
||||
// 原来这里同步置 modalInited=true,第二次打开时地图实例其实还没建好(_state 里没有条目),
|
||||
// 于是走 else 分支拿不到 st,该画的点被静默丢掉。
|
||||
window.rondMap.preview({ id: 'point-modal-map', points: pts });
|
||||
// 单个点 fitBounds 会顶到最大缩放,等底图就绪后固定到街区级
|
||||
var pin = function () {
|
||||
var st = window.rondMap._state['point-modal-map'];
|
||||
if (st && st.setPoints) st.setPoints(pts);
|
||||
if (st && st.map) { st.map.invalidateSize(); st.map.setView([lat, lon], 16); }
|
||||
}
|
||||
};
|
||||
setTimeout(pin, 400);
|
||||
setTimeout(pin, 1200);
|
||||
}
|
||||
function closePointMap() { modal.style.display = 'none'; }
|
||||
document.getElementById('point-modal-close').addEventListener('click', closePointMap);
|
||||
|
||||
@@ -212,7 +212,9 @@
|
||||
{{if ne .Nav "map"}}
|
||||
<footer class="ft">
|
||||
{{.Settings.SiteTitle}}{{if .Settings.SiteSubtitle}} · {{.Settings.SiteSubtitle}}{{end}}
|
||||
{{if .Dataset}} · 数据截至 {{d .Dataset.Last}}{{end}}
|
||||
{{/* 到访被删光的数据集 last_visit_at 是 NULL,而 d 收的是 time.Time —— 少判这一层就会
|
||||
nil 解引用、页脚之后整段不渲染(客户端还拿到 200)。 */}}
|
||||
{{if .Dataset}}{{if .Dataset.Last}} · 数据截至 {{d .Dataset.Last}}{{end}}{{end}}
|
||||
</footer>
|
||||
{{end}}
|
||||
{{if .HasMap}}
|
||||
|
||||
@@ -384,13 +384,21 @@
|
||||
});
|
||||
|
||||
var trackOn = false;
|
||||
// 与图层开关同理:勾选后马上取消时请求还在飞,回包会把轨迹画回来;
|
||||
// 序号顺带解决「连点筛选 / 切底图」时旧回包盖掉新回包。
|
||||
var trackSeq = 0;
|
||||
function refreshTrack() {
|
||||
var seq = ++trackSeq;
|
||||
if (!trackOn) { window.rondMap.setTrack(null, null); return; }
|
||||
var q = qs();
|
||||
loadJSON('/api/track' + (q ? '?' + q : ''))
|
||||
.then(function (d) { window.rondMap.setTrack(d.segs || [], d.modes || []); })
|
||||
.then(function (d) {
|
||||
if (seq !== trackSeq) return;
|
||||
window.rondMap.setTrack(d.segs || [], d.modes || []);
|
||||
})
|
||||
// 轨迹拉不到只影响这一层,不阻塞地图本身;安静失败 + 取消勾选,别让开关停在"已选但没线"的状态
|
||||
.catch(function (err) {
|
||||
if (seq !== trackSeq) return;
|
||||
trackOn = false;
|
||||
document.getElementById('track-toggle').checked = false;
|
||||
window.rondMap.setTrack(null, null);
|
||||
|
||||
@@ -70,12 +70,12 @@
|
||||
当前存储:<b>{{if eq .ImageBackend "s3"}}对象存储(S3 / R2){{else if eq .ImageBackend "local"}}服务器本地磁盘{{else}}{{.ImageBackend}}{{end}}</b>
|
||||
,最多 {{.ImageMaxPer}} 张。传完访客在地点页点「图片」就能看到。
|
||||
</div>
|
||||
<div id="imgs-admin" data-src="{{.Place.SrcPK}}">
|
||||
<div id="imgs-admin" data-src="{{.Place.SrcPK}}" data-name="{{.Place.Name}}">
|
||||
<div class="imgs-grid" id="imgs-grid">
|
||||
{{range .Images}}
|
||||
<figure class="imgs-cell" data-id="{{.ID}}">
|
||||
<img src="{{.URL}}" alt="" loading="lazy">
|
||||
<button type="button" class="imgs-del" data-id="{{.ID}}" title="删除这张">×</button>
|
||||
<img src="{{.URL}}" alt="{{if $.Place.Name}}{{$.Place.Name}} 的图片{{else}}地点图片{{end}}" loading="lazy">
|
||||
<button type="button" class="imgs-del" data-id="{{.ID}}" title="删除这张" aria-label="删除这张图片">×</button>
|
||||
</figure>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -228,7 +228,7 @@
|
||||
<button class="imgs-nav imgs-prev" type="button" id="imgs-prev" aria-label="上一张" hidden>‹</button>
|
||||
<button class="imgs-nav imgs-next" type="button" id="imgs-next" aria-label="下一张" hidden>›</button>
|
||||
<div class="imgs-stage" id="imgs-stage">
|
||||
<img id="imgs-img" alt="">
|
||||
<img id="imgs-img" alt="{{if .Place.Name}}{{.Place.Name}} 的图片{{else}}地点图片{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -386,9 +386,11 @@
|
||||
fig.className = 'imgs-cell';
|
||||
fig.setAttribute('data-id', id);
|
||||
var img = document.createElement('img');
|
||||
img.src = url; img.loading = 'lazy'; img.alt = '';
|
||||
img.src = url; img.loading = 'lazy';
|
||||
img.alt = (admin && admin.dataset.name ? admin.dataset.name + ' 的图片' : '地点图片');
|
||||
var del = document.createElement('button');
|
||||
del.type = 'button'; del.className = 'imgs-del'; del.textContent = '×'; del.title = '删除这张';
|
||||
del.setAttribute('aria-label', '删除这张图片');
|
||||
del.setAttribute('data-id', id);
|
||||
fig.appendChild(img); fig.appendChild(del);
|
||||
return fig;
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<thead><tr><th>方位</th><th>地点</th><th class="num">纬度</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>最北</td><td data-label="地点">{{.North.Name}}<span class="muted"> · {{.North.City}}</span></td><td class="num" data-label="纬度">{{f1 .North.Lat}}°N</td></tr>
|
||||
{{if .South}}<tr><td>最南</td><td data-label="地点">{{.South.Name}}<span class="muted"> · {{.South.City}}</span></td><td class="num" data-label="纬度">{{f1 .South.Lat}}°N</td>{{end}}
|
||||
{{if .South}}<tr><td>最南</td><td data-label="地点">{{.South.Name}}<span class="muted"> · {{.South.City}}</span></td><td class="num" data-label="纬度">{{f1 .South.Lat}}°N</td></tr>{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user