feat(抽奖活动页): 对接语言切换功能并调整布局

This commit is contained in:
hzj 2025-11-03 18:28:29 +08:00
parent 491d44c452
commit 632380ce42
11 changed files with 312 additions and 84 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@ -16,13 +16,8 @@
class="barrage-item"
style="
position: absolute;
left: 0;
border-radius: 48px;
padding: 4px;
transform: translateX(100vw);
display: flex;
align-items: center;
gap: 4px;
@ -65,6 +60,9 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
const props = defineProps({
barrageList: {
@ -113,18 +111,32 @@ const defaultAvatarUrl = (e) => {
}
//
const getBarrageStyle = (item) => ({
animationDuration: `${item.duration}s`,
animationDelay: `${item.delay}s`,
const getBarrageStyle = (item) => {
const isRTL = locale.value === 'ar'
'--start-pos': `${item.startPos}px`,
border: item.type != '' ? '0.667px solid #FFF677' : '0.667px solid #77FF87',
background:
item.type != ''
? 'linear-gradient(270deg, rgba(218, 103, 2, 0.60) -16.85%, rgba(124, 41, 0, 0.60) 82.7%)'
: 'linear-gradient(270deg, rgba(2, 218, 60, 0.60) -16.85%, rgba(0, 124, 54, 0.60) 82.7%)',
})
return {
animationDuration: `${item.duration}s`,
animationDelay: `${item.delay}s`,
'--start-pos': `${item.startPos}px`,
border: item.type != '' ? '0.667px solid #FFF677' : '0.667px solid #77FF87',
background:
item.type != ''
? 'linear-gradient(270deg, rgba(218, 103, 2, 0.60) -16.85%, rgba(124, 41, 0, 0.60) 82.7%)'
: 'linear-gradient(270deg, rgba(2, 218, 60, 0.60) -16.85%, rgba(0, 124, 54, 0.60) 82.7%)',
// RTL
...(isRTL
? {
right: '0',
left: 'auto',
transform: 'translateX(-100vw)',
}
: {
left: '0',
right: 'auto',
transform: 'translateX(100vw)',
}),
}
}
//
const generateBarrageProps = (item) => {
@ -229,12 +241,27 @@ watch(
will-change: transform;
animation: moveLeft linear forwards;
}
@keyframes moveLeft {
0% {
transform: translateX(100vw);
}
100% {
transform: translateX(calc(-100% - var(--start-pos, 0px)));
transform: translateX(-100%);
}
}
/* RTL支持 - 镜像弹幕动画 */
[dir='rtl'] .barrage-item {
animation: moveRight linear forwards;
}
@keyframes moveRight {
0% {
transform: translateX(-100vw);
}
100% {
transform: translateX(100%);
}
}

View File

@ -231,5 +231,28 @@
"invite_user_to_become_bd": "دعوة المستخدم ليصبح BD",
"under_review": "قيد المراجعة",
"approved": "موافق عليه"
"approved": "موافق عليه",
"lucky_dollar": "دولار محظوظ",
"ranking": "الترتيب",
"lottery": "سحب القرعة",
"history": "التاريخ",
"current_earnings": "الأرباح الحالية: {amount}",
"transfer_to_recharge_agent": "تحويل الأموال إلى وكيل الشحن",
"select": "اختيار",
"go_to_withdraw": "اذهب للسحب",
"ticket": "تذكرة اليانصيب",
"current_raffle_tickets": "تذاكر اليانصيب الحالية: {count}",
"task_for_tickets": "احصل على تذاكر اليانصيب من خلال المهام",
"minutes_short": "دقيقة",
"hours_short": "ساعة",
"on_mic_progress": "في وقت الماك ({current}/{total} {unit})",
"send_gift_progress": "أرسل {current}/{total} هدايا",
"invite_user_progress": "ادعو {current}/{total} مستخدمين",
"cash_out_rule_1": "1. يمكن للمستخدمين سحب أموالهم عندما تصل أرباحهم بالدولار إلى 10 دولارات.",
"cash_out_rule_2": "2. يمكن للمستخدمين أيضًا التبديل أو التحويل إلى وكيل شحن للحصول على عوائد أعلى من عمليات السحب النقدي",
"cash_out_rule_3": "3. إذا لم يقم المستخدم بتنفيذ أي عملية قبل انتهاء الحدث، سيُعتبر ذلك أنهم استبعدوا الفرصة لاستلام الدولارات، وسيتم مسح الدولارات التي استلموها بواسطة النظام.",
"withdrawable_amount": "المبلغ القابل للسحب: {amount}",
"fill_info_tips": "يرجى ملء المعلومات بعناية. يمكنك سحب النقدي فقط بعد اكتمال المعلومات.",
"card_issue_tips": "* إذا كان هناك مشكلة في رقم البطاقة ولا يمكن إجراء التحويل، فإن ذلك سيؤدي إلى رفض المراجعة."
}

View File

@ -231,5 +231,28 @@
"invite_user_to_become_bd": "Invite User To Become BD",
"under_review": "Under review",
"approved": "Approved"
"approved": "Approved",
"lucky_dollar": "Lucky Dollar",
"ranking": "Ranking",
"lottery": "Lottery",
"history": "history",
"current_earnings": "Current earnings: {amount}",
"transfer_to_recharge_agent": "Transfer to recharge agent",
"select": "Select",
"go_to_withdraw": "Go to withdraw",
"ticket": "Ticket",
"current_raffle_tickets": "Current raffle tickets: {count}",
"task_for_tickets": "Task for tickets",
"minutes_short": "mins",
"hours_short": "hours",
"on_mic_progress": "On the mic ({current}/{total} {unit})",
"send_gift_progress": "Send {current}/{total} gifts",
"invite_user_progress": "Invite {current}/{total} users",
"cash_out_rule_1": "1. Users can cash out when their dollar earnings reach $10.",
"cash_out_rule_2": "2. Users can also exchange or transfer to a recharge agent to obtain higher returns than cash withdrawals",
"cash_out_rule_3": "3. If the user does not perform any operation before the end of the event, it will be deemed that they have given up the opportunity to receive the dollars, and the dollars they have received will be cleared by the system.",
"withdrawable_amount": "Withdrawable amount: {amount}",
"fill_info_tips": "Please fill in the information carefully. You can only withdraw cash after the information is completed.",
"card_issue_tips": "* If there is a problem with the card number and the transfer cannot be made, it will result in the review being rejected."
}

View File

@ -231,5 +231,28 @@
"invite_user_to_become_bd": "邀请用户成为BD",
"under_review": "审核中",
"approved": "已批准"
"approved": "已批准",
"lucky_dollar": "幸运美元",
"ranking": "排行",
"lottery": "抽奖",
"history": "历史",
"current_earnings": "当前收益:{amount}",
"transfer_to_recharge_agent": "转账至充值代理",
"select": "选择",
"go_to_withdraw": "去提现",
"ticket": "抽奖券",
"current_raffle_tickets": "当前抽奖券:{count}",
"task_for_tickets": "通过任务获取抽奖券",
"minutes_short": "分钟",
"hours_short": "小时",
"on_mic_progress": "在麦时长({current}/{total}{unit}",
"send_gift_progress": "送{current}/{total}个礼物",
"invite_user_progress": "邀请{current}/{total}个用户",
"cash_out_rule_1": "1.当用户的美元收益达到 10 美元时,即可提现。",
"cash_out_rule_2": "2.用户还可以将资金兑换或转账给充值代理商,以获得比提现更高的收益。",
"cash_out_rule_3": "3.如果用户在活动结束前没有进行任何操作,则视为其放弃了获得美元的机会,并且其已收到的美元将被系统清空。",
"withdrawable_amount": "可提取金额:{amount}",
"fill_info_tips": "请仔细填写信息。信息填写完整后才能取款。",
"card_issue_tips": "*如果卡号有问题导致转账无法完成,则审核将被拒绝。"
}

View File

@ -7,10 +7,7 @@
<img :src="images.pageTitle" alt="" width="100%" style="display: block" />
<!-- 弹幕 -->
<Barrage
style="width: 100vw; position: relative; left: -10px; margin: 10px 0"
:barrageList="barrageList"
></Barrage>
<Barrage class="barrage-container" style="width: 100vw" :barrageList="barrageList"></Barrage>
<!-- 模块切换按钮 -->
<div style="display: flex; justify-content: space-around">
@ -41,7 +38,7 @@
margin-bottom: 18vw;
"
>
<div style="font-weight: 590">Ranking</div>
<div style="font-weight: 590">{{ t('ranking') }}</div>
<div
v-for="user in ranking"
style="
@ -58,7 +55,7 @@
<div style="font-weight: 700">{{ user.rank }}</div>
<img
:src="user.avatar || ''"
alt=""
:alt="user.nickname || ''"
width="15%"
style="display: block; border-radius: 50%; aspect-ratio: 1/1; object-fit: cover"
@error="defaultAvatarUrl"
@ -99,13 +96,13 @@
>
<img
src="/src/assets/icon/helpWhite.png"
class="help-btn"
alt=""
style="display: block; width: 16px; position: absolute; top: 4px; right: 8px"
@click="helpShow = true"
/>
<img
:src="userInfo.userAvatar || ''"
alt=""
:alt="userInfo.userNickname || ''"
width="15%"
style="border-radius: 50%; aspect-ratio: 1/1; object-fit: cover"
@error="defaultAvatarUrl"
@ -132,7 +129,7 @@
{{ userInfo.userNickname || '' }}
</div>
<div style="font-weight: 600">
Current amount:${{ currentAmount.availableAmount }}
{{ t('current_earnings', { amount: currentAmount.availableAmount }) }}
</div>
</div>
<!-- <div style="text-align: end; font-weight: 400; font-size: 0.8em">
@ -160,8 +157,9 @@
@click="drawerShowBt('transfer')"
v-if="userIdentity.anchor"
>
<div>Transfer To Recharge Agent</div>
<div>{{ t('transfer_to_recharge_agent') }}</div>
<img
class="flipImg"
src="/src/assets/icon/arrowWhite.png"
alt=""
style="width: 1em; display: block; transition: all 0.2s ease"
@ -197,7 +195,7 @@
<div style="position: relative; width: 50px; margin-right: 12px">
<img
:src="selectedPayee.avatar || ''"
:alt="selectedPayee.name"
:alt="selectedPayee.name || ''"
style="width: 100%; object-fit: cover; border-radius: 50%"
@error="defaultAvatarUrl"
/>
@ -226,7 +224,7 @@
"
@click="searchPayee"
>
Change
{{ t('change') }}
</button>
</div>
<!-- 身份图标 -->
@ -279,7 +277,7 @@
"
@click="searchPayee"
>
Select
{{ t('select') }}
</div>
</div>
@ -312,7 +310,7 @@
@click="transfer"
:disabled="!selectedCoin || !selectedPayee.value?.id"
>
Transfer
{{ t('transfer') }}
</div>
</div>
</div>
@ -323,8 +321,9 @@
style="display: flex; justify-content: space-between; align-items: center"
@click="drawerShowBt('exchange')"
>
<div>Exchange Gold Coins</div>
<div>{{ t('exchange_gold_coins') }}</div>
<img
class="flipImg"
src="/src/assets/icon/arrowWhite.png"
alt=""
style="width: 1em; display: block; transition: all 0.2s ease"
@ -373,7 +372,7 @@
:disabled="!selectedCoin || !selectedPayee.value?.id"
@click="exchangeCoinBt"
>
Exchange
{{ t('exchange') }}
</div>
</div>
</div>
@ -385,8 +384,13 @@
@click="goToWithdraw"
v-if="currentAmount.availableAmount >= 10"
>
<div>Go to withdraw</div>
<img src="/src/assets/icon/arrowWhite.png" alt="" style="width: 1em; display: block" />
<div>{{ t('go_to_withdraw') }}</div>
<img
class="flipImg"
src="/src/assets/icon/arrowWhite.png"
alt=""
style="width: 1em; display: block"
/>
</div>
</div>
</div>
@ -425,7 +429,12 @@
]"
:class="{ 'active-prize': activeIndex === index }"
>
<img :src="prize.prizeImage || ''" alt="" width="50%" style="display: block" />
<img
:src="prize.prizeImage || ''"
:alt="prize.prizeName || ''"
width="50%"
style="display: block"
/>
<div style="font-size: 0.8em">{{ prize.prizeName }}</div>
</div>
@ -492,8 +501,12 @@
justify-content: space-between;
"
>
<div style="font-weight: 590">Current raffle tickets:{{ rollTimes }}</div>
<div style="font-weight: 590; font-size: 0.9em" @click="historyShow = true">History</div>
<div style="font-weight: 590">
{{ t('current_raffle_tickets', { count: rollTimes }) }}
</div>
<div style="font-weight: 590; font-size: 0.9em" @click="historyShow = true">
{{ t('history') }}
</div>
</div>
<!-- 任务列表 -->
@ -510,7 +523,7 @@
gap: 12px;
"
>
<div style="font-weight: 590">Task for Spins</div>
<div style="font-weight: 590">{{ t('task_for_tickets') }}</div>
<div
v-for="task in taskList"
style="
@ -559,7 +572,7 @@
</div>
</div>
<div style="display: flex; align-items: center; gap: 8px">
<img :src="images.ticket" alt="" width="15%" style="display: block" />
<img :src="images.ticket" :alt="t('ticket')" width="15%" style="display: block" />
<div style="color: #2f0; font-weight: 590">*{{ task.rewardValue }}</div>
</div>
</div>
@ -569,7 +582,6 @@
<img
v-if="task.taskStatus == 0"
:src="images.goToTask"
alt=""
width="100%"
style="display: block"
/>
@ -577,13 +589,13 @@
<img
v-else-if="task.taskStatus === 1"
:src="images.receive"
alt=""
:alt="t('receive')"
width="100%"
style="display: block"
@click="receiveTaskReward(task.taskCode)"
/>
<!-- 已完成 -->
<img :src="images.done" alt="" width="100%" style="display: block" v-else />
<img :src="images.done" :alt="t('done')" width="100%" style="display: block" v-else />
</div>
</div>
</div>
@ -617,7 +629,7 @@
<div style="font-weight: 700">{{ userInfo.rank || 999 }}</div>
<img
:src="userInfo.userAvatar || ''"
alt=""
:alt="userInfo.userNickname || ''"
width="15%"
style="border-radius: 50%; aspect-ratio: 1/1; object-fit: cover"
@error="defaultAvatarUrl"
@ -631,14 +643,14 @@
<img
src="/src/assets/icon/dollar.png"
alt=""
style="display: block; object-fit: cover; width: 5vw"
style="display: block; object-fit: cover; width: 6vw"
/>
<div style="color: #2df860; font-weight: 500">{{ userInfo.totalAmount || 0 }}</div>
</div>
</div>
<!-- 弹窗遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<maskLayer :maskLayerShow="maskLayerShow">
<div
style="
width: 100%;
@ -647,10 +659,10 @@
justify-content: center;
align-items: center;
"
@click.stop
@click="closedPopup"
>
<!-- 抽奖结果 -->
<div v-if="resultShow" style="position: relative; width: 90%">
<div v-if="resultShow" style="position: relative; width: 90%" @click.stop>
<img :src="images.rewardBg" alt="" width="100%" style="display: block" />
<div
style="
@ -679,7 +691,7 @@
<!-- 奖品 -->
<img
:src="result[resultShowIndex]?.prize?.prizeImage"
alt=""
:alt="result[resultShowIndex]?.prize?.prizeName || ''"
width="50%"
style="display: block"
/>
@ -709,7 +721,7 @@
position: relative;
"
>
You Get {{ result[resultShowIndex]?.prize?.prizeName }}
{{ t('you_get', { prize: result[resultShowIndex]?.prize?.prizeName }) }}
<div
v-if="result.length > 1"
style="
@ -735,7 +747,7 @@
</div>
<img
:src="images.rewardConfirm"
alt=""
:alt="t('confirm')"
width="40%"
@click="closedPopup"
style="display: block"
@ -744,7 +756,7 @@
</div>
<!-- 中奖历史 -->
<div v-if="historyShow" style="position: relative; width: 90%">
<div v-if="historyShow" style="position: relative; width: 90%" @click.stop>
<img
src="/src/assets/images/Lottery/historyBg.png"
alt=""
@ -753,15 +765,8 @@
/>
<img
src="/src/assets/images/Lottery/cancel.png"
alt=""
style="
display: block;
width: 10vw;
position: absolute;
top: 4vw;
right: 4vw;
z-index: 99999;
"
:alt="t('cancel')"
class="cancel-bt"
@click="closedPopup"
/>
<div style="position: absolute; inset: 0; padding: 20% 3% 3%">
@ -793,11 +798,11 @@
<div style="display: flex; align-items: center; gap: 4px">
<img
:src="record.prize.prizeImage || ''"
alt=""
:alt="record.prize.prizeName || ''"
style="width: 10vw; object-fit: cover; display: block; aspect-ratio: 1/1"
/>
<div style="font-weight: 590; font-size: 0.9em">
{{ record.prize.prizeName }} x1
{{ record.prize.prizeName }}
</div>
</div>
<div style="font-weight: 510; font-size: 0.8em">
@ -809,12 +814,9 @@
</div>
<!-- 帮助弹窗 -->
<div v-if="helpShow" style="position: relative; width: 90%">
<div v-if="helpShow" style="position: relative; width: 90%" @click.stop>
<img :src="images.helpInfo" alt="" width="100%" style="display: block" />
<div
style="position: absolute; width: 8%; aspect-ratio: 1/1; top: 7%; right: 7%"
@click="closedPopup"
></div>
<div class="close-btn" @click="closedPopup"></div>
</div>
</div>
</maskLayer>
@ -848,20 +850,45 @@ import {
import Barrage from '@/components/Lottery/Barrage.vue'
import { setUserInfo } from '@/utils/userStore.js'
import { showError, showWarning, showInfo, showSuccess } from '@/utils/toast.js'
import { useI18n } from 'vue-i18n'
import { setDocumentDirection } from '@/locales/i18n'
const { t, locale } = useI18n()
const router = useRouter()
//
locale.value && setDocumentDirection(locale.value)
// vite
const imageModules = import.meta.glob('@/assets/images/Lottery/*.{jpg,png,svg}', {
eager: true,
})
//
const images = Object.fromEntries(
Object.entries(imageModules).map(([path, module]) => {
const images = computed(() => {
const imageMap = {}
const rtlImageMap = {}
// RTL
Object.entries(imageModules).forEach(([path, module]) => {
const fileName = path.split('/').pop().split('.')[0]
return [fileName, module.default]
imageMap[fileName] = module.default
// _arRTL
if (fileName.endsWith('_ar')) {
const baseName = fileName.slice(0, -3)
rtlImageMap[baseName] = module.default
}
})
)
// RTL使_ar
if (locale.value === 'ar') {
// _ar_ar
return { ...imageMap, ...rtlImageMap }
}
return imageMap
})
//
const barrageList = ref([])
@ -932,9 +959,13 @@ const showTarget = (task) => {
//
currentValue = Math.floor(currentValue)
targetValue = Math.floor(targetValue)
let timeUnit = timeType ? 'mins' : 'hours'
let timeUnit = timeType ? t('minutes_short') : t('hours_short')
return `On the mic(${currentValue}/${targetValue}${timeUnit})`
return t('on_mic_progress', {
current: currentValue,
total: targetValue,
unit: timeUnit,
})
}
//
@ -995,12 +1026,12 @@ const selectCoin = (coin) => {
//
const transfer = async () => {
if (!selectedCoin.value) {
showError('Please select an amount first')
showError(t('please_select_amount'))
return
}
if (!selectedPayee.value?.id) {
showError('Please select a payee first')
showError(t('please_select_payee'))
return
}
@ -1016,10 +1047,13 @@ const transfer = async () => {
const response = await transferActivityDollar(transferData)
if (response.status) {
showSuccess(
`Successfully transferred $${selectedCoin.value.price} to ${selectedPayee.value?.name}`
t('transfer_success', {
price: selectedCoin.value.price,
name: selectedPayee.value?.name,
})
)
} else {
showError(`Failed to transfer $${selectedCoin.value.price}`)
showError(t('transfer_failed', { price: selectedCoin.value.price }))
}
//
@ -1034,7 +1068,7 @@ const transfer = async () => {
//
const exchangeCoinBt = async () => {
if (!selectedCoin.value) {
showError('Please select an amount first')
showError(t('please_select_amount'))
return
}
@ -1048,9 +1082,9 @@ const exchangeCoinBt = async () => {
//
const response = await exchangeCoin(transferData)
if (response.status) {
showSuccess(`Successfully exchange ${selectedCoin.value.amount} coins`)
showSuccess(t('exchange_success_coins', { amount: selectedCoin.value.amount }))
} else {
showError(`Failed to exchange ${selectedCoin.value.amount} coins`)
showError(t('exchange_failed_coins', { amount: selectedCoin.value.amount }))
}
//
@ -1404,6 +1438,37 @@ onUnmounted(() => {
opacity: 0;
}
.help-btn {
display: block;
width: 16px;
position: absolute;
top: 4px;
right: 8px;
}
.close-btn {
position: absolute;
width: 8%;
aspect-ratio: 1/1;
top: 7%;
right: 7%;
}
.cancel-bt {
display: block;
width: 10vw;
position: absolute;
top: 4vw;
right: 4vw;
z-index: 99999;
}
.barrage-container {
position: relative;
left: -10px;
margin: 10px 0;
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
@ -1427,4 +1492,71 @@ onUnmounted(() => {
font-size: 32px;
}
}
/* RTL支持 */
[dir='rtl'] .section-title {
text-align: right;
}
[dir='rtl'] .avatar {
margin-right: 0;
margin-left: 12px;
}
[dir='rtl'] .barrage-container {
right: -10px;
left: auto;
}
[dir='rtl'] .flipImg {
transform: scaleX(-1);
}
[dir='rtl'] .help-btn {
left: 8px;
right: auto;
}
[dir='rtl'] .cancel-bt {
left: 4vw;
right: auto;
}
[dir='rtl'] .close-btn {
left: 7%;
right: auto;
}
[dir='rtl'] .flipImg.rotated {
transform: scaleX(-1) rotate(90deg);
}
[dir='rtl'] .title-icon {
margin-left: 0;
margin-right: 10px;
}
[dir='rtl'] .user-header {
/* flex-direction: row-reverse; */
}
[dir='rtl'] .user-info {
text-align: right;
}
[dir='rtl'] .salary-header {
/* flex-direction: row-reverse; */
}
[dir='rtl'] .task-header {
/* flex-direction: row-reverse; */
}
[dir='rtl'] .task-content {
/* flex-direction: row-reverse; */
}
[dir='rtl'] .action-buttons {
grid-template-columns: 1fr 1fr;
}
</style>