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:
@@ -5,12 +5,42 @@
|
||||
|
||||
const { parseScene } = require('./utils/scene.js')
|
||||
|
||||
// 调试环境配置:''=自动 | localhost | souldev | soulapi
|
||||
const DEBUG_ENV_OPTIONS = [
|
||||
{ key: '', label: '自动', url: null },
|
||||
{ key: 'localhost', label: '本地', url: 'http://localhost:8080' },
|
||||
{ key: 'souldev', label: '测试', url: 'https://souldev.quwanzhi.com' },
|
||||
{ key: 'soulapi', label: '正式', url: 'https://soulapi.quwanzhi.com' }
|
||||
]
|
||||
const STORAGE_KEY_DEBUG_ENV = 'debug_env_override'
|
||||
|
||||
// 根据小程序环境版本或调试覆盖选择 API 地址
|
||||
function getBaseUrlByEnv(override) {
|
||||
if (override) {
|
||||
const opt = DEBUG_ENV_OPTIONS.find(o => o.key === override)
|
||||
if (opt && opt.url) return opt.url
|
||||
}
|
||||
try {
|
||||
const accountInfo = wx.getAccountInfoSync()
|
||||
const env = accountInfo?.miniProgram?.envVersion || 'release'
|
||||
const urls = {
|
||||
develop: 'http://localhost:8080', // 开发版(本地调试)
|
||||
trial: 'https://souldev.quwanzhi.com', // 体验版
|
||||
release: 'https://soulapi.quwanzhi.com' // 正式版
|
||||
}
|
||||
return urls[env] || urls.release
|
||||
} catch (e) {
|
||||
console.warn('[App] 获取环境失败,使用正式版:', e)
|
||||
return 'https://soulapi.quwanzhi.com'
|
||||
}
|
||||
}
|
||||
|
||||
App({
|
||||
globalData: {
|
||||
// API基础地址 - 连接真实后端
|
||||
// baseUrl: 'https://soulapi.quwanzhi.com',
|
||||
// baseUrl: 'https://souldev.quwanzhi.com',
|
||||
baseUrl: 'http://localhost:8080',
|
||||
// API基础地址 - 由 getBaseUrlByEnv() 在 onLaunch 时按环境自动设置
|
||||
baseUrl: '',
|
||||
// 调试环境覆盖:'' | localhost | souldev | soulapi,持久化到 storage
|
||||
debugEnvOverride: '',
|
||||
|
||||
|
||||
// 小程序配置 - 真实AppID
|
||||
@@ -70,6 +100,8 @@ App({
|
||||
},
|
||||
|
||||
onLaunch(options) {
|
||||
this.globalData.debugEnvOverride = wx.getStorageSync(STORAGE_KEY_DEBUG_ENV) || ''
|
||||
this.globalData.baseUrl = getBaseUrlByEnv(this.globalData.debugEnvOverride)
|
||||
this.globalData.readSectionIds = wx.getStorageSync('readSectionIds') || []
|
||||
// 获取系统信息
|
||||
this.getSystemInfo()
|
||||
@@ -248,6 +280,28 @@ App({
|
||||
return ''
|
||||
},
|
||||
|
||||
// 调试:获取当前 API 环境信息
|
||||
getDebugEnvInfo() {
|
||||
const override = this.globalData.debugEnvOverride || ''
|
||||
const opt = DEBUG_ENV_OPTIONS.find(o => o.key === override)
|
||||
return {
|
||||
override,
|
||||
label: opt ? opt.label : '自动',
|
||||
baseUrl: this.globalData.baseUrl
|
||||
}
|
||||
},
|
||||
|
||||
// 调试:一键切换 API 环境(自动→本地→测试→正式→自动),持久化到 storage
|
||||
switchDebugEnv() {
|
||||
const idx = DEBUG_ENV_OPTIONS.findIndex(o => o.key === this.globalData.debugEnvOverride)
|
||||
const nextIdx = (idx + 1) % DEBUG_ENV_OPTIONS.length
|
||||
const next = DEBUG_ENV_OPTIONS[nextIdx]
|
||||
this.globalData.debugEnvOverride = next.key
|
||||
this.globalData.baseUrl = getBaseUrlByEnv(next.key)
|
||||
wx.setStorageSync(STORAGE_KEY_DEBUG_ENV, next.key)
|
||||
return next
|
||||
},
|
||||
|
||||
/**
|
||||
* 自定义导航栏「返回」:有上一页则返回,否则跳转首页(解决从分享进入时点返回无效的问题)
|
||||
*/
|
||||
|
||||
@@ -19,13 +19,19 @@ Page({
|
||||
isVip: false,
|
||||
purchasedSections: [],
|
||||
|
||||
// 书籍数据:以后台内容管理为准,仅用接口 /api/miniprogram/book/all-chapters 返回的数据
|
||||
// 懒加载:篇章列表(不含章节详情),展开时再请求 chapters-by-part
|
||||
totalSections: 0,
|
||||
bookData: [],
|
||||
|
||||
// 展开状态:默认不展开任何篇章,直接显示目录
|
||||
// 展开状态
|
||||
expandedPart: null,
|
||||
|
||||
// 已加载的篇章章节缓存 { partId: chapters }
|
||||
_loadedChapters: {},
|
||||
|
||||
// 固定模块 id -> mid(序言/尾声/附录,供 goToRead 传 mid)
|
||||
fixedSectionsMap: {},
|
||||
|
||||
// 附录
|
||||
appendixList: [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话' },
|
||||
@@ -33,7 +39,7 @@ Page({
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源' }
|
||||
],
|
||||
|
||||
// 每日新增章节
|
||||
// 每日新增章节(懒加载后暂无,可后续用 latest-chapters 补充)
|
||||
dailyChapters: []
|
||||
},
|
||||
|
||||
@@ -45,73 +51,71 @@ Page({
|
||||
})
|
||||
this.updateUserStatus()
|
||||
this.loadVipStatus()
|
||||
this.loadChaptersOnce()
|
||||
this.loadParts()
|
||||
},
|
||||
|
||||
// 固定模块(序言、尾声、附录)不参与中间篇章
|
||||
_isFixedPart(pt) {
|
||||
if (!pt) return false
|
||||
const p = String(pt).toLowerCase().replace(/[_\s||]/g, '')
|
||||
return p.includes('序言') || p.includes('尾声') || p.includes('附录')
|
||||
},
|
||||
|
||||
// 一次请求拉取全量目录,以后台内容管理为准;同时更新 totalSections / bookData / dailyChapters
|
||||
async loadChaptersOnce() {
|
||||
// 懒加载:仅拉取篇章列表 + totalSections + fixedSections
|
||||
async loadParts() {
|
||||
try {
|
||||
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
|
||||
const rows = (res && res.data) || (res && res.chapters) || []
|
||||
|
||||
// 无数据时清空目录,避免展示旧数据
|
||||
if (rows.length === 0) {
|
||||
app.globalData.bookData = []
|
||||
wx.setStorageSync('bookData', [])
|
||||
this.setData({
|
||||
bookData: [],
|
||||
totalSections: 0,
|
||||
dailyChapters: [],
|
||||
expandedPart: null
|
||||
})
|
||||
const res = await app.request({ url: '/api/miniprogram/book/parts', silent: true })
|
||||
if (!res?.success) {
|
||||
this.setData({ bookData: [], totalSections: 0 })
|
||||
return
|
||||
}
|
||||
|
||||
const totalSections = res.total ?? rows.length
|
||||
app.globalData.bookData = rows
|
||||
wx.setStorageSync('bookData', rows)
|
||||
|
||||
// bookData:过滤序言/尾声/附录,按 part 聚合,篇章顺序按 sort_order 与后台一致(含「2026每日派对干货」等)
|
||||
const filtered = rows.filter(r => !this._isFixedPart(r.partTitle || r.part_title))
|
||||
const partMap = new Map()
|
||||
const parts = res.parts || []
|
||||
const totalSections = res.totalSections ?? 0
|
||||
const fixedSections = res.fixedSections || []
|
||||
const numbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
|
||||
filtered.forEach((r) => {
|
||||
const pid = r.partId || r.part_id || 'part-1'
|
||||
const fixedMap = {}
|
||||
fixedSections.forEach(f => { fixedMap[f.id] = f.mid })
|
||||
const appendixList = [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话', mid: fixedMap['appendix-1'] },
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单', mid: fixedMap['appendix-2'] },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源', mid: fixedMap['appendix-3'] }
|
||||
]
|
||||
const bookData = parts.map((p, idx) => ({
|
||||
id: p.id,
|
||||
number: numbers[idx] || String(idx + 1),
|
||||
title: p.title,
|
||||
subtitle: p.subtitle || '',
|
||||
chapterCount: p.chapterCount || 0,
|
||||
chapters: [] // 展开时懒加载
|
||||
}))
|
||||
app.globalData.totalSections = totalSections
|
||||
this.setData({
|
||||
bookData,
|
||||
totalSections,
|
||||
fixedSectionsMap: fixedMap,
|
||||
appendixList,
|
||||
_loadedChapters: {}
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('[Chapters] 加载篇章失败:', e)
|
||||
this.setData({ bookData: [], totalSections: 0 })
|
||||
}
|
||||
},
|
||||
|
||||
// 展开时懒加载该篇章的章节(含 mid,供阅读页 by-mid 请求)
|
||||
async loadChaptersByPart(partId) {
|
||||
if (this.data._loadedChapters[partId]) return
|
||||
try {
|
||||
const res = await app.request({
|
||||
url: `/api/miniprogram/book/chapters-by-part?partId=${encodeURIComponent(partId)}`,
|
||||
silent: true
|
||||
})
|
||||
const rows = (res && res.data) || []
|
||||
const chMap = new Map()
|
||||
rows.forEach(r => {
|
||||
const cid = r.chapterId || r.chapter_id || 'chapter-1'
|
||||
const sortOrder = r.sectionOrder ?? r.sort_order ?? 999999
|
||||
if (!partMap.has(pid)) {
|
||||
const partIdx = partMap.size
|
||||
partMap.set(pid, {
|
||||
id: pid,
|
||||
number: numbers[partIdx] || String(partIdx + 1),
|
||||
title: r.partTitle || r.part_title || '未分类',
|
||||
subtitle: r.chapterTitle || r.chapter_title || '',
|
||||
chapters: new Map(),
|
||||
minSortOrder: sortOrder
|
||||
})
|
||||
}
|
||||
const part = partMap.get(pid)
|
||||
if (sortOrder < part.minSortOrder) part.minSortOrder = sortOrder
|
||||
if (!part.chapters.has(cid)) {
|
||||
part.chapters.set(cid, {
|
||||
if (!chMap.has(cid)) {
|
||||
chMap.set(cid, {
|
||||
id: cid,
|
||||
title: r.chapterTitle || r.chapter_title || '未分类',
|
||||
sections: []
|
||||
})
|
||||
}
|
||||
const ch = part.chapters.get(cid)
|
||||
const isPremium =
|
||||
r.editionPremium === true ||
|
||||
r.edition_premium === true ||
|
||||
r.edition_premium === 1 ||
|
||||
r.edition_premium === '1'
|
||||
const ch = chMap.get(cid)
|
||||
const isPremium = r.editionPremium === true || r.edition_premium === true || r.edition_premium === 1 || r.edition_premium === '1'
|
||||
ch.sections.push({
|
||||
id: r.id,
|
||||
mid: r.mid ?? r.MID ?? 0,
|
||||
@@ -122,46 +126,27 @@ Page({
|
||||
isPremium
|
||||
})
|
||||
})
|
||||
const partList = Array.from(partMap.values())
|
||||
partList.sort((a, b) => (a.minSortOrder ?? 999999) - (b.minSortOrder ?? 999999))
|
||||
const bookData = partList.map((p, idx) => ({
|
||||
id: p.id,
|
||||
number: numbers[idx] || String(idx + 1),
|
||||
title: p.title,
|
||||
subtitle: p.subtitle,
|
||||
chapters: Array.from(p.chapters.values())
|
||||
}))
|
||||
|
||||
const baseSort = 62
|
||||
const daily = rows
|
||||
.filter(r => (r.sectionOrder ?? r.sort_order ?? 0) > baseSort)
|
||||
.sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0))
|
||||
.slice(0, 20)
|
||||
.map(c => {
|
||||
const d = new Date(c.updatedAt || c.updated_at || Date.now())
|
||||
return {
|
||||
id: c.id,
|
||||
mid: c.mid ?? c.MID ?? 0,
|
||||
title: c.section_title || c.title || c.sectionTitle,
|
||||
price: c.price ?? 1,
|
||||
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
|
||||
}
|
||||
})
|
||||
|
||||
this.setData({
|
||||
bookData,
|
||||
totalSections,
|
||||
dailyChapters: daily,
|
||||
expandedPart: this.data.expandedPart
|
||||
const chapters = Array.from(chMap.values())
|
||||
const loaded = { ...this.data._loadedChapters, [partId]: chapters }
|
||||
const bookData = this.data.bookData.map(p =>
|
||||
p.id === partId ? { ...p, chapters } : p
|
||||
)
|
||||
const bookDataFlat = app.globalData.bookData || []
|
||||
rows.forEach(r => {
|
||||
const idx = bookDataFlat.findIndex(c => c.id === r.id)
|
||||
if (idx >= 0) bookDataFlat[idx] = { ...bookDataFlat[idx], ...r }
|
||||
else bookDataFlat.push(r)
|
||||
})
|
||||
app.globalData.bookData = bookDataFlat
|
||||
wx.setStorageSync('bookData', bookDataFlat)
|
||||
this.setData({ bookData, _loadedChapters: loaded })
|
||||
} catch (e) {
|
||||
console.log('[Chapters] 加载目录失败:', e)
|
||||
this.setData({ bookData: [], totalSections: 0 })
|
||||
console.log('[Chapters] 加载章节失败:', e)
|
||||
}
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadChaptersOnce().then(() => wx.stopPullDownRefresh()).catch(() => wx.stopPullDownRefresh())
|
||||
this.loadParts().then(() => wx.stopPullDownRefresh()).catch(() => wx.stopPullDownRefresh())
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -204,12 +189,14 @@ Page({
|
||||
this.setData({ isLoggedIn, hasFullBook, purchasedSections, isVip })
|
||||
},
|
||||
|
||||
// 切换展开状态
|
||||
togglePart(e) {
|
||||
// 切换展开状态,展开时懒加载该篇章章节
|
||||
async togglePart(e) {
|
||||
const partId = e.currentTarget.dataset.id
|
||||
const isExpanding = this.data.expandedPart !== partId
|
||||
this.setData({
|
||||
expandedPart: this.data.expandedPart === partId ? null : partId
|
||||
expandedPart: isExpanding ? partId : null
|
||||
})
|
||||
if (isExpanding) await this.loadChaptersByPart(partId)
|
||||
},
|
||||
|
||||
// 跳转到阅读页(优先传 mid,与分享逻辑一致)
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
|
||||
<!-- 目录内容 -->
|
||||
<view class="chapters-content">
|
||||
<!-- 序言 -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="preface">
|
||||
<!-- 序言(优先传 mid,阅读页用 by-mid 请求) -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="preface" data-mid="{{fixedSectionsMap.preface}}">
|
||||
<view class="item-left">
|
||||
<view class="item-icon icon-brand">📖</view>
|
||||
<text class="item-title">序言|为什么我每天早上6点在Soul开播?</text>
|
||||
@@ -59,14 +59,15 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="part-right">
|
||||
<text class="part-count">{{item.chapters.length}}章</text>
|
||||
<text class="part-count">{{item.chapters.length || item.chapterCount}}章</text>
|
||||
<text class="part-arrow {{expandedPart === item.id ? 'arrow-down' : ''}}">→</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 章节列表 - 展开时显示 -->
|
||||
<!-- 章节列表 - 展开时显示,懒加载 -->
|
||||
<block wx:if="{{expandedPart === item.id}}">
|
||||
<view class="chapters-list">
|
||||
<view wx:if="{{item.chapters.length === 0}}" class="chapters-loading">加载中...</view>
|
||||
<block wx:for="{{item.chapters}}" wx:key="id" wx:for-item="chapter">
|
||||
<view class="chapter-header">{{chapter.title}}</view>
|
||||
<view class="section-list">
|
||||
@@ -92,8 +93,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 尾声 -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="epilogue">
|
||||
<!-- 尾声(优先传 mid) -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="epilogue" data-mid="{{fixedSectionsMap.epilogue}}">
|
||||
<view class="item-left">
|
||||
<view class="item-icon icon-brand">📖</view>
|
||||
<text class="item-title">尾声|这本书的真实目的</text>
|
||||
@@ -114,6 +115,7 @@
|
||||
wx:key="id"
|
||||
bindtap="goToRead"
|
||||
data-id="{{item.id}}"
|
||||
data-mid="{{item.mid}}"
|
||||
>
|
||||
<text class="appendix-text">{{item.title}}</text>
|
||||
<text class="appendix-arrow">→</text>
|
||||
|
||||
@@ -339,6 +339,12 @@
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.chapters-loading {
|
||||
padding: 24rpx;
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.chapter-group {
|
||||
background: rgba(28, 28, 30, 0.5);
|
||||
border-radius: 16rpx;
|
||||
|
||||
@@ -744,9 +744,9 @@ Page({
|
||||
wx.switchTab({ url: '/pages/chapters/chapters' })
|
||||
},
|
||||
|
||||
// 打开设置
|
||||
// 打开资料修改页(找伙伴右上角图标)
|
||||
openSettings() {
|
||||
wx.navigateTo({ url: '/pages/settings/settings' })
|
||||
wx.navigateTo({ url: '/pages/profile-edit/profile-edit' })
|
||||
},
|
||||
|
||||
// 阻止事件冒泡
|
||||
|
||||
@@ -73,13 +73,20 @@ Page({
|
||||
contactWechat: '',
|
||||
contactSaving: false,
|
||||
pendingWithdraw: false,
|
||||
|
||||
// 设置入口:开发版、体验版显示
|
||||
showSettingsEntry: false,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
wx.showShareMenu({ withShareTimeline: true })
|
||||
const accountInfo = wx.getAccountInfoSync ? wx.getAccountInfoSync() : null
|
||||
const envVersion = accountInfo?.miniProgram?.envVersion || ''
|
||||
const showSettingsEntry = envVersion === 'develop' || envVersion === 'trial'
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight,
|
||||
navBarHeight: app.globalData.navBarHeight
|
||||
navBarHeight: app.globalData.navBarHeight,
|
||||
showSettingsEntry
|
||||
})
|
||||
this.loadFeatureConfig()
|
||||
this.initUserStatus()
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
</view>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" wx:if="{{false}}" bindtap="handleMenuTap" data-id="settings">
|
||||
<view class="menu-item" wx:if="{{showSettingsEntry}}" bindtap="handleMenuTap" data-id="settings">
|
||||
<view class="menu-left">
|
||||
<view class="menu-icon-wrap icon-gray"><image class="menu-icon-img" src="/assets/icons/settings-gray.svg" mode="aspectFit"/></view>
|
||||
<text class="menu-text">设置</text>
|
||||
|
||||
@@ -76,7 +76,7 @@ Page({
|
||||
},
|
||||
|
||||
async onLoad(options) {
|
||||
wx.showShareMenu({ withShareTimeline: true })
|
||||
wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] })
|
||||
|
||||
// 预加载 linkTags、linkedMiniprograms(供 onLinkTagTap 用密钥查 appId)
|
||||
if (!app.globalData.linkTagsConfig || !app.globalData.linkedMiniprograms) {
|
||||
@@ -697,6 +697,15 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 底部「分享到朋友圈」按钮点击:微信不支持 button open-type=shareTimeline,只能通过右上角菜单分享,点击时引导用户
|
||||
onShareTimelineTap() {
|
||||
wx.showToast({
|
||||
title: '请点击右上角「...」→ 分享到朋友圈',
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
})
|
||||
},
|
||||
|
||||
// 分享到朋友圈:带文章标题,过长时截断(朋友圈卡片标题显示有限)
|
||||
onShareTimeline() {
|
||||
const { section, sectionId, sectionMid, chapterTitle } = this.data
|
||||
|
||||
@@ -85,10 +85,10 @@
|
||||
<!-- 分享操作区 -->
|
||||
<view class="action-section">
|
||||
<view class="action-row-inline">
|
||||
<button class="action-btn-inline btn-share-inline" open-type="shareTimeline">
|
||||
<view class="action-btn-inline btn-share-inline" bindtap="onShareTimelineTap">
|
||||
<text class="action-icon-small">📣</text>
|
||||
<text class="action-text-small">分享到朋友圈</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="action-btn-inline btn-poster-inline" bindtap="generatePoster">
|
||||
<text class="action-icon-small">🖼️</text>
|
||||
<text class="action-text-small">生成海报</text>
|
||||
|
||||
@@ -14,6 +14,8 @@ Page({
|
||||
|
||||
// 切换账号(开发)
|
||||
showSwitchAccountModal: false,
|
||||
// 调试环境:当前 API 环境标签
|
||||
apiEnvLabel: '自动',
|
||||
switchAccountUserId: '',
|
||||
switchAccountLoading: false,
|
||||
|
||||
@@ -36,17 +38,30 @@ Page({
|
||||
wx.showShareMenu({ withShareTimeline: true })
|
||||
const accountInfo = wx.getAccountInfoSync ? wx.getAccountInfoSync() : null
|
||||
const envVersion = accountInfo?.miniProgram?.envVersion || ''
|
||||
const envInfo = app.getDebugEnvInfo()
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight,
|
||||
isLoggedIn: app.globalData.isLoggedIn,
|
||||
userInfo: app.globalData.userInfo,
|
||||
isDevMode: envVersion === 'develop'
|
||||
isDevMode: envVersion === 'develop',
|
||||
apiEnvLabel: envInfo.label
|
||||
})
|
||||
this.loadBindingInfo()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadBindingInfo()
|
||||
if (this.data.isDevMode) {
|
||||
const envInfo = getApp().getDebugEnvInfo()
|
||||
this.setData({ apiEnvLabel: envInfo.label })
|
||||
}
|
||||
},
|
||||
|
||||
// 一键切换 API 环境(开发版)
|
||||
switchApiEnv() {
|
||||
const next = getApp().switchDebugEnv()
|
||||
this.setData({ apiEnvLabel: next.label })
|
||||
wx.showToast({ title: `已切到 ${next.label}`, icon: 'none' })
|
||||
},
|
||||
|
||||
// 加载绑定信息
|
||||
|
||||
@@ -109,6 +109,15 @@
|
||||
<text class="tip-text">提示:绑定微信号才能使用提现功能</text>
|
||||
</view>
|
||||
|
||||
<!-- 开发专用:切换 API 环境(仅开发版显示) -->
|
||||
<view class="dev-switch-card" wx:if="{{isDevMode}}" bindtap="switchApiEnv">
|
||||
<view class="dev-switch-inner">
|
||||
<text class="dev-switch-icon">🌐</text>
|
||||
<text class="dev-switch-text">切换环境</text>
|
||||
<text class="dev-switch-desc">当前:{{apiEnvLabel}}(点击循环:自动→本地→测试→正式)</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开发专用:切换账号(仅开发版显示) -->
|
||||
<view class="dev-switch-card" wx:if="{{isDevMode}}" bindtap="openSwitchAccountModal">
|
||||
<view class="dev-switch-inner">
|
||||
|
||||
779
soul-admin/dist/assets/index-7GwP_AfR.js
vendored
Normal file
779
soul-admin/dist/assets/index-7GwP_AfR.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
soul-admin/dist/assets/index-B1CMMwBM.css
vendored
Normal file
1
soul-admin/dist/assets/index-B1CMMwBM.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
soul-admin/dist/assets/index-Bd1cCYoa.css
vendored
1
soul-admin/dist/assets/index-Bd1cCYoa.css
vendored
File diff suppressed because one or more lines are too long
779
soul-admin/dist/assets/index-DJPaWrh0.js
vendored
779
soul-admin/dist/assets/index-DJPaWrh0.js
vendored
File diff suppressed because one or more lines are too long
4
soul-admin/dist/index.html
vendored
4
soul-admin/dist/index.html
vendored
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>管理后台 - Soul创业派对</title>
|
||||
<script type="module" crossorigin src="/assets/index-DJPaWrh0.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Bd1cCYoa.css">
|
||||
<script type="module" crossorigin src="/assets/index-7GwP_AfR.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B1CMMwBM.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/auth.ts","./src/api/ckb.ts","./src/api/client.ts","./src/components/richeditor.tsx","./src/components/modules/user/setvipmodal.tsx","./src/components/modules/user/userdetailmodal.tsx","./src/components/ui/pagination.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/select.tsx","./src/components/ui/slider.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/hooks/usedebounce.ts","./src/layouts/adminlayout.tsx","./src/lib/utils.ts","./src/pages/admin-users/adminuserspage.tsx","./src/pages/api-doc/apidocpage.tsx","./src/pages/author-settings/authorsettingspage.tsx","./src/pages/chapters/chapterspage.tsx","./src/pages/content/chaptertree.tsx","./src/pages/content/contentpage.tsx","./src/pages/content/personaddeditmodal.tsx","./src/pages/dashboard/dashboardpage.tsx","./src/pages/distribution/distributionpage.tsx","./src/pages/find-partner/findpartnerpage.tsx","./src/pages/find-partner/tabs/ckbconfigpanel.tsx","./src/pages/find-partner/tabs/ckbstatstab.tsx","./src/pages/find-partner/tabs/findpartnertab.tsx","./src/pages/find-partner/tabs/matchpooltab.tsx","./src/pages/find-partner/tabs/matchrecordstab.tsx","./src/pages/find-partner/tabs/mentorbookingtab.tsx","./src/pages/find-partner/tabs/mentortab.tsx","./src/pages/find-partner/tabs/resourcedockingtab.tsx","./src/pages/find-partner/tabs/teamrecruittab.tsx","./src/pages/linked-mp/linkedmppage.tsx","./src/pages/login/loginpage.tsx","./src/pages/match/matchpage.tsx","./src/pages/match-records/matchrecordspage.tsx","./src/pages/mentor-consultations/mentorconsultationspage.tsx","./src/pages/mentors/mentorspage.tsx","./src/pages/not-found/notfoundpage.tsx","./src/pages/orders/orderspage.tsx","./src/pages/payment/paymentpage.tsx","./src/pages/qrcodes/qrcodespage.tsx","./src/pages/referral-settings/referralsettingspage.tsx","./src/pages/settings/settingspage.tsx","./src/pages/site/sitepage.tsx","./src/pages/users/userspage.tsx","./src/pages/vip-roles/viprolespage.tsx","./src/pages/withdrawals/withdrawalspage.tsx","./src/utils/toast.ts"],"version":"5.6.3"}
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/auth.ts","./src/api/ckb.ts","./src/api/client.ts","./src/components/rechargealert.tsx","./src/components/richeditor.tsx","./src/components/modules/user/setvipmodal.tsx","./src/components/modules/user/userdetailmodal.tsx","./src/components/ui/pagination.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/select.tsx","./src/components/ui/slider.tsx","./src/components/ui/switch.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/hooks/usedebounce.ts","./src/layouts/adminlayout.tsx","./src/lib/utils.ts","./src/pages/admin-users/adminuserspage.tsx","./src/pages/api-doc/apidocpage.tsx","./src/pages/author-settings/authorsettingspage.tsx","./src/pages/chapters/chapterspage.tsx","./src/pages/content/chaptertree.tsx","./src/pages/content/contentpage.tsx","./src/pages/content/personaddeditmodal.tsx","./src/pages/dashboard/dashboardpage.tsx","./src/pages/distribution/distributionpage.tsx","./src/pages/find-partner/findpartnerpage.tsx","./src/pages/find-partner/tabs/ckbconfigpanel.tsx","./src/pages/find-partner/tabs/ckbstatstab.tsx","./src/pages/find-partner/tabs/findpartnertab.tsx","./src/pages/find-partner/tabs/matchpooltab.tsx","./src/pages/find-partner/tabs/matchrecordstab.tsx","./src/pages/find-partner/tabs/mentorbookingtab.tsx","./src/pages/find-partner/tabs/mentortab.tsx","./src/pages/find-partner/tabs/resourcedockingtab.tsx","./src/pages/find-partner/tabs/teamrecruittab.tsx","./src/pages/linked-mp/linkedmppage.tsx","./src/pages/login/loginpage.tsx","./src/pages/match/matchpage.tsx","./src/pages/match-records/matchrecordspage.tsx","./src/pages/mentor-consultations/mentorconsultationspage.tsx","./src/pages/mentors/mentorspage.tsx","./src/pages/not-found/notfoundpage.tsx","./src/pages/orders/orderspage.tsx","./src/pages/payment/paymentpage.tsx","./src/pages/qrcodes/qrcodespage.tsx","./src/pages/referral-settings/referralsettingspage.tsx","./src/pages/settings/settingspage.tsx","./src/pages/site/sitepage.tsx","./src/pages/users/userspage.tsx","./src/pages/vip-roles/viprolespage.tsx","./src/pages/withdrawals/withdrawalspage.tsx","./src/utils/toast.ts"],"version":"5.6.3"}
|
||||
@@ -14,9 +14,18 @@ DB_DSN=cdb_outerroot:Zhiqun1984@tcp(56b4c23f6853c.gz.cdb.myqcloud.com:14413)/sou
|
||||
# 统一 API 域名(支付回调、转账回调、apiDomain 等由此派生;无需尾部斜杠)
|
||||
API_BASE_URL=https://soulapi.quwanzhi.com
|
||||
|
||||
#添加卡若
|
||||
|
||||
#添加卡若(内部 API,用于 /v1/api/scenarios)
|
||||
CKB_LEAD_API_KEY=2y4v5-rjhfc-sg5wy-zklkv-bg0tl
|
||||
|
||||
# 存客宝开放 API:创建/更新/删除获客计划、拉取设备列表
|
||||
# - CKB_OPEN_API_KEY:开放 API Key(开发文档中的 mI9Ol-NO6cS-ho3Py-7Pj22-WyK3A)
|
||||
# - CKB_OPEN_ACCOUNT:对应的存客宝登录账号(手机号或用户名)
|
||||
CKB_OPEN_API_KEY=mI9Ol-NO6cS-ho3Py-7Pj22-WyK3A
|
||||
CKB_OPEN_ACCOUNT=karuo1
|
||||
|
||||
|
||||
|
||||
# 微信小程序配置
|
||||
WECHAT_APPID=wxb8bbb2b10dec74aa
|
||||
WECHAT_APPSECRET=3c1fb1f63e6e052222bbcead9d07fe0c
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"soul-api/internal/database"
|
||||
@@ -14,6 +16,26 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// naturalLessSectionID 对章节 id(如 9.1、9.2、9.10)做自然排序,避免 9.1 < 9.10 < 9.2 的字典序问题
|
||||
func naturalLessSectionID(a, b string) bool {
|
||||
partsA := strings.Split(a, ".")
|
||||
partsB := strings.Split(b, ".")
|
||||
for i := 0; i < len(partsA) && i < len(partsB); i++ {
|
||||
na, errA := strconv.Atoi(partsA[i])
|
||||
nb, errB := strconv.Atoi(partsB[i])
|
||||
if errA != nil || errB != nil {
|
||||
if partsA[i] != partsB[i] {
|
||||
return partsA[i] < partsB[i]
|
||||
}
|
||||
continue
|
||||
}
|
||||
if na != nb {
|
||||
return na < nb
|
||||
}
|
||||
}
|
||||
return len(partsA) < len(partsB)
|
||||
}
|
||||
|
||||
// listSelectCols 列表/导出不加载 content,大幅加速
|
||||
var listSelectCols = []string{
|
||||
"id", "mid", "section_title", "price", "is_free", "is_new",
|
||||
@@ -91,9 +113,23 @@ func computeArticleRankingSections(db *gorm.DB) ([]sectionListItem, error) {
|
||||
// 阅读量前20名: 第1名=20分...第20名=1分;最近更新前30篇: 第1名=30分...第30名=1分;付款数前20名: 第1名=20分...第20名=1分
|
||||
func computeSectionsWithHotScore(db *gorm.DB, setPinned bool) ([]sectionListItem, error) {
|
||||
var rows []model.Chapter
|
||||
if err := db.Select(listSelectCols).Order("sort_order ASC, id ASC").Find(&rows).Error; err != nil {
|
||||
if err := db.Select(listSelectCols).Order("COALESCE(sort_order, 999999) ASC, id ASC").Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 同 sort_order 时按 id 自然排序(9.1 < 9.2 < 9.10),避免字典序 9.1 < 9.10 < 9.2
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
soI, soJ := 999999, 999999
|
||||
if rows[i].SortOrder != nil {
|
||||
soI = *rows[i].SortOrder
|
||||
}
|
||||
if rows[j].SortOrder != nil {
|
||||
soJ = *rows[j].SortOrder
|
||||
}
|
||||
if soI != soJ {
|
||||
return soI < soJ
|
||||
}
|
||||
return naturalLessSectionID(rows[i].ID, rows[j].ID)
|
||||
})
|
||||
ids := make([]string, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
ids = append(ids, r.ID)
|
||||
@@ -320,10 +356,23 @@ func DBBookAction(c *gin.Context) {
|
||||
return
|
||||
case "export":
|
||||
var rows []model.Chapter
|
||||
if err := db.Select(listSelectCols).Order("sort_order ASC, id ASC").Find(&rows).Error; err != nil {
|
||||
if err := db.Select(listSelectCols).Order("COALESCE(sort_order, 999999) ASC, id ASC").Find(&rows).Error; err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
soI, soJ := 999999, 999999
|
||||
if rows[i].SortOrder != nil {
|
||||
soI = *rows[i].SortOrder
|
||||
}
|
||||
if rows[j].SortOrder != nil {
|
||||
soJ = *rows[j].SortOrder
|
||||
}
|
||||
if soI != soJ {
|
||||
return soI < soJ
|
||||
}
|
||||
return naturalLessSectionID(rows[i].ID, rows[j].ID)
|
||||
})
|
||||
sections := make([]sectionListItem, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
price := 1.0
|
||||
|
||||
@@ -37,6 +37,7 @@ func SearchGet(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"keyword": q, "total": 0, "results": []interface{}{}}})
|
||||
return
|
||||
}
|
||||
sortChaptersByNaturalID(list)
|
||||
lowerQ := strings.ToLower(q)
|
||||
results := make([]gin.H, 0, len(list))
|
||||
for _, ch := range list {
|
||||
|
||||
@@ -261,6 +261,8 @@ func Setup(cfg *config.Config) *gin.Engine {
|
||||
miniprogram.POST("/qrcode", handler.MiniprogramQrcode)
|
||||
miniprogram.GET("/qrcode/image", handler.MiniprogramQrcodeImage)
|
||||
miniprogram.GET("/book/all-chapters", handler.BookAllChapters)
|
||||
miniprogram.GET("/book/parts", handler.BookParts)
|
||||
miniprogram.GET("/book/chapters-by-part", handler.BookChaptersByPart)
|
||||
miniprogram.GET("/book/chapter/:id", handler.BookChapterByID)
|
||||
miniprogram.GET("/book/chapter/by-mid/:mid", handler.BookChapterByMID)
|
||||
miniprogram.GET("/book/hot", handler.BookHot)
|
||||
|
||||
Reference in New Issue
Block a user