feat(新页面): 每日充值活动页面

This commit is contained in:
hzj 2026-04-30 11:36:27 +08:00
parent 8cc2fb87aa
commit 5a73708900
11 changed files with 1151 additions and 8 deletions

View File

@ -62,14 +62,50 @@ export const getHistoryTopOne = async (activityType) => {
}
// 获取指定类型活动资源
export const activityResource = async (data) => {
export const apiGetActivityResource = async (activityType) => {
try {
const response = await get(
`/props-activity-cnf/client/listActivityResource?sysOrigin=${data.sysOrigin}&activityType=${data.activityType}`,
`/props-activity-cnf/client/listActivityResource?sysOrigin=LIKEI&activityType=${activityType}`,
)
return response
} catch (error) {
console.error('Failed to get this week rewards and gifts:', error)
console.error('Failed to get activity resource:', error)
console.error('error:' + error.response.errorMsg)
throw error
}
}
// 获取每日充值活动等级任务信息
export const getDailyRechargeInfo = async () => {
try {
const response = await get('/activity/daily-recharge/info')
return response
} catch (error) {
console.error('Failed to get daily recharge info:', error)
console.error('error:' + error.response.errorMsg)
throw error
}
}
// 领取每日充值活动 task1 历史累计奖励
export const claimDailyRechargeTask1 = async () => {
try {
const response = await post('/activity/daily-recharge/claim/task1')
return response
} catch (error) {
console.error('Failed to claim daily recharge task1 reward:', error)
console.error('error:' + error.response.errorMsg)
throw error
}
}
// 领取每日充值活动 task2 当前等级奖励
export const claimDailyRechargeTask2 = async (data) => {
try {
const response = await post('/activity/daily-recharge/claim/task2', data)
return response
} catch (error) {
console.error('Failed to claim daily recharge task2 reward:', error)
console.error('error:' + error.response.errorMsg)
throw error
}

View File

@ -17,6 +17,7 @@ export const PUBLIC_PATHS = Object.freeze([
'/recharge-reward',
'/login-reward',
'/activities/poker-ace',
'/daily-recharge',
'/activities/lucky-dollars-season4',
'/activities/lesser-bairam',
'/activities/lucky-dollars-season3',

View File

@ -495,5 +495,16 @@
"weekly_rank": "الترتيب الأسبوعي",
"daily_rank": "الترتيب اليومي",
"weekly_rewards": "المكافآت الأسبوعية",
"daily_rewards": "المكافآت اليومية"
"daily_rewards": "المكافآت اليومية",
"active_game_current_daily_recharge_amount": "مبلغ الشحن اليومي الحالي: {amount}.",
"active_game_current_daily_game_spending": "إنفاق الألعاب اليومي الحالي: {coins} عملة.",
"active_game_task1_detail": "1. الهدف اليومي: أنفق ({progress} عملة). افتح مكافأة {reward} عملة ليوم غد!",
"active_game_task2_detail": "2. وصل إلى ({progress}) للمطالبة بالمكافأة الحالية.",
"active_game_level_label": "Lv.{level}",
"active_game_rule_1": "(1) يتم إصدار جميع مكافآت العملات المكتسبة اليوم في اليوم التالي.",
"active_game_rule_2": "(2) يجب المطالبة بالمهمة 2 يدويا.",
"active_game_rule_3": "(3) يجب إكمال المهمتين للانتقال إلى المستوى التالي.",
"active_game_vap_playback_failed": "فشل تشغيل VAP",
"active_game_claim_reward_failed": "فشل المطالبة بالمكافأة"
}

View File

@ -495,5 +495,16 @@
"weekly_rank": "সাপ্তাহিক র‍্যাঙ্ক",
"daily_rank": "দৈনিক র‍্যাঙ্ক",
"weekly_rewards": "সাপ্তাহিক পুরস্কার",
"daily_rewards": "দৈনিক পুরস্কার"
"daily_rewards": "দৈনিক পুরস্কার",
"active_game_current_daily_recharge_amount": "বর্তমান দৈনিক রিচার্জের পরিমাণ: {amount}.",
"active_game_current_daily_game_spending": "বর্তমান দৈনিক গেম খরচ: {coins} কয়েন।",
"active_game_task1_detail": "1. দৈনিক লক্ষ্য: খরচ করুন ({progress} কয়েন)। আগামীকালের জন্য {reward} কয়েন পুরস্কার আনলক করুন!",
"active_game_task2_detail": "2. বর্তমান পুরস্কার দাবি করতে ({progress}) পৌঁছান।",
"active_game_level_label": "Lv.{level}",
"active_game_rule_1": "(1) আজ অর্জিত সব কয়েন পুরস্কার পরের দিন দেওয়া হবে।",
"active_game_rule_2": "(2) টাস্ক 2 ম্যানুয়ালি দাবি করতে হবে।",
"active_game_rule_3": "(3) পরবর্তী লেভেলে যেতে দুটি টাস্কই সম্পন্ন করতে হবে।",
"active_game_vap_playback_failed": "VAP প্লেব্যাক ব্যর্থ হয়েছে",
"active_game_claim_reward_failed": "পুরস্কার দাবি করতে ব্যর্থ হয়েছে"
}

View File

@ -495,5 +495,16 @@
"weekly_rank": "Weekly Rank",
"daily_rank": "Daily Rank",
"weekly_rewards": "Weekly Rewards",
"daily_rewards": "Daily Rewards"
"daily_rewards": "Daily Rewards",
"active_game_current_daily_recharge_amount": "Current daily recharge amount: {amount}.",
"active_game_current_daily_game_spending": "Current daily game spending: {coins} coins.",
"active_game_task1_detail": "1. Daily Goal: Spend ({progress} coins). Unlock a {reward} coin reward for tomorrow!",
"active_game_task2_detail": "2. Reach ({progress}) to claim the current reward.",
"active_game_level_label": "Lv.{level}",
"active_game_rule_1": "(1) All coin rewards earned today are issued the next day.",
"active_game_rule_2": "(2) Task 2 must be claimed manually.",
"active_game_rule_3": "(3) You must complete both tasks to proceed to the next level.",
"active_game_vap_playback_failed": "VAP playback failed",
"active_game_claim_reward_failed": "Failed to claim reward"
}

View File

@ -495,5 +495,16 @@
"weekly_rank": "Haftalık Sıralama",
"daily_rank": "Günlük Sıralama",
"weekly_rewards": "Haftalık Ödüller",
"daily_rewards": "Günlük Ödüller"
"daily_rewards": "Günlük Ödüller",
"active_game_current_daily_recharge_amount": "Mevcut günlük yükleme tutarı: {amount}.",
"active_game_current_daily_game_spending": "Mevcut günlük oyun harcaması: {coins} coin.",
"active_game_task1_detail": "1. Günlük Hedef: Harca ({progress} coin). Yarın için {reward} coin ödülünü aç!",
"active_game_task2_detail": "2. Mevcut ödülü almak için ({progress}) seviyesine ulaş.",
"active_game_level_label": "Lv.{level}",
"active_game_rule_1": "(1) Bugün kazanılan tüm coin ödülleri ertesi gün verilir.",
"active_game_rule_2": "(2) Görev 2 manuel olarak alınmalıdır.",
"active_game_rule_3": "(3) Sonraki seviyeye geçmek için iki görevi de tamamlamalısınız.",
"active_game_vap_playback_failed": "VAP oynatma başarısız",
"active_game_claim_reward_failed": "Ödül alınamadı"
}

