使用vue3 + Ts + Vite + ElementPlus实现一个抽奖程序

news2024/11/29 4:27:13

一. 说明

  1. 这是一个通过vue3 + Ts + Vite + ElementPlus实现的一个抽奖程序。
  2. 项目链接

二. 整体架构与功能描述

  1. 左侧设置了奖品说明,每个奖项配有文字和图片简介。总共设置了四个奖项,分别是特等奖1名,一等奖2名,二等奖5名,三等奖10名。(目前每个奖项的名额设置成固定值了,后面会考虑设置成可以调节的值,以供使用者灵活设置。)
  2. 中间部分设置四个奖项的切换按钮,和“开始”抽奖的按钮。点击“开始”参与抽奖的人员名字会随机循环滚动在屏幕上,再次点击会弹出中奖人员名字,同时会将该名字存储起来。每个奖项当名额抽满的时候“开始”按钮会置灰,这时点击抽奖会弹出“该奖项已经抽完”
  3. 右侧设置了三个按钮,分别是:参与人员、抽奖记录、重新抽奖。
    • 参与人员:提供下载模板,清除数据,用户可以自定义导入数据。
    • 抽奖记录:展示了中奖人员的名单,部门,奖项。
    • 重新抽奖:重置上一次的抽奖记录,可一直向上重置。
  4. 说明:目前的设置是每个人只能有一次中奖机会,即中奖后不能在中奖另一种奖项。

三. 数据保存

无后台,纯前端实现而且需要刷新关闭浏览器数据不丢失,使用localStorage,localStorage存入的数据具有持久性,不会因为刷新或关闭浏览器而变化(除非手动刻意的清除)。

四. 主要功能描述

  1. 奖项切换:
 <el-radio-group v-model="prizeValue" class="radioGroup" @change="changePrizeType">
      <el-radio-button v-for="item in prizeType" :label="item" :key=item />
 </el-radio-group>

const prizeType = ['特等奖','一等奖','二等奖','三等奖']
const prizeValue = ref<string>('特等奖')

// 改变抽奖类型
const changePrizeType = (type: string) => {
  prizeValue.value = type
  // 将按钮文字重置为开始
  showName.value = '开始'
  if(haveRemeber.value){
    // 校验当前奖项是否已经被抽完,第一次抽奖时候不校验
    const luckNameList = JSON.parse(localStorage.getItem('luckNameList')!)
    if(luckNameList && luckNameList.length){
      checkoutDraw(luckNameList)
    }
  }
}
  1. 姓名滚动与暂停:
// 1. 洗牌算法:用于姓名每次循环前获得一个随机的姓名数组。主要目的是确保抽奖的公平性即每个人中奖的概率都相同
const shuffle = (arr: any) => {
  const arrNew = [];  // 打乱后的数组
  const len=arr.length 
  for (let i=len;i>0;i--){
    // 生成一个在0-len之间的随机数
    const rand=Math.floor(Math.random()*i)
    // 从原数组中拿出这个随机下标对应的数放入新数组当中
    arrNew.push(arr[rand]);
    // 从原数组当中删除拿出的这个值
    arr.splice(rand,1)
  }
  return arrNew;
}

// 2. 循环姓名列表,每次到最后一个姓名结束时,重新在循环一遍,如此往复。
const forNameList = (list: any) => {
  list = shuffle(list);
  for(let i=0; i<list.length; i++){
    setTimeout(() => {
      if(!isStop.value){
        showName.value = list[i].name;
        if(i == list.length - 1){
          // 当数组循环结束后,没有停止就在继续循环
          // 获取所有的姓名列表
          const allNameList = JSON.parse(localStorage.getItem('nameList')!)
          // 获取中奖人员姓名列表
          const luckNameList = JSON.parse(localStorage.getItem('luckNameList')!)
          if(luckNameList){
            // 将中奖人员从下一次抽奖中过滤掉,确保同一奖项每个人只能有一次中奖机会
            const newNameList = allNameList.filter((itemA: any) => luckNameList.every((itemB: any) => itemB.name !== itemA.name))
            // 将新的姓名列表重新赋值给 useNameList
            useNameList.value = newNameList
          }else{
            useNameList.value = allNameList
          }
          forNameList(useNameList.value)
        }
      }
    },50 * i);
  }
}

