增加基础设置页面

This commit is contained in:
2026-07-29 16:45:22 +08:00
parent 0b22d4f126
commit a9a890a849
15 changed files with 472 additions and 84 deletions
+17 -2
View File
@@ -7,6 +7,7 @@ import (
"goblog/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// 创建评论请求
@@ -24,7 +25,10 @@ func GetComments(c *gin.Context) {
postID := c.Query("post_id")
status := c.Query("status")
db := database.DB.Model(&models.Comment{}).Preload("User")
db := database.DB.Model(&models.Comment{}).Preload("User").
Preload("Post", func(db *gorm.DB) *gorm.DB {
return db.Select("id", "title", "slug")
})
if postID != "" {
db = db.Where("post_id = ?", postID)
@@ -36,7 +40,18 @@ func GetComments(c *gin.Context) {
var comments []models.Comment
db.Order("created_at DESC").Find(&comments)
c.JSON(http.StatusOK, gin.H{"data": comments})
// Comment.Post 在 JSON 中被隐藏,额外附加文章标题和别名供前端展示
type commentWithPost struct {
models.Comment
PostTitle string `json:"post_title"`
PostSlug string `json:"post_slug"`
}
list := make([]commentWithPost, 0, len(comments))
for _, cm := range comments {
list = append(list, commentWithPost{cm, cm.Post.Title, cm.Post.Slug})
}
c.JSON(http.StatusOK, gin.H{"data": list})
}
// 创建评论
+84
View File
@@ -0,0 +1,84 @@
package handlers
import (
"net/http"
"strconv"
"goblog/config"
"goblog/database"
"goblog/models"
"github.com/gin-gonic/gin"
)
// 站点基础设置键名
const (
OptionSiteName = "site_name"
OptionSiteDesc = "site_desc"
OptionShowComments = "show_comments" // "1" 显示评论 / "0" 隐藏评论
OptionCommentPageSize = "comments_per_page" // 前台评论每页条数
)
// GetSettings 获取站点基础设置(管理员)
func GetSettings(c *gin.Context) {
cfg := config.GetGlobalConfig()
if cfg == nil {
cfg = config.Load()
}
opts, err := models.GetOptionsByUser(database.DB, 0)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "读取设置失败"})
return
}
// 默认值:站点信息取配置文件,评论默认显示、每页 10 条
settings := map[string]string{
OptionSiteName: cfg.App.Name,
OptionSiteDesc: cfg.App.Description,
OptionShowComments: "1",
OptionCommentPageSize: "10",
}
for name := range settings {
if v, ok := opts[name]; ok && v != "" {
settings[name] = v
}
}
c.JSON(http.StatusOK, gin.H{"data": settings})
}
// UpdateSettingsRequest 更新站点基础设置请求
type UpdateSettingsRequest struct {
SiteName string `json:"site_name" binding:"required"`
SiteDesc string `json:"site_desc"`
ShowComments bool `json:"show_comments"`
CommentsPerPage int `json:"comments_per_page" binding:"required,min=1,max=100"`
}
// UpdateSettings 保存站点基础设置(管理员)
func UpdateSettings(c *gin.Context) {
var req UpdateSettingsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()})
return
}
showComments := "0"
if req.ShowComments {
showComments = "1"
}
options := map[string]string{
OptionSiteName: req.SiteName,
OptionSiteDesc: req.SiteDesc,
OptionShowComments: showComments,
OptionCommentPageSize: strconv.Itoa(req.CommentsPerPage),
}
if err := models.BatchSetOptions(database.DB, 0, options); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存设置失败"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "设置已保存"})
}
+69 -25
View File
@@ -122,12 +122,11 @@ func IndexView(c *gin.Context) {
siteName := cfg.App.Name
siteDesc := cfg.App.Description
var option models.Option
if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil {
siteName = option.Value
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
siteName = v
}
if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil {
siteDesc = option.Value
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
siteDesc = v
}
data := IndexData{
@@ -164,6 +163,10 @@ type PostDetailData struct {
Year int
PrevPost *models.Post // 上一篇
NextPost *models.Post // 下一篇
// 评论展示设置与分页
ShowComments bool
CommentPage int
CommentTotalPages int
// Sidebar 数据
Categories []models.Category
Tags []models.Tag
@@ -180,14 +183,17 @@ func PostView(c *gin.Context) {
slug := c.Param("slug")
var post models.Post
err := database.DB.Preload("Category").Preload("Tags").Preload("Author").
Preload("Comments", func(db *gorm.DB) *gorm.DB {
return db.Where("status = ? AND parent_id IS NULL", "approved").Preload("Children", func(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", "approved")
})
}).
Where("slug = ? AND status = ?", slug, "published").
First(&post).Error
query := database.DB.Preload("Category").Preload("Tags").Preload("Author").
Where("status = ?", "published").
Session(&gorm.Session{}) // 创建可安全复用的查询会话,避免两次查询条件叠加
err := query.Where("slug = ?", slug).First(&post).Error
// slug 未命中时,若参数为纯数字则回退按文章 ID 查询(兼容 /post/17 形式的链接)
if err != nil {
if id, convErr := strconv.Atoi(slug); convErr == nil {
err = query.Where("id = ?", id).First(&post).Error
}
}
if err != nil {
c.HTML(http.StatusNotFound, "index", gin.H{
@@ -209,6 +215,43 @@ func PostView(c *gin.Context) {
var commentCount int64
database.DB.Model(&models.Comment{}).Where("post_id = ? AND status = ?", post.ID, "approved").Count(&commentCount)
// 读取评论展示设置
showComments := true
if v, optErr := models.GetOptionValue(database.DB, OptionShowComments, 0); optErr == nil {
showComments = v != "0"
}
commentPageSize := 10
if v, optErr := models.GetOptionValue(database.DB, OptionCommentPageSize, 0); optErr == nil {
if n, convErr := strconv.Atoi(v); convErr == nil && n > 0 {
commentPageSize = n
}
}
// 顶级评论分页查询(回复随父评论一起展示,不单独分页)
commentPage, _ := strconv.Atoi(c.DefaultQuery("cpage", "1"))
if commentPage < 1 {
commentPage = 1
}
var comments []models.Comment
commentTotalPages := 0
if showComments {
var topCount int64
database.DB.Model(&models.Comment{}).
Where("post_id = ? AND status = ? AND parent_id IS NULL", post.ID, "approved").
Count(&topCount)
commentTotalPages = int(math.Ceil(float64(topCount) / float64(commentPageSize)))
if commentTotalPages > 0 && commentPage > commentTotalPages {
commentPage = commentTotalPages
}
database.DB.Where("post_id = ? AND status = ? AND parent_id IS NULL", post.ID, "approved").
Preload("Children", func(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", "approved")
}).
Order("created_at ASC").
Offset((commentPage - 1) * commentPageSize).Limit(commentPageSize).
Find(&comments)
}
// 获取上一篇(发布时间更早的文章)
var prevPost models.Post
prevErr := database.DB.Where("status = ? AND published_at < ?", "published", post.PublishedAt).
@@ -223,12 +266,11 @@ func PostView(c *gin.Context) {
siteName := cfg.App.Name
siteDesc := cfg.App.Description
var option models.Option
if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil {
siteName = option.Value
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
siteName = v
}
if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil {
siteDesc = option.Value
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
siteDesc = v
}
// 加载侧边栏数据
@@ -241,9 +283,13 @@ func PostView(c *gin.Context) {
Description: post.Summary,
Post: post,
Pages: pages,
Comments: post.Comments,
Comments: comments,
CommentCount: commentCount,
Year: time.Now().Year(),
// 评论展示设置与分页
ShowComments: showComments,
CommentPage: commentPage,
CommentTotalPages: commentTotalPages,
// Sidebar 数据
Categories: categories,
Tags: tags,
@@ -306,14 +352,12 @@ func PageView(c *gin.Context) {
siteName := cfg.App.Name
siteDesc := cfg.App.Description
var option models.Option
if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil {
siteName = option.Value
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
siteName = v
}
if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil {
siteDesc = option.Value
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
siteDesc = v
}
// 加载侧边栏数据
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()