fix(invitation): 线下提号接入邀请返利并补发历史漏发,返利比例文案改读配置

- 线下提号(admin_pickups)不走订单结账流程,此前完全不发邀请返利;现于 Complete() 内按号主利润对半基数发放卖家侧返利
- 迁移 000086:invitation_rebate_ledgers 增加 source 列,唯一键调整为 (source, order_id, trade_role),支持提号来源且保持幂等
- 新增 cmd/rebate-backfill 幂等补发脚本:默认预演,加 -apply 才写库
- 返利阶段文案不再写死 60%/30%/15%,改为按本次实际生效比例生成(钱包流水备注同步修正)
- 前台邀请页阶段标签/横幅/空态文案、移动端个人中心入口文案均改读接口配置,并修掉合法 0% 比例被 || 覆盖成 60% 的问题
- 新增单测:提号返利发放与幂等、cycleLabel 跟随后台配置
This commit is contained in:
yml2213
2026-10-13 15:12:54 +08:00
parent 0d6a7786a4
commit ead58417e0
12 changed files with 658 additions and 22 deletions
@@ -143,16 +143,43 @@ function formatRate(rate: number) {
return `${Math.round(rate * 100)}%`
}
function cycleLabel(cycle: string) {
/** 配置未加载时用占位符,避免展示与后台配置不符的假比例。 */
function formatRateOrPlaceholder(rate: number | undefined | null) {
return rate === undefined || rate === null ? '-' : formatRate(rate)
}
/** 返利阶段文案:比例取该笔流水实际生效的 rate_applied,不能写死 60/30/15。 */
function cycleLabel(cycle: string, rate: number) {
const percent = formatRate(rate)
switch (cycle) {
case 'first_month':
return '首月高额 (60%)'
return `首月奖励 ${percent}`
case 'second_month':
return '次月奖励 (30%)'
return `次月奖励 ${percent}`
case 'subsequent':
return `长期奖励 ${percent}`
default:
return '长期分成 (15%)'
return `返利 ${percent}`
}
}
/** 顶部横幅文案按后台实时配置比例生成。 */
const heroSubtitle = computed(() => {
const current = summary.value
if (!current) return '买卖双边分成按实名生效周期阶梯计算 · 多劳多得零门槛'
return `买卖双边分成:首月单边 ${formatRate(current.first_month_rate)},次月 ${formatRate(
current.second_month_rate
)},长期 ${formatRate(current.subsequent_rate)} · 多劳多得零门槛`
})
/** 好友空态文案按首月配置比例生成。 */
const emptyFriendsTip = computed(() => {
const rate = summary.value?.first_month_rate
if (rate === undefined || rate === null) {
return '您尚未邀请好友,快去分享专属链接赚取返利吧!'
}
return `您尚未邀请好友,快去分享专属链接赚取首月 ${formatRate(rate)} 返利吧!`
})
</script>
<template>
@@ -166,7 +193,7 @@ function cycleLabel(cycle: string) {
</div>
<h1 class="hero-title">邀请好友赚丰厚返利</h1>
<p class="hero-subtitle">
买卖双边分成:首月单边高达 60%,次月 30%,永久 15% · 多劳多得零门槛
{{ heroSubtitle }}
</p>
</div>
@@ -279,21 +306,21 @@ function cycleLabel(cycle: string) {
<div class="ladder-grid">
<div class="ladder-item ladder-first">
<div class="ladder-tag">阶梯一 · 首月</div>
<div class="ladder-rate">{{ formatRate(summary?.first_month_rate || 0.6) }}</div>
<div class="ladder-rate">{{ formatRateOrPlaceholder(summary?.first_month_rate) }}</div>
<div class="ladder-time">实名生效 1 ~ 30 天</div>
<div class="ladder-desc">每笔订单单边收益高比例奖励</div>
</div>
<div class="ladder-item ladder-second">
<div class="ladder-tag">阶梯二 · 次月</div>
<div class="ladder-rate">{{ formatRate(summary?.second_month_rate || 0.3) }}</div>
<div class="ladder-rate">{{ formatRateOrPlaceholder(summary?.second_month_rate) }}</div>
<div class="ladder-time">实名生效 31 ~ 60 天</div>
<div class="ladder-desc">持续稳定鼓励买卖活跃交易</div>
</div>
<div class="ladder-item ladder-subsequent">
<div class="ladder-tag">阶梯三 · 长期永久</div>
<div class="ladder-rate">{{ formatRate(summary?.subsequent_rate || 0.15) }}</div>
<div class="ladder-rate">{{ formatRateOrPlaceholder(summary?.subsequent_rate) }}</div>
<div class="ladder-time">实名生效 60 天后永久</div>
<div class="ladder-desc">终身绑定,笔笔成交享分成收益</div>
</div>
@@ -339,7 +366,7 @@ function cycleLabel(cycle: string) {
<div v-if="loadingFriends" class="loading-state">加载好友列表中...</div>
<div v-else-if="friends.length === 0" class="empty-state">
<el-icon :size="40" color="#cbd5e1"><User /></el-icon>
<p>您尚未邀请好友,快去分享专属链接赚取首月60%返利吧!</p>
<p>{{ emptyFriendsTip }}</p>
</div>
<div v-else class="table-responsive">
<table class="data-table">
@@ -414,7 +441,7 @@ function cycleLabel(cycle: string) {
</td>
<td>¥{{ formatCent(r.single_side_basis_cent) }}</td>
<td>
<span class="cycle-pill">{{ cycleLabel(r.cycle_stage) }}</span>
<span class="cycle-pill">{{ cycleLabel(r.cycle_stage, r.rate_applied) }}</span>
</td>
<td class="rebate-amount">+¥{{ formatCent(r.rebate_amount_cent) }}</td>
<td>{{ r.settled_at ? r.settled_at.slice(0, 16) : '-' }}</td>