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

news2024/11/20 8:40:14

目录

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

可参考文档

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙入门学习的一些总结

前言 刚开始接触鸿蒙是从2023年开始的&#xff0c;当时公司在调研鸿蒙开发板能否在实际项目中使用。我们当时使用的是OpenHarmony的&#xff0c;基于DAYU/rk3568开发板&#xff0c;最开始系统是3.2的&#xff0c;API最高是API9&#xff0c;DevecoStudio 版本3.1的。 鸿…

国考省考行测:分析推理,形式逻辑,所有有的分析

国考省考行测&#xff1a; 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要的还是申论和行测&#xff0c;所以大家认真准备吧&#xff0c;我讲一起屡屡申论和行测的重要知识点 遇到寒冬&am…

2024三掌柜赠书活动第七期:一本书读懂AIGC:探索AI商业化新时代

目录 前言AI商业化的背景和挑战关于《一本书读懂AIGC&#xff1a;探索AI商业化新时代》编辑推荐内容简介作者简介图书目录书中前言/序言《一本书读懂AIGC&#xff1a;探索AI商业化新时代》全书速览结束语 前言 不用多讲&#xff0c;想必大家也都知道&#xff0c;人工智能在过…

LLM大语言模型(五):用streamlit开发LLM应用

目录 背景准备工作切记streamlit开发LLM demo开一个新页面初始化session先渲染历史消息接收用户输入模拟调用LLM 参考 背景 Streamlit是一个开源Python库&#xff0c;可以轻松创建和共享用于机器学习和数据科学的漂亮的自定义web应用程序&#xff0c;用户可以在几分钟内构建一…

optee编译调试

编译运行 使用的是 ubuntu22.04 需要提前设置好网络 optee运行环境搭建&#xff1a;https://optee.readthedocs.io/en/latest/building/prerequisites.html 安装必要的库 sudo apt install -y \adb \acpica-tools \autoconf \automake \bc \bison \build-essential \ccach…

MySQL十部曲之六:数据操作语句(DML)

文章目录 前言语法约定DELETEINSERTSELECT查询列表SELECT 选项子句FROMWHEREORDER BYGROUP BYHAVINGWINDOWLIMITFOR SELECT ... INTO连接查询CROSS JOIN和INNER JOINON和USINGOUTER JOINNATURE JOIN 子查询标量子查询使用子查询进行比较带有ANY、IN或SOME的子查询带有ALL的子查…

C++ //练习 3.5 编写一段程序从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分隔开来。

C Primer&#xff08;第5版&#xff09; 练习 3.5 练习 3.5 编写一段程序从标准输入中读入多个字符串并将它们连接在一起&#xff0c;输出连接成的大字符串。然后修改上述程序&#xff0c;用空格把输入的多个字符串分隔开来。 环境&#xff1a;Linux Ubuntu&#xff08;云服务…

网络安全B模块(笔记详解)- 越权与下载

1.使用渗透机场景kali中工具扫描服务器场景&#xff0c;将web端口号当作Flag提交&#xff1b; 2.使用渗透机场景windows7访问服务器场景mingling.php,将页面中的Flag提交&#xff1b; 3.使用渗透机场景windows7访问服务器场景mingling.php&#xff0c;分析页面内容&#xff0…

SpringBoot引入 liteflow 规则引擎,yyds!

1前言 在日常的开发过程中&#xff0c;经常会遇到一些串行或者并行的业务流程问题&#xff0c;而业务之间不必存在相关性。 在这样的场景下&#xff0c;使用策略和模板模式的结合可以很好的解决这个问题&#xff0c;但是使用编码的方式会使得文件太多,在业务的部分环节可以这…

Apache Shiro <= 1.2.4反序列化漏洞攻击 CVE-2016-4437 已亲自复现

Apache Shiro < 1.2.4反序列化漏洞攻击 CVE-2016-4437 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议总结 漏洞名称 漏洞描述 在 1.2.5 之前的 Apache Shiro 中&#xff0c;当未为“记住我”功能配置密钥时&#xff0c;远程攻击者可以通过未指定…

