Files
wx_pbzc/pages/leaderboard/leaderboard.wxml
T
lc 1589d6c387 feat(leaderboard): 新增耐力榜(全局单次最久 top10) + 领奖台日期两行 + 顶部哲理文案
- 云函数支持 endurance 周期:遍历全部 records 取 max(duration) 及 bestDate
- 客户端新增耐力榜 tab(maxRank=10),未上榜时在底部 my-bar 显示最佳成绩+产生时间
- 领奖台底座(耐力榜)单次日期拆成「日期+时间」两行,统一三阶底座高度修复第3名贴顶
- 领奖台顶部新增哲理文案,配置进 config.js(leaderboardPodiumSlogan),斜体金句卡片样式
2026-07-31 09:30:30 +08:00

115 lines
6.8 KiB
Plaintext

<view class="container page-enter" style="{{themeStyle}}">
<!-- Period tabs: pill-style segmented control, consistent with settings -->
<view class="period-seg-wrap">
<view class="period-seg">
<view
wx:for="{{periods}}"
wx:key="key"
class="period-seg-item {{activePeriod === item.key ? 'active' : ''}}"
data-period="{{item.key}}"
bindtap="switchPeriod"
>{{item.label}}</view>
</view>
</view>
<!-- 刷新中指示器(下拉刷新) -->
<view class="refresh-indicator" wx:if="{{pullRefreshing}}">
<view class="refresh-spinner"></view>
<text class="refresh-text">刷新中…</text>
</view>
<!-- Loading: 旋转加载图标 + 骨架屏 -->
<view class="leaderboard-skeleton" wx:if="{{loading}}">
<view class="lb-loading">
<view class="lb-loading__spinner"></view>
<text class="lb-loading__text">加载中…</text>
</view>
<ui-skeleton variant="list-row" count="{{5}}" />
</view>
<!-- Empty: 奖杯光环插画 -->
<view class="empty-state" wx:elif="{{empty}}">
<view class="empty-illus">
<view class="empty-halo"></view>
<image class="empty-trophy" src="{{icons.trophyFill}}" mode="aspectFit"></image>
<image class="empty-spark empty-spark--1" src="{{icons.sparkleFill}}" mode="aspectFit"></image>
<image class="empty-spark empty-spark--2" src="{{icons.sparkleFill}}" mode="aspectFit"></image>
<image class="empty-spark empty-spark--3" src="{{icons.sparkleFill}}" mode="aspectFit"></image>
</view>
<text class="empty-text">暂无排行数据</text>
<text class="empty-sub">快去训练,争当第一名!</text>
</view>
<block wx:else>
<!-- 耐力榜领奖台顶部文案:哲理注脚,点出"撑得越久不一定越好" -->
<view class="podium-slogan" wx:if="{{activePeriod === 'endurance'}}">{{podiumSlogan}}</view>
<!-- Podium: top 3 (only when there are at least 3 entries) -->
<view class="podium" wx:if="{{rankedList.length >= 3}}">
<!-- 2nd -->
<view class="podium-slot podium-slot--second rank-item--enter" style="animation-delay: 60ms;">
<view class="podium-avatar-wrap">
<view class="avatar avatar--md {{rankedList[1].avatarUrl ? 'has-avatar' : ''}}"><image class="avatar__img" src="{{rankedList[1].avatarUrl || icons.peopleFill}}" mode="{{rankedList[1].avatarUrl ? 'aspectFill' : 'aspectFit'}}"></image></view>
<view class="podium-medal podium-medal--silver">2</view>
</view>
<text class="podium-name">{{rankedList[1].name}}</text>
<text class="podium-dur">{{rankedList[1].durationText}}</text>
<view class="podium-base podium-base--second"><view class="podium-base__label {{activePeriod === 'endurance' ? 'is-two' : ''}}"><block wx:if="{{activePeriod === 'endurance'}}"><text class="podium-base__date">{{rankedList[1].subDate}}</text><text class="podium-base__time">{{rankedList[1].subTime}}</text></block><text wx:else>{{rankedList[1].subText}}</text></view></view>
</view>
<!-- 1st -->
<view class="podium-slot podium-slot--first rank-item--enter" style="animation-delay: 0ms;">
<image class="podium-crown" src="{{icons.crownFill}}" mode="aspectFit"></image>
<view class="podium-avatar-wrap">
<view class="avatar avatar--md avatar--lg {{rankedList[0].avatarUrl ? 'has-avatar' : ''}}"><image class="avatar__img" src="{{rankedList[0].avatarUrl || icons.peopleFill}}" mode="{{rankedList[0].avatarUrl ? 'aspectFill' : 'aspectFit'}}"></image></view>
<view class="podium-medal podium-medal--gold">1</view>
</view>
<text class="podium-name podium-name--first">{{rankedList[0].name}}</text>
<text class="podium-dur podium-dur--first">{{rankedList[0].durationText}}</text>
<view class="podium-base podium-base--first"><view class="podium-base__label {{activePeriod === 'endurance' ? 'is-two' : ''}}"><block wx:if="{{activePeriod === 'endurance'}}"><text class="podium-base__date">{{rankedList[0].subDate}}</text><text class="podium-base__time">{{rankedList[0].subTime}}</text></block><text wx:else>{{rankedList[0].subText}}</text></view></view>
</view>
<!-- 3rd -->
<view class="podium-slot podium-slot--third rank-item--enter" style="animation-delay: 120ms;">
<view class="podium-avatar-wrap">
<view class="avatar avatar--md {{rankedList[2].avatarUrl ? 'has-avatar' : ''}}"><image class="avatar__img" src="{{rankedList[2].avatarUrl || icons.peopleFill}}" mode="{{rankedList[2].avatarUrl ? 'aspectFill' : 'aspectFit'}}"></image></view>
<view class="podium-medal podium-medal--bronze">3</view>
</view>
<text class="podium-name">{{rankedList[2].name}}</text>
<text class="podium-dur">{{rankedList[2].durationText}}</text>
<view class="podium-base podium-base--third"><view class="podium-base__label {{activePeriod === 'endurance' ? 'is-two' : ''}}"><block wx:if="{{activePeriod === 'endurance'}}"><text class="podium-base__date">{{rankedList[2].subDate}}</text><text class="podium-base__time">{{rankedList[2].subTime}}</text></block><text wx:else>{{rankedList[2].subText}}</text></view></view>
</view>
</view>
<!-- Rank list: top-3 shown on the podium are skipped when podium exists -->
<view class="rank-list">
<view
wx:for="{{rankedList}}"
wx:key="rank"
wx:if="{{rankedList.length < 3 || index >= 3}}"
class="rank-item {{item.isMe ? 'is-me' : ''}} rank-item--enter"
style="animation-delay: {{index < 10 ? index * 60 : 0}}ms;"
>
<view class="rank-num {{item.rank === 1 ? 'gold' : item.rank === 2 ? 'silver' : item.rank === 3 ? 'bronze' : ''}}">
<text>{{item.rank}}</text>
</view>
<view class="avatar avatar--sm {{item.avatarUrl ? 'has-avatar' : ''}}"><image class="avatar__img" src="{{item.avatarUrl || icons.peopleFill}}" mode="{{item.avatarUrl ? 'aspectFill' : 'aspectFit'}}"></image></view>
<view class="rank-info">
<text class="rank-name">{{item.name}}</text>
<text class="rank-sessions">{{item.subText}}</text>
</view>
<text class="rank-dur">{{item.durationText}}</text>
</view>
</view>
</block>
<!-- My entry pinned at bottom (if not already in list) -->
<view class="my-bar" wx:if="{{myEntry && myEntry.rank > rankedList.length}}">
<view class="rank-num my-bar__num">{{myEntry.rank}}</view>
<view class="avatar avatar--sm my-bar__avatar {{myEntry.avatarUrl ? 'has-avatar' : ''}}"><image class="avatar__img" src="{{myEntry.avatarUrl || icons.peopleFill}}" mode="{{myEntry.avatarUrl ? 'aspectFill' : 'aspectFit'}}"></image></view>
<view class="rank-info">
<text class="rank-name">我 ({{myEntry.name}})</text>
<text class="rank-sessions">{{myEntry.subText}}</text>
</view>
<text class="rank-dur">{{myEntry.durationText}}</text>
</view>
</view>