Files
soul-yongping/soul-api/internal/handler/admin_chapters.go
Alex-larget 46f94a9c81 在多个页面中通过骨架屏优化加载状态。
在章节、礼物代付详情、阅读和搜索结果页面,用骨架屏替换传统加载指示器,以提升数据获取过程中的用户体验。
更新骨架屏样式,使加载状态更加美观。
实现章节和配置信息的缓存策略,以优化性能并减少冷启动问题。
2026-03-18 12:56:34 +08:00

166 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"net/http"
"soul-api/internal/cache"
"soul-api/internal/database"
"soul-api/internal/model"
"github.com/gin-gonic/gin"
)
// AdminChaptersList GET /api/admin/chapters 从 chapters 表组树part -> chapters -> sections
func AdminChaptersList(c *gin.Context) {
var list []model.Chapter
if err := database.DB().Order("sort_order ASC, id ASC").Find(&list).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"structure": []interface{}{}, "stats": nil}})
return
}
type section struct {
ID string `json:"id"`
Title string `json:"title"`
Price float64 `json:"price"`
IsFree bool `json:"isFree"`
Status string `json:"status"`
EditionStandard *bool `json:"editionStandard,omitempty"`
EditionPremium *bool `json:"editionPremium,omitempty"`
}
type chapter struct {
ID string `json:"id"`
Title string `json:"title"`
Sections []section `json:"sections"`
}
type part struct {
ID string `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
Chapters []chapter `json:"chapters"`
}
partMap := make(map[string]*part)
chapterMap := make(map[string]map[string]*chapter)
for _, row := range list {
if partMap[row.PartID] == nil {
partMap[row.PartID] = &part{ID: row.PartID, Title: row.PartTitle, Type: "part", Chapters: []chapter{}}
chapterMap[row.PartID] = make(map[string]*chapter)
}
p := partMap[row.PartID]
if chapterMap[row.PartID][row.ChapterID] == nil {
ch := chapter{ID: row.ChapterID, Title: row.ChapterTitle, Sections: []section{}}
p.Chapters = append(p.Chapters, ch)
chapterMap[row.PartID][row.ChapterID] = &p.Chapters[len(p.Chapters)-1]
}
ch := chapterMap[row.PartID][row.ChapterID]
price := 1.0
if row.Price != nil {
price = *row.Price
}
isFree := false
if row.IsFree != nil {
isFree = *row.IsFree
}
st := "published"
if row.Status != nil {
st = *row.Status
}
ch.Sections = append(ch.Sections, section{
ID: row.ID, Title: row.SectionTitle, Price: price, IsFree: isFree, Status: st,
EditionStandard: row.EditionStandard, EditionPremium: row.EditionPremium,
})
}
structure := make([]part, 0, len(partMap))
for _, p := range partMap {
structure = append(structure, *p)
}
var total int64
database.DB().Model(&model.Chapter{}).Count(&total)
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{"structure": structure, "stats": gin.H{"totalSections": total}},
})
}
// AdminChaptersAction POST/PUT/DELETE /api/admin/chapters
func AdminChaptersAction(c *gin.Context) {
var body struct {
Action string `json:"action"`
ID string `json:"id"`
ChapterID string `json:"chapterId"` // 前端兼容section id
SectionTitle string `json:"sectionTitle"`
Ids []string `json:"ids"` // reorder新顺序的 section id 列表
Price *float64 `json:"price"`
IsFree *bool `json:"isFree"`
Status *string `json:"status"`
EditionStandard *bool `json:"editionStandard"`
EditionPremium *bool `json:"editionPremium"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请求体无效"})
return
}
resolveID := func() string {
if body.ID != "" {
return body.ID
}
return body.ChapterID
}
db := database.DB()
if body.Action == "updatePrice" {
id := resolveID()
if id != "" && body.Price != nil {
db.Model(&model.Chapter{}).Where("id = ?", id).Update("price", *body.Price)
}
}
if body.Action == "toggleFree" {
id := resolveID()
if id != "" && body.IsFree != nil {
db.Model(&model.Chapter{}).Where("id = ?", id).Update("is_free", *body.IsFree)
}
}
if body.Action == "updateStatus" {
id := resolveID()
if id != "" && body.Status != nil {
db.Model(&model.Chapter{}).Where("id = ?", id).Update("status", *body.Status)
}
}
if body.Action == "rename" {
id := resolveID()
if id != "" && body.SectionTitle != "" {
db.Model(&model.Chapter{}).Where("id = ?", id).Update("section_title", body.SectionTitle)
}
}
if body.Action == "delete" {
id := resolveID()
if id != "" {
cache.InvalidateChapterContentByID(id)
db.Where("id = ?", id).Delete(&model.Chapter{})
}
}
if body.Action == "reorder" && len(body.Ids) > 0 {
for i, id := range body.Ids {
if id != "" {
db.Model(&model.Chapter{}).Where("id = ?", id).Update("sort_order", i)
}
}
}
if body.Action == "updateEdition" {
id := resolveID()
if id != "" {
updates := make(map[string]interface{})
if body.EditionStandard != nil {
updates["edition_standard"] = *body.EditionStandard
}
if body.EditionPremium != nil {
updates["edition_premium"] = *body.EditionPremium
}
if len(updates) > 0 {
db.Model(&model.Chapter{}).Where("id = ?", id).Updates(updates)
}
}
}
cache.InvalidateBookParts()
InvalidateChaptersByPartCache()
cache.InvalidateBookCache()
c.JSON(http.StatusOK, gin.H{"success": true})
}