feat: 小程序超级个体/个人资料/CKB获客;VIP列表展示过滤;管理端与API联调

- 超级个体:去掉首位特例;列表仅展示有头像且非微信默认昵称(vip.go)
- 个人资料:居中头像、低调联系方式、点头像优先走存客宝 lead(ckbLeadToken)
- 阅读页分享朋友圈复制与 toast 去重
- soul-api: miniprogram users 带 ckbLeadToken;其它 handler 与路由调整
- 脚本:content_upload、miniprogram 上传辅助等

Made-with: Cursor
This commit is contained in:
卡若
2026-03-22 08:34:28 +08:00
parent 17ce20c8ee
commit 5724fba877
119 changed files with 8198 additions and 4369 deletions

View File

@@ -9,13 +9,16 @@ const { checkAndExecute } = require('./utils/ruleEngine.js')
const DEFAULT_APP_ID = 'wxb8bbb2b10dec74aa'
const DEFAULT_MCH_ID = '1318592501'
const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
// 与上传版本号对齐;设置页展示优先用 wx.getAccountInfoSync().miniProgram.version正式版否则用本字段
const APP_DISPLAY_VERSION = '1.7.1'
App({
globalData: {
// API 基础地址:开发时修改下面一行切换环境
// baseUrl: "https://soulapi.quwanzhi.com",
baseUrl: 'http://localhost:8080', // 开发
// baseUrl: 'https://souldev.quwanzhi.com', // 测试
// 与微信后台上传版本号一致,供设置页等展示(避免与线上 version 字段混淆)
appDisplayVersion: APP_DISPLAY_VERSION,
// API仓库默认生产release 强制生产develop/trial 可读 storage「apiBaseUrl」或用 env-switch
baseUrl: 'https://soulapi.quwanzhi.com',
// 小程序配置 - 真实AppID
appId: DEFAULT_APP_ID,
@@ -30,9 +33,14 @@ App({
openId: null, // 微信openId支付必需
isLoggedIn: false,
// 阅读页 @ 解析:/config/read-extras 的 mentionPersons与后台 persons + token 一致)
mentionPersons: [],
// 是否已成功拉取过 read-extras避免仅 linkTags 有缓存时永远拿不到 mentionPersons
readExtrasCacheValid: false,
// 书籍数据bookData 由 chapters-by-part 等逐步填充,不再预加载 all-chapters
bookData: null,
totalSections: 62,
totalSections: 90,
// 购买记录
purchasedSections: [],
@@ -47,6 +55,9 @@ App({
// 推荐绑定
pendingReferralCode: null, // 待绑定的推荐码
// 客服微信号(从系统配置加载,默认值兜底)
serviceWechat: '28533368',
// 主题配置
theme: {
brandColor: '#00CED1',
@@ -81,14 +92,39 @@ App({
// config 统一缓存5min减少重复请求
configCache: null,
configCacheExpires: 0,
// VIP 联系方式检测上次检测时间戳onShow 节流 5 分钟
// VIP 联系方式检测上次检测时间戳onShow 节流(避免与 launch 重复打满接口)
lastVipContactCheck: 0,
// 头像昵称检测:上次检测时间戳onShow 节流 5 分钟
// 头像昵称检测:上次检测时间戳(与 VIP 检测同周期刷新)
lastAvatarNicknameCheck: 0,
},
/** 正式版强制生产 API避免误传 localhost 导致审核/线上全挂 */
initApiBaseUrl() {
const PRODUCTION = 'https://soulapi.quwanzhi.com'
const KEY = 'apiBaseUrl'
try {
const info = wx.getAccountInfoSync?.()
const env = info?.miniProgram?.envVersion || 'release'
if (env === 'release') {
this.globalData.baseUrl = PRODUCTION
try {
const saved = wx.getStorageSync(KEY)
if (saved && saved !== PRODUCTION) wx.removeStorageSync(KEY)
} catch (_) {}
return
}
const saved = wx.getStorageSync(KEY)
if (saved && typeof saved === 'string' && /^https?:\/\//.test(saved)) {
this.globalData.baseUrl = String(saved).replace(/\/$/, '')
}
} catch (_) {
this.globalData.baseUrl = PRODUCTION
}
},
onLaunch(options) {
this.initApiBaseUrl()
// 昵称等隐私组件需先授权input type="nickname" 不会主动触发,需配合 wx.requirePrivacyAuthorize 使用
if (typeof wx.onNeedPrivacyAuthorization === 'function') {
wx.onNeedPrivacyAuthorization((resolve) => {
@@ -169,10 +205,10 @@ App({
this.globalData.lastMpConfigCheck = now
this.getAuditMode()
}
// 从后台切回: VIP 强制跳转,再头像/昵称,节流 5 分钟
const throttle = 5 * 60 * 1000
// 从后台切回:刷新 VIP/头像引导vipGuideThrottleMs=0 表示不限制间隔)
const vipGuideThrottleMs = 0
if (this.globalData.isLoggedIn && this.globalData.userInfo?.id) {
if (!this.globalData.lastVipContactCheck || now - this.globalData.lastVipContactCheck > throttle) {
if (vipGuideThrottleMs <= 0 || !this.globalData.lastVipContactCheck || now - this.globalData.lastVipContactCheck > vipGuideThrottleMs) {
this.globalData.lastVipContactCheck = now
this.globalData.lastAvatarNicknameCheck = now
setTimeout(() => this.checkVipContactRequiredAndGuide(), 500)
@@ -558,9 +594,6 @@ App({
*/
async checkVipContactRequiredAndGuide() {
if (!this.globalData.isLoggedIn || !this.globalData.userInfo?.id) return
const now = Date.now()
if (this._lastVipGuideRun && now - this._lastVipGuideRun < 3000) return // 3 秒内不重复执行,避免 onLaunch+onShow 双重触发
this._lastVipGuideRun = now
const userId = this.globalData.userInfo.id
try {
const pages = getCurrentPages()
@@ -688,10 +721,11 @@ App({
* 获取阅读页扩展配置linkTags、linkedMiniprograms懒加载
*/
async getReadExtras() {
if (Array.isArray(this.globalData.linkTagsConfig) && this.globalData.linkTagsConfig.length > 0) {
if (this.globalData.readExtrasCacheValid) {
return {
linkTags: this.globalData.linkTagsConfig,
linkedMiniprograms: this.globalData.linkedMiniprograms || []
linkTags: this.globalData.linkTagsConfig || [],
linkedMiniprograms: this.globalData.linkedMiniprograms || [],
mentionPersons: this.globalData.mentionPersons || [],
}
}
try {
@@ -699,10 +733,18 @@ App({
if (res) {
if (Array.isArray(res.linkTags)) this.globalData.linkTagsConfig = res.linkTags
if (Array.isArray(res.linkedMiniprograms)) this.globalData.linkedMiniprograms = res.linkedMiniprograms
if (Array.isArray(res.mentionPersons)) this.globalData.mentionPersons = res.mentionPersons
else this.globalData.mentionPersons = []
this.globalData.readExtrasCacheValid = true
return res
}
} catch (e) {}
return { linkTags: [], linkedMiniprograms: [] }
if (!Array.isArray(this.globalData.mentionPersons)) this.globalData.mentionPersons = []
return {
linkTags: this.globalData.linkTagsConfig || [],
linkedMiniprograms: this.globalData.linkedMiniprograms || [],
mentionPersons: this.globalData.mentionPersons,
}
},
/**
@@ -759,14 +801,14 @@ App({
/**
* 小程序更新检测(基于 wx.getUpdateManager
* - 启动时检测;从后台切回前台时也检测(间隔至少 5 分钟,避免频繁请求
* - 启动时检测;从后台切回前台时也检测(间隔即可,避免用户感知「很久才检查更新」
*/
checkUpdate() {
try {
if (!wx.canIUse('getUpdateManager')) return
const now = Date.now()
const lastCheck = this.globalData.lastUpdateCheck || 0
if (lastCheck && now - lastCheck < 5 * 60 * 1000) return // 5 分钟内不重复检测
if (lastCheck && now - lastCheck < 60 * 1000) return // 1 分钟内不重复检测
this.globalData.lastUpdateCheck = now
const updateManager = wx.getUpdateManager()
@@ -1038,13 +1080,6 @@ App({
return null
},
// 模拟登录已废弃 - 不再使用
// 现在必须使用真实的微信登录获取openId作为唯一标识
mockLogin() {
console.warn('[App] mockLogin已废弃请使用真实登录')
return null
},
// 手机号登录:需同时传 wx.login 的 code 与 getPhoneNumber 的 phoneCode
async loginWithPhone(phoneCode) {
if (!this.ensureFullAppForAuth()) {