加强了多个组件的预览百分比处理。

在 API 中新增了章节专属的预览百分比,并更新了相关模型和处理器。
改进了阅读场景下确定有效预览百分比的逻辑。
更新了小程序配置,加入了新的阅读页面入口。
This commit is contained in:
Alex-larget
2026-03-25 17:21:55 +08:00
parent d8362bc7a9
commit ae7402fafa
9 changed files with 233 additions and 82 deletions

View File

@@ -41,7 +41,7 @@ func naturalLessSectionID(a, b string) bool {
var listSelectCols = []string{
"id", "mid", "section_title", "price", "is_free", "is_new",
"part_id", "part_title", "chapter_id", "chapter_title", "sort_order",
"hot_score", "updated_at",
"hot_score", "preview_percent", "updated_at",
}
// sectionListItem 与前端 SectionListItem 一致(小写驼峰)
@@ -60,7 +60,8 @@ type sectionListItem struct {
ClickCount int64 `json:"clickCount"` // 阅读次数reading_progress
PayCount int64 `json:"payCount"` // 付款笔数orders.product_type=section
HotScore float64 `json:"hotScore"` // 热度积分(加权计算)
IsPinned bool `json:"isPinned,omitempty"` // 是否置顶(仅 ranking 返回)
IsPinned bool `json:"isPinned,omitempty"` // 是否置顶(仅 ranking 返回)
PreviewPercent *int `json:"previewPercent,omitempty"`
}
// computeSectionListWithHotScore 计算章节列表(含 hotScore保持 sort_order 顺序,供 章节管理 树使用
@@ -264,19 +265,20 @@ func computeSectionsWithHotScore(db *gorm.DB, setPinned bool) ([]sectionListItem
hot = float64(r.HotScore)
}
item := sectionListItem{
ID: r.ID,
MID: r.MID,
Title: r.SectionTitle,
Price: price,
IsFree: r.IsFree,
IsNew: r.IsNew,
PartID: r.PartID,
PartTitle: r.PartTitle,
ChapterID: r.ChapterID,
ChapterTitle: r.ChapterTitle,
ClickCount: readCnt,
PayCount: payCnt,
HotScore: hot,
ID: r.ID,
MID: r.MID,
Title: r.SectionTitle,
Price: price,
IsFree: r.IsFree,
IsNew: r.IsNew,
PartID: r.PartID,
PartTitle: r.PartTitle,
ChapterID: r.ChapterID,
ChapterTitle: r.ChapterTitle,
ClickCount: readCnt,
PayCount: payCnt,
HotScore: hot,
PreviewPercent: r.PreviewPercent,
}
if setPinned {
item.IsPinned = pinnedSet[r.ID]
@@ -286,6 +288,42 @@ func computeSectionsWithHotScore(db *gorm.DB, setPinned bool) ([]sectionListItem
return sections, nil
}
// dbBookReadSectionOut 管理端 read 详情previewPercent 必须始终出现在 JSONnull=走全局),避免 gin.H+nil 被序列化省略
type dbBookReadSectionOut struct {
ID string `json:"id"`
Title string `json:"title"`
Price float64 `json:"price"`
Content string `json:"content"`
IsNew *bool `json:"isNew,omitempty"`
PartID string `json:"partId"`
PartTitle string `json:"partTitle"`
ChapterID string `json:"chapterId"`
ChapterTitle string `json:"chapterTitle"`
EditionStandard *bool `json:"editionStandard,omitempty"`
EditionPremium *bool `json:"editionPremium,omitempty"`
PreviewPercent *int `json:"previewPercent"` // 禁止 omitempty与 chapters 表 preview_percent 对齐
}
func chapterToReadSectionOut(ch *model.Chapter, price float64) dbBookReadSectionOut {
if ch == nil {
return dbBookReadSectionOut{Price: price}
}
return dbBookReadSectionOut{
ID: ch.ID,
Title: ch.SectionTitle,
Price: price,
Content: ch.Content,
IsNew: ch.IsNew,
PartID: ch.PartID,
PartTitle: ch.PartTitle,
ChapterID: ch.ChapterID,
ChapterTitle: ch.ChapterTitle,
EditionStandard: ch.EditionStandard,
EditionPremium: ch.EditionPremium,
PreviewPercent: ch.PreviewPercent,
}
}
// DBBookAction GET/POST/PUT /api/db/book
func DBBookAction(c *gin.Context) {
db := database.DB()
@@ -336,19 +374,7 @@ func DBBookAction(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"section": gin.H{
"id": ch.ID,
"title": ch.SectionTitle,
"price": price,
"content": ch.Content,
"isNew": ch.IsNew,
"partId": ch.PartID,
"partTitle": ch.PartTitle,
"chapterId": ch.ChapterID,
"chapterTitle": ch.ChapterTitle,
"editionStandard": ch.EditionStandard,
"editionPremium": ch.EditionPremium,
},
"section": chapterToReadSectionOut(&ch, price),
})
return
}
@@ -371,19 +397,7 @@ func DBBookAction(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"section": gin.H{
"id": ch.ID,
"title": ch.SectionTitle,
"price": price,
"content": ch.Content,
"isNew": ch.IsNew,
"partId": ch.PartID,
"partTitle": ch.PartTitle,
"chapterId": ch.ChapterID,
"chapterTitle": ch.ChapterTitle,
"editionStandard": ch.EditionStandard,
"editionPremium": ch.EditionPremium,
},
"section": chapterToReadSectionOut(&ch, price),
})
return
case "section-orders":
@@ -536,6 +550,7 @@ func DBBookAction(c *gin.Context) {
ChapterID string `json:"chapterId"`
ChapterTitle string `json:"chapterTitle"`
HotScore *float64 `json:"hotScore"`
PreviewPercent nullablePreviewPercentJSON `json:"previewPercent"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请求体无效"})
@@ -703,6 +718,20 @@ func DBBookAction(c *gin.Context) {
if body.ChapterTitle != "" {
updates["chapter_title"] = body.ChapterTitle
}
if body.PreviewPercent.Set {
if body.PreviewPercent.Val == nil {
updates["preview_percent"] = nil
} else {
p := *body.PreviewPercent.Val
if p < 1 {
p = 1
}
if p > 100 {
p = 100
}
updates["preview_percent"] = p
}
}
var existing model.Chapter
err = db.Where("id = ?", body.ID).First(&existing).Error
if err == gorm.ErrRecordNotFound {
@@ -748,6 +777,16 @@ func DBBookAction(c *gin.Context) {
if body.IsNew != nil {
ch.IsNew = body.IsNew
}
if body.PreviewPercent.Set && body.PreviewPercent.Val != nil {
p := *body.PreviewPercent.Val
if p < 1 {
p = 1
}
if p > 100 {
p = 100
}
ch.PreviewPercent = &p
}
if err := db.Create(&ch).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
@@ -812,6 +851,26 @@ func DBBookAction(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "不支持的请求方法"})
}
// nullablePreviewPercentJSON 区分JSON 未传 key不改 preview_percent、null清空用全局、数字章节覆盖
type nullablePreviewPercentJSON struct {
Set bool
Val *int
}
func (n *nullablePreviewPercentJSON) UnmarshalJSON(data []byte) error {
n.Set = true
if string(data) == "null" {
n.Val = nil
return nil
}
var v int
if err := json.Unmarshal(data, &v); err != nil {
return err
}
n.Val = &v
return nil
}
type reorderItem struct {
ID string `json:"id"`
PartID string `json:"partId"`