Files
soul-yongping/miniprogram/utils/ruleEngine.js
卡若 708547d0dd feat: 数据概览简化 + 用户管理增加余额/提现列
- 数据概览:去掉代付统计独立卡片,总收入中以小标签显示代付金额
- 数据概览:移除余额统计区块(余额改在用户管理中展示)
- 数据概览:恢复转化率卡片(唯一付费用户/总用户)
- 用户管理:用户列表新增「余额/提现」列,显示钱包余额和已提现金额
- 后端:DBUsersList 增加 user_balances 查询,返回 walletBalance 字段
- 后端:User model 添加 WalletBalance 非数据库字段
- 包含之前的小程序埋点和管理后台点击统计面板

Made-with: Cursor
2026-03-15 15:57:09 +08:00

180 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

/**
* Soul创业派对 - 用户旅程规则引擎
* 在关键页面自动检查用户状态,引导完善信息
*
* 规则逻辑:
* 1. 注册后 → 引导填写头像和昵称
* 2. 浏览付费章节 → 引导绑定手机号
* 3. 完成匹配 → 引导填写 MBTI / 行业等
* 4. 购买全书 → 引导填写完整信息(进 VIP 群)
* 5. 累计浏览 5 章 → 引导分享推广
*/
const app = getApp()
const RULE_COOLDOWN_KEY = 'rule_engine_cooldown'
const COOLDOWN_MS = 60 * 1000
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 { return false }
}
function setCooldown(ruleId) {
try {
const map = wx.getStorageSync(RULE_COOLDOWN_KEY) || {}
map[ruleId] = Date.now()
wx.setStorageSync(RULE_COOLDOWN_KEY, map)
} catch {}
}
function getUserInfo() {
return app.globalData.userInfo || {}
}
function checkRule_FillAvatar() {
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')
return {
ruleId: 'fill_avatar',
title: '完善个人信息',
message: '设置头像和昵称,让其他创业者更容易认识你',
action: 'navigate',
target: '/pages/profile-edit/profile-edit'
}
}
function checkRule_BindPhone() {
const user = getUserInfo()
if (!user.id) return null
if (user.phone) return null
if (isInCooldown('bind_phone')) return null
setCooldown('bind_phone')
return {
ruleId: 'bind_phone',
title: '绑定手机号',
message: '绑定手机号解锁更多功能,保障账户安全',
action: 'bind_phone',
target: null
}
}
function checkRule_FillProfile() {
const user = getUserInfo()
if (!user.id) return null
if (user.mbti && user.industry) return null
if (isInCooldown('fill_profile')) return null
setCooldown('fill_profile')
return {
ruleId: 'fill_profile',
title: '完善创业档案',
message: '填写 MBTI 和行业信息,帮你精准匹配创业伙伴',
action: 'navigate',
target: '/pages/profile-edit/profile-edit'
}
}
function checkRule_ShareAfter5Chapters() {
const user = getUserInfo()
if (!user.id) return null
const readCount = app.globalData.readCount || 0
if (readCount < 5) return null
if (isInCooldown('share_after_5')) return null
setCooldown('share_after_5')
return {
ruleId: 'share_after_5',
title: '邀请好友一起看',
message: '你已阅读 ' + readCount + ' 个章节,分享给好友可获得分销收益',
action: 'navigate',
target: '/pages/referral/referral'
}
}
/**
* 在指定场景下检查规则
* @param {string} scene - 场景after_login, before_read, after_match, after_pay, page_show
* @returns {object|null} - 触发的规则null 表示无需触发
*/
function checkRules(scene) {
const user = getUserInfo()
if (!user.id) return null
switch (scene) {
case 'after_login':
return checkRule_FillAvatar()
case 'before_read':
return checkRule_BindPhone() || checkRule_FillAvatar()
case 'after_match':
return checkRule_FillProfile()
case 'after_pay':
return checkRule_FillProfile()
case 'page_show':
return checkRule_FillAvatar() || checkRule_ShareAfter5Chapters()
default:
return null
}
}
/**
* 执行规则动作
* @param {object} rule - checkRules 返回的规则对象
* @param {object} pageInstance - 页面实例(用于绑定手机号等操作)
*/
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('/api/miniprogram/track', {
method: 'POST',
data: { userId, action: 'rule_trigger', target: ruleId, extraData: { result: action } },
silent: true
}).catch(() => {})
}
/**
* 在页面 onShow 中调用的便捷方法
* @param {string} scene
* @param {object} pageInstance
*/
function checkAndExecute(scene, pageInstance) {
const rule = checkRules(scene)
if (rule) {
setTimeout(() => executeRule(rule, pageInstance), 800)
}
}
module.exports = { checkRules, executeRule, checkAndExecute }