更新小程序,新增VIP会员状态管理功能,优化章节解锁逻辑,支持VIP用户访问增值内容。调整用户详情页面,增加VIP相关字段和功能,提升用户体验。更新会议记录,反映最新讨论内容。

This commit is contained in:
Alex-larget
2026-03-10 11:04:34 +08:00
parent 30ebdb5ac7
commit 05ac60dc7e
60 changed files with 8387 additions and 1583 deletions

View File

@@ -16,6 +16,7 @@ Page({
// 用户状态
isLoggedIn: false,
hasFullBook: false,
isVip: false,
purchasedSections: [],
// 书籍数据 - 完整真实标题
@@ -212,6 +213,7 @@ Page({
navBarHeight: app.globalData.navBarHeight
})
this.updateUserStatus()
this.loadVipStatus()
this.loadChaptersOnce()
},
@@ -258,13 +260,19 @@ Page({
})
}
const ch = part.chapters.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,
title: r.sectionTitle || r.section_title || r.title || '',
isFree: r.isFree === true || (r.price !== undefined && r.price === 0),
price: r.price ?? 1,
isNew: r.isNew === true || r.is_new === true
isNew: r.isNew === true || r.is_new === true,
isPremium
})
})
const bookData = Array.from(partMap.values()).map(p => ({
@@ -310,12 +318,33 @@ Page({
}
}
this.updateUserStatus()
this.loadVipStatus()
},
// 拉取 VIP 状态isVip=会员hasFullBook=9.9 买断)
async loadVipStatus() {
const userId = app.globalData.userInfo?.id
if (!userId) return
try {
const res = await app.request({ url: `/api/miniprogram/vip/status?userId=${userId}`, silent: true, timeout: 3000 })
if (res?.success) {
app.globalData.isVip = !!res.data?.isVip
app.globalData.vipExpireDate = res.data?.expireDate || ''
this.setData({ isVip: app.globalData.isVip })
const userInfo = app.globalData.userInfo || {}
userInfo.isVip = app.globalData.isVip
userInfo.vipExpireDate = app.globalData.vipExpireDate
wx.setStorageSync('userInfo', userInfo)
}
} catch (e) {
// 静默失败不影响目录展示
}
},
// 更新用户状态
updateUserStatus() {
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
this.setData({ isLoggedIn, hasFullBook, purchasedSections })
const { isLoggedIn, hasFullBook, purchasedSections, isVip } = app.globalData
this.setData({ isLoggedIn, hasFullBook, purchasedSections, isVip })
},
// 切换展开状态
@@ -335,8 +364,9 @@ Page({
},
// 检查是否已购买
hasPurchased(sectionId) {
if (this.data.hasFullBook) return true
hasPurchased(sectionId, isPremium) {
if (this.data.isVip) return true
if (!isPremium && this.data.hasFullBook) return true
return this.data.purchasedSections.includes(sectionId)
},

View File

@@ -73,13 +73,14 @@
<block wx:for="{{chapter.sections}}" wx:key="id" wx:for-item="section">
<view class="section-item" bindtap="goToRead" data-id="{{section.id}}" data-mid="{{section.mid}}">
<view class="section-left">
<text class="section-lock {{section.isFree || hasFullBook || purchasedSections.indexOf(section.id) > -1 ? 'lock-open' : 'lock-closed'}}">{{section.isFree || hasFullBook || purchasedSections.indexOf(section.id) > -1 ? '○' : '●'}}</text>
<text class="section-title {{section.isFree || hasFullBook || purchasedSections.indexOf(section.id) > -1 ? '' : 'text-muted'}}">{{section.id}} {{section.title}}</text>
<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>
<text wx:elif="{{hasFullBook || purchasedSections.indexOf(section.id) > -1}}" class="tag tag-purchased">已</text>
<text wx:elif="{{isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1}}" class="tag tag-purchased">已解锁</text>
<text wx:else class="section-price">¥{{section.price}}</text>
<text class="section-arrow"></text>
</view>

View File

@@ -232,6 +232,14 @@
margin-left: 8rpx;
}
.tag-vip {
background: rgba(255, 215, 0, 0.12);
color: #FFD700;
font-size: 20rpx;
padding: 2rpx 8rpx;
margin-left: 8rpx;
}
.text-brand {
color: #00CED1;
}

View File

@@ -148,7 +148,7 @@ Page({
}
const d = this._getUnlockData(member.id)
if (d[field]) return
const isVip = app.globalData.hasFullBook
const isVip = app.globalData.isVip
const usedFree = this._hasUsedFreeForMember(member.id)
if (isVip || !usedFree) {
this._addUnlock(member.id, field)

View File

@@ -742,20 +742,25 @@ Page({
})
},
// VIP状态查询hasFullBook 优先,兼容模拟支付等本地已置 VIP 的情况
// VIP状态查询注意:hasFullBook=9.9 买断,不等同 VIP
async loadVipStatus() {
if (app.globalData.hasFullBook) {
this.setData({ isVip: true, vipExpireDate: this.data.vipExpireDate || '' })
}
const userId = app.globalData.userInfo?.id
if (!userId) return
try {
const res = await app.request({ url: `/api/miniprogram/vip/status?userId=${userId}`, silent: true })
if (res?.success) {
const isVip = !!res.data?.isVip
app.globalData.isVip = isVip
app.globalData.vipExpireDate = res.data?.expireDate || ''
this.setData({
isVip: res.data?.isVip || app.globalData.hasFullBook,
isVip,
vipExpireDate: res.data?.expireDate || this.data.vipExpireDate || ''
})
// 同步到 storage便于其他页面复用注意hasFullBook=买断isVip=会员)
const userInfo = app.globalData.userInfo || {}
userInfo.isVip = isVip
userInfo.vipExpireDate = res.data?.expireDate || ''
wx.setStorageSync('userInfo', userInfo)
}
} catch (e) { console.log('[My] VIP查询失败', e) }
},