View File

@ -495,5 +495,16 @@
"weekly_rank": "周榜",
"daily_rank": "日榜",
"weekly_rewards": "周榜奖励",
"daily_rewards": "日榜奖励"
"daily_rewards": "日榜奖励",
"active_game_current_daily_recharge_amount": "当前每日充值金额:{amount}。",
"active_game_current_daily_game_spending": "当前每日游戏消费:{coins} 金币。",
"active_game_task1_detail": "1. 每日目标:消费({progress} 金币)。解锁 {reward} 金币奖励,奖励将在明天发放!",
"active_game_task2_detail": "2. 达到({progress})即可领取当前奖励。",
"active_game_level_label": "Lv.{level}",
"active_game_rule_1": "1今天获得的所有金币奖励将在次日发放。",
"active_game_rule_2": "2任务 2 需要手动领取。",
"active_game_rule_3": "3必须完成两个任务才能进入下一等级。",
"active_game_vap_playback_failed": "VAP 播放失败",
"active_game_claim_reward_failed": "领取奖励失败"
}

View File

@ -395,6 +395,12 @@ const router = createRouter({
},
// 限时活动
{
path: '/activities/active-game',
name: 'active-game',
component: () => import('../views/Activities/ActiveGame/index.vue'),
meta: { requiresAuth: true },
}, //2026年5月1日上午5点结束
{
path: '/activities/poker-ace',
name: 'pokerAce',

View File

@ -61,6 +61,7 @@ const ACTIVITIES = [
'/login-reward', // 登录奖励
'/activities/poker-ace', // 扑克王牌排行榜
'/daily-recharge', // 每日充值页面
'/activities/lucky-dollars-season4', // 幸运美金活动
'/activities/lesser-bairam', // 开斋节打榜
'/activities/lucky-dollars-season3', // 斋月打榜

View File

@ -0,0 +1,987 @@
<template>
<div class="fullPage">
<!-- 预加载状态 -->
<div v-if="isLoading" class="loading-container">
<!-- 页面背景 -->
<BackgroundLayer :useCanvas="true" :backgroundImages="[imageUrl('bg')]" />
<!-- 加载动画 -->
<LoadingSpinner />
<p>{{ t('loading') }}...</p>
</div>
<!-- 主要内容 -->
<div class="active-game-shell scroll">
<div class="active-game-stage">
<!-- 头部动画带声音 -->
<VapBg class="vap-background-layer" :src="bgVapUrl" />
<!-- 页面背景 -->
<div class="page-background-layer">
<BackgroundLayer
:useCanvas="true"
:backgroundImages="[imageUrl('bg')]"
:layerStyle="{ position: 'absolute', top: 0, left: 0, minHeight: 0, zIndex: 0 }"
/>
</div>
<!-- 页面内容 -->
<div class="page-content">
<!-- 累计奖励 -->
<itemCenter
style="padding: 0 3vw"
:imgUrl="imageUrl('totalRewardBg')"
:contentStyle="`padding:0 7vw;gap:1vw`"
>
<img
v-smart-img
src="/src/assets/icon/Azizi/coin.png"
alt=""
style="display: block; width: 10vw; aspect-ratio: 1/1"
/>
<div
style="
flex: 1;
border-radius: 88px;
background: linear-gradient(180deg, #750a00 0%, #c71f10 48.08%, #750a00 100%);
padding: 1vw 6px;
display: flex;
justify-content: center;
align-items: center;
"
>
<div
style="
background: linear-gradient(180deg, #ffeeb0 0%, #ffcc46 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 1.2em;
font-weight: 500;
"
>
{{ taskInfo.task1PendingCoins }}
</div>
</div>
<img
v-smart-img
:src="imageUrl(getImgName('receiveBt'))"
alt=""
style="display: block; width: 25vw"
:style="{ filter: canClaimTask1Reward ? 'none' : 'grayscale(1)' }"
@click="claimTask1Reward"
/>
</itemCenter>
<!-- 等级任务 -->
<itemCenter
style=""
:imgUrl="imageUrl('lvTaskBg')"
:contentStyle="`
flex-direction: column;
justify-content: flex-start;
padding: 10vw 8vw 5vw;
gap: 8vw;
`"
>
<!-- 标题 -->
<div style="display: flex; justify-content: center; align-items: center; height: 11vw">
<img
v-smart-img
:src="imageUrl(`titleLv${currentLevel}`)"
alt=""
style="width: 18vw"
/>
</div>
<!-- 内容 -->
<div style="width: 100%; display: flex; flex-direction: column; position: relative">
<!-- 规则弹窗展示按钮 -->
<img
v-smart-img
src="/src/assets/icon/Azizi/helpWhite.png"
alt=""
class="ruleBt"
@click="openPopup('rule')"
/>
<!-- 信息展示 -->
<div style="height: 45vw; display: flex; flex-direction: column; gap: 5vw">
<!-- 当前状态 -->
<div style="display: flex; align-items: center">
<div
style="
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2vw;
position: relative;
"
>
<div>
{{
t('active_game_current_daily_recharge_amount', {
amount: displayedRechargeAmountText,
})
}}
</div>
<div>
{{
t('active_game_current_daily_game_spending', {
coins: displayedSpendCoinsText,
})
}}
</div>
</div>
<!-- 展示隐藏按钮 -->
<img
v-smart-img
:src="imageUrl(amountVisible ? 'amountShowBt' : 'amountHiddenBt')"
alt=""
style="width: 5vw; min-width: 0; display: block"
@click="toggleAmountVisible"
/>
</div>
<!-- 任务详情 -->
<div style="display: flex; flex-direction: column; gap: 2vw">
<!-- 任务一 -->
<div :style="{ color: task1Completed ? '#1AFF00' : '#fff' }">
{{
t('active_game_task1_detail', {
progress: task1ProgressText,
reward: currentTask1RewardCoinsText,
})
}}
<img
v-if="task1Completed"
v-smart-img
:src="imageUrl('done')"
alt=""
style="display: inline-block; width: 1em; object-fit: contain"
/>
</div>
<!-- 任务二 -->
<div :style="{ color: taskInfo.task2Completed ? '#1AFF00' : '#fff' }">
{{
t('active_game_task2_detail', {
progress: task2ProgressText,
})
}}
<img
v-if="taskInfo.task2Completed"
v-smart-img
:src="imageUrl('done')"
alt=""
style="display: inline-block; width: 1em; object-fit: contain"
/>
</div>
</div>
</div>
<!-- 宝箱展示 -->
<div style="display: flex; flex-direction: column; align-items: center">
<img
v-smart-img
:src="imageUrl(currentChestImageName)"
alt=""
style="width: 38vw; display: block; object-fit: contain"
/>
<!-- 宝箱等级 -->
<div style="font-size: 1.5em; font-weight: 700">
{{ t('active_game_level_label', { level: currentLevel }) }}
</div>
<!-- 领取按钮 -->
<img
v-smart-img
:src="imageUrl(getImgName(taskRewardButtonImageName))"
alt=""
style="width: 30vw; display: block; object-fit: contain"
:style="{ filter: taskRewardButtonDisabled ? 'grayscale(1)' : 'none' }"
@click="claimTask2Reward"
/>
</div>
</div>
</itemCenter>
<!-- 等级奖励 -->
<itemCenter
style=""
:imgUrl="imageUrl('LvRewardInfoBg')"
:contentStyle="` flex-direction: column;
justify-content: flex-start;
padding: 8vw 7.5vw 14vw;
gap: 4vw;`"
>
<!-- 标题 -->
<div style="display: flex; justify-content: center; align-items: center; height: 11vw">
<img
v-smart-img
:src="imageUrl(getImgName('LvRewardTitle'))"
alt=""
style="width: 40vw"
/>
</div>
<!-- 内容 -->
<div
style="
flex: 1;
min-height: 0;
width: 100%;
display: flex;
flex-direction: column;
gap: 2.5vw;
position: relative;
"
>
<!-- 开箱进度条 -->
<div style="width: 100%; overflow-x: auto" class="scroll">
<div class="chest-progress-row">
<!-- 进度条 -->
<div class="chest-progress-track" :style="progressTrackStyle">
<!-- 进度条图片 -->
<img
v-smart-img
:src="imageUrl('progressBar')"
alt=""
class="chest-progress-bar"
:style="progressBarStyle"
/>
</div>
<!-- 宝箱列表 -->
<div class="chest-progress-list">
<!-- 开头占位 -->
<div></div>
<div
v-for="chest in chestProgressList"
:key="chest.level"
style="width: 14vw; cursor: pointer"
@click="selectRewardChest(chest.level)"
>
<!-- 未达成条件 -->
<img
v-if="!isChestConditionReached(chest.level)"
v-smart-img
:src="imageUrl(getChestImageName(chest))"
alt=""
style="display: block; width: 100%; object-fit: contain"
/>
<!-- 达成条件 -->
<itemCenter
v-else
style="width: 100%"
:imgUrl="imageUrl('LvActive')"
:contentStyle="``"
>
<img
v-smart-img
:src="imageUrl(getChestImageName(chest))"
alt=""
style="display: block; width: 100%; object-fit: contain"
/>
</itemCenter>
</div>
</div>
</div>
</div>
<!-- 奖励展示 -->
<div class="reward-showcase">
<div v-if="mainReward" class="reward-showcase-main">
<div
style="
width: 100%;
height: 71vw;
display: flex;
align-items: center;
justify-content: center;
"
>
<div class="reward-showcase-vap-wrap">
<VapMp4Player
v-if="mainReward.sourceUrl"
:model-value="true"
:src="mainReward.sourceUrl"
mode="inline"
fit="contain"
:loop="true"
:mute="true"
:auto-close="false"
:show-close="false"
:show-status="false"
@error="handleRewardVapError"
/>
<img
v-else
:src="mainReward.cover || ''"
alt=""
style="display: block; width: 90%; object-fit: contain"
@error="(e) => handleRewardImageError(e, mainReward.type)"
/>
</div>
</div>
<div
style="
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
"
>
<div>{{ formatRewardDisplay(mainReward.type, mainReward) }}</div>
</div>
</div>
<div class="reward-showcase-grid">
<div
v-for="(reward, index) in rewardGridList"
:key="index"
class="reward-showcase-item"
>
<div
style="
width: 100%;
aspect-ratio: 1/1;
display: flex;
align-items: center;
justify-content: center;
"
>
<img
:src="reward.cover || ''"
alt=""
style="display: block; width: 90%; object-fit: contain"
@error="(e) => handleRewardImageError(e, reward.type)"
/>
</div>
<div
style="
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
"
>
<div>{{ formatRewardDisplay(reward.type, reward) }}</div>
</div>
</div>
</div>
</div>
</div>
</itemCenter>
</div>
<!-- 弹窗遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<!-- 居中弹窗 -->
<div
style="
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
"
@click="closedPopup"
>
<!-- 规则弹窗 -->
<itemCenter
style=""
:imgUrl="imageUrl('ruleInfoBg')"
:contentStyle="` flex-direction: column;
justify-content: flex-start;
padding: 8vw 7.5vw 5vw;
gap: 4vw;`"
>
<!-- 标题 -->
<div
style="display: flex; justify-content: center; align-items: center; height: 11vw"
>
<img
v-smart-img
:src="imageUrl(getImgName('ruleTitle'))"
alt=""
style="width: 18vw"
/>
</div>
<div
style="
flex: 1;
min-height: 0;
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
font-size: 1.2em;
"
>
<div style="font-size: 1em; color: #fff; font-weight: 700">
{{ t('active_game_rule_1') }}
</div>
<div style="font-size: 1em; color: #fff; font-weight: 700">
{{ t('active_game_rule_2') }}
</div>
<div style="font-size: 1em; color: #fff; font-weight: 700">
{{ t('active_game_rule_3') }}
</div>
</div>
</itemCenter>
</div>
</maskLayer>
</div>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { setDocumentDirection } from '@/locales/i18n'
import { connectToApp } from '@/utils/appConnector.js'
import { getMp4Url, getPngUrl } from '@/config/imagePaths.js'
import { preloadImages } from '@/utils/image/imagePreloader.js'
import { useLangStore } from '@/stores/lang'
import { showError } from '@/utils/toast.js'
import { preloadVapConfig } from '@/utils/vapMp4Config.js'
import {
apiGetActivityResource,
claimDailyRechargeTask1,
claimDailyRechargeTask2,
getDailyRechargeInfo,
} from '@/api/activity.js'
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'
import { handleRewardImageError } from '@/utils/image/imageHandler.js'
import { formatRewardDisplay } from '@/utils/rewardFormatter.js'
import { useThrottle } from '@/utils/useDebounce.js'
import BackgroundLayer from '@/components/BackgroundLayer.vue'
import itemCenter from '@/components/itemCenter.vue'
import maskLayer from '@/components/MaskLayer.vue'
import VapMp4Player from '@/components/VapMp4Player.vue'
import VapBg from './vapBg.vue'
const isLoading = ref(true)
const { t, locale } = useI18n()
const langStore = useLangStore()
const currentLangType = computed(() => {
return langStore.selectedLang?.type || 'en'
})
const isRtlLayout = computed(() => {
return currentLangType.value === 'ar'
})
locale.value && setDocumentDirection(locale.value)
const imageUrl = (filename) => getPngUrl('Activities/ActiveGame/', filename)
const mp4Url = (filename) => getMp4Url('Activities/ActiveGame/', filename)
const bgVapUrl = mp4Url('bg')
const CHEST_PROGRESS_GAP_VW = 2
const CHEST_PROGRESS_ITEM_WIDTH_VW = 14
const CHEST_PROGRESS_FIRST_CENTER_VW = CHEST_PROGRESS_GAP_VW + CHEST_PROGRESS_ITEM_WIDTH_VW / 2
const CHEST_PROGRESS_CENTER_STEP_VW = CHEST_PROGRESS_GAP_VW + CHEST_PROGRESS_ITEM_WIDTH_VW
const CHEST_PROGRESS_TRACK_WIDTH_VW =
10 * CHEST_PROGRESS_GAP_VW + 10 * CHEST_PROGRESS_ITEM_WIDTH_VW - CHEST_PROGRESS_ITEM_WIDTH_VW / 2
//
const getImgName = (filename) => {
return currentLangType.value == 'en' ? filename : `${filename}_${currentLangType.value}`
}
const ruleShow = ref(false)
const amountVisible = ref(false)
const claimingTask1Reward = ref(false)
const claimingTask2Reward = ref(false)
const selectedRewardLevel = ref(1)
const activityRewards = ref([])
const createDefaultTaskInfo = () => ({
task1SpendCoins: 0,
task1MaxLevel: 0,
task1TodayRewardCoins: 0,
task1PendingCoins: 0,
task2RechargeAmount: 0,
task2TargetAmount: 0,
task2Completed: false,
task2Claimed: false,
task2RewardCoins: 0,
levelList: [],
})
const taskInfo = ref(createDefaultTaskInfo())
const maskLayerShow = computed(() => {
return ruleShow.value
})
const normalizeLevel = (levelValue) => {
const level = Number(levelValue) || 1
return Math.min(Math.max(level, 1), 10)
}
const levelMap = computed(() => {
return new Map(taskInfo.value.levelList.map((item) => [Number(item.level), item]))
})
const currentLevel = computed(() => {
return normalizeLevel(taskInfo.value.task1MaxLevel || 1)
})
const currentLevelInfo = computed(() => {
return levelMap.value.get(currentLevel.value) || {}
})
const currentChestImageName = computed(() => {
return `Lv${currentLevel.value}${currentLevelInfo.value.reached ? '_open' : ''}`
})
const canClaimTask1Reward = computed(() => {
return Number(taskInfo.value.task1PendingCoins) > 0 && !claimingTask1Reward.value
})
const currentTask2RewardTop = computed(() => {
return activityRewards.value[currentLevel.value - 1] || {}
})
const canClaimTask2Reward = computed(() => {
return (
!taskInfo.value.task2Claimed &&
task1Completed.value &&
taskInfo.value.task2Completed &&
Boolean(currentTask2RewardTop.value?.rule?.id) &&
!claimingTask2Reward.value
)
})
const taskRewardButtonDisabled = computed(() => {
return !taskInfo.value.task2Claimed && !canClaimTask2Reward.value
})
const taskRewardButtonImageName = computed(() => {
return taskInfo.value.task2Claimed ? 'receivedBt' : 'receiveBt'
})
const chestProgressList = computed(() => {
return Array.from({ length: 10 }, (_, index) => {
const level = index + 1
const levelInfo = levelMap.value.get(level) || {}
return {
level,
reached: Boolean(levelInfo.reached),
}
})
})
const progressBarWidth = computed(() => {
const completedLevelCount = Math.min(Math.max(currentLevel.value - 1, 0), 9)
const completedWidth =
completedLevelCount === 0
? 0
: CHEST_PROGRESS_FIRST_CENTER_VW + (completedLevelCount - 1) * CHEST_PROGRESS_CENTER_STEP_VW
const currentLevelTaskCount =
(task1Completed.value ? 1 : 0) + (taskInfo.value.task2Completed ? 1 : 0)
const currentLevelProgressDistance =
completedLevelCount === 0 ? CHEST_PROGRESS_FIRST_CENTER_VW : CHEST_PROGRESS_CENTER_STEP_VW
const currentLevelWidth = (currentLevelProgressDistance * currentLevelTaskCount) / 2
const progressWidth = Math.min(completedWidth + currentLevelWidth, CHEST_PROGRESS_TRACK_WIDTH_VW)
return `${progressWidth}vw`
})
const progressTrackStyle = computed(() => {
return {
width: `${CHEST_PROGRESS_TRACK_WIDTH_VW}vw`,
left: isRtlLayout.value ? 'auto' : '0',
right: isRtlLayout.value ? '0' : 'auto',
justifyContent: isRtlLayout.value ? 'flex-end' : 'flex-start',
}
})
const progressBarStyle = computed(() => {
return {
width: progressBarWidth.value,
transform: isRtlLayout.value ? 'scaleX(-1)' : 'none',
}
})
const task1Completed = computed(() => {
return Boolean(currentLevelInfo.value.reached)
})
const currentTask1TargetCoins = computed(() => {
return currentLevelInfo.value.spendCoinsTarget || 0
})
const currentTask1RewardCoins = computed(() => {
return currentLevelInfo.value.rewardCoins || taskInfo.value.task1TodayRewardCoins || 0
})
const displayedSpendCoins = computed(() => {
return amountVisible.value ? taskInfo.value.task1SpendCoins : '***'
})
const displayedRechargeAmount = computed(() => {
return amountVisible.value ? taskInfo.value.task2RechargeAmount : '***'
})
const isolateLtrText = (value) => {
return `\u2066${value}\u2069`
}
const displayedSpendCoinsText = computed(() => {
return isolateLtrText(displayedSpendCoins.value)
})
const displayedRechargeAmountText = computed(() => {
return isolateLtrText(`$${displayedRechargeAmount.value}`)
})
const currentTask1RewardCoinsText = computed(() => {
return isolateLtrText(currentTask1RewardCoins.value)
})
const task1ProgressText = computed(() => {
return isolateLtrText(`${displayedSpendCoins.value}/${currentTask1TargetCoins.value}`)
})
const task2ProgressText = computed(() => {
return isolateLtrText(`$${displayedRechargeAmount.value}/$${taskInfo.value.task2TargetAmount}`)
})
const selectedRewardTop = computed(() => {
return activityRewards.value[selectedRewardLevel.value - 1] || {}
})
const selectedRewardList = computed(() => {
return selectedRewardTop.value?.propsGroup?.activityRewardProps || []
})
const mainReward = computed(() => {
return selectedRewardList.value[0] || null
})
const rewardGridList = computed(() => {
return selectedRewardList.value.slice(1, 7)
})
const toggleAmountVisible = () => {
amountVisible.value = !amountVisible.value
}
const selectRewardChest = (level) => {
selectedRewardLevel.value = normalizeLevel(level)
}
const isChestConditionReached = (level) => {
return Boolean(levelMap.value.get(Number(level))?.reached)
}
const getChestImageName = (chest) => {
return `Lv${chest.level}${chest.reached ? '_open' : ''}`
}
const closedPopup = () => {
ruleShow.value = false
}
const openPopup = (type) => {
closedPopup()
switch (type) {
case 'rule':
ruleShow.value = true
break
default:
break
}
}
const handleRewardVapError = (error) => {
console.error('Reward VAP playback failed:', error)
showError(error?.message || t('active_game_vap_playback_failed'))
}
const claimTask1Reward = useThrottle(async () => {
if (!canClaimTask1Reward.value) {
return
}
claimingTask1Reward.value = true
try {
const res = await claimDailyRechargeTask1()
if (res.status) {
await getDailyRechargeTaskInfo()
}
} catch (error) {
showError(error?.errorMsg || error?.message || t('active_game_claim_reward_failed'))
} finally {
claimingTask1Reward.value = false
}
}, 1000)
const claimTask2Reward = useThrottle(async () => {
if (!canClaimTask2Reward.value) {
return
}
claimingTask2Reward.value = true
try {
const res = await claimDailyRechargeTask2({
activityType: 'DAILY_RECHARGE_TASK2',
ruleId: currentTask2RewardTop.value.rule.id,
})
if (res.status) {
await getDailyRechargeTaskInfo()
}
} catch (error) {
showError(error?.errorMsg || error?.message || t('active_game_claim_reward_failed'))
} finally {
claimingTask2Reward.value = false
}
}, 1000)
const getDailyRechargeTaskInfo = async () => {
const res = await getDailyRechargeInfo()
if (res.status && res.body) {
taskInfo.value = {
...createDefaultTaskInfo(),
...res.body,
levelList: Array.isArray(res.body.levelList) ? res.body.levelList : [],
}
selectedRewardLevel.value = normalizeLevel(res.body.task1MaxLevel || 1)
}
}
const getActivityRewards = async () => {
const res = await apiGetActivityResource('DAILY_RECHARGE_TASK2')
if (res.status && res.body) {
activityRewards.value = Array.isArray(res.body) ? res.body : []
}
}
const initData = async () => {
await Promise.all([getDailyRechargeTaskInfo(), getActivityRewards()])
}
const preloadCriticalImages = async () => {
const chestImages = chestProgressList.value.map((chest) => imageUrl(getChestImageName(chest)))
const criticalImages = [
imageUrl('bg'),
imageUrl('totalRewardBg'),
imageUrl('lvTaskBg'),
imageUrl('LvRewardInfoBg'),
imageUrl('ruleInfoBg'),
imageUrl(`titleLv${currentLevel.value}`),
imageUrl(currentChestImageName.value),
imageUrl('amountShowBt'),
imageUrl('amountHiddenBt'),
imageUrl('done'),
imageUrl('LvActive'),
...chestImages,
imageUrl(getImgName('LvRewardTitle')),
imageUrl(getImgName('receiveBt')),
imageUrl(getImgName('receivedBt')),
imageUrl(getImgName('ruleTitle')),
]
await Promise.all([
preloadImages(criticalImages),
preloadVapConfig(resolveProtectedAssetUrl(bgVapUrl)),
])
}
const completePreloading = async () => {
try {
await initData()
await preloadCriticalImages()
} catch (error) {
console.error('预加载过程中发生错误:', error)
} finally {
isLoading.value = false
}
}
const connectToAppHandler = async () => {
await connectToApp(() => {
completePreloading()
})
}
onMounted(() => {
connectToAppHandler()
})
</script>
<style lang="scss" scoped>
* {
color: #fff;
}
.fullPage {
height: 100vh;
position: relative;
z-index: 0;
background: #220e08;
}
.loading-container {
width: 100vw;
height: 100vh;
position: fixed;
inset: 0;
z-index: 10;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
background: #220e08;
}
.active-game-shell {
width: 100vw;
height: 100vh;
overflow-y: auto;
}
.active-game-stage {
width: 100vw;
min-height: 100vh;
position: relative;
isolation: isolate;
}
.page-background-layer {
position: absolute;
top: 120vw;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
overflow: hidden;
pointer-events: none;
}
.vap-background-layer {
width: 100vw;
height: 150vw;
position: absolute;
top: 0;
left: 0;
z-index: 1;
pointer-events: none;
}
.page-content {
position: relative;
z-index: 2;
padding: 132vw 2vw 10vw;
}
.chest-progress-row {
position: relative;
z-index: 0;
display: flex;
align-items: center;
width: max-content;
}
.chest-progress-track {
position: absolute;
z-index: -1;
height: 3vw;
display: flex;
align-items: center;
background: rgba(103, 41, 0, 0.7);
}
.chest-progress-bar {
display: block;
height: 3vw;
object-fit: cover;
transform-origin: center;
}
.chest-progress-list {
display: flex;
align-items: center;
gap: 2vw;
}
.reward-showcase {
flex: 1;
min-height: 0;
width: 100%;
display: flex;
gap: 3vw;
direction: ltr;
unicode-bidi: isolate;
}
.reward-showcase-main,
.reward-showcase-item {
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.reward-showcase-main {
width: 33vw;
height: 100%;
}
.reward-showcase-vap-wrap {
width: 90%;
height: 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.reward-showcase-grid {
flex: 1;
min-width: 0;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(3, 1fr);
column-gap: 3vw;
direction: ltr;
}
.reward-showcase-text {
color: #fff;
font-size: 1em;
text-align: center;
}
.scroll::-webkit-scrollbar {
display: none;
}
[dir='rtl'] .flipImg {
transform: scaleX(-1);
}
.ruleBt {
width: 5vw;
min-width: 0;
display: block;
position: absolute;
top: -5vw;
right: 0px;
}
[dir='rtl'] .ruleBt {
left: 0px;
right: auto;
}
@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;
}
}
</style>

View File

@ -0,0 +1,57 @@
<template>
<div class="active-game-page">
<VapMp4Player
v-model="backgroundVisible"
class="vap-background"
:src="src"
mode="inline"
fit="cover"
:loop="true"
:mute="false"
:auto-close="false"
:show-status="false"
@error="handleVapError"
/>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { showError } from '@/utils/toast.js'
import VapMp4Player from '@/components/VapMp4Player.vue'
import { preloadVapConfig } from '@/utils/vapMp4Config.js'
import { resolveProtectedAssetUrl } from '@/utils/protectedAssets.js'
const props = defineProps({
src: {
type: String,
default: '',
},
})
const backgroundVisible = ref(true)
function preloadTestVap() {
if (props.src) {
preloadVapConfig(resolveProtectedAssetUrl(props.src))
}
}
function handleVapError(error) {
console.error('VAP playback failed:', error)
showError(error?.message || 'VAP playback failed')
}
onMounted(() => {
preloadTestVap()
})
</script>
<style lang="scss" scoped>
.active-game-page {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>