aslan-h5/src/views/Invitation/InvitationToRegister.vue
2025-10-24 23:18:07 +08:00

761 lines
22 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">
<div style="position: absolute; top: 0; left: 0; width: 100%">
<GeneralHeader title="Invitation to register" :showHelp="true" @help="helpInfoShow = true" />
</div>
<!-- 确认邀请码 -->
<div style="padding: 87% 0 5%">
<img
:src="images.enterCode"
alt=""
width="40%"
@click="enterCodeShow = true"
style="display: block"
/>
</div>
<!-- 主体内容 -->
<div
style="padding: 0 3%; display: flex; flex-direction: column; gap: 12px; padding-bottom: 30px"
>
<!-- 倒计时 -->
<!-- <div style="position: relative">
<img :src="images.countdownBg" alt="" width="100%" style="display: block" />
<div
style="
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2em;
font-weight: 700;
letter-spacing: 2px;
"
>
29D 2323:23
</div>
</div> -->
<!-- 我的邀请码 -->
<div
style="
width: 100%;
border-radius: 8px;
background: rgba(194, 174, 232, 0.2);
backdrop-filter: blur(32px);
padding: 12px;
display: flex;
flex-direction: column;
gap: 4px;
"
@click.stop
>
<div style="font-weight: 590">My invitation code:</div>
<div
style="
border-radius: 8px;
background: rgba(255, 255, 255, 0.2);
padding: 12px;
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
"
>
<div style="font-weight: 590">{{ myInviteInfo.inviteCode || '' }}</div>
<img
src="../../assets/icon/copy.png"
alt=""
width="24px"
@click="copyCode(myInviteInfo.inviteCode || '')"
/>
</div>
<div style="font-weight: 510; font-size: 0.7em">
*Copy your invitation code and send it to your new friends.
</div>
</div>
<!-- 模块切换按钮 -->
<div>
<div
v-if="showModule == 'list'"
style="display: flex; justify-content: space-around; align-items: center"
>
<img :src="images.listBtActive" alt="" width="40%" style="display: block" />
<img
:src="images.rewardBt"
alt=""
width="40%"
style="display: block"
@click="showModule = 'reward'"
/>
</div>
<div v-else style="display: flex; justify-content: space-around; align-items: center">
<img
:src="images.listBt"
alt=""
width="40%"
style="display: block"
@click="showModule = 'list'"
/>
<img :src="images.rewardBtActive" alt="" width="40%" style="display: block" />
</div>
</div>
<!-- 邀请列表模块 -->
<div v-if="showModule == 'list'" style="display: flex; flex-direction: column; gap: 12px">
<!-- 收入展示 -->
<div style="display: flex; justify-content: space-around; align-items: center">
<div style="position: relative; width: 47%">
<img :src="images.totalIncome" alt="" width="100%" style="display: block" />
<div
style="
position: absolute;
top: 40%;
width: 100%;
height: 60%;
display: flex;
justify-content: center;
align-items: center;
"
>
<img src="/src/assets/icon/coin.png" alt="" width="10%" style="display: block" />
<div style="font-weight: 590">{{ myInviteIncome.totalIncome || 0 }}</div>
</div>
</div>
<div style="position: relative; width: 47%">
<img :src="images.todayIncome" alt="" width="100%" style="display: block" />
<div
style="
position: absolute;
top: 40%;
width: 100%;
height: 60%;
display: flex;
justify-content: center;
align-items: center;
"
>
<img src="/src/assets/icon/coin.png" alt="" width="10%" style="display: block" />
<div style="font-weight: 590">{{ myInviteIncome.todayIncome || 0 }}</div>
</div>
</div>
</div>
<!-- 邀请人数 -->
<div
style="
border-radius: 8px;
background: rgba(194, 174, 232, 0.2);
backdrop-filter: blur(32px);
padding: 12px;
display: flex;
align-items: center;
gap: 4px;
"
>
<div
style="
width: 3px;
height: 12px;
flex-shrink: 0;
background: radial-gradient(323.25% 50.32% at 50% 50%, #d58ef4 0%, #7a5aff 100%);
"
></div>
<div style="font-weight: 590">
Number of valid users invited: {{ myInviteIncome.validInviteCount || 0 }}
</div>
</div>
<!-- 列表 -->
<div
style="
border-radius: 8px;
background: rgba(194, 174, 232, 0.2);
backdrop-filter: blur(32px);
padding: 8px;
display: flex;
flex-direction: column;
gap: 4px;
"
>
<!-- 头部 -->
<div style="width: 100%; display: flex; align-items: center">
<div style="flex: 1; font-weight: 590; font-size: 0.85em">Users</div>
<div
style="
flex: 1;
font-weight: 590;
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div
style="
width: 1px;
height: 16px;
background: linear-gradient(
rgba(217, 217, 217, 0) 0%,
#d9d9d9 50%,
rgba(217, 217, 217, 0) 100%
);
"
></div>
<div style="font-weight: 590; font-size: 0.82em">Remaining time</div>
<div
style="
width: 1px;
height: 16px;
background: linear-gradient(
rgba(217, 217, 217, 0) 0%,
#d9d9d9 50%,
rgba(217, 217, 217, 0) 100%
);
"
></div>
</div>
<div style="flex: 1; font-weight: 590; text-align: end; font-size: 0.82em">
Prowide income
</div>
</div>
<!-- 列表项 -->
<div
v-for="user in myInviteUsers"
style="width: 100%; display: flex; align-items: center"
>
<div style="flex: 1; font-weight: 590; font-size: 0.7em">
ID:{{ user.invitedUserAccount || '-' }}
</div>
<div
style="
flex: 1;
font-weight: 590;
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div
style="
width: 1px;
height: 16px;
background: linear-gradient(
rgba(217, 217, 217, 0) 0%,
#d9d9d9 50%,
rgba(217, 217, 217, 0) 100%
);
"
></div>
<div style="font-weight: 590; font-size: 0.7em">{{ user.remainingDays || 0 }}Day</div>
<div
style="
width: 1px;
height: 16px;
background: linear-gradient(
rgba(217, 217, 217, 0) 0%,
#d9d9d9 50%,
rgba(217, 217, 217, 0) 100%
);
"
></div>
</div>
<div
style="
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 4px;
"
>
<img src="../../assets/icon/coin.png" alt="" width="15%" />
<div style="color: #ffb627; font-size: 0.8em; font-weight: 590">
{{ user.provideIncome || 0 }}
</div>
</div>
</div>
</div>
</div>
<!-- 奖励说明模块 -->
<div v-if="showModule == 'reward'" style="display: flex; flex-direction: column; gap: 12px">
<div style="position: relative" v-for="poolItem in inviteAwardPool">
<img
:src="images[`reward${poolItem.quantity}people`]"
alt=""
width="100%"
style="display: block"
/>
<div
style="
position: absolute;
inset: 12% 0 0;
display: flex;
flex-direction: column;
justify-content: space-around;
"
>
<div style="display: flex; justify-content: space-around">
<div
style="width: 45%; position: relative"
v-for="(award, index) in poolItem.activityRewardPropsList"
>
<img :src="images.rewardItemBg" alt="" width="100%" />
<div
style="
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
"
>
<img :src="award.cover" alt="" width="50%" />
<div>{{ showAwardText(award) }}</div>
</div>
</div>
</div>
<div style="display: flex; justify-content: center">
<img
v-if="poolItem.state == '1'"
:src="images.receiveBtActive"
alt=""
width="35%"
@click="receiveAward(poolItem.id)"
/>
<img v-else :src="images.receiveBt" alt="" width="35%" />
</div>
</div>
</div>
</div>
</div>
<!-- 弹窗遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<!-- 帮助模块 -->
<div v-if="helpInfoShow" style="margin: 20% 10%; position: relative" @click.stop>
<img :src="images.helpInfo" alt="" width="100%" />
<div
style="position: absolute; width: 8%; aspect-ratio: 1/1; top: 3.8%; right: 7%"
@click="closedPopup"
></div>
</div>
<!-- 输入邀请码模块 -->
<div
v-if="enterCodeShow"
style="
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
"
@click="closedPopup"
>
<div
style="
width: 80%;
border-radius: 12px;
background: #fff;
padding: 12px;
display: flex;
flex-direction: column;
gap: 4px;
"
@click.stop
>
<div style="color: rgba(0, 0, 0, 0.8); font-weight: 800">Enter Invitation Code</div>
<div style="display: flex; justify-content: space-between; align-items: center; gap: 8px">
<!-- 邀请码 -->
<div style="flex: 1">
<input
v-model="invitationCode"
type="text"
style="
width: 100%;
height: 100%;
color: rgba(0, 0, 0, 0.4);
font-weight: 510;
border-radius: 8px;
padding: 4px 8px;
border: 1px solid rgba(0, 0, 0, 0.4);
outline: none;
background-color: transparent;
"
@keyup.enter="confirmInvitationCode"
/>
</div>
<!-- 确认按钮 -->
<img :src="images.confirm" alt="" width="25%" @click="confirm" />
</div>
<div style="color: rgba(0, 0, 0, 0.4); font-weight: 510; font-size: 0.7em">
*Only users who registered within 72 hours can fill in the invitation code. Once the
invitation code is flled in, itcannot be modifed.
</div>
</div>
</div>
</maskLayer>
<!-- 提示弹窗 -->
<div
v-show="tipShow"
style="position: fixed; top: 50vh; display: flex; justify-content: center; width: 100vw"
>
<div
style="padding: 12px; border-radius: 8px; background: rgba(0, 0, 0, 0.4); font-weight: 600"
>
Copied successfully
</div>
</div>
</div>
</template>
<script setup>
import {
connectApplication,
parseAccessOrigin,
parseHeader,
setHttpHeaders,
isInApp,
} from '@/utils/appBridge.js'
import {
appConnectionManager,
isAppConnected,
getAppHeaderInfo,
} from '@/utils/appConnectionManager.js'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import GeneralHeader from '@/components/GeneralHeader.vue'
import maskLayer from '../../components/MaskLayer.vue'
import { getMemberProfile } from '@/api/wallet'
import { showError, showWarning, showInfo, showSuccess } from '@/utils/toast.js'
import { useClipboard } from '@vueuse/core'
import {
getMyInviteCode, // 获取我的邀请码
bindInviteCode, // 绑定邀请码
getMyInviteIncome, // 获取收益统计
getMyInviteUsers, // 有效邀请用户列表
getInviteAwardPool, // 邀请用户奖励池
getInviteReceiveAward, // 邀请用户领取奖励
} from '@/api/invitation.js'
// vite动态导入图片
const imageModules = import.meta.glob('@/assets/images/InvitationNewUser/*.{png,jpg,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 { copy, isSupported } = useClipboard()
const isInAppEnvironment = ref(false) // 检测是否在APP环境中
const appConnected = ref(false)
const headerInfo = ref({})
const helpInfoShow = ref(false) //帮助模块
const enterCodeShow = ref(false) //交易弹窗
const tipShow = ref(false) //提示弹窗
const invitationCode = ref('') //邀请码
const myInviteInfo = ref({}) //我的邀请信息
const myInviteIncome = ref({}) //邀请用户收益统计
const myInviteUsers = ref([]) //邀请用户列表
const inviteAwardPool = ref({})
const showModule = ref('list') //显示模块
// 展示遮罩层
const maskLayerShow = computed(() => {
return helpInfoShow.value || enterCodeShow.value
})
// 关闭弹窗
const closedPopup = () => {
helpInfoShow.value = false
enterCodeShow.value = false
}
// 复制到剪贴板(原生方法)
const copyCode = (text) => {
if (isSupported.value) {
copy(text)
} else {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
}
tipShow.value = true
setTimeout(() => {
tipShow.value = false
}, 2000)
}
// 展示奖品信息
const showAwardText = (award) => {
if (award.type == 'GOLD') return award.quantity
return award.quantity + 'Days'
}
// 更改邀请码
const confirmInvitationCode = () => {
if (!invitationCode.value.trim()) {
showWarning('Please enter a valid invitation code.')
return
}
}
// 获取我的邀请码
const apiGetMyInviteCode = async () => {
const resCode = await getMyInviteCode()
if (resCode.status && resCode.body) {
myInviteInfo.value = resCode.body
}
}
// 绑定邀请码
const confirm = async () => {
if (!invitationCode.value.trim()) {
showWarning('Please enter a valid invitation code.')
return
}
let data = { inviteCode: invitationCode.value.trim(), inviteType: 'REGISTER' }
try {
const resBind = await bindInviteCode(data)
if (resBind.status) {
showSuccess('Invitation code bound successfully!')
} else {
showError('Invitation code binding failed.')
}
invitationCode.value = ''
enterCodeShow.value = false
} catch (error) {
showError(error.errorMsg)
}
}
// 获取收益统计
const apiGetMyInviteIncome = async () => {
const resIncome = await getMyInviteIncome()
if (resIncome.status && resIncome.body) {
myInviteIncome.value = resIncome.body
}
}
// 有效邀请用户列表
const apiGetMyInviteUsers = async () => {
const resUserList = await getMyInviteUsers()
if (resUserList.status && resUserList.body) {
myInviteUsers.value = resUserList.body
}
}
// 邀请用户奖励池
const apiGetInviteAwardPool = async () => {
const resAwardPool = await getInviteAwardPool()
if (resAwardPool.status && resAwardPool.body) {
inviteAwardPool.value = resAwardPool.body
}
}
// 邀请用户领取奖励
const receiveAward = async (awardId) => {
const resAward = await getInviteReceiveAward(awardId)
if (resAward.status && resAward.body) {
apiGetInviteAwardPool() // 刷新奖励池
}
}
// 页面初始化数据
const initData = () => {
apiGetMyInviteCode() // 获取我的邀请码
apiGetMyInviteIncome() // 获取收益统计
apiGetMyInviteUsers() // 有效邀请用户列表
apiGetInviteAwardPool() // 邀请用户奖励池
}
/**
* 连接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()
// 触发连接成功回调
initData()
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()
// 触发连接成功回调
initData()
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()
// 触发连接成功回调
initData()
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()
// 触发连接成功回调
initData()
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 }
}
}
onMounted(() => {
connectToApp()
isInAppEnvironment.value = isInApp()
})
</script>
<style lang="scss" scoped>
* {
color: white;
font-family: 'SF Pro Text';
}
.fullPage {
width: 100vw;
min-height: 100vh;
background-color: #5b4a8a;
background-image: url(../../assets/images/InvitationNewUser/bg.png);
background-size: 100% auto;
background-repeat: no-repeat;
position: relative;
}
@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>