diff --git a/miniprogram/README.md b/miniprogram/README.md index 06118089..2cc11dbf 100644 --- a/miniprogram/README.md +++ b/miniprogram/README.md @@ -35,7 +35,8 @@ miniprogram/ │ ├── purchases/ # 订单页 │ └── settings/ # 设置页 ├── utils/ -│ └── util.js # 工具函数 +│ ├── util.js # 工具函数 +│ └── payment.js # 支付工具 ├── assets/ │ └── icons/ # 图标资源 ├── project.config.json # 项目配置 diff --git a/miniprogram/app.js b/miniprogram/app.js index 92a9ca12..e6522742 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -88,10 +88,7 @@ App({ isSinglePageMode: false, // 更新检测:上次检测时间戳,避免频繁请求 - lastUpdateCheck: 0, - - // 审核模式:后端 /api/miniprogram/config 返回 auditMode=true 时隐藏所有支付相关UI - auditMode: false + lastUpdateCheck: 0 }, onLaunch(options) { @@ -363,7 +360,6 @@ App({ this.globalData.mchId = mpConfig.mchId || this.globalData.mchId this.globalData.withdrawSubscribeTmplId = mpConfig.withdrawSubscribeTmplId || this.globalData.withdrawSubscribeTmplId this.globalData.supportWechat = mpConfig.supportWechat || mpConfig.customerWechat || mpConfig.serviceWechat || '' - this.globalData.auditMode = mpConfig.auditMode || false try { wx.setStorageSync('apiBaseUrl', this.globalData.baseUrl) } catch (_) {} @@ -513,7 +509,7 @@ App({ }, fail: (err) => { const msg = (err && err.errMsg) ? (err.errMsg.indexOf('timeout') !== -1 ? '请求超时,请重试' : '网络异常,请重试') : '网络异常,请重试' - console.warn('[App] request fail:', url, msg) + showError(msg) reject(new Error(msg)) } }) @@ -531,6 +527,7 @@ App({ }) if (!loginRes || !loginRes.code) { console.warn('[App] wx.login 未返回 code') + wx.showToast({ title: '获取登录态失败,请重试', icon: 'none' }) return null } try { @@ -572,13 +569,16 @@ App({ return res.data } } catch (apiError) { - console.warn('[App] API登录失败:', apiError.message) + console.log('[App] API登录失败:', apiError.message) + // 不使用模拟登录,提示用户网络问题 + wx.showToast({ title: '网络异常,请重试', icon: 'none' }) return null } return null } catch (e) { - console.warn('[App] 登录失败:', e) + console.error('[App] 登录失败:', e) + wx.showToast({ title: '登录失败,请重试', icon: 'none' }) return null } }, @@ -630,7 +630,7 @@ App({ return res.data.openId } } catch (e) { - console.warn('[App] 获取openId失败:', e) + console.error('[App] 获取openId失败:', e) } return null @@ -646,7 +646,7 @@ App({ wx.login({ success: resolve, fail: reject }) }) if (!loginRes.code) { - console.warn('[App] loginWithPhone: wx.login 未返回 code') + wx.showToast({ title: '获取登录态失败', icon: 'none' }) return null } const res = await this.request('/api/miniprogram/phone-login', { @@ -677,7 +677,8 @@ App({ return res.data } } catch (e) { - console.warn('[App] 手机号登录失败:', e) + console.log('[App] 手机号登录失败:', e) + wx.showToast({ title: '登录失败,请重试', icon: 'none' }) } return null diff --git a/miniprogram/app.json b/miniprogram/app.json index dcb27c14..9e9f1075 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -6,6 +6,7 @@ "pages/my/my", "pages/read/read", "pages/link-preview/link-preview", + "pages/about/about", "pages/agreement/agreement", "pages/privacy/privacy", "pages/referral/referral", @@ -15,16 +16,13 @@ "pages/addresses/addresses", "pages/addresses/edit", "pages/withdraw-records/withdraw-records", - "pages/wallet/wallet", "pages/vip/vip", "pages/member-detail/member-detail", "pages/mentors/mentors", "pages/mentor-detail/mentor-detail", "pages/profile-show/profile-show", "pages/profile-edit/profile-edit", - "pages/avatar-nickname/avatar-nickname", - "pages/gift-pay/detail", - "pages/gift-pay/list" + "pages/wallet/wallet" ], "window": { "backgroundTextStyle": "light", diff --git a/miniprogram/pages/agreement/agreement.wxml b/miniprogram/pages/agreement/agreement.wxml index a1987185..4060b50c 100644 --- a/miniprogram/pages/agreement/agreement.wxml +++ b/miniprogram/pages/agreement/agreement.wxml @@ -31,7 +31,7 @@ 我们可能适时修订本协议,修订后将在小程序内公示。若您继续使用服务,即视为接受修订后的协议。 七、联系我们 - 如有疑问,请通过 Soul 派对房与我们联系。 + 如有疑问,请通过小程序内「关于作者」或 Soul 派对房与我们联系。 diff --git a/miniprogram/pages/chapters/chapters.js b/miniprogram/pages/chapters/chapters.js index 0d20b06d..b7762e22 100644 --- a/miniprogram/pages/chapters/chapters.js +++ b/miniprogram/pages/chapters/chapters.js @@ -31,10 +31,7 @@ Page({ appendixList: [], // 每日新增章节 - dailyChapters: [], - - // 审核模式 - auditMode: false + dailyChapters: [] }, onLoad() { @@ -186,7 +183,6 @@ Page({ tabBar.setData({ selected: 1 }) } } - this.setData({ auditMode: app.globalData.auditMode }) this.updateUserStatus() this.loadVipStatus() }, diff --git a/miniprogram/pages/chapters/chapters.wxml b/miniprogram/pages/chapters/chapters.wxml index 9f734b15..9c25e577 100644 --- a/miniprogram/pages/chapters/chapters.wxml +++ b/miniprogram/pages/chapters/chapters.wxml @@ -5,7 +5,7 @@ - + 🔍 @@ -17,14 +17,8 @@ - - - - 加载目录中... - - - + 📚 @@ -39,34 +33,9 @@ - - - - - 每日新增 - +{{dailyChapters.length}} - - - - - - {{item.title}} - {{item.dateStr}} · ¥{{item.price}} - - - - - - - - + + + 📖 序言|为什么我每天早上6点在Soul开播? @@ -90,15 +59,14 @@ - {{item.chapters.length || item.chapterCount}}章 + {{item.chapters.length}}章 - + - 加载中... {{chapter.title}} @@ -113,7 +81,6 @@ 免费 已解锁 - 待开放 ¥{{section.price}} @@ -126,8 +93,8 @@ - - + + 📖 尾声|这本书的真实目的 @@ -148,7 +115,6 @@ wx:key="id" bindtap="goToRead" data-id="{{item.id}}" - data-mid="{{item.mid}}" > {{item.title}} diff --git a/miniprogram/pages/chapters/chapters.wxss b/miniprogram/pages/chapters/chapters.wxss index dbf5d2b7..890390e8 100644 --- a/miniprogram/pages/chapters/chapters.wxss +++ b/miniprogram/pages/chapters/chapters.wxss @@ -75,34 +75,6 @@ width: 100%; } -/* ===== 目录加载中 ===== */ -.parts-loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 120rpx 0; - gap: 24rpx; -} - -.parts-loading-spinner { - width: 64rpx; - height: 64rpx; - border: 6rpx solid rgba(255, 255, 255, 0.1); - border-top-color: #00CED1; - border-radius: 50%; - animation: parts-spin 0.8s linear infinite; -} - -.parts-loading-text { - font-size: 28rpx; - color: rgba(255, 255, 255, 0.5); -} - -@keyframes parts-spin { - to { transform: rotate(360deg); } -} - /* ===== 书籍信息卡 ===== */ .book-info-card { display: flex; @@ -174,89 +146,6 @@ box-sizing: border-box; } -/* ===== 每日新增 ===== */ -.daily-section { - margin-bottom: 32rpx; - padding: 24rpx; - background: #1c1c1e; - border-radius: 24rpx; - border: 2rpx solid rgba(255, 255, 255, 0.05); -} - -.daily-header { - display: flex; - align-items: center; - gap: 16rpx; - margin-bottom: 24rpx; -} - -.daily-title { - font-size: 30rpx; - font-weight: 600; - color: #ffffff; -} - -.daily-badge { - font-size: 22rpx; - padding: 4rpx 12rpx; - background: #F6AD55; - color: #ffffff; - border-radius: 20rpx; -} - -.daily-list { - display: flex; - flex-direction: column; - gap: 0; -} - -.daily-item { - display: flex; - align-items: center; - padding: 16rpx 0; - border-bottom: 1rpx solid rgba(255, 255, 255, 0.06); -} - -.daily-item:last-child { - border-bottom: none; -} - -.daily-dot { - width: 12rpx; - height: 12rpx; - border-radius: 50%; - background: rgba(0, 206, 209, 0.6); - margin-right: 20rpx; - flex-shrink: 0; -} - -.daily-content { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 4rpx; -} - -.daily-item-title { - font-size: 26rpx; - color: #ffffff; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.daily-item-meta { - font-size: 22rpx; - color: rgba(255, 255, 255, 0.4); -} - -.daily-arrow { - font-size: 28rpx; - color: rgba(255, 255, 255, 0.4); - margin-left: 16rpx; -} - /* ===== 章节项 ===== */ .chapter-item { display: flex; @@ -450,12 +339,6 @@ margin-left: 16rpx; } -.chapters-loading { - padding: 24rpx; - font-size: 26rpx; - color: rgba(255, 255, 255, 0.5); -} - .chapter-group { background: rgba(28, 28, 30, 0.5); border-radius: 16rpx; diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js index 64ceaa3d..d637900c 100644 --- a/miniprogram/pages/index/index.js +++ b/miniprogram/pages/index/index.js @@ -4,9 +4,9 @@ * 技术支持: 存客宝 */ -console.log('[Index] ===== 首页文件开始加载 =====') - const app = getApp() +const { trackClick } = require('../../utils/trackClick') +const { checkAndExecute } = require('../../utils/ruleEngine') Page({ data: { @@ -20,15 +20,13 @@ Page({ readCount: 0, // 书籍数据 - totalSections: 62, + totalSections: 0, 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: '真实的赚钱' } - ], + // 精选推荐(按热度排行,默认显示3篇,可展开更多) + featuredSections: [], + featuredSectionsAll: [], + featuredExpanded: false, // 最新章节(动态计算) latestSection: null, @@ -47,9 +45,10 @@ Page({ superMembers: [], superMembersLoading: true, - // 最新新增章节(完整列表 + 展示列表,用于展开/折叠) + // 最新新增章节 latestChapters: [], - displayLatestChapters: [], + latestChaptersExpanded: false, + latestChaptersAll: [], // 篇章数(从 bookData 计算) partCount: 0, @@ -57,26 +56,12 @@ Page({ // 加载状态 loading: true, - // 审核模式 - auditMode: false, - // 链接卡若 - 留资弹窗 showLeadModal: false, - leadPhone: '', - - // 展开状态(首页精选/最新) - featuredExpanded: false, - latestExpanded: false, - featuredSectionsFull: [], // 展开时用 book/hot 加载的完整列表 - featuredExpandedLoading: false, - - // 功能配置(搜索开关) - searchEnabled: true + leadPhone: '' }, onLoad(options) { - console.log('[Index] ===== onLoad 触发 =====') - // 获取系统信息 this.setData({ statusBarHeight: app.globalData.statusBarHeight, @@ -85,26 +70,19 @@ Page({ // 处理分享参数(推荐码绑定) 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 触发') - // 设置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() } @@ -114,24 +92,24 @@ Page({ } else if (tabBar) { tabBar.setData({ selected: 0 }) } - } else { - console.log('[Index] TabBar 组件未找到或 getTabBar 方法不存在') } - // 同步审核模式 - this.setData({ auditMode: app.globalData.auditMode }) - // 更新用户状态 this.updateUserStatus() + + // 规则引擎:首页展示时检查(填头像、分享引导等) + checkAndExecute('page_show', this) }, - // 初始化数据:首次进页面并行异步加载,加快首屏展示 initData() { - this.setData({ loading: false }) - this.loadBookData() - this.loadFeaturedFromServer() - this.loadSuperMembers() - this.loadLatestChapters() + Promise.all([ + this.loadBookData(), + this.loadFeaturedFromServer(), + this.loadSuperMembers(), + this.loadLatestChapters() + ]).finally(() => { + this.setData({ loading: false }) + }) }, async loadSuperMembers() { @@ -149,13 +127,8 @@ Page({ avatar: u.avatar || '', isVip: true })) - if (members.length > 0) { - console.log('[Index] 超级个体加载成功:', members.length, '人') - } } - } catch (e) { - console.log('[Index] vip/members 请求失败:', e) - } + } catch (e) {} // 不足 4 个则用有头像的普通用户补充 if (members.length < 4) { try { @@ -172,73 +145,36 @@ Page({ } this.setData({ superMembers: members, superMembersLoading: false }) } catch (e) { - console.log('[Index] 加载超级个体失败:', e) this.setData({ superMembersLoading: false }) } }, - // 从服务端获取精选推荐、最新更新(stitch_soul:book/recommended、book/latest-chapters) + // 从服务端获取精选推荐(按热度排行)和最新更新 async loadFeaturedFromServer() { try { - // 1. 精选推荐:优先用 book/recommended(按阅读量+算法,带 热门/推荐/精选 标签) - let featured = [] + // 1. 精选推荐:从 book/hot 获取热度排行数据 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) => ({ + const hotRes = await app.request({ url: '/api/miniprogram/book/hot?limit=50', silent: true }) + if (hotRes && hotRes.success && Array.isArray(hotRes.data) && hotRes.data.length > 0) { + const tagClassMap = { '热门': 'tag-hot', '推荐': 'tag-rec', '精选': 'tag-rec' } + const all = hotRes.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' + tag: s.tag || '', + tagClass: tagClassMap[s.tag] || 'tag-rec', + hotScore: s.hotScore || s.hot_score || 0, + hotRank: s.hotRank || (i + 1), + price: s.price ?? 1, })) - this.setData({ featuredSections: featured }) + this.setData({ + featuredSectionsAll: all, + featuredSections: all.slice(0, 3), + featuredExpanded: false, + }) } - } catch (e) { console.log('[Index] book/recommended 失败:', e) } - - // 兜底:无 recommended 时从 book/hot 取前3 - 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) { - const tagMap = ['热门', '推荐', '精选'] - featured = hotList.slice(0, 3).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] || '精选', - tagClass: ['tag-hot', 'tag-rec', 'tag-rec'][i] || 'tag-rec' - })) - this.setData({ featuredSections: featured }) - } - } catch (e) { console.log('[Index] book/hot 兜底失败:', e) } - } - 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 }) - } - } + } catch (e) {} // 2. 最新更新:用 book/latest-chapters 取第1条(排除「序言」「尾声」「附录」) try { @@ -260,7 +196,6 @@ Page({ }) } } 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 => { @@ -280,9 +215,7 @@ Page({ }) } } - } catch (e) { - console.log('[Index] 从服务端加载推荐失败:', e) - } + } catch (e) {} }, async loadBookData() { @@ -293,7 +226,7 @@ Page({ const partIds = new Set(chapters.map(c => c.partId || c.part_id || '').filter(Boolean)) this.setData({ bookData: chapters, - totalSections: res.total || chapters.length || 62, + totalSections: res.total || chapters.length || app.globalData.totalSections || 0, partCount: partIds.size || 5 }) } @@ -305,7 +238,7 @@ Page({ // 更新用户状态(已读数 = 用户实际打开过的章节数,仅统计有权限阅读的) updateUserStatus() { const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData - const readCount = Math.min(app.getReadCount(), this.data.totalSections || 62) + const readCount = Math.min(app.getReadCount(), this.data.totalSections || app.globalData.totalSections || 0) this.setData({ isLoggedIn, hasFullBook, @@ -315,35 +248,20 @@ Page({ // 跳转到目录 goToChapters() { + trackClick('home', 'nav_click', '目录') wx.switchTab({ url: '/pages/chapters/chapters' }) }, - async loadFeatureConfig() { - try { - if (app.globalData.features && typeof app.globalData.features.searchEnabled === 'boolean') { - this.setData({ searchEnabled: app.globalData.features.searchEnabled }) - return - } - const res = await app.request({ url: '/api/miniprogram/config', silent: true }) - const features = (res && res.features) || {} - const searchEnabled = features.searchEnabled !== false - if (!app.globalData.features) app.globalData.features = {} - app.globalData.features.searchEnabled = searchEnabled - this.setData({ searchEnabled }) - } catch (e) { - this.setData({ searchEnabled: true }) - } - }, - // 跳转到搜索页 goToSearch() { - if (!this.data.searchEnabled) return + trackClick('home', 'nav_click', '搜索') wx.navigateTo({ url: '/pages/search/search' }) }, // 跳转到阅读页(优先传 mid,与分享逻辑一致) goToRead(e) { const id = e.currentTarget.dataset.id + trackClick('home', 'card_click', 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}` }) @@ -355,10 +273,16 @@ Page({ }, goToVip() { + trackClick('home', 'btn_click', 'VIP') wx.navigateTo({ url: '/pages/vip/vip' }) }, + goToAbout() { + wx.navigateTo({ url: '/pages/about/about' }) + }, + async onLinkKaruo() { + trackClick('home', 'btn_click', '链接卡若') const app = getApp() if (!app.globalData.isLoggedIn || !app.globalData.userInfo) { wx.showModal({ @@ -373,23 +297,31 @@ Page({ 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() + let avatar = (app.globalData.userInfo.avatar || app.globalData.userInfo.avatarUrl || '').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() + if (!avatar) avatar = (profileRes.data.avatar || '').trim() } } catch (e) {} } + if ((!phone && !wechatId) || !avatar) { + wx.showModal({ + title: '完善资料', + content: !avatar ? '请先设置头像和填写联系方式,以便对方联系您' : '请先填写手机号或微信号,以便对方联系您', + confirmText: '去填写', + cancelText: '取消', + success: (res) => { + if (res.confirm) wx.navigateTo({ url: '/pages/profile-edit/profile-edit' }) + } + }) + return + } if (phone || wechatId) { wx.showLoading({ title: '提交中...', mask: true }) try { @@ -405,8 +337,12 @@ Page({ }) wx.hideLoading() if (res && res.success) { - wx.setStorageSync('lead_last_submit_ts', Date.now()) - wx.showToast({ title: res.message || '提交成功', icon: 'success' }) + wx.showModal({ + title: '提交成功', + content: '卡若会主动添加你微信,请注意你的微信消息', + showCancel: false, + confirmText: '好的' + }) } else { wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' }) } @@ -469,11 +405,6 @@ Page({ 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 }) @@ -490,7 +421,6 @@ Page({ wx.hideLoading() this.setData({ showLeadModal: false, leadPhone: '' }) if (res && res.success) { - wx.setStorageSync('lead_last_submit_ts', Date.now()) // 同步手机号到用户资料 try { if (userId) { @@ -519,6 +449,7 @@ Page({ }, async submitLead() { + trackClick('home', 'btn_click', '提交留资') const phone = (this.data.leadPhone || '').trim().replace(/\s/g, '') if (!phone) { wx.showToast({ title: '请输入手机号', icon: 'none' }) @@ -531,75 +462,76 @@ Page({ wx.switchTab({ url: '/pages/match/match' }) }, - // 精选推荐:展开/折叠 - async toggleFeaturedExpanded() { - if (this.data.featuredExpandedLoading) return - 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() { - const expanded = !this.data.latestExpanded - const display = expanded ? this.data.latestChapters : this.data.latestChapters.slice(0, 5) - this.setData({ latestExpanded: expanded, displayLatestChapters: display }) - }, - - // 最新新增:用 latest-chapters 接口(后端按 updated_at 取前 N 条),不拉全量,支持万级文章 async loadLatestChapters() { try { - const res = await app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true }) - const list = (res && res.data) ? res.data : [] + let chapters = app.globalData.bookData || [] + if (!Array.isArray(chapters) || chapters.length === 0) { + const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true }) + 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('附录') - const latest = list - .filter(exclude) - .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: '', // latest-chapters 不返回 content,避免大表全量加载 - price: c.price ?? 1, - dateStr: `${d.getMonth() + 1}/${d.getDate()}` - } - }) - const display = this.data.latestExpanded ? latest : latest.slice(0, 5) - this.setData({ latestChapters: latest, displayLatestChapters: display }) - } catch (e) { console.log('[Index] 加载最新新增失败:', e) } + let candidates = chapters.filter(c => (c.isNew || c.is_new) === true && exclude(c)) + if (candidates.length === 0) { + candidates = chapters.filter(exclude) + } + 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 mapChapter = (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() + 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()}` + } + } + const sorted = 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) + }) + const latestAll = sorted.slice(0, 10).map(mapChapter) + this.setData({ + latestChaptersAll: latestAll, + latestChapters: latestAll.slice(0, 5), + latestChaptersExpanded: false, + }) + } catch (e) {} + }, + + toggleLatestExpand() { + const all = this.data.latestChaptersAll || [] + if (this.data.latestChaptersExpanded) { + this.setData({ latestChapters: all.slice(0, 5), latestChaptersExpanded: false }) + } else { + this.setData({ latestChapters: all, latestChaptersExpanded: true }) + } + }, + + toggleFeaturedExpand() { + const all = this.data.featuredSectionsAll || [] + if (this.data.featuredExpanded) { + this.setData({ featuredSections: all.slice(0, 3), featuredExpanded: false }) + } else { + this.setData({ featuredSections: all, featuredExpanded: true }) + } }, goToMemberDetail(e) { diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index def50a7b..d510110e 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -24,8 +24,8 @@ - - + + 🔍 @@ -38,31 +38,18 @@ - - - - - 阅读进度 - 已读 {{readCount}}/{{totalSections}} - - - - - - + @@ -100,18 +87,14 @@ 成为会员,展示你的项目 - 加入创业派对 → + 加入创业派对 → - - + + 精选推荐 - - {{featuredExpandedLoading ? '加载中...' : (featuredExpanded ? '收起' : '展开更多')}} - {{featuredExpanded ? '▲' : '▼'}} - - {{item.id}} - {{item.tag || '精选'}} + {{item.tag}} {{item.title}} + + {{featuredExpanded ? '收起' : '展开更多'}} + {{featuredExpanded ? '∧' : '∨'}} + - + 最新新增 - - - +{{latestChapters.length}} - - - {{latestExpanded ? '收起' : '展开更多'}} - {{latestExpanded ? '▲' : '▼'}} - + + +{{latestChaptersAll.length || latestChapters.length}} - + - - NEW - {{item.title}} - - - ¥{{item.price}} - + {{item.title}} + + + {{latestChaptersExpanded ? '收起' : '展开更多'}} + {{latestChaptersExpanded ? '∧' : '∨'}} + + diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss index 0462e791..625d679a 100644 --- a/miniprogram/pages/index/index.wxss +++ b/miniprogram/pages/index/index.wxss @@ -689,12 +689,6 @@ margin-bottom: 32rpx; } -.section-header-right { - display: flex; - align-items: center; - gap: 16rpx; -} - .daily-badge-wrap { display: inline-flex; align-items: center; diff --git a/miniprogram/pages/match/match.js b/miniprogram/pages/match/match.js index 11a67471..813e7e96 100644 --- a/miniprogram/pages/match/match.js +++ b/miniprogram/pages/match/match.js @@ -5,7 +5,8 @@ */ const app = getApp() -const { checkAndExecute } = require('../../utils/ruleEngine.js') +const { trackClick } = require('../../utils/trackClick') +const { checkAndExecute } = require('../../utils/ruleEngine') // 默认匹配类型配置 // 找伙伴:真正的匹配功能,匹配数据库中的真实用户 @@ -75,16 +76,13 @@ Page({ // 匹配价格(可配置) matchPrice: 1, - extraMatches: 0, - - auditMode: false + extraMatches: 0 }, onLoad() { wx.showShareMenu({ withShareTimeline: true }) this.setData({ - statusBarHeight: app.globalData.statusBarHeight || 44, - auditMode: app.globalData.auditMode + statusBarHeight: app.globalData.statusBarHeight || 44 }) this.loadMatchConfig() this.loadStoredContact() @@ -107,9 +105,7 @@ Page({ // 加载匹配配置 async loadMatchConfig() { try { - const res = await app.request({ url: '/api/miniprogram/match/config', silent: true, method: 'GET', - method: 'GET' - }) + const res = await app.request({ url: '/api/miniprogram/match/config', silent: true, method: 'GET' }) if (res.success && res.data) { // 更新全局配置,导师顾问类型强制显示「导师顾问」 @@ -200,6 +196,7 @@ Page({ // 选择匹配类型 selectType(e) { + trackClick('match', 'tab_click', e.currentTarget.dataset.type || '类型选择') const typeId = e.currentTarget.dataset.type const type = MATCH_TYPES.find(t => t.id === typeId) this.setData({ @@ -210,6 +207,7 @@ Page({ // 点击匹配按钮 async handleMatchClick() { + trackClick('match', 'btn_click', '开始匹配') const currentType = MATCH_TYPES.find(t => t.id === this.data.selectedType) // 导师顾问:先播匹配动画,动画完成后再跳转(不在此处直接跳) @@ -311,7 +309,7 @@ Page({ confirmText: '去购买', success: (res) => { if (res.confirm) { - wx.switchTab({ url: '/pages/catalog/catalog' }) + wx.switchTab({ url: '/pages/chapters/chapters' }) } } }) @@ -367,7 +365,7 @@ Page({ }, 500) // 1.5-3秒后:导师顾问→跳转;其他类型→弹窗 - const delay = Math.random() * 1500 + 1500 + const delay = Math.random() * 7000 + 3000 setTimeout(() => { clearInterval(timer) this.setData({ isMatching: false }) @@ -416,14 +414,15 @@ Page({ // 从数据库获取真实用户匹配 let matchedUser = null try { - const res = await app.request({ url: '/api/miniprogram/match/users', silent: true, + const res = await app.request({ + url: '/api/miniprogram/match/users', + silent: true, method: 'POST', data: { matchType: this.data.selectedType, userId: app.globalData.userInfo?.id || '' } }) - if (res.success && res.data) { matchedUser = res.data console.log('[Match] 从数据库匹配到用户:', matchedUser.nickname) @@ -433,8 +432,8 @@ Page({ } // 延迟显示结果(模拟匹配过程) - const delay = Math.random() * 2000 + 2000 - setTimeout(() => { + const delay = Math.random() * 7000 + 3000 + const timeoutId = setTimeout(() => { clearInterval(timer) // 如果没有匹配到用户,提示用户 @@ -464,39 +463,9 @@ Page({ // 上报匹配行为到存客宝 this.reportMatch(matchedUser) - }, delay) }, - // 生成模拟匹配数据 - generateMockMatch() { - const nicknames = ['创业先锋', '资源整合者', '私域专家', '导师顾问', '连续创业者'] - const concepts = [ - '专注私域流量运营5年,帮助100+品牌实现从0到1的增长。', - '连续创业者,擅长商业模式设计和资源整合。', - '在Soul分享真实创业故事,希望找到志同道合的合作伙伴。' - ] - const wechats = ['soul_partner_1', 'soul_business_2024', 'soul_startup_fan'] - - const index = Math.floor(Math.random() * nicknames.length) - const currentType = MATCH_TYPES.find(t => t.id === this.data.selectedType) - - return { - id: `user_${Date.now()}`, - nickname: nicknames[index], - avatar: `https://picsum.photos/200/200?random=${Date.now()}`, - tags: ['创业者', '私域运营', currentType?.label || '创业合伙'], - matchScore: Math.floor(Math.random() * 20) + 80, - concept: concepts[index % concepts.length], - wechat: wechats[index % wechats.length], - commonInterests: [ - { icon: '📚', text: '都在读《创业派对》' }, - { icon: '💼', text: '对私域运营感兴趣' }, - { icon: '🎯', text: '相似的创业方向' } - ] - } - }, - // 上报匹配行为 async reportMatch(matchedUser) { try { @@ -515,6 +484,15 @@ Page({ } } }) + // 记录匹配行为到 user_tracks + const uid = app.globalData.userInfo?.id + if (uid) { + app.request('/api/miniprogram/track', { + method: 'POST', + data: { userId: uid, action: 'match', target: matchedUser?.id || '', extraData: { matchType: this.data.selectedType } }, + silent: true + }).catch(() => {}) + } // 匹配后规则:引导填写 MBTI/行业信息 checkAndExecute('after_match', this) } catch (e) { @@ -534,6 +512,7 @@ Page({ // 添加微信好友 handleAddWechat() { + trackClick('match', 'btn_click', '加好友') if (!this.data.currentMatch) return wx.setClipboardData({ @@ -584,6 +563,7 @@ Page({ // 提交加入 async handleJoinSubmit() { + trackClick('match', 'btn_click', '加入提交') const { contactType, phoneNumber, wechatId, joinType, isJoining, canHelp, needHelp } = this.data if (isJoining) return @@ -639,18 +619,16 @@ Page({ this.setData({ showJoinModal: false, joinSuccess: false }) }, 2000) } else { - // 即使API返回失败,也模拟成功(因为已保存本地) - this.setData({ joinSuccess: true }) - setTimeout(() => { - this.setData({ showJoinModal: false, joinSuccess: false }) - }, 2000) + this.setData({ + joinSuccess: false, + joinError: res.error || '提交失败,请稍后重试' + }) } } catch (e) { - // 网络错误时也模拟成功 - this.setData({ joinSuccess: true }) - setTimeout(() => { - this.setData({ showJoinModal: false, joinSuccess: false }) - }, 2000) + this.setData({ + joinSuccess: false, + joinError: e.message || '网络异常,请稍后重试' + }) } finally { this.setData({ isJoining: false }) } @@ -674,6 +652,7 @@ Page({ // 购买匹配次数 async buyMatchCount() { + trackClick('match', 'btn_click', '购买次数') this.setData({ showUnlockModal: false }) try { @@ -727,19 +706,7 @@ Page({ if (e.errMsg && e.errMsg.includes('cancel')) { wx.showToast({ title: '已取消', icon: 'none' }) } else { - // 测试模式 - wx.showModal({ - title: '支付服务暂不可用', - content: '是否使用测试模式购买?', - success: (res) => { - if (res.confirm) { - const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1 - wx.setStorageSync('extra_match_count', extraMatches) - wx.showToast({ title: '测试购买成功', icon: 'success' }) - this.initUserStatus() - } - } - }) + wx.showToast({ title: e.message || '支付失败,请稍后重试', icon: 'none' }) } } }, @@ -750,11 +717,6 @@ Page({ wx.switchTab({ url: '/pages/chapters/chapters' }) }, - // 打开资料修改页(找伙伴右上角图标) - openSettings() { - wx.navigateTo({ url: '/pages/profile-edit/profile-edit' }) - }, - // 阻止事件冒泡 preventBubble() {}, diff --git a/miniprogram/pages/match/match.wxml b/miniprogram/pages/match/match.wxml index 9f602d3a..1d689120 100644 --- a/miniprogram/pages/match/match.wxml +++ b/miniprogram/pages/match/match.wxml @@ -15,15 +15,11 @@ - + 今日免费次数已用完 购买次数 - - - 今日免费次数已用完,明天再来 - @@ -39,16 +35,11 @@ - + 购买次数 ¥1 = 1次匹配 - - - 明天再来 - 今日次数已用完 - 👥 开始匹配 @@ -305,8 +296,8 @@ - - + + 购买匹配次数 diff --git a/miniprogram/pages/my/my.js b/miniprogram/pages/my/my.js index 69c1b188..657966d9 100644 --- a/miniprogram/pages/my/my.js +++ b/miniprogram/pages/my/my.js @@ -77,9 +77,6 @@ Page({ // 我的代付链接 giftList: [], - - // 审核模式 - auditMode: false, }, onLoad() { @@ -95,7 +92,6 @@ Page({ }, onShow() { - this.setData({ auditMode: app.globalData.auditMode }) // 设置TabBar选中状态(根据 matchEnabled 动态设置) if (typeof this.getTabBar === 'function' && this.getTabBar()) { const tabBar = this.getTabBar() @@ -267,8 +263,6 @@ Page({ const newList = list.filter(x => x.id !== item.id) this.setData({ pendingConfirmList: newList }) this.loadPendingConfirm() - this.loadMyEarnings() - this.loadWalletBalance() } if (hasPackage) { @@ -389,8 +383,6 @@ Page({ wx.hideLoading() this.setData({ receivingAll: false }) this.loadPendingConfirm() - this.loadMyEarnings() - this.loadWalletBalance() } }, diff --git a/miniprogram/pages/my/my.wxml b/miniprogram/pages/my/my.wxml index bf489f94..54742ec7 100644 --- a/miniprogram/pages/my/my.wxml +++ b/miniprogram/pages/my/my.wxml @@ -34,9 +34,9 @@ {{userInfo.nickname || '点击设置昵称'}} - {{isVip ? '会员中心' : '成为会员'}} + {{isVip ? '会员中心' : '成为会员'}} - + 会员 匹配 排行 @@ -53,11 +53,11 @@ {{referralCount}} 推荐好友 - + {{earnings === '-' ? '--' : earnings}} 我的收益 - + {{walletBalance > 0 ? '¥' + walletBalance : '0'}} 我的余额 @@ -68,7 +68,7 @@ - + @@ -143,7 +143,7 @@ - + 我的代付链接 @@ -164,7 +164,7 @@ - + 我的订单 diff --git a/miniprogram/pages/my/my.wxss b/miniprogram/pages/my/my.wxss index 57f9fa4a..2d351fe9 100644 --- a/miniprogram/pages/my/my.wxss +++ b/miniprogram/pages/my/my.wxss @@ -59,10 +59,6 @@ .vip-badge-gray { background: rgba(255,255,255,0.2); color: rgba(255,255,255,0.5); } .profile-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 12rpx; } .profile-name-row { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; flex-wrap: wrap; } -.profile-name-actions { display: flex; align-items: center; gap: 16rpx; flex-shrink: 0; } -.profile-edit-btn { display: flex; align-items: center; gap: 8rpx; padding: 8rpx 16rpx; background: rgba(255,255,255,0.08); border-radius: 12rpx; } -.profile-edit-icon { width: 28rpx; height: 28rpx; opacity: 0.7; } -.profile-edit-text { font-size: 24rpx; color: rgba(255,255,255,0.7); } .user-name { font-size: 44rpx; font-weight: bold; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; @@ -182,8 +178,10 @@ .icon-blue .menu-icon-img { width: 32rpx; height: 32rpx; } .icon-gray { background: rgba(156,163,175,0.15); } .icon-gray .menu-icon-img { width: 32rpx; height: 32rpx; } -.icon-gold { background: rgba(200,161,70,0.2); } -.icon-gold .menu-icon-img { width: 32rpx; height: 32rpx; } +.icon-amber { background: rgba(245,158,11,0.2); } +.menu-icon-emoji { font-size: 28rpx; } +.menu-right { display: flex; align-items: center; gap: 12rpx; } +.menu-balance { font-size: 26rpx; color: #4FD1C5; font-weight: 500; } .menu-text { font-size: 28rpx; color: #E5E7EB; font-weight: 500; } .menu-arrow { font-size: 36rpx; color: #9CA3AF; } @@ -253,5 +251,14 @@ .modal-btn-cancel { background: rgba(255,255,255,0.1); color: #fff; } .modal-btn-confirm { background: #4FD1C5; color: #000; font-weight: 600; } +/* 代付链接卡片 */ +.gift-list { display: flex; flex-direction: column; gap: 16rpx; } +.gift-item { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; } +.gift-left { flex: 1; min-width: 0; } +.gift-title { display: block; font-size: 28rpx; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.gift-meta { display: block; font-size: 22rpx; color: #9CA3AF; margin-top: 6rpx; } +.gift-share-btn { display: inline-block; padding: 8rpx 28rpx; background: #4FD1C5; color: #000; font-size: 24rpx; font-weight: 600; border-radius: 20rpx; } +.gift-done { font-size: 24rpx; color: #6B7280; } + /* 底部留白:配合 page padding-bottom,避免内容被 TabBar 遮挡 */ .bottom-space { height: calc(80rpx + env(safe-area-inset-bottom, 0px)); } diff --git a/miniprogram/pages/privacy/privacy.wxml b/miniprogram/pages/privacy/privacy.wxml index 6e4f7a26..cf414ad7 100644 --- a/miniprogram/pages/privacy/privacy.wxml +++ b/miniprogram/pages/privacy/privacy.wxml @@ -34,7 +34,7 @@ 我们可能适时更新本政策,更新后将通过小程序内公示等方式通知您。继续使用即视为接受更新后的政策。 八、联系我们 - 如有隐私相关疑问或投诉,请通过 Soul 派对房与我们联系。 + 如有隐私相关疑问或投诉,请通过小程序内「关于作者」或 Soul 派对房与我们联系。 diff --git a/miniprogram/pages/profile-edit/profile-edit.js b/miniprogram/pages/profile-edit/profile-edit.js index d97b378d..4d86c30c 100644 --- a/miniprogram/pages/profile-edit/profile-edit.js +++ b/miniprogram/pages/profile-edit/profile-edit.js @@ -18,7 +18,6 @@ Page({ isVip: false, avatar: '', nickname: '', - shareCardPath: '', // 分享名片封面图(预生成) mbti: '', mbtiIndex: 0, region: '', @@ -41,15 +40,8 @@ Page({ showAvatarModal: false, }, - onLoad(options) { + onLoad() { this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 }) - wx.showShareMenu({ withShareTimeline: true }) - // 从朋友圈/分享打开且带 id:跳转到名片详情(member-detail) - if (options?.id) { - const ref = options.ref ? `&ref=${options.ref}` : '' - wx.redirectTo({ url: `/pages/member-detail/member-detail?id=${options.id}${ref}` }) - return - } this.loadProfile() }, @@ -93,7 +85,6 @@ Page({ projectIntro: v('projectIntro'), loading: false, }) - setTimeout(() => this.generateShareCard(), 200) } else { this.setData({ loading: false }) } @@ -104,167 +95,6 @@ Page({ goBack() { getApp().goBackOrToHome() }, - // 生成分享名片封面图(参考:头像左+昵称右,分隔线,四栏信息 5:4) - async generateShareCard() { - const { avatar, nickname, region, mbti, industry, position } = this.data - const userId = app.globalData.userInfo?.id - if (!userId) return - try { - const ctx = wx.createCanvasContext('shareCardCanvas', this) - const w = 500 - const h = 400 - const pad = 32 - // 背景(深灰卡片感) - const grd = ctx.createLinearGradient(0, 0, w, h) - grd.addColorStop(0, '#1E293B') - grd.addColorStop(1, '#0F172A') - ctx.setFillStyle(grd) - ctx.fillRect(0, 0, w, h) - // 顶部区域:左头像 + 右昵称 - const avatarSize = 100 - const avatarX = pad + 10 - const avatarY = 50 - const avatarRadius = avatarSize / 2 - const rightStart = avatarX + avatarSize + 28 - const drawAvatar = () => new Promise((resolve) => { - if (avatar && avatar.startsWith('http')) { - wx.downloadFile({ - url: avatar, - success: (res) => { - if (res.statusCode === 200) { - ctx.save() - ctx.beginPath() - ctx.arc(avatarX + avatarRadius, avatarY + avatarRadius, avatarRadius, 0, Math.PI * 2) - ctx.clip() - ctx.drawImage(res.tempFilePath, avatarX, avatarY, avatarSize, avatarSize) - ctx.restore() - } else { - this.drawAvatarPlaceholder(ctx, avatarX, avatarY, avatarSize, nickname) - } - resolve() - }, - fail: () => { - this.drawAvatarPlaceholder(ctx, avatarX, avatarY, avatarSize, nickname) - resolve() - }, - }) - } else { - this.drawAvatarPlaceholder(ctx, avatarX, avatarY, avatarSize, nickname) - resolve() - } - }) - await drawAvatar() - ctx.setStrokeStyle('rgba(94,234,212,0.5)') - ctx.setLineWidth(2) - ctx.beginPath() - ctx.arc(avatarX + avatarRadius, avatarY + avatarRadius, avatarRadius, 0, Math.PI * 2) - ctx.stroke() - // 右侧:昵称 + 个人名片 - const displayName = (nickname || '').trim() || '创业者' - ctx.setFillStyle('#ffffff') - ctx.setFontSize(26) - ctx.setTextAlign('left') - ctx.fillText(displayName, rightStart, avatarY + 36) - ctx.setFillStyle('#94A3B8') - ctx.setFontSize(13) - ctx.fillText('个人名片', rightStart, avatarY + 62) - // 分隔线 - const divY = 168 - ctx.setStrokeStyle('rgba(255,255,255,0.08)') - ctx.setLineWidth(1) - ctx.beginPath() - ctx.moveTo(pad, divY) - ctx.lineTo(w - pad, divY) - ctx.stroke() - // 底部四栏:地区 | MBTI,行业 | 职位 - const labelGray = '#64748B' - const valueWhite = '#F1F5F9' - const rowH = 52 - const colW = (w - pad * 2) / 2 - const truncate = (text, maxW) => { - if (!text) return '' - ctx.setFontSize(15) - let m = ctx.measureText(text) - if (m.width <= maxW) return text - for (let i = text.length - 1; i > 0; i--) { - const t = text.slice(0, i) + '…' - if (ctx.measureText(t).width <= maxW) return t - } - return text[0] + '…' - } - const maxValW = colW - 16 - const items = [ - { label: '地区', value: truncate((region || '').trim() || '未填写', maxValW), x: pad }, - { label: 'MBTI', value: (mbti || '').trim() || '未填写', x: pad + colW }, - { label: '行业', value: truncate((industry || '').trim() || '未填写', maxValW), x: pad }, - { label: '职位', value: truncate((position || '').trim() || '未填写', maxValW), x: pad + colW }, - ] - items.forEach((item, i) => { - const row = Math.floor(i / 2) - const baseY = divY + 36 + row * rowH - ctx.setFillStyle(labelGray) - ctx.setFontSize(12) - ctx.fillText(item.label, item.x, baseY - 8) - ctx.setFillStyle(valueWhite) - ctx.setFontSize(15) - ctx.fillText(item.value, item.x, baseY + 14) - }) - ctx.draw(true, () => { - wx.canvasToTempFilePath({ - canvasId: 'shareCardCanvas', - destWidth: 500, - destHeight: 400, - success: (res) => { - this.setData({ shareCardPath: res.tempFilePath }) - }, - }, this) - }) - } catch (e) { - console.warn('[ShareCard] 生成失败:', e) - } - }, - - drawAvatarPlaceholder(ctx, x, y, size, nickname) { - ctx.setFillStyle('rgba(94,234,212,0.2)') - ctx.beginPath() - ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2) - ctx.fill() - ctx.setFillStyle('#5EEAD4') - ctx.setFontSize(size * 0.42) - ctx.setTextAlign('center') - ctx.fillText((nickname || '?')[0], x + size / 2, y + size / 2 + size * 0.14) - }, - - onShareAppMessage() { - const ref = app.getMyReferralCode() - const userId = app.globalData.userInfo?.id - const nickname = (this.data.nickname || '').trim() || '我' - const path = userId - ? (ref ? `/pages/member-detail/member-detail?id=${userId}&ref=${ref}` : `/pages/member-detail/member-detail?id=${userId}`) - : (ref ? `/pages/profile-edit/profile-edit?ref=${ref}` : '/pages/profile-edit/profile-edit') - const result = { - title: `${nickname}为您分享名片`, - path, - } - if (this.data.shareCardPath) result.imageUrl = this.data.shareCardPath - return result - }, - - onShareTimeline() { - const ref = app.getMyReferralCode() - const userId = app.globalData.userInfo?.id - const nickname = (this.data.nickname || '').trim() || '我' - const query = userId - ? (ref ? `id=${userId}&ref=${ref}` : `id=${userId}`) - : (ref ? `ref=${ref}` : '') - const result = { - title: `${nickname}为您分享名片`, - query: query || '', - } - if (this.data.shareCardPath) result.imageUrl = this.data.shareCardPath - return result - }, - onNicknameInput(e) { this.setData({ nickname: e.detail.value }) }, onNicknameChange(e) { this.setData({ nickname: e.detail.value }) }, onRegionInput(e) { this.setData({ region: e.detail.value }) }, @@ -344,7 +174,6 @@ Page({ } wx.hideLoading() wx.showToast({ title: '头像已更新', icon: 'success' }) - setTimeout(() => this.generateShareCard(), 200) } catch (e) { wx.hideLoading() wx.showToast({ title: e.message || '上传失败', icon: 'none' }) @@ -394,7 +223,6 @@ Page({ } wx.hideLoading() wx.showToast({ title: '头像已更新', icon: 'success' }) - setTimeout(() => this.generateShareCard(), 200) } catch (err) { wx.hideLoading() wx.showToast({ title: err.message || '上传失败,请重试', icon: 'none' }) diff --git a/miniprogram/pages/profile-edit/profile-edit.wxml b/miniprogram/pages/profile-edit/profile-edit.wxml index 8e596269..8c6d57ad 100644 --- a/miniprogram/pages/profile-edit/profile-edit.wxml +++ b/miniprogram/pages/profile-edit/profile-edit.wxml @@ -146,9 +146,6 @@ - - - diff --git a/miniprogram/pages/profile-edit/profile-edit.wxss b/miniprogram/pages/profile-edit/profile-edit.wxss index b623fca1..ea0ad739 100644 --- a/miniprogram/pages/profile-edit/profile-edit.wxss +++ b/miniprogram/pages/profile-edit/profile-edit.wxss @@ -1,9 +1,4 @@ /* 资料编辑 - comprehensive_profile_editor_v1_1 | 配色 enhanced,input/textarea 用 view 包裹 */ - -/* 分享名片 canvas:隐藏,仅用于生成图片 */ -.share-card-canvas { - position: fixed; left: -9999px; top: 0; width: 500px; height: 400px; -} .page { background: #050B14; min-height: 100vh; color: #fff; width: 100%; box-sizing: border-box; overflow-x: hidden; @@ -168,11 +163,8 @@ .btn-choose-avatar { width: 100%; height: 88rpx; - margin: 0; - padding: 0; - display: flex; - align-items: center; - justify-content: center; + line-height: 88rpx; + text-align: center; background: #5EEAD4; color: #050B14; font-size: 30rpx; diff --git a/miniprogram/pages/read/read.js b/miniprogram/pages/read/read.js index 6632bea1..0214dce8 100644 --- a/miniprogram/pages/read/read.js +++ b/miniprogram/pages/read/read.js @@ -77,14 +77,10 @@ Page({ _lastScrollTop: 0, // 章节 mid(扫码/海报分享用,便于分享 path 带 mid) - sectionMid: null, - - // 审核模式 - auditMode: false + sectionMid: null }, async onLoad(options) { - this.setData({ auditMode: app.globalData.auditMode }) wx.showShareMenu({ withShareTimeline: true }) // 预加载 linkTags、linkedMiniprograms、persons(供 onLinkTagTap / onMentionTap 和内容自动匹配用) diff --git a/miniprogram/pages/read/read.wxml b/miniprogram/pages/read/read.wxml index 6b8f2c79..3d32073f 100644 --- a/miniprogram/pages/read/read.wxml +++ b/miniprogram/pages/read/read.wxml @@ -89,7 +89,7 @@ 📣 分享给好友 - + 🎁 代付分享 @@ -98,7 +98,7 @@ 生成海报 - @@ -169,18 +169,18 @@ 🔒 - 解锁完整内容 - 完整内容即将开放 - 已阅读50%,购买后继续阅读 - 该内容正在准备中,敬请期待 + 解锁完整内容 + 已阅读50%,购买后继续阅读 - - + + + 购买本章 ¥{{section && section.price != null ? section.price : sectionPrice}} + @@ -193,7 +193,7 @@ - 分享给好友一起学习,还能赚取佣金 + 分享给好友一起学习,还能赚取佣金 @@ -250,7 +250,7 @@ - - - + + 选择充值金额 当前已选 ¥{{selectedAmount}} @@ -47,8 +47,8 @@ - - + + 充值 diff --git a/miniprogram/pages/wallet/wallet.wxss b/miniprogram/pages/wallet/wallet.wxss index e1ea463a..d1800187 100644 --- a/miniprogram/pages/wallet/wallet.wxss +++ b/miniprogram/pages/wallet/wallet.wxss @@ -5,6 +5,7 @@ padding-bottom: 64rpx; } +/* 导航栏 */ .nav-bar { position: fixed; top: 0; @@ -45,6 +46,7 @@ width: 100%; } +/* 余额卡片 - 渐变背景 */ .balance-card { margin: 24rpx 24rpx 32rpx; background: linear-gradient(135deg, #1c1c1e 0%, rgba(56, 189, 172, 0.15) 100%); @@ -84,6 +86,7 @@ color: rgba(255, 255, 255, 0.4); } +/* 区块标题 */ .section { margin: 0 24rpx 32rpx; } @@ -104,6 +107,7 @@ color: rgba(255, 255, 255, 0.45); } +/* 金额选择卡片 */ .amount-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -164,6 +168,7 @@ color: rgba(213, 255, 250, 0.72); } +/* 操作按钮 */ .action-row { display: flex; gap: 24rpx; @@ -183,7 +188,13 @@ background: #38bdac; color: #0a0a0a; } +.btn-refund { + background: #1c1c1e; + color: rgba(255, 255, 255, 0.9); + border: 2rpx solid rgba(56, 189, 172, 0.4); +} +/* 交易记录 */ .transactions { background: #1c1c1e; border-radius: 24rpx; diff --git a/miniprogram/utils/readingTracker.js b/miniprogram/utils/readingTracker.js index a10e5731..2962bb1c 100644 --- a/miniprogram/utils/readingTracker.js +++ b/miniprogram/utils/readingTracker.js @@ -170,10 +170,10 @@ class ReadingTracker { const userId = app.globalData.userInfo?.id if (!userId) return - // 计算本次上报的时长(仅发送增量 delta,后端会累加,避免重复累加导致阅读分钟数异常) + // 计算本次上报的时长 const now = Date.now() - const delta = Math.round((now - this.activeTracker.lastScrollTime) / 1000) - this.activeTracker.totalDuration += delta + const duration = Math.round((now - this.activeTracker.lastScrollTime) / 1000) + this.activeTracker.totalDuration += duration this.activeTracker.lastScrollTime = now try { @@ -183,7 +183,7 @@ class ReadingTracker { userId, sectionId: this.activeTracker.sectionId, progress: this.activeTracker.maxProgress, - duration: Math.max(0, delta), + duration: this.activeTracker.totalDuration, status: this.activeTracker.isCompleted ? 'completed' : 'reading', completedAt: this.activeTracker.completedAt } diff --git a/miniprogram/utils/ruleEngine.js b/miniprogram/utils/ruleEngine.js index 925e802e..506f787c 100644 --- a/miniprogram/utils/ruleEngine.js +++ b/miniprogram/utils/ruleEngine.js @@ -1,7 +1,6 @@ /** * Soul创业派对 - 用户旅程规则引擎 * 从后端 /api/miniprogram/user-rules 读取启用的规则,按场景触发引导 - * 稳定版兼容:readCount 用 getReadCount(),hasPurchasedFull 用 hasFullBook,完善头像跳 avatar-nickname * * trigger → scene 映射: * 注册 → after_login @@ -86,7 +85,6 @@ function getRuleInfo(rules, triggerName) { return rules.find(r => r.trigger === triggerName) } -// 稳定版:跳转 avatar-nickname(与 _ensureProfileCompletedAfterLogin 一致) function checkRule_FillAvatar(rules) { if (!isRuleEnabled(rules, '注册')) return null const user = getUserInfo() @@ -102,7 +100,7 @@ function checkRule_FillAvatar(rules) { title: info?.title || '完善个人信息', message: info?.description || '设置头像和昵称,让其他创业者更容易认识你', action: 'navigate', - target: '/pages/avatar-nickname/avatar-nickname' + target: '/pages/profile-edit/profile-edit' } } @@ -140,12 +138,11 @@ function checkRule_FillProfile(rules) { } } -// 稳定版兼容:readCount 用 getReadCount() function checkRule_ShareAfter5Chapters(rules) { if (!isRuleEnabled(rules, '累计浏览5章节')) return null const user = getUserInfo() if (!user.id) return null - const readCount = (typeof app.getReadCount === 'function' ? app.getReadCount() : (app.globalData.readCount || 0)) + const readCount = app.globalData.readCount || 0 if (readCount < 5) return null if (isInCooldown('share_after_5')) return null setCooldown('share_after_5') @@ -159,12 +156,11 @@ function checkRule_ShareAfter5Chapters(rules) { } } -// 稳定版兼容:hasPurchasedFull 用 hasFullBook function checkRule_FillVipInfo(rules) { if (!isRuleEnabled(rules, '完成付款')) return null const user = getUserInfo() if (!user.id) return null - if (!(app.globalData.hasFullBook || app.globalData.hasPurchasedFull)) return null + if (!app.globalData.hasPurchasedFull) return null if (user.wechatId && user.address) return null if (isInCooldown('fill_vip_info')) return null setCooldown('fill_vip_info') diff --git a/miniprogram/utils/trackClick.js b/miniprogram/utils/trackClick.js index 332bdef1..557edb9c 100644 --- a/miniprogram/utils/trackClick.js +++ b/miniprogram/utils/trackClick.js @@ -9,11 +9,11 @@ const app = getApp() */ function trackClick(module, action, target, extra) { const userId = app.globalData.userInfo?.id || '' - app.request({ - url: '/api/miniprogram/track', + if (!userId) return + app.request('/api/miniprogram/track', { method: 'POST', data: { - userId: userId || undefined, + userId, action, target, extraData: Object.assign({ module, page: module }, extra || {}) diff --git a/miniprogram/utils/util.js b/miniprogram/utils/util.js index 3242cc94..5a35effc 100644 --- a/miniprogram/utils/util.js +++ b/miniprogram/utils/util.js @@ -32,18 +32,6 @@ const formatMoney = (amount, decimals = 2) => { return Number(amount).toFixed(decimals) } -/** - * 格式化统计数字:≥1万显示 x.xw,≥1千显示 x.xk,否则原样 - * @param {number} n - 原始数字 - * @returns {string} - */ -const formatStatNum = n => { - const num = Number(n) || 0 - if (num >= 10000) return (num / 10000).toFixed(1).replace(/\.0$/, '') + 'w' - if (num >= 1000) return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k' - return String(num) -} - // 防抖函数 const debounce = (fn, delay = 300) => { let timer = null @@ -187,7 +175,6 @@ module.exports = { formatTime, formatDate, formatMoney, - formatStatNum, formatNumber, debounce, throttle,