Files
xingque/pages/fighter-rank/fighter-rank.js
XingQue 32f89529c6 fix: 星之界排行榜展示字段与排序规则对齐
管事主指标改为有效人数,组长主指标改为收益总额,副指标展示无效/有效邀请与收益。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 21:51:47 +08:00

484 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 鎺掕姒? * 榛樿 POST /yonghu/phbhqsj
* 鏄熶箣鐣?xzj) POST /yonghu/xzjphbhqsj锛堥個璇蜂汉鏁版帓搴忕瓑涓撶敤瑙勫垯锛? */
import request from '../../utils/request.js'
import { resolveAvatarUrl, getDefaultAvatarUrl } from '../../utils/avatar.js'
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: '鎺ュ崟鍛? },
{ key: 'guanshi', label: '' },
{ key: 'zuzhang', label: '缁勯暱' },
{ key: 'shangjia', label: '鍟嗗' },
]
const DATE_LIST = [
{ key: '浠婃棩', label: '浠婃棩' },
{ key: '鏄ㄦ棩', label: '鏄ㄦ棩' },
{ key: '', label: '' },
{ key: '涓婂懆', label: '涓婂懆' },
{ key: '', label: '' },
{ key: '涓婃湀', label: '涓婃湀' },
{ key: '鎬绘', label: '鎬绘' },
]
const ROLE_META = {
dashou: {
title: '鎺ュ崟鍛樻帓琛屾',
sortField: 'chengjiao_zonge',
sortLabel: '鍒嗙孩鎬婚',
mainType: 'money',
metrics: [
{ field: 'jiedan_zongliang', label: '鎺ュ崟閲?, type: 'int' },
{ field: 'jiedan_zonge', label: '鎺ュ崟棰?, type: 'money' },
{ field: 'chengjiao_zongliang', label: '鎴愪氦閲?, type: 'int' },
],
},
guanshi: {
title: '绠′簨鎺掕姒?,
sortField: 'shouru_zonge',
sortLabel: '鏀跺叆鎬婚',
mainType: 'money',
metrics: [
{ field: 'yaoqing_dashou_shu', label: '?, type: 'int' },
{ field: 'chongzhi_dashou_shu', label: '鍏呭€?, type: 'int' },
],
},
zuzhang: {
title: '缁勯暱鎺掕?,
sortField: 'shouru_zonge',
sortLabel: '鏀跺叆鎬婚',
mainType: 'money',
metrics: [
{ field: 'yaoqing_guanshi_shu', label: '閭€璇?, type: 'int' },
{ field: 'fenyong_jine', label: '鍒嗕剑', type: 'money' },
],
},
shangjia: {
title: '鍟嗗鎺掕?,
sortField: 'jiesuan_jine',
sortLabel: '鎴愪氦鎬婚',
mainType: 'money',
metrics: [
{ field: 'jiesuan_dingdan_shu', label: '鎴愪氦閲?, type: 'int' },
{ field: 'paifa_dingdan_shu', label: '娲惧崟閲?, type: 'int' },
{ field: 'paifa_jine', label: '娲惧崟棰?, type: 'money' },
],
},
}
/** 鏄熶箣鐣屼笓鐢ㄥ睍绀轰笌鎺掑簭瀛楁 */
const XZJ_ROLE_META = {
dashou: {
title: '鎺ュ崟鍛樻帓琛屾',
sortField: 'chengjiao_zonge',
sortLabel: '鎴愪氦閲戦',
mainType: 'money',
metrics: [
{ field: 'jiedan_zongliang', label: '鎺ュ崟閲?, type: 'int' },
{ field: 'jiedan_zonge', label: '鎺ュ崟棰?, type: 'money' },
{ field: 'chengjiao_zongliang', label: '鎴愪氦閲?, type: 'int' },
],
},
guanshi: {
title: '绠′簨鎺掕姒?,
sortField: 'chongzhi_dashou_shu',
sortLabel: '鏈夋晥浜烘暟',
mainType: 'int',
metrics: [
{ field: 'wuxiao_yaoqing_dashou_shu', label: '鏃犳晥閭?, type: 'int' },
{ field: 'yaoqing_dashou_shu', label: '閭€璇蜂汉鏁?, type: 'int' },
{ field: 'shouru_zonge', label: '鏀剁泭鎬婚', type: 'money' },
],
},
zuzhang: {
title: '缁勯暱鎺掕?,
sortField: 'shouru_zonge',
sortLabel: '鏀剁泭鎬婚',
mainType: 'money',
metrics: [
{ field: 'wuxiao_yaoqing_guanshi_shu', label: '鏃犳晥閭€璇?, type: 'int' },
{ field: 'youxiao_guanshi_shu', label: '鏈夋晥浜烘暟', type: 'int' },
{ field: 'yaoqing_guanshi_shu', label: '璇蜂汉鏁?, type: 'int' },
],
},
shangjia: {
title: '鍟嗗鎺掕姒?,
sortField: 'paifa_jine',
sortLabel: '娲惧崟娴佹按',
mainType: 'money',
metrics: [
{ field: 'paifa_dingdan_shu', label: '娲惧崟閲?, type: 'int' },
{ field: 'paifa_jine', label: '娲惧崟娴佹按', type: 'money' },
],
},
}
const ACTIVE_ROLE_META = IS_XZJ ? XZJ_ROLE_META : ROLE_META
function isInvalidAvatarPath(path) {
if (path === null || path === undefined) return true
if (typeof path !== 'string') return true
const s = path.trim()
return !s || s === 'null' || s === 'undefined' || s === 'none'
}
function getDefaultAvatar(app) {
return getDefaultAvatarUrl(app)
}
function buildAvatar(path, app) {
app = app || getApp()
if (isInvalidAvatarPath(path)) return getDefaultAvatar(app)
return resolveAvatarUrl(path, app) || getDefaultAvatar(app)
}
Page({
data: {
imgUrls: {},
roleList: ROLE_LIST,
dateList: DATE_LIST,
currentRole: 'dashou',
currentDate: '浠婃棩',
fanwei: 'club',
showJituanScope: false, // 涓存椂闅愯棌闆嗗洟鎬绘锛屾仮澶嶆椂鏀?true 骞惰皟鏁?fanwei 榛樿鍊? myClubId: CLUB_ID || 'xq',
roleMeta: ROLE_META.dashou,
sortLabel: '鍒嗙孩鎬婚',
isLoading: true,
rankList: [],
topThree: [],
restList: [],
isEmpty: false,
defaultAvatar: '',
rewardInfo: null,
showClaimBar: false,
claimLoading: false,
},
onLoad(options) {
const type = options.type || options.rankType || 'dashou'
const validRole = ACTIVE_ROLE_META[type] ? type : 'dashou'
this.initPage()
this.applyRole(validRole, false)
this.fetchRankList()
},
onPullDownRefresh() {
this.fetchRankList().finally(() => wx.stopPullDownRefresh())
},
initPage() {
this.syncDefaultAvatar()
const g = getApp().globalData
const base = (g.ossImageUrl || '') + 'beijing/paihangbang/'
this.setData({
imgUrls: {
emptyIcon: base + 'empty.png',
refreshIcon: base + 'icon-refresh.png',
},
})
},
/** 涓?app.js 涓€鑷达細ossImageUrl + morentouxiang锛岄厤缃紓姝ュ姞杞藉悗 onShow 鍐嶅悓姝?*/
syncDefaultAvatar() {
const def = getDefaultAvatar()
if (def && def !== this.data.defaultAvatar) {
this.setData({ defaultAvatar: def })
} else if (!this.data.defaultAvatar && def) {
this.setData({ defaultAvatar: def })
}
},
/** 閰嶇疆鏅氫簬椤甸潰鍔犺浇鏃讹紝琛ュ叏绌哄ご鍍?*/
ensureAvatars() {
const def = getDefaultAvatar()
if (!def) return
const g = getApp().globalData
const oss = g.ossImageUrl || ''
const { rankList, topThree, restList } = this.data
if (!rankList.length) {
if (def !== this.data.defaultAvatar) this.setData({ defaultAvatar: def })
return
}
const isValidUrl = (url) => {
if (!url || typeof url !== 'string') return false
if (url.startsWith('http://') || url.startsWith('https://')) return true
return !!(oss && url.indexOf(oss) === 0)
}
const fix = (item) => ({
...item,
avatar: isValidUrl(item.avatar) ? item.avatar : def,
})
this.setData({
defaultAvatar: def,
rankList: rankList.map(fix),
topThree: topThree.map(fix),
restList: restList.map(fix),
})
},
applyRole(role, reload = true) {
const meta = ACTIVE_ROLE_META[role]
wx.setNavigationBarTitle({ title: '鎬绘帓琛屾' })
this.setData({ currentRole: role, roleMeta: meta, sortLabel: meta.sortLabel })
if (reload) this.fetchRankList()
},
onRoleTap(e) {
const role = e.currentTarget.dataset.role
if (!role || role === this.data.currentRole) return
this.applyRole(role)
},
onDateTap(e) {
const date = e.currentTarget.dataset.date
if (!date || date === this.data.currentDate) return
this.setData({ currentDate: date })
this.fetchRankList()
},
onScopeTap(e) {
const fanwei = e.currentTarget.dataset.fanwei
if (!fanwei || fanwei === this.data.fanwei) return
if (fanwei === 'jituan' && !this.data.showJituanScope) 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()
},
formatMoney(val) {
const n = parseFloat(val)
return Number.isNaN(n) ? '0.00' : n.toFixed(2)
},
formatInt(val) {
const n = parseInt(val, 10)
return Number.isNaN(n) ? '0' : String(n)
},
normalizeItem(raw, index, role) {
const meta = ACTIVE_ROLE_META[role]
const app = getApp()
const uid = raw.yonghuid || raw.uid || ''
const nicheng = raw.nicheng || raw.nick || (role === 'dashou' ? '鎵撴墜' : role === 'guanshi' ? '' : role === 'zuzhang' ? '缁勯暱' : role === 'shangjia' ? '鍟嗗' : '鐢ㄦ埛')
const def = getDefaultAvatar(app)
const avatar = buildAvatar(raw.touxiang || raw.avatar, app) || def
const metrics = meta.metrics.map((m) => {
const v = raw[m.field]
return {
label: m.label,
value: m.type === 'money' ? this.formatMoney(v) : this.formatInt(v),
}
})
const hasBackendMetric = raw.metric_value !== undefined && raw.metric_value !== null
const sortRaw = IS_XZJ
? (raw[meta.sortField] ?? raw.metric_value)
: (hasBackendMetric ? raw.metric_value : (raw[meta.sortField] ?? raw.metric_value))
let mainType = meta.mainType || 'money'
if (hasBackendMetric && raw.metric_is_int !== undefined) {
mainType = raw.metric_is_int ? 'int' : 'money'
} else if (raw.metric_label) {
mainType = (Number.isInteger(Number(sortRaw)) && !String(sortRaw).includes('.')) ? 'int' : 'money'
}
const mainValue = mainType === 'money'
? this.formatMoney(sortRaw)
: this.formatInt(sortRaw)
return {
mingci: raw.mingci || index + 1,
uid,
nicheng,
avatar,
clubName: raw.club_name || raw.club_id || '',
rewardAmount: raw.reward_amount || 0,
mainType,
mainPrefix: mainType === 'money' ? '' : '',
mainValue,
mainLabel: (IS_XZJ ? meta.sortLabel : (raw.metric_label || meta.sortLabel)),
metrics,
}
},
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: '/yonghu/phbjlxx',
method: 'POST',
data: {
shenfen: this.data.currentRole,
riqi: this.data.currentDate,
club_id: this.data.myClubId,
},
})
const body = res?.data || {}
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 = currentRole
// 鍚嶆椤哄簭瀹屽叏娌跨敤鍚庣杩斿洖锛屽墠绔笉鍋?sort / 閲嶆帓
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),
restList: normalized.slice(3),
isEmpty: normalized.length === 0,
isLoading: false,
})
this.ensureAvatars()
if (!rewardFromRank) {
await this.fetchRewardInfo()
}
} catch (e) {
console.error('鎺掕姒滃姞杞藉け璐?, e)
wx.showToast({ title: '鍔犺浇澶辫触', icon: 'none' })
this.setData({ rankList: [], topThree: [], restList: [], isEmpty: true, isLoading: false })
}
},
onAvatarError(e) {
const { zone, index } = e.currentTarget.dataset
const fallback = getDefaultAvatar()
if (!fallback) return
const key = zone === 'top' ? `topThree[${index}].avatar` : `restList[${index}].avatar`
this.setData({ [key]: fallback })
},
registerNotificationComponent() {
const app = getApp()
const comp = this.selectComponent('#global-notification')
if (comp?.showNotification) {
app.globalData.globalNotification = {
show: (d) => comp.showNotification(d),
hide: () => comp.hideNotification(),
}
}
},
onShow() {
this.syncDefaultAvatar()
this.ensureAvatars()
this.registerNotificationComponent()
},
})