统计页把「我的结论」与「访客点赞」拆成两块

用户问「数据统计处的推荐地点和踩雷地点只针对站长吗?应该分开,用户的点赞点踩单独做一块」。
他看到的没错:那张卡的**列表全是站长结论**(verdictPlaces = place_votes),访客票只在副标题里
露了个总数(👍 N · 👎 M)——既看不出哪几个地点被顶上去,也容易被当成两者合并的榜。

- 新增 votes.TopVisitorVoted:按访客票数列地点榜。HAVING 只算**至少有一票的那个方向**,
  否则 0 票的地点会把榜塞满;同票数按 visit_count DESC, p.id 排,刷新之间顺序不跳。
  票按坐标键挂,所以 join 回当前数据集的地点才定位得到(与 Tallies/verdictPlaces 同一口径)。
- 「推荐 & 踩雷(我的结论)」只讲站长,副标题只留「推荐 N · 踩雷 M」,标题与列表都改成「我」的口吻。
- 新增「访客点赞」卡:副标题总票数,两列列 👍/👎 票最多的地点并带票数,各有空态。
- 加载拆成两个 pageJob(verdicts / visitorvotes)并行,两块互不依赖就不该串着等。
- 两块共用同一个可见性开关(IsAdmin || Settings.MarksPublic),与地点页投票区一致。

用例(stats_votes_split_test.go)按卡片标题切出两段 HTML,断言「我标的地点只出现在站长块、
访客顶的地点只出现在访客块」,并核对两边各自的计数——比单看总数更能证明没串台。

⚠️ 又一次同一个坑:两张票表都按坐标键读、与数据集无关,place_visitor_votes 还没有 user_id、
不随删用户清理 ⇒ 上一轮跑剩的票会被算进来、票数越堆越多。新增 isolateVoteState 按 place_key
清两张表(前置 + cleanup 各一次)。

顺带订正 README:那里还写着站长结论「按 src_pk 存」,其实早就是坐标键了。
全量验收 PASS=338 FAIL=0 SKIP=0。
This commit is contained in:
tamakyi
2026-10-01 01:47:48 +08:00
parent 4c3032cf48
commit ff46a9cbb8
9 changed files with 340 additions and 13 deletions
+37 -4
View File
@@ -21,14 +21,18 @@
</div>
{{if or .IsAdmin .Settings.MarksPublic}}
{{/* 第一块:站长自己的结论。只有站长能标,所以这里只说「我的」。 */}}
<div class="card" style="margin-bottom:16px">
<h2>推荐 &amp; 踩雷</h2>
<div class="row" style="justify-content:space-between;align-items:center;flex-wrap:wrap">
<h2 style="margin:0">推荐 &amp; 踩雷(我的结论)</h2>
<span class="muted" style="font-size:12.5px">推荐 {{num .VoteRec}} · 踩雷 {{num .VoteAvoid}}</span>
</div>
<div class="card-sub">
站长结论 推荐 {{num .VoteRec}} · 踩雷 {{num .VoteAvoid}} | 访客票数 👍 {{num .VoteUp}} · 👎 {{num .VoteDown}}
只统计<strong>你自己</strong>在地点页或地图气泡里标的 👍/👎——按坐标键存,重新上传备份也不会丢。
</div>
<div class="grid g2">
<div>
<div class="muted" style="font-size:12.5px;margin-bottom:6px">👍 推荐的地点</div>
<div class="muted" style="font-size:12.5px;margin-bottom:6px">👍 我推荐的地点</div>
{{if .Recommended}}
<div class="chips">
{{range .Recommended}}<a class="chip" href="{{$.PlaceLink .ID}}"><span class="dot" style="background:#34c759"></span>{{if .Name}}{{.Name}}{{else}}未命名{{end}}{{if .City}}<span class="muted"> · {{.City}}</span>{{end}}</a>{{end}}
@@ -36,7 +40,7 @@
{{else}}<div class="empty" style="padding:10px 0">还没有标记</div>{{end}}
</div>
<div>
<div class="muted" style="font-size:12.5px;margin-bottom:6px">👎 踩雷的地点</div>
<div class="muted" style="font-size:12.5px;margin-bottom:6px">👎 我踩雷的地点</div>
{{if .Avoided}}
<div class="chips">
{{range .Avoided}}<a class="chip" href="{{$.PlaceLink .ID}}"><span class="dot" style="background:#ff3b30"></span>{{if .Name}}{{.Name}}{{else}}未命名{{end}}{{if .City}}<span class="muted"> · {{.City}}</span>{{end}}</a>{{end}}
@@ -45,6 +49,35 @@
</div>
</div>
</div>
{{/* 第二块:访客的点赞点踩。匿名一人一票、可改可撤,与上面的站长结论是两码事。 */}}
<div class="card" style="margin-bottom:16px">
<div class="row" style="justify-content:space-between;align-items:center;flex-wrap:wrap">
<h2 style="margin:0">访客点赞</h2>
<span class="muted" style="font-size:12.5px">👍 {{num .VoteUp}} 票 · 👎 {{num .VoteDown}} 票</span>
</div>
<div class="card-sub">
访客在地点页点的 👍/👎,一人一票、可改可撤。与「我的结论」互不影响:你标你的,访客投访客的。
</div>
<div class="grid g2">
<div>
<div class="muted" style="font-size:12.5px;margin-bottom:6px">👍 被点赞最多的地点</div>
{{if .VisitorUp}}
<div class="chips">
{{range .VisitorUp}}<a class="chip" href="{{$.PlaceLink .ID}}"><span class="dot" style="background:#34c759"></span>{{if .Name}}{{.Name}}{{else}}未命名{{end}}<span class="muted"> · 👍 {{.Up}}{{if .Down}} · 👎 {{.Down}}{{end}}</span></a>{{end}}
</div>
{{else}}<div class="empty" style="padding:10px 0">还没有访客点赞</div>{{end}}
</div>
<div>
<div class="muted" style="font-size:12.5px;margin-bottom:6px">👎 被点踩最多的地点</div>
{{if .VisitorDown}}
<div class="chips">
{{range .VisitorDown}}<a class="chip" href="{{$.PlaceLink .ID}}"><span class="dot" style="background:#ff3b30"></span>{{if .Name}}{{.Name}}{{else}}未命名{{end}}<span class="muted"> · 👎 {{.Down}}{{if .Up}} · 👍 {{.Up}}{{end}}</span></a>{{end}}
</div>
{{else}}<div class="empty" style="padding:10px 0">还没有访客点踩</div>{{end}}
</div>
</div>
</div>
{{end}}
<div class="card">