feat: 排行榜奖励展示、俱乐部分榜切换与手动领取

- 集团总榜/本俱乐部榜切换,奖金仅在本俱乐部榜行内展示
- 待领取底栏与规则说明,对接 phbjlxx/phbjllq/phbjlphb

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-01 17:40:57 +08:00
parent 1d7d7109c4
commit 64db9f52ae
3 changed files with 271 additions and 17 deletions

View File

@@ -9,6 +9,7 @@ import { CLUB_ID } from '../../config/club-config.js'
const IS_XZJ = CLUB_ID === 'xzj'
const RANK_API = IS_XZJ ? '/yonghu/xzjphbhqsj' : '/yonghu/phbhqsj'
const CLOSED_DATES = ['昨日', '上周', '上月']
const ROLE_LIST = [
{ key: 'dashou', label: '接单员' },
@@ -145,6 +146,8 @@ Page({
dateList: DATE_LIST,
currentRole: 'dashou',
currentDate: '今日',
fanwei: 'jituan',
myClubId: CLUB_ID || 'xq',
roleMeta: ROLE_META.dashou,
sortLabel: '分红总额',
isLoading: true,
@@ -153,6 +156,9 @@ Page({
restList: [],
isEmpty: false,
defaultAvatar: '',
rewardInfo: null,
showClaimBar: false,
claimLoading: false,
},
onLoad(options) {
@@ -239,6 +245,60 @@ Page({
this.fetchRankList()
},
onScopeTap(e) {
const fanwei = e.currentTarget.dataset.fanwei
if (!fanwei || fanwei === this.data.fanwei) return
this.setData({ fanwei })
this.fetchRankList()
},
onShowRules() {
const r = this.data.rewardInfo
if (!r || !r.tiers || !r.tiers.length) {
wx.showToast({ title: '暂无奖励规则', icon: 'none' })
return
}
const lines = r.tiers.map((t) => {
const label = t.label || `${t.rank_from}${t.rank_to > t.rank_from ? '-' + t.rank_to : ''}`
return `${label}:¥${t.amount}`
})
const tip = r.period_status === 'open' ? '(本期进行中,结束后可领取)' : ''
wx.showModal({
title: `${r.title || '排行榜奖励'}${tip}`,
content: [
`俱乐部:${r.club_id || this.data.myClubId}`,
`排序:${r.sort_label || ''}`,
...lines,
r.description || '',
].filter(Boolean).join('\n'),
showCancel: false,
})
},
async onClaimTap() {
const claim = this.data.rewardInfo && this.data.rewardInfo.my_claim
if (!claim || !claim.claim_id || this.data.claimLoading) return
this.setData({ claimLoading: true })
try {
const res = await request({
url: '/yonghu/phbjllq',
method: 'POST',
data: { claim_id: claim.claim_id },
})
const body = res?.data || {}
if (body.code === 200) {
wx.showToast({ title: `已领取 ¥${claim.amount}`, icon: 'success' })
await this.fetchRewardInfo()
} else {
wx.showToast({ title: body.msg || '领取失败', icon: 'none' })
}
} catch (e) {
wx.showToast({ title: '领取失败', icon: 'none' })
} finally {
this.setData({ claimLoading: false })
}
},
onRefreshTap() {
this.fetchRankList()
},
@@ -267,8 +327,8 @@ Page({
value: m.type === 'money' ? this.formatMoney(v) : this.formatInt(v),
}
})
const sortRaw = raw[meta.sortField]
const mainType = meta.mainType || 'money'
const sortRaw = raw[meta.sortField] ?? raw.metric_value
const mainType = raw.metric_label ? (Number.isInteger(Number(sortRaw)) && !String(sortRaw).includes('.') ? 'int' : 'money') : (meta.mainType || 'money')
const mainValue = mainType === 'money'
? this.formatMoney(sortRaw)
: this.formatInt(sortRaw)
@@ -277,30 +337,101 @@ Page({
uid,
nicheng,
avatar,
clubName: raw.club_name || raw.club_id || '',
rewardAmount: raw.reward_amount || 0,
mainType,
mainPrefix: mainType === 'money' ? '¥' : '',
mainValue,
mainLabel: meta.sortLabel,
mainLabel: raw.metric_label || meta.sortLabel,
metrics,
}
},
async fetchRankList() {
this.setData({ isLoading: true, isEmpty: false })
applyRewardToList(list, rewardInfo, fanwei) {
if (fanwei !== 'club') return list
if (!rewardInfo || !rewardInfo.rank_rewards) return list
const map = rewardInfo.rank_rewards
return list.map((item) => ({
...item,
rewardAmount: map[item.mingci] || item.rewardAmount || 0,
}))
},
async fetchRewardInfo() {
try {
const res = await request({
url: RANK_API,
url: '/yonghu/phbjlxx',
method: 'POST',
data: { shenfen: this.data.currentRole, riqi: this.data.currentDate },
data: {
shenfen: this.data.currentRole,
riqi: this.data.currentDate,
club_id: this.data.myClubId,
},
})
const body = res?.data || {}
const ok = body.code === 200 || body.code === 0
let list = ok && body.data
? (body.data.list || body.data.paihang_list || body.data.rank_list || [])
: []
if (body.code !== 200 || !body.data) return
const rewardInfo = body.data
const showClaimBar = !!(rewardInfo.my_claim && rewardInfo.my_claim.status === 'pending')
const { rankList, fanwei } = this.data
const merged = this.applyRewardToList(rankList, rewardInfo, fanwei)
this.setData({
rewardInfo,
showClaimBar,
rankList: merged,
topThree: merged.slice(0, 3),
restList: merged.slice(3),
})
} catch (e) {
console.warn('奖励信息加载失败', e)
}
},
async fetchRankList() {
this.setData({ isLoading: true, isEmpty: false, showClaimBar: false })
try {
const { currentRole, currentDate, fanwei, myClubId } = this.data
let list = []
let rewardFromRank = null
if (fanwei === 'club') {
const res = await request({
url: '/yonghu/phbjlphb',
method: 'POST',
data: {
shenfen: currentRole,
riqi: currentDate,
fanwei: 'club',
club_id: myClubId,
},
})
const body = res?.data || {}
if (body.code === 200 && body.data) {
list = body.data.list || []
rewardFromRank = body.data.reward || null
if (body.data.sort_label) {
this.setData({ sortLabel: body.data.sort_label })
}
}
} else {
const res = await request({
url: RANK_API,
method: 'POST',
data: { shenfen: currentRole, riqi: currentDate },
})
const body = res?.data || {}
const ok = body.code === 200 || body.code === 0
list = ok && body.data
? (body.data.list || body.data.paihang_list || body.data.rank_list || [])
: []
}
if (!Array.isArray(list)) list = []
const role = this.data.currentRole
const normalized = list.slice(0, 50).map((item, i) => this.normalizeItem(item, i, role))
const role = currentRole
let normalized = list.slice(0, 50).map((item, i) => this.normalizeItem(item, i, role))
if (rewardFromRank) {
normalized = this.applyRewardToList(normalized, rewardFromRank, 'club')
this.setData({ rewardInfo: rewardFromRank, showClaimBar: !!(rewardFromRank.my_claim && rewardFromRank.my_claim.status === 'pending') })
}
this.setData({
rankList: normalized,
topThree: normalized.slice(0, 3),
@@ -309,6 +440,9 @@ Page({
isLoading: false,
})
this.ensureAvatars()
if (!rewardFromRank) {
await this.fetchRewardInfo()
}
} catch (e) {
console.error('排行榜加载失败', e)
wx.showToast({ title: '加载失败', icon: 'none' })

View File

@@ -25,6 +25,22 @@
>{{item.label}}</view>
</view>
<text class="sort-tip">{{currentDate}} · 按{{sortLabel}} · TOP50</text>
<view class="scope-row">
<view class="scope-chip {{fanwei === 'jituan' ? 'on' : ''}}" data-fanwei="jituan" bindtap="onScopeTap">集团总榜</view>
<view class="scope-chip {{fanwei === 'club' ? 'on' : ''}}" data-fanwei="club" bindtap="onScopeTap">本俱乐部榜</view>
</view>
<view class="reward-banner" wx:if="{{rewardInfo && rewardInfo.scheme_enabled}}" bindtap="onShowRules">
<view class="reward-banner-main">
<text class="reward-tag">🏆 {{rewardInfo.title || '排行榜奖励'}}</text>
<text class="reward-club">俱乐部 {{rewardInfo.club_id || myClubId}}</text>
<text class="reward-status" wx:if="{{rewardInfo.period_status === 'open'}}">进行中 · 结束后可领</text>
<text class="reward-status pending" wx:elif="{{rewardInfo.my_claim}}">待领取 ¥{{rewardInfo.my_claim.amount}}</text>
<text class="reward-status claimed" wx:elif="{{rewardInfo.my_claimed}}">已领取 ¥{{rewardInfo.my_claimed.amount}}</text>
</view>
<text class="reward-link">规则 </text>
</view>
</view>
<view class="state-wrap" wx:if="{{isLoading}}">
@@ -54,6 +70,7 @@
<view class="p-amount">
<text class="p-money">{{topThree[1].mainPrefix}}{{topThree[1].mainValue}}</text>
<text class="p-label">{{topThree[1].mainLabel}}</text>
<text class="p-reward" wx:if="{{topThree[1].rewardAmount > 0}}">奖 ¥{{topThree[1].rewardAmount}}</text>
</view>
<view class="p-metrics">
<text class="p-metric" wx:for="{{topThree[1].metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
@@ -72,6 +89,7 @@
<view class="p-amount">
<text class="p-money p-money-first">{{topThree[0].mainPrefix}}{{topThree[0].mainValue}}</text>
<text class="p-label">{{topThree[0].mainLabel}}</text>
<text class="p-reward" wx:if="{{topThree[0].rewardAmount > 0}}">奖 ¥{{topThree[0].rewardAmount}}</text>
</view>
<view class="p-metrics">
<text class="p-metric" wx:for="{{topThree[0].metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
@@ -90,6 +108,7 @@
<view class="p-amount">
<text class="p-money">{{topThree[2].mainPrefix}}{{topThree[2].mainValue}}</text>
<text class="p-label">{{topThree[2].mainLabel}}</text>
<text class="p-reward" wx:if="{{topThree[2].rewardAmount > 0}}">奖 ¥{{topThree[2].rewardAmount}}</text>
</view>
<view class="p-metrics">
<text class="p-metric" wx:for="{{topThree[2].metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
@@ -114,7 +133,7 @@
<image class="lc-avatar" src="{{item.avatar || defaultAvatar}}" mode="aspectFill" data-zone="rest" data-index="{{index}}" binderror="onAvatarError" />
<view class="lc-info">
<text class="lc-name">{{item.nicheng}}</text>
<text class="lc-uid">ID {{item.uid}}</text>
<text class="lc-uid">ID {{item.uid}}<text wx:if="{{item.clubName && fanwei === 'jituan'}}"> · {{item.clubName}}</text></text>
<view class="lc-tags">
<text class="lc-tag" wx:for="{{item.metrics}}" wx:key="label" wx:for-item="m">{{m.label}} {{m.value}}</text>
</view>
@@ -122,6 +141,7 @@
<view class="lc-score">
<text class="lc-money">{{item.mainPrefix}}{{item.mainValue}}</text>
<text class="lc-label">{{item.mainLabel}}</text>
<text class="lc-reward" wx:if="{{item.rewardAmount > 0}}">奖¥{{item.rewardAmount}}</text>
</view>
</view>
</view>
@@ -129,6 +149,14 @@
<view class="bottom-gap"></view>
</scroll-view>
<view class="claim-bar" wx:if="{{showClaimBar && rewardInfo.my_claim}}">
<view class="claim-text">
<text class="claim-title">🎉 第{{rewardInfo.my_claim.mingci}}名 · 待领取</text>
<text class="claim-amount">¥{{rewardInfo.my_claim.amount}}</text>
</view>
<button class="claim-btn" loading="{{claimLoading}}" bindtap="onClaimTap">立即领取</button>
</view>
</view>
<global-notification id="global-notification" />

View File

@@ -107,8 +107,102 @@ page {
}
.sort-tip {
display: block;
font-size: 22rpx;
color: rgba(255,255,255,0.3);
color: rgba(255,255,255,0.45);
margin-top: 8rpx;
}
.scope-row {
display: flex;
gap: 12rpx;
margin-top: 16rpx;
}
.scope-chip {
padding: 10rpx 24rpx;
border-radius: 999rpx;
font-size: 24rpx;
color: rgba(255,255,255,0.65);
background: rgba(20, 26, 40, 0.75);
border: 1rpx solid rgba(255,255,255,0.12);
}
.scope-chip.on {
color: #fff;
background: rgba(255, 193, 7, 0.35);
border-color: rgba(255, 193, 7, 0.5);
}
.reward-banner {
margin-top: 16rpx;
padding: 20rpx 24rpx;
border-radius: 16rpx;
background: linear-gradient(135deg, rgba(255,193,7,0.2), rgba(107,163,247,0.15));
border: 1rpx solid rgba(255,193,7,0.35);
display: flex;
align-items: center;
justify-content: space-between;
}
.reward-banner-main {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.reward-tag { font-size: 28rpx; font-weight: 600; color: #ffd666; }
.reward-club { font-size: 22rpx; color: rgba(255,255,255,0.7); }
.reward-status { font-size: 22rpx; color: rgba(255,255,255,0.55); }
.reward-status.pending { color: #52c41a; font-weight: 600; }
.reward-status.claimed { color: #8c8c8c; }
.reward-link { font-size: 24rpx; color: #8EB8FF; }
.p-reward {
display: block;
margin-top: 6rpx;
font-size: 22rpx;
color: #ffd666;
font-weight: 600;
}
.lc-reward {
display: block;
margin-top: 4rpx;
font-size: 22rpx;
color: #ffd666;
}
.claim-bar {
position: fixed;
left: 0; right: 0; bottom: 0;
z-index: 100;
padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom));
background: linear-gradient(180deg, rgba(13,17,23,0.2), rgba(13,17,23,0.95));
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1rpx solid rgba(255,193,7,0.3);
}
.claim-text { display: flex; flex-direction: column; }
.claim-title { font-size: 26rpx; color: #fff; }
.claim-amount { font-size: 36rpx; font-weight: 700; color: #ffd666; }
.claim-btn {
margin: 0;
padding: 0 40rpx;
height: 72rpx;
line-height: 72rpx;
font-size: 28rpx;
background: linear-gradient(135deg, #ffc107, #ff9800);
color: #1a1a1a;
border-radius: 999rpx;
border: none;
}
.bottom-gap {
height: 160rpx;
}
/* ========== 状态 ========== */
@@ -463,5 +557,3 @@ page {
font-size: 20rpx;
color: rgba(255,255,255,0.32);
}
.bottom-gap { height: 48rpx; }