/** * Soul创业派对 - 找伙伴页 * 按H5网页端完全重构 * 开发: 卡若 */ const app = getApp() // 默认匹配类型配置 // 找伙伴:真正的匹配功能,匹配数据库中的真实用户 // 资源对接:需要登录+购买章节才能使用,填写2项信息(我能帮到你什么、我需要什么帮助) // 导师顾问:跳转到存客宝添加微信 // 团队招募:跳转到存客宝添加微信 let MATCH_TYPES = [ { id: 'partner', label: '找伙伴', matchLabel: '找伙伴', icon: '⭐', matchFromDB: true, showJoinAfterMatch: false }, { id: 'investor', label: '资源对接', matchLabel: '资源对接', icon: '👥', matchFromDB: true, showJoinAfterMatch: true, requirePurchase: true }, { id: 'mentor', label: '导师顾问', matchLabel: '立即咨询', icon: '❤️', matchFromDB: true, showJoinAfterMatch: true }, { id: 'team', label: '团队招募', matchLabel: '团队招募', icon: '🎮', matchFromDB: true, showJoinAfterMatch: true } ] let FREE_MATCH_LIMIT = 3 // 每日免费匹配次数 Page({ data: { statusBarHeight: 44, // 匹配类型 matchTypes: MATCH_TYPES, selectedType: 'partner', currentTypeLabel: '找伙伴', // 用户状态 isLoggedIn: false, hasPurchased: false, hasFullBook: false, // 匹配次数 todayMatchCount: 0, totalMatchesAllowed: FREE_MATCH_LIMIT, matchesRemaining: FREE_MATCH_LIMIT, showQuotaExhausted: false, needPayToMatch: false, // 匹配状态 isMatching: false, matchAttempts: 0, currentMatch: null, // 加入弹窗 showJoinModal: false, joinType: null, joinTypeLabel: '', contactType: 'phone', phoneNumber: '', wechatId: '', userPhone: '', isJoining: false, joinSuccess: false, joinError: '', needBindFirst: false, // 资源对接表单 canHelp: '', needHelp: '', goodAt: '', // 解锁弹窗 showUnlockModal: false, // 手机号绑定弹窗(一键加好友前校验) showBindPhoneModal: false, pendingAddWechatAfterBind: false, bindPhoneInput: '', showMatchPhoneManualInput: false, // 登录弹窗(未登录时点击匹配弹出) showLoginModal: false, isLoggingIn: false, agreeProtocol: false, // 匹配价格(可配置) matchPrice: 1, extraMatches: 0 }, onLoad() { this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 }) this.loadMatchConfig() this.loadStoredContact() this.refreshMatchCountAndStatus() }, onShow() { if (typeof this.getTabBar === 'function' && this.getTabBar()) { const tabBar = this.getTabBar() if (tabBar.updateSelected) { tabBar.updateSelected() } else { tabBar.setData({ selected: 2 }) } } this.loadStoredContact() this.refreshMatchCountAndStatus() }, // 加载匹配配置 async loadMatchConfig() { try { const res = await app.request('/api/miniprogram/match/config', { method: 'GET' }) if (res.success && res.data) { // 更新全局配置 MATCH_TYPES = res.data.matchTypes || MATCH_TYPES FREE_MATCH_LIMIT = res.data.freeMatchLimit || FREE_MATCH_LIMIT const matchPrice = res.data.matchPrice || 1 this.setData({ matchTypes: MATCH_TYPES, totalMatchesAllowed: FREE_MATCH_LIMIT, matchPrice: matchPrice }) console.log('[Match] 加载匹配配置成功:', { types: MATCH_TYPES.length, freeLimit: FREE_MATCH_LIMIT, price: matchPrice }) } } catch (e) { console.log('[Match] 加载匹配配置失败,使用默认配置:', e) } }, // 加载本地存储的联系方式(含用户资料的手机号、微信号) loadStoredContact() { const ui = app.globalData.userInfo || {} const phone = wx.getStorageSync('user_phone') || ui.phone || '' const wechat = wx.getStorageSync('user_wechat') || ui.wechat || ui.wechatId || '' this.setData({ phoneNumber: phone, wechatId: wechat, userPhone: phone }) }, // 从服务端刷新匹配配额并初始化用户状态(前后端双向校验,服务端为权威) async refreshMatchCountAndStatus() { if (app.globalData.isLoggedIn && app.globalData.userInfo?.id) { try { const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(app.globalData.userInfo.id)}`) if (res.success && res.data) { app.globalData.matchCount = res.data.matchCount ?? 0 app.globalData.matchQuota = res.data.matchQuota || null } } catch (e) { console.log('[Match] 拉取 matchQuota 失败:', e) } } this.initUserStatus() }, // 初始化用户状态(matchQuota 服务端纯计算:订单+match_records) initUserStatus() { const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData const quota = app.globalData.matchQuota // 今日剩余次数、今日已用:来自服务端 matchQuota(未登录无法计算,不能显示已用完) const remainToday = quota?.remainToday ?? 0 const matchesUsedToday = quota?.matchesUsedToday ?? 0 const purchasedRemain = quota?.purchasedRemain ?? 0 const totalMatchesAllowed = hasFullBook ? 999999 : (quota ? remainToday + matchesUsedToday : FREE_MATCH_LIMIT) // 仅登录且服务端返回配额时,才判断是否已用完;未登录时显示「开始匹配」 const needPayToMatch = isLoggedIn && !hasFullBook && (quota ? remainToday <= 0 : false) const showQuotaExhausted = isLoggedIn && !hasFullBook && (quota ? remainToday <= 0 : false) this.setData({ isLoggedIn, hasFullBook, hasPurchased: true, todayMatchCount: matchesUsedToday, totalMatchesAllowed, matchesRemaining: hasFullBook ? 999999 : (isLoggedIn && quota ? remainToday : (isLoggedIn ? 0 : FREE_MATCH_LIMIT)), needPayToMatch, showQuotaExhausted, extraMatches: purchasedRemain }) }, // 选择匹配类型 selectType(e) { const typeId = e.currentTarget.dataset.type const type = MATCH_TYPES.find(t => t.id === typeId) this.setData({ selectedType: typeId, currentTypeLabel: type?.matchLabel || type?.label || '创业伙伴' }) }, // 点击匹配按钮 handleMatchClick() { // 检测是否登录,未登录则弹出登录弹窗 if (!this.data.isLoggedIn) { this.setData({ showLoginModal: true, agreeProtocol: false }) return } const currentType = MATCH_TYPES.find(t => t.id === this.data.selectedType) // 资源对接类型需要购买章节才能使用 if (currentType && currentType.id === 'investor') { // 检查是否购买过章节 const hasPurchased = app.globalData.purchasedSections?.length > 0 || app.globalData.hasFullBook if (!hasPurchased) { wx.showModal({ title: '需要购买章节', content: '购买任意章节后即可使用资源对接功能', confirmText: '去购买', success: (res) => { if (res.confirm) { wx.switchTab({ url: '/pages/chapters/chapters' }) } } }) return } } // 如果是需要填写联系方式的类型(资源对接、导师顾问、团队招募) if (currentType && currentType.showJoinAfterMatch) { // 先检查是否已绑定联系方式 const hasPhone = !!this.data.phoneNumber const hasWechat = !!this.data.wechatId if (!hasPhone && !hasWechat) { // 没有绑定联系方式,先显示绑定提示(仍尝试加载已有资料填充) this.loadStoredContact() this.setData({ showJoinModal: true, joinType: currentType.id, joinTypeLabel: currentType.matchLabel || currentType.label, joinSuccess: false, joinError: '', needBindFirst: true }) return } // 已绑定联系方式,先显示匹配动画1-3秒,再弹出确认 this.startMatchingAnimation(currentType) return } // 创业合伙类型 - 真正的匹配功能(仅当登录且服务端确认次数用尽时,弹出购买) if (this.data.showQuotaExhausted) { this.setData({ showUnlockModal: true }) return } this.startMatch() }, // 匹配动画后弹出加入确认 startMatchingAnimation(currentType) { // 显示匹配中状态 this.setData({ isMatching: true, matchAttempts: 0, currentMatch: null }) // 动画计时 const timer = setInterval(() => { this.setData({ matchAttempts: this.data.matchAttempts + 1 }) }, 500) // 1-3秒随机延迟后显示弹窗 const delay = Math.random() * 2000 + 1000 setTimeout(() => { clearInterval(timer) // 打开弹窗前调取用户资料填充手机号、微信号 this.loadStoredContact() this.setData({ isMatching: false, showJoinModal: true, joinType: currentType.id, joinTypeLabel: currentType.matchLabel || currentType.label, joinSuccess: false, joinError: '', needBindFirst: false }) }, delay) }, // 显示购买提示 showPurchaseTip() { wx.showModal({ title: '需要购买书籍', content: '购买《Soul创业派对》后即可使用匹配功能,仅需9.9元', confirmText: '去购买', success: (res) => { if (res.confirm) { this.goToChapters() } } }) }, // 开始匹配 - 只匹配数据库中的真实用户 async startMatch() { this.loadStoredContact() this.setData({ isMatching: true, matchAttempts: 0, currentMatch: null }) // 匹配动画计时器 const timer = setInterval(() => { this.setData({ matchAttempts: this.data.matchAttempts + 1 }) }, 1000) // 从数据库获取真实用户匹配(后端会校验剩余次数) let matchedUser = null let quotaExceeded = false try { const ui = app.globalData.userInfo || {} const phone = (wx.getStorageSync('user_phone') || ui.phone || this.data.phoneNumber || '').trim() const wechatId = (wx.getStorageSync('user_wechat') || ui.wechat || ui.wechatId || this.data.wechatId || '').trim() const res = await app.request('/api/miniprogram/match/users', { method: 'POST', data: { matchType: this.data.selectedType, userId: app.globalData.userInfo?.id || '', phone, wechatId } }) if (res.success && res.data) { matchedUser = res.data console.log('[Match] 从数据库匹配到用户:', matchedUser.nickname) } else if (res.code === 'QUOTA_EXCEEDED') { quotaExceeded = true } } catch (e) { console.log('[Match] 数据库匹配失败:', e) } // 延迟显示结果(模拟匹配过程) const delay = Math.random() * 2000 + 2000 setTimeout(() => { clearInterval(timer) // 次数用尽(后端校验) if (quotaExceeded) { this.setData({ isMatching: false }) wx.showModal({ title: '今日匹配次数已用完', content: '请购买更多次数继续匹配', confirmText: '去购买', success: (r) => { if (r.confirm) this.setData({ showUnlockModal: true }) } }) this.refreshMatchCountAndStatus() return } // 如果没有匹配到用户,提示用户 if (!matchedUser) { this.setData({ isMatching: false }) wx.showModal({ title: '暂无匹配', content: '当前暂无合适的匹配用户,请稍后再试', showCancel: false, confirmText: '知道了' }) return } // 匹配成功:从服务端刷新配额(后端已写入 match_records) this.setData({ isMatching: false, currentMatch: matchedUser, needPayToMatch: false }) this.refreshMatchCountAndStatus() // 上报匹配行为到存客宝 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 { await app.request('/api/miniprogram/ckb/match', { method: 'POST', data: { matchType: this.data.selectedType, phone: this.data.phoneNumber, wechat: this.data.wechatId, userId: app.globalData.userInfo?.id || '', nickname: app.globalData.userInfo?.nickname || '', matchedUser: { id: matchedUser.id, nickname: matchedUser.nickname, matchScore: matchedUser.matchScore } } }) } catch (e) { console.log('上报匹配失败:', e) } }, // 取消匹配 cancelMatch() { this.setData({ isMatching: false, matchAttempts: 0 }) }, // 重置匹配(返回) resetMatch() { this.setData({ currentMatch: null }) }, // 添加微信好友(先校验手机号绑定) handleAddWechat() { if (!this.data.currentMatch) return // 未登录需先登录 if (!app.globalData.isLoggedIn) { wx.showModal({ title: '需要登录', content: '请先登录后再添加好友', confirmText: '去登录', success: (res) => { if (res.confirm) wx.switchTab({ url: '/pages/my/my' }) } }) return } // 判断是否已绑定手机号(本地缓存或用户资料) const hasPhone = !!( wx.getStorageSync('user_phone') || app.globalData.userInfo?.phone ) if (!hasPhone) { this.setData({ showBindPhoneModal: true, pendingAddWechatAfterBind: true }) return } this.doCopyWechat() }, // 执行复制联系方式(优先微信号,无则复制手机号) doCopyWechat() { if (!this.data.currentMatch) return const wechat = (this.data.currentMatch.wechat || this.data.currentMatch.wechatId || '').trim() const phone = (this.data.currentMatch.phone || '').trim() const toCopy = wechat || phone if (!toCopy) { wx.showModal({ title: '暂无可复制', content: '该用户未提供微信号或手机号,请通过其他方式联系', showCancel: false, confirmText: '知道了' }) return } const label = wechat ? '微信号' : '手机号' wx.setClipboardData({ data: toCopy, success: () => { wx.showModal({ title: wechat ? '微信号已复制' : '手机号已复制', content: wechat ? `${label}:${toCopy}\n\n请打开微信添加好友,备注"创业合作"即可` : `${label}:${toCopy}\n\n可通过微信搜索该手机号添加好友`, showCancel: false, confirmText: '知道了' }) }, fail: () => { wx.showToast({ title: '复制失败,请重试', icon: 'none' }) } }) }, // 切换联系方式类型(同步刷新用户资料填充) switchContactType(e) { const type = e.currentTarget.dataset.type this.loadStoredContact() this.setData({ contactType: type, joinError: '' }) }, // 手机号输入 onPhoneInput(e) { this.setData({ phoneNumber: e.detail.value.replace(/\D/g, '').slice(0, 11), joinError: '' }) }, // 资源对接表单输入 onCanHelpInput(e) { this.setData({ canHelp: e.detail.value }) }, onNeedHelpInput(e) { this.setData({ needHelp: e.detail.value }) }, onGoodAtInput(e) { this.setData({ goodAt: e.detail.value }) }, // 微信号输入 onWechatInput(e) { this.setData({ wechatId: e.detail.value, joinError: '' }) }, // 提交加入 async handleJoinSubmit() { const { contactType, phoneNumber, wechatId, joinType, isJoining, canHelp, needHelp } = this.data if (isJoining) return // 验证联系方式 if (contactType === 'phone') { if (!phoneNumber || phoneNumber.length !== 11) { this.setData({ joinError: '请输入正确的11位手机号' }) return } } else { if (!wechatId || wechatId.length < 6) { this.setData({ joinError: '请输入正确的微信号(至少6位)' }) return } } // 资源对接需要填写两项信息 if (joinType === 'investor') { if (!canHelp || canHelp.trim().length < 2) { this.setData({ joinError: '请填写"我能帮到你什么"' }) return } if (!needHelp || needHelp.trim().length < 2) { this.setData({ joinError: '请填写"我需要什么帮助"' }) return } } this.setData({ isJoining: true, joinError: '' }) try { const res = await app.request('/api/miniprogram/ckb/join', { method: 'POST', data: { type: joinType, phone: contactType === 'phone' ? phoneNumber : '', wechat: contactType === 'wechat' ? wechatId : '', userId: app.globalData.userInfo?.id || '', // 资源对接专属字段 canHelp: joinType === 'investor' ? canHelp : '', needHelp: joinType === 'investor' ? needHelp : '' } }) // 保存联系方式到本地 if (phoneNumber) wx.setStorageSync('user_phone', phoneNumber) if (wechatId) wx.setStorageSync('user_wechat', wechatId) if (res.success) { this.setData({ joinSuccess: true }) setTimeout(() => { this.setData({ showJoinModal: false, joinSuccess: false }) }, 2000) } else { // 即使API返回失败,也模拟成功(因为已保存本地) this.setData({ joinSuccess: true }) setTimeout(() => { this.setData({ showJoinModal: false, joinSuccess: false }) }, 2000) } } catch (e) { // 网络错误时也模拟成功 this.setData({ joinSuccess: true }) setTimeout(() => { this.setData({ showJoinModal: false, joinSuccess: false }) }, 2000) } finally { this.setData({ isJoining: false }) } }, // 关闭加入弹窗 closeJoinModal() { if (this.data.isJoining) return this.setData({ showJoinModal: false, joinError: '' }) }, // 关闭手机绑定弹窗 closeBindPhoneModal() { this.setData({ showBindPhoneModal: false, pendingAddWechatAfterBind: false, bindPhoneInput: '', showMatchPhoneManualInput: false }) }, // 关闭登录弹窗 closeLoginModal() { if (this.data.isLoggingIn) return this.setData({ showLoginModal: false }) }, // 切换协议勾选 toggleAgree() { this.setData({ agreeProtocol: !this.data.agreeProtocol }) }, // 打开用户协议 openUserProtocol() { wx.navigateTo({ url: '/pages/agreement/agreement' }) }, // 打开隐私政策 openPrivacy() { wx.navigateTo({ url: '/pages/privacy/privacy' }) }, // 微信登录(匹配页) async handleMatchWechatLogin() { if (!this.data.agreeProtocol) { wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' }) return } this.setData({ isLoggingIn: true }) try { const result = await app.login() if (result) { // 登录成功后必须拉取 matchQuota,否则无法正确显示剩余次数 await this.refreshMatchCountAndStatus() this.setData({ showLoginModal: false, agreeProtocol: false }) wx.showToast({ title: '登录成功', icon: 'success' }) } else { wx.showToast({ title: '登录失败,请重试', icon: 'none' }) } } catch (e) { console.error('[Match] 微信登录错误:', e) wx.showToast({ title: '登录失败,请重试', icon: 'none' }) } finally { this.setData({ isLoggingIn: false }) } }, // 一键获取手机号(匹配页加好友前绑定) async onMatchGetPhoneNumber(e) { if (e.detail.errMsg !== 'getPhoneNumber:ok') { wx.showToast({ title: '授权失败', icon: 'none' }) return } const code = e.detail.code if (!code) { this.setData({ showMatchPhoneManualInput: true }) return } try { wx.showLoading({ title: '获取中...', mask: true }) const userId = app.globalData.userInfo?.id const res = await app.request('/api/miniprogram/phone', { method: 'POST', data: { code, userId } }) wx.hideLoading() if (res.success && res.phoneNumber) { await this.saveMatchPhoneAndContinue(res.phoneNumber) } else { this.setData({ showMatchPhoneManualInput: true }) } } catch (err) { wx.hideLoading() this.setData({ showMatchPhoneManualInput: true }) } }, // 切换为手动输入 onMatchShowManualInput() { this.setData({ showMatchPhoneManualInput: true }) }, // 手动输入手机号 onMatchPhoneInput(e) { this.setData({ bindPhoneInput: e.detail.value.replace(/\D/g, '').slice(0, 11) }) }, // 确认手动绑定手机号 async confirmMatchPhoneBind() { const { bindPhoneInput } = this.data if (!bindPhoneInput || bindPhoneInput.length !== 11) { wx.showToast({ title: '请输入正确的11位手机号', icon: 'none' }) return } if (!/^1[3-9]\d{9}$/.test(bindPhoneInput)) { wx.showToast({ title: '请输入正确的手机号', icon: 'none' }) return } await this.saveMatchPhoneAndContinue(bindPhoneInput) }, // 保存手机号到本地+服务器,并继续加好友 async saveMatchPhoneAndContinue(phone) { wx.setStorageSync('user_phone', phone) if (app.globalData.userInfo) { app.globalData.userInfo.phone = phone wx.setStorageSync('userInfo', app.globalData.userInfo) } this.setData({ phoneNumber: phone, userPhone: phone, bindPhoneInput: '' }) this.loadStoredContact() try { const userId = app.globalData.userInfo?.id if (userId) { await app.request('/api/miniprogram/user/profile', { method: 'POST', data: { userId, phone } }) } } catch (e) { console.log('[Match] 同步手机号到服务器失败:', e) } const pending = this.data.pendingAddWechatAfterBind this.closeBindPhoneModal() if (pending) { wx.showToast({ title: '绑定成功', icon: 'success' }) setTimeout(() => this.doCopyWechat(), 500) } }, // 显示解锁弹窗 showUnlockModal() { this.setData({ showUnlockModal: true }) }, // 关闭解锁弹窗 closeUnlockModal() { this.setData({ showUnlockModal: false }) }, // 支付成功后立即查询订单状态并刷新(首轮 0 延迟,之后每 800ms 重试) async pollOrderAndRefresh(orderSn) { const maxAttempts = 12 const interval = 800 for (let i = 0; i < maxAttempts; i++) { try { const r = await app.request(`/api/miniprogram/pay?orderSn=${encodeURIComponent(orderSn)}`, { method: 'GET', silent: true }) if (r?.data?.status === 'paid') { await this.refreshMatchCountAndStatus() return } } catch (_) {} if (i < maxAttempts - 1) await new Promise(r => setTimeout(r, interval)) } await this.refreshMatchCountAndStatus() }, // 购买匹配次数(与购买章节逻辑一致,写入订单) async buyMatchCount() { this.setData({ showUnlockModal: false }) try { // 获取openId let openId = app.globalData.openId || wx.getStorageSync('openId') if (!openId) { openId = await app.getOpenId() } if (!openId) { wx.showToast({ title: '请先登录', icon: 'none' }) return } const matchPrice = this.data.matchPrice || 1 // 邀请码:与章节支付一致,写入订单便于分销归属与对账 const referralCode = wx.getStorageSync('referral_code') || '' // 调用支付接口购买匹配次数(productType: match,订单类型:购买匹配次数) const res = await app.request('/api/miniprogram/pay', { method: 'POST', data: { openId, productType: 'match', productId: 'match_1', amount: matchPrice, description: '匹配次数x1', userId: app.globalData.userInfo?.id || '', referralCode: referralCode || undefined } }) if (res.success && res.data?.payParams) { const orderSn = res.data.orderSn // 调用微信支付 await new Promise((resolve, reject) => { wx.requestPayment({ ...res.data.payParams, success: resolve, fail: reject }) }) wx.showToast({ title: '购买成功', icon: 'success' }) // 轮询订单状态,确认已支付后再刷新(不依赖 PayNotify 回调时机) this.pollOrderAndRefresh(orderSn) } else { throw new Error(res.error || '创建订单失败') } } catch (e) { if (e.errMsg && e.errMsg.includes('cancel')) { wx.showToast({ title: '已取消', icon: 'none' }) } else { // 测试模式(无支付环境时本地模拟) wx.showModal({ title: '支付服务暂不可用', content: '是否使用测试模式购买?', success: (res) => { if (res.confirm) { app.globalData.matchCount = (app.globalData.matchCount ?? 0) + 1 wx.showToast({ title: '测试购买成功', icon: 'success' }) this.initUserStatus() } } }) } } }, // 跳转到目录页购买 goToChapters() { this.setData({ showUnlockModal: false }) wx.switchTab({ url: '/pages/chapters/chapters' }) }, // 打开设置 openSettings() { wx.navigateTo({ url: '/pages/settings/settings' }) }, // 阻止事件冒泡 preventBubble() {}, onShareAppMessage() { const ref = app.getMyReferralCode() return { title: 'Soul创业派对 - 找伙伴', path: ref ? `/pages/match/match?ref=${ref}` : '/pages/match/match' } } })