希望和各位前端大神一起学习【QQ:1787750205】

最近项目有一个需求,实现一个抽奖功能,于是封装了一个抽奖组件,
可以配置奖项权重(中奖比例)或指定抽中某一个固定奖项。

组件页面代码luckDraw.wxml

<view class="body" style="width:{{bodyWidth}};margin-left:calc((100vw - {{bodyWidth}}) / 2);">
  <!-- 奖项列表 -->
  <view class="itemBody">
    <!-- 奖项 -->
    <view class="item" 
      style="width:calc((100% - 8rpx * {{size * 2}}) / {{size}});height:{{itemHeight?itemHeight+'rpx': 'calc(('+bodyWidth+' - 8rpx * '+size * 2+') / '+size+')'}};" 
      wx:for="{{allList}}" wx:key="index">
      <!-- 当前奖项抽中时的遮罩 -->
      <view wx:if="{{item.isChoose}}" 
        class="showItem" 
        style="background-color:{{chooseColor}}"></view>
      <!-- 奖项图片 -->
      <image class="bgPic" wx:if="{{item.info.pic}}" src="{{item.info.pic}}" />
      <!-- 奖项标题 -->
      <view class="itemTitle">{{item.info.title}}</view>
      <!-- 奖项副标题 -->
      <view class="itemSubTitle">{{item.info.subTitle}}</view>
    </view>
  </view>
  <!-- 开始抽奖按钮 -->
	<view class="startBtn" 
    style="width:calc({{bodyWidth}} / 3 - 16rpx);height:calc({{bodyWidth}} / 3 - 16rpx);margin-left:calc(({{bodyWidth}} - ({{bodyWidth}} / 3 - 16rpx)) / 2);top:calc(({{bodyWidth}} - ({{bodyWidth}} / 3 - 16rpx)) / 2);background-color:{{startBtn.bgColor?startBtn.bgColor:'#FEE300'}};border-radius:{{startBtn.radius?startBtn.radius:10}}rpx;" 
    bindtap="setRes">
    <!-- 按钮文字 -->
    <view style="color:{{startBtn.color?startBtn.color:'#F6251D'}}">{{startBtn.text?startBtn.text:'开始抽奖'}}</view>
    <!-- 按钮副标题 -->
    <text style="color:{{startBtn.subColor?startBtn.subColor:'#FB6761'}}">{{startBtn.subTitle?startBtn.subTitle:''}}</text>
  </view>
</view>

组件样式表luckDraw.wxss

.body{
  width: 99.5vw;position: relative;
}
.itemBody{
  width: 100%;position: relative;display: flex;flex-wrap: wrap;justify-content: center;
}
.item{
  position: relative;margin: 6rpx;display: flex;flex-direction: column;align-items: center;justify-content: center;
}
.showItem{
  position: absolute;z-index: 30;width: 100%;height: 100%;opacity: 0.5;border: 1rpx solid;
}
.bgPic{
  position: absolute;width: 100%;height: 100%;z-index: 1;
}
.itemTitle{
  position: relative;font-size: 30rpx;font-weight: bold;z-index: 10;text-align: center;padding: 0 10rpx;
}
.itemSubTitle{
  position: relative;font-size: 24rpx;margin-top: 10rpx;z-index: 10;text-align: center;padding: 0 10rpx;
}
.startBtn{
  position: absolute;z-index: 20;display: flex;flex-direction: column;align-items: center;justify-content: center;font-weight: bold;font-size: 40rpx;
}
.startBtn text{
  display: block;font-weight: normal;font-size: 28rpx;margin-top: 10rpx;
}

组件逻辑脚本 luckDraw.js

