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

目录

  • 需求背景
  • 需求实现
    • 实现过程图片示意
    • 实现代码
  • 页面效果
  • 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 插件官网文档

可参考文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/346518.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSDN社区怎么修改社区名称?上传社区logo等信息?

今天看到有人求助CSDN社区中如何修改社区名称以及上传社区logo等社区信息&#xff0c;对于这个问题&#xff0c;刚好boke112百科也创建有一个WordPress社区&#xff0c;所以知道怎么操作&#xff0c;具体如下&#xff1a; 1、前往CSDN社区并登录 >> 在右侧“我的社区”中…

【Java】SpringMVC参数接收(一)

1、接收单个参数 &#xff08;1&#xff09;直接接收参数 RequestMapping("/hello") RestController public class HelloSpring {RequestMapping("/t2")public String t2(String name){return "name" name;} } 当没有传入参数时&#xff0c;返…

IDEA远程服务器开发

IDEA的远程开发是在本地去操远程服务器上的代码&#xff0c;可以直接将本地代码的编译,构建,调试,运行等工作都放在远程服务器上而本地运行一个客户端远程去操作服务器上的代码,就如同我们平常写代码一样。相比于云桌面成本更低,开发效率更高。 1.首先服务器配置jdk&#xff0…

Vue2基础-Vue组件化编程

文章目录 一、模块与组件1、概念2、分类 二、非单文件组件1、创建组件2、注册组件1&#xff09;局部注册2&#xff09;全局注册 3、注意点1&#xff09;组件名2&#xff09;关于组件标签 三、VueComponent1、概念2、内置关系 三、单文件组件1、格式2、引用1&#xff09;暴露2&a…

多场景四向穿梭车系统|HEGERLS托盘四向车是如何实现自动识别存取搬运拣选功能的?

一般来说&#xff0c;物料包装形式可分为托盘和料箱&#xff0c;然而二者在仓储内部物流的作业方式却全然不同。托盘截面较大&#xff0c;则适用于成品搬运&#xff1b;而料箱相对小一些的&#xff0c;则需以原配件、零配件为主。当然所有的物流形式都离不开托盘&#xff0c;工…

Sqlite真空命令VACUUM

之前在项目中使用了sqlite数据库&#xff0c;当日志变大时&#xff0c;执行CRUD操作就会变慢 后来尝试删除7天前的记录进行优化 delete from XX_CollectData where CreateTime<2024-01-24 发现sqlite文件的大小就没有变化&#xff0c;delete命令只是逻辑删除&#xff0c;…

构建库函数雏形(以GPIO为例)

构建库函数雏形 进行外设结构体定义构建置位和复位函数进行库函数的自定义 step I&#xff1a; \textbf{step I&#xff1a;} step I&#xff1a; 对端口进行输出数据类型枚举 step II&#xff1a; \textbf{step II&#xff1a;} step II&#xff1a;对端口进行结构化描述 step…

76.Go分布式ID总览

文章目录 简介一&#xff1a;UUID二、雪花算法三&#xff1a;Leaf-snowflake四&#xff1a;数据库自增ID五&#xff1a;使用Redis实现分布式ID生成六&#xff1a;使用数据库分段&#xff08;Leaf-segment&#xff09;七 &#xff1a;增强版Leaf-segment八&#xff1a;Tinyid九&…

Linux破解密码

破解root密码&#xff08;Linux 7&#xff09; 1、先重启——e 2、Linux 16这一行 末尾加rd.break&#xff08;不要回车&#xff09;中断加载内核 3、再ctrlx启动&#xff0c;进入救援模式 4、mount -o remount&#xff0c;rw /sysroot/——&#xff08;mount挂载 o——opti…

第四十周:文献阅读+GAN

目录 摘要 Abstract 文献阅读&#xff1a;结合小波变换和主成分分析的长短期记忆神经网络深度学习在城市日需水量预测中的应用 现有问题 创新点 方法论 PCA&#xff08;主要成分分析法&#xff09; DWT&#xff08;离散小波变换&#xff09; DWT-PCA-LSTM模型 研究实…

