目录
前言
虽然有一些抽奖插件比如lucky-canvas来帮助我们快速发开抽奖小活动,但一些高定制的项目,只能自己手写抽奖(组件构成复杂,插件的css满足不了),今天记录一个九宫格抽奖demo,走一遍抽奖活动设计思路。
一、实现思路
假设要实现如下的效果图:(网上截的图,css意思意思就行,具体项目按照设计稿来)
1.组件结构
九宫格抽奖的组件结构很简单。首先肯定要有一个父盒子容器,来存放奖品和抽奖按钮。所以优先使用flex布局。
接着就是各种不同的奖品item,和抽奖按钮安置其中。推荐用v-for循环出来(也可以直接手写出来9个奖品盒子)。具体prize-item的盒子里面有什么,取决于需求。以下是参考图简单实现:
<template>
<div class="container">
<div v-for="(item, index) in DrawList" :key="index" class="prize-item">
<img :src="item.url" alt="" />
<p class="desc">{{ item.desc }}</p>
</div>
</div>
</template>
2.数据结构
①奖品列表
项目里,奖项有可能是前端固定,也有可能是后台返回的数据,所以放在一个数组里进行管理。
②抽奖按钮
抽奖按钮有可能是文字、图片。还有可能在抽奖过程中改变,所以用一个对象管理。
③v-for的抽奖列表
奖品列表和抽奖按钮加起来,就是完整的渲染列表,由于跟随前两者变化,推荐使用计算属性。
<script setup lang="ts">
import none from '../assets/none.png'
import topPrize from '../assets/top-prize.png'
import secondPrize from '../assets/second-prize.png'
import thirdPrize from '../assets/third-prize.png'
import fourthPrize from '../assets/fourth-prize.png'
import redEnvelope from '../assets/red-envelope.png'
interface Prize {
url: string
desc: string
}
const pirzeList: Prize[] = [
{ url: redEnvelope, desc: '现金红包' },
{ url: topPrize, desc: '一等奖' },
{ url: thirdPrize, desc: '三等奖' },
{ url: none, desc: '谢谢参与' },
{ url: secondPrize, desc: '二等奖' },
{ url: redEnvelope, desc: '现金红包' },
{ url: none, desc: '谢谢参与' },
{ url: fourthPrize, desc: '四等奖' }
]
const btnStart = { url: '', desc: '开始抽奖' }
const DrawList = computed(() => {
return [...pirzeList.slice(0, 4), btnStart, ...pirzeList.slice(4)]
})
</script>
至此,ui就呈现出来了。
3.组件交互
①抽奖顺序
九宫格抽奖一般是顺时针旋转,所以需要一个抽奖顺序,让它按照顺时针进行。(也有随机位置的,那更简单,一个Math.random控制index就可以了。)
而不是按照抽奖列表数组的index进行,那会变成这样:
于是这里专门根据顺时针对应的drawList的index来组成了一个数组drawOrder:
const drawOrder = [0, 1, 2, 5, 8, 7, 6, 3] // 抽奖顺序
const currentIndex = ref<number | null>(null) // 当前选中的奖品
②奖品高亮
专门用一个active类来表示选中高亮,只要抽奖的当前的位置等于列表里面的奖品位置,该项高亮。
.active {
background-color: rgb(214, 83, 83);
}
③中奖
首先需要判断只有抽奖列表里面的抽奖按钮才能触发click事件。也就是index===4。
其次开始抽奖后不能再抽奖了,所以需要一个变量表示是否在抽奖。(防抖效果)
最后抽奖次数倒数为0的时候停下中奖。所以需要定义总抽奖次数(多久后停下来)
let count = 0 // 抽奖次数
let isDrawing = false // 是否正在抽奖
const circle = 32 // 一圈8个奖品,至少转4圈
这里具体是哪个奖品中奖是从后台获取。为了方便,以下代码直接写成第五个。
const draw = (index: number) => {
if (index === 4) {
// 开始抽奖
if (isDrawing) {
return
}
isDrawing = true
const position = 5 // 假设后台返回的中奖位置是5
const timer = setInterval(() => {
currentIndex.value = drawOrder[count % drawOrder.length]
count++
if (count > circle && currentIndex.value === drawOrder[position - 1]) {
// 抽奖结束
clearInterval(timer)
// 停顿一会显示中奖
setTimeout(() => {
alert('恭喜你中奖了')
isDrawing = false
count = 0
currentIndex.value = null
}, 500)
}
}, 50)
}
}
中间的逻辑自行替换。需要替换的有:
- 圈数 --> 时间(可能)
- position从后台获取
- 抽奖完成后的其他逻辑
二、完整代码
<template>
<div class="container">
<div
v-for="(item, index) in DrawList"
:key="index"
class="prize-item"
:class="currentIndex === index ? 'active' : ''"
@click="() => draw(index)"
>
<img :src="item.url" alt="" />
<p class="desc">{{ item.desc }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import none from '../assets/none.png'
import topPrize from '../assets/top-prize.png'
import secondPrize from '../assets/second-prize.png'
import thirdPrize from '../assets/third-prize.png'
import fourthPrize from '../assets/fourth-prize.png'
import redEnvelope from '../assets/red-envelope.png'
interface Prize {
url: string
desc: string
}
const pirzeList: Prize[] = [
{ url: redEnvelope, desc: '现金红包' },
{ url: topPrize, desc: '一等奖' },
{ url: thirdPrize, desc: '三等奖' },
{ url: none, desc: '谢谢参与' },
{ url: secondPrize, desc: '二等奖' },
{ url: redEnvelope, desc: '现金红包' },
{ url: none, desc: '谢谢参与' },
{ url: fourthPrize, desc: '四等奖' }
]
const btnStart = { url: '', desc: '开始抽奖' }
const DrawList = computed(() => {
return [...pirzeList.slice(0, 4), btnStart, ...pirzeList.slice(4)]
})
const drawOrder = [0, 1, 2, 5, 8, 7, 6, 3] // 抽奖顺序
let count = 0 // 抽奖次数
let isDrawing = false // 是否正在抽奖
const currentIndex = ref<number | null>(null) // 当前选中的奖品
const circle = 32 // 一圈8个奖品,至少转4圈
const draw = (index: number) => {
if (index === 4) {
// 开始抽奖
if (isDrawing) {
return
}
isDrawing = true
const position = 5 // 假设后台返回的中奖位置是5
const timer = setInterval(() => {
currentIndex.value = drawOrder[count % drawOrder.length]
count++
if (count > circle && currentIndex.value === drawOrder[position - 1]) {
// 抽奖结束
clearInterval(timer)
// 停顿一会显示中奖
setTimeout(() => {
alert('恭喜你中奖了')
isDrawing = false
count = 0
currentIndex.value = null
}, 500)
}
}, 50)
}
}
</script>
<style lang="scss" scoped>
.container {
width: 528px;
height: 528px;
background: url('../assets/bg.png') no-repeat;
padding: 55px 55px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
.prize-item {
width: 130px;
height: 130px;
text-align: center;
img {
margin-top: 10px;
}
}
.active {
background-color: rgb(214, 83, 83);
}
}
</style>