时间线操作行补真实计数(评论数 / 浏览数),classic 首页同步加操作行

- posts 表加 view_count 列(迁移自动补列),公开文章详情每被拉取 +1;
  列表接口子查询带出非删除评论数
- classic 首页文章卡补上操作行(回复 / 赞 / 查看),与 vivid 同行为:
  回复跳评论区锚点、赞本机 localStorage、查看进详情;
  卡片底部 meta 行的日期不再重复包「查看」链接
- vivid 操作行的回复 / 查看旁展示对应计数(万以上缩写)
This commit is contained in:
Sakurasan committed 2026-10-08 00:15:34 +08:00
1 parent 7171c95275
commit 916dd74034
6 files changed
+102 -12

No files matched your search

+3
View File
@@ -192,6 +192,9 @@ func (a *API) getPost(w http.ResponseWriter, r *http.Request) {
httpx.NotFound(w)
return
}
// 浏览数:详情页每被拉取一次 +1(草稿不走这里;不区分访客,博客够用)。
// 自增失败静默——计数器丢一次无关紧要,不能让文章因此打不开。
a.Store.BumpViewCount(p.ID)
// 邻居给详情页侧栏的时间线用;取不到不该让详情页挂掉。
nb, err := a.Store.Neighbors(slug)
if err != nil {
+4
View File
@@ -35,6 +35,10 @@ type Post struct {
// LinkCard 是正文里第一个外链的预览卡片(发布时抓一次,存 posts.link_card)。
// nil = 没有链接或抓取失败,前台不渲染卡片。
LinkCard *LinkCard `json:"link_card,omitempty"`
// CommentCount 是非删除评论数(列表接口子查询带出,操作行「回复」旁展示)。
CommentCount int64 `json:"comment_count"`
// ViewCount 是浏览数(公开文章详情每次拉取 +1)。
ViewCount int64 `json:"view_count"`
}
// LinkCard 是链接预览卡片:由 internal/linkmeta 抓取,以 JSON 存在 posts 表里。
+11 -2
View File
@@ -164,6 +164,8 @@ func (s *Store) migrate() error {
`ALTER TABLE posts ADD COLUMN images TEXT NOT NULL DEFAULT '[]'`,
// 正文首个外链的预览卡片(og 标题/描述/封面),JSON 对象或空串
`ALTER TABLE posts ADD COLUMN link_card TEXT NOT NULL DEFAULT ''`,
// 浏览数:公开文章详情每被拉取一次 +1(不区分访客,博客场景够用)
`ALTER TABLE posts ADD COLUMN view_count INTEGER NOT NULL DEFAULT 0`,
// 账号角色:owner(站主,全库唯一)/ reader(评论区访客)
`ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'reader'`,
// 后台账号密码(provider=admin 的行才有值):多用户改造后密码上库,
@@ -580,7 +582,9 @@ const postCols = `id, kind, title, slug, summary, cover_url, content_md, content
const listCols = `id, kind, title, slug, summary, cover_url,
'' AS content_md,
CASE WHEN kind = 'short' THEN content_html ELSE substr(content_html, 1, 600) END AS content_html,
status, published_at, created_at, updated_at, reading_minutes, LENGTH(content_md), images, link_card`
status, published_at, created_at, updated_at, reading_minutes, LENGTH(content_md), images, link_card,
(SELECT COUNT(*) FROM comments c WHERE c.post_id = posts.id AND c.is_deleted = 0) AS comment_count,
view_count`
// images 列的 JSON 编解码(列存 '[]',Go 侧 []string;坏数据静默为空)。
func decodeImages(s string) []string {
@@ -634,7 +638,7 @@ func scanPost(rows interface{ Scan(...any) error }) (model.Post, error) {
var imgs, card string
err := rows.Scan(&p.ID, &p.Kind, &p.Title, &p.Slug, &p.Summary, &p.CoverURL,
&p.ContentMd, &p.ContentHTML, &p.Status, &p.PublishedAt, &p.CreatedAt, &p.UpdatedAt,
&p.ReadingMinutes, &p.ContentLen, &imgs, &card)
&p.ReadingMinutes, &p.ContentLen, &imgs, &card, &p.CommentCount, &p.ViewCount)
p.Tags = []string{}
p.Images = decodeImages(imgs)
p.LinkCard = decodeLinkCard(card)
@@ -1473,6 +1477,11 @@ func (s *Store) Archive() ([]model.ArchiveYear, error) {
return years, nil
}
// BumpViewCount 浏览数自增。静默失败:计数器不该影响文章可读性。
func (s *Store) BumpViewCount(id int64) {
_, _ = s.db.Exec(s.db.Q(`UPDATE posts SET view_count = view_count + 1 WHERE id = ?`), id)
}
func splitDate(rfc3339 string) (string, string) {
if len(rfc3339) < 7 {
return "", ""