Snipaste_2025-07-01_15-46-06.png
<template>
<div class="lottery-wrap">
<div class="lottery-container">
<!-- 页面标题 -->
<div class="lottery-header">
<!-- 每日收起 -->
<span class="lottery-hand-day"></span>
<span class="lottery-subject"> 用好运赢时长、云币和<span class="money">现金</span> </span>
</div>
<!-- 九宫格抽奖区域 -->
<div class="lottery-main">
<div class="lottery-grid-container">
<div class="lottery-rule" @click="handleRouter('rule')">
<span>规则</span>
</div>
<span class="lottery-my-reward" @click="handleRouter('my-reward')">
<span>
我的奖励
</span></span
>
<!-- 剩余次数 -->
<div class="lottery-num">剩余次数: {{ remainingTimes }}</div>
<div class="lottery-grid">
<!-- 使用v-for循环渲染所有格子 -->
<van-loading type="spinner" class="loading" v-if="!loading" />
<template v-for="(prize, index) in prizesWithButton">
<div :key="index" v-if="index != 4" class="lottery-item" :class="{ active: currentActiveIndex === index }">
<div class="prize-card">
<img :src="prize.prize_img" alt="奖品图片" class="prize-image" />
<div class="prize-name">{{ prize.display_name }}</div>
</div>
</div>
<!-- 中间位置(索引4)是抽奖按钮 -->
<div :key="index" v-else-if="index === 4" class="lottery-button " @click="startLottery" :disabled="isRunning || remainingTimes <= 0">
<div :class="['animation-bg']" v-if="showAnimation"></div>
<div class="finger" />
</div>
</template>
</div>
<!-- 分享 -->
<div class="lottery-share" @click="handleShare"></div>
</div>
<!-- 活动规则 -->
<div class="lottery-rules">
<ul class="rules-list">
<li>1.每人每日1次抽奖机会</li>
<li>2.分享朋友圈,微信群等好友助力可获得1次</li>
<li>3.分享后,新用户注册可再获得1次</li>
</ul>
<div class="footer">
<div class="share-record" @click="handleRouter('share-record')">好友助力记录</div>
<div class="register-reward" @click="handleRouter('share-record')">注册好友记录</div>
</div>
</div>
</div>
</div>
<!-- 新增背景动画容器 -->
<div :class="[{ 'show-animation': showAnimation }]"></div>
<ChearResult :title="chearTitle" :open="chearOpen" :status="chearStatus" @start="startChear" @close="closeChear" />
<PopTips v-if="showResultModal" :android_version="android_version" @close="handleClose" :prize="resultPrize" />
</div>
</template>
<script lang="ts">
import { bindChannel, boost, shareInfo, userInfo, getActivityH5LotteryList, postActivityH5Lottery, getShareUrl } from '@/axios/apis/user-lottery';
import { ePrizeType, iPrize } from '@/interface/user-lottery';
import { IShareInfoRes } from '@/axios/types/Login';
import { Toast } from 'vant';
import { Component, Vue, Watch } from 'vue-property-decorator';
import { mapState } from 'vuex';
import ChearResult from './ChearResult.vue';
import PopTips from './PopTips.vue';
import sleep from '@/utils/sleep';
import { dowebCopy } from '@/js/common';
import WxApi from '@/utils/wxSdk';
import { getWechatConfig } from '@/axios/apis/promoter';
import removeUrlSearchParamAndUpdate from '@/utils/removeUrlSearchParamAndUpdate';
enum ChearTitleMap {
cussess = '恭喜您助力成功',
default = '帮我助力',
}
enum ChearStatusMap {
success = 'success',
fail = 'fail',
default = 'default',
}
@Component({
components: { ChearResult, PopTips },
computed: {
...mapState('login', ['loginSuccess', 'isWx', 'isAndroid']),
},
})
export default class extends Vue {
loginSuccess!: boolean;
isAndroid!: boolean;
isWx!: boolean;
loading: boolean = false;
// 奖品配置(不含中间按钮)
prizes: iPrize[] = [];
// 当前活跃的格子索引
currentActiveIndex = -1;
isPC = !/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent);
// 抽奖状态
isRunning = false;
// 剩余抽奖次数
remainingTimes: number = 0;
prize_verify: string = '';
//分享地址
shareUrl: string = '';
// 是否显示结果弹窗
showResultModal = false;
tempModel: iPrize = {
prize_type: ePrizeType.COIN,
prize_id: -1,
prize_img: '',
promote_code_pack_id: -1,
display_name: '',
daily_quantity: -1,
total_quantity: -1,
prize_rate: -1,
user_max_rate_num: -1,
default_prize_id: -1,
amount: -1,
promote_code: '',
android_version: '',
postion: -1,
};
// 当前抽奖结果
resultPrize = JSON.parse(JSON.stringify(this.tempModel));
/** 分享的code */
scode: string = '';
/** 是否绑定渠道 */
isBindChannel = false;
shareInfoData: IShareInfoRes | null = null;
showChearDialog = false;
// 动画相关属性
totalRounds = 3; // 抽奖总圈数
animationSpeed = 150;
currentRound = 0; // 当前圈数
animationTimer: any = null; // 定时器
winIndex = 0; // 中奖索引
chearTitle: ChearTitleMap | string = ChearTitleMap.default;
chearOpen = false;
chearStatus = ChearStatusMap.default;
// promote_code = '';
android_version = '';
activity_id: string = '';
// 新增背景动画控制属性
showAnimation = false;
// 九宫格的顺序(按顺时针排列)
get gridOrder() {
return [0, 1, 2, 5, 8, 7, 6, 3];
}
// 包含中间按钮的完整奖品列表
get prizesWithButton() {
const newPrizes = [...this.prizes];
if (this.prizes.length === 8) {
newPrizes.splice(4, 0, JSON.parse(JSON.stringify(this.tempModel))); // 在索引4位置插入按钮
}
return newPrizes;
}
@Watch('loginSuccess')
onLoginSuccess(val: boolean) {
if (val) {
this.scode && this.checkCheerStatus();
this.init();
}
}
closeChear() {
this.chearOpen = false;
console.log(this.activity_id, '---closeChear');
}
async startChear() {
this.closeChear();
if (!this.shareInfoData) {
return;
}
await sleep(500);
const { code, msg } = await boost({ boosted_uid: this.shareInfoData?.boosted_uid!, activity_id: +this.activity_id });
removeUrlSearchParamAndUpdate('scode');
if (code === 0) {
this.chearStatus = ChearStatusMap.success;
this.chearTitle = ChearTitleMap.cussess;
this.chearOpen = true;
return;
}
Toast.fail(msg);
}
mounted() {
this.scode = new URLSearchParams(location.search).get('scode') || '';
this.activity_id = new URLSearchParams(location.search).get('activity_id') || '';
this.init();
if (this.loginSuccess) {
this.scode && this.checkCheerStatus();
}
}
// 检查助力状态
async checkCheerStatus() {
const { data: userData } = await userInfo();
if (!userData.channel && this.scode) {
this.isBindChannel = true;
}
await this.queryShareInfo();
}
async queryShareInfo() {
const { code, data, msg } = await shareInfo({ scode: this.scode });
if (code !== 0) {
Toast.fail(msg);
return Promise.reject();
}
if (!data.can_boost) {
this.chearTitle = data.reason;
this.chearStatus = ChearStatusMap.fail;
}
this.chearOpen = true;
this.shareInfoData = data;
if (this.isBindChannel && data.channel) {
await this.setBindChannel(data.channel);
}
}
async setBindChannel(channel: string) {
await bindChannel({ channel });
}
handleClose() {
this.showResultModal = false;
this.currentActiveIndex = -1;
}
// 开始抽奖
async startLottery() {
if (!this.loginSuccess) {
this.$store.dispatch('login/checkingLogin');
return;
}
if (this.remainingTimes <= 0) {
Toast.fail('没有抽奖次数了');
return;
}
if (this.isRunning) {
Toast.fail('请不要重复点击哦!');
return;
}
this.resultPrize = JSON.parse(JSON.stringify(this.tempModel)); //清空上次奖品
this.isRunning = true;
this.currentRound = 0;
this.winIndex = await this.getRandomWinIndex();
if (this.winIndex > -1) {
this.startAnimation();
}
}
// 获奖品
async getRandomWinIndex() {
let index = -1;
try {
const res: any = await postActivityH5Lottery({ activity_id: JSON.parse(this.activity_id), prize_verify: this.prize_verify });
const { code, data, msg } = res;
if (code != 0) {
Toast.fail(msg);
this.isRunning = false;
} else {
this.remainingTimes--;
const { promote_code, prize_info } = data;
index = this.prizes.findIndex(item => item.prize_id === prize_info.prize_id);
this.resultPrize = this.prizes[index];
this.resultPrize.promote_code = promote_code;
}
} catch (err) {
index = -1;
this.isRunning = false;
}
return index;
}
// 开始动画
// 开始动画
startAnimation() {
let currentStep = 0;
// 定义有效奖品位置(排除中间按钮4)
const validPrizePositions = this.gridOrder;
// 定义奖品索引到额外步数的映射(可扩展)
const prizeIndexToExtraSteps: Record<number, number> = {
0: 0,
1: 1,
2: 2,
3: 7,
4: 3,
5: 6,
6: 5,
7: 4,
};
// 获取当前奖品索引对应的额外步数
const extraSteps = prizeIndexToExtraSteps[this.winIndex] || 0;
// 固定5圈转动
const totalPrizePositions = validPrizePositions.length;
const totalStepsBase = totalPrizePositions * this.totalRounds;
// 计算中奖位置在转动顺序中的索引
const winIndexInOrder = validPrizePositions.findIndex(index => index === this.winIndex);
console.log(this.winIndex, winIndexInOrder, '--winindex---', totalStepsBase);
// 最终停止的步数
const finalStep = totalStepsBase + extraSteps;
const animate = () => {
const gridIndex = validPrizePositions[currentStep % totalPrizePositions];
this.currentActiveIndex = gridIndex;
// 动态速度控制
const currentSpeed =
currentStep < totalStepsBase * 0.6
? 120 // 快速阶段
: currentStep < totalStepsBase * 0.8
? 200 // 中速阶段
: 300; // 慢速阶段
// 动画结束条件
if (currentStep === finalStep) {
// this.currentActiveIndex = this.winIndex == 4 ? 5 : this.winIndex;
clearInterval(this.animationTimer);
this.showResult();
return;
}
currentStep++;
this.animationTimer = setTimeout(animate, currentSpeed);
};
this.animationTimer = setTimeout(animate, 100); // 初始延迟100ms
}
// 显示抽奖结果
showResult() {
// this.resultPrize = this.prizesWithButton[this.winIndex];
this.isRunning = false;
// 抽奖结束后显示背景动画(持续10秒)
this.showAnimation = true;
// 10秒后隐藏动画
setTimeout(() => {
this.showAnimation = false;
this.showResultModal = true;
}, 1500);
}
async handleShare() {
const res = await getShareUrl(this.activity_id);
if (res.code !== 0) {
Toast.fail(res.msg);
return;
}
const url = res.data;
const imgUrl = 'https://static.xiongmaozhanggui.com/panda/backstage/uploads/20250624/1750735354858.jpg';
const title = '领沃云电脑';
const desc = '用好运赢时长、云币和现金,邀请您祝我一臂之力。';
if (this.isAndroid) {
try {
// @ts-ignore
window.androidBridge.openShareDialog(url, title, desc, imgUrl);
} catch (error) {
// @ts-ignore
console.log('androidBridge error\n', error, '\nmessage:', error.message);
}
return;
}
if (!this.isWx) {
dowebCopy(url, '复制分享链接成功!请分享给好友');
return;
}
//如果是微信掉起微信分享'http://172.16.98.91:8081/user-lottery.html?activity_id=1&scode=E02g5'
this.wechatShare(url, title, desc, imgUrl);
}
/**
* 微信分享功能实现 - 使用封装的 WxApi 类 jsApiList: ['onMenuShareAppMessage', 'onMenuShareTimeline'], // 必填,需要使用的JS接口列表
*/
async wechatShare(url: string, title: string, desc: string, imgUrl: string) {
try {
// 1. 获取微信JS-SDK配置
const config: any = await this.getWechatConfig();
// 2. 创建WxApi实例
const wxApi = new WxApi({
debug: false, // 调试模式,生产环境建议关闭
appId: config?.app_id,
timestamp: config?.timestamp,
nonceStr: config.nonce_str,
signature: config.signature,
openTagList: ['wx-open-launch-weapp'],
jsApiList: [
'updateAppMessageShareData', // 分享给朋友
'updateTimelineShareData', // 分享到朋友圈
'showMenuItems', // 显示菜单按钮
],
});
wxApi.checkApi([
'updateAppMessageShareData', // 分享给朋友
'updateTimelineShareData', // 分享到朋友圈
'showMenuItems', // 显示菜单按钮
]);
// 3. 设置错误处理
wxApi.onError((err: any) => {
console.error('微信API错误:', err);
Toast('微信分享功能出现错误,请稍后再试');
});
// 4. 等待微信API准备就绪
await wxApi.ready();
const content: any = {
title,
desc,
link: url,
imgUrl,
};
const contentLine: any = {
title,
link: url,
imgUrl,
};
// 5. 更新分享配置
try {
console.log(this.isPC, 'ispc');
if (this.isPC) {
await Promise.all([wxApi.updateAppMessageShareData(content)]);
} else {
await Promise.all([wxApi.updateAppMessageShareData(content), wxApi.updateTimelineShareData(contentLine)]);
}
Toast('分享配置已准备好,请点击右上角...分享', { duration: 3000 });
} catch (error) {
console.log(error, '---error---------');
}
// 6. 显示分享菜单
wxApi.showMenu();
} catch (error) {
console.error('微信分享初始化失败', error);
Toast('微信分享功能加载失败,请稍后再试');
}
}
/**
* 获取微信JS-SDK配置
*/
async getWechatConfig() {
try {
const { data, code, msg } = await getWechatConfig({ uri: this.$store.state.shareAuthUrl });
const { app_id, timestamp, nonce_str, signature } = data;
if (code === 0) {
return {
app_id,
timestamp,
nonce_str,
signature,
};
} else {
Toast.fail(msg);
}
} catch (error) {
console.error('获取微信JS-SDK配置失败', error);
throw new Error('获取微信配置失败');
}
}
handleRouter(path: string) {
if (!this.loginSuccess) {
this.$store.dispatch('login/checkingLogin');
return;
} else {
this.$router.push(`/${path}`);
}
}
// 关闭结果弹窗
closeResultModal() {
this.showResultModal = false;
}
async init() {
try {
this.loading = true;
const { code, data, msg } = await getActivityH5LotteryList(this.activity_id);
if (code != 0) {
Toast.fail(msg);
return Promise.reject();
}
const { user_lottery_num, prize_verify, activity_lottery_list } = data;
this.prizes = activity_lottery_list.prizes;
this.remainingTimes = user_lottery_num;
this.prize_verify = prize_verify;
this.android_version = activity_lottery_list.android_version;
localStorage.setItem('android_version', this.android_version);
localStorage.setItem('activity_describe', activity_lottery_list.activity_describe);
} finally {
this.loading = false;
}
}
beforeDestroy() {
if (this.animationTimer) {
clearInterval(this.animationTimer);
}
}
}
</script>
<style lang="scss">
@import '~@/static/styles/_mixins.scss';
.lottery-wrap {
background-color: #99e0ff;
width: 100vw;
height: 100%;
min-height: 850px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
overflow: hidden;
// 新增背景动画样式(持续10秒)
.show-animation {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100%;
background-color: rgba(0, 0, 0, 0.45);
z-index: 4;
display: flex;
align-items: center;
justify-content: center;
}
.hide {
opacity: 0;
}
@keyframes circleSpread {
0% {
transform: translateX(0px);
}
1% {
transform: translateX(-5px);
}
2% {
transform: translateY(5px);
}
4% {
transform: translateY(-5px);
}
6% {
transform: translateX(-5px);
}
8% {
transform: translateX(0px);
}
10% {
transform: translateX(5px);
}
12% {
transform: translateY(5px);
}
14% {
transform: translateY(-5px);
}
16% {
transform: translateX(-5px);
}
18% {
transform: translateX(0px);
}
20% {
transform: scale(1);
opacity: 0.9;
}
50% {
transform: scale(3);
opacity: 0.8;
}
70% {
transform: scale(5);
opacity: 0.4;
}
100% {
transform: scale(10);
opacity: 0;
}
}
@keyframes handHide {
0% {
opacity: 1;
}
90% {
opacity: 1;
}
95% {
opacity: 0.5;
}
100% {
opacity: 0;
}
}
.lottery-container {
@include bg-img('user-lottery/container-bg.png');
background-size: 100% 100%;
width: 100vw;
height: 543px;
display: flex;
flex-direction: column;
align-items: center;
/* 头部样式 */
.lottery-header {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
height: 90px;
top: 0;
> span {
position: absolute;
}
.lottery-hand-day {
@include bg-img('user-lottery/hand-day.png');
width: 375px;
height: 190px;
top: 0;
}
.lottery-subject {
@include bg-img('user-lottery/subject.png');
width: 194px;
height: 30px;
line-height: 30px;
color: #4f0176;
font-size: 14px;
font-weight: 700;
text-align: center;
top: 122px;
.money {
color: #ff3700;
}
}
}
.lottery-main {
margin-bottom: 100px;
height: 660px;
margin-top: 100px;
/* 九宫格容器 */
.lottery-grid-container {
position: relative;
top: -28px;
@include bg-img('user-lottery/lettery-bg.png');
width: 375px;
height: 543px;
.lottery-rule {
position: absolute;
right: 10px;
top: 48px;
@include bg-img('user-lottery/rule.png');
width: 52px;
height: 49px;
> span {
display: inline-block;
margin: 32px 0 0 14px;
transform: scale(0.9);
}
}
.lottery-my-reward {
position: absolute;
right: 10px;
top: 108px;
@include bg-img('user-lottery/my-reward.png');
width: 52px;
height: 49px;
> span {
display: inline-block;
margin: 32px 0 0 0px;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
transform: scale(0.9);
}
}
.lottery-num {
position: absolute;
color: #e2d8ff;
font-weight: 900;
top: 190px;
left: 50%;
transform: translateX(-50%);
}
/* 九宫格样式 */
.lottery-grid {
position: absolute;
top: 230px;
left: 50%;
transform: translateX(-50%);
display: grid;
grid-template-columns: 75px 75px 75px;
grid-template-rows: 64px 64px 64px;
gap: 0;
padding: 0;
box-sizing: border-box;
.loading {
position: absolute;
display: flex;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.lottery-item {
display: flex;
justify-content: center;
align-items: center;
border-radius: 12px;
transition: all 0.3s ease;
position: relative;
@include bg-img('user-lottery/grid-bg.png');
width: 80px;
height: 68px;
z-index: 1;
/* 激活状态样式 */
&.active {
transform: scale(1);
@include bg-img('user-lottery/grid-bg-active.png');
width: 80px;
height: 68px;
z-index: 2;
color: #732300;
}
}
/* 奖品卡片样式 */
.prize-card {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8px;
box-sizing: border-box;
.prize-image {
width: 30px;
height: 30px;
object-fit: cover;
}
.prize-name {
font-size: 14px;
color: #374481;
text-align: center;
margin-bottom: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
font-weight: 500;
font-size: 12px;
transform: scale(0.9);
}
}
/* 抽奖按钮样式 - 中间位置 */
.lottery-button {
border: none;
outline: none;
cursor: pointer;
transition: all 0.3s ease;
grid-column: 2 / 3;
grid-row: 2 / 3;
@include bg-img('user-lottery/start-bg.gif');
width: 70px;
height: 70px;
margin-left: 4px;
position: relative;
z-index: 4;
.animation-bg {
position: absolute;
top: 0%;
left: 0%;
z-index: 3;
width: 73px;
height: 74px;
background: rgba(255, 230, 115, 0.45);
box-shadow: 0px 0px 20px 0px #ffdd36;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
animation: circleSpread 1.5s linear 1 forwards; // 动画持续时间改为10秒
&::before {
content: '';
display: inline-block;
position: absolute;
width: 32px;
height: 30px;
background: #fff4bb;
box-shadow: 0px 0px 20px 0px #ffdd36;
border-radius: 8px;
}
}
.finger {
position: absolute;
@include bg-img('user-lottery/finger.gif');
width: 64px;
height: 64px;
top: 55px;
left: -60px;
z-index: 5;
animation: handHide 4s forwards;
}
}
}
.lottery-share {
position: absolute;
@include bg-img('user-lottery/share.png');
width: 181px;
height: 62px;
top: 453px;
left: 50%;
transform: translateX(-50%);
}
} /* 活动规则样式 */
.lottery-rules {
position: absolute;
top: 505px;
width: 343px;
height: 108px;
background: linear-gradient(180deg, #e8dfff 0%, #ffffff 100%);
border-radius: 12px 12px 12px 12px;
border: 2px solid #ffffff;
left: 50%;
top: 615px;
transform: translateX(-50%);
.rules-list {
li {
font-size: 14px;
color: #1d035b;
margin: 8px 0 8px 12px;
}
}
}
}
.footer {
position: relative;
display: flex;
width: 343px;
bottom: 0px;
margin: 50px 0 50px 0;
justify-content: space-between;
> div {
width: 165px;
height: 49px;
text-align: center;
font-family: Source Han Sans, Source Han Sans;
font-weight: 900;
font-size: 18px;
color: #ffffff;
line-height: 49px;
}
.share-record {
@include bg-img('user-lottery/friend-help-btn.png');
}
.register-reward {
@include bg-img('user-lottery/register-record-btn.png');
}
}
/* 结果弹窗样式 */
.result-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
animation: fadeIn 0.3s ease forwards;
}
}
}
</style>