let _this
let chooseIndex = 0//当前选中的奖项
let relSize//有效奖项个数
let size//规格
let indexList = []//存放奖项下标映射
let isRonate = false//是否正处于抽奖状态
// 私有函数使用‘_’号开头命名,仅作为本功能工具函数使用
Component({
  properties: {
    size: {//设置size*size的宫格  
      //实际有效奖项个数:3*3-8 4*4-12 5*5-16 6*6-20,总结规律:(size - 1) * 4
      type: Number,
      value: 3//默认为3*3的九宫格,可以自定义4*4的16宫格或者更多,但是建议最多5个
    },
    list: {//奖项列表
      type: Array,
      value: []
      /**
       * weight:权重(概率),如果没传则默认视为概率都是一样的
       * title:奖项名称【必填】
       * subTitle:奖项副标题
       * pic:奖项图片
       * 其他自定义属性(结果回调时根据业务进行不同的事件处理)
       */
    },
    bodyWidth: {//整体宽度
      type: String,
      value: '100vw'
    },
    itemHeight: {//子项高度 默认和宽度一致
      type: Number,
      value: 0
    },
    chooseColor: {//选中时的遮罩颜色
      type: String,
      value: '#000'
    },
    startBtn: {
      type: Object,
      value: {
        /**
         * bgColor:抽奖按钮背景色
         * color:抽奖按钮主体文字颜色
         * subColor:抽奖按钮副标题文字颜色
         * radius:抽奖按钮圆角
         * text:抽奖按钮主体文字
         * subTitle:抽奖按钮副标题文字
         */
      }
    }
  },
  data: {
    allList: []
  },
  methods: {
    //初始化数据处理
    render: function(){
      let list = this.data.list
      //自动生成跟weight权重(比例)配合使用的奖项id
      list.forEach((item,index)=>{
        item['id'] = index
        if (!item.weight) {//如果没传权重则默认视为概率为 1/权重总数
          item['weight'] = 1
        }
      });
      this.setData({
        list
      },()=>{
        size = this.data.size
        relSize = (size - 1) * 4
        let allList = []
        // 分别设置上右下左最开始取的下标
        let renderT = 0,
            renderR = size - 1,
            renderB = size * 3 - 2 - 1,
            renderL = size * 3 - 2 + (size - 3);
        //创建抽奖转盘列表
        for (let i = 0; i < size * size; i++) {
          allList.push({index: i})
        }
        //判断哪些是真实有效的列表项
        allList.forEach((item,index) => {
          item.isChoose = false
          let isRelItem = index + 1 < size + 2 || index + 1 > (size * size - size - 1) || (index + 1) % size == 1 || (index + 1) % size == 0
          item.isRelItem = isRelItem
          //第二行开始顺时针取边上的
          let nowL = index + 1
          if (nowL > size) {
            // console.log(`${nowL}%${size}=${nowL%size}`)
            //先取右边除第一行外的最后一个
            if (nowL % size == 0) {
              item.relIndex = renderR + 1
              item.info = list[renderR + 1]
              renderR += 1
            }
            //再取下边除最后一个的其他
            if (nowL > (size * (size - 1)) && nowL < size * size){
              //需要反向设置
              item.relIndex = renderB
              item.info = list[renderB]
              renderB -= 1
            }
            //取左边除第一个和最后一个的其他
            if (nowL % size == 1 && nowL != size * (size - 1) + 1) {
              item.relIndex = renderL
              item.info = list[renderL]
              renderL -= 1
            }
          }else{
            item.relIndex = renderT
            item.info = list[renderT]
            renderT += 1
          }
          //将奖品信息装入有效的抽奖项
          if(isRelItem){
            item.info = list[item.relIndex]
          }
        })
        this.setData({allList},()=>{
          let findItem
          indexList = []
          for (let i = 0; i < relSize; i++) {
            findItem = allList.find(item => item.relIndex == i)
            indexList.push(findItem.index)
          }
        })
      });
    },
    //设置结果并通过triggerEvent回调给父组件,默认为基于权重生成随机数,如果指定了awards则固定获得第awards+1个奖项
    setRes: function(e,awards){
      //参数e仅是为了忽略内部参数,勿删
      //如果正在抽奖则不可重复抽奖,需要等上一次抽奖完成
      if (isRonate) return;
      if (!awards) {
        awards = this._weightRandom();
      }else{
        awards = Number(awards)
      }
      console.log(awards)
      this._ronate(awards,2,res=>{
        _this.triggerEvent('luckdraw', {luckdraw: res}, {})
      })
    },
    //【私有函数】基于权重生成随机数,可指定值
    _weightRandom: function(awards){
      let randomConfig = this.data.list;
      let randomList = [];
      for (let i in randomConfig) {
          for (let j = 0; j < randomConfig[i].weight; j++) {
              randomList.push(randomConfig[i].id);
          }
      }
      let randomValue = randomList[Math.floor(Math.random() * randomList.length)];
      //一直生成,直到生成希望的为止
      // if (awards != 0) {
      //     while (randomValue == curVal ) {
      //         randomValue  = randomList[Math.floor(Math.random() * randomList.length)];
      //     }
      // }
      return randomValue;
    },
    //【私有函数】抽奖转动(最终停留奖项的下标,转圈次数,回调函数)
    _ronate: function(jumpNum,quanNum,result){
      let jumpnum = 0
      isRonate = true
      _this.setData({
        ['allList[0].isChoose']:true
      })
      //若不是第一次抽奖,上一次抽中的奖项重置
      if (chooseIndex != 0) {
        _this.setData({
          ['allList['+indexList[chooseIndex]+'].isChoose']:false
        },()=>{
          chooseIndex = 0
        })
      }
      //基准延时速度
      let step = 150;
      let myFunction = function(){
        clearInterval(timer);
        _this.setData({
          ['allList['+indexList[chooseIndex]+'].isChoose']:false
        })
        chooseIndex ++
        jumpnum ++
        if (size == 3) {
          if (jumpNum < relSize / 2) {
            step = step + jumpnum * 2
          }else{
            step = step + jumpnum
          }
        } else if(size == 4) {
          if (jumpNum < relSize / 2) {
            step = step + jumpnum
          }else{
            step = step + jumpnum * 0.5
          }
        }else{
          if (jumpNum < relSize / 2) {
            step = step + jumpnum * 0.5
          }else{
            step = step + jumpnum * 0.25
          }
        }
        if (chooseIndex == relSize) {
          chooseIndex = 0
        }
        _this.setData({
          ['allList['+indexList[chooseIndex]+'].isChoose']:true
        })
        if (jumpnum == jumpNum + (quanNum * relSize)) {
          clearInterval(timer)
          isRonate = false
          let resItem = _this.data.allList[indexList[chooseIndex]]
          result(resItem)
        }else{
          timer = setInterval(myFunction, step);
        }
      }
      let timer = setInterval(myFunction, step);
    }
  },
  attached: function(){
    _this = this
    this.render();
  }
})

