feat: 前台标签接入 antd 组件、列表封面图、favicon 随站点 Logo 及侧栏收窄

- 前台列表/详情/标签页标签统一 antd Tag;标签页改 CheckableTagGroup 单选
  (选中态由组件承接,再点已选标签回到全部);移除首页标签筛选条,
  标签入口收敛至顶部「标签」页
- 公开列表新增 cover 字段(取正文第一张站内图,store.FirstImageRef),
  首页条目左侧显示封面缩略图,无图条目保持纯文字
- favicon 随站点 Logo:webui.Meta.Icon 服务端注入 index.html,
  前端 useSiteTitle 兜底(dev 模板占位符场景)并即时生效;
  设置页「重置默认」清空 Logo 后回退内置 /favicon.svg
- 后台侧栏改 antd Menu(inline 分组),宽度 256→208、窄侧栏档 224→184
This commit is contained in:
2026-09-09 11:51:06 +08:00
parent 5457aeaa33
commit 5bac272f6d
13 changed files with 199 additions and 174 deletions
+3 -1
View File
@@ -40,13 +40,14 @@ func (s *Server) handleSiteInfo(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, ss)
}
// publicNoteItem 公开列表项(元信息,不含全文)。
// publicNoteItem 公开列表项(元信息 + 封面图,不含全文)。
type publicNoteItem struct {
Slug string `json:"slug"`
Title string `json:"title"`
Summary string `json:"summary"`
Tags []string `json:"tags"`
Pinned bool `json:"pinned"`
Cover string `json:"cover,omitempty"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
PublishedAt int64 `json:"published_at"`
@@ -71,6 +72,7 @@ func (s *Server) handlePublicNotes(w http.ResponseWriter, r *http.Request) {
items = append(items, publicNoteItem{
Slug: n.Slug, Title: n.Title, Summary: n.Summary,
Tags: n.Tags, Pinned: n.Pinned,
Cover: store.FirstImageRef(n.Content),
CreatedAt: n.CreatedAt, UpdatedAt: n.UpdatedAt, PublishedAt: n.PublishedAt,
})
}
+9
View File
@@ -299,6 +299,7 @@ func (s *Server) ResolveMeta(r *http.Request) webui.Meta {
OGType: "website",
SiteName: ss.SiteTitle,
OGURL: baseURL(r) + "/",
Icon: favicon(ss.SiteLogo),
}
slug := metaSlug(r.URL.Path)
if slug == "" {
@@ -318,6 +319,14 @@ func (s *Server) ResolveMeta(r *http.Request) webui.Meta {
return s.noteMeta(r, m, note)
}
// favicon favicon 地址:站点 Logo 优先,未设置回退内置图标。
func favicon(siteLogo string) string {
if siteLogo != "" {
return siteLogo
}
return "/favicon.svg"
}
func (s *Server) noteMeta(r *http.Request, base webui.Meta, n *store.Note) webui.Meta {
desc := n.Summary
if desc == "" {
+10
View File
@@ -64,6 +64,16 @@ func (s *Store) ImageIsPublic(id int64) (bool, error) {
// imageRefRe 扫描 content 中对 /api/images/{id} 的引用(§6.2 引用维护)。
var imageRefRe = regexp.MustCompile(`/api/images/(\d+)`)
// FirstImageRef 返回 content 中按出现顺序的第一张站内图片路径(无则空串)。
// 供公开列表推导封面图使用。
func FirstImageRef(content string) string {
m := imageRefRe.FindStringSubmatch(content)
if len(m) < 2 {
return ""
}
return m[0]
}
// RebuildImageRefs 在**同一事务**内重建笔记的图片引用:
// 删除该笔记全部 refs → 正则扫描 content → 重建(§6.2)。
func (s *Store) RebuildImageRefs(noteID int64, content string) error {
+2 -1
View File
@@ -167,7 +167,8 @@ func (s *Store) ListPublicNotes(page, pageSize int, tag string) ([]Note, int, er
if err := s.db.QueryRow(`SELECT COUNT(*) FROM notes n WHERE `+where, args...).Scan(&total); err != nil {
return nil, 0, err
}
q := `SELECT n.id, n.slug, n.title, n.summary, '' AS content, n.status, n.tags, n.pinned, n.deleted_at, n.created_at, n.updated_at, COALESCE(n.published_at, n.created_at) AS published_at
// 含全文:列表页需从正文推导封面图(FirstImageRef),个人规模条数有限可接受
q := `SELECT n.id, n.slug, n.title, n.summary, n.content, n.status, n.tags, n.pinned, n.deleted_at, n.created_at, n.updated_at, COALESCE(n.published_at, n.created_at) AS published_at
FROM notes n WHERE ` + where + `
ORDER BY n.pinned DESC, n.updated_at DESC, n.id DESC LIMIT ? OFFSET ?`
rows, err := s.db.Query(q, append(args, pageSize, (page-1)*pageSize)...)
+1
View File
@@ -30,6 +30,7 @@ type Meta struct {
OGURL string
OGImage string
SiteName string
Icon string // favicon:站点 Logo,未设置回退 /favicon.svg
}
// UI 静态资源服务。