// 3. 开始抽奖与暂停
const drawStart = () => {
  // isStop.value ? startDraw() : stopDraw()
  const luckNameList = JSON.parse(localStorage.getItem('luckNameList')!)
  if(haveRemeber.value){  // 如果没有参与人员不进行后续操作直接弹出导入人员提示
    if(luckNameList && luckNameList.length){
      // 校验当前奖项是否已经被抽完
      const flag = checkoutDraw(luckNameList)
      // 根据 startStatus 的状态决定当前奖项能否再抽
      if(flag){
        if(isStop.value){
          startDraw()
        }else{
          stopDraw()
        }
      }else{  
        // 不可以抽奖,说明该奖项名额已经抽完了
        ElMessage.warning(`${prizeValue.value}` + '已经抽完了!')
      }  
    }else{
      //此时没有中奖人员,任何奖项下都可以抽奖
      if(isStop.value){
        startDraw()
      }else{
        stopDraw()
      }
    }
  }else{
    dialogVisible.value = true
  }
}

// 开始
const startDraw = () => {
  // 如果没有导入抽奖人员数据则提示 “请先导入抽奖人员数据!”
  if(!haveRemeber.value){
    dialogVisible.value = true
  }else{
    // 如果有数据开始循环滚动姓名,再次点击则停止滚动并弹出中奖人员
    isStop.value = false  // 开始循环  
    // 获取所有的姓名列表
    const allNameList = JSON.parse(localStorage.getItem('nameList')!)
    // 获取中奖人员
    const luckNameList = JSON.parse(localStorage.getItem('luckNameList')!)
    // 如果清除了中奖人员
    if(!luckNameList || luckNameList && !luckNameList.length){
      useNameList.value = allNameList
    }
    forNameList(useNameList.value)  // 循环姓名数组
  }     
}

// 暂停
const stopDraw = () => {
  isStop.value = true
  dialogVisible.value = true
  // 获取中奖人员数据弹窗显示,并存储起来
  const tableData = JSON.parse(localStorage.getItem('tableData')!)  // 所有数据
  let tableDrawData: any = JSON.parse(localStorage.getItem('luckNameList')!) || []
  tableData.forEach((item: any) => {
    if(item.name == showName.value){
      tableDrawData.push({
        name: item.name,
        sex: item.sex,
        dept: item.dept,
        prize: prizeValue.value
      })
    }
  })
  localStorage.setItem('luckNameList',JSON.stringify(tableDrawData))

  // 获取所有的姓名列表
  const allNameList = JSON.parse(localStorage.getItem('nameList')!)

  // 将中奖人员从下一次抽奖中过滤掉,确保每个人只能有一次中奖机会,也就是从 allNameList 中过滤掉 tableDrawData中的元素
  const newNameList = allNameList.filter((itemA: any) => tableDrawData.every((itemB: any) => itemB.name !== itemA.name))

  // 将新的姓名列表重新赋值给 useNameList
  useNameList.value = newNameList

  // 校验当前奖项是否已经被抽完
  checkoutDraw(tableDrawData)
}
  1. 检验当前奖项是否已经抽完,初始化,切换奖项类型,点击抽奖,抽奖结束的时候都需要校验
const checkoutDraw = (list: luckNameListType[]) => {
  // 此时表示有中奖人员,需要根据条件来确认是否可以在继续抽奖
  // 整理每一个奖项出现的次数
  let prizeList = list.map((item: luckNameListType) => item.prize)
  const prizObj =  prizeList.reduce((preValue: any, curValue: string)=>{
    preValue[curValue] = (preValue[curValue] + 1) || 1
    return preValue
  },{})
  console.log(prizObj,'prizObj==');

  // 如果此时在抽特等奖,只能有 1 个名额
  if(prizeValue.value === '特等奖'){
    if(prizObj['特等奖'] == 1) {  // 此时特等奖不能在抽了
      startStatus.value = false 
    }else{
      startStatus.value = true 
    }
  }else if(prizeValue.value === '一等奖'){  // 如果此时在抽一等奖,只能有 2 个名额
    if(prizObj['一等奖'] == 2 ){  // 此时一等奖不能在抽了
      startStatus.value = false 
      } else{
      startStatus.value = true 
    }
  }else if(prizeValue.value === '二等奖'){  // 如果此时在抽二等奖,只能有 5 个名额
    if(prizObj['二等奖'] == 5){  // 此时二等奖不能在抽了
      startStatus.value = false 
      } else{
      startStatus.value = true 
    }
  }else if(prizeValue.value === '三等奖'){  // 如果此时在抽三等奖,只能有 10 个名额
    if(prizObj['三等奖'] == 10){  // 此时三等奖不能在抽了
      startStatus.value = false 
      } else{
      startStatus.value = true 
    }
  }
  return startStatus.value
}
  1. 重新抽奖:重置上一次的抽奖结果,可以一直向前重置,重置后刚才中奖的人应该继续放入待抽奖姓名列表中。
