Merge branch 'yongxu-dev' into devlop

# Conflicts:
#	miniprogram/pages/profile-edit/profile-edit.js
#	miniprogram/pages/profile-edit/profile-edit.wxml
#	miniprogram/pages/settings/settings.js
#	miniprogram/utils/ruleEngine.js
#	soul-admin/src/pages/distribution/DistributionPage.tsx
#	soul-admin/src/pages/users/UsersPage.tsx
#	soul-api/.env.production
#	soul-api/.gitignore
#	soul-api/internal/handler/db_ckb_leads.go
#	soul-api/internal/handler/miniprogram.go
#	soul-api/internal/handler/referral.go
#	开发文档/1、需求/archive/链接人与事-存客宝同步-需求规划.md
#	开发文档/1、需求/archive/链接人与事-实现方案.md
This commit is contained in:
Alex-larget
2026-03-20 14:48:02 +08:00
247 changed files with 8990 additions and 6983 deletions

View File

@@ -1,5 +1,5 @@
/**
* Soul创业派对 - 内容解析工具
* 卡若创业派对 - 内容解析工具
* 解析 TipTap HTML 为阅读页可展示的 segments
*
* segment 类型:

View File

@@ -1,6 +1,7 @@
/**
* Soul创业派对 - 用户旅程规则引擎
* 卡若创业派对 - 用户旅程规则引擎
* 从后端 /api/miniprogram/user-rules 读取启用的规则,按场景触发引导
* 稳定版兼容readCount 用 getReadCount()hasPurchasedFull 用 hasFullBook完善头像跳 avatar-nickname
*
* trigger → scene 映射:
* 注册 → after_login
@@ -15,7 +16,14 @@
* 浏览导师页 → browse_mentor
*/
const app = getApp()
function getAppInstance() {
try {
const a = getApp()
return a && a.globalData ? a : null
} catch (e) {
return null
}
}
const RULE_COOLDOWN_KEY = 'rule_engine_cooldown'
const COOLDOWN_MS = 60 * 1000
@@ -59,11 +67,14 @@ function setCooldown(ruleId) {
}
function getUserInfo() {
return app.globalData.userInfo || {}
const app = getAppInstance()
return app ? (app.globalData.userInfo || {}) : {}
}
async function loadRules() {
if (_cachedRules && Date.now() - _cacheTs < CACHE_TTL) return _cachedRules
const app = getAppInstance()
if (!app) return _cachedRules || []
try {
const res = await app.request({ url: '/api/miniprogram/user-rules', method: 'GET', silent: true })
if (res && res.success && res.rules) {
@@ -85,8 +96,12 @@ function getRuleInfo(rules, triggerName) {
return rules.find(r => r.trigger === triggerName)
}
// 稳定版:跳转 avatar-nickname专注头像+昵称,首次登录由 app.login 强制 redirect
// VIP 用户不触发:统一由 checkVipContactRequiredAndGuide 引导到 profile-edit避免与主流程冲突
function checkRule_FillAvatar(rules) {
if (!isRuleEnabled(rules, '注册')) return null
const app = getAppInstance()
if (app && app.globalData.isVip) return null
const user = getUserInfo()
if (!user.id) return null
const avatar = user.avatar || user.avatarUrl || ''
@@ -100,7 +115,7 @@ function checkRule_FillAvatar(rules) {
title: info?.title || '完善个人信息',
message: info?.description || '设置头像和昵称,让其他创业者更容易认识你',
action: 'navigate',
target: '/pages/profile-edit/profile-edit'
target: '/pages/avatar-nickname/avatar-nickname'
}
}
@@ -138,11 +153,13 @@ 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 = app.globalData.readCount || 0
const app = getAppInstance()
const readCount = app ? (typeof app.getReadCount === 'function' ? app.getReadCount() : (app.globalData.readCount || 0)) : 0
if (readCount < 5) return null
if (isInCooldown('share_after_5')) return null
setCooldown('share_after_5')
@@ -156,11 +173,13 @@ 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.hasPurchasedFull) return null
const app = getAppInstance()
if (!app || !(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')
@@ -212,7 +231,8 @@ function checkRule_Withdraw(rules) {
if (!isRuleEnabled(rules, '收益满50元')) return null
const user = getUserInfo()
if (!user.id) return null
const earnings = app.globalData.totalEarnings || 0
const app = getAppInstance()
const earnings = app ? (app.globalData.totalEarnings || 0) : 0
if (earnings < 50) return null
if (isInCooldown('withdraw_50')) return null
setCooldown('withdraw_50')
@@ -274,6 +294,8 @@ function executeRule(rule, pageInstance) {
function _trackRuleAction(ruleId, action) {
const userId = getUserInfo().id
if (!userId) return
const app = getAppInstance()
if (!app) return
app.request({
url: '/api/miniprogram/track',
method: 'POST',

View File

@@ -1,5 +1,5 @@
/**
* Soul创业派对 - 小程序码 scene 参数统一编解码(海报生成 ↔ 扫码解析闭环)
* 卡若创业派对 - 小程序码 scene 参数统一编解码(海报生成 ↔ 扫码解析闭环)
* 官方以 options.scene 接收扫码参数;后端生成码时会把 & 转为 _故解析时同时支持 & 和 _
* scene 同时可带两个参数:章节标识(mid/id) + 推荐人(ref)
*/

View File

@@ -171,6 +171,20 @@ const showConfirm = (title, content) => {
})
}
/**
* 从头像 URL 提取路径部分(不含域名),用于保存到后端
* 例如https://xxx.com/uploads/avatars/1.jpg → /uploads/avatars/1.jpg
* @param {string} url - 完整 URL 或路径
* @returns {string}
*/
const toAvatarPath = url => {
if (!url || typeof url !== 'string') return url || ''
const idx = url.indexOf('/uploads/')
if (idx >= 0) return url.substring(idx)
if (url.startsWith('/')) return url
return url
}
module.exports = {
formatTime,
formatDate,
@@ -188,5 +202,6 @@ module.exports = {
showToast,
showLoading,
hideLoading,
showConfirm
showConfirm,
toAvatarPath
}