/** * 卡若创业派对 - 首页 * 开发: 卡若 * 技术支持: 存客宝 */ console.log('[Index] ===== 首页文件开始加载 =====') const app = getApp() const { trackClick } = require('../../utils/trackClick') Page({ data: { // 系统信息 statusBarHeight: 44, navBarHeight: 88, // 用户信息 isLoggedIn: false, hasFullBook: false, readCount: 0, // 书籍数据 totalSections: 62, bookData: [], // 推荐章节 featuredSections: [ { id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', tagClass: 'tag-free', part: '真实的人' }, { id: '3.1', title: '3000万流水如何跑出来', tag: '热门', tagClass: 'tag-pink', part: '真实的行业' }, { id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', tagClass: 'tag-purple', part: '真实的赚钱' } ], // 最新章节(动态计算) 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: [], displayLatestChapters: [], // 篇章数(从 bookData 计算) partCount: 0, // 加载状态 loading: true, // 链接卡若 - 留资弹窗 showLeadModal: false, leadPhone: '', showPrivacyModal: false, // 展开状态(首页精选/最新) featuredExpanded: false, latestExpanded: false, featuredSectionsFull: [], // 展开时用 book/hot 加载的完整列表 featuredExpandedLoading: false, // 功能配置(搜索开关) searchEnabled: true, // 审核模式:隐藏支付相关入口 auditMode: false }, 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.loadFeatureConfig() this.initData() }, onShow() { console.log('[Index] onShow 触发') this.setData({ auditMode: app.globalData.auditMode || false }) // 设置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 会员和普通用户,合并后取前 4 个(VIP 优先) const [vipRes, usersRes] = await Promise.all([ app.request({ url: '/api/miniprogram/vip/members', silent: true }).catch(() => null), app.request({ url: '/api/miniprogram/users?limit=20', silent: true }).catch(() => null) ]) let members = [] if (vipRes && vipRes.success && Array.isArray(vipRes.data) && vipRes.data.length > 0) { members = vipRes.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, '人') } if (members.length < 4 && usersRes && usersRes.success && Array.isArray(usersRes.data)) { const existIds = new Set(members.map(m => m.id)) const extra = usersRes.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) } this.setData({ superMembers: members, superMembersLoading: false }) } catch (e) { console.log('[Index] 加载超级个体失败:', e) this.setData({ superMembersLoading: false }) } }, // 精选推荐 + 最新更新 + 最新列表:一次请求 recommended + latest-chapters,避免重复 async loadFeaturedAndLatest() { try { const excludeFixed = (c) => { const pt = (c.part_title || c.partTitle || '').toLowerCase() return !pt.includes('序言') && !pt.includes('尾声') && !pt.includes('附录') } const toSection = (s, i, tagMap = ['热门', '推荐', '精选']) => ({ 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: s.tag || tagMap[i] || '精选', tagClass: ['tag-hot', 'tag-rec', 'tag-rec'][i] || 'tag-rec' }) const [recRes, latestRes] = await Promise.all([ app.request({ url: '/api/miniprogram/book/recommended', silent: true }).catch(() => null), app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true }).catch(() => null) ]) // 1. 精选推荐(recommended → hot 兜底) let featured = [] if (recRes && recRes.success && Array.isArray(recRes.data) && recRes.data.length > 0) { featured = recRes.data.map((s, i) => toSection(s, i)) } if (featured.length === 0) { try { const hotRes = await app.request({ url: '/api/miniprogram/book/hot?limit=10', silent: true }) const hotList = (hotRes && hotRes.data) ? hotRes.data : [] if (hotList.length > 0) featured = hotList.slice(0, 3).map((s, i) => toSection(s, i)) } catch (e) { console.log('[Index] book/hot 兜底失败:', e) } } if (featured.length > 0) this.setData({ featuredSections: featured }) // 2. 最新更新 + 最新列表(共用 latest-chapters 数据) const rawList = (latestRes && latestRes.data) ? latestRes.data : [] const latestList = rawList.filter(excludeFixed) 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 || '' } }) } 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 || [] app.globalData.totalSections = total || 62 this.setData({ totalSections: app.globalData.totalSections, partCount: parts.length || 5 }) } } catch (e) { this.setData({ totalSections: app.globalData.totalSections || 62, partCount: 5 }) } }, // 更新用户状态(已读数 = 用户实际打开过的章节数,仅统计有权限阅读的) updateUserStatus() { const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData const readCount = Math.min(app.getReadCount(), this.data.totalSections || 62) this.setData({ isLoggedIn, hasFullBook, readCount }) }, // 跳转到目录 goToChapters() { trackClick('home', 'nav_click', '阅读进度') wx.switchTab({ url: '/pages/chapters/chapters' }) }, async loadFeatureConfig() { try { const hasCachedFeatures = app.globalData.features && typeof app.globalData.features.searchEnabled === 'boolean' if (hasCachedFeatures) { this.setData({ searchEnabled: app.globalData.features.searchEnabled, auditMode: app.globalData.auditMode || false }) return } const res = await app.getConfig() const features = (res && res.features) || {} const mp = (res && res.mpConfig) || {} const searchEnabled = features.searchEnabled !== false const auditMode = !!mp.auditMode if (!app.globalData.features) app.globalData.features = {} app.globalData.features.searchEnabled = searchEnabled app.globalData.auditMode = auditMode this.setData({ searchEnabled, auditMode }) } catch (e) { this.setData({ searchEnabled: true, auditMode: app.globalData.auditMode || false }) } }, // 跳转到搜索页 goToSearch() { if (!this.data.searchEnabled) return 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 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() { trackClick('home', 'btn_click', '加入创业派对') wx.navigateTo({ url: '/pages/vip/vip' }) }, async onLinkKaruo() { trackClick('home', 'btn_click', '链接卡若') 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 // 2 分钟内只能点一次(与后端限频一致) const leadLastTs = wx.getStorageSync('lead_last_submit_ts') || 0 if (Date.now() - leadLastTs < 2 * 60 * 1000) { wx.showToast({ title: '操作太频繁,请2分钟后再试', icon: 'none' }) return } 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) { wx.showLoading({ title: '提交中...', mask: true }) try { const res = await app.request({ url: '/api/miniprogram/ckb/index-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('lead_last_submit_ts', Date.now()) 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: '', showPrivacyModal: false }) }, // 阻止弹窗内部点击事件冒泡到遮罩层 stopPropagation() {}, onLeadPhoneInput(e) { this.setData({ leadPhone: (e.detail.value || '').trim() }) }, // 微信隐私协议同意(getPhoneNumber 需先同意) onAgreePrivacyForLead() { if (app._privacyResolve) { app._privacyResolve({ buttonId: 'agree-privacy-btn', event: 'agree' }) app._privacyResolve = null } this.setData({ showPrivacyModal: false }) }, // 一键获取手机号(微信能力),成功后直接提交链接卡若 async onGetPhoneNumberForLead(e) { if (e.detail.errMsg !== 'getPhoneNumber:ok') { wx.showToast({ title: '未获取到手机号,请手动输入', icon: 'none' }) return } const code = e.detail.code if (!code) { wx.showToast({ title: '获取失败,请手动输入', icon: 'none' }) return } const app = getApp() const userId = app.globalData.userInfo?.id wx.showLoading({ title: '获取中...', mask: true }) try { const res = await app.request({ url: '/api/miniprogram/phone', method: 'POST', data: { code, userId } }) wx.hideLoading() if (res && res.success && res.phoneNumber) { await this._submitLeadWithPhone(res.phoneNumber) } else { wx.showToast({ title: '获取失败,请手动输入', icon: 'none' }) } } catch (err) { wx.hideLoading() wx.showToast({ title: err.message || '获取失败,请手动输入', icon: 'none' }) } }, // 内部:用手机号提交链接卡若(一键获取与手动输入共用) async _submitLeadWithPhone(phone) { const p = (phone || '').trim().replace(/\s/g, '') if (!p || p.length < 11) { wx.showToast({ title: '请输入正确的手机号', icon: 'none' }) return } const leadLastTs = wx.getStorageSync('lead_last_submit_ts') || 0 if (Date.now() - leadLastTs < 2 * 60 * 1000) { wx.showToast({ title: '操作太频繁,请2分钟后再试', icon: 'none' }) return } const app = getApp() const userId = app.globalData.userInfo?.id wx.showLoading({ title: '提交中...', mask: true }) try { const res = await app.request({ url: '/api/miniprogram/ckb/index-lead', method: 'POST', data: { userId, phone: p, name: (app.globalData.userInfo?.nickname || '').trim() || undefined, }, }) wx.hideLoading() this.setData({ showLeadModal: false, leadPhone: '' }) if (res && res.success) { wx.setStorageSync('lead_last_submit_ts', Date.now()) // 同步手机号到用户资料 try { if (userId) { await app.request({ url: '/api/miniprogram/user/profile', method: 'POST', data: { userId, phone: p }, }) if (app.globalData.userInfo) { app.globalData.userInfo.phone = p wx.setStorageSync('userInfo', app.globalData.userInfo) } wx.setStorageSync('user_phone', p) } } catch (e) { console.log('[Index] 同步手机号到用户资料失败:', e && e.message) } 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' }) } }, async submitLead() { const phone = (this.data.leadPhone || '').trim().replace(/\s/g, '') if (!phone) { wx.showToast({ title: '请输入手机号', icon: 'none' }) return } await this._submitLeadWithPhone(phone) }, goToSuperList() { wx.switchTab({ url: '/pages/match/match' }) }, // 精选推荐:展开/折叠 async toggleFeaturedExpanded() { if (this.data.featuredExpandedLoading) return trackClick('home', 'tab_click', this.data.featuredExpanded ? '精选收起' : '精选展开') if (this.data.featuredExpanded) { const collapsed = this.data.featuredSectionsFull.length > 0 ? this.data.featuredSectionsFull.slice(0, 3) : this.data.featuredSections this.setData({ featuredExpanded: false, featuredSections: collapsed }) return } if (this.data.featuredSectionsFull.length > 0) { this.setData({ featuredExpanded: true, featuredSections: this.data.featuredSectionsFull }) return } this.setData({ featuredExpandedLoading: true }) try { const res = await app.request({ url: '/api/miniprogram/book/hot?limit=50', silent: true }) const list = (res && res.data) ? res.data : [] const tagMap = ['热门', '推荐', '精选'] const full = list.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: tagMap[i % 3] || '精选', tagClass: ['tag-hot', 'tag-rec', 'tag-rec'][i % 3] || 'tag-rec' })) this.setData({ featuredSectionsFull: full, featuredSections: full, featuredExpanded: true, featuredExpandedLoading: false }) } catch (e) { console.log('[Index] 加载精选更多失败:', e) this.setData({ featuredExpandedLoading: false }) } }, // 最新新增:展开/折叠(默认 5 条,点击展开剩余) toggleLatestExpanded() { trackClick('home', 'tab_click', this.data.latestExpanded ? '最新收起' : '最新展开') const expanded = !this.data.latestExpanded const display = expanded ? this.data.latestChapters : this.data.latestChapters.slice(0, 5) this.setData({ latestExpanded: expanded, displayLatestChapters: display }) }, goToMemberDetail(e) { const id = e.currentTarget.dataset.id 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}` : '' } } })