refactor: update API base URL and enhance read page functionality

- 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.
This commit is contained in:
Alex-larget
2026-03-30 18:47:43 +08:00
parent 07c26281ff
commit ace61a7290
6 changed files with 61 additions and 26 deletions

View File

@@ -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),

View File

@@ -149,7 +149,12 @@
</view>
</view>
<view class="paywall-audit-tip" wx:if="{{auditMode}}">审核中,暂不支持购买</view>
<text class="paywall-desc paywall-desc--moments-expanded" wx:if="{{momentsPaywallExpanded}}">{{readUi.singlePageExpandedHint || '预览不可付款,请点底部「前往小程序」。'}}</text>
<view class="paywall-expanded-stack" wx:if="{{momentsPaywallExpanded}}">
<text class="paywall-expanded-lead">{{readUi.singlePageExpandedHint || '预览页不能直接付款,务必先点底栏「前往小程序」。'}}</text>
<view class="paywall-expanded-divider"></view>
<text class="paywall-expanded-line">{{readUi.shareTipLine || '好友经你分享购买,你可获得约 90% 收益'}}</text>
<text class="paywall-expanded-line">{{readUi.fullPaywallTip || '转发给需要的人,一起学习还能赚佣金'}}</text>
</view>
</block>
<block wx:else>
@@ -236,7 +241,12 @@
</view>
</view>
<view class="paywall-audit-tip" wx:if="{{auditMode}}">审核中,暂不支持购买</view>
<text class="paywall-desc paywall-desc--moments-expanded" wx:if="{{momentsPaywallExpanded}}">{{readUi.singlePageExpandedHint || '预览不可付款,请点底部「前往小程序」。'}}</text>
<view class="paywall-expanded-stack" wx:if="{{momentsPaywallExpanded}}">
<text class="paywall-expanded-lead">{{readUi.singlePageExpandedHint || '预览页不能直接付款,务必先点底栏「前往小程序」。'}}</text>
<view class="paywall-expanded-divider"></view>
<text class="paywall-expanded-line">{{readUi.shareTipLine || '好友经你分享购买,你可获得约 90% 收益'}}</text>
<text class="paywall-expanded-line">{{readUi.fullPaywallTip || '转发给需要的人,一起学习还能赚佣金'}}</text>
</view>
</block>
<block wx:else>

View File

@@ -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;
}
/* 朋友圈单页:未点解锁前的一行轻提示 */

View File

@@ -28,7 +28,7 @@
"pathName": "pages/read/read",
"query": "mid=233",
"scene": null,
"launchMode": "default"
"launchMode": "singlePage"
},
{
"name": "个人资料",

View File

@@ -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.goHTML 剥标签后与 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 {

View File

@@ -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, ">") {