使用页面 index.wxml

<!-- 使用抽奖组件-默认为依据奖项权重随机抽奖 -->
<luck-draw size="{{4}}" 
  list="{{list}}"
  startBtn="{{startBtn}}" 
  id="luckDraw" 
  bind:luckdraw="luckdraw"></luck-draw>
<!-- 固定抽到奖项“谢谢参与”(data-num设置指定奖项的下标) -->
<view bindtap="go" data-num="11" style="margin-top:100rpx">
  指定抽中固定奖项
</view>

使用页面逻辑脚本 index.js

let _this
let luckDrawBtn;
Page({
  data: {
    list: [//奖励列表 weight权重(中奖比例)
      {title: '特等奖',subTitle: '苏州全款4室1厅住宅*1',pic: './timg.jpg',weight:1},
      {title: '一等奖',subTitle: '全款五菱神车*1',pic: './timg.jpg',weight:2},
      {title: '二等奖',subTitle: '全年帮还花呗',pic: './timg.jpg',weight:3},
      {title: '三等奖',subTitle: '现金8888元',pic: './timg.jpg',weight:4},
      {title: '四等奖',subTitle: '现金888元',pic: './timg.jpg',weight:5},
      {title: '五等奖',subTitle: '现金88元',pic: './timg.jpg',weight:6},
      {title: '六等奖',subTitle: '现金8元',pic: './timg.jpg',weight:7},
      {title: '幸运奖',subTitle: '通用优惠券1000元',pic: './timg.jpg',weight:8},
      {title: '支持奖',subTitle: '通用优惠券100元',pic: './timg.jpg',weight:13},
      {title: '鼓励奖',subTitle: '通用优惠券10元',pic: './timg.jpg',weight:25},
      {title: '安慰奖',subTitle: '通用优惠券1元',pic: './timg.jpg',weight:50},
      {title: '谢谢参与',subTitle: '',pic: './timg.jpg',weight:100,type:'未中奖'}
    ],
    startBtn: {
      text: '随机抽奖',
      subTitle: '100葫芦/次'
    }
  },
  onLoad: function(){
    _this = this
  },
  onReady: function(){
    luckDrawBtn = _this.selectComponent("#luckDraw");
  },
  //指定抽某一个奖项
  go: function(e){
    luckDrawBtn.setRes(null,e.currentTarget.dataset.num)
  },
  //抽奖组件回调
  luckdraw: function(e){
    // 回调中可以根据业务进行相应时间处理,例如抽奖次数-1或积分-1或对抽中奖项进行判断
    let item = e.detail.luckdraw
    if (item.info.type == '未中奖') {
      wx.showModal({
        title: item.info.title,
        content: `好可惜啊,就差一点就中奖了呢!试试换个姿势再试一次呢!`,
        showCancel: false
      })
      return
    }
    wx.showModal({
      title: '恭喜中奖',
      content: `恭喜抽中【${item.info.title}】-${item.info.subTitle},请等待奖品发放!`,
      showCancel: false
    })
  }
})

效果

Alt

Alt
Alt

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