feat: 小程序阅读记录与资料链路、管理端用户规则、API/VIP/推荐与运营脚本

- miniprogram: reading-records、imageUrl/mpNavigate、多页资料与 VIP 展示调整
- soul-admin: Users/Settings/UserDetailModal、dist 构建产物更新
- soul-api: user/vip/referral/ckb/db、MBTI 头像管理、user_rule_completion、迁移 SQL
- .cursor: karuo-party 与飞书文档;.gitignore 忽略 .tmp_skill_bundle

Made-with: Cursor
This commit is contained in:
卡若
2026-03-23 18:38:23 +08:00
parent cb6e2bff56
commit fa3da12b16
82 changed files with 5621 additions and 2723 deletions

View File

@@ -8,6 +8,18 @@ const app = getApp()
const { checkAndExecute } = require('../../utils/ruleEngine.js')
const { trackClick } = require('../../utils/trackClick')
/** 是否视为未设置头像(勿用 includes('132'):微信 CDN 合法头像 URL 普遍含 /132/ 尺寸段) */
function isMissingOrPlaceholderAvatar(avatarUrl, hasAvatarFromServer) {
if (hasAvatarFromServer === true || hasAvatarFromServer === 1) return false
const a = (avatarUrl || '').trim()
if (!a) return true
const u = a.toLowerCase()
if (u.includes('default')) return true
// 微信默认占位常见以 /0 结尾;/132/ 为正常尺寸路径,不能当作占位
if (/\/0($|[?#])/.test(u)) return true
return false
}
// 默认匹配类型配置
// 找伙伴:真正的匹配功能,匹配数据库中的真实用户
// 资源对接:需要登录+购买章节才能使用填写2项信息我能帮到你什么、我需要什么帮助
@@ -226,19 +238,21 @@ Page({
if (!userId) { callback(); return }
try {
const res = await app.request({ url: `/api/miniprogram/user/profile?userId=${userId}`, silent: true })
const avatar = res?.data?.avatarUrl || app.globalData.userInfo?.avatarUrl || ''
const isDefaultAvatar = !avatar || avatar.includes('default') || avatar.includes('132')
if (isDefaultAvatar) {
const d = res?.data || {}
const avatar = (d.avatar || d.avatarUrl || app.globalData.userInfo?.avatar || app.globalData.userInfo?.avatarUrl || '').trim()
const hasAvatarFlag = d.hasAvatar === true || d.hasAvatar === 1
if (isMissingOrPlaceholderAvatar(avatar, hasAvatarFlag)) {
wx.showModal({
title: '完善头像',
content: '请先设置头像后再使用匹配功能',
confirmText: '去设置',
cancelText: '取消',
success: (r) => { if (r.confirm) wx.navigateTo({ url: '/pages/profile-edit/profile-edit' }) }
})
return
}
const phone = (res?.data?.phone || wx.getStorageSync('user_phone') || '').trim().replace(/\s/g, '')
const wechat = (res?.data?.wechatId || res?.data?.wechat_id || wx.getStorageSync('user_wechat') || '').trim()
const phone = (d.phone || wx.getStorageSync('user_phone') || '').trim().replace(/\s/g, '')
const wechat = (d.wechatId || d.wechat_id || wx.getStorageSync('user_wechat') || '').trim()
if (phone && /^1[3-9]\d{9}$/.test(phone)) {
callback()
return
@@ -413,6 +427,16 @@ Page({
// 开始匹配 - 只匹配数据库中的真实用户
async startMatch() {
const uidEarly = app.globalData.userInfo?.id
if (!uidEarly) {
wx.showModal({
title: '需要登录',
content: '找伙伴匹配需登录账号,请先登录',
confirmText: '去登录',
success: (r) => { if (r.confirm) wx.switchTab({ url: '/pages/my/my' }) },
})
return
}
this.setData({
isMatching: true,
matchAttempts: 0,
@@ -424,23 +448,39 @@ Page({
this.setData({ matchAttempts: this.data.matchAttempts + 1 })
}, 1000)
// 从数据库获取真实用户匹配
// 从数据库获取真实用户匹配(带上手机/微信写入 match_records与流量池运营对齐
let matchedUser = null
let matchFailHint = ''
const uid = app.globalData.userInfo?.id || ''
const phoneForMatch = (this.data.phoneNumber || wx.getStorageSync('user_phone') || '').trim().replace(/\s/g, '')
const wechatForMatch = (this.data.wechatId || wx.getStorageSync('user_wechat') || '').trim()
try {
const res = await app.request({ url: '/api/miniprogram/match/users', silent: true,
const res = await app.request({
url: '/api/miniprogram/match/users',
silent: true,
method: 'POST',
data: {
matchType: this.data.selectedType,
userId: app.globalData.userInfo?.id || ''
}
userId: uid,
phone: phoneForMatch || undefined,
wechatId: wechatForMatch || undefined,
},
})
if (res.success && res.data) {
matchedUser = res.data
console.log('[Match] 从数据库匹配到用户:', matchedUser.nickname)
} else if (res && !res.success) {
matchFailHint = res.message || res.error || ''
if (res.code === 'QUOTA_EXCEEDED') {
matchFailHint = matchFailHint || '今日免费次数已用完,可购买额外匹配次数后再试'
} else if (res.code === 'NO_USERS') {
matchFailHint = matchFailHint || '当前流量池暂无可匹配用户,可稍后再试;补全档案后匹配范围通常更大。'
}
}
} catch (e) {
console.log('[Match] 数据库匹配失败:', e)
matchFailHint = (e && e.message) ? String(e.message) : '网络异常,请稍后重试'
}
// 延迟显示结果(模拟匹配过程)
@@ -453,7 +493,7 @@ Page({
this.setData({ isMatching: false })
wx.showModal({
title: '暂无匹配',
content: '当前暂无合适的匹配用户,请稍后再试',
content: matchFailHint || '当前暂无合适的匹配用户,请稍后再试',
showCancel: false,
confirmText: '知道了'
})
@@ -498,7 +538,7 @@ Page({
}
}
})
// 匹配后规则:引导填写 MBTI/行业信息
// 匹配后规则:资料未齐时提示补全(服务端 profile 合并,见 ruleEngine
checkAndExecute('after_match', this)
} catch (e) {
console.log('上报匹配失败:', e)