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

一. 说明

  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))// 将新的姓名列表重新赋值给 useNameListuseNameList.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 = truedialogVisible.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))// 将新的姓名列表重新赋值给 useNameListuseNameList.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) || 1return 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.hqwc.cn/news/26754.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Eureka的使用手册

一、导入依赖&#xff08;服务端和客户端导入的依赖不一样&#xff09; 服务端&#xff1a; <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependenc…

Camtasia Studio 2023怎么导出mp4格式的视频的详细教程介绍

很多用户刚接触Camtasia Studio 2023&#xff0c;不熟悉如何保存mp4格式的视频。在今天的文章中小编为大家带来了Camtasia Studio 2023保存为mp4格式的视频的详细教程介绍。 Camtasia Studio 2023保存为mp4格式的视频的详细教程 1、 打开Camtasia Studio。 Camtasia Studio- …

[相遇 Bug] - ImportError: numpy.core.multiarray failed to import

背景: 因为最近在看点云模型, 在自己的环境上部署该项目: https://github.com/open-mmlab/OpenPCDet/tree/master 执行命令: 这里执行github项目给的demo.py文件, 命令格式如下: python demo.py --cfg_file cfgs/kitti_models/pointpillar.yaml --ckpt xxx/pointpillar_772…

PLSQL编程

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

做渲染多好的CPU配置才够用?经常看到的核心和线程数到底是什么?

很多设计师想买一台做渲染的电脑时&#xff0c;经常会看到处理器&#xff08;CPU&#xff09;的介绍中提到几核几线程的信息&#xff0c;却不懂到底是什么意思。其实CPU的几核几线程是指CPU的核心数和线程数&#xff0c;它们是衡量CPU性能的两个重要指标。那么做渲染要有多好的…

等保——windows终端和服务器测评

一、本文适用于Windows系统&#xff0c;但有些版本不适用&#xff0c;例如win10、win11等&#xff0c;因为没有密码策略模块 二、针对于win7的测评过程 1、winR打开命令行&#xff0c;输入gpedit.msc&#xff0c;打开本地组策略编辑器&#xff08;win10以上版本没有这个模块&…

QQ号码3个月未登陆真的要回收?

7月17日消息&#xff0c;微信号长期未使用会被回收的消息引起热议。 腾讯微信团队微博发文称&#xff1a;为保障用户的微信账号安全&#xff0c;注册后不活跃&#xff0c;长期未登录&#xff0c;并且没有零钱的微信账号&#xff0c;会被系统注销&#xff0c;无法使用。 不过也有…

海外媒体发稿:链游媒体发稿写作方法及优缺点解析

链游媒体发稿是一种新的媒体发布机制&#xff0c;它可以把信息准确、及时、有效地传播给大量的人&#xff0c;帮助企业实现信息的最大化传播&#xff0c;因此越来越多的公司也开始使用链游媒体发稿服务&#xff0c;本文就介绍链游媒体发稿写作的方法及小技巧。 一、链游媒体发稿…

JVM系统优化实践(19):GC生产环境案例(二)

您好&#xff0c;这里是「码农镖局」CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 接昨天的问题继续来说&#xff0c;在高并发场景中&#xff0c;对象过多容易导致OOM。由于高并发导致Young GC存活对象过多&#xff0c;因此会有太多对象进入老年代&#xf…

2.3Listbox列表部件

2.3Listbox列表部件 创建主窗口 window tk.Tk() window.title(my window) window.geometry(200x200)创建一个label用于显示 var1 tk.StringVar() #创建变量 l tk.Label(window,bgyellow,width4,textvariablevar1) l.pack()创建一个方法用于按钮的点击事件 def print_s…

Python自动化测试基础必备知识点总结

一、自动化测试的概念 性能系统负载能力稳定性过载操作下的系统瓶颈自动化测试&#xff0c;使用程序代替人工&#xff0c;可以提高测试效率性&#xff0c;自动化测试能自动化使用代码模拟大量用户&#xff0c;让用户请求多页和多用户并发请求收集参数&#xff0c;并对系统负载…

FPGA实现UART协议的接收与发送

一、接收模块uart_rx.v UART协议&#xff0c;空闲时&#xff0c;TX和RX数据线都是通过上拉电阻拉高的状态&#xff0c;这样才能在起始位到来时检测到一个下降的边沿。 UART数据格式 uart_rx.v模块输入输出示意图 RX_start。首先&#xff0c;找到起始位的开始时刻RX_start&…