微信小程序:自定义底栏接入 AI 入口(含图标与样式)、AI 聊天相关页面与历史/报告等能力;审核模式与配置联动;资料/企业/首页/授权等页面与 app 入口配合调整。
后端 API:AI 对话/报告/余额告警 等服务与路由;小程序 tabBar 配置与分润相关能力;出站推送 Webhook 与管理端调试;飞书留资、小程序数据分析 等支撑;多份 数据库迁移(AI 聊天、灵魂文章、tabBar、分润等)。 管理端 / 超管:看板、用户/订单/分销/财务、设置与 小程序 tabBar 管理、分润规则、灵魂文章、AI 配置与监控 等大量页面与能力扩展;主题与布局更新。 抖音小程序:与微信侧在 结果页/购买/个人中心 等方向做了同步或优化。 另含 部署/脚本、开发文档 等配套更新。 邀请码与解锁:后端 分销/邀请码 接口与 add_invite_codes 等 SQL;小程序侧 邀请码弹窗组件、个人中心/推广/多类结果页 的填写入口与 解锁门控(inviteCodeGate、unlockGate);并补充 《小程序解锁引导与邀请码方案》 文档。 超管后台:路由与 企业中心(EnterpriseHub) 等交互与结构优化。 小程序体验:在 首页结果、推广、各测评结果页 上继续打磨;与 邀请码门控、审核门控 小步联动。
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
// pages/promo/index.js
|
||||
const app = getApp()
|
||||
const { request } = require('../../utils/request')
|
||||
const inviteCodeGate = require('../../utils/inviteCodeGate.js')
|
||||
const { shouldHideInviteCodeEntry } = require('../../utils/miniprogramAuditGate.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -30,7 +32,16 @@ Page({
|
||||
withdrawAmountInput: '',
|
||||
withdrawError: '',
|
||||
withdrawFeeYuan: '0.00',
|
||||
withdrawActualYuan: '0.00'
|
||||
withdrawActualYuan: '0.00',
|
||||
myInviteCode: '',
|
||||
myInviteCodeMeta: null,
|
||||
/** loading | ready | error | nodata — 避免因接口失败整块不渲染 */
|
||||
myInviteCodeLoadState: 'loading',
|
||||
myInviteCodeErrMsg: '',
|
||||
myInviteCodeHint: '',
|
||||
showInviteCodeDialog: false,
|
||||
/** 与结果页一致:审核/提审不展示邀请码相关块 */
|
||||
hideInviteCodeEntry: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -41,7 +52,10 @@ Page({
|
||||
wx.showToast({ title: '请先登录后查看推广中心', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const hide = shouldHideInviteCodeEntry(app.globalData || {})
|
||||
this.setData({ hideInviteCodeEntry: hide })
|
||||
this.loadStats()
|
||||
if (!hide) this.loadMyInviteCode()
|
||||
this.loadBindings(true)
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -50,8 +64,104 @@ Page({
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const hide = shouldHideInviteCodeEntry(app.globalData || {})
|
||||
this.setData({ hideInviteCodeEntry: hide })
|
||||
const token = app.globalData.token || wx.getStorageSync('token')
|
||||
if (token) this.loadStats()
|
||||
if (token) {
|
||||
this.loadStats()
|
||||
if (!hide) this.loadMyInviteCode()
|
||||
}
|
||||
},
|
||||
|
||||
loadMyInviteCode() {
|
||||
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: '',
|
||||
myInviteCodeMeta: null,
|
||||
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,
|
||||
myInviteCodeMeta: data,
|
||||
myInviteCodeLoadState: 'ready',
|
||||
myInviteCodeErrMsg: '',
|
||||
myInviteCodeHint: ''
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
myInviteCode: '',
|
||||
myInviteCodeMeta: data,
|
||||
myInviteCodeLoadState: 'nodata',
|
||||
myInviteCodeErrMsg: '',
|
||||
myInviteCodeHint: hint || '暂无可用邀请码'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
this.setData({
|
||||
myInviteCode: '',
|
||||
myInviteCodeMeta: null,
|
||||
myInviteCodeLoadState: 'error',
|
||||
myInviteCodeErrMsg: '网络异常,请稍后重试',
|
||||
myInviteCodeHint: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
retryMyInviteCode() {
|
||||
this.loadMyInviteCode()
|
||||
},
|
||||
|
||||
copyMyInviteCode() {
|
||||
const code = (this.data.myInviteCode || '').trim()
|
||||
if (!code) return
|
||||
wx.setClipboardData({
|
||||
data: code,
|
||||
success: () => wx.showToast({ title: '已复制邀请码', icon: 'success' })
|
||||
})
|
||||
},
|
||||
|
||||
/** 填写他人邀请码(与结果页支付前弹框同一组件) */
|
||||
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)
|
||||
},
|
||||
|
||||
/** 加载推广统计数据 */
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "推广中心",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black"
|
||||
"navigationBarTextStyle": "black",
|
||||
"usingComponents": {
|
||||
"invite-code-dialog": "/components/invite-code-dialog/invite-code-dialog"
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,40 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<block wx:if="{{!hideInviteCodeEntry}}">
|
||||
<view class="my-invite-card">
|
||||
<view class="my-invite-card__head">
|
||||
<text class="my-invite-card__title">我的邀请码</text>
|
||||
<view wx:if="{{myInviteCodeLoadState === 'ready' && myInviteCode}}" class="my-invite-card__copy" bindtap="copyMyInviteCode">复制</view>
|
||||
<view wx:elif="{{myInviteCodeLoadState === 'error'}}" class="my-invite-card__retry" bindtap="retryMyInviteCode">重试</view>
|
||||
</view>
|
||||
<block wx:if="{{myInviteCodeLoadState === 'loading'}}">
|
||||
<text class="my-invite-card__code my-invite-card__code--muted">加载中…</text>
|
||||
</block>
|
||||
<block wx:elif="{{myInviteCodeLoadState === 'error'}}">
|
||||
<text class="my-invite-card__err">{{myInviteCodeErrMsg}}</text>
|
||||
<text class="my-invite-card__tip">若提示功能未就绪,请在数据库执行 api/database/add_invite_codes.sql 建表后再试。</text>
|
||||
</block>
|
||||
<block wx:elif="{{myInviteCodeLoadState === 'ready' && myInviteCode}}">
|
||||
<text class="my-invite-card__code" user-select="true">{{myInviteCode}}</text>
|
||||
<text class="my-invite-card__tip">好友在付费解锁前填写此码,即可与你建立推广绑定</text>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<text class="my-invite-card__err">{{myInviteCodeHint}}</text>
|
||||
<text wx:if="{{myInviteCodeLoadState === 'nodata'}}" class="my-invite-card__tip">你可稍后下拉刷新或联系管理员。</text>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<view class="invite-fill-entry" bindtap="openInviteCodeFill">
|
||||
<text class="invite-fill-entry__icon">🔗</text>
|
||||
<view class="invite-fill-entry__main">
|
||||
<text class="invite-fill-entry__title">填写好友邀请码</text>
|
||||
<text class="invite-fill-entry__sub">绑定推广关系 · 付费前也可在此填写</text>
|
||||
</view>
|
||||
<text class="invite-fill-entry__arrow">›</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 自定义提现金额弹框 -->
|
||||
<view class="withdraw-mask" wx:if="{{showWithdrawDialog}}">
|
||||
<view class="withdraw-dialog">
|
||||
@@ -216,4 +250,10 @@
|
||||
</view>
|
||||
|
||||
<view class="safe-bottom"></view>
|
||||
|
||||
<invite-code-dialog
|
||||
visible="{{showInviteCodeDialog}}"
|
||||
bind:skip="onInviteCodeSkip"
|
||||
bind:success="onInviteCodeSuccess"
|
||||
/>
|
||||
</view>
|
||||
|
||||
@@ -19,6 +19,70 @@ page {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 我的邀请码 */
|
||||
.my-invite-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 28rpx 32rpx;
|
||||
margin-bottom: 32rpx;
|
||||
border: 2rpx solid #fce7f3;
|
||||
box-shadow: 0 4rpx 24rpx rgba(244, 63, 94, 0.06);
|
||||
}
|
||||
.my-invite-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.my-invite-card__title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
.my-invite-card__copy {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #e11d48;
|
||||
padding: 10rpx 24rpx;
|
||||
background: #fff1f2;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
.my-invite-card__code {
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
color: #0f172a;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.my-invite-card__tip {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.45;
|
||||
color: #64748b;
|
||||
}
|
||||
.my-invite-card__code--muted {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
letter-spacing: normal;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.my-invite-card__err {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.5;
|
||||
color: #b45309;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.my-invite-card__retry {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #e11d48;
|
||||
padding: 10rpx 24rpx;
|
||||
background: #fff1f2;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.hero-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
Reference in New Issue
Block a user