/** * 鎺掕姒? * 榛樿 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() }, })