feat(新活动): 足球游戏活动
This commit is contained in:
parent
7d5d2fb62d
commit
bf4e057107
@ -20,6 +20,7 @@ export const PUBLIC_PATHS = Object.freeze([
|
|||||||
'/activities/poker-ace',
|
'/activities/poker-ace',
|
||||||
'/daily-recharge',
|
'/daily-recharge',
|
||||||
'/activities/gulben-festival',
|
'/activities/gulben-festival',
|
||||||
|
'/activities/bounty-football',
|
||||||
'/activities/lucky-dollars-season5',
|
'/activities/lucky-dollars-season5',
|
||||||
'/activities/lucky-dollars-season4',
|
'/activities/lucky-dollars-season4',
|
||||||
'/activities/lesser-bairam',
|
'/activities/lesser-bairam',
|
||||||
|
|||||||
@ -487,5 +487,12 @@
|
|||||||
"gulben_room_rule_title": "قواعد النشاط:",
|
"gulben_room_rule_title": "قواعد النشاط:",
|
||||||
"gulben_room_rule_1": "1. سيتم احتساب إيرادات غرفتك خلال النشاط. بعد انتهاء النشاط، ستحصل أفضل 10 غرف في الترتيب على مكافآت سخية من العملات و VIP بناء على مستوى إيراداتها.",
|
"gulben_room_rule_1": "1. سيتم احتساب إيرادات غرفتك خلال النشاط. بعد انتهاء النشاط، ستحصل أفضل 10 غرف في الترتيب على مكافآت سخية من العملات و VIP بناء على مستوى إيراداتها.",
|
||||||
"gulben_room_rule_2": "2. هذا الحدث غير مرتبط ب iPhone أو Google.",
|
"gulben_room_rule_2": "2. هذا الحدث غير مرتبط ب iPhone أو Google.",
|
||||||
"gulben_room_rule_3": "3. تحتفظ الجهة الرسمية بحق التفسير النهائي لهذا الحدث."
|
"gulben_room_rule_3": "3. تحتفظ الجهة الرسمية بحق التفسير النهائي لهذا الحدث.",
|
||||||
|
"bounty_football_help_rule_1": "1. كلما جمع المستخدم المزيد من العملات خلال الحدث، ارتفع ترتيبه.",
|
||||||
|
"bounty_football_help_rule_2": "2. سيستمر الحدث لمدة 15 يوما. بعد انتهاء الحدث، سيتم توزيع المكافآت على أفضل ثلاثة مستخدمين في لوحة الترتيب.",
|
||||||
|
"bounty_football_help_rule_3": "3. يجب على أفضل ثلاثة مستخدمين التواصل مع الموظفين الرسميين في الوقت المناسب لمناقشة كيفية الحصول على مكافأة الدولار الأمريكي.",
|
||||||
|
"bounty_football_help_rule_3_withdrawal": "(1) السحب: يرجى تقديم ID حساب استلام USDT أو رمز QR الخاص بالاستلام.",
|
||||||
|
"bounty_football_help_rule_3_no_withdrawal": "(2) عدم السحب: إذا تم تحويلها إلى عملات، فسنمنحك 5% عملات ذهبية إضافية فوق المبلغ الأصلي ($1:10500 coins).",
|
||||||
|
"bounty_football_help_rule_4": "4. قواعد ID: يمكن للمستخدمين التواصل مع الموظفين الرسميين من خلال تقديم لقطة شاشة لترتيبهم ورقم ID المحدد لاستخدامه (على سبيل المثال: عندما تكون المكافأة \"ID: ABBBBA\"، يمكن للمستخدم تقديم \"ID: 100001\" كمعرف خاص به، أو منح حق الاستخدام لشخص آخر).",
|
||||||
|
"bounty_football_help_note": "*يرجى ملاحظة أن ID يجب ألا يكون مستخدما حاليا من قبل أي شخص آخر."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -487,5 +487,12 @@
|
|||||||
"gulben_room_rule_title": "অ্যাক্টিভিটির নিয়ম:",
|
"gulben_room_rule_title": "অ্যাক্টিভিটির নিয়ম:",
|
||||||
"gulben_room_rule_1": "1. অ্যাক্টিভিটি চলাকালীন আপনার রুমের আয় গণনা করা হবে। অ্যাক্টিভিটি শেষ হওয়ার পরে, শীর্ষ 10 র্যাঙ্ক করা রুম তাদের আয়ের স্তর অনুযায়ী উদার কয়েন এবং VIP পুরস্কার পাবে।",
|
"gulben_room_rule_1": "1. অ্যাক্টিভিটি চলাকালীন আপনার রুমের আয় গণনা করা হবে। অ্যাক্টিভিটি শেষ হওয়ার পরে, শীর্ষ 10 র্যাঙ্ক করা রুম তাদের আয়ের স্তর অনুযায়ী উদার কয়েন এবং VIP পুরস্কার পাবে।",
|
||||||
"gulben_room_rule_2": "2. এই ইভেন্টটি iPhone বা Google-এর সাথে সম্পর্কিত নয়।",
|
"gulben_room_rule_2": "2. এই ইভেন্টটি iPhone বা Google-এর সাথে সম্পর্কিত নয়।",
|
||||||
"gulben_room_rule_3": "3. এই ইভেন্টের চূড়ান্ত ব্যাখ্যার অধিকার অফিসিয়াল পক্ষ সংরক্ষণ করে।"
|
"gulben_room_rule_3": "3. এই ইভেন্টের চূড়ান্ত ব্যাখ্যার অধিকার অফিসিয়াল পক্ষ সংরক্ষণ করে।",
|
||||||
|
"bounty_football_help_rule_1": "১. ইভেন্ট চলাকালীন একজন ব্যবহারকারী যত বেশি কয়েন জমা করবেন, তার র্যাঙ্কিং তত বেশি হবে।",
|
||||||
|
"bounty_football_help_rule_2": "২. ইভেন্টটি ১৫ দিন চলবে। ইভেন্ট শেষ হওয়ার পরে, লিডারবোর্ডের শীর্ষ তিন ব্যবহারকারীকে পুরস্কার বিতরণ করা হবে।",
|
||||||
|
"bounty_football_help_rule_3": "৩. শীর্ষ তিন ব্যবহারকারীকে USD পুরস্কার কীভাবে গ্রহণ করবেন তা আলোচনা করতে সময়মতো অফিসিয়াল স্টাফের সাথে যোগাযোগ করতে হবে।",
|
||||||
|
"bounty_football_help_rule_3_withdrawal": "(১) উত্তোলন: অনুগ্রহ করে আপনার USDT গ্রহণকারী অ্যাকাউন্ট ID বা গ্রহণকারী QR কোড প্রদান করুন।",
|
||||||
|
"bounty_football_help_rule_3_no_withdrawal": "(২) উত্তোলন নয়: কয়েনে রূপান্তর করলে, আমরা মূল পরিমাণের ওপর অতিরিক্ত ৫% গোল্ড কয়েন দেব ($1:10500 coins)।",
|
||||||
|
"bounty_football_help_rule_4": "৪.ID নিয়ম: ব্যবহারকারীরা নিজেদের র্যাঙ্কিংয়ের স্ক্রিনশট এবং নির্বাচিত ID নম্বর প্রদান করে অফিসিয়াল স্টাফের সাথে যোগাযোগ করে এটি ব্যবহার করতে পারেন (যেমন: পুরস্কার যদি \"ID: ABBBBA\" হয়, ব্যবহারকারী \"ID: 100001\" নিজের ID হিসেবে দিতে পারেন, অথবা অন্য কাউকে ব্যবহারের অধিকার দিতে পারেন)।",
|
||||||
|
"bounty_football_help_note": "*অনুগ্রহ করে মনে রাখবেন, ID বর্তমানে অন্য কেউ ব্যবহার করছে এমন হওয়া যাবে না।"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -487,5 +487,12 @@
|
|||||||
"gulben_room_rule_title": "Activity Rules:",
|
"gulben_room_rule_title": "Activity Rules:",
|
||||||
"gulben_room_rule_1": "1.Your room's revenue will be tallied during the activity. After the activity ends, the top 10 ranked rooms will receive generous coins and VIP rewards based on their revenue level.",
|
"gulben_room_rule_1": "1.Your room's revenue will be tallied during the activity. After the activity ends, the top 10 ranked rooms will receive generous coins and VIP rewards based on their revenue level.",
|
||||||
"gulben_room_rule_2": "2. This event is not related to iPhone or Google.",
|
"gulben_room_rule_2": "2. This event is not related to iPhone or Google.",
|
||||||
"gulben_room_rule_3": "3. The official party reserves the final right of interpretation for this event."
|
"gulben_room_rule_3": "3. The official party reserves the final right of interpretation for this event.",
|
||||||
|
"bounty_football_help_rule_1": "1. The more coins a user accumulates during the event, the higher their ranking will be.",
|
||||||
|
"bounty_football_help_rule_2": "2. The event will last for 15 days. After the event ends, rewards will be distributed to the top three users on the leaderboard.",
|
||||||
|
"bounty_football_help_rule_3": "3. The top three users should contact the official staff in a timely manner to discuss how to obtain the USD reward.",
|
||||||
|
"bounty_football_help_rule_3_withdrawal": "(1) Withdrawal: Please provide your USDT receiving account ID or receiving QR code.",
|
||||||
|
"bounty_football_help_rule_3_no_withdrawal": "(2) No withdrawal: If converted to coins, we will give you an additional 5% gold coins on top of the original amount ($1:10500 coins).",
|
||||||
|
"bounty_football_help_rule_4": "4.ID Rules: Users can locate official staff by providing a screenshot of their ranking and the number of the selected ID to use it (for example: when the reward is \"ID: ABBBBA\", the user can provide \"ID: 100001\" as their own ID, or grant the usage rights to another person).",
|
||||||
|
"bounty_football_help_note": "*Please note that the ID must not currently be in use by anyone else."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -487,5 +487,12 @@
|
|||||||
"gulben_room_rule_title": "Etkinlik Kuralları:",
|
"gulben_room_rule_title": "Etkinlik Kuralları:",
|
||||||
"gulben_room_rule_1": "1. Etkinlik süresince odanızın geliri hesaplanacaktır. Etkinlik sona erdikten sonra, ilk 10 sıradaki odalar gelir seviyelerine göre cömert coin ve VIP ödülleri alacaktır.",
|
"gulben_room_rule_1": "1. Etkinlik süresince odanızın geliri hesaplanacaktır. Etkinlik sona erdikten sonra, ilk 10 sıradaki odalar gelir seviyelerine göre cömert coin ve VIP ödülleri alacaktır.",
|
||||||
"gulben_room_rule_2": "2. Bu etkinliğin iPhone veya Google ile ilgisi yoktur.",
|
"gulben_room_rule_2": "2. Bu etkinliğin iPhone veya Google ile ilgisi yoktur.",
|
||||||
"gulben_room_rule_3": "3. Bu etkinliğin nihai yorum hakkı resmi tarafa aittir."
|
"gulben_room_rule_3": "3. Bu etkinliğin nihai yorum hakkı resmi tarafa aittir.",
|
||||||
|
"bounty_football_help_rule_1": "1. Bir kullanıcı etkinlik boyunca ne kadar çok coin biriktirirse sıralaması o kadar yüksek olur.",
|
||||||
|
"bounty_football_help_rule_2": "2. Etkinlik 15 gün sürecektir. Etkinlik sona erdikten sonra ödüller liderlik tablosundaki ilk üç kullanıcıya dağıtılacaktır.",
|
||||||
|
"bounty_football_help_rule_3": "3. İlk üç kullanıcı, USD ödülünü nasıl alacaklarını görüşmek için resmi personelle zamanında iletişime geçmelidir.",
|
||||||
|
"bounty_football_help_rule_3_withdrawal": "(1) Para çekme: Lütfen USDT alıcı hesap ID'nizi veya alıcı QR kodunuzu sağlayın.",
|
||||||
|
"bounty_football_help_rule_3_no_withdrawal": "(2) Para çekmeme: Coin'e dönüştürülürse, orijinal tutara ek olarak %5 ekstra altın coin vereceğiz ($1:10500 coins).",
|
||||||
|
"bounty_football_help_rule_4": "4.ID Kuralları: Kullanıcılar, sıralamalarının ekran görüntüsünü ve seçilen ID numarasını sağlayarak resmi personelden bu ID'yi kullanmayı talep edebilir (örneğin: ödül \"ID: ABBBBA\" olduğunda, kullanıcı \"ID: 100001\" bilgisini kendi ID'si olarak sağlayabilir veya kullanım hakkını başka bir kişiye verebilir).",
|
||||||
|
"bounty_football_help_note": "*Lütfen bu ID'nin şu anda başka biri tarafından kullanılmıyor olması gerektiğini unutmayın."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -487,5 +487,12 @@
|
|||||||
"gulben_room_rule_title": "活动规则:",
|
"gulben_room_rule_title": "活动规则:",
|
||||||
"gulben_room_rule_1": "1.活动期间将统计你的房间流水。活动结束后,排名前 10 的房间将根据其流水等级获得丰厚金币和 VIP 奖励。",
|
"gulben_room_rule_1": "1.活动期间将统计你的房间流水。活动结束后,排名前 10 的房间将根据其流水等级获得丰厚金币和 VIP 奖励。",
|
||||||
"gulben_room_rule_2": "2. 本活动与 iPhone 或 Google 无关。",
|
"gulben_room_rule_2": "2. 本活动与 iPhone 或 Google 无关。",
|
||||||
"gulben_room_rule_3": "3. 本活动最终解释权归官方所有。"
|
"gulben_room_rule_3": "3. 本活动最终解释权归官方所有。",
|
||||||
|
"bounty_football_help_rule_1": "1. 活动期间用户累计的金币越多,排名越高。",
|
||||||
|
"bounty_football_help_rule_2": "2. 活动将持续15天。活动结束后,奖励将发放给排行榜前三名用户。",
|
||||||
|
"bounty_football_help_rule_3": "3. 前三名用户应及时联系官方工作人员,沟通如何领取美元奖励。",
|
||||||
|
"bounty_football_help_rule_3_withdrawal": "(1) 提现:请提供您的 USDT 收款账户 ID 或收款二维码。",
|
||||||
|
"bounty_football_help_rule_3_no_withdrawal": "(2) 不提现:如果兑换为金币,我们将在原金额基础上额外赠送 5% 金币($1:10500 coins)。",
|
||||||
|
"bounty_football_help_rule_4": "4.ID 规则:用户可以通过提供自己的排名截图和所选 ID 编号来联系官方工作人员使用该 ID(例如:奖励为 \"ID: ABBBBA\" 时,用户可以提供 \"ID: 100001\" 作为自己的 ID,或将使用权授予他人)。",
|
||||||
|
"bounty_football_help_note": "*请注意,该 ID 当前不得被其他任何人使用。"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -414,6 +414,12 @@ const router = createRouter({
|
|||||||
component: () => import('../views/Activities/GulbenFestival/index.vue'),
|
component: () => import('../views/Activities/GulbenFestival/index.vue'),
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
}, // 古尔邦节活动,2026年6月7日上午5点结束
|
}, // 古尔邦节活动,2026年6月7日上午5点结束
|
||||||
|
{
|
||||||
|
path: '/activities/bounty-football',
|
||||||
|
name: 'bounty-football',
|
||||||
|
component: () => import('../views/Activities/BountyFootball/index.vue'),
|
||||||
|
meta: { requiresAuth: false },
|
||||||
|
}, // BountyFootball
|
||||||
{
|
{
|
||||||
path: '/activities/lucky-dollars-season5',
|
path: '/activities/lucky-dollars-season5',
|
||||||
name: 'lucky-dollars-season5',
|
name: 'lucky-dollars-season5',
|
||||||
|
|||||||
@ -64,6 +64,7 @@ const ACTIVITIES = [
|
|||||||
'/daily-recharge', // 每日充值页面
|
'/daily-recharge', // 每日充值页面
|
||||||
'/activities/lucky-dollars-season5', // 幸运美金活动
|
'/activities/lucky-dollars-season5', // 幸运美金活动
|
||||||
'/activities/gulben-festival', // 古尔邦节活动
|
'/activities/gulben-festival', // 古尔邦节活动
|
||||||
|
'/activities/bounty-football', // BountyFootball
|
||||||
'/activities/lucky-dollars-season4', // 幸运美金活动
|
'/activities/lucky-dollars-season4', // 幸运美金活动
|
||||||
'/activities/lesser-bairam', // 开斋节打榜
|
'/activities/lesser-bairam', // 开斋节打榜
|
||||||
'/activities/lucky-dollars-season3', // 斋月打榜
|
'/activities/lucky-dollars-season3', // 斋月打榜
|
||||||
|
|||||||
321
src/views/Activities/BountyFootball/components/topUser.vue
Normal file
321
src/views/Activities/BountyFootball/components/topUser.vue
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<div style="position: relative">
|
||||||
|
<img
|
||||||
|
v-smart-img.noFade
|
||||||
|
:src="BorderImgUrl"
|
||||||
|
alt=""
|
||||||
|
width="100%"
|
||||||
|
style="display: block; position: relative; z-index: 2"
|
||||||
|
/>
|
||||||
|
<!-- 头像 -->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
"
|
||||||
|
:style="{ height: avatarContainerHeight }"
|
||||||
|
>
|
||||||
|
<div :style="{ width: avatarWidth }">
|
||||||
|
<img
|
||||||
|
:src="avatarUrl || ''"
|
||||||
|
alt=""
|
||||||
|
width="100%"
|
||||||
|
class="top-avatar"
|
||||||
|
:class="{ 'top-avatar-square': avatarSquare }"
|
||||||
|
style="display: block; aspect-ratio: 1/1; object-fit: cover"
|
||||||
|
@error="handleAvatarImageError"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户名和贡献值 -->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2vw;
|
||||||
|
"
|
||||||
|
:style="{ height: bottomSectionHeight }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="display: flex; justify-content: center; align-items: center"
|
||||||
|
:style="{
|
||||||
|
width: nameValueWidth,
|
||||||
|
height: nameValueHeight,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div style="font-weight: 700" class="showText" :class="isTopOne ? 'top1Text' : 'text'">
|
||||||
|
{{ name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="distribution-value-pill"
|
||||||
|
:class="distributionValueClass"
|
||||||
|
style="padding: 1px 2px; display: flex; justify-content: center; align-items: center"
|
||||||
|
:style="{
|
||||||
|
width: distributionValueWidth,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-smart-img.noFade
|
||||||
|
:src="resolvedValueIconUrl"
|
||||||
|
alt=""
|
||||||
|
style="display: block; aspect-ratio: 1/1; flex: 0 0 auto"
|
||||||
|
:style="{ width: coinWidth }"
|
||||||
|
/>
|
||||||
|
<div style="font-weight: 590" class="showText" :class="isTopOne ? 'top1Text' : 'text'">
|
||||||
|
{{ distributionValue }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { handleAvatarImageError } from '@/utils/image/imageHandler.js'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
ranking: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
avatarUrl: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
BorderImgUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
distributionValue: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
valueIconUrl: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
avatarSquare: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否为第一名
|
||||||
|
const isTopOne = computed(() => {
|
||||||
|
return props.ranking === '1'
|
||||||
|
})
|
||||||
|
|
||||||
|
const resolvedValueIconUrl = computed(() => {
|
||||||
|
return props.valueIconUrl || new URL('/src/assets/icon/Azizi/coin.png', import.meta.url).href
|
||||||
|
})
|
||||||
|
|
||||||
|
const distributionValueClass = computed(() => {
|
||||||
|
switch (props.ranking) {
|
||||||
|
case '1':
|
||||||
|
return 'distribution-value-pill-top1'
|
||||||
|
case '2':
|
||||||
|
return 'distribution-value-pill-top2'
|
||||||
|
case '3':
|
||||||
|
return 'distribution-value-pill-top3'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 头像容器高度
|
||||||
|
const avatarContainerHeight = computed(() => {
|
||||||
|
switch (props.ranking) {
|
||||||
|
case '1':
|
||||||
|
return '83%'
|
||||||
|
case '2':
|
||||||
|
return '83%'
|
||||||
|
case '3':
|
||||||
|
return '83%'
|
||||||
|
default:
|
||||||
|
return '83%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 头像宽度
|
||||||
|
const avatarWidth = computed(() => {
|
||||||
|
switch (props.ranking) {
|
||||||
|
case '1':
|
||||||
|
return '40%'
|
||||||
|
case '2':
|
||||||
|
return '40%'
|
||||||
|
case '3':
|
||||||
|
return '40%'
|
||||||
|
default:
|
||||||
|
return '40%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 底部信息区域高度
|
||||||
|
const bottomSectionHeight = computed(() => {
|
||||||
|
switch (props.ranking) {
|
||||||
|
case '1':
|
||||||
|
return '40%'
|
||||||
|
case '2':
|
||||||
|
return '40%'
|
||||||
|
case '3':
|
||||||
|
return '40%'
|
||||||
|
default:
|
||||||
|
return '40%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 名称宽度
|
||||||
|
const nameValueWidth = computed(() => {
|
||||||
|
switch (props.ranking) {
|
||||||
|
case '1':
|
||||||
|
return '52%'
|
||||||
|
case '2':
|
||||||
|
return '52%'
|
||||||
|
case '3':
|
||||||
|
return '52%'
|
||||||
|
default:
|
||||||
|
return '52%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 名称高度
|
||||||
|
const nameValueHeight = computed(() => {
|
||||||
|
switch (props.ranking) {
|
||||||
|
case '1':
|
||||||
|
return '35%'
|
||||||
|
case '2':
|
||||||
|
return '30%'
|
||||||
|
case '3':
|
||||||
|
return '30%'
|
||||||
|
default:
|
||||||
|
return '30%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 贡献值宽度
|
||||||
|
const distributionValueWidth = computed(() => {
|
||||||
|
switch (props.ranking) {
|
||||||
|
case '1':
|
||||||
|
return '55%'
|
||||||
|
case '2':
|
||||||
|
return '50%'
|
||||||
|
case '3':
|
||||||
|
return '50%'
|
||||||
|
default:
|
||||||
|
return '50%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 金币图标宽度
|
||||||
|
const coinWidth = computed(() => {
|
||||||
|
switch (props.ranking) {
|
||||||
|
case '1':
|
||||||
|
return '1em'
|
||||||
|
case '2':
|
||||||
|
return '0.9em'
|
||||||
|
case '3':
|
||||||
|
return '0.9em'
|
||||||
|
default:
|
||||||
|
return '0.9em'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
* {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showText {
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top1Text {
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-avatar-square {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distribution-value-pill {
|
||||||
|
border-radius: 40px;
|
||||||
|
border: 0.64px solid #ffdf36;
|
||||||
|
box-shadow: 0 0 2.558px 0 rgba(0, 0, 0, 0.4) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distribution-value-pill-top1 {
|
||||||
|
background: linear-gradient(270deg, #3a0606 0%, #d84949 47.45%, #3a0606 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.distribution-value-pill-top2 {
|
||||||
|
background: linear-gradient(270deg, #223457 0%, #859de7 47.45%, #213655 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.distribution-value-pill-top3 {
|
||||||
|
background: linear-gradient(270deg, #13301a 0%, #89db93 47.45%, #112a16 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 360px) {
|
||||||
|
* {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 360px) {
|
||||||
|
* {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
* {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1024px) {
|
||||||
|
* {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
940
src/views/Activities/BountyFootball/index.vue
Normal file
940
src/views/Activities/BountyFootball/index.vue
Normal file
@ -0,0 +1,940 @@
|
|||||||
|
<!-- src/views/Activities/Game/index.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="fullPage">
|
||||||
|
<!-- 预加载状态 -->
|
||||||
|
<div v-if="isLoading" class="loading-container">
|
||||||
|
<LoadingContent>{{ $t('loading') }}...</LoadingContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主要内容 -->
|
||||||
|
<div v-else>
|
||||||
|
<div ref="bgRef" class="bg" @scroll.passive="checkRankingLoadTrigger">
|
||||||
|
<img v-smart-img class="bg2-layer" :src="imageUrl('bg2')" alt="" />
|
||||||
|
|
||||||
|
<itemCenter
|
||||||
|
style="min-height: 150vw"
|
||||||
|
:imgUrl="imageUrl('bg')"
|
||||||
|
:contentStyle="`flex-direction: column;justify-content: flex-start;gap:10vw;`"
|
||||||
|
>
|
||||||
|
<!-- 状态栏占位区域(仅在APP中显示) -->
|
||||||
|
<!-- <div v-if="isInAppEnvironment" style="height: 30px"></div> -->
|
||||||
|
|
||||||
|
<!-- 按钮 -->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
padding: 90vw 0 0;
|
||||||
|
width: 100%;
|
||||||
|
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: 4vw"
|
||||||
|
@click="maskLayerShow = true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</itemCenter>
|
||||||
|
|
||||||
|
<!-- 倒计时 -->
|
||||||
|
<itemCenter
|
||||||
|
style="min-height: 23vw; padding: 0 3vw"
|
||||||
|
:imgUrl="imageUrl('timeBg')"
|
||||||
|
:contentStyle="`direction:ltr;padding:0 3vw 5vw;gap:3.5vw`"
|
||||||
|
>
|
||||||
|
<div style="flex: 1; min-width: 0; display: flex; justify-content: center">
|
||||||
|
<div class="timeText">{{ Days }}</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; min-width: 0; display: flex; justify-content: center">
|
||||||
|
<div class="timeText">{{ Hours }}</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; min-width: 0; display: flex; justify-content: center">
|
||||||
|
<div class="timeText">{{ Minutes }}</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; min-width: 0; display: flex; justify-content: center">
|
||||||
|
<div class="timeText">{{ Second }}</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-smart-img
|
||||||
|
:src="imageUrl(getImgName('rankingBt'))"
|
||||||
|
alt=""
|
||||||
|
:class="{ 'grayscale-container': !rankingShow }"
|
||||||
|
style="display: block; width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 奖励按钮 -->
|
||||||
|
<div style="width: 40%; position: relative" @click="rankingShow = false">
|
||||||
|
<img
|
||||||
|
v-smart-img
|
||||||
|
:src="imageUrl(getImgName('rewardBt'))"
|
||||||
|
alt=""
|
||||||
|
:class="{ 'grayscale-container': rankingShow }"
|
||||||
|
style="display: block; width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</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('rankBg')})`"></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- 前三名 -->
|
||||||
|
<itemCenter
|
||||||
|
style="min-height: 75vw"
|
||||||
|
:imgUrl="imageUrl('RankingMain')"
|
||||||
|
:contentStyle="`padding: 21% 3% 0;flex-direction: column;`"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<!-- 第一 -->
|
||||||
|
<div style="display: flex; justify-content: center">
|
||||||
|
<TopUser
|
||||||
|
ranking="1"
|
||||||
|
:BorderImgUrl="imageUrl('top1')"
|
||||||
|
:avatarUrl="RankingHasTop3[0].avatar"
|
||||||
|
:name="RankingHasTop3[0].nickname"
|
||||||
|
:distributionValue="RankingHasTop3[0].quantity"
|
||||||
|
:valueIconUrl="coinIcon"
|
||||||
|
style="width: 42%"
|
||||||
|
@click="viewUserInfo(RankingHasTop3[0].userId)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 第二三 -->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: -40%;
|
||||||
|
pointer-events: none;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="pointer-events: auto; width: 32%"
|
||||||
|
@click.stop="viewUserInfo(RankingHasTop3[1].userId)"
|
||||||
|
>
|
||||||
|
<TopUser
|
||||||
|
ranking="2"
|
||||||
|
:BorderImgUrl="imageUrl('top2')"
|
||||||
|
:avatarUrl="RankingHasTop3[1].avatar"
|
||||||
|
:name="RankingHasTop3[1].nickname"
|
||||||
|
:distributionValue="RankingHasTop3[1].quantity"
|
||||||
|
:valueIconUrl="coinIcon"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style="pointer-events: auto; width: 32%"
|
||||||
|
@click.stop="viewUserInfo(RankingHasTop3[2].userId)"
|
||||||
|
>
|
||||||
|
<TopUser
|
||||||
|
ranking="3"
|
||||||
|
:BorderImgUrl="imageUrl('top3')"
|
||||||
|
:avatarUrl="RankingHasTop3[2].avatar"
|
||||||
|
:name="RankingHasTop3[2].nickname"
|
||||||
|
:distributionValue="RankingHasTop3[2].quantity"
|
||||||
|
:valueIconUrl="coinIcon"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</itemCenter>
|
||||||
|
|
||||||
|
<!-- 第四名开始 -->
|
||||||
|
<div
|
||||||
|
v-if="showRanking.length > 0"
|
||||||
|
style="position: relative; z-index: 2; width: 100vw"
|
||||||
|
>
|
||||||
|
<itemCenter
|
||||||
|
v-for="(listItem, index) in showRanking"
|
||||||
|
:key="listItem.userId"
|
||||||
|
style="min-height: 22vw"
|
||||||
|
:imgUrl="imageUrl('RankingItem')"
|
||||||
|
:contentStyle="`padding: 3vw 8vw 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
|
||||||
|
: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">
|
||||||
|
{{ $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/Azizi/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>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-show="rankingShow && hasMoreRanking"
|
||||||
|
ref="rankingLoadTrigger"
|
||||||
|
class="ranking-load-trigger"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<!-- 底框 -->
|
||||||
|
<img
|
||||||
|
v-smart-img
|
||||||
|
:src="imageUrl('RankingBottomBorder')"
|
||||||
|
alt=""
|
||||||
|
width="100%"
|
||||||
|
style="display: block; margin-top: -13vw; 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
|
||||||
|
style="min-height: 129vw"
|
||||||
|
:imgUrl="topImg[Number(rewardsTop.rankRange) - 1]"
|
||||||
|
:contentStyle="`display: grid;grid-template-columns: repeat(2, 1fr);gap: 3.5vw;padding: 24vw 8vw 5vw 8vw;`"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(reward, rewardIndex) in rewardsTop.rewards"
|
||||||
|
:key="rewardIndex"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- 奖励图片 -->
|
||||||
|
<img
|
||||||
|
:src="reward.cover || ''"
|
||||||
|
alt=""
|
||||||
|
style="display: block; width: 70%; object-fit: cover"
|
||||||
|
@error="(e) => handleRewardImageError(e, reward.type)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 数值 -->
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
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="min-height: 20vw; position: fixed; bottom: 0; z-index: 2"
|
||||||
|
:imgUrl="imageUrl('myRankingBg')"
|
||||||
|
:contentStyle="`padding: 0 8%`"
|
||||||
|
>
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<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
|
||||||
|
: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/Azizi/coin.png"
|
||||||
|
alt=""
|
||||||
|
style="display: block; width: 1.5em; aspect-ratio: 1/1; object-fit: cover"
|
||||||
|
/>
|
||||||
|
<div style="color: #fff; font-weight: 700">
|
||||||
|
{{ myRanking.quantity || 0 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</itemCenter>
|
||||||
|
|
||||||
|
<!-- 弹窗遮罩层 -->
|
||||||
|
<maskLayer :maskLayerShow="maskLayerShow" @click="maskLayerShow = false">
|
||||||
|
<!-- help弹窗 -->
|
||||||
|
<div style="margin: 20% 0" @click.stop>
|
||||||
|
<itemCenter
|
||||||
|
style="min-height: 152vw"
|
||||||
|
:imgUrl="imageUrl(getImgName('helpBg'))"
|
||||||
|
:contentStyle="`inset: 22vw 7vw 10vw; overflow-y: auto; display: block;`"
|
||||||
|
>
|
||||||
|
<div class="helpInfo">
|
||||||
|
<p>{{ $t('bounty_football_help_rule_1') }}</p>
|
||||||
|
<p>{{ $t('bounty_football_help_rule_2') }}</p>
|
||||||
|
<p>{{ $t('bounty_football_help_rule_3') }}</p>
|
||||||
|
<p>{{ $t('bounty_football_help_rule_3_withdrawal') }}</p>
|
||||||
|
<p>{{ $t('bounty_football_help_rule_3_no_withdrawal') }}</p>
|
||||||
|
<p>{{ $t('bounty_football_help_rule_4') }}</p>
|
||||||
|
<p>{{ $t('bounty_football_help_note') }}</p>
|
||||||
|
</div>
|
||||||
|
</itemCenter>
|
||||||
|
</div>
|
||||||
|
</maskLayer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, nextTick, 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/image/imagePreloader.js'
|
||||||
|
import { handleAvatarImageError, handleRewardImageError } from '@/utils/image/imageHandler.js'
|
||||||
|
import { formatRewardDisplay } from '@/utils/rewardFormatter.js'
|
||||||
|
import { useDebounce } from '@/utils/useDebounce.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 coinIcon = new URL('/src/assets/icon/Azizi/coin.png', import.meta.url).href
|
||||||
|
|
||||||
|
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
|
||||||
|
const maskLayerShow = ref(false) //帮助模块
|
||||||
|
|
||||||
|
// 预加载状态
|
||||||
|
const isLoading = ref(true)
|
||||||
|
|
||||||
|
const activityType = ref(20)
|
||||||
|
const templateId = ref('2061370141245067265')
|
||||||
|
const cycleKey = ref('20260601')
|
||||||
|
|
||||||
|
const langStore = useLangStore()
|
||||||
|
// 当前语言类型
|
||||||
|
const currentLangType = computed(() => {
|
||||||
|
return langStore.selectedLang?.type || 'en'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取OSS图片URL的函数
|
||||||
|
const imageUrl = (filename) => getPngUrl('Activities/BountyFootball/', filename)
|
||||||
|
|
||||||
|
// 根据语言获取图片名
|
||||||
|
const getImgName = (filename) => {
|
||||||
|
return currentLangType.value === 'en' ? filename : `${filename}_${currentLangType.value}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 倒计时
|
||||||
|
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 rankingAllData = ref([])
|
||||||
|
const myRanking = ref({}) // 我的排名
|
||||||
|
const rewardsList = ref([]) // 奖励列表
|
||||||
|
const topImg = ref([imageUrl('rewardBgTop1'), imageUrl('rewardBgTop2'), imageUrl('rewardBgTop3')]) // top图片
|
||||||
|
const bgRef = ref(null)
|
||||||
|
const rankingLoadTrigger = ref(null)
|
||||||
|
const rankingPageSize = 10
|
||||||
|
let rankingObserver = null
|
||||||
|
|
||||||
|
const normalizeRankingUser = (user = {}) => {
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
userId: user.userId || user.uid || '',
|
||||||
|
avatar: user.avatar || user.userAvatar || '',
|
||||||
|
nickname: user.nickname || user.userName || '',
|
||||||
|
quantity: user.quantity ?? user.amount ?? 0,
|
||||||
|
rank: user.rank ?? user.ranking ?? '',
|
||||||
|
account: user.account || '',
|
||||||
|
specialAccount: user.specialAccount || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理排行榜小于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 []
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasMoreRanking = computed(() => {
|
||||||
|
return Ranking.value.length < rankingAllData.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const isRankingTriggerVisible = () => {
|
||||||
|
if (!rankingLoadTrigger.value) return false
|
||||||
|
|
||||||
|
const triggerRect = rankingLoadTrigger.value.getBoundingClientRect()
|
||||||
|
const rootRect = bgRef.value?.getBoundingClientRect()
|
||||||
|
const viewportTop = rootRect?.top ?? 0
|
||||||
|
const viewportBottom = rootRect?.bottom ?? window.innerHeight
|
||||||
|
|
||||||
|
return triggerRect.top <= viewportBottom && triggerRect.bottom >= viewportTop
|
||||||
|
}
|
||||||
|
|
||||||
|
const appendRankingBatch = () => {
|
||||||
|
if (!hasMoreRanking.value) return
|
||||||
|
|
||||||
|
const start = Ranking.value.length
|
||||||
|
const nextRanking = rankingAllData.value.slice(start, start + rankingPageSize)
|
||||||
|
Ranking.value.push(...nextRanking)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRankingLoadTrigger = () => {
|
||||||
|
if (!rankingShow.value || !hasMoreRanking.value || !isRankingTriggerVisible()) return
|
||||||
|
|
||||||
|
console.log('上拉加载')
|
||||||
|
appendRankingBatch()
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (hasMoreRanking.value && isRankingTriggerVisible()) {
|
||||||
|
debouncedLoadMoreRanking()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedLoadMoreRanking = useDebounce(handleRankingLoadTrigger, 300)
|
||||||
|
|
||||||
|
const checkRankingLoadTrigger = () => {
|
||||||
|
if (!rankingShow.value || !hasMoreRanking.value) return
|
||||||
|
debouncedLoadMoreRanking()
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadInitialRanking = () => {
|
||||||
|
appendRankingBatch()
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
checkRankingLoadTrigger()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const destroyRankingObserver = () => {
|
||||||
|
if (rankingObserver) {
|
||||||
|
rankingObserver.disconnect()
|
||||||
|
rankingObserver = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initRankingObserver = async () => {
|
||||||
|
destroyRankingObserver()
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
if (!rankingLoadTrigger.value || typeof IntersectionObserver === 'undefined') return
|
||||||
|
|
||||||
|
rankingObserver = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
if (entries.some((entry) => entry.isIntersecting)) {
|
||||||
|
debouncedLoadMoreRanking()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: bgRef.value || null,
|
||||||
|
rootMargin: '0px 0px 150px 0px',
|
||||||
|
threshold: 0.1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
rankingObserver.observe(rankingLoadTrigger.value)
|
||||||
|
checkRankingLoadTrigger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取北京时间2026年6月8号早上5点
|
||||||
|
const getTargetTime = () => {
|
||||||
|
const targetDate = new Date(2026, 5, 8, 5, 0, 0)
|
||||||
|
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) => {
|
||||||
|
return formatRewardDisplay(type, reward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取排行榜和我的排名
|
||||||
|
const getListAndMy = async () => {
|
||||||
|
let data = {
|
||||||
|
activityType: activityType.value,
|
||||||
|
cycleKey: cycleKey.value,
|
||||||
|
}
|
||||||
|
const resMyWeekStarRanking = await getRankingListAndMyRanking(data)
|
||||||
|
console.log('resMyWeekStarRanking:', resMyWeekStarRanking)
|
||||||
|
if (resMyWeekStarRanking.status && resMyWeekStarRanking.body) {
|
||||||
|
myRanking.value = normalizeRankingUser(resMyWeekStarRanking.body.currentUserRank || {})
|
||||||
|
rankingAllData.value = (resMyWeekStarRanking.body.rankingList || []).map((item) =>
|
||||||
|
normalizeRankingUser(item),
|
||||||
|
)
|
||||||
|
Ranking.value = []
|
||||||
|
loadInitialRanking()
|
||||||
|
await initRankingObserver()
|
||||||
|
} else {
|
||||||
|
myRanking.value = {}
|
||||||
|
Ranking.value = []
|
||||||
|
rankingAllData.value = []
|
||||||
|
destroyRankingObserver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取每周奖励列表和礼物
|
||||||
|
const getRewardsAndGifts = async () => {
|
||||||
|
const resRewardsAndGifts = await getThisWeekRewardsAndGifts(templateId.value)
|
||||||
|
if (resRewardsAndGifts.status && resRewardsAndGifts.body) {
|
||||||
|
rewardsList.value = resRewardsAndGifts.body.butOneRewards
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新页面数据
|
||||||
|
const initData = async () => {
|
||||||
|
await Promise.all([
|
||||||
|
getListAndMy(), // 获取排行榜和我的排名
|
||||||
|
getRewardsAndGifts(), //获取本周的礼物和奖励列表
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预加载关键图片
|
||||||
|
const preloadCriticalImages = async () => {
|
||||||
|
const criticalImages = [
|
||||||
|
imageUrl('bg'),
|
||||||
|
imageUrl('bg2'),
|
||||||
|
imageUrl('helpBt'),
|
||||||
|
imageUrl('timeBg'),
|
||||||
|
imageUrl(getImgName('rankingBt')),
|
||||||
|
imageUrl(getImgName('rewardBt')),
|
||||||
|
imageUrl('rankBg'),
|
||||||
|
imageUrl('RankingItem'),
|
||||||
|
imageUrl('RankingMain'),
|
||||||
|
imageUrl('RankingBottomBorder'),
|
||||||
|
imageUrl('myRankingBg'),
|
||||||
|
imageUrl(getImgName('helpBg')),
|
||||||
|
imageUrl('rewardBgTop1'),
|
||||||
|
imageUrl('rewardBgTop2'),
|
||||||
|
imageUrl('rewardBgTop3'),
|
||||||
|
imageUrl('top1'),
|
||||||
|
imageUrl('top2'),
|
||||||
|
imageUrl('top3'),
|
||||||
|
]
|
||||||
|
|
||||||
|
await preloadImages(criticalImages)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成预加载
|
||||||
|
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) //摧毁计时器
|
||||||
|
destroyRankingObserver()
|
||||||
|
debouncedLoadMoreRanking.cancel()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
* {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullPage {
|
||||||
|
position: relative;
|
||||||
|
background: #053126;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg > :not(.bg2-layer):not(.mask-layer) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg :deep(.mask-layer) {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 10000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg2-layer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 0;
|
||||||
|
display: block;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
object-fit: cover;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grayscale-container {
|
||||||
|
filter: grayscale(100%) brightness(90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 120vw 0 0;
|
||||||
|
background-image: var(--bg2-url);
|
||||||
|
background-repeat: repeat-y;
|
||||||
|
background-position: top center;
|
||||||
|
background-size: 100% auto;
|
||||||
|
z-index: 0; /* 确保在.bg之下 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ranking-bg {
|
||||||
|
background-color: #053126;
|
||||||
|
background-image: var(--ranking-bg-url);
|
||||||
|
background-size: 100% auto;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
inset: 15vw 2vw 4vw;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ranking-load-trigger {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeText {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 590;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeGap {
|
||||||
|
width: 6px;
|
||||||
|
align-self: stretch;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
margin-top: -23vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加加载动画样式 */
|
||||||
|
.loading-container {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[dir='rtl'] .timeBox {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpInfo > p {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
115
src/views/Activities/BountyFootball/page.config.js
Normal file
115
src/views/Activities/BountyFootball/page.config.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
export const pageConfig = {
|
||||||
|
name: 'BountyFootball',
|
||||||
|
routePath: '/activities/bounty-football',
|
||||||
|
routeName: 'bounty-football',
|
||||||
|
assetPath: 'Activities/BountyFootball/',
|
||||||
|
|
||||||
|
activity: {
|
||||||
|
templateId: '2061370141245067265',
|
||||||
|
activityType: 20,
|
||||||
|
cycleKey: '20260601',
|
||||||
|
},
|
||||||
|
|
||||||
|
preload: {
|
||||||
|
blocking: [
|
||||||
|
'bg',
|
||||||
|
'bg2',
|
||||||
|
'helpBt',
|
||||||
|
'timeBg',
|
||||||
|
'rankingBt',
|
||||||
|
'rewardBt',
|
||||||
|
'rankBg',
|
||||||
|
'RankingMain',
|
||||||
|
'RankingItem',
|
||||||
|
'RankingBottomBorder',
|
||||||
|
'myRankingBg',
|
||||||
|
'helpBg',
|
||||||
|
'top1',
|
||||||
|
'top2',
|
||||||
|
'top3',
|
||||||
|
'rewardBgTop1',
|
||||||
|
'rewardBgTop2',
|
||||||
|
'rewardBgTop3',
|
||||||
|
],
|
||||||
|
background: [],
|
||||||
|
},
|
||||||
|
|
||||||
|
modules: {
|
||||||
|
tabs: {
|
||||||
|
enabled: true,
|
||||||
|
defaultValue: 'ranking',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
value: 'ranking',
|
||||||
|
label: 'Ranking',
|
||||||
|
buttonImage: 'rankingBt',
|
||||||
|
activeButtonImage: 'rankingBt',
|
||||||
|
remark: '排行榜按钮,未选中时页面使用灰度效果。',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'reward',
|
||||||
|
label: 'Reward',
|
||||||
|
buttonImage: 'rewardBt',
|
||||||
|
activeButtonImage: 'rewardBt',
|
||||||
|
remark: '奖励按钮,未选中时页面使用灰度效果。',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
name: 'ranking',
|
||||||
|
label: 'Ranking',
|
||||||
|
enabled: true,
|
||||||
|
apis: ['getRankingListAndMyRanking'],
|
||||||
|
loadMode: 'client-incremental',
|
||||||
|
pageSize: 10,
|
||||||
|
images: ['rankBg', 'RankingMain', 'RankingItem', 'RankingBottomBorder', 'myRankingBg'],
|
||||||
|
previewImages: ['top1', 'top2', 'top3'],
|
||||||
|
remark: '默认展示排行榜;包含前三名、第四名以后列表和底部我的排名。',
|
||||||
|
children: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reward',
|
||||||
|
label: 'Reward',
|
||||||
|
enabled: true,
|
||||||
|
apis: ['getThisWeekRewardsAndGifts'],
|
||||||
|
images: ['rewardBgTop1', 'rewardBgTop2', 'rewardBgTop3'],
|
||||||
|
previewImages: [],
|
||||||
|
remark: '只展示奖励列表;已按需求取消 index 中的活动礼物模块。',
|
||||||
|
children: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
popups: [
|
||||||
|
{
|
||||||
|
name: 'help',
|
||||||
|
triggerImage: 'helpBt',
|
||||||
|
backgroundImage: 'helpBg',
|
||||||
|
contentImage: '',
|
||||||
|
apis: [],
|
||||||
|
remark:
|
||||||
|
'规则说明弹窗;helpBg 支持多语言图片。MaskLayer 必须保持 fixed 顶层,不要被 .bg 子层级规则覆盖;规则文案按编号拆段,白色字体,字号 1em。',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
name: 'countdown',
|
||||||
|
enabled: true,
|
||||||
|
deadline: '2026-06-12 05:00:00',
|
||||||
|
images: ['timeBg'],
|
||||||
|
fields: {
|
||||||
|
endTime: '2026-06-12 05:00:00',
|
||||||
|
remainingText: '',
|
||||||
|
},
|
||||||
|
remark: '天/时/分/秒使用 timeBg 承载;当前 index.vue 目标时间为 2026-06-12 05:00:00。',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
images: {
|
||||||
|
localized: {
|
||||||
|
ar: ['helpBg', 'rankingBt', 'rewardBt'],
|
||||||
|
bn: ['helpBg', 'rankingBt', 'rewardBt'],
|
||||||
|
tr: ['helpBg', 'rankingBt', 'rewardBt'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
377
src/views/Activities/BountyFootball/页面创建信息.md
Normal file
377
src/views/Activities/BountyFootball/页面创建信息.md
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
# 页面创建信息
|
||||||
|
|
||||||
|
## 1. 基础信息
|
||||||
|
|
||||||
|
- 页面类型:Activities
|
||||||
|
- 页面名称:BountyFootball
|
||||||
|
- 页面目录:src/views/Activities/BountyFootball
|
||||||
|
- 参考页面:已存在 `src/views/Activities/BountyFootball/index.vue`
|
||||||
|
- 备注:已按本地图片目录扫描结果同步图片清单、预加载和页面用图;按需求取消 index 中的活动礼物模块。
|
||||||
|
|
||||||
|
## 2. 路由和权限
|
||||||
|
|
||||||
|
- route path:/activities/bounty-football
|
||||||
|
- route name:bounty-football
|
||||||
|
- 是否需要登录:否
|
||||||
|
- 是否加入 `src/config/security.js`:是
|
||||||
|
- 是否加入 `src/utils/permissionManager.js`:是
|
||||||
|
|
||||||
|
### 2.1 路由代码片段
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
path: '/activities/bounty-football',
|
||||||
|
name: 'bounty-football',
|
||||||
|
component: () => import('../views/Activities/BountyFootball/index.vue'),
|
||||||
|
meta: { requiresAuth: false },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 安全白名单片段
|
||||||
|
|
||||||
|
```js
|
||||||
|
'/activities/bounty-football',
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 权限页面片段
|
||||||
|
|
||||||
|
```js
|
||||||
|
'/activities/bounty-football', // BountyFootball
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. OSS 图片目录
|
||||||
|
|
||||||
|
- OSS 相对路径:Activities/BountyFootball/
|
||||||
|
- 本地开发目录:public/oss/h5/Azizi/Activities/BountyFootball/
|
||||||
|
- 是否需要 Codex 自动创建本地开发目录:是
|
||||||
|
- 图片是否已放入本地目录:是
|
||||||
|
|
||||||
|
说明:本地开发目录只作为开发期 OSS 图片镜像使用,方便页面优先读取本地资源;发布时图片由你上传到真实 OSS,不随项目提交 Git。新建本地目录时不生成 `.gitkeep`,空目录不被 Git 跟踪是正常现象。
|
||||||
|
|
||||||
|
### 3.1 多语言图片清单
|
||||||
|
|
||||||
|
| 图片名 | 支持语言 | 备注 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| bg | en | 页面主背景 |
|
||||||
|
| bg2 | en | `.bg` 容器第一层底图,作为内容最底层背景 |
|
||||||
|
| helpBg | en, ar, bn, tr | 规则说明弹窗背景 |
|
||||||
|
| helpBt | en | 规则/帮助按钮 |
|
||||||
|
| myRankingBg | en | 底部我的排名固定栏背景 |
|
||||||
|
| rankBg | en | 排行榜内容区背景 |
|
||||||
|
| RankingBottomBorder | en | 排行榜底部边框 |
|
||||||
|
| rankingBt | en, ar, bn, tr | 排行榜按钮 |
|
||||||
|
| RankingItem | en | 排行榜列表项背景 |
|
||||||
|
| RankingMain | en | 排行榜前三名展示区背景 |
|
||||||
|
| rewardBgTop1 | en | 第一名奖励区背景 |
|
||||||
|
| rewardBgTop2 | en | 第二名奖励区背景 |
|
||||||
|
| rewardBgTop3 | en | 第三名奖励区背景 |
|
||||||
|
| rewardBt | en, ar, bn, tr | 奖励按钮 |
|
||||||
|
| timeBg | en | 倒计时背景 |
|
||||||
|
| top1 | en | 第一名头像/排名装饰 |
|
||||||
|
| top2 | en | 第二名头像/排名装饰 |
|
||||||
|
| top3 | en | 第三名头像/排名装饰 |
|
||||||
|
|
||||||
|
### 3.2 进入页面前同步预加载图片
|
||||||
|
|
||||||
|
| 图片名 | 是否包含多语言 | 备注 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| bg | 否 | 页面主背景 |
|
||||||
|
| bg2 | 否 | `.bg` 容器第一层底图,作为内容最底层背景 |
|
||||||
|
| helpBt | 否 | 规则/帮助按钮 |
|
||||||
|
| timeBg | 否 | 倒计时背景 |
|
||||||
|
| rankingBt | 是 | 标签页排行榜按钮 |
|
||||||
|
| rewardBt | 是 | 标签页奖励按钮 |
|
||||||
|
| rankBg | 否 | 排行榜内容区背景 |
|
||||||
|
| RankingMain | 否 | 排行榜前三名展示区背景 |
|
||||||
|
| RankingItem | 否 | 排行榜列表项背景 |
|
||||||
|
| RankingBottomBorder | 否 | 排行榜底部边框 |
|
||||||
|
| myRankingBg | 否 | 底部我的排名固定栏背景 |
|
||||||
|
| helpBg | 是 | 规则说明弹窗背景 |
|
||||||
|
| top1 | 否 | 第一名头像/排名装饰 |
|
||||||
|
| top2 | 否 | 第二名头像/排名装饰 |
|
||||||
|
| top3 | 否 | 第三名头像/排名装饰 |
|
||||||
|
| rewardBgTop1 | 否 | 第一名奖励区背景 |
|
||||||
|
| rewardBgTop2 | 否 | 第二名奖励区背景 |
|
||||||
|
| rewardBgTop3 | 否 | 第三名奖励区背景 |
|
||||||
|
|
||||||
|
### 3.3 进入页面后后台预加载图片
|
||||||
|
|
||||||
|
| 图片名 | 是否包含多语言 | 备注 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| | | 当前 index.vue 会在进入页面前预加载全部已使用本地图片,暂无后台预加载项 |
|
||||||
|
|
||||||
|
## 4. 模块配置
|
||||||
|
|
||||||
|
没有填写的模块,初版页面先不处理。
|
||||||
|
|
||||||
|
### 4.1 标签页模块
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"defaultValue": "ranking",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "ranking",
|
||||||
|
"label": "Ranking",
|
||||||
|
"buttonImage": "rankingBt",
|
||||||
|
"activeButtonImage": "rankingBt",
|
||||||
|
"remark": "排行榜按钮,未选中时页面使用灰度效果"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "reward",
|
||||||
|
"label": "Reward",
|
||||||
|
"buttonImage": "rewardBt",
|
||||||
|
"activeButtonImage": "rewardBt",
|
||||||
|
"remark": "奖励按钮,未选中时页面使用灰度效果"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 页面区块模块
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "ranking",
|
||||||
|
"label": "Ranking",
|
||||||
|
"enabled": true,
|
||||||
|
"apis": ["getRankingListAndMyRanking"],
|
||||||
|
"loadMode": "client-incremental",
|
||||||
|
"pageSize": 10,
|
||||||
|
"images": ["rankBg", "RankingMain", "RankingItem", "RankingBottomBorder", "myRankingBg"],
|
||||||
|
"previewImages": ["top1", "top2", "top3"],
|
||||||
|
"remark": "默认展示排行榜;包含前三名、第四名以后列表和底部我的排名",
|
||||||
|
"children": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reward",
|
||||||
|
"label": "Reward",
|
||||||
|
"enabled": true,
|
||||||
|
"apis": ["getThisWeekRewardsAndGifts"],
|
||||||
|
"images": ["rewardBgTop1", "rewardBgTop2", "rewardBgTop3"],
|
||||||
|
"previewImages": [],
|
||||||
|
"remark": "只展示奖励列表;已按需求取消 index 中的活动礼物模块",
|
||||||
|
"children": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
倒计时模块固定保留:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "countdown",
|
||||||
|
"enabled": true,
|
||||||
|
"deadline": "2026-06-12 05:00:00",
|
||||||
|
"images": ["timeBg"],
|
||||||
|
"fields": {
|
||||||
|
"endTime": "2026-06-12 05:00:00",
|
||||||
|
"remainingText": ""
|
||||||
|
},
|
||||||
|
"remark": "天/时/分/秒使用 timeBg 承载;当前 index.vue 目标时间为 2026-06-12 05:00:00。"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 弹窗模块
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "help",
|
||||||
|
"triggerImage": "helpBt",
|
||||||
|
"backgroundImage": "helpBg",
|
||||||
|
"contentImage": "",
|
||||||
|
"apis": [],
|
||||||
|
"remark": "规则说明弹窗;helpBg 支持多语言图片;MaskLayer 必须保持 fixed 顶层,不要被 .bg 子层级规则覆盖;规则文案按编号拆段,白色字体,字号 1em。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 排行榜模块
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": "",
|
||||||
|
"api": "",
|
||||||
|
"loadMode": "",
|
||||||
|
"pageSize": "",
|
||||||
|
"topCount": "",
|
||||||
|
"myRankingPath": "",
|
||||||
|
"fieldMap": {
|
||||||
|
"avatar": "",
|
||||||
|
"name": "",
|
||||||
|
"value": "",
|
||||||
|
"userId": "",
|
||||||
|
"rank": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 抽奖模块
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": "",
|
||||||
|
"drawApis": [],
|
||||||
|
"prizePoolApi": "",
|
||||||
|
"drawCountApi": "",
|
||||||
|
"extraApis": [],
|
||||||
|
"displayMode": "",
|
||||||
|
"slotCount": "",
|
||||||
|
"historyButtonPosition": "",
|
||||||
|
"images": {
|
||||||
|
"background": "",
|
||||||
|
"activeFrame": "",
|
||||||
|
"singleDrawButton": "",
|
||||||
|
"multiDrawButton": "",
|
||||||
|
"historyButton": "",
|
||||||
|
"resultBackground": ""
|
||||||
|
},
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 接口参数
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"templateId": "2061370141245067265",
|
||||||
|
"activityType": 20,
|
||||||
|
"cycleKey": "20260601"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 组件需求
|
||||||
|
|
||||||
|
需要哪个就填“是”,不需要或没填写的初版页面先不接。
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "BackgroundLayer",
|
||||||
|
"enabled": "",
|
||||||
|
"remark": "长背景分段"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "itemCenter",
|
||||||
|
"enabled": "",
|
||||||
|
"remark": "图片容器"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "topUser / TopUser",
|
||||||
|
"enabled": "",
|
||||||
|
"remark": "前三名用户展示"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Barrage",
|
||||||
|
"enabled": "",
|
||||||
|
"remark": "弹幕"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ActivityTabs",
|
||||||
|
"enabled": "",
|
||||||
|
"remark": "标签页"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ActivityLotteryGrid",
|
||||||
|
"enabled": "",
|
||||||
|
"remark": "抽奖格子"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.1 当前页面布局记录
|
||||||
|
|
||||||
|
本节按当前 `index.vue` 的实时结构记录,后续如从 md 同步回 Vue,需要保留以下规则:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scrollContainer": {
|
||||||
|
"selector": ".bg",
|
||||||
|
"style": {
|
||||||
|
"width": "100vw",
|
||||||
|
"height": "100vh",
|
||||||
|
"overflowX": "hidden",
|
||||||
|
"overflowY": "auto",
|
||||||
|
"scrollbar": "hidden",
|
||||||
|
"position": "relative",
|
||||||
|
"zIndex": 1
|
||||||
|
},
|
||||||
|
"remark": "页面上方内容在 .bg 内垂直滚动,隐藏滚动条。"
|
||||||
|
},
|
||||||
|
"backgroundLayer": {
|
||||||
|
"image": "bg2",
|
||||||
|
"placement": ".bg 的第一个子节点",
|
||||||
|
"layer": "内容最底层",
|
||||||
|
"style": {
|
||||||
|
"position": "absolute",
|
||||||
|
"top": 0,
|
||||||
|
"left": 0,
|
||||||
|
"zIndex": 0,
|
||||||
|
"width": "100vw",
|
||||||
|
"height": "100vh",
|
||||||
|
"objectFit": "cover",
|
||||||
|
"pointerEvents": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maskLayer": {
|
||||||
|
"component": "MaskLayer",
|
||||||
|
"position": "fixed",
|
||||||
|
"zIndex": 10000,
|
||||||
|
"remark": ".bg > :not(.bg2-layer) 这类子层级规则不能覆盖 MaskLayer 根节点;当前 Vue 使用 .bg > :not(.bg2-layer):not(.mask-layer) 并用 :deep(.mask-layer) 保持遮罩在视口最上层。"
|
||||||
|
},
|
||||||
|
"helpPopupText": {
|
||||||
|
"style": {
|
||||||
|
"color": "#fff",
|
||||||
|
"fontSize": "1em"
|
||||||
|
},
|
||||||
|
"paragraphs": [
|
||||||
|
"1. The more coins a user accumulates during the event, the higher their ranking will be.",
|
||||||
|
"2. The event will last for 15 days. After the event ends, rewards will be distributed to the top three users on the leaderboard.",
|
||||||
|
"3. The top three users should contact the official staff in a timely manner to discuss how to obtain the USD reward.",
|
||||||
|
"(1) Withdrawal: Please provide your USDT receiving account ID or receiving QR code.",
|
||||||
|
"(2) No withdrawal: If converted to coins, we will give you an additional 5% gold coins on top of the original amount ($1:10500 coins).",
|
||||||
|
"4.ID Rules: Users can locate official staff by providing a screenshot of their ranking and the number of the selected ID to use it (for example: when the reward is \"ID: ABBBBA\", the user can provide \"ID: 100001\" as their own ID, or grant the usage rights to another person).",
|
||||||
|
"*Please note that the ID must not currently be in use by anyone else."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 建页后记录
|
||||||
|
|
||||||
|
这部分由 Codex 建页后填写。
|
||||||
|
|
||||||
|
### 7.1 已完成
|
||||||
|
|
||||||
|
- 已创建页面目录:src/views/Activities/BountyFootball
|
||||||
|
- 已创建专属页面信息文件:src/views/Activities/BountyFootball/页面创建信息.md
|
||||||
|
- 已创建初始配置文件:src/views/Activities/BountyFootball/page.config.js
|
||||||
|
- 已创建本地开发图片目录:public/oss/h5/Azizi/Activities/BountyFootball/
|
||||||
|
- 已扫描本地图片目录并更新 3.1 图片清单、3.2/3.3 预加载清单
|
||||||
|
- 已同步更新 `page.config.js` 的 `preload`、`modules`、`popups`、`features`、`images.localized`
|
||||||
|
- 已将 `index.vue` 的资源路径切换为 `Activities/BountyFootball/` 下的 PNG 图片
|
||||||
|
- 已取消 `index.vue` 中的活动礼物模块,保留排行榜与奖励列表模块
|
||||||
|
- 已写入 `src/router/index.js`、`src/config/security.js`、`src/utils/permissionManager.js`
|
||||||
|
- 已将页面内不存在的 `Atu/coin.png` 修正为项目现有的 `Azizi/coin.png`
|
||||||
|
- 已补充 `bg2` 为 `.bg` 容器第一层底图,并将 `.bg` 改为 100vw/100vh 的隐藏滚动条纵向滚动容器
|
||||||
|
- 已更新 `templateId` 为 `2061370141245067265`,并移除 `page.config.js` 中当前未使用的空接口参数
|
||||||
|
- 已修复 MaskLayer 被 `.bg` 子层级样式覆盖导致遮罩出现在页面下方的问题,并补充规则弹窗文案与样式
|
||||||
|
- 已重新扫描图片并替换前三名头像框与前三名奖励背景的新命名:`top1/top2/top3`、`rewardBgTop1/rewardBgTop2/rewardBgTop3`
|
||||||
|
- 已将 help 弹窗硬编码文案切换为 `src/locales` 多语言 key:`bounty_football_help_*`
|
||||||
|
|
||||||
|
### 7.2 未填写所以跳过
|
||||||
|
|
||||||
|
- 抽奖配置未补充,暂不处理
|
||||||
|
- 其余接口参数当前页面未使用,已从 `page.config.js` 移除
|
||||||
|
|
||||||
|
### 7.3 待你微调
|
||||||
|
|
||||||
|
- 确认规则文案是否需要后续接入多语言翻译 key
|
||||||
|
|
||||||
|
### 7.4 验证结果
|
||||||
|
|
||||||
|
- `npm run build`:通过
|
||||||
|
- 备注:构建通过;仍保留项目既有的 `secondBg.png` 运行时解析提示和 `http.js` 动态/静态导入提示。
|
||||||
Loading…
x
Reference in New Issue
Block a user