前端实现转盘抽奖 - 使用 lucky-canvas 插件

需求背景

要求实现转盘转动抽奖的功能:

  1. 只有正确率大于等于 80% 才可以进行抽奖;
  2. “谢谢参与”概率为 90%,“恭喜中奖”概率为 10%;

需求实现

在这里插入图片描述
在这里插入图片描述

实现过程图片示意

在这里插入图片描述

实现代码

安装插件

npm install @lucky-canvas/vue@latest

main.js 全局引入组件

import VueLuckyCanvas from '@lucky-canvas/vue'
Vue.use(VueLuckyCanvas)

实现代码

<template>
  <div class="exam-result">
    <div class="info">
      <div class="progress">
        <nut-circleprogress
            :progress="(correct / total).toFixed(1) * 100"
            :is-auto="true"
            color="#ff4d4f"
            path-color="#ffeded"
        >
          <div class="progressDiv">
            <div class="accuracy">正确率{
  { (correct / total).toFixed(1) * 100 }}%</div>
          </div>
        </nut-circleprogress>
      </div>
    </div>
    <div class="content">
      <div class="result-table">
        <div style="padding: 10px 10px 10px 15px">试卷分析</div>
      </div>
      <div class="result-table">
        <div class="item">
          <div class="title">题目总量:</div>
          <div class="total">{
  { total }}</div>
          <div class="unit"></div>
        </div>
      </div>
      <div class="result-table">
        <div class="item">
          <div class="title">正确题数:</div>
          <div class="correct">{
  { correct }}</div>
          <div class="unit"></div>
        </div>
        <div class="item">
          <div class="title">错误题数:</div>
          <div class="error">{
  { total - correct }}
          </div>
          <div class="unit"></div>
        </div>
      </div>
    </div>
    <div v-if="examType === 'challenge' && (correct / total).toFixed(1) >= 0.8" class="lottery_draw_btn">恭喜您获得抽奖资格 <nut-button type="primary" size="mini" @click="toLotteryDraw">点击进行抽奖</nut-button></div>
    <nut-dialog teleport="#app" :title="isShowlotteryDraw ? '点击“开始”抽奖' : ''" content="" v-model:visible="dialogVisible" customClass="task" :noCancelBtn="true" :noOkBtn="true" :closeOnClickOverlay="false">
      <nut-icon name="close" @click="dialogVisible = false" />
      <LuckyWheel
        v-if="isShowlotteryDraw"
        class="myLucky"
        ref="myLuckyRef"
        width="320px"
        height="320px"
        :prizes="prizes"
        :blocks="blocks"
        :buttons="buttons"
        @start="startCallback"
        @end="endCallback"
      />
      <div v-else class="result" :style="{
    '--color': lotteryDrawIndex === 1 ? 'red' : '#000'}">{
  { lotteryDrawIndex === 1 ? "恭喜中奖" : "谢谢参与" }}</div>
    </nut-dialog>
  </div>
  <fallback></fallback>
</template>

<script>
import {
     
  reactive, toRefs, ref, getCurrentInstance
} from 'vue'
import {
      useRoute } from 'vue-router'

export default {
     
  name: 'result',
  setup() {
     
    // const myLuckyRef = ref(null) // 【ref问题】我的代码里这种办法取不到 ref,使用 getCurrentInstance 取 ref
    const instance = getCurrentInstance() // 【ref解决】使用 getCurrentInstance 取 ref
    const route = useRoute()
    const state = reactive({
     
      lotteryDrawIndex: 0, // 最终转盘定格的索引
      isShowlotteryDraw: true, // 是否抽奖完成
      // 转盘背景配置
      blocks: [{
     
        padding: '20px',
        imgs: [{
     
          // src: 'https://img.iwave.net.cn/jeep/51c95637a377c3a12d09abe8b0f975e6.png',
          src: require('@/assets/images/lottery_draw.png'),
          width: 320,
          height: 320,
          rotate: true
        }]
      }],
      // 每个扇形区域奖品配置
      prizes: [...Array(10).keys()].map((index) => ({
     
        fonts: [
          {
     
            text: index % 2 === 0 ? '谢谢参与' : '恭喜中奖',
            top: '15%',
            fontSize: '15px',
            fontColor: '#ed1c24',
          },
        ],
        background: index % 2 === 0 ? '#fff5cc' : '#e9d6e9',
      })),
      // 抽奖按钮配置
      buttons: [
        {
      radius: '50px', background: '#d034ac' },
        {
      radius: '45px', background: '#fe97b2' },
        {
     
          radius: '35px',
          background: '#f04a07',
          pointer: true,
          fonts: [{
      text: '开始', top: '-10px', fontColor: '#fff' }]
        }
      ],
      // 抽奖弹框是否展示
      dialogVisible: false
    })
    // 获取正确题数、总题数
    const {
      correct, total, examType } = route.query

    const toLotteryDraw = () => {
     
      state.dialogVisible = true
    }

    // 点击抽奖按钮会触发star回调
    const startCallback = () => {
     
      console.log('"开始抽奖"----', '开始抽奖')
      // 调用抽奖组件的play方法开始游戏
      // console.log('myLucky.value----', myLuckyRef.value) // 【ref问题】
      // myLuckyRef.value?.play() // 【ref问题】

      if (instance) {
     
        instance.refs?.myLuckyRef?.play() // 【ref解决】
      }
      // this.$refs.myLucky.play()  // 【ref】vue2写法


      // 模拟调用接口异步抽奖
      setTimeout(() => {
     
        // 假设index(谢谢参与90%,恭喜中奖10%)
        const index = `${ Math.random()}`.slice(2, 3) * 1
        state.lotteryDrawIndex = index === 1 ? 1 : 2
        // 调用stop停止旋转并传递中奖索引
        // this.$refs.myLuckyRef.stop(index)   // 【ref】vue2写法
        // myLuckyRef.value?.stop(index) // 【ref问题】
        if (instance) {
     
          instance.refs?.myLuckyRef?.stop(state.lotteryDrawIndex) // 【ref解决】
        }
      }, 3000)
    }
    // 抽奖结束会触发end回调
    const endCallback = (prize) => {
     
      console.log('"结束抽奖"----', '结束抽奖')
      console.log(prize)
      state.isShowlotteryDraw = false
    }

    return {
     
      ...toRefs(state),
      correct,
      total,
      examType,
      toLotteryDraw,
      startCallback,
      endCallback
    }
  }
}
</script>

