You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

432 lines
22 KiB

<template>
<view class="rank-page" :style="{'--statusbar': statusBarHeight + 'px'}">
<view class="nav" :style="{paddingTop: statusBarHeight + 'px'}">
<view class="nav-back" @tap="goBack"><text></text></view>
<view class="nav-title">星球排行榜</view>
</view>
<scroll-view scroll-y enhanced :show-scrollbar="false" class="page" :style="{paddingTop: (statusBarHeight + 58) + 'px'}" @scrolltolower="loadMore">
<view v-if="loading" class="loading-card">正在扫描财富坐标...</view>
<block v-else>
<view class="radar-card">
<view class="radar-visual">
<view class="radar-sweep"></view>
<view class="radar-ring ring-a"></view>
<view class="radar-ring ring-b"></view>
<view class="radar-dot"></view>
</view>
<view class="radar-copy">
<view class="radar-kicker">SCHOOL RICH LIST</view>
<view class="radar-title">全校首富雷达</view>
<view class="radar-sub">从榜单里选定目标,发起星际追查,抢回今日补给权。</view>
</view>
<view class="radar-count">
<text>{{home.remainHunt || 0}}</text>
<text>次追查</text>
</view>
</view>
<view class="rank-head">
<view>
<view class="rank-kicker">WEALTH COORDINATES</view>
<view class="rank-title">财富坐标榜</view>
</view>
</view>
<view v-if="podiumList.length" class="podium-card">
<view class="podium-bg-glow"></view>
<view class="podium-head">
<view>
<view class="podium-kicker">TOP HUNTERS ARE WATCHING</view>
<view class="podium-title">前三领奖台</view>
</view>
<view class="podium-prize">抢榜越高 · 越容易被盯上</view>
</view>
<view class="reward-rule">
<view class="reward-main">每日 21:30 按校区分别结算,有券前 5 名瓜分星球券</view>
<view class="reward-chips">
<text>第1名 +5</text>
<text>第2名 +4</text>
<text>第3名 +3</text>
<text>第4名 +2</text>
<text>第5名 +1</text>
</view>
</view>
<view class="podium-stage">
<view
class="podium-player second"
v-if="podiumSecond"
@tap="onHunt(podiumSecond)">
<view class="crown silver">2</view>
<image class="podium-avatar" :class="{stealth: podiumSecond.stealth}" :src="podiumSecond.stealth ? defaultAvatar : (podiumSecond.avatar || defaultAvatar)" mode="aspectFill"></image>
<view class="podium-name">{{podiumName(podiumSecond)}}</view>
<view class="podium-ticket">{{podiumSecond.ticketCount || 0}}券</view>
<view class="podium-base">第二名</view>
</view>
<view
class="podium-player first"
v-if="podiumFirst"
@tap="onHunt(podiumFirst)">
<view class="crown gold">1</view>
<view class="king-aura"></view>
<image class="podium-avatar champion" :class="{stealth: podiumFirst.stealth}" :src="podiumFirst.stealth ? defaultAvatar : (podiumFirst.avatar || defaultAvatar)" mode="aspectFill"></image>
<view class="podium-name champion-name">{{podiumName(podiumFirst)}}</view>
<view class="podium-ticket hot">{{podiumFirst.ticketCount || 0}}券</view>
<view class="podium-base king">第一名</view>
</view>
<view
class="podium-player third"
v-if="podiumThird"
@tap="onHunt(podiumThird)">
<view class="crown bronze">3</view>
<image class="podium-avatar" :class="{stealth: podiumThird.stealth}" :src="podiumThird.stealth ? defaultAvatar : (podiumThird.avatar || defaultAvatar)" mode="aspectFill"></image>
<view class="podium-name">{{podiumName(podiumThird)}}</view>
<view class="podium-ticket">{{podiumThird.ticketCount || 0}}券</view>
<view class="podium-base">第三名</view>
</view>
</view>
<view class="podium-tip">点领奖台头像即可发起追查,今天还剩 {{home.remainHunt || 0}} 次机会</view>
</view>
<view v-if="normalList.length" class="rank-list">
<view
class="rank-row"
v-for="(item, index) in normalList"
:key="item._rankKey">
<view class="rank-no">#{{item._displayRankNo}}</view>
<image class="avatar" :class="{stealth: item.stealth}" :src="item.stealth ? defaultAvatar : (item.avatar || defaultAvatar)" mode="aspectFill"></image>
<view class="user-main">
<view class="user-name">
<text>{{item.stealth ? '隐身侠' : (item.nickname || '神秘同学')}}</text>
<text v-if="item.stealth" class="stealth-tag">隐身</text>
<text v-if="item.self" class="me-tag">ME</text>
</view>
<view class="user-meta">
<text>{{item.ticketCount || 0}} 张星球券</text>
<text v-if="!item.stealth && item.rankKeepDays > 0">霸榜 {{item.rankKeepDays}} 天</text>
</view>
</view>
<view
class="hunt-btn"
:class="{disabled: item.self || item.stealth || item.shielded || (home.remainHunt || 0) <= 0}"
@tap="onHunt(item)">
{{huntText(item)}}
</view>
</view>
</view>
<view v-else-if="!podiumList.length" class="empty-card">暂无上榜居民,攒券登顶成为今日主角</view>
<view v-if="rankList.length" class="load-more">{{loadMoreText}}</view>
<view class="bottom-space"></view>
</block>
</scroll-view>
<hunt-modal
:show="huntModal.show"
:phase="huntModal.phase"
:result="huntModal.result"
:target="huntModal.target"
@close="closeHunt">
</hunt-modal>
</view>
</template>
<script>
import huntModal from '@/package1/components/planet/hunt-modal.vue'
export default {
components: {
huntModal
},
data() {
return {
statusBarHeight: 20,
loading: true,
userId: '',
regionId: '',
nickname: '',
avatar: '',
college: '',
pageNum: 1,
pageSize: 10,
rankRows: [],
listLoading: false,
noMore: false,
defaultAvatar: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/41cfb56caff4419b94b69d0f2303b602.png',
home: {
remainHunt: 0,
rankList: []
},
huntModal: {
show: false,
phase: 'searching',
result: null,
target: {}
},
huntTimers: []
}
},
computed: {
rankList() {
return (this.rankRows || []).map((item, index) => {
return Object.assign({}, item, {
_rankKey: String(item.userId || item.rankNo || index),
_displayRankNo: item.rankNo || (index + 1)
})
})
},
totalPages() {
return Math.max(1, Number(this.home.rankTotalPages || 1))
},
pagedList() {
return this.rankList
},
podiumList() {
return this.rankList.filter((item) => item._displayRankNo >= 1 && item._displayRankNo <= 3)
},
podiumMap() {
const map = {}
this.podiumList.forEach((item) => {
map[item._displayRankNo] = item
})
return map
},
podiumFirst() {
return this.podiumMap[1]
},
podiumSecond() {
return this.podiumMap[2]
},
podiumThird() {
return this.podiumMap[3]
},
normalList() {
return this.rankList.filter((item) => item._displayRankNo > 3)
},
loadMoreText() {
if (this.listLoading) return '正在加载更多坐标...'
if (this.noMore) return '没有更多财富坐标了'
return '上滑加载更多坐标'
}
},
onLoad() {
const sys = uni.getSystemInfoSync()
this.statusBarHeight = sys.statusBarHeight || 20
this.userId = uni.getStorageSync('id') || ''
this.nickname = uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || ''
this.avatar = uni.getStorageSync('avatarUrl') || uni.getStorageSync('avatar') || ''
try {
const area = uni.getStorageSync('area')
if (area) this.regionId = JSON.parse(area).id || ''
} catch (e) {}
this.loadHome(true)
},
onUnload() {
this.clearHuntTimers()
},
methods: {
loadHome(reset) {
if (this.listLoading) return
if (!this.userId) {
this.tui.toast('请先登录')
return
}
if (!this.regionId) {
this.loading = false
this.tui.toast('未获取到校区信息')
return
}
if (reset) {
this.pageNum = 1
this.noMore = false
this.rankRows = []
}
if (reset) this.loading = true
this.listLoading = true
this.tui.request('/app/planet/home', 'POST', {
userId: this.userId,
regionId: this.regionId,
nickname: this.nickname,
avatar: this.avatar,
college: this.college,
pageNumber: this.pageNum,
pageSize: this.pageSize
}, false, false, true).then((res) => {
this.loading = false
this.listLoading = false
if (res.code == 200 && res.result) {
this.home = res.result
this.pageNum = Number(res.result.rankPageNumber || this.pageNum)
if (this.pageNum > this.totalPages) this.pageNum = this.totalPages
const records = res.result.rankList || []
this.rankRows = reset ? records : this.rankRows.concat(records)
this.noMore = this.pageNum >= this.totalPages || records.length < this.pageSize
} else if (res.message) {
this.tui.toast(res.message)
}
}).catch(() => {
this.loading = false
this.listLoading = false
})
},
loadMore() {
if (this.listLoading || this.noMore) return
if (this.pageNum >= this.totalPages) {
this.noMore = true
return
}
this.pageNum += 1
this.loadHome(false)
},
huntText(item) {
if (item.self) return '榜上有我'
if (item.stealth) return '已隐身'
if (item.shielded) return '防护中'
if ((this.home.remainHunt || 0) <= 0) return '次数用完'
return '发起追查'
},
podiumName(item) {
if (!item) return '神秘同学'
if (item.stealth) return '隐身侠'
return item.nickname || '神秘同学'
},
onHunt(item) {
if (item.self) {
this.tui.toast('不能追捕自己')
return
}
if (item.stealth) {
this.tui.toast('目标正在隐身,暂时无法追查')
return
}
if (item.shielded) {
this.tui.toast('目标已开启防护罩')
return
}
if ((this.home.remainHunt || 0) <= 0) {
this.tui.toast('今日追捕次数已用完')
return
}
this.clearHuntTimers()
this.huntModal.show = true
this.huntModal.phase = 'searching'
this.huntModal.result = null
this.huntModal.target = item
this.huntTimers.push(setTimeout(() => { this.huntModal.phase = 'locking' }, 900))
this.huntTimers.push(setTimeout(() => { this.huntModal.phase = 'chasing' }, 1800))
this.tui.request('/app/planet/hunt', 'POST', {
userId: this.userId,
regionId: this.regionId,
toUserId: item.userId
}, false, false, true).then((res) => {
this.huntTimers.push(setTimeout(() => {
if (res.code == 200 && res.result) {
this.huntModal.result = res.result
this.huntModal.phase = 'result'
this.loadHome(true)
} else {
this.huntModal.show = false
this.tui.toast(res.message)
}
}, 2600))
}).catch(() => {
this.huntModal.show = false
})
},
clearHuntTimers() {
this.huntTimers.forEach((timer) => clearTimeout(timer))
this.huntTimers = []
},
closeHunt() {
this.huntModal.show = false
this.huntModal.result = null
},
goBack() {
uni.navigateBack({
delta: 1,
fail() {
uni.navigateTo({ url: '/package1/planet/index' })
}
})
}
}
}
</script>
<style lang="scss" scoped>
.rank-page { min-height: 100vh; background: linear-gradient(155deg, #F3FFF4 0%, #EAF8FF 46%, #FFF7DD 100%); color: #12342F; }
.nav { position: fixed; top: 0; left: 0; right: 0; height: 44px; z-index: 20; display: flex; align-items: center; justify-content: center; box-sizing: content-box; background: linear-gradient(180deg, rgba(243,255,244,0.98), rgba(243,255,244,0)); }
.nav-back { position: absolute; left: 20rpx; bottom: 0; height: 44px; width: 60rpx; display: flex; align-items: center; justify-content: center; }
.nav-back text { color: #12342F; font-size: 48rpx; font-weight: 300; }
.nav-title { color: #12342F; font-size: 34rpx; font-weight: 900; align-self: flex-end; padding-bottom: 6rpx; }
.page { height: 100vh; box-sizing: border-box; padding-left: 22rpx; padding-right: 22rpx; }
.loading-card, .empty-card { margin-top: 24rpx; height: 220rpx; border-radius: 38rpx; background: rgba(255,255,255,0.76); display: flex; align-items: center; justify-content: center; color: #6B817D; font-size: 26rpx; }
.radar-card { position: relative; margin-top: 24rpx; min-height: 210rpx; padding: 30rpx 24rpx; border-radius: 46rpx; background: radial-gradient(circle at 18% 50%, rgba(255,122,89,0.16), transparent 34%), linear-gradient(135deg, rgba(255,255,255,0.9), rgba(255,246,219,0.7)); border: 2rpx solid rgba(255,255,255,0.94); box-shadow: 0 24rpx 58rpx rgba(255,122,89,0.12); display: flex; align-items: center; overflow: hidden; }
.radar-visual { position: relative; width: 138rpx; height: 138rpx; border-radius: 50%; border: 2rpx solid rgba(255,122,89,0.22); background: radial-gradient(circle, rgba(255,122,89,0.13), rgba(255,255,255,0.48)); flex-shrink: 0; overflow: hidden; }
.radar-ring { position: absolute; border-radius: 50%; border: 1rpx solid rgba(255,122,89,0.22); }
.ring-a { left: 24rpx; top: 24rpx; width: 88rpx; height: 88rpx; }
.ring-b { left: 48rpx; top: 48rpx; width: 40rpx; height: 40rpx; }
.radar-sweep { position: absolute; left: 50%; top: 50%; width: 64rpx; height: 5rpx; border-radius: 999rpx; transform-origin: left center; background: linear-gradient(90deg, rgba(255,122,89,0.76), rgba(255,122,89,0)); animation: sweep 1.8s linear infinite; }
.radar-dot { position: absolute; right: 30rpx; top: 38rpx; width: 16rpx; height: 16rpx; border-radius: 50%; background: #FF7A59; box-shadow: 0 0 18rpx rgba(255,122,89,0.55); }
@keyframes sweep { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.radar-copy { flex: 1; min-width: 0; margin-left: 22rpx; }
.radar-kicker, .rank-kicker { color: #FF7A59; font-size: 18rpx; font-weight: 900; letter-spacing: 3rpx; }
.radar-title { margin-top: 6rpx; color: #12342F; font-size: 38rpx; font-weight: 900; }
.radar-sub { margin-top: 8rpx; color: #7E9691; font-size: 23rpx; line-height: 1.42; font-weight: 600; }
.radar-count { width: 112rpx; height: 112rpx; border-radius: 34rpx; background: linear-gradient(135deg, #FF7A59, #FFB84D); color: #FFFFFF; display: flex; flex-direction: column; align-items: center; justify-content: center; box-shadow: 0 12rpx 26rpx rgba(255,122,89,0.18); transform: rotate(4deg); flex-shrink: 0; }
.radar-count text:first-child { font-size: 40rpx; font-weight: 900; line-height: 42rpx; }
.radar-count text:last-child { font-size: 19rpx; font-weight: 800; margin-top: 4rpx; }
.rank-head { margin-top: 30rpx; display: flex; align-items: flex-end; justify-content: space-between; }
.rank-title { margin-top: 6rpx; color: #12342F; font-size: 40rpx; font-weight: 900; }
.rank-page-no { color: #6B817D; font-size: 23rpx; font-weight: 800; }
.podium-card { position: relative; margin-top: 22rpx; padding: 30rpx 24rpx 26rpx; border-radius: 46rpx; background: radial-gradient(circle at 50% 8%, rgba(255,184,77,0.42), transparent 36%), linear-gradient(155deg, rgba(30,27,75,0.94), rgba(36,58,84,0.9) 48%, rgba(18,52,47,0.88)); border: 2rpx solid rgba(255,255,255,0.24); box-shadow: 0 30rpx 72rpx rgba(30,27,75,0.24); overflow: hidden; }
.podium-bg-glow { position: absolute; left: 50%; top: -170rpx; width: 620rpx; height: 620rpx; transform: translateX(-50%); border-radius: 50%; background: radial-gradient(circle, rgba(255,214,92,0.38), rgba(255,122,89,0.12) 42%, transparent 68%); animation: podiumPulse 2.4s ease-in-out infinite; }
@keyframes podiumPulse { 0%,100%{ opacity:.58; transform: translateX(-50%) scale(.92); } 50%{ opacity:1; transform: translateX(-50%) scale(1.06); } }
.podium-head { position: relative; z-index: 1; display: flex; justify-content: space-between; gap: 16rpx; align-items: flex-start; }
.podium-kicker { color: rgba(255,214,92,0.86); font-size: 17rpx; font-weight: 900; letter-spacing: 2rpx; }
.podium-title { margin-top: 6rpx; color: #FFFFFF; font-size: 40rpx; font-weight: 900; text-shadow: 0 8rpx 20rpx rgba(0,0,0,0.18); }
.podium-prize { max-width: 210rpx; padding: 10rpx 16rpx; border-radius: 999rpx; background: rgba(255,255,255,0.12); color: #FFE08A; font-size: 19rpx; font-weight: 900; line-height: 1.25; text-align: center; border: 1rpx solid rgba(255,255,255,0.18); }
.reward-rule { position: relative; z-index: 1; margin-top: 20rpx; padding: 18rpx; border-radius: 28rpx; background: linear-gradient(135deg, rgba(255,224,138,0.18), rgba(255,255,255,0.1)); border: 1rpx solid rgba(255,255,255,0.18); box-shadow: inset 0 1rpx 0 rgba(255,255,255,0.18); }
.reward-main { color: #FFF4B8; font-size: 23rpx; font-weight: 900; line-height: 1.35; }
.reward-chips { margin-top: 12rpx; display: flex; flex-wrap: wrap; gap: 10rpx; }
.reward-chips text { padding: 7rpx 12rpx; border-radius: 999rpx; background: rgba(255,255,255,0.16); color: rgba(255,255,255,0.92); font-size: 19rpx; font-weight: 900; }
.podium-stage { position: relative; z-index: 1; margin-top: 28rpx; height: 430rpx; display: flex; align-items: flex-end; justify-content: center; gap: 14rpx; }
.podium-player { position: relative; width: 200rpx; display: flex; flex-direction: column; align-items: center; transition: transform .18s ease; }
.podium-player:active { transform: scale(.96); }
.podium-player.first { width: 222rpx; margin-bottom: 28rpx; }
.podium-player.second { margin-bottom: 0; }
.podium-player.third { margin-bottom: -18rpx; }
.king-aura { position: absolute; top: 22rpx; width: 190rpx; height: 190rpx; border-radius: 50%; background: radial-gradient(circle, rgba(255,214,92,0.48), transparent 68%); animation: kingAura 1.8s ease-in-out infinite; }
@keyframes kingAura { 0%,100%{ transform: scale(.88); opacity:.66; } 50%{ transform: scale(1.08); opacity:1; } }
.crown { position: relative; z-index: 3; width: 62rpx; height: 50rpx; line-height: 50rpx; border-radius: 18rpx 18rpx 10rpx 10rpx; text-align: center; color: #4A2A00; font-size: 28rpx; font-weight: 900; box-shadow: 0 12rpx 24rpx rgba(0,0,0,0.2); transform: translateY(16rpx); }
.crown.gold { background: linear-gradient(145deg, #FFF7B8, #FFB84D); }
.crown.silver { background: linear-gradient(145deg, #FFFFFF, #BFD7FF); color: #28415F; }
.crown.bronze { background: linear-gradient(145deg, #FFE0B8, #C87A35); color: #4A2500; }
.podium-avatar { position: relative; z-index: 2; width: 112rpx; height: 112rpx; border-radius: 38rpx; border: 6rpx solid rgba(255,255,255,0.88); box-shadow: 0 18rpx 36rpx rgba(0,0,0,0.22); background: rgba(255,255,255,0.22); }
.podium-avatar.champion { width: 136rpx; height: 136rpx; border-radius: 46rpx; border-color: #FFE08A; box-shadow: 0 20rpx 46rpx rgba(255,184,77,0.36); animation: championFloat 2.1s ease-in-out infinite; }
@keyframes championFloat { 0%,100%{ transform: translateY(0); } 50%{ transform: translateY(-8rpx); } }
.podium-avatar.stealth { opacity: .62; filter: grayscale(100%); }
.podium-name { margin-top: 14rpx; max-width: 178rpx; color: #FFFFFF; font-size: 24rpx; font-weight: 900; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.champion-name { font-size: 28rpx; color: #FFF4B8; }
.podium-ticket { margin-top: 8rpx; padding: 6rpx 14rpx; border-radius: 999rpx; background: rgba(255,255,255,0.14); color: rgba(255,255,255,0.88); font-size: 20rpx; font-weight: 900; }
.podium-ticket.hot { background: linear-gradient(135deg, #FF7A59, #FFB84D); color: #FFFFFF; box-shadow: 0 10rpx 24rpx rgba(255,122,89,0.3); }
.podium-base { margin-top: 16rpx; width: 170rpx; height: 92rpx; line-height: 92rpx; border-radius: 28rpx 28rpx 12rpx 12rpx; text-align: center; color: rgba(255,255,255,0.92); font-size: 24rpx; font-weight: 900; background: linear-gradient(180deg, rgba(255,255,255,0.18), rgba(255,255,255,0.07)); border: 1rpx solid rgba(255,255,255,0.16); }
.podium-base.king { width: 198rpx; height: 126rpx; line-height: 126rpx; color: #4A2A00; font-size: 28rpx; background: linear-gradient(180deg, #FFE08A, #FFB84D); border: 0; box-shadow: inset 0 10rpx 22rpx rgba(255,255,255,0.26), 0 18rpx 36rpx rgba(255,184,77,0.22); }
.podium-tip { position: relative; z-index: 1; margin-top: 16rpx; height: 58rpx; line-height: 58rpx; border-radius: 999rpx; background: rgba(255,255,255,0.12); color: #FFE08A; font-size: 22rpx; font-weight: 900; text-align: center; border: 1rpx solid rgba(255,255,255,0.16); }
.rank-list { margin-top: 18rpx; }
.rank-row { margin-top: 16rpx; padding: 22rpx; border-radius: 34rpx; background: rgba(255,255,255,0.82); border: 2rpx solid rgba(255,255,255,0.94); box-shadow: 0 16rpx 36rpx rgba(53,214,166,0.1); display: flex; align-items: center; }
.rank-no { min-width: 70rpx; color: #22B889; font-size: 28rpx; font-weight: 900; }
.avatar { width: 86rpx; height: 86rpx; border-radius: 28rpx; border: 4rpx solid rgba(255,255,255,0.94); box-shadow: 0 12rpx 24rpx rgba(18,52,47,0.12); flex-shrink: 0; }
.avatar.stealth { opacity: .58; filter: grayscale(100%); }
.user-main { flex: 1; min-width: 0; margin-left: 18rpx; }
.user-name { display: flex; align-items: center; color: #12342F; font-size: 28rpx; font-weight: 900; }
.user-name text:first-child { max-width: 220rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.me-tag { margin-left: 10rpx; padding: 4rpx 10rpx; border-radius: 999rpx; background: linear-gradient(135deg, #35D6A6, #4FB7FF); color: #FFFFFF; font-size: 18rpx; }
.stealth-tag { margin-left: 10rpx; padding: 4rpx 10rpx; border-radius: 999rpx; background: #EEF2FF; color: #7C3AED; font-size: 18rpx; }
.user-meta { margin-top: 8rpx; display: flex; flex-wrap: wrap; gap: 10rpx; color: #6B817D; font-size: 22rpx; font-weight: 700; }
.hunt-btn { width: 132rpx; height: 62rpx; line-height: 62rpx; border-radius: 999rpx; text-align: center; background: linear-gradient(135deg, #FF7A59, #FFB84D); color: #FFFFFF; font-size: 22rpx; font-weight: 900; box-shadow: 0 14rpx 28rpx rgba(255,122,89,0.2); flex-shrink: 0; }
.hunt-btn.disabled { background: rgba(18,52,47,0.08); color: #9AA9A5; box-shadow: none; }
.load-more { margin-top: 28rpx; height: 66rpx; line-height: 66rpx; text-align: center; border-radius: 999rpx; background: rgba(255,255,255,0.72); color: #22B889; font-size: 24rpx; font-weight: 900; box-shadow: 0 12rpx 26rpx rgba(53,214,166,0.1); }
.bottom-space { height: 70rpx; }
</style>