feat: 小程序超级个体/个人资料/CKB获客;VIP列表展示过滤;管理端与API联调
- 超级个体:去掉首位特例;列表仅展示有头像且非微信默认昵称(vip.go) - 个人资料:居中头像、低调联系方式、点头像优先走存客宝 lead(ckbLeadToken) - 阅读页分享朋友圈复制与 toast 去重 - soul-api: miniprogram users 带 ckbLeadToken;其它 handler 与路由调整 - 脚本:content_upload、miniprogram 上传辅助等 Made-with: Cursor
This commit is contained in:
@@ -29,6 +29,9 @@ Page({
|
||||
|
||||
// 已加载的篇章章节缓存 { partId: chapters }
|
||||
_loadedChapters: {},
|
||||
|
||||
// 小三角点击动画:当前触发的子章 id(与 chapter.id 比对)
|
||||
_triangleAnimating: '',
|
||||
|
||||
// 固定模块 id -> mid(序言/尾声/附录,供 goToRead 传 mid)
|
||||
fixedSectionsMap: {},
|
||||
@@ -152,6 +155,12 @@ Page({
|
||||
})
|
||||
})
|
||||
const chapters = Array.from(chMap.values())
|
||||
chapters.forEach(ch => ch.sections.reverse())
|
||||
// 目录子章下列表:默认最多展示 5 条,点小三角每次再展开 5 条
|
||||
chapters.forEach((ch) => {
|
||||
const n = ch.sections.length
|
||||
ch.sectionVisibleLimit = n === 0 ? 0 : Math.min(5, n)
|
||||
})
|
||||
const loaded = { ...this.data._loadedChapters, [partId]: chapters }
|
||||
const bookData = this.data.bookData.map(p =>
|
||||
p.id === partId ? { ...p, chapters } : p
|
||||
@@ -227,6 +236,43 @@ Page({
|
||||
if (isExpanding) await this.loadChaptersByPart(partId)
|
||||
},
|
||||
|
||||
expandSectionChapter(e) {
|
||||
const partId = e.currentTarget.dataset.partId
|
||||
const chapterId = e.currentTarget.dataset.chapterId
|
||||
if (!partId || !chapterId) return
|
||||
trackClick('chapters', 'tab_click', '目录_子章展开5条')
|
||||
|
||||
const part = this.data.bookData.find((p) => p.id === partId)
|
||||
const chapter = part && (part.chapters || []).find((c) => c.id === chapterId)
|
||||
if (!chapter || !chapter.sections || chapter.sections.length === 0) return
|
||||
|
||||
const total = chapter.sections.length
|
||||
const cur = typeof chapter.sectionVisibleLimit === 'number' ? chapter.sectionVisibleLimit : Math.min(5, total)
|
||||
const next = Math.min(cur + 5, total)
|
||||
if (next === cur) return
|
||||
|
||||
const bookData = this.data.bookData.map((p) => {
|
||||
if (p.id !== partId) return p
|
||||
return {
|
||||
...p,
|
||||
chapters: (p.chapters || []).map((ch) =>
|
||||
ch.id === chapterId ? { ...ch, sectionVisibleLimit: next } : ch
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
// 先去掉动画 class 再打上,便于连续点击重复触发动画
|
||||
this.setData({ _triangleAnimating: '', bookData })
|
||||
setTimeout(() => {
|
||||
this.setData({ _triangleAnimating: chapterId })
|
||||
setTimeout(() => {
|
||||
if (this.data._triangleAnimating === chapterId) {
|
||||
this.setData({ _triangleAnimating: '' })
|
||||
}
|
||||
}, 480)
|
||||
}, 30)
|
||||
},
|
||||
|
||||
// 跳转到阅读页(优先传 mid,与分享逻辑一致)
|
||||
goToRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
|
||||
@@ -88,8 +88,8 @@
|
||||
<block wx:for="{{item.chapters}}" wx:key="id" wx:for-item="chapter">
|
||||
<view class="chapter-header">{{chapter.title}}</view>
|
||||
<view class="section-list">
|
||||
<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}}">
|
||||
<block wx:for="{{chapter.sections}}" wx:key="id" wx:for-item="section" wx:for-index="secIdx">
|
||||
<view class="section-item" wx:if="{{secIdx < chapter.sectionVisibleLimit}}" bindtap="goToRead" data-id="{{section.id}}" data-mid="{{section.mid}}">
|
||||
<view class="section-left">
|
||||
<view class="section-lock-wrap">
|
||||
<icon wx:if="{{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1}}" name="lock-open" size="24" color="#00CED1" customClass="section-lock lock-open"></icon>
|
||||
@@ -100,12 +100,14 @@
|
||||
</view>
|
||||
<view class="section-right">
|
||||
<text wx:if="{{section.isFree}}" class="tag tag-free">免费</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 wx:elif="{{!(isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1)}}" class="section-price">¥{{section.price}}</text>
|
||||
<icon name="chevron-right" size="24" color="rgba(255,255,255,0.3)" customClass="section-arrow"></icon>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view class="section-expand-trigger" wx:if="{{chapter.sections.length > chapter.sectionVisibleLimit}}" bindtap="expandSectionChapter" data-part-id="{{item.id}}" data-chapter-id="{{chapter.id}}">
|
||||
<view class="latest-expand-triangle {{_triangleAnimating === chapter.id ? 'tri-bounce' : ''}}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
@@ -577,6 +577,49 @@
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* ===== 展开三角 ===== */
|
||||
.section-expand-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 0 12rpx;
|
||||
}
|
||||
|
||||
.latest-expand-triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 18rpx solid transparent;
|
||||
border-right: 18rpx solid transparent;
|
||||
border-top: 14rpx solid rgba(0, 206, 209, 0.55);
|
||||
opacity: 0.85;
|
||||
transform-origin: 50% 0;
|
||||
transition: border-top-color 0.15s ease;
|
||||
}
|
||||
|
||||
.section-expand-trigger:active .latest-expand-triangle {
|
||||
border-top-color: #00CED1;
|
||||
}
|
||||
|
||||
@keyframes catalog-tri-nudge {
|
||||
0% {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 0.85;
|
||||
}
|
||||
40% {
|
||||
transform: translateY(10rpx) scale(1.12);
|
||||
opacity: 1;
|
||||
border-top-color: #00CED1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.latest-expand-triangle.tri-bounce {
|
||||
animation: catalog-tri-nudge 0.45s ease-out;
|
||||
}
|
||||
|
||||
/* ===== 底部留白 ===== */
|
||||
.bottom-space {
|
||||
height: 40rpx;
|
||||
|
||||
Reference in New Issue
Block a user