Use vue3 + Ts + Vite + ElementPlus to implement a lottery program

1. Description

  1. This is a lottery program implemented through vue3 + Ts + Vite + ElementPlus.
  2. project link

2. Overall structure and function description

  1. 左侧设置了奖品说明,每个奖项配有文字和图片简介. A total of four awards are set up, namely 1 special prize, 2 first prizes, 5 second prizes, and 10 third prizes. (Currently, the number of places for each award is set to a fixed value, and will be considered to be set to an adjustable value later for users to set flexibly.)
  2. 中间部分设置四个奖项的切换按钮,和“开始”抽奖的按钮。Click "Start" and the names of the people participating in the lottery will randomly scroll on the screen, and clicking again will pop up the names of the winners, and the names will be stored at the same time. When the quota for each award is full, the "Start" button will be grayed out. At this time, clicking the lottery will pop up "This award has been drawn"
  3. 右侧设置了三个按钮,分别是:参与人员、抽奖记录、重新抽奖。
    • Participants: Provide download templates, clear data, and users can customize and import data.
    • Lottery draw record: Shows the list of winners, departments, awards.
    • Re-draw: Reset the previous lottery record, it can be reset all the way up.
  4. 说明:目前的设置是每个人只能有一次中奖机会,即中奖后不能在中奖另一种奖项。

3. Data storage

无后台,纯前端实现Moreover, it is necessary to refresh and close the browser without losing data. Using localStorage, the data stored in localStorage is persistent and will not change due to refreshing or closing the browser (unless it is manually and deliberately cleared).

4. Main function description

  1. Award switch:
 <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. Name Roll and Pause:
// 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. Check whether the current award has been drawn, initialize, switch the award type, click the lottery, and check when the lottery is over
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. Re-draw: Reset the result of the last lottery, you can reset it all the way forward, after the reset, the person who just won the lottery should continue to be placed in the list of names to be drawn.
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. Download the template: You can prepare a template file in advance, I put it under assets here
import {
    
     saveAs } from 'file-saver'
const downTemplate = () => {
    
    
  const fileName = '参与抽奖人员模板.xlsx';  // 模板文件名
  const fileUrl = './src/assets/template/'  // 存放模板文件的路径(相对于index.html)
  saveAs(fileUrl + fileName, fileName)
}
  1. Import Data
<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. Clear data: Clear the data of participants and lottery records, these two are independent and do not affect each other
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('取消清空!')
  })
}

5. Some example diagrams

insert image description here
insert image description here
insert image description here
insert image description here

6. Note

The above code only represents part of the code of the current function, if there are subsequent function additions or changes. The above code may be changed accordingly, and if there is a change, it will be changed synchronously.

Guess you like

Origin blog.csdn.net/du_aitiantian/article/details/131766282