去创造,不要消费

最近&#xff0c;想必许多人的社交圈&#xff0c;都被B站的五四视频《后浪》刷屏了。 如果你没看过&#xff0c;也没关系&#xff0c;因为我并不打算讲它。 &#xff08;熟悉我的老读者会知道&#xff0c;我文章最开头的东西一般只是个引子&#xff0c;并不重要&#xff09; 言…

Peter算法小课堂—二叉堆(优先队列)

课前小视频&#xff1a;(7 封私信 / 62 条消息) 看动画&#xff0c;学算法&#xff0c;C实现建立二叉堆&#xff0c;优先队列和堆排序的基础 - 知乎 (zhihu.com) 二叉堆&#xff08;优先队列&#xff09; 大家想想&#xff0c;什么数据结构能做到插入&#xff08;删除&#x…

mysql8安装基础操作(一)

一、下载mysql8.0 1.查看系统glibc版本 这里可以看到glibc版本为2.17&#xff0c;所以下载mysql8.0的版本时候尽量和glibc版本对应 [rootnode2 ~]# rpm -qa |grep -w glibc glibc-2.17-222.el7.x86_64 glibc-devel-2.17-222.el7.x86_64 glibc-common-2.17-222.el7.x86_64 gl…

C++爱好者的科目四易错点总结

科目四易错点总结 在科目四考试中&#xff0c;一部分内容是可以通过刷题快速掌握的&#xff0c;一部分内容缺因易混淆而降低我们的准确率&#xff0c;本文主要对后者进行总结&#xff0c;期待大家补充与指正。 注&#xff1a; 本文不是全部的知识点总结处 本文不是权威机构 本文…

Linux:利用匿名管道构建进程池

文章目录 进程池实现进程池创建信道和进程发送任务释放资源 进程池代码总结 本篇的主题是借助前面所学的基础管道实现一个进程池&#xff0c;那么在实现进程池前先了解进程池是什么&#xff0c;进程池有什么意义&#xff0c;进而对于进程池有一个基本的把握 进程池 给定一个进…

sql注入中的过滤问题

直接上题目&#xff1a; 当我输入?id1 or 11#时提示错误&#xff0c;从这里可以看出对面过滤了点什么导致我的语句出错了&#xff0c;因为我这个语句是用真的&#xff0c;不管放在哪都是会一定正确的&#xff0c;但是这里提示错误的。 然后我第一想法就是空格&#xff0c;我在…

代码增强LLM

大模型时代的语言模型&#xff08;LLM&#xff09;不仅在尺寸上变得更大了&#xff0c;而且训练数据也同时包含了自然语言和形式语言&#xff08;代码&#xff09;。作为人类和计算机之间的媒介&#xff0c;代码可以将高级目标转换为可执行的中间步骤&#xff0c;具有语法标准、…

VCRUNTIME140_1.dll丢失是怎么回事,需要如何修复

vcruntime140_1.dll 是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它是 Microsoft Visual C 2015 Redistributable 组件的一部分。这个文件包含了微软的 C 标准库的运行时组件&#xff0c;特别是与并行编程相关的部分。当开发者使用 Visual C 2015 及以上版本编…

HCIP-三层架构实验

实验拓扑 实验需求 实验思路 配置IP地址 链路聚合 vlan配置 配置生产树 实验步骤 配置IP地址 以R1为例 <Huawei>sys [Huawei]sys r1 [r1]int g0/0/02 [r1-GigabitEthernet0/0/2]ip address 12.1.1.1 24 Jan 28 2024 17:09:03-08:00 r1 %%01IFNET/4/LINK_STATE(l…

BGP:02 BGP认证

这是实验拓扑&#xff0c;物理接口IP地址来建立BGP邻居关系。 认证是指路由器对路由信息来源的可靠性及路由信息本身的完整性进行检测的机制。 下面是基本和BGP配置&#xff1a; R1: sys sysname R1 int loop 0 ip add 1.1.1.1 24 int g0/0/0 ip add 192.168.12.1 24 qbgp 1…