const themeMod = require('../../utils/theme') const iconsMod = require('../../utils/icons') const util = require('../../utils/util') const storage = require('../../utils/storage') const config = require('../../config') Page({ data: { theme: { primary: '#FF6B35', primaryLight: '#FF8C5A', primaryBg: '#FFF3ED', primaryRgb: '255,107,53' }, themeStyle: themeMod.BASE_VARS, periods: [ { key: 'day', label: '日榜' }, { key: 'month', label: '月榜' }, { key: 'year', label: '年榜' } ], activePeriod: 'day', rankedList: [], myEntry: null, loading: false, empty: false, icons: iconsMod.build() }, onLoad() { themeMod.applyThemeToPage(this) this._firstShow = true }, onShow() { themeMod.applyThemeToPage(this) const theme = themeMod.getCurrentTheme() this.setData({ theme, icons: iconsMod.build(theme) }) try { const tb = this.getTabBar() if (tb) tb.setData({ selected: 2 }) } catch (e) {} if (this._firstShow) { this._firstShow = false this.fetchRank() return } this.fetchRank() }, onPullDownRefresh() { this.fetchRank(() => wx.stopPullDownRefresh()) }, switchPeriod(e) { const period = e.currentTarget.dataset.period if (period === this.data.activePeriod) return this.setData({ activePeriod: period, rankedList: [], myEntry: null }) this.fetchRank() }, fetchRank(cb) { this.setData({ loading: true, empty: false }) wx.cloud.callFunction({ name: 'leaderboard', data: { period: this.data.activePeriod, maxRank: config.leaderboardMaxRank } }).then(res => { this._applyResult(res.result || {}) if (cb) cb() }).catch((err) => { // Cloud function not deployed or network error — fall back to // local data but let the user know the leaderboard is degraded. console.warn('[leaderboard] cloud function failed, using local fallback:', err) wx.showToast({ title: '云端榜单暂不可用', icon: 'none', duration: 2000 }) this._applyLocal() if (cb) cb() }) }, _applyResult(result) { const list = (result.ranked || []).map(item => ({ ...item, durationText: util.formatDuration(item.duration) })) const myEntry = result.myEntry ? { ...result.myEntry, durationText: util.formatDuration(result.myEntry.duration), isMe: true } : null this.setData({ rankedList: list, myEntry, loading: false, empty: list.length === 0 && !myEntry }) }, /** Local fallback: use local storage when cloud function isn't deployed */ _applyLocal() { const period = this.data.activePeriod const allRecords = Object.values(storage.getRecords()).flat() const today = storage.getToday() const thisMonth = today.substring(0, 7) const thisYear = String(new Date().getFullYear()) const profile = storage.getProfile() const localName = profile.nickname || '我' let duration = 0 let sessions = 0 allRecords.forEach(r => { if (period === 'day' && util.dateOnly(r.date) === util.dateOnly(today)) { duration += r.duration; sessions++ } if (period === 'month' && r.date.startsWith(thisMonth)) { duration += r.duration; sessions++ } if (period === 'year' && r.date.startsWith(thisYear)) { duration += r.duration; sessions++ } }) if (duration > 0) { const entry = { rank: 1, openid: 'local', name: localName, nickname: profile.nickname || '', duration, durationText: util.formatDuration(duration), sessions } this.setData({ rankedList: [entry], myEntry: { ...entry, isMe: true }, loading: false, empty: false }) } else { this.setData({ loading: false, empty: true }) } } })