diff --git a/application/api/controller/Auth.php b/application/api/controller/Auth.php
index 109a75c..48eff2f 100644
--- a/application/api/controller/Auth.php
+++ b/application/api/controller/Auth.php
@@ -101,10 +101,7 @@ class Auth extends V1
DistributionService::bindRelation($uid, $inviterId, $inviteCode);
$user = Db::name('user')->where('id', $uid)->find();
} else {
- // 老用户:若未绑定过推荐人,本次传入有效推荐人则尝试绑定
- if ($inviterId > 0 || $inviteCode !== '') {
- DistributionService::bindRelation($user['id'], $inviterId, $inviteCode);
- }
+ // 老用户:不再自动补绑推荐人(老用户须在分销中心手动填写邀请码补绑)
// 老用户:仅在本次带上了新值且确实不同时更新(不覆盖运营在后台改过的资料)
$update = [];
if ($nickname !== '' && $nickname !== (string)$user['nickname']) {
@@ -550,20 +547,12 @@ class Auth extends V1
// 频率限制:同一手机号 + IP,1 分钟最多 5 次
$this->rateLimit('pwdlogin:' . $phone . ':' . $this->request->ip(), 5, 60);
- $inviterId = (int)$this->input('inviterId', $this->input('inviter_id', 0));
- $inviteCode = trim((string)$this->input('inviteCode', $this->input('invite_code', '')));
-
// 查用户:不存在直接失败,不再自动注册(防止批量造号)
$user = Db::name('user')->where('mobile', $phone)->find();
if (!$user) {
$this->fail('手机号未注册,请先使用微信登录');
}
- // 老用户:若未绑定过推荐人,本次传入有效推荐人则尝试绑定
- if ($inviterId > 0 || $inviteCode !== '') {
- DistributionService::bindRelation($user['id'], $inviterId, $inviteCode);
- }
-
// 校验密码:只认加盐哈希(已移除历史遗留的 123456 万能密码)
$salt = isset($user['salt']) ? (string)$user['salt'] : '';
$checkHash = md5(md5($password) . $salt);
diff --git a/application/api/controller/Distribution.php b/application/api/controller/Distribution.php
index 6eaa90a..8325ca8 100644
--- a/application/api/controller/Distribution.php
+++ b/application/api/controller/Distribution.php
@@ -46,6 +46,22 @@ class Distribution extends V1
$code = (string)$ext['invite_code'];
$shareUrl = 'https://fenleixinxi.gzyakj.com.cn/#/pages/index/index?ic=' . urlencode($code);
+ // 查询当前用户的上级推荐人信息(用于前端展示「已绑定推荐人」或「手动绑定邀请码」入口)
+ $inviterId = (int)(isset($ext['inviter_id']) ? $ext['inviter_id'] : 0);
+ if ($inviterId <= 0) {
+ $l1Rel = Db::name('fc_dist_relation')->where('user_id', $uid)->where('level', 1)->find();
+ if ($l1Rel && (int)$l1Rel['parent_id'] > 0) {
+ $inviterId = (int)$l1Rel['parent_id'];
+ }
+ }
+ $inviterNickname = '';
+ if ($inviterId > 0) {
+ $inviterUser = Db::name('user')->where('id', $inviterId)->field('nickname,username')->find();
+ if ($inviterUser) {
+ $inviterNickname = (string)($inviterUser['nickname'] ?: $inviterUser['username']);
+ }
+ }
+
// 商家转账确认收款页需要的商户号与小程序 AppID(非敏感,可下发)
$epay = (array)get_addon_config('epay');
$wx = isset($epay['wechat']) && is_array($epay['wechat']) ? $epay['wechat'] : [];
@@ -60,6 +76,8 @@ class Distribution extends V1
'l1Count' => $l1,
'l2Count' => $l2,
'inviteCode' => $code,
+ 'inviterId' => $inviterId,
+ 'inviterNickname' => $inviterNickname,
'shareUrl' => $shareUrl,
'posterUrl' => '',
// 最低提现金额(后台「分销设置」下发,默认 50)
@@ -351,6 +369,72 @@ class Distribution extends V1
$this->ok($list);
}
+ /**
+ * D7 手动输入邀请码补绑推荐人(老用户不支持自动补绑,仅支持通过手动填写邀请码完成补绑)
+ * POST /api/v1/distribution/bind 入参 {inviteCode}
+ *
+ * @return void 输出绑定结果与推荐人信息JSON
+ */
+ public function bind()
+ {
+ $user = $this->auth();
+ $uid = (int)$user['id'];
+
+ $inviteCode = strtoupper(trim((string)$this->input('inviteCode', $this->input('invite_code', ''))));
+ if ($inviteCode === '') {
+ $this->fail('请输入有效的邀请码');
+ }
+
+ // 检查当前用户是否已绑定过推荐人
+ $ext = $this->ext($uid);
+ $existingInviter = (int)(isset($ext['inviter_id']) ? $ext['inviter_id'] : 0);
+ if ($existingInviter <= 0) {
+ $l1Rel = Db::name('fc_dist_relation')->where('user_id', $uid)->where('level', 1)->find();
+ if ($l1Rel && (int)$l1Rel['parent_id'] > 0) {
+ $existingInviter = (int)$l1Rel['parent_id'];
+ }
+ }
+ if ($existingInviter > 0) {
+ $this->fail('您已绑定过推荐人,不可重复绑定');
+ }
+
+ // 校验邀请码并定位目标推荐人
+ if (isset($ext['invite_code']) && strtoupper((string)$ext['invite_code']) === $inviteCode) {
+ $this->fail('不能填写自己的邀请码');
+ }
+ $parentExt = Db::name('fc_user_ext')->where('invite_code', $inviteCode)->find();
+ if (!$parentExt || (int)$parentExt['user_id'] <= 0) {
+ $this->fail('邀请码不存在,请核对后重试');
+ }
+ $targetInviterId = (int)$parentExt['user_id'];
+ if ($targetInviterId === $uid) {
+ $this->fail('不能绑定自己为推荐人');
+ }
+
+ // 防环校验:不能绑定自己的下级成员
+ $isDownline = Db::name('fc_dist_relation')
+ ->where('user_id', $targetInviterId)
+ ->where('parent_id', $uid)
+ ->count();
+ if ($isDownline > 0) {
+ $this->fail('不能绑定您的下级成员为推荐人');
+ }
+
+ $ok = \app\common\library\DistributionService::bindRelation($uid, $targetInviterId, $inviteCode);
+ if (!$ok) {
+ $this->fail('绑定失败,请核对邀请码后重试');
+ }
+
+ $inviterUser = Db::name('user')->where('id', $targetInviterId)->field('nickname,username')->find();
+ $inviterNickname = $inviterUser ? (string)($inviterUser['nickname'] ?: $inviterUser['username']) : '同城合伙人';
+
+ $this->ok([
+ 'bound' => true,
+ 'inviterId' => $targetInviterId,
+ 'inviterNickname' => $inviterNickname,
+ ]);
+ }
+
/**
* 计算当前用户可提现余额
* 可提现余额 = Σ已结算佣金(status=1) - Σ(申请中/已打款)提现
diff --git a/application/common/library/DistributionService.php b/application/common/library/DistributionService.php
index a28f157..fc2fb58 100644
--- a/application/common/library/DistributionService.php
+++ b/application/common/library/DistributionService.php
@@ -37,8 +37,8 @@ class DistributionService
}
$parent1Id = 0;
- // 优先通过邀请码查询推荐人
- $inviteCode = trim((string)$inviteCode);
+ // 优先通过邀请码查询推荐人(忽略大小写)
+ $inviteCode = strtoupper(trim((string)$inviteCode));
if ($inviteCode !== '') {
$parentExt = Db::name('fc_user_ext')->where('invite_code', $inviteCode)->find();
if ($parentExt && (int)$parentExt['user_id'] > 0) {
@@ -59,6 +59,15 @@ class DistributionService
return false;
}
+ // 防环校验:不能将自己的直接或间接下线绑定为自己的推荐人
+ $isDownline = Db::name('fc_dist_relation')
+ ->where('user_id', $parent1Id)
+ ->where('parent_id', $userId)
+ ->count();
+ if ($isDownline > 0) {
+ return false;
+ }
+
$now = time();
// 更新用户扩展表的直接推荐人ID
@@ -94,6 +103,32 @@ class DistributionService
}
}
+ // 3. 若当前用户在补绑推荐人之前已经发展了 1 级下线,则为这些 1 级下线补齐以 $parent1Id 为 2 级推荐人的关系
+ $myL1Children = Db::name('fc_dist_relation')
+ ->where('parent_id', $userId)
+ ->where('level', 1)
+ ->select();
+ if (!empty($myL1Children)) {
+ foreach ($myL1Children as $childRow) {
+ $childId = (int)$childRow['user_id'];
+ if ($childId <= 0 || $childId === $parent1Id) {
+ continue;
+ }
+ $hasL2 = Db::name('fc_dist_relation')
+ ->where('user_id', $childId)
+ ->where('level', 2)
+ ->count();
+ if ($hasL2 <= 0) {
+ Db::name('fc_dist_relation')->insert([
+ 'user_id' => $childId,
+ 'parent_id' => $parent1Id,
+ 'level' => 2,
+ 'createtime' => $now,
+ ]);
+ }
+ }
+ }
+
return true;
}
diff --git a/application/route.php b/application/route.php
index 30816f5..6923e36 100755
--- a/application/route.php
+++ b/application/route.php
@@ -101,6 +101,7 @@ return [
'api/v1/distribution/poster' => 'api/Distribution/poster',
'api/v1/distribution/withdraw' => 'api/Distribution/withdraw',
'api/v1/distribution/withdraw-records' => 'api/Distribution/withdrawRecords',
+ 'api/v1/distribution/bind' => 'api/Distribution/bind',
/* =============== 六、用户端 · 消息中心(Module-MESSAGE) =============== */
'api/v1/message/list' => 'api/Message/list',
diff --git a/uniapp/App.vue b/uniapp/App.vue
index 22da80c..de2665b 100644
--- a/uniapp/App.vue
+++ b/uniapp/App.vue
@@ -13,10 +13,14 @@ export default {
},
methods: {
/**
- * 捕获并解析邀请分销参数(支持普通 query 与微信小程序码 scene 场景值)
+ * 捕获并解析邀请分销参数(仅供未注册新用户首次登录建档使用,老用户须手动输入邀请码补绑)
* @param {Object} options 小程序启动或页面入参
*/
captureInvite(options) {
+ // 已登录老用户不自动捕获或自动补绑,须在分销中心手动填写邀请码绑定
+ if (uni.getStorageSync('fc_token')) {
+ return
+ }
const query = (options && options.query) || {}
let ic = query.ic || query.inviteCode || query.invite_code
let inviterId = query.inviterId || query.inviter_id
@@ -31,9 +35,6 @@ export default {
ic = sceneStr.slice(3)
} else if (sceneStr.indexOf('uid_') === 0) {
inviterId = sceneStr.slice(4)
- } else if (/^id_\d+_u_(\d+)$/.test(sceneStr)) {
- const m = sceneStr.match(/^id_\d+_u_(\d+)$/)
- if (m && m[1]) inviterId = m[1]
} else if (sceneStr.indexOf('uid=') === 0) {
inviterId = sceneStr.slice(4)
} else if (/^id_\d+_u_(\d+)$/.test(sceneStr)) {
diff --git a/uniapp/common/api/distribution.js b/uniapp/common/api/distribution.js
index 79febda..b53f6f5 100644
--- a/uniapp/common/api/distribution.js
+++ b/uniapp/common/api/distribution.js
@@ -17,6 +17,8 @@ export const distributionApi = {
withdraw: (payload) => post('/distribution/withdraw', payload, { silent: true }),
/** 提现记录 */
withdrawRecords: () => get('/distribution/withdraw-records', {}, { silent: true }),
+ /** 绑定分销推荐人(扫码自动补绑 / 手动填写邀请码绑定) */
+ bind: (payload = {}) => post('/distribution/bind', payload, { silent: true }),
}
export default distributionApi
diff --git a/uniapp/common/store/index.js b/uniapp/common/store/index.js
index 3c59882..983e70a 100644
--- a/uniapp/common/store/index.js
+++ b/uniapp/common/store/index.js
@@ -41,6 +41,8 @@ export const useUserStore = () => {
storage.set(config.storageKeys.token, state.token)
storage.set(config.storageKeys.userInfo, state.userInfo)
storage.set(config.storageKeys.userType, state.userType)
+ storage.remove('fc_invite_code')
+ storage.remove('fc_inviter_id')
return res
}
diff --git a/uniapp/pages/detail/detail.vue b/uniapp/pages/detail/detail.vue
index 05c2af3..f846d89 100644
--- a/uniapp/pages/detail/detail.vue
+++ b/uniapp/pages/detail/detail.vue
@@ -708,8 +708,10 @@ export default {
}
} catch (e) {}
}
- if (ic) uni.setStorageSync('fc_invite_code', String(ic))
- if (inviterId) uni.setStorageSync('fc_inviter_id', Number(inviterId))
+ if (!uni.getStorageSync('fc_token')) {
+ if (ic) uni.setStorageSync('fc_invite_code', String(ic))
+ if (inviterId) uni.setStorageSync('fc_inviter_id', Number(inviterId))
+ }
}
this.id = Number((query && query.id) || postIdFromScene || 101)
this.posterImagePath = ''
diff --git a/uniapp/pages/distribution/index.vue b/uniapp/pages/distribution/index.vue
index 086ca06..06b16e3 100644
--- a/uniapp/pages/distribution/index.vue
+++ b/uniapp/pages/distribution/index.vue
@@ -82,7 +82,7 @@
-
+
@@ -95,7 +95,7 @@
-
+
@@ -109,12 +109,16 @@
专属海报
+
-
-
-
- 复制链接
-
+
+
+ 我的推荐人:{{ overview.inviterNickname || ('合伙人 #' + overview.inviterId) }}
+ 已锁定
+
+
+ 有好友邀请码?填写后锁定推荐关系
+ 填写邀请码 ›
@@ -477,6 +481,44 @@
+
+
+
+
+
+ 绑定推荐人邀请码
+
+
+
+
+
+
+
+
+
+
+
+ 每位用户仅可绑定一次推荐人,绑定后终身锁定不可更改
+
+
+
+ {{ bindSubmitting ? '绑定中…' : '确认绑定' }}
+
+
+
+
+
@@ -557,6 +599,9 @@ export default {
minWithdraw: 50,
showWithdraw: false,
showRuleModal: false,
+ showBindModal: false,
+ bindCodeInput: '',
+ bindSubmitting: false,
showPosterModal: false,
posterLoading: false,
posterImagePath: '',
@@ -930,6 +975,49 @@ export default {
this.copyText(this.overview.inviteCode, '邀请码已复制')
},
+ /**
+ * 打开手动绑定推荐人邀请码弹层
+ */
+ openBindModal() {
+ this.bindCodeInput = ''
+ this.showBindModal = true
+ },
+
+ /**
+ * 提交手动绑定推荐人邀请码
+ */
+ async submitBindCode() {
+ if (this.bindSubmitting) return
+ const code = String(this.bindCodeInput || '').trim().toUpperCase()
+ if (!code) {
+ uni.showToast({ title: '请输入好友的邀请码', icon: 'none' })
+ return
+ }
+ if (this.overview && this.overview.inviteCode && code === String(this.overview.inviteCode).toUpperCase()) {
+ uni.showToast({ title: '不能填写自己的邀请码', icon: 'none' })
+ return
+ }
+ this.bindSubmitting = true
+ try {
+ const res = await distributionApi.bind({ inviteCode: code })
+ this.showBindModal = false
+ this.bindCodeInput = ''
+ if (res && res.inviterId) {
+ this.overview = {
+ ...this.overview,
+ inviterId: res.inviterId,
+ inviterNickname: res.inviterNickname || '',
+ }
+ }
+ uni.showToast({ title: '推荐人绑定成功', icon: 'success' })
+ this.init()
+ } catch (e) {
+ uni.showToast({ title: (e && e.message) || '绑定失败,请核对邀请码', icon: 'none' })
+ } finally {
+ this.bindSubmitting = false
+ }
+ },
+
/**
* 复制我的专属推广链接
*/
@@ -1867,6 +1955,46 @@ export default {
margin-right: 8rpx;
}
+/* 推荐人展示与手动绑定入口栏 */
+.dist__inviter-bar {
+ margin-top: 20rpx;
+ padding: 18rpx 24rpx;
+ border-radius: 16rpx;
+ background: #F8FAFC;
+ border: 1rpx dashed #CBD5E1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ &:active {
+ opacity: 0.85;
+ }
+}
+.dist__inviter-bar--bound {
+ background: #F0FDF4;
+ border: 1rpx solid #DCFCE7;
+}
+.dist__inviter-label {
+ font-size: 24rpx;
+ color: #475569;
+}
+.dist__inviter-bar--bound .dist__inviter-label {
+ color: #1E293B;
+ font-weight: 500;
+}
+.dist__inviter-action {
+ font-size: 24rpx;
+ font-weight: 600;
+ color: #00A854;
+}
+.dist__inviter-tag {
+ font-size: 20rpx;
+ font-weight: 600;
+ color: #00A854;
+ background: #DCFCE7;
+ padding: 4rpx 14rpx;
+ border-radius: 999rpx;
+}
+
/* ==================== 顶部 Tab 导航 ==================== */
.dist__tabs-bar {
display: flex;
diff --git a/uniapp/pages/index/index.vue b/uniapp/pages/index/index.vue
index ef29765..41eb880 100644
--- a/uniapp/pages/index/index.vue
+++ b/uniapp/pages/index/index.vue
@@ -423,7 +423,7 @@ export default {
* @param {Object} query 路由启动参数
*/
onLoad(query) {
- if (query) {
+ if (query && !uni.getStorageSync('fc_token')) {
const ic = query.ic || query.inviteCode || query.invite_code
const inviterId = query.inviterId || query.inviter_id
if (ic) uni.setStorageSync('fc_invite_code', String(ic))
diff --git a/uniapp/pages/merchant/detail.vue b/uniapp/pages/merchant/detail.vue
index d636996..2ea1f6e 100644
--- a/uniapp/pages/merchant/detail.vue
+++ b/uniapp/pages/merchant/detail.vue
@@ -233,7 +233,7 @@ export default {
* @param {Object} query 路由入参
*/
onLoad(query) {
- if (query) {
+ if (query && !uni.getStorageSync('fc_token')) {
const ic = query.ic || query.inviteCode || query.invite_code
const inviterId = query.inviterId || query.inviter_id
if (ic) uni.setStorageSync('fc_invite_code', String(ic))