使用vue2和vue3分别实现九宫格抽奖功能

关于九宫格抽奖的功能,想必大家都见过。外圈为奖品,中间是一个抽奖的按钮,接下来就讲解怎么实现九宫格的抽奖功能。

本demo使用技术:vue2/vue3,作用域插槽,定时器,递归自调用,注册全局组件

这里我就搭建个新vue3的项目,虽然是vue3,但也完全可以使用vue2的写法实现

创建项目删除初始化代码省略…

在这里我是写死的数据。首先我们在App组件里声明list数组,用来当做奖品。这里我们在list数组里写8个项,id从0到7,text可随意写(九宫格为什么写8个,是因为另一个是按钮,并不是奖品)

list: [
        {
          id: 0,
          text: "腾讯100元",
        },
        {
          id: 1,
          text: "腾讯50元",
        },
        {
          id: 2,
          text: "腾讯10元",
        },
        {
          id: 3,
          text: "腾讯5元",
        },
        {
          id: 4,
          text: "腾讯一元",
        },
        {
          id: 5,
          text: "腾讯100积分",
        },
        {
          id: 6,
          text: "腾讯500元",
        },
        {
          id: 7,
          text: "腾讯1毛钱",
        },
      ],

然后我们通过父子组件的传值,把list传给子组件。子组件使用props接收。代码省略

然后写个li标签,并使用v-for把每一项循环渲染出来。
九宫格的样式可随意写懒的可以直接复制,我这里cj_box类名写在了ul标签上
完整样式:

.cj_box {
  width: 800px;
  height: 800px;
  margin: auto;
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  li {
    width: 251px;
    height: 251px;
    background-color: #eee;
    text-align: center;
    line-height: 251px;
    &:nth-child(5) {
      cursor: pointer;
    }
  }
  li.active {
    background-color: rgb(233, 159, 159);
  }
}

注意:我们这里给了九宫格的样式后,渲染li的顺序是从上往下,从左往右。然而正常的九宫格应该是顺时针围绕在外围一圈的,因为中间的按钮。 不一样的人,也可能会有不一样的思路
在这里插入图片描述
在这里我们对他进行更改下标即可,在计算属性里对list数组进行map处理,然后使用switch语句就能实现

let ary = this.list.map((item, index) => {
        switch (index) {
          case 3:
            return {
              ...this.list[7],
              index: 7,
            };
          case 4:
            return {
              ...this.list[3],
              index: 3,
            };
          case 5:
            return {
              ...this.list[6],
              index: 6,
            };
          case 6:
            return {
              ...this.list[5],
              index: 5,
            };
          case 7:
            return {
              ...this.list[4],
              index: 4,
            };

          default:
            return {
              ...item,
              index: index,
            };
        }
      });

以上代码,阅读起来可能会有点麻烦。但仔细看也并不难,只是把正常下标,改成了九宫格形式的下标。

就比如第一个case的下标是3,那就是正常情况下的第二行第一个li;把他改成九宫格形式,那就是第八个li了,下标也自然是7了,以此类推。最后的default,也就是前三个li,他们不用改变

在这里插入图片描述
下面我们开始增加第五个li,点击按钮(计算属性完整代码如下)

computed: {
    renderList() {
      let ary = this.list.map((item, index) => {
        switch (index) {
          case 3:
            return {
              ...this.list[7],
              index: 7,
            };
          case 4:
            return {
              ...this.list[3],
              index: 3,
            };
          case 5:
            return {
              ...this.list[6],
              index: 6,
            };
          case 6:
            return {
              ...this.list[5],
              index: 5,
            };
          case 7:
            return {
              ...this.list[4],
              index: 4,
            };

          default:
            return {
              ...item,
              index: index,
            };
        }
      });
      return ary
        .slice(0, 4)
        .concat({ id: "btn", text: "开始抽奖" })
        .concat(ary.slice(4));
    },
  },

最下面的 return ary 就是给数组增加一个标签,赋予id和text

基本搭建完成,如图:
在这里插入图片描述

接下来就给中间按钮添加点击事件了,不要迷惑
这里直接在循环的li标签里,写@click就行,然后把循环的每一项当做参数,传递过去。
这里循环遍历的不是list 而是renderList函数

<li
  v-for="item in renderList"
  :key="item.id"
  @click="fn(item)"
