统计页把「我的结论」与「访客点赞」拆成两块
用户问「数据统计处的推荐地点和踩雷地点只针对站长吗?应该分开,用户的点赞点踩单独做一块」。 他看到的没错:那张卡的**列表全是站长结论**(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:
@@ -160,6 +160,46 @@ func (s *Store) Tally(ctx context.Context, datasetID int64, srcPK int) (Tally, e
|
||||
return t, err
|
||||
}
|
||||
|
||||
// VotedPlace 是「访客票榜」上的一条:某个地点收到的 👍/👎 票数。
|
||||
type VotedPlace struct {
|
||||
ID int64
|
||||
SrcPK int
|
||||
Name string
|
||||
City string
|
||||
Up int
|
||||
Down int
|
||||
}
|
||||
|
||||
// TopVisitorVoted 按访客票数排地点榜:vote 传 1 取「👍 最多」、传 -1 取「👎 最多」。
|
||||
//
|
||||
// 只算**至少有一票**的那个方向(HAVING),否则「0 票」的地点会把榜单塞满;
|
||||
// 同票数时按到访次数、再按编号排,保证顺序稳定(不同页面刷新之间不会跳来跳去)。
|
||||
//
|
||||
// 票按坐标键挂(换数据集仍跟着同一地点),所以得 join 回当前数据集的地点才定位得到。
|
||||
func (s *Store) TopVisitorVoted(ctx context.Context, datasetID int64, vote, limit int) ([]VotedPlace, error) {
|
||||
rows, err := s.DB.QueryContext(ctx, `SELECT p.id, p.src_pk, COALESCE(p.name,''), COALESCE(p.city,''),
|
||||
count(*) FILTER (WHERE vv.vote=1), count(*) FILTER (WHERE vv.vote=-1)
|
||||
FROM places p JOIN place_visitor_votes vv ON vv.place_key = `+placeKeyExpr+`
|
||||
WHERE p.dataset_id=$1 AND p.lat IS NOT NULL AND p.lon IS NOT NULL
|
||||
GROUP BY p.id
|
||||
HAVING count(*) FILTER (WHERE vv.vote=$2) > 0
|
||||
ORDER BY count(*) FILTER (WHERE vv.vote=$2) DESC, p.visit_count DESC, p.id
|
||||
LIMIT $3`, datasetID, vote, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []VotedPlace
|
||||
for rows.Next() {
|
||||
var vp VotedPlace
|
||||
if err := rows.Scan(&vp.ID, &vp.SrcPK, &vp.Name, &vp.City, &vp.Up, &vp.Down); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, vp)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// VoterAll 返回该访客投过的全部票:src_pk -> vote,供地图一次性标注「我投的」。
|
||||
func (s *Store) VoterAll(ctx context.Context, datasetID int64, voterKey string) (map[int]int, error) {
|
||||
out := map[int]int{}
|
||||
|
||||
Reference in New Issue
Block a user