From 916dd74034f1b4ce9899cb5668dc355525fca52d Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:15:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=B6=E9=97=B4=E7=BA=BF=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E8=A1=8C=E8=A1=A5=E7=9C=9F=E5=AE=9E=E8=AE=A1=E6=95=B0=EF=BC=88?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E6=95=B0=20/=20=E6=B5=8F=E8=A7=88=E6=95=B0?= =?UTF-8?q?=EF=BC=89=EF=BC=8Cclassic=20=E9=A6=96=E9=A1=B5=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=8A=A0=E6=93=8D=E4=BD=9C=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - posts 表加 view_count 列(迁移自动补列),公开文章详情每被拉取 +1; 列表接口子查询带出非删除评论数 - classic 首页文章卡补上操作行(回复 / 赞 / 查看),与 vivid 同行为: 回复跳评论区锚点、赞本机 localStorage、查看进详情; 卡片底部 meta 行的日期不再重复包「查看」链接 - vivid 操作行的回复 / 查看旁展示对应计数(万以上缩写) --- backend/internal/api/api.go | 3 ++ backend/internal/model/model.go | 4 ++ backend/internal/store/store.go | 13 ++++- frontend/src/components/PostCard.vue | 77 +++++++++++++++++++++++++-- frontend/src/ui/yohaku/YohakuFeed.vue | 10 ++-- frontend/src/utils.js | 7 +++ 6 files changed, 102 insertions(+), 12 deletions(-) diff --git a/backend/internal/api/api.go b/backend/internal/api/api.go index 753c2b5..075fa71 100644 --- a/backend/internal/api/api.go +++ b/backend/internal/api/api.go @@ -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 { diff --git a/backend/internal/model/model.go b/backend/internal/model/model.go index 52a3a4e..7df603d 100644 --- a/backend/internal/model/model.go +++ b/backend/internal/model/model.go @@ -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 表里。 diff --git a/backend/internal/store/store.go b/backend/internal/store/store.go index 5337502..b5118a6 100644 --- a/backend/internal/store/store.go +++ b/backend/internal/store/store.go @@ -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 "", "" diff --git a/frontend/src/components/PostCard.vue b/frontend/src/components/PostCard.vue index 875ff66..fb7c021 100644 --- a/frontend/src/components/PostCard.vue +++ b/frontend/src/components/PostCard.vue @@ -1,14 +1,33 @@