>
{{ item.text }}
</li>

这样就把写了个点击事件,并传递了参数
接着就在methods里写代码了


注意看代码里的注释信息

fn(item) {
		// 在return ary那里 我们添加的点击li,赋予的id是btn
		// 下面代码当不等于btn的时候,那就意味着不是点击按钮,直接return
      if (item.id !== "btn") return;
      // 然后在date里,定义一个下标赋值为null,初始值设为null是控制抽奖转动,不点击按钮的时候,就不会有显示中奖的高亮
      this.activeIndex = null;
      // 封装一个抽奖的函数,并在fn函数里调用  ,参数为执行的时间参数
      this.move(10);
    },

注意看代码里的注释信息

// 参数time,表示传入的时间数,每10毫秒执行一次
move(time) {
	this.timer = setTimeout(() => {
	// 这里给空下标加1,每次执行累加 1
	      let n = this.activeIndex + 1;
	      // 把获取累加下标的值,对8进行取余,那么就会在下标为8里一直循环,永远不会出现下标为9的情况
	      this.activeIndex = n % 8;
	      // 这里是递归自调用一直循环,参数的设置代表了速度逐渐变慢,但并不会停下
	      this.move(time + time * 0.1);
	      // time时间值,
	}, time);
}

表面上可以进行抽奖了,现在添加一下抽奖的样式
绑定下面的class类即可,根据下标变动,

<li
   v-for="item in renderList"
   :key="item.id"
   :class="{ active: activeIndex === item.index }"
   @click="fn(item)"
>
    {{ item.text }}
</li>

现在是不是就有了表面效果了


下面给个判断,让他停下来,不然会一直抽奖下去

move(time) {
      if (time > 800) {
      	
      } else {
        this.timer = setTimeout(() => {
          let n = this.activeIndex + 1;
          this.activeIndex = n % 8;
          this.move(time + time * 0.1);
        }, time);
      }
    },

添加if判断,把代码放在else里,当时间参数不大于800时就继续执行,这样就能停下来了。但有个问题是,他永远都抽中了同一个奖品


更改:
date里声明变量,

data() {
    return {
      activeIndex: null,
      cj: 5, // 控制中奖为第五个下标
    };
  },

这里的控制中奖,既可以设置为随机中奖,也可以设置为只种一个奖(就很棒!)我们这里的下标为五的奖品是100积分


在上面空的if为true代码块里,再写个判断
注意代码注释

move(time) {
      if (time > 800) {
      // 当中奖下标不等于我设置的奖品下标时,继续执行,
        if (this.activeIndex !== this.cj) {
        // 定时器
          this.timer = setTimeout(() => {
            let n = this.activeIndex + 1;
            this.activeIndex = n % 8;
            // 这里时间参数要小,这样才不会看出来倪端,不然猛的停下来,一看就太假。这样就是慢慢停到你想让他中奖的那个li上
            this.move(time + time * 0.05);
          }, time);
        } else {
        // 中奖下标等于我设置的奖品下标时,中奖了
          console.log("中奖了", this.cj);
        }
      } else {
        this.timer = setTimeout(() => {
          let n = this.activeIndex + 1;
          this.activeIndex = n % 8;
          this.move(time + time * 0.1);
        }, time);
      }
    },

太没人性了,下面我们写随机中奖
注意代码注释

fn(item) {
      if (item.id !== "btn") return;
      this.activeIndex = null;
      // 控制随机中奖,先清空写死的中奖下标
      this.cj = null
       setTimeout(()=>{
       // 使用Math.random 随机数 *8    然后赋值给我们的   this.cj 即可
          this.cj = Math.floor(Math.random()*8)   
          console.log(this.cj);
      },1000)
      this.move(10);
    },

这样就很银杏化了!!!


下面我们把item.text,写成作用域插槽

在App的子组件标签里,写上作用域插槽

<cj :list="list">
	// 这里是奖品
	<template #item='itemScope'>
	    <p>
	        {{itemScope.itemDate.text}}
	    </p>
	</template>
	// 这里是按钮
	<template #btn>
	    <p>
	       用户的按钮
	    </p>
	</template>
</cj>

然后在子组件里,更改 item.text

