From ace61a729098be873c86a35f2f204ac7c0d19d5e Mon Sep 17 00:00:00 2001 From: Alex-larget <33240357+Alex-larget@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:47:43 +0800 Subject: [PATCH] refactor: update API base URL and enhance read page functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed API base URL from localhost:8080 to https://soulapi.quwanzhi.com for production. - Added new read page configuration for "阅读详情" with specific query parameters. - Implemented a fallback mechanism for read page UI to handle cases where cloud data is unavailable. - Updated read page content and clipboard text formatting for better user experience. - Removed deprecated gift sharing UI elements to streamline the interface. --- miniprogram/pages/read/read.js | 11 ++++-- miniprogram/pages/read/read.wxml | 14 ++++++- miniprogram/pages/read/read.wxss | 40 +++++++++++++++++--- miniprogram/project.private.config.json | 2 +- soul-api/internal/handler/book.go | 18 +++------ soul-api/internal/handler/chapter_preview.go | 2 +- 6 files changed, 61 insertions(+), 26 deletions(-) diff --git a/miniprogram/pages/read/read.js b/miniprogram/pages/read/read.js index b3b7899a..677f8bec 100644 --- a/miniprogram/pages/read/read.js +++ b/miniprogram/pages/read/read.js @@ -28,6 +28,7 @@ const mpPagePopups = require('../../utils/mpPagePopups.js') const READ_UI_DEFAULTS = { singlePageUnlockTitle: '解锁完整内容', singlePagePayButtonText: '支付 ¥{price} 解锁全文', + /** 单页展开区首行:付款引导;其下为分隔线 + shareTipLine + fullPaywallTip */ singlePageExpandedHint: '预览页不能直接付款,务必先点底栏「前往小程序」。', payTapModalTitle: '解锁说明', payTapModalContent: @@ -613,7 +614,8 @@ Page({ const parseCfg = getContentParseConfig() const sectionPrice = this.data.sectionPrice ?? 1 let res = prefetchedChapter - if (!res || !res.content) { + const hasChapterBody = (r) => r && String(r.data?.content ?? r.content ?? '').length > 0 + if (!res || !hasChapterBody(res)) { res = await app.request({ url: this._getChapterUrl({ id }), silent: true }) } this._applyChapterMetaFromResponse(res) @@ -656,14 +658,15 @@ Page({ console.error('[Read] 加载内容失败,尝试本地缓存:', e) try { const cached = wx.getStorageSync(cacheKey) - if (cached && cached.content) { + const cachedDisplay = cached && (cached.data?.content ?? cached.content) + if (cached && String(cachedDisplay || '').length) { await app.getReadExtras() this._applyChapterMetaFromResponse(cached) - const { lines, segments } = contentParser.parseContent(cached.content, getContentParseConfig()) + const { lines, segments } = contentParser.parseContent(cachedDisplay, getContentParseConfig()) // 预览比例由前端按高度裁切,这里渲染完整内容 const previewCount = lines.length this.setData({ - content: cached.content, + content: cachedDisplay, contentParagraphs: lines, contentSegments: normalizeMentionSegments(segments), previewParagraphs: lines.slice(0, previewCount), diff --git a/miniprogram/pages/read/read.wxml b/miniprogram/pages/read/read.wxml index 6899cced..eadcf8bd 100644 --- a/miniprogram/pages/read/read.wxml +++ b/miniprogram/pages/read/read.wxml @@ -149,7 +149,12 @@ 审核中,暂不支持购买 - {{readUi.singlePageExpandedHint || '预览不可付款,请点底部「前往小程序」。'}} + + {{readUi.singlePageExpandedHint || '预览页不能直接付款,务必先点底栏「前往小程序」。'}} + + {{readUi.shareTipLine || '好友经你分享购买,你可获得约 90% 收益'}} + {{readUi.fullPaywallTip || '转发给需要的人,一起学习还能赚佣金'}} + @@ -236,7 +241,12 @@ 审核中,暂不支持购买 - {{readUi.singlePageExpandedHint || '预览不可付款,请点底部「前往小程序」。'}} + + {{readUi.singlePageExpandedHint || '预览页不能直接付款,务必先点底栏「前往小程序」。'}} + + {{readUi.shareTipLine || '好友经你分享购买,你可获得约 90% 收益'}} + {{readUi.fullPaywallTip || '转发给需要的人,一起学习还能赚佣金'}} + diff --git a/miniprogram/pages/read/read.wxss b/miniprogram/pages/read/read.wxss index 4d6543a7..fafe0157 100644 --- a/miniprogram/pages/read/read.wxss +++ b/miniprogram/pages/read/read.wxss @@ -299,6 +299,13 @@ } .paywall--single-preview .paywall-title { margin-bottom: 20rpx; + font-size: 34rpx; +} +.paywall--single-preview .paywall-desc { + font-size: 24rpx; +} +.paywall--single-preview .purchase-section .btn-label { + font-size: 26rpx; } /* 朋友圈单页图二:首段说明 + 与购买行间距收紧 */ .paywall-sp-lead { @@ -328,12 +335,35 @@ text-align: center; color: rgba(255, 255, 255, 0.58); } -.paywall-desc--moments-expanded { - margin-top: 28rpx !important; - margin-bottom: 0 !important; - font-size: 26rpx !important; - line-height: 1.45; +/* 单页:点「解锁说明」后展开 — 首行引导 / 分隔线 / 两行分润说明(与运营稿一致) */ +.paywall-expanded-stack { + margin-top: 28rpx; padding: 0 8rpx; + width: 100%; + box-sizing: border-box; +} +.paywall-expanded-lead { + display: block; + font-size: 22rpx; + line-height: 1.45; + color: rgba(255, 255, 255, 0.55); + text-align: center; +} +.paywall-expanded-divider { + height: 0; + border-top: 1rpx solid rgba(255, 255, 255, 0.08); + margin: 20rpx 0; +} +.paywall-expanded-line { + display: block; + font-size: 22rpx; + line-height: 1.45; + color: rgba(255, 255, 255, 0.5); + text-align: center; + margin-bottom: 12rpx; +} +.paywall-expanded-line:last-child { + margin-bottom: 0; } /* 朋友圈单页:未点解锁前的一行轻提示 */ diff --git a/miniprogram/project.private.config.json b/miniprogram/project.private.config.json index 7d4bdd14..0133be56 100644 --- a/miniprogram/project.private.config.json +++ b/miniprogram/project.private.config.json @@ -28,7 +28,7 @@ "pathName": "pages/read/read", "query": "mid=233", "scene": null, - "launchMode": "default" + "launchMode": "singlePage" }, { "name": "个人资料", diff --git a/soul-api/internal/handler/book.go b/soul-api/internal/handler/book.go index 339ac422..a6419913 100644 --- a/soul-api/internal/handler/book.go +++ b/soul-api/internal/handler/book.go @@ -644,7 +644,8 @@ func getUnpaidPreviewPercent(db *gorm.DB) int { // findChapterAndRespond 按条件查章节并返回统一格式 // 免费判断优先级:system_config.free_chapters / chapter_config.freeChapters > chapters.is_free/price -// 付费章节:若请求携带 userId 且有购买权限则返回完整 content,否则返回 previewContent +// data.content 始终返回完整 HTML;未解锁时的「试读比例」由小程序对渲染后高度做 overflow 裁切(见 miniprogram read _updatePreviewClipHeight)。 +// 纯文本截取 previewContent 仅用于 H5 落地页等,不再用于小程序读章节响应。 // content 缓存:优先 Redis,命中时仅查元数据(不含 LONGTEXT),未命中时查全量并回填缓存 func findChapterAndRespond(c *gin.Context, whereFn func(*gorm.DB) *gorm.DB) { var ch model.Chapter @@ -679,27 +680,18 @@ func findChapterAndRespond(c *gin.Context, whereFn func(*gorm.DB) *gorm.DB) { isFree = true } - // 确定返回的 content:免费直接返回,付费须校验购买权限 userID := c.Query("userId") isPremium := ch.EditionPremium != nil && *ch.EditionPremium hasFullAccess := isFree || checkUserChapterAccess(db, userID, ch.ID, isPremium) - var returnContent string - // 未解锁:正文截取见 chapter_preview.go(HTML 剥标签后与 H5 一致) - if hasFullAccess { - returnContent = ch.Content - } else { - percent := chapterPreviewPercent(db, &ch) - returnContent = previewContent(ch.Content, percent) - } + effectivePct := chapterPreviewPercent(db, &ch) + returnContent := ch.Content - // data 中的 content 必须与外层 content 一致,避免泄露完整内容给未授权用户 chForResponse := ch chForResponse.Content = returnContent chForResponse.PartTitle = sanitizeChapterTitleField(ch.PartTitle) chForResponse.ChapterTitle = sanitizeChapterTitleField(ch.ChapterTitle) chForResponse.SectionTitle = sanitizeChapterTitleField(ch.SectionTitle) - effectivePct := chapterPreviewPercent(db, &ch) out := gin.H{ "success": true, "data": chForResponse, @@ -715,7 +707,7 @@ func findChapterAndRespond(c *gin.Context, whereFn func(*gorm.DB) *gorm.DB) { "readPreviewUi": mergeReadPreviewUI(db), } if !hasFullAccess { - out["previewPercent"] = getUnpaidPreviewPercent(db) + out["previewPercent"] = effectivePct } // 文章详情内直接输出上一篇/下一篇,省去单独请求 if list := getOrderedChapterList(); len(list) > 0 { diff --git a/soul-api/internal/handler/chapter_preview.go b/soul-api/internal/handler/chapter_preview.go index 5999c2dd..4ba4f108 100644 --- a/soul-api/internal/handler/chapter_preview.go +++ b/soul-api/internal/handler/chapter_preview.go @@ -44,7 +44,7 @@ func chapterPreviewPercent(db *gorm.DB, ch *model.Chapter) int { return g } -// previewContent 取内容的前 percent%(按纯文本 rune 计;原 HTML 先剥标签),与 h5 落地页一致 +// previewContent 取内容的前 percent%(按纯文本 rune 计;原 HTML 先剥标签)——用于 H5 落地页等;小程序读章节接口应返回全文并由端上按渲染高度裁切。 func previewContent(content string, percent int) string { work := content if strings.Contains(content, "<") && strings.Contains(content, ">") {