微信小程序:自定义底栏接入 AI 入口(含图标与样式)、AI 聊天相关页面与历史/报告等能力;审核模式与配置联动;资料/企业/首页/授权等页面与 app 入口配合调整。

后端 API:AI 对话/报告/余额告警 等服务与路由;小程序 tabBar 配置与分润相关能力;出站推送 Webhook 与管理端调试;飞书留资、小程序数据分析 等支撑;多份 数据库迁移(AI 聊天、灵魂文章、tabBar、分润等)。
管理端 / 超管:看板、用户/订单/分销/财务、设置与 小程序 tabBar 管理、分润规则、灵魂文章、AI 配置与监控 等大量页面与能力扩展;主题与布局更新。
抖音小程序:与微信侧在 结果页/购买/个人中心 等方向做了同步或优化。
另含 部署/脚本、开发文档 等配套更新。
邀请码与解锁:后端 分销/邀请码 接口与 add_invite_codes 等 SQL;小程序侧 邀请码弹窗组件、个人中心/推广/多类结果页 的填写入口与 解锁门控(inviteCodeGate、unlockGate);并补充 《小程序解锁引导与邀请码方案》 文档。
超管后台:路由与 企业中心(EnterpriseHub) 等交互与结构优化。
小程序体验:在 首页结果、推广、各测评结果页 上继续打磨;与 邀请码门控、审核门控 小步联动。
This commit is contained in:
Ghost
2026-04-23 11:28:16 +08:00
parent d07235b64c
commit d7137f9349
53 changed files with 1817 additions and 226 deletions

View File

@@ -4,6 +4,7 @@ const { getTypeOnly } = require('../../utils/resultFormat')
const { request } = require('../../utils/request')
const { buildRecoMiniProgramPath } = require('../../utils/recoMiniProgram.js')
const { isAuditHideAiMode } = require('../../utils/miniprogramAuditGate.js')
const inviteCodeGate = require('../../utils/inviteCodeGate.js')
/** 与超管「小程序 · 推荐文章展示」中「区块标题」一致;接口无字段时的兜底 */
const PROFILE_RECO_LABEL_FALLBACK = '我的由来'
@@ -79,7 +80,13 @@ Page({
profileRecoSectionLabel: '',
profileRecoArticle: null,
/** 与神仙 AI 精选推荐同源:配置了 AppID 时优先跳转目标小程序 */
profileRecoJump: null
profileRecoJump: null,
/** 拉取自 /api/distribution/my-invite-code与推广中心一致 */
myInviteCode: '',
myInviteCodeLoadState: 'idle',
myInviteCodeErrMsg: '',
myInviteCodeHint: '',
showInviteCodeDialog: false
},
_computeShowLatestTestRow(d) {
@@ -223,19 +230,127 @@ Page({
// 2. 已登录则从服务端拉取最近记录;失败降级读 localStorage
if (token || userInfo) {
if (token && this.data.permDistribution) {
this.setData({ myInviteCodeLoadState: 'loading', myInviteCodeErrMsg: '' })
}
this._loadRecentFromAPI()
this._loadPromoStats()
this._loadMyInviteCode()
this._loadProfileRecoTeaser()
} else {
this.setData({
profileRecoShow: false,
profileRecoArticle: null,
profileRecoSectionLabel: '',
profileRecoJump: null
profileRecoJump: null,
myInviteCode: '',
myInviteCodeLoadState: 'idle',
myInviteCodeErrMsg: '',
myInviteCodeHint: '',
showInviteCodeDialog: false
})
}
},
openInviteCodeFill() {
app.ensureLogin().then((ok) => {
if (!ok) {
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
inviteCodeGate.openInviteCodeDialog(this)
})
},
onInviteCodeSkip() {
inviteCodeGate.finishInviteCodeGate(this, true)
},
onInviteCodeSuccess() {
inviteCodeGate.finishInviteCodeGate(this, true)
},
_loadMyInviteCode() {
const token = app.globalData.token || wx.getStorageSync('token')
if (!token || !this.data.permDistribution) {
this.setData({
myInviteCode: '',
myInviteCodeLoadState: 'idle',
myInviteCodeErrMsg: '',
myInviteCodeHint: ''
})
return
}
this.setData({
myInviteCodeLoadState: 'loading',
myInviteCodeErrMsg: ''
})
request({
url: '/api/distribution/my-invite-code',
method: 'GET',
success: (res) => {
const payload = res && res.data
const httpOk = res.statusCode >= 200 && res.statusCode < 300
const bizOk = payload && Number(payload.code) === 200
if (!httpOk || !bizOk) {
const msg =
(payload && payload.message) ||
(typeof res.statusCode === 'number' ? `请求失败 (${res.statusCode})` : '加载失败')
this.setData({
myInviteCode: '',
myInviteCodeLoadState: 'error',
myInviteCodeErrMsg: msg,
myInviteCodeHint: ''
})
return
}
const data = payload.data || {}
const raw = data.code != null ? String(data.code).trim() : ''
const code = raw ? raw.toUpperCase() : ''
const hint = (data.hint && String(data.hint).trim()) || ''
if (code) {
this.setData({
myInviteCode: code,
myInviteCodeLoadState: 'ready',
myInviteCodeErrMsg: '',
myInviteCodeHint: ''
})
} else {
this.setData({
myInviteCode: '',
myInviteCodeLoadState: 'nodata',
myInviteCodeErrMsg: '',
myInviteCodeHint: hint || '暂无可用邀请码'
})
}
},
fail: () =>
this.setData({
myInviteCode: '',
myInviteCodeLoadState: 'error',
myInviteCodeErrMsg: '网络异常',
myInviteCodeHint: ''
})
})
},
onInviteStripTap() {
if (this.data.myInviteCodeLoadState === 'error') {
this._loadMyInviteCode()
return
}
this.goToPromo()
},
copyMyInviteCode() {
const code = (this.data.myInviteCode || '').trim()
if (!code) return
wx.setClipboardData({
data: code,
success: () => wx.showToast({ title: '已复制邀请码', icon: 'success' })
})
},
_parseRecoJumpFromDisplay(d) {
if (!d || typeof d !== 'object') return null
const appId = ((d.recoJumpMiniAppId || '') + '').trim()