804 lines
21 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.

<!-- src/views/Ranking/Overall/Ranking.vue -->
<template>
<div class="fullPage" :style="{ backgroundColor }">
<!-- 预加载状态 -->
<div v-show="isLoading" class="loading-container">
<!-- 背景图 -->
<BackgroundLayer :backgroundImages="[currentBgImage]" />
<!-- 加载动画 -->
<div class="loading-spinner"></div>
<p>{{ $t('loading') }}...</p>
</div>
<!-- 主要内容 -->
<div v-show="!isLoading" style="position: relative; z-index: 4">
<!-- 背景图 -->
<BackgroundLayer
:backgroundImages="[currentBgImage]"
:layerStyle="{
zIndex: -1,
}"
/>
<!-- 哨兵元素 -->
<div ref="sentinel"></div>
<!-- 粘性布局贴顶部 -->
<div
ref="stickyHeader"
style="
position: sticky;
top: 0;
z-index: 999;
border-radius: 0 0 12px 12px;
transition: all 0.3s;
"
>
<!-- 状态栏占位区域仅在APP中显示 -->
<div v-if="isInAppEnvironment" style="height: 60px"></div>
<!-- 顶部导航栏 -->
<div style="padding: 4px 8%">
<div style="display: flex; justify-content: space-around; position: relative">
<div
class="tab"
@click="changeType('Wealth')"
style=""
:style="{ color: rankingType == 'Wealth' ? '#FFB760' : '#fff' }"
>
{{ t('ranking_wealth') }}
</div>
<div
class="tab"
@click="changeType('Charm')"
style=""
:style="{ color: rankingType == 'Charm' ? '#60FF83' : '#fff' }"
>
{{ t('ranking_charm') }}
</div>
<div
class="tab"
@click="changeType('Room')"
style=""
:style="{ color: rankingType == 'Room' ? '#9B60FF' : '#fff' }"
>
{{ t('ranking_room') }}
</div>
<!-- 推出键 -->
<img
v-smart-img
src="../../../assets/icon/arrowBack.png"
alt=""
style="position: absolute; left: 0; top: 50%; transform: translateY(-50%)"
width="6%"
@click="closePage"
/>
</div>
</div>
<!-- 排行榜时间选择 -->
<div
style="
margin: 10px 4% 0;
padding: 4px 16px;
border-radius: 32px;
backdrop-filter: blur(32px);
display: grid;
grid-template-columns: repeat(3, 1fr);
"
:style="{ border, background }"
>
<!-- <div
class="tag"
:class="{ tagActive: selectedTimeTab == 'Hourly' }"
@click="changeDateType('Hourly')"
>
{{ t('ranking_hourly') }}
</div> -->
<div class="tabItem">
<div
class="tag"
:class="{ tagActive: selectedTimeTab == 'Daily' }"
@click="changeDateType('Daily')"
>
{{ t('ranking_daily') }}
</div>
</div>
<div class="tabItem">
<div
class="tag"
:class="{ tagActive: selectedTimeTab == 'Weekly' }"
@click="changeDateType('Weekly')"
>
{{ t('ranking_weekly') }}
</div>
</div>
<div class="tabItem">
<div
class="tag"
:class="{ tagActive: selectedTimeTab == 'Monthly' }"
@click="changeDateType('Monthly')"
>
{{ t('ranking_monthly') }}
</div>
</div>
</div>
</div>
<!-- 排行榜模块 -->
<div style="padding: 10px 0">
<!-- 前3名 -->
<div style="margin: 3% 10px 0; min-height: 70vw">
<!-- 第一 -->
<div style="display: flex; justify-content: center">
<TopUser
:Type="rankingType"
:isTopOne="true"
:BorderImgUrl="topBorderBg"
:avatarUrl="RankingTop3[0].avatar || ''"
:name="RankingTop3[0].nickname || ''"
:distributionValue="RankingTop3[0].quantityFormat || ''"
style="width: 40%"
@click="gotoAppPage(RankingTop3[0].id)"
/>
</div>
<!-- 第二三 -->
<div style="display: flex; justify-content: space-between; margin-top: -45%">
<TopUser
:Type="rankingType"
:BorderImgUrl="top2BorderBg"
:avatarUrl="RankingTop3[1].avatar || ''"
:name="RankingTop3[1].nickname || ''"
:distributionValue="RankingTop3[1].quantityFormat || ''"
style="width: 32%"
@click="gotoAppPage(RankingTop3[1].id)"
/>
<TopUser
:Type="rankingType"
:BorderImgUrl="top3BorderBg"
:avatarUrl="RankingTop3[2].avatar || ''"
:name="RankingTop3[2].nickname || ''"
:distributionValue="RankingTop3[2].quantityFormat || ''"
style="width: 32%"
@click="gotoAppPage(RankingTop3[2].id)"
/>
</div>
</div>
<!-- 第四名开始 -->
<div style="padding: 3vw 3vw 0; display: flex; flex-direction: column; gap: 2vw">
<div
v-for="(userInfo, index) in RankingFromTop4"
:key="userInfo.id"
style="
border-radius: 12px;
backdrop-filter: blur(32px);
padding: 3vw;
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
"
:style="{ background: rankItemBackground, border: '1px ' + rankItemBorder }"
@click="gotoAppPage(userInfo.id)"
>
<!-- 基本信息 -->
<div
style="
flex: 1;
min-width: 0;
align-self: stretch;
display: flex;
justify-content: space-around;
align-items: center;
gap: 8px;
"
>
<!-- 排名 -->
<div
style="
width: auto;
min-width: 0;
align-self: stretch;
display: flex;
justify-content: center;
align-items: center;
"
>
<div style="font-weight: 700">{{ index + 4 }}</div>
</div>
<!-- 头像 -->
<img
v-smart-img
:src="userInfo.avatar || ''"
alt=""
style="
display: block;
width: 3em;
aspect-ratio: 1/1;
border-radius: 50%;
object-fit: cover;
"
:style="{ borderRadius: rankingType === 'Room' ? '10%' : '50%' }"
@error="handleAvatarImageError"
/>
<!-- 名称 -->
<div
style="
flex: 1;
min-width: 0;
padding: 0 4px;
font-weight: 700;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
>
{{ userInfo.nickname }}
</div>
</div>
<!-- 贡献值 -->
<div
style="
width: auto;
min-width: 0;
align-self: stretch;
display: flex;
align-items: center;
"
>
<img
v-smart-img
src="/src/assets/icon/coin.png"
alt=""
style="display: block; width: 1.3em; aspect-ratio: 1/1"
/>
<div style="font-weight: 500; color: rgba(248, 182, 45, 1)">
{{ userInfo.quantityFormat }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 我的排名占位 -->
<div v-if="!isLoading" style="height: 15vw"></div>
<!-- 我的排名 -->
<div
v-if="!isLoading"
style="position: fixed; bottom: 0; z-index: 999; padding: 0 1px; width: 100%"
:style="{ backgroundColor }"
>
<div
style="
border-radius: 12px 12px 0 0;
backdrop-filter: blur(32px);
padding: 2%;
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
"
:style="{
background: rankItemBackground,
borderTop: '2px ' + rankItemBorder,
borderLeft: '1px ' + rankItemBorder,
borderRight: '1px ' + rankItemBorder,
}"
>
<!-- 基本信息 -->
<div
style="
flex: 1;
min-width: 0;
align-self: stretch;
display: flex;
justify-content: space-around;
align-items: center;
gap: 8px;
"
>
<!-- 排名 -->
<div
style="
width: auto;
min-width: 0;
align-self: stretch;
display: flex;
justify-content: center;
align-items: center;
"
>
<div style="font-weight: 700">{{ myRanking.rank || 999 }}</div>
</div>
<!-- 头像 -->
<img
v-smart-img
:src="myRanking.avatar || ''"
alt=""
style="
display: block;
width: 3em;
aspect-ratio: 1/1;
border-radius: 50%;
object-fit: cover;
"
:style="{ borderRadius: rankingType === 'Room' ? '10%' : '50%' }"
@error="handleAvatarImageError"
/>
<!-- 名称 -->
<div
style="
flex: 1;
min-width: 0;
font-weight: 700;
padding: 0 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
>
{{ myRanking.nickname || '' }}
</div>
</div>
<!-- 贡献值 -->
<div
style="
width: auto;
min-width: 0;
align-self: stretch;
display: flex;
align-items: center;
"
>
<img
v-smart-img
src="/src/assets/icon/coin.png"
alt=""
style="display: block; width: 1.3em; aspect-ratio: 1/1"
/>
<div style="font-weight: 500; color: rgba(248, 182, 45, 1)">
{{ myRanking.quantityFormat || 0 }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { isInApp, closePage, viewUserInfo, gotoRoom } from '@/utils/appBridge.js'
import { connectToApp } from '@/utils/appConnector.js'
import { preloadImages } from '@/utils/imagePreloader'
import { getPngUrl } from '@/config/imagePaths.js'
import { handleAvatarImageError } from '@/utils/imageHandler.js'
import { useThrottle } from '@/utils/useDebounce'
import { getWealthRanking, getCharmRanking, getRoomRanking, getMyRanking } from '@/api/ranking'
import BackgroundLayer from '@/components/BackgroundLayer.vue'
import TopUser from './components/topUser.vue'
const route = useRoute()
const { t, locale } = useI18n()
// 获取OSS图片URL的函数
const imageUrl = (filename) => getPngUrl('Ranking/Overall/', filename)
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
const rankingType = ref(route.query.first || 'Wealth') //榜单类型
const selectedTimeTab = ref('Daily') //时间标签
// 接口参数
const params_type = ref('GIFTS_SEND') //排行榜类型
const params_dateType = ref('DAY') //时间类型
const totalRanking = ref({})
const showRanking = ref([])
const myRanking = ref({}) //我的排名
// 预加载状态
const isLoading = ref(true)
const currentBgImage = computed(() => {
if (rankingType.value == 'Wealth') {
return imageUrl('bgWealth')
} else if (rankingType.value == 'Charm') {
return imageUrl('bgCharm')
} else if (rankingType.value == 'Room') {
return imageUrl('bgRoom')
}
return '' // 默认返回空字符串
})
// 预加载关键图片
const preloadCriticalImages = async () => {
const criticalImages = [
imageUrl('bgWealth'),
imageUrl('bgCharm'),
imageUrl('bgRoom'),
imageUrl('top1-wealth'),
imageUrl('top1-charm'),
imageUrl('top1-room'),
imageUrl('top2'),
imageUrl('top3'),
imageUrl('top2-room'),
imageUrl('top3-room'),
]
await preloadImages(criticalImages)
}
// 筛选展示榜单
const updateTimeRanking = () => {
console.log('updateTimeRanking()//筛选展示榜单')
if (selectedTimeTab.value == 'Hourly' && totalRanking.value.hourly) {
showRanking.value = totalRanking.value.hourly
console.log('showRanking.value:', showRanking.value)
} else if (selectedTimeTab.value == 'Daily' && totalRanking.value.daily) {
showRanking.value = totalRanking.value.daily
} else if (selectedTimeTab.value == 'Weekly' && totalRanking.value.weekly) {
showRanking.value = totalRanking.value.weekly
} else if (selectedTimeTab.value == 'Monthly' && totalRanking.value.monthly) {
showRanking.value = totalRanking.value.monthly
}
}
const ThrottleChangeType = (type) => {
if (rankingType.value != type) {
console.log('改变rankingType')
rankingType.value = type
selectedTimeTab.value = 'Daily'
params_dateType.value = 'DAY'
if (type == 'Wealth') {
params_type.value = 'GIFTS_SEND'
} else if (type == 'Charm') {
params_type.value = 'GIFTS_RECEIVED'
} else if (type == 'Room') {
params_type.value = 'ROOM_GIFTS'
}
// 清空数据
totalRanking.value = {}
showRanking.value = []
myRanking.value = {}
getRanking() // 获取榜单
getMyRankingInfo() //获取我的排名
}
}
// 改变排行榜类型
const changeType = useThrottle(ThrottleChangeType, 1000)
// 改变时间类型
const ThrottleChangeDateType = (dateType) => {
if (selectedTimeTab.value != dateType) {
console.log('改变rankingType')
selectedTimeTab.value = dateType
if (dateType == 'Hourly') {
params_dateType.value = 'HOUR'
} else if (dateType == 'Daily') {
params_dateType.value = 'DAY'
} else if (dateType == 'Weekly') {
params_dateType.value = 'WEEK'
} else if (dateType == 'Monthly') {
params_dateType.value = 'MONTH'
}
// 清空数据
showRanking.value = []
myRanking.value = {}
updateTimeRanking() //筛选展示榜单
getMyRankingInfo() //获取我的排名
}
}
const changeDateType = useThrottle(ThrottleChangeDateType, 500)
// 前三名榜单
const RankingTop3 = computed(() => {
console.log('刷新RankingTop3')
let RankingShow = [...showRanking.value]
if (showRanking.value.length < 3) {
let addNullUser = Array.from({ length: 3 - showRanking.value.length }, () => ({
avatar: '',
nickname: '',
quantityFormat: '',
}))
RankingShow.push(...addNullUser)
}
return RankingShow
})
// 从第四名开始榜单
const RankingFromTop4 = computed(() => {
return showRanking.value.filter((_, index) => index >= 3)
// return []
})
// 动态样式
const backgroundColor = ref('') //页面样式
const border = ref('') //时间标签样式
const background = ref('') //时间标签样式
const rankItemBackground = ref('') //排名项背景
const rankItemBorder = ref('') //排名项边框
const topBorderBg = ref('') //top1头像框
const top2BorderBg = ref('') //top2头像框
const top3BorderBg = ref('') //top3头像框
const sentinel = ref(null)
const stickyHeader = ref(null)
watchEffect(() => {
if (rankingType.value == 'Wealth') {
backgroundColor.value = '#230F0A'
topBorderBg.value = imageUrl('top1-wealth')
top2BorderBg.value = imageUrl('top2')
top3BorderBg.value = imageUrl('top3')
border.value = '1px solid rgba(221, 114, 0, 0.39)'
background.value = 'rgba(252, 194, 147, 0.20)'
rankItemBackground.value =
'linear-gradient(97deg, rgba(50, 34, 21, 0.60) 12.03%, rgba(99, 52, 33, 0.60) 100.37%)'
rankItemBorder.value = 'solid #FFA166'
params_type.value = 'GIFTS_SEND'
}
if (rankingType.value == 'Charm') {
backgroundColor.value = '#112C1E'
topBorderBg.value = imageUrl('top1-charm')
top2BorderBg.value = imageUrl('top2')
top3BorderBg.value = imageUrl('top3')
border.value = '1px solid rgba(0, 221, 74, 0.39)'
background.value = 'rgba(147, 252, 166, 0.20)'
rankItemBackground.value =
'linear-gradient(97deg, rgba(21, 50, 22, 0.60) 12.03%, rgba(33, 99, 40, 0.60) 100.37%)'
rankItemBorder.value = 'solid #66FF82'
params_type.value = 'GIFTS_RECEIVED'
}
if (rankingType.value == 'Room') {
backgroundColor.value = '#211031'
topBorderBg.value = imageUrl('top1-room')
top2BorderBg.value = imageUrl('top2-room')
top3BorderBg.value = imageUrl('top3-room')
border.value = '1px solid rgba(155, 0, 221, 0.39)'
background.value = 'rgba(219, 147, 252, 0.20)'
rankItemBackground.value =
'linear-gradient(97deg, rgba(33, 21, 50, 0.60) 12.03%, rgba(60, 33, 99, 0.60) 100.37%)'
rankItemBorder.value = 'solid #A366FF'
params_type.value = 'ROOM_GIFTS'
}
})
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// 哨兵元素离开视口
if (!entry.isIntersecting) {
console.log('哨兵元素离开视口')
if (stickyHeader.value) {
stickyHeader.value.classList.add('stuck')
console.log('添加stuck类')
} else {
console.log('元素未找到!')
}
} else {
stickyHeader.value.classList.remove('stuck')
}
})
})
// 跳转app
const gotoAppPage = (id) => {
if (isInAppEnvironment.value) {
if (rankingType.value == 'Room') {
gotoRoom(id)
} else {
viewUserInfo(id)
}
}
}
// 获取榜单
const getRanking = async () => {
console.log('获取榜单')
let resRanking = {}
if (rankingType.value == 'Wealth') {
resRanking = await getWealthRanking()
} else if (rankingType.value == 'Charm') {
resRanking = await getCharmRanking()
} else if (rankingType.value == 'Room') {
resRanking = await getRoomRanking()
}
if (resRanking.status && resRanking.body) {
totalRanking.value = resRanking.body
updateTimeRanking() //筛选展示榜单
}
}
//获取我的排名
const getMyRankingInfo = async () => {
console.log('获取我的排名')
const resMyRanking = await getMyRanking(params_type.value, params_dateType.value)
if (resMyRanking.status && resMyRanking.body) {
myRanking.value = resMyRanking.body
} else {
myRanking.value = {}
}
}
// 刷新页面数据
const initData = async () => {
await Promise.all([
getRanking(), // 获取榜单
getMyRankingInfo(), //获取我的排名
])
}
// 完成预加载
const completePreloading = async () => {
try {
// 执行所有初始化操作
await Promise.all([initData(), preloadCriticalImages()])
} catch (error) {
console.error('预加载过程中发生错误:', error)
} finally {
// 无论成功或失败都结束加载状态
isLoading.value = false
}
}
// 使用工具函数连接APP
const connectToAppHandler = async () => {
await connectToApp(() => {
// 连接成功回调
completePreloading() // 完成预加载
})
}
onMounted(() => {
connectToAppHandler()
isInAppEnvironment.value = isInApp()
observer.observe(sentinel.value)
})
</script>
<style lang="scss" scoped>
* {
color: #fff;
}
.fullPage {
width: 100vw;
min-height: 100vh;
background-size: 100% auto;
background-repeat: no-repeat;
position: relative;
}
.tab {
font-weight: 700;
font-size: 1em;
}
.tabItem {
display: flex;
justify-content: center;
align-items: center;
}
.tag {
font-weight: 510;
padding: 4px 10%;
}
.tagActive {
color: black;
border-radius: 32px;
border: 1px solid #f5f54c;
box-sizing: border-box;
background: #fcfc93;
box-shadow: 0 0 8.6px 3px #fc8b02 inset;
display: flex;
justify-content: center;
align-items: center;
}
.stuck {
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(32px);
}
/* 添加加载动画样式 */
.loading-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #666;
overflow: hidden;
position: relative;
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid #f3f3f3;
border-top: 3px solid #f59e0b;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 12px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 14px;
}
}
@media screen and (min-width: 768px) {
* {
font-size: 24px;
}
}
@media screen and (min-width: 1024px) {
* {
font-size: 32px;
}
}
</style>