1489 lines
44 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
style="width: 100vw; position: relative; left: -10px; margin: 10px 0"
:barrageList="barrageList"
></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="user in ranking"
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">{{ user.rank }}</div>
<img
:src="user.avatar || ''"
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">
{{ user.nickname || '' }}
</div>
</div>
<div style="display: flex; gap: 4px">
<img
src="/src/assets/icon/dollar.png"
alt=""
style="display: block; object-fit: cover; width: 5vw"
/>
<div style="color: #2df860; font-weight: 500">{{ user.totalAmount }}</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: 20px 12px 12px;
display: flex;
align-items: center;
gap: 8px;
position: relative;
"
>
<img
src="/src/assets/icon/helpWhite.png"
alt=""
style="display: block; width: 16px; position: absolute; top: 4px; right: 8px"
@click="helpShow = true"
/>
<img
:src="userInfo.userAvatar || ''"
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;
"
>
{{ userInfo.userNickname || '' }}
</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;
display: flex;
flex-direction: column;
gap: 8px;
"
>
<!-- 转账功能 -->
<div
style="display: flex; justify-content: space-between; align-items: center"
@click="drawerShowBt('transfer')"
v-if="userIdentity.anchor"
>
<div>Transfer To Recharge Agent</div>
<img
src="/src/assets/icon/arrowWhite.png"
alt=""
style="width: 1em; display: block; transition: all 0.2s ease"
:class="{ rotated: transferShow }"
/>
</div>
<!-- 转账抽屉 -->
<transition name="drawer">
<div
v-if="transferShow"
style="
background-color: transparent;
display: flex;
flex-direction: column;
gap: 12px;
"
>
<!-- 转账对象 -->
<div
v-if="selectedPayee.id"
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
v-else
style="
display: flex;
justify-content: center;
align-items: center;
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 0;
"
>
<div
style="
font-weight: 590;
padding: 16px;
border-radius: 8px;
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"
>
Select
</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>
</transition>
<!-- 兑换金币 -->
<div
style="display: flex; justify-content: space-between; align-items: center"
@click="drawerShowBt('exchange')"
>
<div>Exchange Gold Coins</div>
<img
src="/src/assets/icon/arrowWhite.png"
alt=""
style="width: 1em; display: block; transition: all 0.2s ease"
:class="{ rotated: exchangeShow }"
/>
</div>
<!-- 兑换金币抽屉 -->
<transition name="drawer">
<div
v-if="exchangeShow"
style="
background-color: transparent;
display: flex;
flex-direction: column;
gap: 12px;
"
>
<!-- 转账金额选择 -->
<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/coin.png" alt="" style="width: 40%" />
<div style="font-size: 0.7em">{{ coin.amount }} coins x1</div>
<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;
"
:disabled="!selectedCoin || !selectedPayee.value?.id"
>
Exchange
</div>
</div>
</div>
</transition>
<!-- 提取现金 -->
<div style="display: flex; justify-content: space-between; align-items: center">
<div>Go to withdraw</div>
<img src="/src/assets/icon/arrowWhite.png" alt="" style="width: 1em; display: block" />
</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 activity.prizeList"
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="prize.prizeImage || ''" alt="" width="50%" style="display: block" />
<div style="font-size: 0.8em">{{ prize.prizeName }}</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" @click="historyShow = true">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.taskType == 1"
:src="images.talking"
alt=""
width="100%"
style="display: block"
/>
<img
v-if="task.taskType == 2"
:src="images.gift"
alt=""
width="100%"
style="display: block"
/>
<img
v-if="task.taskType == 3"
:src="images.invite"
alt=""
width="100%"
style="display: block"
/>
</div>
<!-- 内容 -->
<div style="width: 60%">
<div>
<div v-if="task.taskType == 1" style="color: rgba(0, 0, 0, 0.8); font-weight: 590">
{{ showTarget(task) }}
</div>
<div v-else style="color: rgba(0, 0, 0, 0.8); font-weight: 590">
{{ task.taskName }}
</div>
</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">*{{ task.rewardValue }}</div>
</div>
</div>
<!-- 按钮 -->
<div style="width: 20%">
<!-- 前往任务 -->
<img
v-if="task.taskStatus == 0"
:src="images.goToTask"
alt=""
width="100%"
style="display: block"
/>
<!-- 领取抽奖券 -->
<img
v-else-if="task.taskStatus === 1"
:src="images.receive"
alt=""
width="100%"
style="display: block"
@click="receiveTaskReward(task.taskCode)"
/>
<!-- 已完成 -->
<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">{{ userInfo.rank || 999 }}</div>
<img
:src="userInfo.userAvatar || ''"
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">
{{ userInfo.userNickname || '' }}
</div>
</div>
<div style="display: flex; gap: 4px">
<img
src="/src/assets/icon/dollar.png"
alt=""
style="display: block; object-fit: cover; width: 5vw"
/>
<div style="color: #2df860; font-weight: 500">{{ userInfo.totalAmount || 0 }}</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 v-if="resultShow" 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="result[resultShowIndex]?.prize?.prizeImage"
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]?.prize?.prizeName }}
<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 v-if="historyShow" style="position: relative; width: 90%">
<img
src="/src/assets/images/Lottery/historyBg.png"
alt=""
width="100%"
style="display: block"
/>
<img
src="/src/assets/images/Lottery/cancel.png"
alt=""
style="
display: block;
width: 10vw;
position: absolute;
top: 4vw;
right: 4vw;
z-index: 99999;
"
@click="closedPopup"
/>
<div style="position: absolute; inset: 0; padding: 20% 3% 3%">
<div
style="
width: 100%;
height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
"
class="scrollbar"
>
<div
v-for="(record, index) in myRecords"
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;
justify-content: space-between;
align-items: center;
"
>
<div style="display: flex; align-items: center; gap: 4px">
<img
:src="record.prize.prizeImage || ''"
alt=""
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
</div>
</div>
<div style="font-weight: 510; font-size: 0.8em">
{{ formatUTCCustom(record.drawTime) }}
</div>
</div>
</div>
</div>
</div>
<!-- 帮助弹窗 -->
<div v-if="helpShow" style="position: relative; width: 90%">
<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>
</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 maskLayer from '@/components/MaskLayer.vue'
import { useDebounce, useThrottle } from '@/utils/useDebounce'
import { formatUTCCustom } from '@/utils/utcFormat.js'
import { getMemberProfile, getUserIdentity } from '@/api/wallet'
import { getSelectedPayee, clearSelectedPayee } from '@/utils/payeeStore.js'
import { useRouter } from 'vue-router'
import {
ranklist, //获取排行榜
validActivity, // 获取有效活动
activityDetail, // 获取活动详情
myTickets, // 获取剩余中奖券
onceDraw, // 执行单次抽奖
multiDraw, // 执行连抽
drawRecords, // 获取中奖记录
ActTaskList, // 获取任务列表
receiveTickets, // 领取任务奖励
winnerHistory, //获取历史中奖用户
} from '@/api/lottery'
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]
})
)
// 弹幕数据(示例)
const barrageList = ref([])
// 活动详情
const activity = ref({}) //活动详情和奖池
const userInfo = ref({}) //用户信息
const userIdentity = ref({}) //用户身份
const ranking = ref([]) //奖品排名
const activeIndex = ref(-1) // 当前高亮格子
const isRolling = ref(false) // 抽奖状态
const rollTimes = ref(0) // 当前可抽奖次数
const result = ref([]) // 抽奖结果
const resultShowIndex = ref(0)
const myRecords = ref([]) // 抽奖结果
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
const resultShow = ref(false)
const helpShow = ref(false)
const historyShow = ref(false)
const maskLayerShow = computed(() => {
return helpShow.value || resultShow.value || historyShow.value
})
const rankShow = ref(true)
const incomeShow = ref(false)
const lotteryShow = ref(false)
const transferShow = ref(false)
const exchangeShow = ref(false)
const withdrawShow = ref(false)
const selectedCoin = ref(null)
const appConnected = ref(false)
const headerInfo = ref({})
// 抽屉展示
const drawerShowBt = (type) => {
transferShow.value = type == 'transfer' ? !transferShow.value : false
exchangeShow.value = type == 'exchange' ? !exchangeShow.value : false
withdrawShow.value = type == 'withdraw' ? !withdrawShow.value : false
selectedCoin.value = null
}
// 点击展示主体内容
const handleBt = (type) => {
rankShow.value = type == 'Rank' ? true : false
incomeShow.value = type == 'Income' ? true : false
lotteryShow.value = type == 'Lottery' ? true : false
drawerShowBt('') // 收起抽屉
selectedCoin.value = null // 清除选择币种
}
const taskList = ref([])
// 展示任务目标
const showTarget = (task) => {
let timeType = task.targetValue < 60
let currentValue = timeType ? task.currentValue : task.currentValue / 60
let targetValue = timeType ? task.targetValue : task.targetValue / 60
let timeUnit = timeType ? 'mins' : 'hours'
return `On the mic(${currentValue}/${targetValue}${timeUnit})`
}
// 选中的收款人
const selectedPayee = ref({})
// 初始化收款人信息
const initializePayee = () => {
const savedPayee = getSelectedPayee()
if (savedPayee) {
Object.assign(selectedPayee.value, savedPayee)
handleBt('Income')
drawerShowBt('transfer')
}
}
// 清除当前选中的收款人
const clearPayee = () => {
Object.assign(selectedPayee.value, {
id: null,
account: '',
name: '',
avatar: '',
type: '',
isHost: false,
isAgency: false,
hasSalary: false,
})
clearSelectedPayee()
}
const searchPayee = () => {
router.push({ path: '/search-payee', query: { from: 'lottery' } })
}
// 转账金币选项
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 = () => {
resultShow.value = false
helpShow.value = false
historyShow.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) => {
console.log('开始抽奖动画')
return new Promise((resolve) => {
activeIndex.value = -1
let speed = 200 // 初始速度
let currentCycle = 0 // 当前圈数
let constantSpeedCycle = 0 // 当前匀速圈数
let totalCycles = 1 // 至少旋转2圈数
const roll = () => {
activeIndex.value = (activeIndex.value + 1) % activity.value.prizeList.length
console.log('当前索引:', activeIndex.value)
if (activeIndex.value === resultIndex && speed > 200) {
setTimeout(() => {
resolve() // 抽奖结束
}, 500)
} else {
if (activeIndex.value === activity.value.prizeList.length - 1) {
currentCycle++
}
// 加速旋转
if (currentCycle < totalCycles) {
speed -= 20
} else {
// 匀速旋转
if (activeIndex.value === activity.value.prizeList.length - 1) {
constantSpeedCycle++
}
if (constantSpeedCycle > 2) {
// 最后减速效果
speed += 20
}
}
setTimeout(roll, speed)
}
}
roll()
})
}
// 连抽
const sweepstakes = async (consecutive) => {
if (isRolling.value) return // 防止重复点击
// 检查抽奖次数
if (rollTimes.value >= consecutive) {
isRolling.value = true
let data = {
activityId: activity.value.activity.id,
drawCount: consecutive,
}
try {
// 单抽
if (consecutive == 1) {
const resOnceDraw = await onceDraw(data)
if (resOnceDraw.status && resOnceDraw.body) {
const resItem = resOnceDraw.body //接口获取的抽奖结果
const index = activity.value.prizeList.findIndex(
(prize) => prize.prizeName == resItem?.prize.prizeName
) //转盘第几位停下
console.log('index:', index)
await startLottery(index)
result.value.push(resItem)
}
}
//连抽
else {
const resMultiDraw = await multiDraw(data)
if (resMultiDraw.status && resMultiDraw.body) {
const resDrawList = resMultiDraw.body //连抽获取的抽奖列表结果
for (let i = 0; i < resDrawList.results.length; i++) {
const resItem = resDrawList.results[i]
const index = activity.value.prizeList.findIndex(
(prize) => prize.prizeName == resItem?.prize.prizeName
) //转盘第几位停下
console.log('index:', index)
await startLottery(index)
result.value.push(resItem)
}
}
}
getTickets() //重新获取拥有的抽奖券
resultShow.value = true
isRolling.value = false
} catch (error) {
isRolling.value = false
}
}
}
//获取用户信息
const getUserInfo = async () => {
if (Object.keys(userInfo.value).length === 0) {
const resUserInfo = await getMemberProfile()
if (resUserInfo.status && resUserInfo.body) {
userInfo.value =
userInfo.value == {} ? resUserInfo.body : Object.assign(userInfo.value, resUserInfo.body)
}
}
const resIdentity = await getUserIdentity()
if (resIdentity.status && resIdentity.body) {
userIdentity.value = resIdentity.body
}
}
/**
* 连接APP并获取认证信息优化版 - 仅专注连接)
*/
const connectToApp = async () => {
console.group('🔗 APP Connection Process')
console.debug('🚀 Starting APP connection...')
try {
// 检查是否在APP环境中
if (!isInApp()) {
console.debug('🌐 Browser environment detected')
appConnected.value = true
console.groupEnd()
// 触发连接成功回调
getUserInfo()
return { success: true, environment: 'browser' }
}
// 检查是否已经连接且未过期
if (isAppConnected()) {
console.debug('✅ APP already connected and valid')
appConnected.value = true
// 使用缓存的头部信息
const cachedHeaderInfo = getAppHeaderInfo()
if (cachedHeaderInfo) {
headerInfo.value = cachedHeaderInfo
console.debug('🔧 Using cached HTTP headers')
}
console.groupEnd()
// 触发连接成功回调
getUserInfo()
return { success: true, environment: 'app', fromCache: true }
}
// 检查是否有正在进行的连接
if (appConnectionManager.isConnecting()) {
console.debug('⏳ Connection already in progress, waiting...')
await appConnectionManager.getConnectionPromise()
appConnected.value = true
console.debug('✅ Connected via existing process')
console.groupEnd()
// 触发连接成功回调
getUserInfo()
return { success: true, environment: 'app', fromCache: true }
}
// 建立新的APP连接
console.debug('📱 Establishing new APP connection...')
const connectionResult = await new Promise((resolve, reject) => {
const connectionPromise = new Promise((connectResolve, connectReject) => {
connectApplication(async (access) => {
try {
const result = parseAccessOrigin(access)
if (result.success) {
// 解析头部信息
headerInfo.value = parseHeader(result.data)
// 设置HTTP请求头
console.debug('🔧 Setting HTTP headers...')
await setHttpHeaders(headerInfo.value)
// 标记连接成功
appConnected.value = true
appConnectionManager.setConnected(headerInfo.value)
console.debug('🎉 APP connection established successfully')
connectResolve({ success: true, environment: 'app', fromCache: false })
} else {
console.error('❌ Failed to parse access origin:', result.error)
appConnectionManager.reset()
connectReject(new Error(result.error))
}
} catch (error) {
console.error('❌ Connection process failed:', error)
appConnectionManager.reset()
connectReject(error)
}
})
})
// 设置连接状态
appConnectionManager.setConnecting(connectionPromise)
connectionPromise.then(resolve).catch(reject)
})
console.debug('✅ Connection completed successfully')
console.groupEnd()
// 触发连接成功回调
getUserInfo()
return connectionResult
} catch (error) {
console.error('❌ Connection failed:', error)
appConnected.value = false
console.groupEnd()
// 触发连接失败回调
// if (onConnectionFailed) {
// onConnectionFailed(error)
// }
// 连接失败的特殊处理
if (error.message && error.message.includes('parse access')) {
router.push({ path: '/not_app', query: { message: error.message } })
}
return { success: false, error: error.message }
}
}
//获取排行榜
const getRanking = async () => {
const resRanking = await ranklist()
if (resRanking.status && resRanking.body) {
ranking.value = resRanking.body
// 在排行榜中查我的信息
const myInfo = ranking.value.find((item) => item.account === userInfo.value.account)
if (myInfo) {
userInfo.value = userInfo.value == {} ? myInfo : Object.assign(userInfo.value, myInfo)
}
} else {
ranking.value = {}
}
}
//获取奖池
const getActivityDetail = async () => {
const resvalidActivity = await validActivity() //活动是否有效
console.log('id:', resvalidActivity.body?.id)
console.log('id2:', JSON.stringify(resvalidActivity.body))
if (resvalidActivity.body?.activityCode) {
const resDetail = await activityDetail(resvalidActivity.body?.activityCode)
if (resDetail.status && resDetail.body) {
activity.value = resDetail.body
} else {
activity.value = {}
}
}
}
//获取拥有的抽奖券
const getTickets = async () => {
const resTickets = await myTickets()
if (resTickets.status && resTickets.body) {
rollTimes.value = resTickets.body
} else {
rollTimes.value = 0
}
}
//获取任务列表
const getTaskList = async () => {
const resTaskList = await ActTaskList()
if (resTaskList.status && resTaskList.body) {
taskList.value = resTaskList.body
}
}
//获取中奖记录
const getDrawRecords = async () => {
const resDrawRecords = await drawRecords()
if (resDrawRecords.status && resDrawRecords.body) {
myRecords.value = resDrawRecords.body?.records || []
}
}
//获取中奖记录
const getWinners = async () => {
const resWinners = await winnerHistory()
if (resWinners.status && resWinners.body) {
barrageList.value = resWinners.body
}
}
//获取中奖记录
const receiveTaskReward = async (code) => {
let data = {
taskCode: code,
}
const resReceive = await receiveTickets(data)
if (resReceive.status && resReceive.body) {
getTaskList() //获取任务列表
}
}
onMounted(() => {
connectToApp()
isInAppEnvironment.value = isInApp()
initializePayee() //获取收款人信息
getRanking() //获取排行榜
getActivityDetail() //获取奖池
getDrawRecords() // 获取中奖记录
getTickets() //获取拥有的抽奖券
getTaskList() //获取任务列表
getWinners() //获取历史中奖用户
})
onUnmounted(() => {
clearPayee() //清空收款人信息
})
</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;
}
.scrollbar::-webkit-scrollbar {
display: none;
}
.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);
}
.rotated {
transform: rotate(90deg);
}
.drawer-enter-active,
.drawer-leave-active {
transition: all 0.2s ease-out;
}
.drawer-enter-from,
.drawer-leave-to {
transform: translateY(-5px);
opacity: 0;
}
@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>