实施美团式的支付流程,并增强相关功能

- 将支付流程统一至礼品支付页面,禁止从阅读页面进行支付,以优化用户体验。
- 更新了礼物支付详情页面,为发起人和朋友展示了不同的用户界面元素,包括为发起人提供的分享按钮和为朋友提供的支付按钮。
- 增强了后端逻辑,以确保在支付处理过程中正确将收益归因于发起人。
- 增加了每日章节更新,并改进了章节页面的加载状态,以提升用户交互体验。
- 更新了文档,以反映新的支付流程和相关变更。
This commit is contained in:
Alex-larget
2026-03-17 12:15:08 +08:00
parent 0d12ab1d07
commit 601044ec60
30 changed files with 822 additions and 238 deletions

View File

@@ -6,6 +6,7 @@
*/
const app = getApp()
const { trackClick } = require('../../utils/trackClick')
Page({
data: {
@@ -55,6 +56,7 @@ Page({
this.updateUserStatus()
this.loadVipStatus()
this.loadParts()
this.loadDailyChapters()
},
// 懒加载:仅拉取篇章列表 + totalSections + fixedSections
@@ -174,7 +176,34 @@ Page({
},
onPullDownRefresh() {
this.loadParts().then(() => wx.stopPullDownRefresh()).catch(() => wx.stopPullDownRefresh())
Promise.all([this.loadParts(), this.loadDailyChapters()])
.then(() => wx.stopPullDownRefresh())
.catch(() => wx.stopPullDownRefresh())
},
// 每日新增:用 latest-chapters 接口,展示最近更新章节
async loadDailyChapters() {
try {
const res = await app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true })
const list = (res && res.data) ? res.data : []
const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase()
const exclude = c => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
const daily = list
.filter(exclude)
.slice(0, 10)
.map(c => {
const d = new Date(c.updatedAt || c.updated_at || Date.now())
const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || ''
return {
id: c.id,
mid: c.mid ?? c.MID ?? 0,
title,
price: c.price ?? 1,
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
}
})
this.setData({ dailyChapters: daily })
} catch (e) { console.log('[Chapters] 加载每日新增失败:', e) }
},
onShow() {
@@ -219,6 +248,7 @@ Page({
// 切换展开状态,展开时懒加载该篇章章节
async togglePart(e) {
trackClick('chapters', 'tab_click', e.currentTarget.dataset.id || '篇章')
const partId = e.currentTarget.dataset.id
const isExpanding = this.data.expandedPart !== partId
this.setData({
@@ -231,6 +261,7 @@ Page({
goToRead(e) {
const id = e.currentTarget.dataset.id
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
trackClick('chapters', 'card_click', id || '章节')
const q = mid ? `mid=${mid}` : `id=${id}`
wx.navigateTo({ url: `/pages/read/read?${q}` })
},
@@ -249,6 +280,7 @@ Page({
// 跳转到搜索页
goToSearch() {
trackClick('chapters', 'nav_click', '搜索')
wx.navigateTo({ url: '/pages/search/search' })
},

View File

@@ -40,6 +40,31 @@
<!-- 目录内容 -->
<view class="chapters-content" wx:if="{{!partsLoading}}">
<!-- 每日新增(最近更新章节快捷入口) -->
<view class="daily-section" wx:if="{{dailyChapters.length > 0}}">
<view class="daily-header">
<text class="daily-title">每日新增</text>
<text class="daily-badge">+{{dailyChapters.length}}</text>
</view>
<view class="daily-list">
<view
class="daily-item"
wx:for="{{dailyChapters}}"
wx:key="id"
bindtap="goToRead"
data-id="{{item.id}}"
data-mid="{{item.mid}}"
>
<view class="daily-dot"></view>
<view class="daily-content">
<text class="daily-item-title">{{item.title}}</text>
<text class="daily-item-meta">{{item.dateStr}} · ¥{{item.price}}</text>
</view>
<text class="daily-arrow"></text>
</view>
</view>
</view>
<!-- 序言(优先传 mid阅读页用 by-mid 请求) -->
<view class="chapter-item" bindtap="goToRead" data-id="preface" data-mid="{{fixedSectionsMap.preface}}">
<view class="item-left">
@@ -83,6 +108,7 @@
<text class="section-lock {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? 'lock-open' : 'lock-closed'}}">{{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '○' : '●'}}</text>
<text class="section-title {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '' : 'text-muted'}}">{{section.id}} {{section.title}}</text>
<text wx:if="{{section.isNew}}" class="tag tag-new">NEW</text>
<text wx:if="{{section.isPremium}}" class="tag tag-vip">增值</text>
</view>
<view class="section-right">
<text wx:if="{{section.isFree}}" class="tag tag-free">免费</text>

View File

@@ -174,6 +174,89 @@
box-sizing: border-box;
}
/* ===== 每日新增 ===== */
.daily-section {
margin-bottom: 32rpx;
padding: 24rpx;
background: #1c1c1e;
border-radius: 24rpx;
border: 2rpx solid rgba(255, 255, 255, 0.05);
}
.daily-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
}
.daily-title {
font-size: 30rpx;
font-weight: 600;
color: #ffffff;
}
.daily-badge {
font-size: 22rpx;
padding: 4rpx 12rpx;
background: #F6AD55;
color: #ffffff;
border-radius: 20rpx;
}
.daily-list {
display: flex;
flex-direction: column;
gap: 0;
}
.daily-item {
display: flex;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
}
.daily-item:last-child {
border-bottom: none;
}
.daily-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: rgba(0, 206, 209, 0.6);
margin-right: 20rpx;
flex-shrink: 0;
}
.daily-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.daily-item-title {
font-size: 26rpx;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.daily-item-meta {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.4);
}
.daily-arrow {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.4);
margin-left: 16rpx;
}
/* ===== 章节项 ===== */
.chapter-item {
display: flex;