Vue.js学习笔记(四)抽奖组件封装——九宫格抽奖

基于VUE2九宫格组件的开发


前言

由于业务需求,需要手动开发九宫格抽奖组件,特此记录学习宫格组件开发。


一、开发步骤

1.数据准备

data 代码如下:包含页面功能所需要的变量

 data() {
    return {
      /** dom内使用window对象需要先声明,否则报错 */
      window,
      goPointer: require("@/assets/images/goPointer.png"),
      /** 活动设置 */
      activeInfo: {
        /** 
        * 中奖概率,需要根据自己的业务需求定义格式
        * 由于后台只会填写会中奖的奖项
        * 所以后面需要自己补齐未中奖的数据 */
        probabilities: {
          "一等奖": 10,
          "二等奖": 10,
          "三等奖": 10,
          "四等奖": 10
        },
        /** 
        * 奖品信息,用于获取下标
        * 包含奖项和未中奖,一维数组 */
        prizeList: [],
        /** 
        * 奖品信息,用于页面渲染
        * 包含奖项和未中奖,二维数组 */
        prizeListCol: []
      },
      /** 选中状态样式 */
      activeStyle: {
        backgroundColor: '#FDE2E1',
        borderColor: '#F25561',
        color: '#F25561'
      },
      /** 抽奖动画 */
      animationInfo: {
        /** 是否处于抽奖状态 */
        isTurnOver: false,
        /** 抽奖次数 */
        goTimes: 3,
        /** 中奖结果下标 */
        winningIndex: null,
        /** 中奖名字 */
        winningName: '',
        /** 初始化速度 */
        startSpeed: 150,
        /** 动画实时速度 */
        speed: 0,
        /** 定时器实例 */
        interval: null,
        /** 最大抽奖时长 */
        maxAnimationTime: 4500,
        /** 当前走到的index */
        activeIndex: null,
        /** 动画路径数组 */
        rorateArr: [0, 1, 2, 4, 7, 6, 5, 3],
        /** 动画第一格 */
        rotateIndex: 0,
        /** 开始时间 */
        startTime: null,
        /** 结束时间 */
        endTime: null
      }
    }
  },

created 代码如下:主要处理请求回来的奖项数据

created() {
    let data = [
      {
        name: '一等奖',
        describe: '一等奖',
        img: 'https://img01.yzcdn.cn/vant/cat.jpeg',
        isSelected: false
      },
      {
        name: '二等奖',
        describe: '二等奖',
        img: 'https://img01.yzcdn.cn/vant/cat.jpeg',
        isSelected: false
      },
      {
        name: '三等奖',
        describe: '三等奖',
        img: 'https://img01.yzcdn.cn/vant/cat.jpeg',
        isSelected: false
      },
      {
        name: '四等奖',
        describe: '四等奖',
        img: 'https://img01.yzcdn.cn/vant/cat.jpeg',
        isSelected: false
      },
    ];
    /** 不足8个数据自动补足 */
    while (data.length < 8) {
      for (let i = 0; i < data.length; i++) {
        data.splice(i * 2 + 1, 0, {
          name: '未中奖',
          describe: '未中奖',
          img: 'https://img01.yzcdn.cn/vant/cat.jpeg'
        });
        if (data.length >= 8) {
          break;
        }
      }
    }
    /** 把Z字形下标转为O型 */
    const newData = nineGridOrder(data, 3);
    this.activeInfo.prizeList.push(...newData);
    /** 切割成二维数组用于渲染页面 */
    const TDData = spliceArray(newData, 3);
    this.activeInfo.prizeListCol.push(...TDData);
  },

数据处理所用到的方法

/**
 *  将顺序从Z型布局变成圈型布局
 *  @param arr Array, 待改变顺序的数组
 *  @param level Number, 所希望生成的多宫格的层级
 *  @return 改变顺序后的数组
 * */
export function nineGridOrder(arr, level) {
  if (Array.isArray(arr) && arr.length == (4 * level - 4)) {
    // 对数据进行浅拷贝
    let list = [...arr];
    // 在当前层级下期望的元素总个数
    const expectCount = 4 * level - 4;
    // 取要进行对应插入的尾部
    let tailArr = list.splice(expectCount - level + 2, expectCount).reverse();
    // 取要进行翻转处理的尾部
    let otherTail = list.splice(expectCount - 2 * level + 2, expectCount).reverse();
    // 进行对应插入
    for (let i = 0; i < tailArr.length; i++) {
      list.splice(level + 2 * i, 0, tailArr[i])
    }
    // 重新组合后输出
    return list.concat(otherTail)
  } else {
    throw ('层级与数据对应不正确,请检查输入层级后重试')
  }
}