const drawAgain = () => {
  // 重新抽奖指的是清除最近一次抽奖记录,可以一直重置,直到抽奖记录为空。
  let tableDrawData = JSON.parse(localStorage.getItem('luckNameList')!)
  if(tableDrawData && tableDrawData.length){
    ElMessageBox.confirm(
      '确定要重置上一次的抽奖操作吗?',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }
    ).then(() => {
      const popValue = tableDrawData.pop()
      localStorage.setItem('luckNameList',JSON.stringify(tableDrawData))
      // 重置后刚才中奖的人应该继续放入待抽奖姓名列表中
      useNameList.value.push({
        name: popValue.name
      })
      ElMessage.success('重置成功!')
    }).catch(() => {
      ElMessage.info('取消重置!')
    })
  }else{
    ElMessage.warning('请先完成一次抽奖!')
  }
}
  1. 下载模板:可提前准备一个模板文件,我这里是放在assets下面的
import { saveAs } from 'file-saver'
const downTemplate = () => {
  const fileName = '参与抽奖人员模板.xlsx';  // 模板文件名
  const fileUrl = './src/assets/template/'  // 存放模板文件的路径(相对于index.html)
  saveAs(fileUrl + fileName, fileName)
}
  1. 导入数据
<el-button v-show="drawTitle === '参与人员'" class="importData">
        导入数据
        <input class="inputFile" type="file" accept=".xls,.xlsx" @change="importData" />
</el-button>

import * as XLSX from 'xlsx'

const importData = (e: any) => {
  const file = e.target.files[0]   // 获取file对象
  const fileReader = new FileReader()  // 创建文件读取器
  fileReader.onload = (event) => {
    const result = event.target!.result  // 获取读取的结果
    const workBook = XLSX.read(result, {type: 'binary'})  // xlsx读取返回的结果
    const importData = XLSX.utils.sheet_to_json(
      workBook.Sheets[workBook.SheetNames[0]]
    )
    importData.forEach((item: any) => {
      tableDataTemp.value.push({
        name: item.姓名,
        sex: item.性别,
        dept: item.部门
      })
    });
    // 只存放姓名
    importData.forEach((item: any) => {
      tableNameData.value.push({
        name: item.姓名
      })
    });
    // 将导入的表格数据存到localStorage中
    localStorage.setItem('tableData', JSON.stringify(tableDataTemp.value))

    // 将姓名数据存在localStorage中
    localStorage.setItem('nameList', JSON.stringify(tableNameData.value))
    emits('nameList', tableNameData.value)
    // 给当前表格数据赋值
    tableData.value = tableDataTemp.value
    
    // 将导入的表格数据中的姓名存到pinia中
    appStore.getNameList(tableNameData.value)
  }
  fileReader.readAsBinaryString(file);
  // ((document.getElementsByClassName("inputFile")[0]).value = '')
}
  1. 清除数据:清除参与人员和抽奖记录的数据这两个是独立互不影响的
const clearData = () => {
  ElMessageBox.confirm(
    '确定要清空所有数据吗?',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }
  ).then(() => {
    let empty: any = []
    tableData.value = []
    if(props.drawTitle === '参与人员'){
      localStorage.setItem('tableData', JSON.stringify(empty))
      localStorage.setItem('nameList', JSON.stringify(empty))
      emits('clearData','参与人员')
    }else{
      localStorage.setItem('luckNameList', JSON.stringify(empty))
      emits('clearData','抽奖记录')
    }
    ElMessage.success('清空成功!')
  }).catch(() => {
    ElMessage.info('取消清空!')
  })
}

