2025-10-21 16:50:16 +08:00

948 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="fullPage">
<!-- 状态栏占位区域仅在APP中显示 -->
<div v-if="isInAppEnvironment" style="height: 30px"></div>
<div style="padding: 10px 10px 10px">
<!-- 页面标题 -->
<img :src="images.pageTitle" alt="" width="100%" style="display: block" />
<!-- 弹幕 -->
<!-- <Barrage></Barrage> -->
<!-- 模块切换按钮 -->
<div style="display: flex; justify-content: space-around">
<div style="width: 30%" @click="handleBt('Rank')">
<img :src="rankShow ? images.rankingBtActive : images.rankingBt" alt="" width="100%" />
</div>
<div style="width: 30%" @click="handleBt('Income')">
<img :src="incomeShow ? images.incomeBtActive : images.incomeBt" alt="" width="100%" />
</div>
<div style="width: 30%" @click="handleBt('Lottery')">
<img :src="lotteryShow ? images.lotteryBtActive : images.lotteryBt" alt="" width="100%" />
</div>
</div>
<!-- 排行榜模块 -->
<div
v-if="rankShow"
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
backdrop-filter: blur(32px);
display: flex;
padding: 12px;
flex-direction: column;
gap: 12px;
margin-bottom: 18vw;
"
>
<div style="font-weight: 590">Ranking</div>
<div
v-for="value in 10"
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(32px);
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
"
>
<div style="display: flex; align-items: center; gap: 4px">
<div style="font-weight: 700">999</div>
<img
:src="''"
alt=""
width="15%"
style="border-radius: 50%; aspect-ratio: 1/1; object-fit: cover"
@error="defaultAvatarUrl"
/>
<div style="width: 70%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis">
asdfadsfasfasfda
</div>
</div>
<div style="display: flex; gap: 4px">
<img src="/src/assets/icon/dollar.png" alt="" width="10vw" />
<div style="color: #2df860; font-weight: 500">99999</div>
</div>
</div>
</div>
<!-- 提现模块 -->
<div v-if="incomeShow" style="width: 100%; display: flex; flex-direction: column; gap: 3vw">
<!-- 个人信息 -->
<div
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(32px);
padding: 12px;
display: flex;
align-items: center;
gap: 8px;
"
>
<img
:src="''"
alt=""
width="15%"
style="border-radius: 50%; aspect-ratio: 1/1; object-fit: cover"
@error="defaultAvatarUrl"
/>
<div
style="
width: 85%;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 4px;
"
>
<div style="display: flex; justify-content: space-between; align-items: center">
<div
style="
max-width: 40%;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
"
>
Nanfangjianasdfasdfasdfasdf
</div>
<div style="font-weight: 600">Current amount:$1</div>
</div>
<div style="text-align: end; font-weight: 400; font-size: 0.8em">
Only $9 left to withdraw $10.00
</div>
</div>
</div>
<!-- 标题 -->
<div
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(32px);
padding: 12px;
font-weight: 600;
"
>
Transfer to others
</div>
<!-- 转账功能 -->
<div
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
backdrop-filter: blur(32px);
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px;
"
>
<!-- 转账对象 -->
<div
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
backdrop-filter: blur(32px);
padding: 12px;
"
>
<!-- 基本信息 -->
<div style="display: flex; align-items: center">
<!-- 头像 -->
<div style="position: relative; width: 50px; margin-right: 12px">
<img
:src="selectedPayee.avatar || ''"
:alt="selectedPayee.name"
style="width: 100%; object-fit: cover; border-radius: 50%"
@error="defaultAvatarUrl"
/>
</div>
<!-- 个人信息 -->
<div style="flex: 1">
<div style="margin: 0 0 4px 0; font-weight: 700">
{{ selectedPayee.name || '' }}
</div>
<div style="margin: 0; font-size: 0.9em; font-weight: 500">
ID: {{ selectedPayee.account || '' }}
</div>
</div>
<!-- 换人按钮 -->
<button
style="
border-radius: 12px;
background: linear-gradient(
152deg,
rgba(198, 112, 255, 0.6) 7.01%,
rgba(119, 38, 255, 0.6) 92.99%
);
border: none;
font-weight: 500;
padding: 2px 8px;
"
@click="searchPayee"
>
Change
</button>
</div>
<!-- 身份图标 -->
<div style="margin-left: 50px; display: flex; gap: 5px">
<img v-if="selectedPayee.isHost" src="/src/assets/icon/host.png" alt="" width="30%" />
<img
v-if="selectedPayee.isAgency"
src="/src/assets/icon/agency.png"
alt=""
width="30%"
style="display: block"
/>
<img
v-if="selectedPayee.hasSalary"
src="/src/assets/icon/RA.png"
alt=""
width="30%"
/>
</div>
</div>
<!-- 空状态 - 默认显示 -->
<div style="display: flex; justify-content: center; align-items: center">
<div
style="
font-weight: 590;
padding: 16px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
backdrop-filter: blur(32px);
"
@click="searchPayee"
>
Search
</div>
</div>
<!-- 转账金额选择 -->
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px">
<div
v-for="coin in coinOptions"
:key="coin.id"
@click="selectCoin(coin)"
:class="['coin-item', { selected: selectedCoin === coin.id }]"
>
<img src="/src/assets/icon/dollar.png" alt="" style="width: 40%" />
<div style="font-weight: 500; color: #131111">${{ coin.price }}</div>
</div>
</div>
<!-- 转账按钮 -->
<div style="display: flex; justify-content: center; align-items: center">
<div
style="
width: 70%;
padding: 12px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 32px;
background: linear-gradient(135deg, #bb92ff 2.82%, #8b45ff 99.15%);
font-weight: 590;
"
@click="transfer"
:disabled="!selectedCoin || !selectedPayee.value?.id"
>
Transfer
</div>
</div>
</div>
</div>
<!-- 抽奖模块 -->
<div v-if="lotteryShow" style="width: 100%; display: flex; flex-direction: column; gap: 3vw">
<!-- 抽奖转盘 -->
<div style="position: relative">
<img :src="images.turnTable" alt="" width="100%" style="display: block" />
<div
style="
position: absolute;
inset: 0;
margin: 11% 0 10.5%;
padding: 0 8%;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 4.5%;
"
>
<div
v-for="(prize, index) in prizes"
style="
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
border-width: 2px;
border-style: solid;
"
:style="[
getGridPosition(index),
{ borderColor: activeIndex === index ? '#00ffa3' : 'transparent' },
]"
:class="{ 'active-prize': activeIndex === index }"
>
<img src="/src/assets/icon/coinMore.png" alt="" width="50%" style="display: block" />
<div>{{ prize.value }}</div>
</div>
<!-- 按钮 -->
<div
style="grid-area: 2 / 2; display: flex; justify-content: center; align-items: center"
:disabled="isRolling"
@click="sweepstakes(1)"
></div>
</div>
<!-- 底部饰品 -->
<div
style="
position: absolute;
bottom: -2vw;
left: 0;
width: 100%;
display: flex;
justify-content: space-between;
"
>
<img
:src="images.giftLeft"
alt=""
width="20%"
style="display: block; margin-left: -10px"
/>
<img
:src="images.giftRight"
alt=""
width="20%"
style="display: block; margin-right: -10px"
/>
</div>
</div>
<!-- 抽奖按钮 -->
<div style="display: flex; justify-content: space-around">
<img
:src="images.spin1"
alt=""
width="40%"
@click="sweepstakes(1)"
style="display: block"
/>
<img
:src="images.spin10"
alt=""
width="40%"
@click="sweepstakes(10)"
style="display: block"
/>
</div>
<!-- 抽奖次数 -->
<div
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
backdrop-filter: blur(32px);
padding: 12px;
display: flex;
justify-content: space-between;
"
>
<div style="font-weight: 590">Current raffle tickets:{{ rollTimes }}</div>
<div style="font-weight: 590; font-size: 0.9em">History</div>
</div>
<!-- 任务列表 -->
<div
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
backdrop-filter: blur(32px);
padding: 12px;
display: flex;
flex-direction: column;
gap: 12px;
"
>
<div style="font-weight: 590">Task for Spins</div>
<div
v-for="task in taskList"
style="
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 4px 0 rgba(1, 255, 39, 0.25);
backdrop-filter: blur(32px);
display: flex;
padding: 8px;
justify-content: space-between;
align-items: center;
"
>
<!-- 图标 -->
<div style="width: 20%">
<img
v-if="task.type === 'talk'"
:src="images.talking"
alt=""
width="100%"
style="display: block"
/>
<img
v-if="task.type === 'gift'"
:src="images.gift"
alt=""
width="100%"
style="display: block"
/>
<img
v-if="task.type === 'invite'"
:src="images.invite"
alt=""
width="100%"
style="display: block"
/>
</div>
<!-- 内容 -->
<div style="width: 60%">
<div style="color: rgba(0, 0, 0, 0.8); font-weight: 590">{{ task.title }}</div>
<div style="display: flex; align-items: center; gap: 8px">
<img :src="images.ticket" alt="" width="15%" style="display: block" />
<div style="color: #2f0; font-weight: 590">*1</div>
</div>
</div>
<!-- 按钮 -->
<div style="width: 20%">
<img
:src="images.goToTask"
alt=""
width="100%"
style="display: block"
v-if="task.done === 0"
/>
<img
:src="images.receive"
alt=""
width="100%"
style="display: block"
v-else-if="task.received === 0"
/>
<img :src="images.done" alt="" width="100%" style="display: block" v-else />
</div>
</div>
</div>
</div>
</div>
<!-- 我的排名 -->
<div
v-if="rankShow"
style="
position: fixed;
bottom: 0;
left: 0;
z-index: 999;
width: 100vw;
padding: 12px;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 12px 12px 0 0;
border-top: 1px solid #77ff87;
border-right: 1px solid #77ff87;
border-left: 1px solid #77ff87;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(32px);
"
>
<div style="display: flex; align-items: center; gap: 4px">
<div style="font-weight: 700">999</div>
<img
:src="''"
alt=""
width="15%"
style="border-radius: 50%; aspect-ratio: 1/1; object-fit: cover"
@error="defaultAvatarUrl"
/>
<div style="width: 70%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis">
asdfadsfasfasfda
</div>
</div>
<div style="display: flex; gap: 4px">
<img src="/src/assets/icon/dollar.png" alt="" width="10vw" />
<div style="color: #2df860; font-weight: 500">99999</div>
</div>
</div>
<!-- 弹窗遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<div
style="
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
"
@click.stop
>
<div style="position: relative; width: 90%">
<img :src="images.rewardBg" alt="" width="100%" style="display: block" />
<div
style="
position: absolute;
inset: 23% 10% 10%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
"
>
<div
style="width: 100%; display: flex; justify-content: space-around; align-items: center"
>
<!-- 上一个奖品 -->
<div style="width: 8%">
<img
v-if="result.length > 1 && resultShowIndex > 0"
:src="images.backGift"
alt=""
width="100%"
style="display: block; object-fit: cover"
@click="resultShowIndex--"
/>
</div>
<!-- 奖品 -->
<img src="/src/assets/icon/coinMore.png" alt="" width="50%" style="display: block" />
<!-- 下一个奖品 -->
<div style="width: 8%">
<img
v-if="result.length > 1 && resultShowIndex + 1 < result.length"
:src="images.nextGift"
alt=""
width="100%"
style="display: block; object-fit: cover"
@click="resultShowIndex++"
/>
</div>
</div>
<div
style="
width: 100%;
text-align: center;
font-size: 1.2em;
font-weight: 590;
background: linear-gradient(0deg, #f3b700 0%, #ffeec6 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
position: relative;
"
>
You Get {{ result[resultShowIndex]?.value }}
<div
v-if="result.length > 1"
style="
position: absolute;
z-index: 9999;
inset: 0;
padding-right: 10%;
display: flex;
justify-content: flex-end;
align-items: center;
font-weight: 590;
background: linear-gradient(0deg, #f3b700 0%, #ffeec6 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
"
>
{{ resultShowIndex + 1 }} / {{ result.length }}
</div>
</div>
<img
:src="images.rewardConfirm"
alt=""
width="40%"
@click="closedPopup"
style="display: block"
/>
</div>
</div>
</div>
</maskLayer>
</div>
</template>
<script setup>
import {
connectApplication,
parseAccessOrigin,
parseHeader,
setHttpHeaders,
isInApp,
} from '@/utils/appBridge.js'
import {
appConnectionManager,
isAppConnected,
getAppHeaderInfo,
} from '@/utils/appConnectionManager.js'
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import GeneralHeader from '@/components/GeneralHeader.vue'
import itemCenter from '@/components/WeeklyStar/itemCenter.vue'
import TopUser from '@/components/WeeklyStar/topUser.vue'
import maskLayer from '@/components/MaskLayer.vue'
import { useDebounce, useThrottle } from '@/utils/useDebounce'
import { getMemberProfile } from '@/api/wallet'
import { useRouter } from 'vue-router'
// import Barrage from '@/components/Lottery/Barrage.vue'
const router = useRouter()
// 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 fileName = path.split('/').pop().split('.')[0]
return [fileName, module.default]
})
)
// 奖品数据 (8个位置)
const prizes = ref([
{ value: '奖品1' },
{ value: '奖品2' },
{ value: '奖品3' },
{ value: '奖品4' },
{ value: '奖品5' },
{ value: '奖品6' },
{ value: '奖品7' },
{ value: '奖品8' },
])
const activeIndex = ref(-1) // 当前高亮格子
const isRolling = ref(false) // 抽奖状态
const rollTimes = ref(11) // 当前可抽奖次数
const result = ref([]) // 抽奖结果
const resultShowIndex = ref(0)
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
const maskLayerShow = ref(false)
const rankShow = ref(true)
const incomeShow = ref(false)
const lotteryShow = ref(false)
const selectedCoin = ref(null)
// 点击展示主体内容
const handleBt = (type) => {
rankShow.value = type == 'Rank' ? true : false
incomeShow.value = type == 'Income' ? true : false
lotteryShow.value = type == 'Lottery' ? true : false
}
const taskList = ref([
{ type: 'talk', title: 'On the mic(0/15mins)', done: 1, received: 1, num: 1 },
{ type: 'talk', title: 'On the mic(0/30mins)', done: 1, received: 0, num: 1 },
{ type: 'talk', title: 'On the mic(0/1hours)', done: 0, received: 0, num: 1 },
{ type: 'talk', title: 'On the mic(0/2hours)', done: 0, received: 0, num: 1 },
{ type: 'gift', title: 'Send a gift', done: 1, received: 1, num: 1 },
{ type: 'gift', title: 'Send three gifts', done: 0, received: 0, num: 1 },
{ type: 'gift', title: 'Send ten gifts', done: 0, received: 0, num: 1 },
{ type: 'gift', title: 'Send fifteen gifts', done: 0, received: 0, num: 1 },
{ type: 'invite', title: 'Invite a user', done: 1, received: 1, num: 1 },
{ type: 'invite', title: 'Invite three users', done: 0, received: 0, num: 1 },
])
// 选中的收款人
const selectedPayee = ref({
id: null,
account: '3232131',
name: 'asdfasdfasdfas',
avatar: '',
type: '',
isHost: true,
isAgency: true,
hasSalary: true,
})
const searchPayee = () => {
router.push('/search-payee')
}
// 转账金币选项
const coinOptions = ref([
{ id: 1, amount: 10500, price: 1 },
{ id: 2, amount: 52500, price: 5 },
{ id: 3, amount: 105000, price: 10 },
])
//选择金币
const selectCoin = (coin) => {
// 单选逻辑:如果已经选中相同的金币,则取消选择;否则选中新的金币
if (selectedCoin.value === coin.id) {
selectedCoin.value = null
} else {
selectedCoin.value = coin.id
}
}
const transfer = async () => {
if (!selectedCoin.value) {
showError('Please select an amount first')
return
}
if (!selectedPayee.value?.id) {
showError('Please select a payee first')
return
}
const selectedCoinData = coinOptions.find((coin) => coin.id === selectedCoin.value)
// 获取支付密码
// const password = prompt('Please enter your payment password:')
// if (!password) {
// showError('Payment password is required')
// return
// }
// 构造请求参数
const transferData = {
amount: selectedCoinData.price,
acceptUserId: selectedPayee.value?.id,
acceptMethod: 'BANK_TRANSFER',
// password: password
}
try {
// 调用转账接口
const response = await userBankTransfer(transferData)
if (response.status) {
showSuccess(
`Successfully transferred $${selectedCoinData.price} to ${selectedPayee.value?.name}`
)
} else {
showError(`Failed to transfer $${selectedCoinData.price}`)
}
// 重置选择
selectedCoin.value = null
// 刷新银行余额
await fetchBankBalance()
} catch (error) {
// 错误处理
console.error('Transfer failed:', error)
if (error.errorCode === 5000) {
showError('Insufficient balance')
} else if (error.errorCode === 5010 || error.errorCode === 5009) {
showError('Payment password error')
} else {
showError('Transfer failed, please try again later')
}
}
}
const closedPopup = () => {
maskLayerShow.value = false
result.value = []
resultShowIndex.value = 0
}
const defaultAvatarUrl = (e) => {
console.log('头像资源出错')
e.target.onerror = null //防止循环
e.target.src = new URL('/src/assets/images/WeeklyStar/defaultAvatar.png', import.meta.url).href
}
const getGridPosition = (index) => {
//顺时针位置
const positions = [
{ gridArea: '1/1' }, //上左
{ gridArea: '1/2' }, //上中
{ gridArea: '1/3' }, //上右
{ gridArea: '2/3' }, //中右
{ gridArea: '3/3' }, //下右
{ gridArea: '3/2' }, //下中
{ gridArea: '3/1' }, //下左
{ gridArea: '2/1' }, //中左
]
return positions[index]
}
// 抽奖
const startLottery = (resultIndex) => {
return new Promise((resolve) => {
if (isRolling.value) return // 防止重复点击
isRolling.value = true
activeIndex.value = -1
let speed = 200 // 初始速度
let currentCycle = 0 // 当前圈数
let constantSpeedCycle = 0 // 当前匀速圈数
let totalCycles = 1 // 至少旋转2圈数
const roll = () => {
activeIndex.value = (activeIndex.value + 1) % prizes.value.length
if (activeIndex.value === resultIndex && speed > 200) {
isRolling.value = false
setTimeout(() => {
resolve() // 抽奖结束
}, 500)
} else {
if (activeIndex.value === prizes.value.length - 1) {
currentCycle++
}
// 加速旋转
if (currentCycle < totalCycles) {
speed -= 20
} else {
// 匀速旋转
if (activeIndex.value === prizes.value.length - 1) {
constantSpeedCycle++
}
if (constantSpeedCycle > 2) {
// 最后减速效果
speed += 20
}
}
setTimeout(roll, speed)
}
}
roll()
})
}
// 连抽
const sweepstakes = async (consecutive) => {
// 检查抽奖次数
if (rollTimes.value >= consecutive) {
// 抽奖次数
for (let i = 0; i < consecutive; i++) {
const item = { value: `奖品${Math.floor(Math.random() * 8) + 1}` } //接口获取的抽奖结果
const index = prizes.value.findIndex((prizes) => prizes.value === item.value) //转盘第几位停下
console.log('index:', index)
await startLottery(index)
result.value.push(item)
}
rollTimes.value -= consecutive
maskLayerShow.value = true
}
}
onMounted(() => {
isInAppEnvironment.value = isInApp()
})
</script>
<style lang="scss" scoped>
* {
color: #fff;
font-family: 'SF Pro Text';
}
.fullPage {
width: 100vw;
min-height: 100vh;
background: #1f5631;
background-image: url(@/assets/images/Lottery/bg.png);
background-size: 100% auto;
background-repeat: no-repeat;
position: relative;
}
.active-prize {
border-radius: 8px;
box-shadow: 0 51px 80px 0 rgba(56, 253, 182, 0.49), 0 21.307px 33.422px 0 rgba(56, 253, 182, 0.35),
0 11.392px 17.869px 0 rgba(56, 253, 182, 0.29), 0 6.386px 10.017px 0 rgba(56, 253, 182, 0.25),
0 3.392px 5.32px 0 rgba(56, 253, 182, 0.2), 0 1.411px 2.214px 0 rgba(56, 253, 182, 0.14);
}
/* 金币网格 */
.coin-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 2px solid transparent;
transition: all 0.2s;
aspect-ratio: 1/1;
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
backdrop-filter: blur(32px);
}
.coin-item.selected {
border-color: #8b5cf6;
}
.coin-item:active {
transform: scale(0.98);
}
@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>