aslan-h5/src/views/TopList/TopList.vue

967 lines
30 KiB
Vue
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.

<template>
<div class="fullPage">
<div class="bg">
<!-- 状态栏占位区域仅在APP中显示 -->
<div v-if="isInAppEnvironment" style="height: 30px"></div>
<!-- 帮助按钮 -->
<img
:src="images.help"
alt=""
style="width: 10%; position: absolute; top: 48px; right: 8px; z-index: 4"
@click="helpInfoShow = true"
/>
<!-- history按钮 -->
<img
:src="images.history"
alt=""
style="width: 20%; margin-top: 20px"
@click="historyShow = true"
/>
<!-- 每周榜首 -->
<div style="display: flex; flex-direction: column; align-items: center">
<!-- 主标题 -->
<img :src="images.listTitle" alt="" style="width: 90%" />
<!-- 榜首 -->
<div style="width: 100%; display: flex; justify-content: space-around; margin-top: 16px">
<div style="width: 40%" v-for="(topUser, index) in listTop" :key="index">
<div
style="
position: relative;
width: 100%;
min-height: max-content;
display: flex;
justify-content: center;
align-items: center;
"
>
<!-- 头像框 -->
<img :src="topUser.Frame" alt="" style="width: 100%; z-index: 1" />
<!-- 头像 -->
<div
style="
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 0;
"
>
<img
:src="topUser.userInfo.userAvatar"
alt=""
width="70%"
style="border-radius: 50%; aspect-ratio: 1/1; object-fit: cover"
/>
</div>
<div
style="
height: 10%;
width: 50%;
position: absolute;
z-index: 2;
display: flex;
justify-content: center;
align-items: center;
"
:style="{ bottom: topUser.type == 'topKing' ? '9.3%' : '7%' }"
>
<div
style="
color: #fff;
font-weight: 860;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
class="topName"
>
{{ topUser.userInfo.userNickname }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 刷新倒计时 -->
<div style="display: flex; justify-content: center; margin-top: 20px">
<!-- 天数 -->
<div style="width: 10%; position: relative; margin-right: 10px">
<img :src="images.dayBg" alt="" width="100%" style="display: block" />
<div
style="
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-weight: 860;
"
>
{{ Days }}D
</div>
</div>
<!-- 当天时间倒计时 -->
<div style="width: 23.75%; position: relative">
<img :src="images.timeBg" alt="" width="100%" style="display: block" />
<div
style="
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-weight: 860;
"
>
{{ Hours }} : {{ Minutes }} : {{ Second }}
</div>
</div>
</div>
<!-- rewards -->
<div style="display: flex; justify-content: flex-end; margin-top: -8%">
<img :src="images.rewards" alt="" style="width: 20%" @click="rewardsShow = true" />
</div>
<!-- 每周礼物 -->
<div style="display: flex; justify-content: center">
<div style="width: 100%; position: relative">
<img :src="images.eventGifts" alt="" style="width: 100%" />
<div
style="
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
"
>
<div style="width: 80%; display: flex; justify-content: space-around; margin-top: 10%">
<div
v-for="(gift, index) in gifts"
:key="index"
style="width: 30%; display: flex; flex-direction: column"
>
<div style="position: relative">
<img style="" :src="images.gift" alt="" width="100%" />
<div
style="
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
"
>
<img :src="gift.giftPhoto" alt="" width="50%" />
</div>
</div>
<div style="display: flex; justify-content: center; align-items: center; gap: 5px">
<img
src="/src/assets/icon/coin.png"
alt=""
style="width: 18%; aspect-ratio: 1/1"
/>
<div style="color: rgba(255, 255, 255, 1); font-weight: 590">
{{ gift.giftCandy }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 排行榜模块 -->
<div style="width: 100vw">
<!-- 排行榜切换按钮 -->
<div style="width: 100%">
<div
v-if="visibleKingList"
style="width: 100%; display: flex; justify-content: space-around"
>
<img :src="images.kingBtActive" alt="" style="width: 40%" />
<img :src="images.queenBt" alt="" style="width: 40%" @click="visibleKingList = false" />
</div>
<div
v-if="!visibleKingList"
style="width: 100%; display: flex; justify-content: space-around"
>
<img :src="images.kingBt" alt="" style="width: 40%" @click="visibleKingList = true" />
<img :src="images.queenBtActive" alt="" style="width: 40%" />
</div>
</div>
<!-- 排行榜 -->
<div style="width: 100%">
<borderImg title="Ranking">
<div
style="margin: -2px 0; position: relative"
v-for="(listItem, index) in topList"
:key="index"
>
<img :src="images['border-item-user']" alt="" style="width: 100%; display: block" />
<div
style="
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 0 15%;
"
>
<div style="width: 70%; display: flex; align-items: center">
<div style="color: #edb247; font-weight: 700; width: 28px">
{{ listItem.rank }}
</div>
<div
style="
width: 28%;
aspect-ratio: 1/1;
border-radius: 50%;
margin: 0 8px;
background: linear-gradient(
to bottom,
rgba(255, 224, 60, 1),
rgba(255, 189, 19, 1)
);
display: flex;
justify-content: center;
align-items: center;
"
>
<img
:src="listItem.userAvatar"
alt=""
style="width: 90%; aspect-ratio: 1/1; border-radius: 50%; object-fit: cover"
/>
</div>
<div
style="width: calc(72% - 28px - 26px); display: flex; flex-direction: column"
>
<div
style="
color: #fff;
font-weight: 860;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
"
class="topUserNickname"
>
{{ listItem.userNickname }}
</div>
<div style="color: #fff; font-weight: 590">ID:{{ listItem.account }}</div>
</div>
</div>
<div style="display: flex; width: 30%; display: flex; align-items: center">
<img src="/src/assets/icon/coin.png" alt="" style="width: 20%; height: 20%" />
<div style="color: #edb247; font-weight: 700">
{{ listItem.quantity }}
</div>
</div>
</div>
</div>
<!-- 触发加载功能 -->
<div ref="kingListLoadmore" v-show="showKingLoading && visibleKingList"></div>
<!-- 触发加载功能 -->
<div ref="queenListloadmore" v-show="showQueenLoading && !visibleKingList"></div>
</borderImg>
</div>
</div>
</div>
<!-- 弹窗遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<!-- help弹窗 -->
<div v-if="helpInfoShow" style="margin: 20% 0" @click.stop>
<img :src="images.helpInfo" alt="" style="width: 100%" />
</div>
<!-- history弹窗 -->
<div v-if="historyShow" style="margin: 20% 0" @click.stop>
<div style="width: 100%">
<borderImg title="History">
<div
style="
background-size: 100% 100%;
min-height: 20px;
margin: -10px 0;
padding: 10px 10%;
"
:style="{ backgroundImage: `url(${images['border-item']})` }"
>
<!-- 内容 -->
<div
style="width: 100%; display: flex; justify-content: space-around; margin-top: 16px"
>
<div
style="width: 40%"
v-for="(topOne, topIndex) in showTopOne.top"
:key="topIndex"
>
<div
style="
position: relative;
width: 100%;
min-height: max-content;
display: flex;
justify-content: center;
align-items: center;
"
>
<!-- 头像框 -->
<img :src="topOne.Frame" alt="" style="width: 100%; z-index: 1" />
<!-- 头像 -->
<div
style="
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
z-index: 0;
"
>
<img
:src="topOne.userInfo.userAvatar"
alt=""
width="70%"
style="border-radius: 50%; aspect-ratio: 1/1; object-fit: cover"
/>
</div>
<div
style="
height: 10%;
width: 50%;
color: #fff;
position: absolute;
z-index: 2;
font-weight: 860;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
"
:style="{ bottom: topOne.type == 'topKing' ? '9.3%' : '7%' }"
>
<div
style="
color: #fff;
font-weight: 860;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
class="topName"
>
{{ topOne.userInfo.userNickname }}
</div>
</div>
</div>
<div style="color: #fff; font-weight: 590; text-align: center">
ID:{{ topOne.userInfo.account }}
</div>
</div>
</div>
<div
style="
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
"
>
<img
:src="images.timeBack"
alt=""
width="18px"
height="18px"
@click="changeHistoryIndex(1)"
/>
<div
style="
width: 40%;
text-align: center;
font-size: 14px;
font-weight: 590;
background-image: -webkit-linear-gradient(
top,
rgba(243, 183, 0, 1),
rgba(255, 238, 198, 1)
);
background-clip: text;
color: transparent;
"
>
{{ showTopOne.group }}
</div>
<img
:src="images.timeNext"
alt=""
width="18px"
height="18px"
@click="changeHistoryIndex(-1)"
/>
</div>
</div>
</borderImg>
</div>
</div>
<!-- rewards弹窗 -->
<div v-if="rewardsShow" style="padding: 20% 0" @click.stop>
<div style="position: relative">
<!-- 取消按钮 -->
<img
:src="images.cancel"
alt=""
style="width: 7%; position: absolute; top: 0; right: 6%; z-index: 99"
@click="closedPopup"
/>
<div style="width: 100%">
<borderImg title="Rewards">
<div
style="
background-size: 100% 100%;
min-height: 20px;
margin: -10px 0;
padding: 10px 10%;
"
:style="{ backgroundImage: `url(${images['border-item']})` }"
>
<!-- 奖励榜切换按钮 -->
<div style="width: 100%">
<div
v-if="visibleKingRewards"
style="width: 100%; display: flex; justify-content: space-around"
>
<img :src="images.kingBtActive" alt="" style="width: 40%" />
<img
:src="images.queenBt"
alt=""
style="width: 40%"
@click="visibleKingRewards = false"
/>
</div>
<div
v-if="!visibleKingRewards"
style="width: 100%; display: flex; justify-content: space-around"
>
<img
:src="images.kingBt"
alt=""
style="width: 40%"
@click="visibleKingRewards = true"
/>
<img :src="images.queenBtActive" alt="" style="width: 40%" />
</div>
</div>
<!-- 奖励榜 -->
<div v-for="(rewardsTop, top) in rewardsList" :key="top">
<div
style="display: flex; flex-direction: column; align-items: center"
v-if="rewardsTop.rewards.length > 0"
>
<img
:src="topImg[Number(rewardsTop.rankRange) - 1]"
alt=""
style="width: 40%; margin: 24px 0"
/>
<div
style="
width: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3%;
"
>
<div
v-for="(reward, rewardIndex) in rewardsTop.rewards"
:key="rewardIndex"
style="width: 100%"
>
<div style="position: relative">
<img :src="images.gift" style="" alt="" width="100%" />
<div
style="
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
"
>
<img :src="reward.cover" alt="" width="50%" style="aspect-ratio: 1/1" />
</div>
</div>
<div
style="
color: rgba(255, 255, 255, 1);
font-weight: 590;
text-align: center;
"
>
{{ reward.name }}
</div>
</div>
</div>
</div>
</div>
</div>
</borderImg>
</div>
</div>
</div>
</maskLayer>
<!-- 我的排名 -->
<div
style="
position: sticky;
bottom: 0;
left: 0;
widows: 100vw;
background-color: rgba(77, 30, 22, 1);
display: flex;
justify-content: center;
margin-top: 10px;
"
>
<img :src="images.myRankingBg" alt="" style="width: calc(100% - 1px); display: block" />
<div
style="
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8%;
"
>
<div style="flex: 1; display: flex; align-items: center">
<div style="color: #edb247; font-weight: 700">{{ myRanking.rank || 0 }}</div>
<div
style="
width: 30%;
aspect-ratio: 1/1;
border-radius: 50%;
margin: 0 12px;
background: linear-gradient(to bottom, rgba(255, 224, 60, 1), rgba(255, 189, 19, 1));
display: flex;
justify-content: center;
align-items: center;
"
>
<img
:src="myRanking.userAvatar"
alt=""
style="width: 90%; aspect-ratio: 1/1; border-radius: 50%; object-fit: cover"
/>
</div>
<div style="display: flex; flex-direction: column">
<div style="color: #fff; font-weight: 860">
{{ myRanking.userNickname }}
</div>
</div>
</div>
<div style="display: flex; width: 30%; display: flex; align-items: center">
<img src="/src/assets/icon/coin.png" alt="" style="width: 20%; height: 20%" />
<!-- 1:,0: -->
<div
v-if="myRanking.userSex == 1"
style="color: #edb247; font-weight: 700; margin-left: 8px"
>
{{ myRanking.wealthQuantity }}
</div>
<div v-else style="color: #edb247; font-weight: 700; margin-left: 8px">
{{ myRanking.charmQuantity }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { isInApp } from '../../utils/appBridge.js'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import borderImg from '../../components/TopList/borderImg.vue'
import maskLayer from '../../components/MaskLayer.vue'
import {
getWeekTopList,
getWeekMyContribution,
getThisWeekRewardsAndGifts,
getHistoryTopOne,
} from '@/api/topList.js'
import { useDebounce, useThrottle } from '@/utils/useDebounce'
//vite动态批量导入图片
const imageModules = import.meta.glob('@/assets/images/TopList/*.{png,jpg,svg}', { eager: true })
// 生成文件名映射对象(自动移除路径和扩展名)
const images = Object.fromEntries(
Object.entries(imageModules).map(([path, module]) => {
const fileName = path.split('/').pop().split('.')[0]
return [fileName, module.default]
})
)
const visibleKingList = ref(true)
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
const historyList = ref([]) // 榜首历史记录
const showTopOne = computed(() => {
return {
group: historyList.value[historyIndex.value].group,
top: [
{
type: 'topKing',
Frame: images.kingFrame,
userInfo: historyList.value[historyIndex.value].wealth,
},
{
type: 'topQueen',
Frame: images.queenFrame,
userInfo: historyList.value[historyIndex.value].charm,
},
],
}
})
const historyIndex = ref(0)
const changeHistoryIndex = (num) => {
let index = historyIndex.value + num
let length = historyList.value.length
if (index >= 0 && index < length) {
historyIndex.value = index
}
}
const myRanking = ref({}) // 我的排名
const kingListCurrent = ref(1) //国王榜第几页
const kingListParams = computed(() => ({
type: 'THIS_WEEK',
kingQueenType: 'WEALTH',
current: kingListCurrent.value,
size: 10,
}))
const kingList = ref([]) // 国王榜
const queenListCurrent = ref(1) //女王榜第几页
const queenListParams = computed(() => ({
type: 'THIS_WEEK',
kingQueenType: 'CHARM',
current: queenListCurrent.value,
size: 10,
}))
const queenList = ref([]) // 女王榜
const listTop = ref([
{
type: 'topKing',
Frame: images.kingFrame,
userInfo: {},
},
{
type: 'topQueen',
Frame: images.queenFrame,
userInfo: {},
},
]) //本周的top1国王和女王
// 展示用排行榜
const topList = computed(() => {
return visibleKingList.value ? kingList.value : queenList.value
})
const showKingLoading = ref(true) //触底加载功能
const showQueenLoading = ref(true) //触底加载功能
const topImg = ref([images.top1, images.top2, images.top3]) // top图片
const visibleKingRewards = ref(true) // 展示奖励榜标签
const kingRewards = ref([]) //国王奖励列表
const queenRewards = ref([]) //女王奖励列表
const rewardsList = computed(() => {
return visibleKingRewards.value ? kingRewards.value : queenRewards.value
}) //展示奖励列表
const maskLayerShow = computed(() => {
return helpInfoShow.value || historyShow.value || rewardsShow.value
})
const helpInfoShow = ref(false)
const historyShow = ref(false)
const rewardsShow = ref(false)
// 获取底部加载div
const kingListLoadmore = ref(null)
const queenListloadmore = ref(null)
const debouceGetKingList = useThrottle(() => {
getTopList(kingListParams.value)
}, 1000)
const debouceGetQueenList = useThrottle(() => {
getTopList(queenListParams.value)
}, 1000)
const gifts = ref([])
// 倒计时
const Days = ref(0)
const Hours = ref(0)
const Minutes = ref(0)
const Second = ref(0)
let timer = null
// 获取沙特下周一早上六点的时间戳
const getNextMondayInSaudi = () => {
const now = new Date()
const utcDay = now.getUTCDay() //获取UTC星期几0(周日)-6(周六)
// 计算到下周一的所需天数
const daysToAdd = utcDay === 1 ? 7 : (1 + 7 - utcDay) % 7
const nextMondayUTC = new Date(now) //以当前UTC时间为基准而不是重新获取
nextMondayUTC.setUTCDate(now.getUTCDate() + daysToAdd) //增加到下周一那天
nextMondayUTC.setUTCHours(3, 0, 0, 0) // 设置为沙特下周一当天早上6点北京时间3点
return nextMondayUTC.getTime() //得到下周一00:00:00.000的时间戳
}
// 时间格式化为2位
const formatTime = (value) => {
return value < 10 ? `0${value}` : value
}
const targetTime = ref(getNextMondayInSaudi()) // 闭包保存目标时间 (核心修正)
// 更新倒计时
const getCountdown = () => {
const now = Date.now()
let diff = targetTime.value - now
if (diff <= 0) {
updatePageData() // 刷新页面数据
targetTime.value += 7 * 86400000 // +7天 (更新闭包变量)
diff = targetTime.value - now
}
// 计算时间单位
const totalSeconds = Math.floor(diff / 1000)
Days.value = Math.floor(totalSeconds / (3600 * 24)) //totalSeconds / (3600 * 24)取余留天数并用Math.floor取整
Hours.value = formatTime(Math.floor((totalSeconds % (3600 * 24)) / 3600)) //(totalSeconds % (3600 * 24)取余留秒数,除一小时并取整
Minutes.value = formatTime(Math.floor((totalSeconds % 3600) / 60)) //(totalSeconds % 3600)每个小时的余留秒数,除一分钟并取整
Second.value = formatTime(Math.floor(totalSeconds % 60)) //取一分钟内余留秒数,并取整
}
// 获取俩榜单
const getTopList = async (params) => {
const resWeekTopList = await getWeekTopList(params)
if (resWeekTopList.status && resWeekTopList.body) {
if (params.kingQueenType == 'WEALTH') {
if (kingList.value.length == 0) listTop.value[0].userInfo = resWeekTopList.body.records[0] //首次加载获取king榜首
kingList.value.push(...resWeekTopList.body.records)
if (params.current * params.size >= resWeekTopList.body.total) showKingLoading.value = false
} else {
if (queenList.value.length == 0) listTop.value[1].userInfo = resWeekTopList.body.records[0] //首次加载获取king榜首
queenList.value.push(...resWeekTopList.body.records)
if (params.current * params.size >= resWeekTopList.body.total) showQueenLoading.value = false
}
}
}
// 获取我的贡献
const getMyContribution = async () => {
const resWeekMyContribution = await getWeekMyContribution()
console.log('resWeekMyContribution:', resWeekMyContribution)
if (resWeekMyContribution.status && resWeekMyContribution.body) {
myRanking.value = resWeekMyContribution.body
}
}
// 获取每周奖励列表和礼物
const getRewardsAndGifts = async () => {
const resRewardsAndGifts = await getThisWeekRewardsAndGifts()
if (resRewardsAndGifts.status && resRewardsAndGifts.body) {
gifts.value = resRewardsAndGifts.body.gifts
kingRewards.value = resRewardsAndGifts.body.butOneRewards
queenRewards.value = resRewardsAndGifts.body.butTwoRewards
console.log('kingRewards:', kingRewards.value)
console.log('queenRewards:', queenRewards.value)
}
}
const getAllTopOne = async () => {
const resHistoryTopOne = await getHistoryTopOne()
if (resHistoryTopOne.status && resHistoryTopOne.body) {
historyList.value = resHistoryTopOne.body
}
}
// 关闭弹窗
const closedPopup = () => {
helpInfoShow.value = false
historyShow.value = false
rewardsShow.value = false
}
// IntersectionObserver配置
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.intersectionRatio > 0) {
console.log('监控到底部')
if (entry.target == kingListLoadmore.value && kingList.value.length != 0) {
console.log('kingListLoadmore')
kingListCurrent.value++
debouceGetKingList()
}
if (entry.target == queenListloadmore.value && queenList.value.length != 0) {
console.log('queenListloadmore')
queenListCurrent.value++
debouceGetQueenList()
}
}
})
})
// 刷新页面数据
const updatePageData = () => {
getTopList(kingListParams.value) // 获取国王榜单
getTopList(queenListParams.value) // 获取女王榜单
getMyContribution() // 获取我的贡献
getRewardsAndGifts() //获取本周的礼物和国王和皇后top奖励列表
getAllTopOne() //获取近几个月的榜首国王女王
}
// 组件挂载时检测环境
onMounted(() => {
isInAppEnvironment.value = isInApp()
getCountdown()
timer = setInterval(getCountdown, 1000) //每秒更新
updatePageData() // 刷新页面数据
if (kingListLoadmore.value) {
observer.observe(kingListLoadmore.value)
} //监控国王加载标志
if (queenListloadmore.value) {
observer.observe(queenListloadmore.value)
} //监控女王加载标志
})
onUnmounted(() => {
clearInterval(timer)
})
</script>
<style scoped>
* {
color: black;
font-family: 'SF Pro';
}
.fullPage {
background-color: rgba(77, 30, 22, 1);
min-height: 100vh;
position: relative;
}
.bg {
width: 100vw;
background-image: url('@/assets/images/TopList/bg.png');
background-size: 100% auto;
background-repeat: no-repeat;
position: relative;
}
.topUserNickname::-webkit-scrollbar {
display: none;
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
}
.topName {
font-size: 8px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 16px;
}
.topName {
font-size: 10px;
}
}
@media screen and (min-width: 768px) {
* {
font-size: 24px;
}
.topName {
font-size: 20px;
}
}
@media screen and (min-width: 1024px) {
* {
font-size: 32px;
}
.topName {
font-size: 30px;
}
}
</style>