Files

501 lines
17 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Soul创业派对 - 首页
* 开发: 卡若
* 技术支持: 存客宝
*/
console.log('[Index] ===== 首页文件开始加载 =====')
const app = getApp()
Page({
data: {
// 系统信息
statusBarHeight: 44,
navBarHeight: 88,
// 用户信息
isLoggedIn: false,
hasFullBook: false,
readCount: 0,
// 书籍数据
totalSections: 62,
bookData: [],
// 推荐章节:以服务端推荐算法为准,不再预置写死内容
featuredSections: [],
// 最新章节(动态计算)
latestSection: 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: [],
// 篇章数(从 bookData 计算)
partCount: 0,
// 加载状态
loading: true,
// 链接卡若 - 留资弹窗
showLeadModal: false,
leadPhone: ''
},
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 })
this.initData()
},
onShow() {
console.log('[Index] onShow 触发')
// 设置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.loadFeaturedFromServer()
this.loadSuperMembers()
this.loadLatestChapters()
},
async loadSuperMembers() {
this.setData({ superMembersLoading: true })
try {
// 优先加载 VIP 会员(购买 1980 fullbook/vip 订单的用户)
let members = []
try {
const res = await app.request({ url: '/api/miniprogram/vip/members', silent: true })
if (res && res.success && res.data) {
// 不再过滤无头像用户,无头像时用首字母展示
members = (Array.isArray(res.data) ? res.data : []).slice(0, 4).map(u => ({
id: u.id,
name: u.nickname || u.vipName || u.vip_name || '会员', // 超级个体:用户资料优先,随「我的」修改实时生效
avatar: u.avatar || '',
isVip: true
}))
if (members.length > 0) {
console.log('[Index] 超级个体加载成功:', members.length, '人')
}
}
} catch (e) {
console.log('[Index] vip/members 请求失败:', e)
}
// 不足 4 个则用有头像的普通用户补充
if (members.length < 4) {
try {
const dbRes = await app.request({ url: '/api/miniprogram/users?limit=20', silent: true })
if (dbRes && dbRes.success && dbRes.data) {
const existIds = new Set(members.map(m => m.id))
const extra = (Array.isArray(dbRes.data) ? dbRes.data : [])
.filter(u => u.avatar && u.nickname && !existIds.has(u.id))
.slice(0, 4 - members.length)
.map(u => ({ id: u.id, name: u.nickname, avatar: u.avatar, isVip: u.is_vip === 1 }))
members = members.concat(extra)
}
} catch (e) {}
}
this.setData({ superMembers: members, superMembersLoading: false })
} catch (e) {
console.log('[Index] 加载超级个体失败:', e)
this.setData({ superMembersLoading: false })
}
},
// 从服务端获取精选推荐、最新更新stitch_soulbook/recommended、book/latest-chapters
async loadFeaturedFromServer() {
try {
// 1. 精选推荐:优先用 book/recommended按阅读量+算法,带 热门/推荐/精选 标签)
let featured = []
try {
const recRes = await app.request({ url: '/api/miniprogram/book/recommended', silent: true })
if (recRes && recRes.success && Array.isArray(recRes.data) && recRes.data.length > 0) {
featured = recRes.data.map((s, i) => ({
id: s.id || s.section_id,
mid: s.mid ?? s.MID ?? 0,
title: s.sectionTitle || s.section_title || s.title || s.chapterTitle || '',
part: (s.partTitle || s.part_title || '').replace(/[_|]/g, ' ').trim(),
tag: s.tag || ['热门', '推荐', '精选'][i] || '精选',
tagClass: ['tag-hot', 'tag-rec', 'tag-rec'][i] || 'tag-rec'
}))
this.setData({ featuredSections: featured })
}
} catch (e) { console.log('[Index] book/recommended 失败:', e) }
// 兜底:无 recommended 时从 all-chapters 按更新时间取前3
if (featured.length === 0) {
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
const chapters = (res && res.data) || (res && res.chapters) || []
const valid = chapters.filter(c => {
const pt = (c.part_title || c.partTitle || '').toLowerCase()
return !pt.includes('序言') && !pt.includes('尾声') && !pt.includes('附录')
})
if (valid.length > 0) {
const tagMap = ['热门', '推荐', '精选']
featured = valid
.sort((a, b) => new Date(b.updated_at || b.updatedAt || 0) - new Date(a.updated_at || a.updatedAt || 0))
.slice(0, 3)
.map((s, i) => ({
id: s.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: tagMap[i] || '精选',
tagClass: ['tag-hot', 'tag-rec', 'tag-rec'][i] || 'tag-rec'
}))
this.setData({ featuredSections: featured })
}
}
// 2. 最新更新:用 book/latest-chapters 取第1条
try {
const latestRes = await app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true })
const latestList = (latestRes && latestRes.data) ? latestRes.data : []
if (latestList.length > 0) {
const l = latestList[0]
this.setData({
latestSection: {
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 || ''
}
})
}
} catch (e) {
// 兜底:从 all-chapters 取
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
const chapters = (res && res.data) || (res && res.chapters) || []
const valid = chapters.filter(c => {
const pt = (c.part_title || c.partTitle || '').toLowerCase()
return !pt.includes('序言') && !pt.includes('尾声') && !pt.includes('附录')
})
if (valid.length > 0) {
valid.sort((a, b) => new Date(b.updated_at || b.updatedAt || 0) - new Date(a.updated_at || a.updatedAt || 0))
const latest = valid[0]
this.setData({
latestSection: {
id: latest.id,
mid: latest.mid ?? latest.MID ?? 0,
title: latest.section_title || latest.sectionTitle || latest.title || latest.chapterTitle || '',
part: latest.part_title || latest.partTitle || ''
}
})
}
}
} catch (e) {
console.log('[Index] 从服务端加载推荐失败:', e)
}
},
async loadBookData() {
try {
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
if (res && (res.data || res.chapters)) {
const chapters = res.data || res.chapters || []
const partIds = new Set(chapters.map(c => c.partId || c.part_id || '').filter(Boolean))
this.setData({
bookData: chapters,
totalSections: res.total || chapters.length || 62,
partCount: partIds.size || 5
})
}
} catch (e) {
console.error('加载书籍数据失败:', e)
}
},
// 更新用户状态(已读数 = 用户实际打开过的章节数,仅统计有权限阅读的)
updateUserStatus() {
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
const readCount = Math.min(app.getReadCount(), this.data.totalSections || 62)
this.setData({
isLoggedIn,
hasFullBook,
readCount
})
},
// 跳转到目录
goToChapters() {
wx.switchTab({ url: '/pages/chapters/chapters' })
},
// 跳转到搜索页
goToSearch() {
wx.navigateTo({ url: '/pages/search/search' })
},
// 跳转到阅读页(优先传 mid与分享逻辑一致
goToRead(e) {
const id = e.currentTarget.dataset.id
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
const q = mid ? `mid=${mid}` : `id=${id}`
wx.navigateTo({ url: `/pages/read/read?${q}` })
},
// 跳转到匹配页
goToMatch() {
wx.switchTab({ url: '/pages/match/match' })
},
goToVip() {
wx.navigateTo({ url: '/pages/vip/vip' })
},
goToAbout() {
wx.navigateTo({ url: '/pages/about/about' })
},
async onLinkKaruo() {
const app = getApp()
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) {
wx.showModal({
title: '提示',
content: '请先登录后再链接卡若',
confirmText: '去登录',
cancelText: '取消',
success: (res) => {
if (res.confirm) wx.switchTab({ url: '/pages/my/my' })
}
})
return
}
const userId = app.globalData.userInfo.id
const leadKey = 'karuo_lead_' + userId
let phone = (app.globalData.userInfo.phone || '').trim()
let wechatId = (app.globalData.userInfo.wechatId || app.globalData.userInfo.wechat_id || '').trim()
if (!phone && !wechatId) {
try {
const profileRes = await app.request({ url: `/api/miniprogram/user/profile?userId=${userId}`, silent: true })
if (profileRes?.success && profileRes.data) {
phone = (profileRes.data.phone || '').trim()
wechatId = (profileRes.data.wechatId || profileRes.data.wechat_id || '').trim()
}
} catch (e) {}
}
if (phone || wechatId) {
const hasLead = wx.getStorageSync(leadKey)
if (hasLead) {
wx.showToast({ title: '已提交联系方式,卡若会尽快联系你', icon: 'none' })
return
}
wx.showLoading({ title: '提交中...', mask: true })
try {
const res = await app.request({
url: '/api/miniprogram/ckb/lead',
method: 'POST',
data: {
userId,
phone: phone || undefined,
wechatId: wechatId || undefined,
name: (app.globalData.userInfo.nickname || '').trim() || undefined
}
})
wx.hideLoading()
if (res && res.success) {
wx.setStorageSync(leadKey, true)
wx.showToast({ title: res.message || '提交成功', icon: 'success' })
} else {
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
}
} catch (e) {
wx.hideLoading()
wx.showToast({ title: e.message || '提交失败', icon: 'none' })
}
return
}
this.setData({ showLeadModal: true, leadPhone: '' })
},
closeLeadModal() {
this.setData({ showLeadModal: false, leadPhone: '' })
},
onLeadPhoneInput(e) {
this.setData({ leadPhone: (e.detail.value || '').trim() })
},
async submitLead() {
const phone = (this.data.leadPhone || '').trim().replace(/\s/g, '')
if (!phone) {
wx.showToast({ title: '请输入手机号', icon: 'none' })
return
}
if (phone.length < 11) {
wx.showToast({ title: '请输入正确的手机号', icon: 'none' })
return
}
const app = getApp()
const userId = app.globalData.userInfo?.id
const leadKey = userId ? ('karuo_lead_' + userId) : ''
wx.showLoading({ title: '提交中...', mask: true })
try {
const res = await app.request({
url: '/api/miniprogram/ckb/lead',
method: 'POST',
data: {
userId,
phone,
name: (app.globalData.userInfo?.nickname || '').trim() || undefined
}
})
wx.hideLoading()
this.setData({ showLeadModal: false, leadPhone: '' })
if (res && res.success) {
if (leadKey) wx.setStorageSync(leadKey, true)
wx.showToast({ title: res.message || '提交成功,卡若会尽快联系您', icon: 'success' })
} else {
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
}
} catch (e) {
wx.hideLoading()
wx.showToast({ title: e.message || '提交失败', icon: 'none' })
}
},
goToSuperList() {
wx.switchTab({ url: '/pages/match/match' })
},
async loadLatestChapters() {
try {
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
const chapters = (res && res.data) || (res && res.chapters) || []
const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase()
const exclude = c => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
// stitch_soul优先取 isNew 标记的章节;若无则取最近更新的前 10 章(排除序言/尾声/附录)
let candidates = chapters.filter(c => (c.isNew || c.is_new) === true && exclude(c))
if (candidates.length === 0) {
candidates = chapters.filter(exclude)
}
// 解析「第X场」用于倒序最新场次大放在最上方
const sessionNum = (c) => {
const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || ''
const m = title.match(/第\s*(\d+)\s*场/) || title.match(/第(\d+)场/)
if (m) return parseInt(m[1], 10)
const id = c.id != null ? String(c.id) : ''
if (/^\d+$/.test(id)) return parseInt(id, 10)
return 0
}
const latest = candidates
.sort((a, b) => {
const na = sessionNum(a)
const nb = sessionNum(b)
if (na !== nb) return nb - na // 场次倒序:最新在上
return new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0)
})
.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 || ''
const rawContent = (c.content || '').replace(/<[^>]+>/g, '').trim()
// 描述仅用正文摘要,避免 #id 或标题重复;截取 36 字
let desc = ''
if (rawContent && rawContent.length > 0) {
const clean = rawContent.replace(/^#[\d.]+\s*/, '').trim()
desc = clean.length > 36 ? clean.slice(0, 36) + '...' : clean
}
return {
id: c.id,
mid: c.mid ?? c.MID ?? 0,
title,
desc,
price: c.price ?? 1,
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
}
})
this.setData({ latestChapters: latest })
} catch (e) { console.log('[Index] 加载最新新增失败:', e) }
},
goToMemberDetail(e) {
const id = e.currentTarget.dataset.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.loadFeaturedFromServer(),
this.loadSuperMembers(),
this.loadLatestChapters()
])
this.updateUserStatus()
wx.stopPullDownRefresh()
},
onShareAppMessage() {
const ref = app.getMyReferralCode()
return {
title: 'Soul创业派对 - 真实商业故事',
path: ref ? `/pages/index/index?ref=${ref}` : '/pages/index/index'
}
},
onShareTimeline() {
const ref = app.getMyReferralCode()
return { title: 'Soul创业派对 - 真实商业故事', query: ref ? `ref=${ref}` : '' }
}
})