938 lines
26 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/Activities/ -->
<template>
<div class="fullPage">
<!-- 预加载状态 -->
<div v-if="isLoading" class="loading-container">
<div class="loading-spinner"></div>
<p>{{ $t('loading') }}...</p>
</div>
<!-- 主要内容 -->
<div v-else>
<!-- 新增的背景图层 -->
<div class="background-overlay" :style="{ '--bg2-url': `url(${imageUrl('bg2')})` }"></div>
<div class="bg">
<itemCenter :imgUrl="imageUrl('bg')" :contentStyle="`display: block;`">
<!-- 状态栏占位区域仅在APP中显示 -->
<!-- <div v-if="isInAppEnvironment" style="height: 30px"></div> -->
<!-- 按钮 -->
<div
style="margin-top: 130vw; display: flex; justify-content: flex-end; align-items: center"
>
<!-- 帮助按钮 -->
<img
v-smart-img
:src="imageUrl('helpBt')"
alt=""
style="display: block; position: relative; width: 10vw; margin-right: 8px"
@click="maskLayerShow = true"
/>
</div>
</itemCenter>
<!-- 倒计时 -->
<div class="timeBox">
<div style="width: 20%">
<itemCenter :imgUrl="imageUrl('timeBg')">
<div class="timeText" style="">{{ Days }}D</div>
</itemCenter>
</div>
<div class="timeText timeGap"></div>
<div style="width: 20%">
<itemCenter :imgUrl="imageUrl('timeBg')">
<div class="timeText" style="">{{ Hours }}</div>
</itemCenter>
</div>
<div class="timeText timeGap">:</div>
<div style="width: 20%">
<itemCenter :imgUrl="imageUrl('timeBg')">
<div class="timeText" style="">{{ Minutes }}</div>
</itemCenter>
</div>
<div class="timeText timeGap">:</div>
<div style="width: 20%">
<itemCenter :imgUrl="imageUrl('timeBg')">
<div class="timeText" style="">{{ Second }}</div>
</itemCenter>
</div>
</div>
<!-- 每周礼物 -->
<div style="display: flex; justify-content: center; margin-top: 10px">
<itemCenter :imgUrl="imageUrl('giftsBg')" :contentStyle="'padding: 43% 0 19.5%;'">
<div
style="
width: 84%;
height: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
align-self: flex-start;
gap: 5.5vw;
margin-left: 1vw;
"
>
<div
v-for="(gift, index) in gifts"
:key="index"
style="
width: 100%;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
"
>
<!-- 礼物图 -->
<div
style="
width: 100%;
aspect-ratio: 1/1;
padding-bottom: 2vw;
display: flex;
justify-content: center;
align-items: center;
"
>
<img
v-smart-img
:src="gift.giftPhoto"
alt=""
width="70%"
style="display: block; object-fit: cover"
/>
</div>
<!-- 价值 -->
<div
style="
height: 5vw;
display: flex;
justify-content: center;
align-items: center;
gap: 2px;
"
>
<img
v-smart-img
src="/src/assets/icon/coin.png"
alt=""
style="display: block; width: 1.5em; aspect-ratio: 1/1"
/>
<div style="color: rgba(255, 255, 255, 1); font-weight: 590">
{{ gift.giftCandy }}
</div>
</div>
</div>
</div>
</itemCenter>
</div>
<!-- 切换按钮 -->
<div style="display: flex; justify-content: space-around; margin-top: 2vw">
<!-- 排行榜按钮 -->
<div style="width: 40%; position: relative" @click="rankingShow = true">
<img
v-show="!rankingShow"
v-smart-img
class="toggle-image"
:src="imageUrl('rankingBt')"
alt=""
style="display: block; width: 100%"
/>
<img
v-show="rankingShow"
v-smart-img
class="toggle-image"
:src="imageUrl('rankingBtActive')"
alt=""
style="display: block; width: 100%"
/>
</div>
<!-- 奖励按钮 -->
<div style="width: 40%; position: relative" @click="rankingShow = false">
<img
v-show="rankingShow"
v-smart-img
class="toggle-image"
:src="imageUrl('rewardsBt')"
alt=""
style="display: block; width: 100%"
/>
<img
v-show="!rankingShow"
v-smart-img
class="toggle-image"
:src="imageUrl('rewardBtActive')"
alt=""
style="display: block; width: 100%"
/>
</div>
</div>
<!-- 展示内容 -->
<div>
<!-- Ranking -->
<div v-show="rankingShow" style="position: relative; width: 100%; margin: 10px 0">
<!-- 背景图 -->
<div
class="ranking-bg"
:style="{ '--ranking-bg-url': `url(${imageUrl('RankingBg')})` }"
></div>
<div style="position: relative; z-index: 1">
<!-- 前三名 -->
<itemCenter
:imgUrl="imageUrl('RankingMain')"
:contentStyle="`padding: 28% 5% 0;flex-direction: column;`"
>
<div>
<!-- 第一 -->
<div style="display: flex; justify-content: center">
<TopUser
ranking="1"
:BorderImgUrl="imageUrl('topOne')"
:avatarUrl="RankingHasTop3[0].avatar"
:name="RankingHasTop3[0].nickname"
:distributionValue="RankingHasTop3[0].quantity"
style="width: 41%"
@click="viewUserInfo(RankingHasTop3[0].userId)"
/>
</div>
<!-- 第二三 -->
<div
style="
position: relative;
z-index: 3;
display: flex;
justify-content: space-between;
margin-top: -30vw;
pointer-events: none;
"
>
<div
style="pointer-events: auto; width: 28%"
@click.stop="viewUserInfo(RankingHasTop3[1].userId)"
>
<TopUser
ranking="2"
:BorderImgUrl="imageUrl('topTwo')"
:avatarUrl="RankingHasTop3[1].avatar"
:name="RankingHasTop3[1].nickname"
:distributionValue="RankingHasTop3[1].quantity"
style="width: 100%"
/>
</div>
<div
style="pointer-events: auto; width: 28%"
@click.stop="viewUserInfo(RankingHasTop3[2].userId)"
>
<TopUser
ranking="3"
:BorderImgUrl="imageUrl('topThree')"
:avatarUrl="RankingHasTop3[2].avatar"
:name="RankingHasTop3[2].nickname"
:distributionValue="RankingHasTop3[2].quantity"
style="width: 100%"
/>
</div>
</div>
</div>
</itemCenter>
<!-- 第四名开始 -->
<div v-if="showRanking.length > 0" style="position: relative; z-index: 2">
<itemCenter
v-for="(listItem, index) in showRanking"
:key="listItem.userId"
:imgUrl="imageUrl('RankingItem')"
:contentStyle="`padding: 0 10% 2%`"
:lazy="true"
:flip="true"
:immediate="index < 2"
@click="viewUserInfo(listItem.userId)"
>
<!-- 基本信息 -->
<div
style="
flex: 1;
min-width: 0;
display: flex;
justify-content: space-around;
align-items: center;
gap: 4px;
"
>
<!-- 排名 -->
<div
style="width: 9%; display: flex; justify-content: center; align-items: center"
>
<div style="color: #ffe601; font-weight: 700">
{{ listItem?.rank }}
</div>
</div>
<!-- 头像 -->
<img
v-smart-img
:src="listItem?.avatar || ''"
alt=""
style="
display: block;
width: 23%;
margin: 0 3%;
aspect-ratio: 1/1;
border-radius: 50%;
object-fit: cover;
position: relative;
z-index: -1;
"
@error="handleAvatarImageError"
/>
<!-- 名称id -->
<div style="flex: 1; min-width: 0; display: flex; flex-direction: column">
<div
style="
color: #fff;
font-weight: 860;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
class="UserNickname"
>
{{ listItem?.nickname }}
</div>
<div style="color: #fff; font-weight: 590" class="UserNickname">
{{ $t('user_id_prefix') }} {{ listItem?.account }}
</div>
</div>
</div>
<!-- 贡献值 -->
<div
style="
width: auto;
min-width: 0;
display: flex;
align-items: center;
"
>
<img
v-smart-img
src="/src/assets/icon/coin.png"
alt=""
style="display: block; width: 1.5em; aspect-ratio: 1/1; object-fit: cover"
/>
<div style="color: #ffe601; font-weight: 700" class="UserNickname">
{{ listItem?.quantity }}
</div>
</div>
</itemCenter>
</div>
<!-- 底框 -->
<img
v-smart-img
:src="imageUrl('RankingBottomBorder')"
alt=""
width="100%"
style="display: block; margin-top: -10vw; position: relative; z-index: 1"
/>
</div>
</div>
<!-- Rewards -->
<div
v-show="!rankingShow"
style="display: flex; flex-direction: column; align-items: center"
>
<div
v-for="(rewardsTop, top) in rewardsList"
:key="top"
style="position: relative; width: 100%; margin: 10px 0"
>
<itemCenter
:imgUrl="topImg[Number(rewardsTop.rankRange) - 1]"
:contentStyle="`display: grid;grid-template-columns: repeat(2, 1fr);gap:5vw 7.5vw;padding: 38vw 11.5vw 22vw 9vw;`"
>
<div
v-for="(reward, rewardIndex) in rewardsTop.rewards"
:key="rewardIndex"
style="
width: 100%;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
"
>
<div
style="
width: 100%;
aspect-ratio: 1/1;
display: flex;
justify-content: center;
align-items: center;
"
>
<!-- 奖励图片 -->
<img
v-smart-img
:src="reward.cover || ''"
alt=""
style="display: block; width: 60%; object-fit: cover"
@error="(e) => handleRewardImageError(e, reward.type)"
/>
</div>
<!-- 数值 -->
<div
style="
width: 100%;
height: 10vw;
display: flex;
justify-content: center;
align-items: center;
"
>
<div style="min-width: 0; font-size: 1.2em; font-weight: 590; color: #fff">
{{ showDetail(reward.type, reward) }}
</div>
</div>
</div>
</itemCenter>
</div>
</div>
</div>
<!-- 我的排名占位 -->
<div style="height: 25vw"></div>
<!-- 我的排名 -->
<itemCenter
style="position: fixed; bottom: 0; z-index: 2"
:imgUrl="imageUrl('myRankingBg')"
:contentStyle="`padding: 3% 7% 2%`"
:flip="true"
>
<!-- 基本信息 -->
<div
style="
flex: 1;
min-width: 0;
display: flex;
justify-content: space-around;
align-items: center;
gap: 8px;
"
>
<!-- 排名 -->
<div
style="
width: 9%;
min-width: 0;
display: flex;
justify-content: center;
align-items: center;
"
>
<div style="color: #ffe601; font-weight: 700">
{{ myRanking.rank || 999 }}
</div>
</div>
<!-- 头像 -->
<img
v-smart-img
:src="myRanking?.avatar || ''"
alt=""
style="
display: block;
width: 20%;
margin: 0 2%;
aspect-ratio: 1/1;
border-radius: 50%;
object-fit: cover;
position: relative;
z-index: -1;
"
@error="handleAvatarImageError"
/>
<!-- 名称 -->
<div style="flex: 1; min-width: 0; display: flex; flex-direction: column">
<div
style="
color: #fff;
font-weight: 860;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
class="UserNickname"
>
{{ myRanking.nickname }}
</div>
</div>
</div>
<!-- 贡献值 -->
<div
style="
width: auto;
min-width: 0;
display: flex;
align-items: center;
"
>
<img
v-smart-img
src="/src/assets/icon/coin.png"
alt=""
style="display: block; width: 1.5em; aspect-ratio: 1/1; object-fit: cover"
/>
<div style="color: #edb247; font-weight: 700">
{{ myRanking.quantity || 0 }}
</div>
</div>
</itemCenter>
<!-- 弹窗遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="maskLayerShow = false">
<!-- help弹窗 -->
<div style="margin: 20% 0" @click.stop>
<img
v-smart-img
:src="imageUrl(getImgName('helpInfo'))"
alt=""
style="display: block; width: 100%"
/>
</div>
</maskLayer>
</div>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { isInApp, viewUserInfo } from '@/utils/appBridge.js'
import { connectToApp } from '@/utils/appConnector.js'
import { useLangStore } from '@/stores/lang'
import { getPngUrl } from '@/config/imagePaths.js'
import { preloadImages } from '@/utils/imagePreloader.js'
import { handleAvatarImageError, handleRewardImageError } from '@/utils/imageHandler.js'
import { getThisWeekRewardsAndGifts, getRankingListAndMyRanking } from '@/api/activity.js'
import itemCenter from '@/components/itemCenter.vue'
import TopUser from './components/topUser.vue'
import maskLayer from '@/components/MaskLayer.vue'
const langStore = useLangStore()
// 当前语言类型
const currentLangType = computed(() => {
return langStore.selectedLang?.type || 'en'
})
// 获取OSS图片URL的函数
const imageUrl = (filename) => getPngUrl('Activities/MerryChristmas/', filename)
// 根据语言获取图片名
const getImgName = (filename) => {
return currentLangType.value === 'ar' ? `${filename}_ar` : filename
}
// 预加载状态
const isLoading = ref(true)
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
const maskLayerShow = ref(false) //帮助模块
// 倒计时
const Days = ref(0)
const Hours = ref(0)
const Minutes = ref(0)
const Second = ref(0)
let timer = null
// 模块切换展示
const rankingShow = ref(true) //排行榜
const Ranking = ref([]) // 排行榜
const myRanking = ref({}) // 我的排名
const rewardsList = ref([]) // 奖励列表
const gifts = ref([]) // 本周礼物
const topImg = ref([imageUrl('top1RewardBg'), imageUrl('top2RewardBg'), imageUrl('top3RewardBg')]) // top图片
// 处理排行榜小于3人时的情况
const RankingHasTop3 = computed(() => {
let RankingShow = [...Ranking.value]
if (Ranking.value.length < 3) {
let addNullUser = Array.from({ length: 3 - Ranking.value.length }, () => ({
avatar: '',
nickname: '',
quantity: '',
}))
RankingShow.push(...addNullUser)
}
return RankingShow
})
// 展示用的榜单
const showRanking = computed(() => {
return Ranking.value.filter((_, index) => index >= 3)
// return []
})
// 获取2025年12月31日上午5点的时间戳北京时间
const getTargetTime = () => {
const targetDate = new Date(2025, 11, 31, 5, 0, 0) // 月份从0开始11代表12月
return targetDate.getTime()
}
const targetTime = ref(getTargetTime())
// 格式化时间
const formatTime = (value) => {
return value < 10 ? `0${value}` : value
}
// 倒计时
const getCountdown = () => {
const now = Date.now()
let diff = targetTime.value - now
// 如果已过期,显示全零
if (diff <= 0) {
Days.value = 0
Hours.value = '00'
Minutes.value = '00'
Second.value = '00'
// 清除定时器
if (timer) {
clearInterval(timer)
timer = null
}
return
}
const totalSeconds = Math.floor(diff / 1000)
Days.value = Math.floor(totalSeconds / (24 * 3600))
Hours.value = formatTime(Math.floor((totalSeconds % (24 * 3600)) / 3600))
Minutes.value = formatTime(Math.floor((totalSeconds % 3600) / 60))
Second.value = formatTime(Math.floor(totalSeconds % 60))
}
// 格式化奖励的展示信息
const showDetail = (type, reward) => {
let showamount = ['GOLD', 'DIAMOND', 'GIFT', 'FRAGMENTS']
if (showamount.includes(type)) {
return reward.content
} else {
return `${reward.quantity}Days`
}
}
// 获取排行榜和我的排名
const getListAndMy = async () => {
let data = {
activityType: 12,
}
const resMyWeekStarRanking = await getRankingListAndMyRanking(data)
console.log('resMyWeekStarRanking:', resMyWeekStarRanking)
if (resMyWeekStarRanking.status && resMyWeekStarRanking.body) {
myRanking.value = resMyWeekStarRanking.body.currentUserRank
Ranking.value = resMyWeekStarRanking.body.rankingList
}
}
// 获取每周奖励列表和礼物
const getRewardsAndGifts = async () => {
const resRewardsAndGifts = await getThisWeekRewardsAndGifts('2003763785923244033')
if (resRewardsAndGifts.status && resRewardsAndGifts.body) {
gifts.value = resRewardsAndGifts.body.gifts
rewardsList.value = resRewardsAndGifts.body.butOneRewards
}
}
// 刷新页面数据
const initData = async () => {
await Promise.all([
getListAndMy(), // 获取排行榜和我的排名
getRewardsAndGifts(), //获取本周的礼物和奖励列表
])
}
// 预加载关键图片
const preloadCriticalImages = async () => {
const criticalImages = [
imageUrl('bg'),
// imageUrl('bg2'),
imageUrl('giftsBg'),
imageUrl('helpBt'),
imageUrl('helpInfo'),
imageUrl('myRankingBg'),
// imageUrl('RankingBg'),
imageUrl('RankingBottomBorder'),
imageUrl('rankingBt'),
imageUrl('rankingBtActive'),
imageUrl('RankingItem'),
imageUrl('RankingMain'),
imageUrl('rewardBtActive'),
imageUrl('rewardsBt'),
imageUrl('timeBg'),
imageUrl('top1RewardBg'),
imageUrl('top2RewardBg'),
imageUrl('top3RewardBg'),
imageUrl('topOne'),
imageUrl('topTwo'),
imageUrl('topThree'),
]
const criticalImages_ar = [
imageUrl('bg'),
// imageUrl('bg2'),
imageUrl('giftsBg'),
imageUrl('helpBt'),
imageUrl('helpInfo_ar'),
imageUrl('myRankingBg'),
// imageUrl('RankingBg'),
imageUrl('RankingBottomBorder'),
imageUrl('rankingBt'),
imageUrl('rankingBtActive'),
imageUrl('RankingItem'),
imageUrl('RankingMain'),
imageUrl('rewardBtActive'),
imageUrl('rewardsBt'),
imageUrl('timeBg'),
imageUrl('top1RewardBg'),
imageUrl('top2RewardBg'),
imageUrl('top3RewardBg'),
imageUrl('topOne'),
imageUrl('topTwo'),
imageUrl('topThree'),
]
if (currentLangType.value == 'en') {
await preloadImages(criticalImages)
} else if (currentLangType.value == 'ar') {
await preloadImages(criticalImages_ar)
}
}
// 完成预加载
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()
getCountdown()
timer = setInterval(getCountdown, 1000) //倒计时每秒更新
})
onUnmounted(() => {
clearInterval(timer) //摧毁计时器
})
</script>
<style lang="scss" scoped>
* {
color: #fff;
font-family: 'SF Pro';
}
.fullPage {
position: relative;
background: #014529;
}
.bg {
width: 100vw;
min-height: 100vh;
position: relative;
z-index: 1;
}
.background-overlay {
position: absolute;
inset: 140vw 0 0;
background-image: var(--bg2-url);
background-repeat: repeat-y;
background-position: top center;
background-size: 100% auto;
z-index: 0; /* 确保在.bg之下 */
}
.valueBg {
background-image: var(--value-bg-url);
background-size: auto 100%;
background-repeat: no-repeat;
background-position: center center;
position: relative;
}
.timeText {
color: #fff;
font-weight: 590;
font-size: 2em;
}
.timeGap {
width: 6px;
align-self: stretch;
display: flex;
justify-content: center;
align-items: center;
}
.ranking-bg {
background-color: linear-gradient(180deg, #7c0101 0%, #400000 100%), #d9d9d9;
background-image: var(--ranking-bg-url);
background-size: 100% auto;
background-repeat: no-repeat;
position: absolute;
inset: 31vw 4vw 5vw;
z-index: 1;
}
.btTitle {
color: #fff;
font-weight: 700;
}
.topTitle {
font-size: 1.5em;
}
.history-name {
font-size: 0.8em;
}
.history-time {
font-size: 0.8em;
}
.UserNickname {
font-size: 0.8em;
}
.timeBox {
display: flex;
justify-content: center;
}
/* 添加加载动画样式 */
.loading-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #666;
}
.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;
}
.toggle-image {
transition: all 0.3s ease-in-out;
}
@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: 12px;
}
}
@media screen and (min-width: 768px) {
* {
font-size: 24px;
}
}
@media screen and (min-width: 1024px) {
* {
font-size: 32px;
}
}
[dir='rtl'] .timeBox {
flex-direction: row-reverse;
}
[dir='rtl'] .flipImg {
transform: scaleX(-1);
}
</style>