Files
soul-yongping/miniprogram/pages/index/index.js

521 lines
18 KiB
JavaScript
Raw Normal View History

/**
* 卡若创业派对 - 首页
* 开发: 卡若
* 技术支持: 存客宝
*/
const app = getApp()
2026-03-17 18:22:06 +08:00
const { trackClick } = require('../../utils/trackClick')
const { cleanSingleLineField } = require('../../utils/contentParser')
const { navigateMpPath } = require('../../utils/mpNavigate.js')
const { isSafeImageSrc } = require('../../utils/imageUrl.js')
const { submitCkbLead } = require('../../utils/soulBridge')
/** 置顶人物无头像时的占位图 */
const DEFAULT_KARUO_LINK_AVATAR = '/assets/images/karuo-link-avatar.png'
/** 与首页固定「卡若」获客位重复时从横滑列表剔除(含历史误写「卡路」) */
function isKaruoHostDuplicateName(displayName) {
const s = String(displayName || '').trim()
return s === '卡若' || s === '卡路'
}
/** 超级个体无头像占位:仅展示中文首字,避免头像圆里出现英文字母 */
function superAvatarLetter(displayName) {
const s = String(displayName || '').trim()
if (!s) return '会'
const ch = s[0]
return /[\u4e00-\u9fff]/.test(ch) ? ch : '会'
}
Page({
data: {
// 系统信息
statusBarHeight: 44,
navBarHeight: 88,
// 用户信息
isLoggedIn: false,
hasFullBook: false,
readCount: 0,
2026-03-24 15:44:08 +08:00
// 书籍数据totalSections 来自 book/parts初始用 app.getTotalSections() 兜底)
totalSections: 0, // onLoad 后由 loadBookData 更新
bookData: [],
2026-03-24 15:44:08 +08:00
// 推荐章节(来自 recommended/hot API初始为空避免占位错误
featuredSections: [],
// Banner 推荐(优先用 recommended API 第一条,回退 latest-chapters
bannerSection: null,
latestLabel: '最新更新',
// 内容概览
partsList: [
{ id: 'part-1', number: '一', title: '真实的人', subtitle: '人与人之间的底层逻辑' },
{ id: 'part-2', number: '二', title: '真实的行业', subtitle: '电商、内容、传统行业解析' },
{ id: 'part-3', number: '三', title: '真实的错误', subtitle: '我和别人犯过的错' },
{ id: 'part-4', number: '四', title: '真实的赚钱', subtitle: '底层结构与真实案例' },
{ id: 'part-5', number: '五', title: '真实的社会', subtitle: '未来职业与商业生态' }
],
// 超级个体VIP会员
superMembers: [],
superMembersLoading: true,
// 最新新增章节(完整列表 + 展示列表,用于展开/折叠)
latestChapters: [],
displayLatestChapters: [],
// 篇章数(从 bookData 计算)
partCount: 0,
// 加载状态
loading: true,
// 展开状态(首页精选/最新)
featuredExpanded: false,
latestExpanded: false,
featuredSectionsFull: [], // 精选排行榜全量(最多 50默认只展示前 3 条
2026-03-17 13:17:49 +08:00
// 功能配置(搜索开关)
2026-03-17 18:22:06 +08:00
searchEnabled: true,
// 审核模式:隐藏支付相关入口
auditMode: false,
// mp_config.mpUi.homePage后台系统设置 mpUi
mpUiLogoTitle: '卡若创业派对',
mpUiLogoSubtitle: '来自派对房的真实故事',
/** 仅当有置顶 @人物时展示,文案与头像由 _applyHomeMpUi 写入 */
mpUiLinkKaruoText: '',
mpUiLinkKaruoDisplay: DEFAULT_KARUO_LINK_AVATAR,
mpUiSearchPlaceholder: '搜索章节标题或内容...',
mpUiBannerTag: '推荐',
mpUiBannerReadMore: '点击阅读',
mpUiSuperTitle: '超级个体',
mpUiPickTitle: '精选推荐',
mpUiLatestTitle: '最新新增',
/** 后台 @列表置顶人物:有则右上角展示绑定用户头像 + @名称,点击走 ckb/lead */
homePinnedPerson: null,
},
onLoad(options) {
console.log('[Index] ===== onLoad 触发 =====')
// 获取系统信息
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
navBarHeight: app.globalData.navBarHeight
})
// 处理分享参数(推荐码绑定)
if (options && options.ref) {
console.log('[Index] 检测到推荐码:', options.ref)
app.handleReferralCode({ query: options })
}
wx.showShareMenu({ withShareTimeline: true })
2026-03-17 13:17:49 +08:00
this.loadFeatureConfig()
this.initData()
},
onShow() {
console.log('[Index] onShow 触发')
2026-03-17 18:22:06 +08:00
this.setData({ auditMode: app.globalData.auditMode || false })
void this.loadHomePinnedPerson()
// 设置TabBar选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
const tabBar = this.getTabBar()
console.log('[Index] TabBar 组件:', tabBar ? '已找到' : '未找到')
// 主动触发配置加载
if (tabBar && tabBar.loadFeatureConfig) {
console.log('[Index] 主动调用 TabBar.loadFeatureConfig()')
tabBar.loadFeatureConfig()
}
// 更新选中状态
if (tabBar && tabBar.updateSelected) {
tabBar.updateSelected()
} else if (tabBar) {
tabBar.setData({ selected: 0 })
}
} else {
console.log('[Index] TabBar 组件未找到或 getTabBar 方法不存在')
}
// 更新用户状态
this.updateUserStatus()
},
// 初始化数据:首次进页面并行异步加载,加快首屏展示
initData() {
this.setData({ loading: false })
this.loadBookData()
this.loadFeaturedAndLatest()
this.loadSuperMembers()
},
async loadSuperMembers() {
this.setData({ superMembersLoading: true })
try {
// 仅走后端 VIP 列表排序vip_sort、vip_activated_at不在端上拼普通用户
const vipRes = await app.request({ url: '/api/miniprogram/vip/members?limit=24', silent: true }).catch(() => null)
let members = []
if (vipRes && vipRes.success && Array.isArray(vipRes.data) && vipRes.data.length > 0) {
members = vipRes.data.map(u => {
const raw = u.name || u.nickname || u.vipName || u.vip_name || '会员'
const name = cleanSingleLineField(raw) || '会员'
return {
id: u.id,
name,
avatar: u.avatar || '',
isVip: true,
avatarLetter: superAvatarLetter(name)
}
}).filter((m) => !isKaruoHostDuplicateName(m.name))
console.log('[Index] 超级个体(后端排序):', members.length, '人')
}
this.setData({ superMembers: members, superMembersLoading: false })
} catch (e) {
console.log('[Index] 加载超级个体失败:', e)
this.setData({ superMembersLoading: false })
}
},
// 精选推荐 + 最新更新 + 最新列表顺序以后端为准recommended=排行榜算法latest=updated_at
async loadFeaturedAndLatest() {
try {
const tagClassForTag = (tag) => (tag === '热门' ? 'tag-hot' : 'tag-rec')
const toSectionFromRanking = (s) => {
const tag = s.tag || '精选'
return {
id: s.id || s.section_id,
mid: s.mid ?? s.MID ?? 0,
title: s.section_title || s.sectionTitle || s.title || s.chapterTitle || '',
part: (s.part_title || s.partTitle || '').replace(/[_|]/g, ' ').trim(),
tag,
tagClass: tagClassForTag(tag)
}
}
const fallbackTags = ['热门', '推荐', '精选']
const toSectionFromHot = (s, i) => {
const tag = fallbackTags[i % 3]
return {
id: s.id || s.section_id,
mid: s.mid ?? s.MID ?? 0,
title: s.section_title || s.sectionTitle || s.title || s.chapterTitle || '',
part: (s.part_title || s.partTitle || '').replace(/[_|]/g, ' ').trim(),
tag,
tagClass: tagClassForTag(tag)
}
}
const [recRes, latestRes] = await Promise.all([
app.request({ url: '/api/miniprogram/book/recommended?limit=50', silent: true }).catch(() => null),
app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true }).catch(() => null)
])
// 1. 精选推荐一次拉全量≤50默认只显示 3 条;点列表下三角展开(与「最新新增」一致)
let featuredFull = []
if (recRes && recRes.success && Array.isArray(recRes.data) && recRes.data.length > 0) {
featuredFull = recRes.data.map((s) => toSectionFromRanking(s))
}
if (featuredFull.length === 0) {
try {
const hotRes = await app.request({ url: '/api/miniprogram/book/hot?limit=50', silent: true })
const hotList = (hotRes && hotRes.data) ? hotRes.data : []
if (hotList.length > 0) featuredFull = hotList.map((s, i) => toSectionFromHot(s, i))
} catch (e) { console.log('[Index] book/hot 兜底失败:', e) }
}
if (featuredFull.length > 0) {
this.setData({
featuredSectionsFull: featuredFull,
featuredSections: featuredFull.slice(0, 3),
featuredExpanded: false
})
} else {
this.setData({
featuredSectionsFull: [],
featuredSections: [],
featuredExpanded: false
})
}
// 2. Banner 推荐:优先取 recommended 第一条,回退 latest 第一条
const rawList = (latestRes && latestRes.data) ? latestRes.data : []
// 按更新时间倒序,最新在前(与后台展示一致)
const latestList = [...rawList].sort((a, b) => {
const ta = new Date(a.updatedAt || a.updated_at || 0).getTime()
const tb = new Date(b.updatedAt || b.updated_at || 0).getTime()
return tb - ta
})
if (featuredFull.length > 0) {
this.setData({ bannerSection: featuredFull[0] })
} else if (latestList.length > 0) {
const l = latestList[0]
this.setData({
bannerSection: {
id: l.id,
mid: l.mid ?? l.MID ?? 0,
title: l.section_title || l.sectionTitle || l.title || l.chapterTitle || '',
part: l.part_title || l.partTitle || ''
}
})
}
const latestChapters = latestList.slice(0, 20).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,
desc: '',
price: c.price ?? 1,
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
}
})
const display = this.data.latestExpanded ? latestChapters : latestChapters.slice(0, 5)
this.setData({ latestChapters, displayLatestChapters: display })
} catch (e) {
console.log('[Index] 从服务端加载推荐失败:', e)
}
},
async loadBookData() {
try {
const res = await app.request({ url: '/api/miniprogram/book/parts', silent: true })
if (res?.success) {
const total = res.totalSections ?? 0
const parts = res.parts || []
2026-03-24 15:44:08 +08:00
app.globalData.totalSections = (total != null && total > 0) ? total : app.getTotalSections()
this.setData({
totalSections: app.globalData.totalSections,
partCount: parts.length || 5
})
}
} catch (e) {
2026-03-24 15:44:08 +08:00
this.setData({ totalSections: app.getTotalSections(), partCount: 5 })
}
},
// 更新用户状态(已读数 = 用户实际打开过的章节数,仅统计有权限阅读的)
updateUserStatus() {
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
2026-03-24 15:44:08 +08:00
const readCount = Math.min(app.getReadCount(), this.data.totalSections || app.getTotalSections())
this.setData({
isLoggedIn,
hasFullBook,
readCount
})
},
// 跳转到目录
goToChapters() {
2026-03-17 18:22:06 +08:00
trackClick('home', 'nav_click', '阅读进度')
wx.switchTab({ url: '/pages/chapters/chapters' })
},
_applyHomeMpUi() {
const h = app.globalData.configCache?.mpConfig?.mpUi?.homePage || {}
const baseTitle = String(h.logoTitle || '卡若创业派对').trim() || '卡若创业派对'
const prefix = String(h.pinnedTitlePrefix != null ? h.pinnedTitlePrefix : '派对会员').trim()
const tpl = String(h.pinnedMainTitleTemplate || '').trim()
const patch = {
mpUiLogoTitle: baseTitle,
mpUiLogoSubtitle: String(h.logoSubtitle || '来自派对房的真实故事').trim() || '来自派对房的真实故事',
mpUiSearchPlaceholder: String(h.searchPlaceholder || '搜索章节标题或内容...').trim() || '搜索章节标题或内容...',
mpUiBannerTag: String(h.bannerTag || '推荐').trim() || '推荐',
mpUiBannerReadMore: String(h.bannerReadMoreText || '点击阅读').trim() || '点击阅读',
mpUiSuperTitle: String(h.superSectionTitle || '超级个体').trim() || '超级个体',
mpUiPickTitle: String(h.pickSectionTitle || '精选推荐').trim() || '精选推荐',
mpUiLatestTitle: String(h.latestSectionTitle || '最新新增').trim() || '最新新增',
}
const pinned = this.data.homePinnedPerson
if (pinned && pinned.token) {
const displayAv =
pinned.avatar && isSafeImageSrc(pinned.avatar) ? pinned.avatar : DEFAULT_KARUO_LINK_AVATAR
const nm = pinned.name || '好友'
patch.mpUiLinkKaruoText = `点击链接${nm}`
patch.mpUiLinkKaruoDisplay = displayAv
let mainTitle = baseTitle
if (tpl) {
mainTitle = tpl
.replace(/\{\{name\}\}/g, nm)
.replace(/\{\{prefix\}\}/g, prefix)
.trim() || baseTitle
} else if (prefix) {
mainTitle = `${prefix} · ${nm}`
} else {
mainTitle = `@${nm}`
}
patch.mpUiLogoTitle = mainTitle
} else {
patch.mpUiLinkKaruoText = ''
patch.mpUiLinkKaruoDisplay = DEFAULT_KARUO_LINK_AVATAR
}
this.setData(patch)
try {
wx.setNavigationBarTitle({ title: patch.mpUiLogoTitle || '首页' })
} catch (_) {}
},
/** 拉取后台置顶 @人物,合并到首页右上角「链接」区 */
async loadHomePinnedPerson() {
try {
const res = await app.request({ url: '/api/miniprogram/ckb/pinned-person', silent: true })
if (res && res.success && res.data && res.data.token) {
const name = cleanSingleLineField(res.data.name) || '好友'
let av = String(res.data.avatar || '').trim()
if (!isSafeImageSrc(av)) av = ''
this.setData({
homePinnedPerson: {
token: String(res.data.token).trim(),
name,
avatar: av,
},
})
} else {
this.setData({ homePinnedPerson: null })
}
} catch (e) {
console.log('[Index] pinned-person:', e)
this.setData({ homePinnedPerson: null })
}
this._applyHomeMpUi()
},
2026-03-17 13:17:49 +08:00
async loadFeatureConfig() {
try {
2026-03-17 18:22:06 +08:00
const hasCachedFeatures = app.globalData.features && typeof app.globalData.features.searchEnabled === 'boolean'
if (!hasCachedFeatures) {
const res = await app.getConfig()
const features = (res && res.features) || (res && res.data && res.data.features) || {}
const searchEnabled = features.searchEnabled !== false
if (!app.globalData.features) app.globalData.features = {}
app.globalData.features.searchEnabled = searchEnabled
if (typeof features.matchEnabled === 'boolean') app.globalData.features.matchEnabled = features.matchEnabled
if (typeof features.referralEnabled === 'boolean') app.globalData.features.referralEnabled = features.referralEnabled
const mp = (res && res.mpConfig) || {}
app.globalData.auditMode = !!mp.auditMode
2026-03-17 13:17:49 +08:00
}
await app.getAuditMode()
const searchEnabled = app.globalData.features?.searchEnabled !== false
this.setData({
searchEnabled,
auditMode: app.globalData.auditMode || false
})
this._applyHomeMpUi()
2026-03-17 13:17:49 +08:00
} catch (e) {
try {
await app.getAuditMode()
} catch (_) {}
this.setData({
searchEnabled: app.globalData.features?.searchEnabled !== false,
auditMode: app.globalData.auditMode || false
})
this._applyHomeMpUi()
2026-03-17 13:17:49 +08:00
}
await this.loadHomePinnedPerson()
2026-03-17 13:17:49 +08:00
},
// 跳转到搜索页
goToSearch() {
2026-03-17 13:17:49 +08:00
if (!this.data.searchEnabled) return
2026-03-17 18:22:06 +08:00
trackClick('home', 'nav_click', '搜索')
wx.navigateTo({ url: '/pages/search/search' })
},
// 跳转到阅读页(传 mid与分享一致无 mid 时传 id
goToRead(e) {
const id = e.currentTarget.dataset.id
const mid = e.currentTarget.dataset.mid
2026-03-17 18:22:06 +08:00
trackClick('home', 'card_click', id || '章节')
const q = mid ? `mid=${mid}` : `id=${id}`
wx.navigateTo({ url: `/pages/read/read?${q}` })
},
// 跳转到匹配页
goToMatch() {
wx.switchTab({ url: '/pages/match/match' })
},
goToVip() {
2026-03-17 18:22:06 +08:00
trackClick('home', 'btn_click', '加入创业派对')
wx.navigateTo({ url: '/pages/vip/vip' })
},
async onLinkKaruo() {
const pinned = this.data.homePinnedPerson
if (!pinned || !pinned.token) return
trackClick('home', 'btn_click', '置顶@人物留资')
await submitCkbLead(getApp(), {
targetUserId: pinned.token,
targetNickname: pinned.name,
source: 'home_pinned_person',
})
2026-03-11 14:49:45 +08:00
},
goToSuperList() {
wx.switchTab({ url: '/pages/match/match' })
},
// 精选推荐:列表下方小三角展开(数据已在 loadFeaturedAndLatest 一次拉齐)
expandFeaturedChapters() {
if (this.data.featuredExpanded) return
const full = this.data.featuredSectionsFull || []
if (full.length <= 3) return
trackClick('home', 'tab_click', '精选展开_底部三角')
this.setData({ featuredExpanded: true, featuredSections: full })
},
// 最新新增:列表下方小三角展开(无「收起」,展开后整页向下滚动查看)
expandLatestChapters() {
if (this.data.latestExpanded) return
trackClick('home', 'tab_click', '最新展开_底部三角')
const full = this.data.latestChapters || []
this.setData({
latestExpanded: true,
displayLatestChapters: full
})
},
goToMemberDetail(e) {
const id = e.currentTarget.dataset.id
2026-03-17 18:22:06 +08:00
trackClick('home', 'card_click', '超级个体_' + (id || ''))
wx.navigateTo({ url: `/pages/member-detail/member-detail?id=${id}` })
},
// 跳转到我的页面
goToMy() {
wx.switchTab({ url: '/pages/my/my' })
},
// 下拉刷新(等待各异步加载完成后再结束)
async onPullDownRefresh() {
await Promise.all([
this.loadBookData(),
this.loadFeaturedAndLatest(),
this.loadSuperMembers()
])
this.updateUserStatus()
wx.stopPullDownRefresh()
},
onShareAppMessage() {
const ref = app.getMyReferralCode()
return {
title: '卡若创业派对 - 真实商业故事',
path: ref ? `/pages/index/index?ref=${ref}` : '/pages/index/index'
}
},
onShareTimeline() {
const ref = app.getMyReferralCode()
return { title: '卡若创业派对 - 真实商业故事', query: ref ? `ref=${ref}` : '' }
}
})