Files
rond-with-you/internal/web/templates/topic.html
T
tamakyi eb35abb7f2 专题页的迷雾按专题圈的区域收口
以前专题的中心/半径只用来定视图,迷雾图层一直是全局的;管理页那句
「迷雾由地图按视野自动呈现,不需要单独圈选」其实是把「没做」写成了
「不用做」。现在专题圈了区域时,迷雾只在那个圆内显示。

做法是给迷雾单独一个 pane(zIndex 250,底图 200 之上)再对该 pane 做
clip-path: circle()——不能直接裁 tilePane,底图也在里面,会把地图一起挖空。

两个坑:
- 圆心必须先 project() 到底图坐标系(与 fitCircle 一致),否则整体偏移;
- 裁剪圆的注册必须排在 applyFit() 之后:视图未定时 latLngToContainerPoint()
  会抛「Set map center and zoom first.」,把后续图层添加一起打断——实测
  表现是 pane 建好了、迷雾瓦片一张都不出来,而且不报错。

迷雾是累积快照、没有时间维度,所以只按区域收口,不按专题的时间范围收口。
守卫 TestTopicFogIsClippedToArea 钉住两端与注册顺序。
2026-10-10 10:41:27 +08:00

101 lines
4.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "content"}}
{{if not .HasData}}
<div class="wrap">{{template "emptystate" .}}</div>
{{else}}
<div class="wrap">
<div class="page-head">
<div>
<h1>{{.Topic.Title}}</h1>
{{if .Topic.Subtitle}}<div class="sub">{{.Topic.Subtitle}}</div>{{end}}
{{/* 时间范围独立成行:塞进 KPI 网格里会被挤成好几行 */}}
<div class="topic-range">{{.Topic.RangeLabel}}{{if .Topic.DayCount}} · 共 {{num .Topic.DayCount}} 天{{end}}</div>
</div>
<div class="row">
<a class="btn" href="/topics">← 全部专题</a>
{{if .IsAdmin}}<a class="btn" href="/admin/topics?edit={{.Topic.ID}}">编辑</a>{{end}}
</div>
</div>
{{if not .Topic.Enabled}}<div class="alert err">这个专题尚未对外展示,只有你自己能看到。</div>{{end}}
{{if .Topic.Description}}<div class="card topic-desc">{{.Topic.Description}}</div>{{end}}
<div class="card">
<div class="kpis">
<div class="kpi"><div class="k">{{if .Topic.HasArea}}区域内地点{{else}}地点{{end}}</div><div class="v">{{num .Topic.PlaceCount}}</div></div>
<div class="kpi"><div class="k">到访次数</div><div class="v">{{num .Topic.VisitCount}}</div></div>
<div class="kpi"><div class="k">涉及城市</div><div class="v">{{num .Topic.CityCount}}</div></div>
<div class="kpi"><div class="k">累计停留</div><div class="v">{{dur .Topic.DwellMin}}</div></div>
</div>
</div>
<div class="card" style="padding:10px">
{{/* 高度写 inline:站点里 #map{height:100%} 是 ID 选择器,只靠外部 CSS 类
容易在别处规则变动/缓存不一致时把容器算成 0 高,地图就整块消失 */}}
<div id="map" class="topic-map" style="height:520px">
<div id="map-fallback" class="map-fallback">地图加载中……若一直不出现,请强制刷新(Ctrl+F5)或检查浏览器是否拦截了地图脚本</div>
</div>
</div>
<div class="card">
<h2>去过的地方</h2>
<div class="card-sub">共 {{num .Topic.PlaceCount}} 个,按到访次数排序</div>
{{if .Points}}
<div class="tw">
<table class="tb tb-stack">
<thead><tr><th>地点</th><th>城市</th><th>类型</th><th class="num">到访</th><th class="num">停留</th><th>最近一次</th></tr></thead>
<tbody>
{{range .Points}}
<tr>
<td>{{if .Name}}{{.Name}}{{else}}<span class="muted">(未命名)</span>{{end}}</td>
<td class="muted" data-label="城市">{{.City}}</td>
<td class="muted" data-label="类型">{{if .Act}}<span class="dot" style="background:{{csscolor .Color}}"></span>{{.Act}}{{end}}</td>
<td class="num" data-label="到访">{{num .Count}}</td>
<td class="num muted" data-label="停留">{{dur .Dwell}}</td>
<td class="muted mono" data-label="最近一次">{{.LastAt}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<div class="empty">这个时间 / 区域范围内没有记录</div>
{{end}}
</div>
</div>
<script>
(function () {
function whenReady(cb) {
if (document.readyState === 'complete') { setTimeout(cb, 0); return; }
window.addEventListener('load', cb);
}
whenReady(function () {
var el = document.getElementById('map');
// 移动端矮一些(inline 高度优先于外部 CSS,只能在这里按视口改)
if (el && window.matchMedia('(max-width: 620px)').matches) el.style.height = '360px';
if (window.rondMap) {
window.rondMap.full({
id: 'map',
points: {{.PointsJSON}},
fitCircle: {{.FitCircleJSON}},
fitBBox: {{.FitBBoxJSON}},
showFog: {{if .Topic.ShowFog}}true{{else}}false{{end}},
{{/* 圈了区域的专题:迷雾只在该圆内显示(rond-map.js 里给迷雾单独一个 pane 做 clip-path) */}}
{{if .Topic.HasArea}}fogArea: {lat: {{.Topic.CenterLat}}, lon: {{.Topic.CenterLon}}, radiusKm: {{.Topic.RadiusKm}}},{{end}}
back: '{{.BackQS}}'
});
}
// 地图脚本没就绪(被拦 / 加载失败)时,把兜底提示留在原位,用户至少知道发生了什么
setTimeout(function () {
var fb = document.getElementById('map-fallback');
if (fb && el && el.classList.contains('leaflet-container')) fb.style.display = 'none';
}, 1200);
});
})();
</script>
{{end}}
{{end}}