加强了多个组件的预览百分比处理。
在 API 中新增了章节专属的预览百分比,并更新了相关模型和处理器。 改进了阅读场景下确定有效预览百分比的逻辑。 更新了小程序配置,加入了新的阅读页面入口。
This commit is contained in:
@@ -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 必须始终出现在 JSON(null=走全局),避免 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"`
|
||||
|
||||
Reference in New Issue
Block a user