<style scoped lang="less">
.exam-result {
     
  .info {
     
    margin: 0 0 5px;
    padding: 10px;
    background-color: white;

    .progress {
     
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 5px;
      position: relative;

      .nut-circleprogress {
     
        width: 145px !important;
        height: 145px !important;
        position: relative;

        .progressDiv {
     
          display: flex;
          flex-direction: column;
          align-items: center;

          .accuracy {
     
            color: #00000080;
            background-color: #ffeded;
            padding: 2px 8px;
            font-size: 13px;
            border-radius: 5px;
          }
        }

      }

      .circle {
     
        position: absolute;
        height: 145px;
        width: 145px;
        background-color: #ffeded;
        border-radius: 50%;
        top: 5px;
        left: 50%;
        transform: translate(-50%);

        .circle1 {
     
          position: absolute;
          height: 115px;
          width: 115px;
          background-color: #ffffff;
          border-radius: 50%;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
        }
      }
    }

    .count {
     
      background-color: #fffbf3;
      margin-top: 10px;
      padding-top: 5px;
      color: #797e79;
      font-size: 14px;
      display: flex;
      justify-content: space-around;

      .centerDiv {
     
        display: flex;
        align-items: baseline;
        justify-content: center;

        .number {
     
          margin-right: 5px;
          font-size: 20px;
          color: #FAAD14;
        }

        .text {
     
          font-size: 12px;
        }
      }
    }
  }

  .content {
     
    margin-bottom: 10px;
    background: white;
    border-bottom: 1px solid #dcdcdc;

    .result-table {
     
      display: flex;
      font-size: 16px;
      font-weight: bolder;
      color: #000;

      .item {
     
        display: flex;
        align-items: baseline;
        border-top: 0.5px solid #dcdcdc;
        flex: 1;
        font-size: 16px;
        padding: 10px 10px 10px 15px;
        color: #7f7f7f;
        font-weight: normal;

        &:nth-child(2n+1) {
     
          border-right: 0.5px solid #dcdcdc;
        }

        .title {
     
          margin-right: 5px;
          font-size: 14px;
        }

        .unit {
     
          font-size: 12px;
          margin-left: 5px;
        }

        .time,
        .total {
     
          color: black;
          font-size: 16px;
        }

        .correct {
     
          color: #04be01;
          font-size: 18px;
        }

        .error {
     
          color: red;
          font-size: 18px;
        }
      }
    }
  }
  
  // 弹框样式
  ::v-deep .popup-center.round {
     
    width: 90%;
    .nut-dialog {
     
      width: 100%;
      padding: 20px 5px;
      .nut-dialog__content {
     
        max-height: unset;
        .nut-icon-close {
     
          position: absolute;
          top: 15px;
          right: 15px;
        }
        // 转盘结束展示结果
        .result {
     
          height: 80px;
          line-height: 80px;
          font-size: 20px;
          font-weight: bold;
          color: var(--color);
        }
        // 转盘
        .myLucky {
     
          display: inline-block;
        }
      }
    }
  }
  // 抽奖弹框按钮
  .lottery_draw_btn {
     
    height: 25PX;
    line-height: 25PX;
    padding: 0 10px;
    cursor: pointer;
    font-size: 16px;
    color: red;
  }
}
</style>

页面效果

在这里插入图片描述
在这里插入图片描述

lucky-canvas 插件官方文档

lucky-canvas 插件官网
lucky-canvas 插件官网文档

可参考文档

相关推荐

  1. 前端工具】

    2024-01-25 17:48:01       31 阅读
  2. 前端打印(不使用,没有副作用)

    2024-01-25 17:48:01       20 阅读
  3. 【React】前端 uuidjs 的使用 --随机生成id

    2024-01-25 17:48:01       8 阅读
  4. 前端实用-日期处理工具Moment.js

    2024-01-25 17:48:01       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-25 17:48:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-25 17:48:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-25 17:48:01       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-25 17:48:01       20 阅读

热门阅读

  1. 数据结构刷题笔记

    2024-01-25 17:48:01       40 阅读
  2. 杂七杂八的命令

    2024-01-25 17:48:01       36 阅读
  3. 【Ubuntu】systemctl 命令

    2024-01-25 17:48:01       37 阅读
  4. Vue3全局组件和自定义指令

    2024-01-25 17:48:01       40 阅读
  5. Unity串口通信教程:基础知识和实践指南

    2024-01-25 17:48:01       36 阅读
  6. 阿里云对象存储(OSS)服务

    2024-01-25 17:48:01       42 阅读
  7. Vue 中如何模块化使用 Vuex

    2024-01-25 17:48:01       37 阅读
  8. redis漏洞研究

    2024-01-25 17:48:01       38 阅读
  9. 用Python画出漂亮的地图

    2024-01-25 17:48:01       35 阅读