Enhance mini program environment configuration and chapter loading logic
- Introduced a debugging environment configuration in app.js, allowing for dynamic API endpoint selection based on the environment. - Implemented lazy loading for chapter parts in chapters.js, improving performance by only fetching necessary data. - Updated UI elements in chapters.wxml to reflect changes in chapter loading and added loading indicators. - Enhanced error handling and data management for chapter retrieval, ensuring a smoother user experience. - Added functionality for switching API environments in settings.js, visible only in development mode.
This commit is contained in:
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -19,6 +20,23 @@ import (
|
||||
// excludeParts 排除序言、尾声、附录(不参与精选推荐/热门排序)
|
||||
var excludeParts = []string{"序言", "尾声", "附录"}
|
||||
|
||||
// sortChaptersByNaturalID 同 sort_order 时按 id 自然排序(9.1 < 9.2 < 9.10),调用 db_book 的 naturalLessSectionID
|
||||
func sortChaptersByNaturalID(list []model.Chapter) {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
soI, soJ := 999999, 999999
|
||||
if list[i].SortOrder != nil {
|
||||
soI = *list[i].SortOrder
|
||||
}
|
||||
if list[j].SortOrder != nil {
|
||||
soJ = *list[j].SortOrder
|
||||
}
|
||||
if soI != soJ {
|
||||
return soI < soJ
|
||||
}
|
||||
return naturalLessSectionID(list[i].ID, list[j].ID)
|
||||
})
|
||||
}
|
||||
|
||||
// allChaptersSelectCols 列表不加载 content(longtext),避免 502 超时
|
||||
var allChaptersSelectCols = []string{
|
||||
"mid", "id", "part_id", "part_title", "chapter_id", "chapter_title",
|
||||
@@ -44,6 +62,7 @@ func WarmAllChaptersCache() {
|
||||
if err := q.Order("COALESCE(sort_order, 999999) ASC, id ASC").Find(&list).Error; err != nil {
|
||||
return
|
||||
}
|
||||
sortChaptersByNaturalID(list)
|
||||
freeIDs := getFreeChapterIDs(db)
|
||||
for i := range list {
|
||||
if freeIDs[list[i].ID] {
|
||||
@@ -91,6 +110,7 @@ func BookAllChapters(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}})
|
||||
return
|
||||
}
|
||||
sortChaptersByNaturalID(list)
|
||||
freeIDs := getFreeChapterIDs(db)
|
||||
for i := range list {
|
||||
if freeIDs[list[i].ID] {
|
||||
@@ -122,6 +142,112 @@ func BookChapterByID(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// BookParts GET /api/miniprogram/book/parts 目录懒加载:仅返回篇章列表,不含章节详情
|
||||
// 返回 parts(排除序言/尾声/附录)、totalSections、fixedSections(id, mid, title 供序言/尾声/附录跳转用 mid)
|
||||
func BookParts(c *gin.Context) {
|
||||
db := database.DB()
|
||||
// 固定模块(序言、尾声、附录)的 id、mid、title,供 goToRead 传 data-mid
|
||||
var fixedList []struct {
|
||||
ID string `json:"id"`
|
||||
MID int `json:"mid"`
|
||||
SectionTitle string `json:"title"`
|
||||
}
|
||||
for _, p := range excludeParts {
|
||||
var rows []model.Chapter
|
||||
if err := db.Model(&model.Chapter{}).Select("id", "mid", "section_title", "sort_order").
|
||||
Where("part_title LIKE ?", "%"+p+"%").
|
||||
Order("COALESCE(sort_order, 999999) ASC, id ASC").
|
||||
Find(&rows).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
sortChaptersByNaturalID(rows)
|
||||
for _, r := range rows {
|
||||
fixedList = append(fixedList, struct {
|
||||
ID string `json:"id"`
|
||||
MID int `json:"mid"`
|
||||
SectionTitle string `json:"title"`
|
||||
}{r.ID, r.MID, r.SectionTitle})
|
||||
}
|
||||
}
|
||||
|
||||
// 中间篇章:轻量聚合,不拉取 content
|
||||
type partRow struct {
|
||||
PartID string `json:"id"`
|
||||
PartTitle string `json:"title"`
|
||||
Subtitle string `json:"subtitle"`
|
||||
ChapterCount int `json:"chapterCount"`
|
||||
MinSortOrder int `json:"minSortOrder"`
|
||||
}
|
||||
where := "1=1"
|
||||
args := []interface{}{}
|
||||
for _, p := range excludeParts {
|
||||
where += " AND part_title NOT LIKE ?"
|
||||
args = append(args, "%"+p+"%")
|
||||
}
|
||||
var raw []struct {
|
||||
PartID string `gorm:"column:part_id"`
|
||||
PartTitle string `gorm:"column:part_title"`
|
||||
Subtitle string `gorm:"column:subtitle"`
|
||||
ChapterCount int `gorm:"column:chapter_count"`
|
||||
MinSortOrder int `gorm:"column:min_sort"`
|
||||
}
|
||||
sql := `SELECT part_id, part_title, '' as subtitle,
|
||||
COUNT(DISTINCT chapter_id) as chapter_count,
|
||||
MIN(COALESCE(sort_order, 999999)) as min_sort
|
||||
FROM chapters WHERE ` + where + `
|
||||
GROUP BY part_id, part_title ORDER BY min_sort ASC, part_id ASC`
|
||||
if err := db.Raw(sql, args...).Scan(&raw).Error; err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "parts": []interface{}{}, "totalSections": 0, "fixedSections": fixedList})
|
||||
return
|
||||
}
|
||||
parts := make([]partRow, len(raw))
|
||||
for i, r := range raw {
|
||||
parts[i] = partRow{
|
||||
PartID: r.PartID, PartTitle: r.PartTitle, Subtitle: r.Subtitle,
|
||||
ChapterCount: r.ChapterCount, MinSortOrder: r.MinSortOrder,
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
db.Model(&model.Chapter{}).Count(&total)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"parts": parts,
|
||||
"totalSections": total,
|
||||
"fixedSections": fixedList,
|
||||
})
|
||||
}
|
||||
|
||||
// BookChaptersByPart GET /api/miniprogram/book/chapters-by-part?partId=xxx 按篇章返回章节列表(含 mid,供阅读页 by-mid 请求)
|
||||
func BookChaptersByPart(c *gin.Context) {
|
||||
partId := c.Query("partId")
|
||||
if partId == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 partId"})
|
||||
return
|
||||
}
|
||||
db := database.DB()
|
||||
var list []model.Chapter
|
||||
if err := db.Model(&model.Chapter{}).Select(allChaptersSelectCols).
|
||||
Where("part_id = ?", partId).
|
||||
Order("COALESCE(sort_order, 999999) ASC, id ASC").
|
||||
Find(&list).Error; err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}})
|
||||
return
|
||||
}
|
||||
sortChaptersByNaturalID(list)
|
||||
freeIDs := getFreeChapterIDs(db)
|
||||
for i := range list {
|
||||
if freeIDs[list[i].ID] {
|
||||
t := true
|
||||
z := float64(0)
|
||||
list[i].IsFree = &t
|
||||
list[i].Price = &z
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": list})
|
||||
}
|
||||
|
||||
// BookChapterByMID GET /api/book/chapter/by-mid/:mid 按自增主键 mid 查询(新链接推荐)
|
||||
func BookChapterByMID(c *gin.Context) {
|
||||
midStr := c.Param("mid")
|
||||
@@ -391,6 +517,7 @@ func bookHotChaptersSorted(db *gorm.DB, limit int) []model.Chapter {
|
||||
if err := q.Order("sort_order ASC, id ASC").Find(&all).Error; err != nil || len(all) == 0 {
|
||||
return nil
|
||||
}
|
||||
sortChaptersByNaturalID(all)
|
||||
// 从 reading_progress 统计阅读量
|
||||
ids := make([]string, 0, len(all))
|
||||
for _, c := range all {
|
||||
@@ -440,6 +567,7 @@ func BookHot(c *gin.Context) {
|
||||
q = q.Where("part_title NOT LIKE ?", "%"+p+"%")
|
||||
}
|
||||
q.Order("sort_order ASC, id ASC").Limit(10).Find(&list)
|
||||
sortChaptersByNaturalID(list)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": list})
|
||||
}
|
||||
@@ -491,6 +619,12 @@ func BookLatestChapters(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": []interface{}{}})
|
||||
return
|
||||
}
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if !list[i].UpdatedAt.Equal(list[j].UpdatedAt) {
|
||||
return list[i].UpdatedAt.After(list[j].UpdatedAt)
|
||||
}
|
||||
return naturalLessSectionID(list[i].ID, list[j].ID)
|
||||
})
|
||||
freeIDs := getFreeChapterIDs(db)
|
||||
for i := range list {
|
||||
if freeIDs[list[i].ID] {
|
||||
@@ -528,6 +662,7 @@ func BookSearch(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "results": []interface{}{}, "total": 0, "keyword": q})
|
||||
return
|
||||
}
|
||||
sortChaptersByNaturalID(list)
|
||||
lowerQ := strings.ToLower(q)
|
||||
results := make([]gin.H, 0, len(list))
|
||||
for _, ch := range list {
|
||||
|
||||
Reference in New Issue
Block a user