/** * Soul创业派对 - 用户旅程规则引擎 * 从后端 /api/miniprogram/user-rules 读取启用的规则,按场景触发引导 * 稳定版兼容:readCount 用 getReadCount(),hasPurchasedFull 用 hasFullBook,完善头像跳 avatar-nickname * * trigger → scene 映射: * 注册 → after_login * 点击收费章节 → before_read * 完成匹配 → after_match * 完成付款 → after_pay * 累计浏览5章节 → page_show * 加入派对房 → before_join_party * 绑定微信 → after_bindwechat * 收益满50元 → earnings_check * 手动触发 → manual * 浏览导师页 → browse_mentor */ const app = getApp() const RULE_COOLDOWN_KEY = 'rule_engine_cooldown' const COOLDOWN_MS = 60 * 1000 let _cachedRules = null let _cacheTs = 0 const CACHE_TTL = 5 * 60 * 1000 const TRIGGER_SCENE_MAP = { '注册': 'after_login', '点击收费章节': 'before_read', '完成匹配': 'after_match', '完成付款': 'after_pay', '累计浏览5章节': 'page_show', '加入派对房': 'before_join_party', '绑定微信': 'after_bindwechat', '收益满50元': 'earnings_check', '手动触发': 'manual', '浏览导师页': 'browse_mentor', } function isInCooldown(ruleId) { try { const map = wx.getStorageSync(RULE_COOLDOWN_KEY) || {} const ts = map[ruleId] if (!ts) return false return Date.now() - ts < COOLDOWN_MS } catch (e) { console.warn('[RuleEngine] 读取冷却状态失败:', e) return false } } function setCooldown(ruleId) { try { const map = wx.getStorageSync(RULE_COOLDOWN_KEY) || {} map[ruleId] = Date.now() wx.setStorageSync(RULE_COOLDOWN_KEY, map) } catch (e) { console.warn('[RuleEngine] 写入冷却状态失败:', e) } } function getUserInfo() { return app.globalData.userInfo || {} } async function loadRules() { if (_cachedRules && Date.now() - _cacheTs < CACHE_TTL) return _cachedRules try { const res = await app.request({ url: '/api/miniprogram/user-rules', method: 'GET', silent: true }) if (res && res.success && res.rules) { _cachedRules = res.rules _cacheTs = Date.now() return _cachedRules } } catch (e) { console.warn('[RuleEngine] 加载规则失败,继续使用缓存:', e) } return _cachedRules || [] } function isRuleEnabled(rules, triggerName) { return rules.some(r => r.trigger === triggerName) } 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() if (!user.id) return null const avatar = user.avatar || user.avatarUrl || '' const nickname = user.nickname || '' if (avatar && !avatar.includes('default') && nickname && nickname !== '微信用户' && !nickname.startsWith('微信用户')) return null if (isInCooldown('fill_avatar')) return null setCooldown('fill_avatar') const info = getRuleInfo(rules, '注册') return { ruleId: 'fill_avatar', title: info?.title || '完善个人信息', message: info?.description || '设置头像和昵称,让其他创业者更容易认识你', action: 'navigate', target: '/pages/avatar-nickname/avatar-nickname' } } function checkRule_BindPhone(rules) { if (!isRuleEnabled(rules, '点击收费章节')) return null const user = getUserInfo() if (!user.id) return null if (user.phone) return null if (isInCooldown('bind_phone')) return null setCooldown('bind_phone') const info = getRuleInfo(rules, '点击收费章节') return { ruleId: 'bind_phone', title: info?.title || '绑定手机号', message: info?.description || '绑定手机号解锁更多功能,保障账户安全', action: 'bind_phone', target: null } } function checkRule_FillProfile(rules) { if (!isRuleEnabled(rules, '完成匹配')) return null const user = getUserInfo() if (!user.id) return null if (user.mbti && user.industry) return null if (isInCooldown('fill_profile')) return null setCooldown('fill_profile') const info = getRuleInfo(rules, '完成匹配') return { ruleId: 'fill_profile', title: info?.title || '完善创业档案', message: info?.description || '填写 MBTI 和行业信息,帮你精准匹配创业伙伴', action: 'navigate', target: '/pages/profile-edit/profile-edit' } } // 稳定版兼容: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)) if (readCount < 5) return null if (isInCooldown('share_after_5')) return null setCooldown('share_after_5') const info = getRuleInfo(rules, '累计浏览5章节') return { ruleId: 'share_after_5', title: info?.title || '邀请好友一起看', message: info?.description || '你已阅读 ' + readCount + ' 个章节,分享给好友可获得分销收益', action: 'navigate', target: '/pages/referral/referral' } } // 稳定版兼容: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 (user.wechatId && user.address) return null if (isInCooldown('fill_vip_info')) return null setCooldown('fill_vip_info') const info = getRuleInfo(rules, '完成付款') return { ruleId: 'fill_vip_info', title: info?.title || '填写完整信息', message: info?.description || '购买全书后,需填写完整信息以进入 VIP 群', action: 'navigate', target: '/pages/profile-edit/profile-edit' } } function checkRule_JoinParty(rules) { if (!isRuleEnabled(rules, '加入派对房')) return null const user = getUserInfo() if (!user.id) return null if (user.projectIntro) return null if (isInCooldown('join_party')) return null setCooldown('join_party') const info = getRuleInfo(rules, '加入派对房') return { ruleId: 'join_party', title: info?.title || '填写项目介绍', message: info?.description || '进入派对房前,引导填写项目介绍和核心需求', action: 'navigate', target: '/pages/profile-edit/profile-edit' } } function checkRule_BindWechat(rules) { if (!isRuleEnabled(rules, '绑定微信')) return null const user = getUserInfo() if (!user.id) return null if (user.wechatId) return null if (isInCooldown('bind_wechat')) return null setCooldown('bind_wechat') const info = getRuleInfo(rules, '绑定微信') return { ruleId: 'bind_wechat', title: info?.title || '绑定微信号', message: info?.description || '绑定微信后,引导开启分销功能', action: 'navigate', target: '/pages/settings/settings' } } function checkRule_Withdraw(rules) { if (!isRuleEnabled(rules, '收益满50元')) return null const user = getUserInfo() if (!user.id) return null const earnings = app.globalData.totalEarnings || 0 if (earnings < 50) return null if (isInCooldown('withdraw_50')) return null setCooldown('withdraw_50') const info = getRuleInfo(rules, '收益满50元') return { ruleId: 'withdraw_50', title: info?.title || '可以提现了', message: info?.description || '累计分销收益超过 50 元,快去申请提现吧', action: 'navigate', target: '/pages/referral/referral' } } function checkRulesSync(scene, rules) { const user = getUserInfo() if (!user.id) return null switch (scene) { case 'after_login': return checkRule_FillAvatar(rules) case 'before_read': return checkRule_BindPhone(rules) || checkRule_FillAvatar(rules) case 'after_match': return checkRule_FillProfile(rules) || checkRule_JoinParty(rules) case 'after_pay': return checkRule_FillVipInfo(rules) || checkRule_FillProfile(rules) case 'page_show': return checkRule_FillAvatar(rules) || checkRule_ShareAfter5Chapters(rules) || checkRule_BindWechat(rules) || checkRule_Withdraw(rules) case 'before_join_party': return checkRule_JoinParty(rules) default: return null } } function executeRule(rule, pageInstance) { if (!rule) return wx.showModal({ title: rule.title, content: rule.message, confirmText: '去完善', cancelText: '稍后再说', success: (res) => { if (res.confirm) { if (rule.action === 'navigate' && rule.target) { wx.navigateTo({ url: rule.target }) } else if (rule.action === 'bind_phone' && pageInstance) { if (typeof pageInstance.showPhoneBinding === 'function') { pageInstance.showPhoneBinding() } } } _trackRuleAction(rule.ruleId, res.confirm ? 'confirm' : 'cancel') } }) } function _trackRuleAction(ruleId, action) { const userId = getUserInfo().id if (!userId) return app.request({ url: '/api/miniprogram/track', method: 'POST', data: { userId, action: 'rule_trigger', target: ruleId, extraData: { result: action } }, silent: true }).catch(() => {}) } async function checkAndExecute(scene, pageInstance) { const rules = await loadRules() const rule = checkRulesSync(scene, rules) if (rule) { setTimeout(() => executeRule(rule, pageInstance), 800) } } module.exports = { checkRules: checkRulesSync, executeRule, checkAndExecute, loadRules }