feat: 新增发布日期自选、站点 Logo、回收站清空等十项改进

- 编辑页标题改 filled 变体;标签改 tags 选择器(可勾选既有/输入新建)
- 笔记发布日期可自选(schema v2 新增 published_at 并回填),前台展示发布日期
- 登录页移除「请输入管理密码以继续」「连续失败将被暂时锁定」文案
- 站点设置每页条数收敛为 10/20/30/50 选择器,后端白名单同源校验
- 站点设置两卡片宽屏左右/窄屏上下;修改密码按钮改常规大小
- 后台各页顶栏固定高度,切换页面不再抖动
- 站点 Logo:settings 新增 site_logo(站内路径),gc/孤儿清单豁免 Logo 图片,
  博客 header 站点名前展示,设置页支持上传/更换/清除
- 回收站右上角一键清空(DELETE /api/admin/trash,含确认弹窗)
- pn init 写入 Markdown 语法示例文档(slug welcome,公开)与程序生成的示例图片
This commit is contained in:
2026-09-09 11:10:30 +08:00
parent 87d9bab5dc
commit 5457aeaa33
25 changed files with 600 additions and 257 deletions
+34 -2
View File
@@ -14,6 +14,7 @@ const (
KeySiteDesc = "site_desc"
KeyPageSize = "page_size"
KeyBeianNo = "beian_no"
KeySiteLogo = "site_logo"
)
// GetSetting 读取单个设置。
@@ -39,6 +40,7 @@ type SiteSettings struct {
SiteDesc string `json:"site_desc"`
PageSize int `json:"page_size"`
BeianNo string `json:"beian_no"`
SiteLogo string `json:"site_logo"`
}
// Defaults,未初始化时兜底。
@@ -49,6 +51,15 @@ const (
DefaultBeianNo = ""
)
// ValidPageSize 每页条数可选项(前端选择器同源:10/20/30/50)。
func ValidPageSize(n int) bool {
switch n {
case 10, 20, 30, 50:
return true
}
return false
}
// GetSiteSettings 读取站点设置(白名单键,带默认值)。
func (s *Store) GetSiteSettings() (*SiteSettings, error) {
ss := &SiteSettings{SiteTitle: DefaultSiteTitle, SiteDesc: DefaultSiteDesc, PageSize: DefaultPageSize, BeianNo: DefaultBeianNo}
@@ -65,8 +76,9 @@ func (s *Store) GetSiteSettings() (*SiteSettings, error) {
if v, ok, err := s.GetSetting(KeyPageSize); err != nil {
return nil, err
} else if ok {
// strconv.Atoi 全文解析:拒绝 "10abc" 类部分解析的脏值(评审 round2 P2-13)
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil && n >= 1 && n <= 100 {
// strconv.Atoi 全文解析:拒绝 "10abc" 类部分解析的脏值(评审 round2 P2-13);
// 脏值/越界值回退默认,而非落入任意 [1,100]
if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil && ValidPageSize(n) {
ss.PageSize = n
}
}
@@ -75,5 +87,25 @@ func (s *Store) GetSiteSettings() (*SiteSettings, error) {
} else if ok {
ss.BeianNo = strings.TrimSpace(v)
}
if v, ok, err := s.GetSetting(KeySiteLogo); err != nil {
return nil, err
} else if ok {
ss.SiteLogo = strings.TrimSpace(v)
}
return ss, nil
}
// logoImageID 从 site_logo 设置解析受 GC 保护的图片 id(无 logo 或非本站图片路径返回 0)。
func (s *Store) logoImageID() int64 {
v, ok, err := s.GetSetting(KeySiteLogo)
if err != nil || !ok || v == "" {
return 0
}
if m := imageRefRe.FindStringSubmatch(v); m != nil {
var id int64
if _, err := fmtSscanInt(m[1], &id); err == nil && id > 0 {
return id
}
}
return 0
}