/**
 *  function, 对数组进行分组处理,比如8个元素的数组会被分成3+2+3
 *  @param arr Array, 待处理的数组
 *  @param level Number, 所希望生成的多宫格的层级
 *  @return Array,改变后的数组
 * */
export function spliceArray(arr, level) {
  if (Array.isArray(arr) && level >= 3) {
    let list = [...arr];
    let head = list.splice(0, level);
    let tail = list.splice(list.length - level, list.length - 1);
    let tempArr = departArray(list, 2);
    tempArr.unshift(head);
    tempArr.push(tail);
    return tempArr
  }
}

/** 将数组按两个一组进行分组 */
function departArray(arr, length, store) {
  let list = [...arr];
  let temList = store || [];
  if (list.length >= length) {
    temList.push(list.splice(0, length))
    return departArray(list, length, temList);
  } else {
    return temList;
  }
}

2.页面布局

组件用到了vant2,可以根据自己需要改成原生DOM,或者其他组件库的组件

<template>
  <div :class="device">

    <!-- 内容 -->
    <van-row class="container">
      <van-col span="24" class="gridBox">
        <div class="gridMain" :style="`height:${window.innerWidth * 0.9}px;width:${window.innerWidth * 0.9}px;`">
          <van-row v-for="(item, index) in activeInfo.prizeListCol" :key="index" class="gridRow">
            <van-col span="8" v-for="(items, indexs) in item" :key="indexs" class="gridCol">
              <div :style="items.isSelected ? activeStyle : ''">
                <p>{{ items.name }}</p>
                <!-- <img :src="items.img" /> -->
              </div>
            </van-col>
          </van-row>
          <div class="go">
            <div @click="go">
              立即抽奖
            </div>
          </div>
        </div>
      </van-col>
    </van-row>
  </div>
</template>

样式,使用了less样式预处理器

<style lang="less">
.container {
  min-height: 100vh;
  width: 100%;
}

.gridBox {
  float: none;

  .gridMain {
    margin: 30px auto;
    position: relative;
  }

  .gridRow {
    display: flex;
    justify-content: space-between;
    width: 100%;
    height: 33.33%;

    .gridCol {
      box-sizing: border-box;
      padding: 10px;
      height: 100%;
      overflow: hidden;
      display: table;

      div {
        display: table-cell;
        vertical-align: middle;
        border: 2px solid #F6C494;
        border-radius: 8px;
        background-color: #fff;
        color: #F6C494;

        img {
          width: 100%;
          height: auto;
        }
      }

    }
  }

  .gridRow::after {
    display: none;
  }

  .go {
    position: absolute;
    width: 33.33%;
    height: 33.33%;
    box-sizing: border-box;
    text-align: center;
    line-height: 100%;
    padding: 10px;
    top: calc(50% - (33.33% / 2));
    left: calc(50% - (33.33% / 2));
    display: table;

    div {
      background: #F25B52;
      border: 2px solid #FAE5CC;
      color: #FFF;
      display: table-cell;
      vertical-align: middle;
      border-radius: 8px;
      font-size: 18px;
      font-weight: bold;
      letter-spacing: 2px;
    }
  }
}
</style>

3.事件方法