五. 部分示例图

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

六. 注意

上述代码仅代表当前所做功能的部分代码,如果后续有功能添加或者改动。上述代码可能会有相应改变,如有改变会同步更改。

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

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

相关文章

平安养老险党委书记、董事长甘为民:聚焦养老主业 助推养老保障事业高质量发展

每经记者 涂颖浩 每经编辑 马子卿 随着人口老龄化趋势加剧&#xff0c;中国养老金融市场呈现出巨大的潜力&#xff0c;逐步迈入养老新时代。近日&#xff0c;平安养老险党委书记、董事长甘为民在接受《每日经济新闻》记者专访时表示&#xff0c;过往单纯的养老发展模式难以满足…

Jmeter性能测试 —— 性能测试的概念

性能测试的概念 性能测试是指通过特定方式&#xff0c;对被测系统按照一定策略施加压力&#xff0c;获取系统 响应时间、TPS&#xff08;Transaction Per Second&#xff09;、吞吐量、资源利用率等性能指标&#xff0c;以期保证生产系统的性能能够满足用户需求的过程。 性能…

浅析编译与链接

生成可执行文件的四个过程 当编写和构建计算机程序时&#xff0c;预处理、编译、汇编和链接是将源代码转化为可执行程序的关键过程。以下是对每个阶段的详细解释&#xff1a; 1. 预处理&#xff08;Preprocessing&#xff09;&#xff1a;将.c/.cpp文件中的头文件展开、宏展开…

【PostgreSQL内核学习(一)—— Ubuntu源码安装PostgreSQL】

Ubuntu源码安装PostgreSQL 1. PostgreSQL官网下载压缩包2. 解压&安装2.1 解压文件2.2 安装依赖2.3 执行安装2.4 执行安装2.5 添加路径到文件 3. 初始化数据库与使用3.1 初始化数据库3.2 启动数据库服务3.3 启动数据库 1. PostgreSQL官网下载压缩包 下载地址&#xff1a;ht…

【黑马头条之freemarker入门】

本笔记内容为黑马头条项目的freemarker部分 目录 一、freemarker 介绍 二、环境搭建&&快速入门 1、创建测试工程 2、配置文件 3、创建模型类 4、创建模板 5、创建controller 6、创建启动类 7、测试 三、freemarker基础 1、基础语法种类 2、集合指令&#…

【iOS】—— 面向对象,Runtime,ARC等问题总结

对于暑假学习大多数是对之前学习的一个复习&#xff0c;在这里只做对之前学习欠缺知识的补充以及这些知识点涉及的一些问题&#xff0c;从问题入手学习。 文章目录 面向对象1.一个NSObject对象占多少内存&#xff1f;2.对象的isa指针指向哪里&#xff1f;3.OC的类信息存放在哪…

PLSQL编程

1.概念和目的 1.1. 什么是PL/SQL? PL/SQL&#xff08;Procedure Language/SQL&#xff09; 是Oracle对sql语言的过程化扩展 (类似于Basic)&#xff1b; 指在SQL命令语言中增加了过程处理语句&#xff08;如分支、循环等&#xff09;&#xff0c;使SQL语言具有过程处理能力。…

Spring @Autowired 注解原理

