2026-03-17 12:21:33 +08:00
|
|
|
|
/**
|
2026-03-20 11:31:04 +08:00
|
|
|
|
* 卡若创业派对 - 用户旅程规则引擎
|
2026-03-23 18:38:23 +08:00
|
|
|
|
* 从后端 /api/miniprogram/user-rules 读取启用的规则,按场景触发提示(文案偏利他、少用命令式)
|
2026-03-17 12:21:33 +08:00
|
|
|
|
* 稳定版兼容:readCount 用 getReadCount(),hasPurchasedFull 用 hasFullBook,完善头像跳 avatar-nickname
|
|
|
|
|
|
*
|
|
|
|
|
|
* trigger → scene 映射:
|
2026-03-24 12:54:39 +08:00
|
|
|
|
* 注册 → after_login(头像或昵称未完善)
|
|
|
|
|
|
* update_avatar / 完善头像 → 仅头像未完善
|
|
|
|
|
|
* update_nickname / 修改昵称 → 仅昵称为默认
|
2026-03-17 12:21:33 +08:00
|
|
|
|
* 点击收费章节 → before_read
|
|
|
|
|
|
* 完成匹配 → after_match
|
|
|
|
|
|
* 完成付款 → after_pay
|
|
|
|
|
|
* 累计浏览5章节 → page_show
|
|
|
|
|
|
* 加入派对房 → before_join_party
|
|
|
|
|
|
* 绑定微信 → after_bindwechat
|
|
|
|
|
|
* 收益满50元 → earnings_check
|
|
|
|
|
|
* 手动触发 → manual
|
|
|
|
|
|
* 浏览导师页 → browse_mentor
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-03-20 14:38:51 +08:00
|
|
|
|
function getAppInstance() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const a = getApp()
|
|
|
|
|
|
return a && a.globalData ? a : null
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-17 12:21:33 +08:00
|
|
|
|
|
|
|
|
|
|
const RULE_COOLDOWN_KEY = 'rule_engine_cooldown'
|
2026-03-22 08:34:28 +08:00
|
|
|
|
// 0 = 关闭冷却(需求:去掉「操作频繁 / N 分钟」类体感限制)
|
|
|
|
|
|
const COOLDOWN_MS = 0
|
2026-03-17 12:21:33 +08:00
|
|
|
|
let _cachedRules = null
|
|
|
|
|
|
let _cacheTs = 0
|
|
|
|
|
|
const CACHE_TTL = 5 * 60 * 1000
|
|
|
|
|
|
|
|
|
|
|
|
const TRIGGER_SCENE_MAP = {
|
|
|
|
|
|
'注册': 'after_login',
|
2026-03-24 12:54:39 +08:00
|
|
|
|
'完善头像': 'after_login',
|
|
|
|
|
|
'修改昵称': 'after_login',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
'点击收费章节': 'before_read',
|
|
|
|
|
|
'完成匹配': 'after_match',
|
|
|
|
|
|
'完成付款': 'after_pay',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
'发起支付': 'before_pay',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
'累计浏览5章节': 'page_show',
|
|
|
|
|
|
'加入派对房': 'before_join_party',
|
|
|
|
|
|
'绑定微信': 'after_bindwechat',
|
|
|
|
|
|
'收益满50元': 'earnings_check',
|
|
|
|
|
|
'手动触发': 'manual',
|
|
|
|
|
|
'浏览导师页': 'browse_mentor',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 12:54:39 +08:00
|
|
|
|
// 与后台「规则类型 trigger」一致:支持英文 key 与同义中文(逻辑在 isRuleEnabled 定义之后)
|
|
|
|
|
|
const TRIGGER_KEYS_AVATAR = ['update_avatar', '完善头像']
|
|
|
|
|
|
const TRIGGER_KEYS_NICKNAME = ['update_nickname', '修改昵称']
|
|
|
|
|
|
|
2026-03-17 12:21:33 +08:00
|
|
|
|
function isInCooldown(ruleId) {
|
2026-03-22 08:34:28 +08:00
|
|
|
|
if (!COOLDOWN_MS || COOLDOWN_MS <= 0) return false
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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) {
|
2026-03-22 08:34:28 +08:00
|
|
|
|
if (!COOLDOWN_MS || COOLDOWN_MS <= 0) return
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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() {
|
2026-03-20 14:38:51 +08:00
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
return app ? (app.globalData.userInfo || {}) : {}
|
2026-03-17 12:21:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 18:38:23 +08:00
|
|
|
|
function trimStr(v) {
|
|
|
|
|
|
if (v == null || v === undefined) return ''
|
|
|
|
|
|
const s = String(v).trim()
|
|
|
|
|
|
return s
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 合并服务端 profile,避免本地 userInfo 未同步导致「已填写仍弹窗」 */
|
|
|
|
|
|
async function fetchProfileMergeUser() {
|
|
|
|
|
|
const base = { ...getUserInfo() }
|
|
|
|
|
|
const userId = base.id
|
|
|
|
|
|
if (!userId) return base
|
|
|
|
|
|
try {
|
|
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
const res = await app.request({ url: `/api/miniprogram/user/profile?userId=${userId}`, silent: true })
|
|
|
|
|
|
if (res?.success && res.data) {
|
|
|
|
|
|
const d = res.data
|
|
|
|
|
|
return {
|
|
|
|
|
|
...base,
|
|
|
|
|
|
mbti: d.mbti != null ? d.mbti : base.mbti,
|
|
|
|
|
|
industry: d.industry != null ? d.industry : base.industry,
|
|
|
|
|
|
position: d.position != null ? d.position : base.position,
|
|
|
|
|
|
projectIntro: d.projectIntro || d.project_intro || base.projectIntro,
|
|
|
|
|
|
phone: d.phone != null ? d.phone : base.phone,
|
|
|
|
|
|
wechatId: d.wechatId || d.wechat_id || base.wechatId,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
return base
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 12:21:33 +08:00
|
|
|
|
async function loadRules() {
|
|
|
|
|
|
if (_cachedRules && Date.now() - _cacheTs < CACHE_TTL) return _cachedRules
|
2026-03-20 14:38:51 +08:00
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
if (!app) return _cachedRules || []
|
2026-03-23 18:38:23 +08:00
|
|
|
|
const userId = (app.globalData.userInfo || {}).id || ''
|
2026-03-17 12:21:33 +08:00
|
|
|
|
try {
|
2026-03-23 18:38:23 +08:00
|
|
|
|
const url = userId ? `/api/miniprogram/user-rules?userId=${userId}` : '/api/miniprogram/user-rules'
|
|
|
|
|
|
const res = await app.request({ url, method: 'GET', silent: true })
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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) {
|
2026-03-23 18:38:23 +08:00
|
|
|
|
return rules.some(r => r.trigger === triggerName && !r.completed)
|
2026-03-17 12:21:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getRuleInfo(rules, triggerName) {
|
2026-03-23 18:38:23 +08:00
|
|
|
|
return rules.find(r => r.trigger === triggerName && !r.completed)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 12:54:39 +08:00
|
|
|
|
function isAnyTriggerEnabled(rules, keys) {
|
|
|
|
|
|
return keys.some((k) => isRuleEnabled(rules, k))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getFirstRuleInfo(rules, keys) {
|
|
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
|
|
const info = getRuleInfo(rules, keys[i])
|
|
|
|
|
|
if (info) return info
|
|
|
|
|
|
}
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isAvatarMissingOrDefault(user) {
|
|
|
|
|
|
user = user || getUserInfo()
|
|
|
|
|
|
const avatar = user.avatar || user.avatarUrl || ''
|
|
|
|
|
|
return !avatar || avatar.includes('default')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isNicknamePlaceholder(nickname) {
|
|
|
|
|
|
const n = trimStr(nickname)
|
|
|
|
|
|
return !n || n === '微信用户' || n.startsWith('微信用户')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 18:38:23 +08:00
|
|
|
|
function markRuleCompleted(ruleId) {
|
|
|
|
|
|
const userId = getUserInfo().id
|
|
|
|
|
|
if (!userId || !ruleId) return
|
|
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
if (!app) return
|
|
|
|
|
|
const numericId = typeof ruleId === 'number' ? ruleId : null
|
|
|
|
|
|
if (!numericId) return
|
|
|
|
|
|
app.request({
|
|
|
|
|
|
url: '/api/miniprogram/user-rules/complete',
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
data: { userId, ruleId: numericId },
|
|
|
|
|
|
silent: true
|
|
|
|
|
|
}).catch(() => {})
|
2026-03-17 12:21:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 12:54:39 +08:00
|
|
|
|
// 仅头像:trigger = update_avatar 或 完善头像
|
|
|
|
|
|
function checkRule_UpdateAvatar(rules) {
|
|
|
|
|
|
if (!isAnyTriggerEnabled(rules, TRIGGER_KEYS_AVATAR)) return null
|
|
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
if (app && app.globalData.isVip) return null
|
|
|
|
|
|
const user = getUserInfo()
|
|
|
|
|
|
if (!user.id) return null
|
|
|
|
|
|
if (!isAvatarMissingOrDefault(user)) return null
|
|
|
|
|
|
if (isInCooldown('update_avatar')) return null
|
|
|
|
|
|
setCooldown('update_avatar')
|
|
|
|
|
|
const info = getFirstRuleInfo(rules, TRIGGER_KEYS_AVATAR)
|
|
|
|
|
|
return {
|
|
|
|
|
|
ruleId: 'update_avatar',
|
|
|
|
|
|
serverRuleId: info?.id,
|
|
|
|
|
|
title: info?.title || '上传头像',
|
|
|
|
|
|
message: info?.description || '换一张清晰头像,伙伴在名片和匹配里更容易认出你。',
|
|
|
|
|
|
confirmText: '去设置',
|
|
|
|
|
|
cancelText: '关闭',
|
|
|
|
|
|
action: 'navigate',
|
|
|
|
|
|
target: '/pages/avatar-nickname/avatar-nickname?focus=avatar'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 仅昵称:trigger = update_nickname 或 修改昵称
|
|
|
|
|
|
function checkRule_UpdateNickname(rules) {
|
|
|
|
|
|
if (!isAnyTriggerEnabled(rules, TRIGGER_KEYS_NICKNAME)) return null
|
|
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
if (app && app.globalData.isVip) return null
|
|
|
|
|
|
const user = getUserInfo()
|
|
|
|
|
|
if (!user.id) return null
|
|
|
|
|
|
const nickname = user.nickname || user.nickName || ''
|
|
|
|
|
|
if (!isNicknamePlaceholder(nickname)) return null
|
|
|
|
|
|
if (isInCooldown('update_nickname')) return null
|
|
|
|
|
|
setCooldown('update_nickname')
|
|
|
|
|
|
const info = getFirstRuleInfo(rules, TRIGGER_KEYS_NICKNAME)
|
|
|
|
|
|
return {
|
|
|
|
|
|
ruleId: 'update_nickname',
|
|
|
|
|
|
serverRuleId: info?.id,
|
|
|
|
|
|
title: info?.title || '修改昵称',
|
|
|
|
|
|
message: info?.description || '改一个真实好记的昵称,方便伙伴称呼你。',
|
|
|
|
|
|
confirmText: '去填写',
|
|
|
|
|
|
cancelText: '关闭',
|
|
|
|
|
|
action: 'navigate',
|
|
|
|
|
|
target: '/pages/avatar-nickname/avatar-nickname?focus=nickname'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 稳定版:trigger=注册 时头像或昵称任一未完善则引导(与上面两项拆分配置并存)
|
2026-03-23 18:38:23 +08:00
|
|
|
|
// VIP 用户不触发:统一由 checkVipContactRequiredAndGuide 跳转 profile-edit,避免与主流程冲突
|
2026-03-17 12:21:33 +08:00
|
|
|
|
function checkRule_FillAvatar(rules) {
|
|
|
|
|
|
if (!isRuleEnabled(rules, '注册')) return null
|
2026-03-20 14:38:51 +08:00
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
if (app && app.globalData.isVip) return null
|
2026-03-17 12:21:33 +08:00
|
|
|
|
const user = getUserInfo()
|
|
|
|
|
|
if (!user.id) return null
|
2026-03-24 12:54:39 +08:00
|
|
|
|
const nickname = user.nickname || user.nickName || ''
|
|
|
|
|
|
if (!isAvatarMissingOrDefault(user) && !isNicknamePlaceholder(nickname)) return null
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (isInCooldown('fill_avatar')) return null
|
|
|
|
|
|
setCooldown('fill_avatar')
|
|
|
|
|
|
const info = getRuleInfo(rules, '注册')
|
2026-03-24 12:54:39 +08:00
|
|
|
|
const needNick = isNicknamePlaceholder(nickname)
|
|
|
|
|
|
const needAv = isAvatarMissingOrDefault(user)
|
|
|
|
|
|
const focus = needAv && !needNick ? 'avatar' : needNick && !needAv ? 'nickname' : ''
|
|
|
|
|
|
const qs = focus ? `?focus=${focus}` : ''
|
2026-03-17 12:21:33 +08:00
|
|
|
|
return {
|
|
|
|
|
|
ruleId: 'fill_avatar',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
serverRuleId: info?.id,
|
|
|
|
|
|
title: info?.title || '设置头像与昵称',
|
|
|
|
|
|
message: info?.description || '头像与昵称会展示在名片与匹配卡片上,方便伙伴认出你。',
|
|
|
|
|
|
confirmText: '去设置',
|
|
|
|
|
|
cancelText: '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
action: 'navigate',
|
2026-03-24 12:54:39 +08:00
|
|
|
|
target: '/pages/avatar-nickname/avatar-nickname' + qs
|
2026-03-17 12:21:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 12:54:39 +08:00
|
|
|
|
/** 头像/昵称类引导:优先拆条规则(完善头像、修改昵称),其次合并规则(注册) */
|
|
|
|
|
|
function checkAvatarNicknameGuides(rules) {
|
|
|
|
|
|
return checkRule_UpdateAvatar(rules) || checkRule_UpdateNickname(rules) || checkRule_FillAvatar(rules)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
serverRuleId: info?.id,
|
2026-03-17 12:21:33 +08:00
|
|
|
|
title: info?.title || '绑定手机号',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
message: info?.description || '绑定后可用于登录验证、收益与重要通知,账户更安全。',
|
|
|
|
|
|
confirmText: '去绑定',
|
|
|
|
|
|
cancelText: '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
action: 'bind_phone',
|
|
|
|
|
|
target: null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 18:38:23 +08:00
|
|
|
|
function checkRule_FillProfile(rules, user) {
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (!isRuleEnabled(rules, '完成匹配')) return null
|
2026-03-23 18:38:23 +08:00
|
|
|
|
user = user || getUserInfo()
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (!user.id) return null
|
2026-03-23 18:38:23 +08:00
|
|
|
|
const mbti = trimStr(user.mbti)
|
|
|
|
|
|
const industry = trimStr(user.industry)
|
|
|
|
|
|
const position = trimStr(user.position)
|
|
|
|
|
|
if (mbti && industry && position) return null
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (isInCooldown('fill_profile')) return null
|
|
|
|
|
|
setCooldown('fill_profile')
|
|
|
|
|
|
const info = getRuleInfo(rules, '完成匹配')
|
|
|
|
|
|
return {
|
|
|
|
|
|
ruleId: 'fill_profile',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
serverRuleId: info?.id,
|
|
|
|
|
|
title: info?.title || '补充档案信息',
|
|
|
|
|
|
message: info?.description || '补全 MBTI、行业和职位后,匹配页能更准确地向对方展示你,减少无效沟通。',
|
|
|
|
|
|
confirmText: '去填写',
|
|
|
|
|
|
cancelText: '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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
|
2026-03-20 14:38:51 +08:00
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
const readCount = app ? (typeof app.getReadCount === 'function' ? app.getReadCount() : (app.globalData.readCount || 0)) : 0
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
serverRuleId: info?.id,
|
2026-03-17 12:21:33 +08:00
|
|
|
|
title: info?.title || '邀请好友一起看',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
message: info?.description || '你已阅读 ' + readCount + ' 个章节,好友通过你的分享购买时,你可获得对应分销收益。',
|
|
|
|
|
|
confirmText: '查看分享',
|
|
|
|
|
|
cancelText: '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
action: 'navigate',
|
|
|
|
|
|
target: '/pages/referral/referral'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 稳定版兼容:hasPurchasedFull 用 hasFullBook
|
2026-03-23 18:38:23 +08:00
|
|
|
|
function checkRule_FillVipInfo(rules, user) {
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (!isRuleEnabled(rules, '完成付款')) return null
|
2026-03-23 18:38:23 +08:00
|
|
|
|
user = user || getUserInfo()
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (!user.id) return null
|
2026-03-20 14:38:51 +08:00
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
if (!app || !(app.globalData.hasFullBook || app.globalData.hasPurchasedFull)) return null
|
2026-03-23 18:38:23 +08:00
|
|
|
|
const wxId = trimStr(user.wechatId || user.wechat_id)
|
|
|
|
|
|
const addr = trimStr(user.address)
|
|
|
|
|
|
if (wxId && addr) return null
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (isInCooldown('fill_vip_info')) return null
|
|
|
|
|
|
setCooldown('fill_vip_info')
|
|
|
|
|
|
const info = getRuleInfo(rules, '完成付款')
|
|
|
|
|
|
return {
|
|
|
|
|
|
ruleId: 'fill_vip_info',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
serverRuleId: info?.id,
|
|
|
|
|
|
title: info?.title || '补全 VIP 资料',
|
|
|
|
|
|
message: info?.description || '补全微信号与收货地址等信息,便于进入 VIP 群、寄送物料与售后联系。',
|
|
|
|
|
|
confirmText: '去填写',
|
|
|
|
|
|
cancelText: '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
action: 'navigate',
|
|
|
|
|
|
target: '/pages/profile-edit/profile-edit'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 18:38:23 +08:00
|
|
|
|
function checkRule_JoinParty(rules, user) {
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (!isRuleEnabled(rules, '加入派对房')) return null
|
2026-03-23 18:38:23 +08:00
|
|
|
|
user = user || getUserInfo()
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (!user.id) return null
|
2026-03-23 18:38:23 +08:00
|
|
|
|
if (trimStr(user.projectIntro)) return null
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (isInCooldown('join_party')) return null
|
|
|
|
|
|
setCooldown('join_party')
|
|
|
|
|
|
const info = getRuleInfo(rules, '加入派对房')
|
|
|
|
|
|
return {
|
|
|
|
|
|
ruleId: 'join_party',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
serverRuleId: info?.id,
|
|
|
|
|
|
title: info?.title || '补充项目介绍',
|
|
|
|
|
|
message: info?.description || '用简短文字说明项目与需求,派对房里的伙伴能更快判断是否与你有合作空间。',
|
|
|
|
|
|
confirmText: '去填写',
|
|
|
|
|
|
cancelText: '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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
|
2026-03-23 18:38:23 +08:00
|
|
|
|
if (trimStr(user.wechatId || user.wechat_id)) return null
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (isInCooldown('bind_wechat')) return null
|
|
|
|
|
|
setCooldown('bind_wechat')
|
|
|
|
|
|
const info = getRuleInfo(rules, '绑定微信')
|
|
|
|
|
|
return {
|
|
|
|
|
|
ruleId: 'bind_wechat',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
serverRuleId: info?.id,
|
2026-03-17 12:21:33 +08:00
|
|
|
|
title: info?.title || '绑定微信号',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
message: info?.description || '绑定后可用于分销结算、提现核对与重要通知。',
|
|
|
|
|
|
confirmText: '去设置',
|
|
|
|
|
|
cancelText: '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
action: 'navigate',
|
|
|
|
|
|
target: '/pages/settings/settings'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function checkRule_Withdraw(rules) {
|
|
|
|
|
|
if (!isRuleEnabled(rules, '收益满50元')) return null
|
|
|
|
|
|
const user = getUserInfo()
|
|
|
|
|
|
if (!user.id) return null
|
2026-03-20 14:38:51 +08:00
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
const earnings = app ? (app.globalData.totalEarnings || 0) : 0
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (earnings < 50) return null
|
|
|
|
|
|
if (isInCooldown('withdraw_50')) return null
|
|
|
|
|
|
setCooldown('withdraw_50')
|
|
|
|
|
|
const info = getRuleInfo(rules, '收益满50元')
|
|
|
|
|
|
return {
|
|
|
|
|
|
ruleId: 'withdraw_50',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
serverRuleId: info?.id,
|
2026-03-17 12:21:33 +08:00
|
|
|
|
title: info?.title || '可以提现了',
|
2026-03-23 18:38:23 +08:00
|
|
|
|
message: info?.description || '累计分销收益已达到提现条件,可在推荐收益页发起提现到微信零钱。',
|
|
|
|
|
|
confirmText: '去查看',
|
|
|
|
|
|
cancelText: '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
action: 'navigate',
|
|
|
|
|
|
target: '/pages/referral/referral'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function checkRulesSync(scene, rules) {
|
|
|
|
|
|
const user = getUserInfo()
|
|
|
|
|
|
if (!user.id) return null
|
|
|
|
|
|
|
|
|
|
|
|
switch (scene) {
|
|
|
|
|
|
case 'after_login':
|
2026-03-24 12:54:39 +08:00
|
|
|
|
return checkAvatarNicknameGuides(rules)
|
2026-03-17 12:21:33 +08:00
|
|
|
|
case 'before_read':
|
2026-03-24 12:54:39 +08:00
|
|
|
|
return checkRule_BindPhone(rules) || checkAvatarNicknameGuides(rules)
|
2026-03-23 18:38:23 +08:00
|
|
|
|
case 'before_pay':
|
2026-03-24 12:54:39 +08:00
|
|
|
|
return checkAvatarNicknameGuides(rules) || checkRule_BindPhone(rules) || checkRule_FillProfile(rules)
|
2026-03-17 12:21:33 +08:00
|
|
|
|
case 'after_match':
|
2026-03-23 18:38:23 +08:00
|
|
|
|
return null
|
2026-03-17 12:21:33 +08:00
|
|
|
|
case 'after_pay':
|
|
|
|
|
|
return checkRule_FillVipInfo(rules) || checkRule_FillProfile(rules)
|
|
|
|
|
|
case 'page_show':
|
2026-03-24 12:54:39 +08:00
|
|
|
|
return checkAvatarNicknameGuides(rules) || checkRule_ShareAfter5Chapters(rules) || checkRule_BindWechat(rules) || checkRule_Withdraw(rules)
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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,
|
2026-03-23 18:38:23 +08:00
|
|
|
|
confirmText: rule.confirmText || '去填写',
|
|
|
|
|
|
cancelText: rule.cancelText !== undefined ? rule.cancelText : '关闭',
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-23 18:38:23 +08:00
|
|
|
|
if (rule.serverRuleId) {
|
|
|
|
|
|
markRuleCompleted(rule.serverRuleId)
|
|
|
|
|
|
}
|
2026-03-17 12:21:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
_trackRuleAction(rule.ruleId, res.confirm ? 'confirm' : 'cancel')
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _trackRuleAction(ruleId, action) {
|
|
|
|
|
|
const userId = getUserInfo().id
|
|
|
|
|
|
if (!userId) return
|
2026-03-20 14:38:51 +08:00
|
|
|
|
const app = getAppInstance()
|
|
|
|
|
|
if (!app) return
|
2026-03-17 12:21:33 +08:00
|
|
|
|
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()
|
2026-03-23 18:38:23 +08:00
|
|
|
|
let rule = null
|
|
|
|
|
|
if (scene === 'after_match') {
|
|
|
|
|
|
const u = await fetchProfileMergeUser()
|
|
|
|
|
|
rule = checkRule_FillProfile(rules, u) || checkRule_JoinParty(rules, u)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
rule = checkRulesSync(scene, rules)
|
|
|
|
|
|
}
|
2026-03-17 12:21:33 +08:00
|
|
|
|
if (rule) {
|
|
|
|
|
|
setTimeout(() => executeRule(rule, pageInstance), 800)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 18:38:23 +08:00
|
|
|
|
module.exports = { checkRules: checkRulesSync, executeRule, checkAndExecute, loadRules, markRuleCompleted }
|