759 lines
22 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/Game/index.vue -->
<template>
<div class="fullPage">
<div class="bg" :style="{ '--bg-url': `url(${imageUrl('bg')})` }">
<!-- 状态栏占位区域仅在APP中显示 -->
<div v-if="isInAppEnvironment" style="height: 30px"></div>
<!-- 按钮 -->
<div style="margin-top: 106vw; display: flex; justify-content: flex-end; align-items: center">
<!-- 帮助按钮 -->
<img
v-smart-img
src="../../../assets/icon/helpAct.png"
alt=""
style="display: block; position: relative; width: 10%; margin-right: 8px"
@click="maskLayerShow = true"
/>
</div>
<!-- 倒计时 -->
<div style="display: flex; justify-content: center; margin-top: 15vw">
<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>
<div
v-if="currentLangType == 'en'"
style="display: flex; justify-content: space-around; margin-top: 2vw"
>
<!-- 排行榜按钮 -->
<div style="width: 40%; position: relative">
<img
v-smart-img
:src="imageUrl('rankingBt')"
alt=""
style="display: block; width: 100%"
:style="{ opacity: rankingShow ? 0 : 1 }"
@click="rankingShow = true"
/>
<img
v-smart-img
:src="imageUrl('rankingBtActive')"
alt=""
style="display: block; width: 100%; position: absolute; top: 0; left: 0"
:style="{ opacity: rankingShow ? 1 : 0 }"
@click="rankingShow = true"
/>
</div>
<!-- 奖励按钮 -->
<div style="width: 40%; position: relative">
<img
v-smart-img
:src="imageUrl('rewardsBt')"
alt=""
style="display: block; width: 100%"
:style="{ opacity: rankingShow ? 1 : 0 }"
@click="rankingShow = false"
/>
<img
v-smart-img
:src="imageUrl('rewardBtActive')"
alt=""
style="display: block; width: 100%; position: absolute; top: 0; left: 0"
:style="{ opacity: rankingShow ? 0 : 1 }"
@click="rankingShow = false"
/>
</div>
</div>
<!-- 阿拉伯语版本 -->
<div
v-if="currentLangType == 'ar'"
style="display: flex; justify-content: space-around; margin-top: 2vw"
>
<!-- 排行榜按钮 -->
<div style="width: 40%; position: relative">
<img
v-smart-img
:src="imageUrl('rankingBtAr')"
alt=""
style="display: block; width: 100%"
:style="{ opacity: rankingShow ? 0 : 1 }"
@click="rankingShow = true"
/>
<img
v-smart-img
:src="imageUrl('rankingBtActiveAr')"
alt=""
style="display: block; width: 100%; position: absolute; top: 0; left: 0"
:style="{ opacity: rankingShow ? 1 : 0 }"
@click="rankingShow = true"
/>
</div>
<!-- 奖励按钮 -->
<div style="width: 40%; position: relative">
<img
v-smart-img
:src="imageUrl('rewardsBtAr')"
alt=""
style="display: block; width: 100%"
:style="{ opacity: rankingShow ? 1 : 0 }"
@click="rankingShow = false"
/>
<img
v-smart-img
:src="imageUrl('rewardBtActiveAr')"
alt=""
style="display: block; width: 100%; position: absolute; top: 0; left: 0"
:style="{ opacity: rankingShow ? 0 : 1 }"
@click="rankingShow = false"
/>
</div>
</div>
</div>
<!-- 展示内容 -->
<div>
<!-- Ranking -->
<div v-show="rankingShow" style="position: relative; width: 100%">
<div style="position: relative; z-index: 2">
<!-- 前三名 -->
<itemCenter
:imgUrl="imageUrl('RankingMain')"
:contentStyle="`padding: 25% 5% 0;flex-direction: column;`"
>
<div>
<!-- 第一 -->
<div style="display: flex; justify-content: center">
<TopUser
:BorderImgUrl="imageUrl('topOne')"
:avatarUrl="RankingHasTop3[0].avatar"
:name="RankingHasTop3[0].nickname"
:distributionValue="RankingHasTop3[0].quantity"
style="width: 55%"
@click="viewUserInfo(RankingHasTop3[0].userId)"
/>
</div>
<!-- 第二三 -->
<div style="display: flex; justify-content: space-between; margin-top: -30%">
<TopUser
:isTopOne="false"
ranking="2"
:BorderImgUrl="imageUrl('topTwo')"
:avatarUrl="RankingHasTop3[1].avatar"
:name="RankingHasTop3[1].nickname"
:distributionValue="RankingHasTop3[1].quantity"
style="width: 34%"
@click="viewUserInfo(RankingHasTop3[1].userId)"
/>
<TopUser
:isTopOne="false"
ranking="3"
:BorderImgUrl="imageUrl('topThree')"
:avatarUrl="RankingHasTop3[2].avatar"
:name="RankingHasTop3[2].nickname"
:distributionValue="RankingHasTop3[2].quantity"
style="width: 34%"
@click="viewUserInfo(RankingHasTop3[2].userId)"
/>
</div>
</div>
</itemCenter>
<!-- 第四名开始 -->
<div
v-if="showRanking.length > 0"
style="margin-bottom: 6vw; position: relative; z-index: 2"
>
<itemCenter
v-for="(listItem, index) in showRanking"
:key="listItem.userId"
:imgUrl="imageUrl('RankingItem')"
:contentStyle="`padding: 2% 13% 0`"
:lazy="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="color: #ffe601; font-weight: 700">
{{ listItem?.rank }}
</div>
<!-- 头像 -->
<img
v-smart-img
:src="listItem?.avatar || ''"
alt=""
style="
display: block;
width: 3.5em;
margin: 0 2%;
aspect-ratio: 1/1;
border-radius: 50%;
object-fit: cover;
"
@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">
ID:{{ 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: -40vw; 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: 12px;padding: 25vw 8vw 6vw;`"
>
<div
v-for="(reward, rewardIndex) in rewardsTop.rewards"
:key="rewardIndex"
style="width: 100%"
>
<itemCenter :imgUrl="imageUrl('giftItemBg')">
<img
v-smart-img
:src="reward.cover || ''"
alt=""
width="70%"
style="display: block; object-fit: cover"
@error="(e) => handleRewardImageError(e, reward.type)"
/>
</itemCenter>
<div
style="
text-align: center;
font-size: 1.2em;
font-weight: 900;
font-style: italic;
background: linear-gradient(
90deg,
#fff677 6.36%,
#d39f1e 26%,
#fff891 49.56%,
#ffc93e 70.51%,
#ffc 93.64%
);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
"
>
{{ showDetail(reward.type, reward) }}
</div>
</div>
</itemCenter>
</div>
</div>
</div>
<!-- 我的排名占位 -->
<div style="height: 30vw"></div>
<!-- 我的排名 -->
<itemCenter
style="position: fixed; bottom: 0; z-index: 2"
:imgUrl="imageUrl('myRankingBg')"
:contentStyle="`padding: 2% 8% 0`"
>
<!-- 基本信息 -->
<div
style="
flex: 1;
min-width: 0;
display: flex;
justify-content: space-around;
align-items: center;
gap: 8px;
"
>
<!-- 排名 -->
<div style="color: #ffe601; font-weight: 700">
{{ myRanking.rank || 999 }}
</div>
<!-- 头像 -->
<img
v-smart-img
:src="myRanking.avatar || ''"
alt=""
style="
display: block;
width: 4em;
aspect-ratio: 1/1;
border-radius: 50%;
object-fit: cover;
"
@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: #ffe601; 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-if="currentLangType == 'en'"
v-smart-img
:src="imageUrl('helpInfo')"
alt=""
style="display: block; width: 100%"
/>
<img
v-if="currentLangType == 'ar'"
v-smart-img
:src="imageUrl('helpInfoAr')"
alt=""
style="display: block; width: 100%"
/>
</div>
</maskLayer>
</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'
// 获取OSS图片URL的函数
const imageUrl = (filename) => getPngUrl('Activities/Game/', filename)
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
const maskLayerShow = ref(false) //帮助模块
const langStore = useLangStore()
// 当前语言类型
const currentLangType = computed(() => {
return langStore.selectedLang?.type || 'en'
})
// 倒计时
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 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)
})
// 获取2025年12月15日上午0点的时间戳北京时间
const getTargetTime = () => {
const targetDate = new Date(2025, 11, 15, 0, 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 if (type === 'DOLLARS') {
return `$${reward.content}`
} else {
return `${reward.quantity}Days`
}
}
// 获取排行榜和我的排名
const getListAndMy = async () => {
let data = {
activityType: 1068,
cycleType: 3,
cycleKey: '202511',
}
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('1994334094730010626')
if (resRewardsAndGifts.status && resRewardsAndGifts.body) {
rewardsList.value = resRewardsAndGifts.body.butOneRewards
if (rewardsList.value[0]) {
rewardsList.value[0].rewards.push({
type: 'DOLLARS',
content: 50,
})
}
if (rewardsList.value[1]) {
rewardsList.value[1].rewards.push({
type: 'DOLLARS',
content: 30,
})
}
if (rewardsList.value[2]) {
rewardsList.value[2].rewards.push({
type: 'DOLLARS',
content: 10,
})
}
}
}
// 刷新页面数据
const initData = () => {
getListAndMy() // 获取排行榜和我的排名
getRewardsAndGifts() //获取本周的礼物和奖励列表
}
// 预加载关键图片
const preloadCriticalImages = () => {
const criticalImages = [
imageUrl('bg'),
imageUrl('timeBg'),
imageUrl('rankingBt'),
imageUrl('rankingBtActive'),
imageUrl('rankingBtAr'),
imageUrl('rankingBtActiveAr'),
imageUrl('rewardBtActive'),
imageUrl('rewardBtActiveAr'),
imageUrl('rewardsBt'),
imageUrl('rewardsBtAr'),
imageUrl('RankingMain'),
imageUrl('topOne'),
imageUrl('topTwo'),
imageUrl('topThree'),
imageUrl('RankingItem'),
imageUrl('RankingBottomBorder'),
imageUrl('myRankingBg'),
imageUrl('helpInfo'),
imageUrl('helpInfoAr'),
imageUrl('top1RewardBg'),
imageUrl('top2RewardBg'),
imageUrl('top3RewardBg'),
imageUrl('giftItemBg'),
]
preloadImages(criticalImages)
}
// 使用工具函数连接APP
const connectToAppHandler = async () => {
await connectToApp(() => {
// 连接成功回调
initData() //初始化数据
preloadCriticalImages() //预加载关键图片
})
}
// 组件挂载时检测环境
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: #2e0649;
}
.bg {
width: 100vw;
min-height: 100vh;
background-image: var(--bg-url);
background-size: 100% auto;
background-repeat: no-repeat;
position: relative;
z-index: 1;
}
.timeText {
color: #fff;
font-weight: 590;
font-size: 1.5em;
margin-bottom: 1vw;
}
.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;
position: absolute;
inset: 10vw 2% 1vw;
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;
}
@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>