Spring Autowired 注解原理 1.Autowired 使用 ComponentScan("org.example.bean") public class AnnoContextDemo {Autowiredprivate User user;public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplic…

Ultipa嬴图数据库 | 深圳国际金融科技大赛圆满落幕

2023年7月13日&#xff0c;由深圳市地方金融监督管理局、深圳市福田区人民政府、深圳市南山区人民政府指导&#xff0c;招商局金融科技有限公司主办的2022深圳国际金融科技大赛总决赛在福田区圆满落幕。经过从初赛到决赛&#xff0c;共计103个项目的激烈角逐&#xff0c;Ultipa…

Unity视角拉近时物体缺失的问题处理

在Unity的开发过程中&#xff0c;我们可能会遇到以下情况&#xff1a; 就是在场景的不断编辑中&#xff0c;突然又一次打开场景&#xff0c;再拉近或拉远场景视角时&#xff0c;会出现场景中的对象会显示不全的问题。 出现了这样的情况会让场景的预览很不友好。 出现这个问题的…

【006】面向 6G 的深度图像语义通信模型

摘要 目前的语义通信模型在处理图像数据方面仍有可改善的部分&#xff0c;包括有效的图像语义编解码、高效的语义模型训练和精准的图像语义评估。为此&#xff0c;提出了一种深度图像语义通信&#xff08;DeepISC&#xff09;模型。首先采用基于 vision transformer 的自编码器…

数字IC后端设计实现中的Post-mask ECO应该怎么做?

在数字IC后端设计实现中&#xff0c;我们经常会涉及到芯片需要做Function ECO。常见的Function ECO可以分为pre mask ECO和post mask ECO两种。因此&#xff0c;作为一个数字IC后端工程师&#xff0c;必须熟练掌握这两种Function ECO的实现流程及其实现技巧。 两者的区别在于&…

栈和队列【数据结构】

1、栈 &#xff08;1&#xff09;Stack.h #pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h>typedef int STDataType;typedef struct Stack {STDataType* a;int top;int capacity; }ST;void STInit(ST*…

[JVM] 5. 运行时数据区(2)-- 程序计数器(Program Counter Register)

一、概述 JVM中的程序计数器&#xff08;Program Counter Register&#xff09;是对物理PC寄存器的一种抽象模拟。它是一块很小的内存空间&#xff0c;几乎可以忽略不记。也是运行速度最快的存储区域。在 JVM 规范中&#xff0c;每个线程都有它自己的程序计数器&#xff0c;是…

.nvmrc无效

背景 既然你已经使用了nvm那么他的功能我就不介绍了。但是使用场景我与开发小伙伴探讨了一下发现很多问题。你的nvm使用方法真的正确吗&#xff1f; 问题&#xff1a;假设现在有10个项目对应10个不同的node版本&#xff0c;你应该怎么来管理呢&#xff1f; 同学1&#xff1a; …

LT8619C是一款高性能HDMI转TTL/BT656/BT601/BT1120加2PORT LVDS,支持高达4K30HZ的分辨率。

LT8619C • 概述&#xff1a; Lontium的LT8619C是一款高性能的HDMI/双模式DP接收器芯片&#xff0c;符合HDMI 1.4规范&#xff0c;支持HDCP1.4解码&#xff0c;对HDMI的支持分辨率高达4Kx2K30Hz。TTL输出可支持RGB、BT656、BT1120&#xff0c;支持最多24位RGB或BT656/BT1120输…

动手学深度学习——多层感知机(原理解释+代码详解)

目录 一、多层感知机1. 隐藏层1.1 线性模型可能会出错1.2 在网络中加入隐藏层1.3 从线性到非线性1.4 通用近似定理 2. 激活函数2.1 ReLU函数2.2 sigmoid函数2.3 tanh函数 3. 小结 二、多层感知机的从零开始实现2.1 初始化模型参数2.2 激活函数2.3 模型2.4 损失函数2.5 训练 三、…

【数据结构刷题】消失的数字和轮转数组

目录 一.消失的数字 方法一:异或全部元素 方法二:利用等差数列求和-该数组全部元素之和。 二.轮转数组 题型1:实现一个函数&#xff0c;可以左旋字符串中的k个字符。 写法1:暴力求解 根据该题写出右旋转 写法2&#xff1a;三步旋转法(左逆序&#xff0c;右逆序&#xff0c;整体…

2023年7月字节前端青训营入营题目记录(大题)

前言&#xff1a; 不一定是完整的题目内容&#xff0c;但意思差不多是一个意思 1.实现一个url解析成对象的函数&#xff1a; function ParseParams(url: string): Record<string, any> {const paramsstr url.split("?")[1];const paramsArr paramsstr.spl…

IDEA中把导航栏的字体放大

IDEA中如何把导航栏的字体放大&#xff1f; 选择File--Settings--Appearance&#xff0c;找到下面的size,选择自己想要的字体大小后点击 OK 即可。 字体大了果然看的更舒服了~写代码都有动力了哈哈哈哈 服了~