/** 计算方法 */
computed: {
    /** 动画运行时长 */
    rotateTime() {
      return this.animationInfo.endTime - this.animationInfo.startTime
    },
    /** 动画运行速度 */
    speedIntervalValue() {
      return this.animationInfo.startSpeed / 10
    }
  },
  /** 事件 */
  methods: {
    /** 获取抽奖结果 */
    generatePrize() {
      /** 生成一个 0 到 99 之间的随机数 */
      const randomNum = Math.floor(Math.random() * 100);
      let cumulativeProbability = 0;
      for (const prize in this.activeInfo.probabilities) {
        cumulativeProbability += this.activeInfo.probabilities[prize];
        if (randomNum < cumulativeProbability) {
          /** 返回中奖内容 */
          return prize;
        }
      }
      // 默认返回未中奖
      return "未中奖";
    },
    /** 点击抽奖 */
    go() {
      if (!this.animationInfo.isTurnOver && this.animationInfo.goTimes > 0) {
        this.animationInfo.isTurnOver = true;
        /** 抽奖结果 */
        const result = this.generatePrize();
        this.animationInfo.winningName = result;
        /** 
         * 获取奖项下标
         * 奖项名字可能会重复,所以需要找到奖项的所有下标保存到数组里
         * 根据下标数组随机生成一个数字来决定选择哪个下标成为最终结果的下标
         *  */
        const resultIndexArray = this.activeInfo.prizeList.reduce((acc, item, index) => {
          if (item.name === result) {
            acc.push(index);
          }
          return acc;
        }, []);
        const randomResultIndex = Math.floor(Math.random() * resultIndexArray.length);
        this.animationInfo.winningIndex = resultIndexArray[randomResultIndex];
        this.animationInfo.goTimes--;
        this.reset();
        const timeout = setTimeout(() => {
          this.lottery()
          clearTimeout(timeout)
        }, this.animationInfo.speed)
      }
      else if (!this.animationInfo.isTurnOver && this.animationInfo.goTimes <= 0) {
        this.$toast({
          message: '抱歉,您的抽奖次数用完了',
          duration: 3000,
        });
      }
      else {
        this.$toast('请勿重复点击')
      }
    },
    /** 抽奖动画逻辑 */
    lottery() {
      /** 清除旧定时器 */
      clearInterval(this.animationInfo.interval)
      this.animationInfo.interval = null;
      if (!this.animationInfo.isTurnOver) { return }
      /** 变动动画速度,逐渐加快 */
      this.animationInfo.speed -= this.speedIntervalValue;
      if (this.animationInfo.speed <= 50) {
        /** 最快速度 */
        this.animationInfo.speed = 50
      };
      /** 距离动画时间结束不到3秒时,动画逐渐减慢 */
      if (this.animationInfo.maxAnimationTime - this.rotateTime <= 2000) {
        /** 这里加两倍,因为再次进入lottery方法,默认会减一倍,所以这里实际上是相当于加一倍*/
        this.animationInfo.speed += this.speedIntervalValue * 2
      }
      if (this.rotateTime > this.animationInfo.maxAnimationTime) {
        /** 超过时长了 */
        if (this.animationInfo.winningIndex == this.animationInfo.activeIndex) {
          /** 动画跑到了中奖奖品所在位置 */
          this.animationInfo.isTurnOver = false;
          this.$toast(this.animationInfo.winningName === '未中奖' ? '很遗憾,您未中奖!' : `恭喜您,获得${this.animationInfo.winningName}`)
          return
        }
      }
      this.rotate();
      this.animationInfo.interval = setInterval(this.lottery, this.animationInfo.speed)
    },
    /** 选中高亮 */
    rotate() {
      /** 从第一开始跑 */ 
      if (this.animationInfo.rotateIndex === this.animationInfo.rorateArr.length) {
        this.animationInfo.rotateIndex = 0;
        this.activeInfo.prizeList[this.animationInfo.rotateIndex].isSelected = true;
      }
      /** 走到哪就点亮哪个方格 */ 
      this.animationInfo.activeIndex = this.animationInfo.rorateArr[this.animationInfo.rotateIndex];
      this.activeInfo.prizeList.forEach((item, index) => {
        if (this.animationInfo.activeIndex === index) {
          this.activeInfo.prizeList[index].isSelected = true;
        } else {
          this.activeInfo.prizeList[index].isSelected = false;
        }
      })
      this.animationInfo.endTime = new Date().getTime();
      /** 记录当前位置 */ 
      this.animationInfo.rotateIndex += 1;
    },
    /** 重置 */
    reset() {
      this.animationInfo.startTime = new Date().getTime();
      this.animationInfo.endTime = this.animationInfo.startTime;
      this.animationInfo.speed = this.animationInfo.startSpeed;
      this.animationInfo.rotateIndex = 0;
    }
}

二、最终效果

在这里插入图片描述


总结

以上就是今天要讲的内容,本文仅仅简单介绍了九宫格抽奖组件的基本实现,仅供学习参考。

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值