// li标签里,先注释掉  {{ item.text }}
// {{ item.text }}
// 写上slot   把item数据传递过去,接着进行v-if判断一下,不写v-if的话九宫格就全是奖品,没有按钮了
  <slot name="item" :itemDate="item" v-if="item.id!=='btn'"></slot>
  <slot name="btn" v-else></slot>

作用域插槽就完成了,下面我们改成vue3的写法


VUE3 (不多做详细解释),有过了解的,一看便懂
ps:vue3不需要data,因此没有this。使用的vue3内部的方法,都需要引入。函数和变量都写在一个函数里,都需要return出去。对于ref绑定的动态值做处理时,都需要写成 变量.value

<template>
  <ul class="cj_box">
    <li
      v-for="item in renderList"
      :key="item.id"
      :class="{ active: activeIndex === item.index }"
      @click="fn(item)"
    >
      <slot name="item" :itemDate="item" v-if="item.id !== 'btn'"></slot>
      <slot name="btn" v-else></slot>
    </li>
  </ul>
</template>

<script>
// 注册
import { computed, ref } from "vue";
export default {
  name: "cj",
  props: {
    list: Array,
  },
  // setup(),传入子组件信息
  setup(props) {
    // 下标
    let activeIndex = ref(null);
    // 默认中奖
    let cj = ref(5);
    // 时间值
    let timer = null;

    // 点击开始抽奖
    let fn = function (item) {
      if (item.id !== "btn") return;
      activeIndex.value = null;
      // 控制随机中奖
      cj.value = null;
      setTimeout(() => {
        cj.value = Math.floor(Math.random() * 8);
        console.log(cj);
      }, 1000);
      // 调用抽奖函数
      move(10);
    };
    // 抽奖函数
    let move = function (time) {
      if (time > 800) {
        // 若非随机,指定奖品
        if (activeIndex.value !== cj.value) {
          timer = setTimeout(() => {
            let n = activeIndex.value + 1;
            activeIndex.value = n % 8;
            move(time + time * 0.05);
          }, time);
        } else {
          // 中奖了
          console.log("中奖了", cj);
        }
      } else {
        // 抽奖
        timer = setTimeout(() => {
          let n = activeIndex.value + 1;
          activeIndex.value = n % 8;
          move(time + time * 0.1);
        }, time);
      }
    };
    // 更改下标,改成九宫格形式
    let renderList = computed(() => {
      let ary =
        // 对如果为空,做处理
        props.list &&
        props.list.map((item, index) => {
          switch (index) {
            case 3:
              return {
                ...props.list[7],
                index: 7,
              };
            case 4:
              return {
                ...props.list[3],
                index: 3,
              };
            case 5:
              return {
                ...props.list[6],
                index: 6,
              };
            case 6:
              return {
                ...props.list[5],
                index: 5,
              };
            case 7:
              return {
                ...props.list[4],
                index: 4,
              };

            default:
              return {
                ...item,
                index: index,
              };
          }
        });
      // 添加点击按钮
      return ary
        .slice(0, 4)
        .concat({ id: "btn", text: "开始抽奖" })
        .concat(ary.slice(4));
    });
    return {
      activeIndex,
      renderList,
      cj,
      fn,
      move,
      timer,
    };
  },
};
</script>

<style scoped lang="less">
.cj_box {
  width: 800px;
  height: 800px;
  margin: auto;
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  li {
    width: 251px;
    height: 251px;
    background-color: #eee;
    text-align: center;
    line-height: 251px;
    &:nth-child(5) {
      cursor: pointer;
    }
  }
  li.active {
    background-color: rgb(233, 159, 159);
  }
}
</style>


补充:
可写个独立的js文件,引入vue组件并注册,然后把js文件,在mian.js里引入。使用时,直接组件名就行,不需要引入和注册

// 独立js文件名字为 cj.js       cj3.vue组件为vue3的写法
import cj3 from './cj3.vue'
export default {
    install(_Vue) {
        console.log(_Vue);
        _Vue.component('cj', cj3)
    }
}
// main文件   引入 cj.js并注册
import { createApp } from 'vue'
import App from './App.vue'
import cj from './abc/cj.js'

createApp(App).use(cj).mount('#app')

如下就是在App组件里,使用通过cj.js,全局注册的cj.vue组件。直接使用即可
在这里插入图片描述


结束!

加油! Respect peace and love

Logo

前往低代码交流专区

更多推荐