【sgTree】自定义组件:加载el-tree树节点整棵树数据,实现增删改操作。

特性 可以自定义主键、配置选项支持预定义节点图标&#xff1a;folder文件夹|normal普通样式多个提示文本可以自定义支持动态接口增删改节点可以自定义根节点id可以设置最多允许添加的层级深度支持拖拽排序&#xff0c;排序过程还可以针对拖拽的节点深度进行自定义限制支持隐藏…

运用ETLCloud快速实现数据清洗、转换

一、数据清洗和转换的重要性及传统方式的痛点 1.数据清洗的重要性 数据清洗、转换作为数据ETL流程中的转换步骤&#xff0c;是指在数据收集、处理、存储和使用的整个过程中&#xff0c;对数据进行检查、处理和修复的过程&#xff0c;是数据分析中必不可少的环节&#xff0c;对…

人工智能技术应用:引领未来的智能时代

随着科技的日新月异&#xff0c;人工智能&#xff08;AI&#xff09;技术应用正逐渐成为我们生活中不可或缺的一部分。从智能手机中的语音助手到无人驾驶汽车&#xff0c;人工智能技术正在推动世界进入一个智能时代。本文将重点探讨人工智能技术在生活、医疗、交通等领域的应用…

Redis(秒杀活动、持久化之RDB、AOF)

目录 秒杀活动 一、测压工具jmete的使用 二、java实现秒杀活动 1、myseckillcontroller 2、先启动pos请求添加商品&#xff0c;再启动jmeter进行压测 Redis持久化 一 、Redis持久化之RDB 1.RDB是什么 2. 备份是如何执行的 3.Fork 4. RDB持久化流程 5. dump.rdb文件 6…

03-TiDB-单机上模拟部署生产环境集群

1、安装集群cluster组件 tiup cluster # 已安装的可以更新 # tiup update --self && tiup update cluster 2、修改主机sshd 服务的连接数限制 vim /etc/ssh/sshd_config # MaxSessions 20#重启 sshd 服务&#xff1a; service sshd restart 3、设置集群配置文件top…

一个监控小技巧,巧妙破解超低温冰箱难题!

在当今科技飞速发展的时代&#xff0c;超低温冰箱监控系统以其在各行各业中关键的温度控制和环境监测功能而备受关注。 超低温环境对于存储生物样本、药品和其他温度敏感物品至关重要&#xff0c;而监控系统则提供了实时、精准的环境数据&#xff0c;确保这些物品的质量和安全性…

Ubuntu apt update提示:GPG 缺少公钥解决方法

Ubuntu 运行: sudo apt update #or sudo apt-get update提示&#xff1a;GPG 缺少公钥以及404 Not Found&#xff0c;如下面所示&#xff0c;有mirror.bwbot.org 和ppa.launchpadcontent.net两个源出现问题。 好多网友用后面的方法解决 真正解决&#xff1a;gpg --verify sig:…

bash 5.2中文修订4

Compound Commands 复合命令 复合命令是 shell 编程语言的结构。每个构造都以保留字或控制运算符开始&#xff0c;并以相应的保留字或运算符终止。与复合命令关联的任何重定向&#xff08;请参阅 Redirections &#xff09;都适用于该复合命令中的所有命令&#xff0c;除非显式…

web蓝桥杯真题--14、关于你的欢迎语

介绍 营销号&#xff0c;有时候需要一些特定的欢迎语&#xff0c;但针对特定的用户&#xff0c;我们希望可以个性化一点。本题需要在项目文件中修改代码存在的问题&#xff0c;实现根据模版生成特定用户的欢迎语。 准备 本题已经内置了初始代码&#xff0c;打开实验环境&…

Unity读书系列《Unity3D游戏开发》——拓展编辑器(一)

文章目录 前言一、扩展Project视图1、右键扩展菜单&#xff08;Asset&#xff09;2、监听事件3、拓展布局 二、扩展Hierarchy视图1、拓展菜单&#xff08;GameObject&#xff09;2、拓展布局3、重写菜单 三、扩展Inspector视图1、扩展原生组件2、扩展继承组件 四、扩展Scene视图…