VIte + Vue3 + ElementPlus仿写掘金抽奖

1,120 阅读3分钟

字节跳动前端青训营大作业

Github项目地址

使用vite + vue3 来初始化项目

为什么选择vite呢

  1. 快速启动,Vite 会在本地启动一个开发服务器,来管理开发环境的资源请求
  2. 无需打包,热更新更快
  3. 原生ES module 需要什么就引入什么
$ npm init vite-app <project-name> 
$ cd <project-name> //进入项目目录 
$ npm install //安装项目所需依赖 
$ npm run dev //启动项目

生成的项目目录

image.png

首先我们进入src的components目录下创建组件

Home组件

Home组件主要负责页面的框架与初始展示

LuckDraw组件

LuckDraw组件主要展示抽奖与中将列表组件

具体代码如下:

<template>
  <div>
    <p class="title">掘金幸运抽奖</p>
    <div class="box">
      <div class="left_box">
        <div class="left_header">
          <div class="count">
            当前矿石数:<span>{{ money }}</span>
          </div>
          <div :class="{ sign: true, signed: isSign }" @click="signHandle">
            {{ isSign ? "已签到" : "签到" }}
          </div>
        </div>
        <div class="panel">
          <Lotterypanel
            :money="money"
            @updateMoney="updateMoney"
            @getPrizeHandle="getPrizeHandle"
          ></Lotterypanel>
        </div>
      </div>
      <div class="right_box">
        <Prizes :list="wonPrizes"></Prizes>
      </div>
    </div>
  </div>
</template>

<script>
import { reactive, ref } from "vue";
import Lotterypanel from "./../Lotterypanel/Lotterypanel.vue";
import Prizes from "./../Prizes/Prizes.vue";
export default {
  setup() {
    const money = ref(400);
    const isSign = ref(false);
    // 触发签到事件
    const signHandle = function () {
      if (isSign.value) {
        return;
      }
      money.value += 200;
      isSign.value = true;
    };

    const updateMoney = function () {
      money.value -= 200;
    };

    // 抽中的奖品
    const wonPrizes = reactive([]);
    const getPrizeHandle = function (prize) {
      const [date] = new Date().toLocaleString().split(" ");
      wonPrizes.push({
        ...prize,
        date,
      });
    };
    return {
      money,
      isSign,
      signHandle,
      updateMoney,
      wonPrizes,
      getPrizeHandle,
    };
  },
  components: {
    Lotterypanel,
    Prizes,
  },
};
</script>

<style lang="less" scoped>
 .......省略
</style>

首先,分别定义money与isSign来百事初始矿石数量与是否签到,初始矿石数量400,默认没有签到;在签到按钮上绑定签到事件,签到后money值增加200且isSign为true。 wonPrizes表示抽中的奖品列表,引入Prizes组件展示奖品列表,将wonPrizes作为props传递给Prizes子组件进行展示

将money传递给Lotterypanel组件(完成具体抽奖逻辑)用于判断剩余矿石数是否可以抽奖,自定义事件updateMoney与getPrizeHandle分别用于获取抽奖后的最新矿石数和获得的奖品

下面来到重头戏,Lotterypanel组件

Lotterypanel组件(抽奖逻辑实现)

先上代码

<template>
  <div class="container">
    <div class="item-box">
      <div
        :class="{
          item,
          btn: item.id === 8 || count === item.id,
        }"
        v-for="item in prizeList"
        :key="item.id"
      >
        <div class="img" v-if="item.id !== 8">
          <img :src="item.img" alt="" />
        </div>
        <div class="text" @click="luckDrawHandle" v-else>抽奖</div>
        <span>{{ item.price ? `${item.price}/次` : item.name }}</span>
      </div>
    </div>
    <!-- <Toast></Toast> -->
    <el-dialog
      title="恭喜您中将了!"
      v-model="prizeDialogVisible"
      width="30%"
      @close="count = -1"
    >
      <div class="own-prize">
        <!-- <div class="img-box">
          <img :src="ownPrize.img" alt="" />
        </div> -->
        <span class="prize-name">{{ ownPrizeName }}</span>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="prizeDialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="prizeDialogVisible = false"
            >确 定</el-button
          >
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script>
import { ref, toRefs } from "vue";
import { prizeList } from "../../utils/prizeList";
// import Toast from "./../common/Toast/Toast.vue";
import { ElDialog, ElButton, ElMessage } from "element-plus";
export default {
  props: {
    money: {
      type: Number,
      required: true,
    },
  },
  setup(props, { emit }) {
    const count = ref(-1);
    const { money: curMoney } = toRefs(props);
    const ownPrizeName = ref(""); // 获得奖品的名字
    const prizeDialogVisible = ref(false);

    // 抽中的奖品
    const getPrize = function (id) {
      const [prize] = prizeList.filter((item) => item.id == id);
      return prize;
    };

    // 中奖后
    const winPrize = function (val) {
      const ownPrize = getPrize(val);
      emit("getPrizeHandle", ownPrize);
      ownPrizeName.value = ownPrize.name;
      prizeDialogVisible.value = !prizeDialogVisible.value;
    };
    // 触发抽奖
    const luckDrawHandle = function () {
      if (curMoney.value < 200) {
        ElMessage({
          showClose: true,
          message: "矿石不足,签到领取200矿石哦~",
          type: "error",
        });
        return;
      }
      emit("updateMoney");
      const random = Math.random() * 100;
      let i = 1;
      let timer;
      function _handle() {
        timer = setTimeout(() => {
          count.value += 1;
          if (count.value > 8) {
            count.value = 0;
          }
          i++;
          if (i > 25 && prizeList[count.value].probability >= random) {
            clearTimeout(timer);
            winPrize(count.value);
          } else {
            _handle();
          }
        }, i * 10);
      }
      _handle();
    };
    return {
      count,
      prizeList,
      luckDrawHandle,
      prizeDialogVisible,
      ownPrizeName,
    };
  },
  components: {
    // Toast,
    ElDialog,
    ElButton,
  },
};
</script>

<style lang="less" scoped>
 .......略
</style>

prizeList表示奖品池,支持自定义,目前写在前端,后期会进行更新到后端数据库中

我们来看最重要的抽奖逻辑

// 触发抽奖
    const luckDrawHandle = function () {
      if (curMoney.value < 200) {
        ElMessage({
          showClose: true,
          message: "矿石不足,签到领取200矿石哦~",
          type: "error",
        });
        return;
      }
      emit("updateMoney");
      const random = Math.random() * 100;
      let i = 1;
      let timer;
      function _handle() {
        timer = setTimeout(() => {
          count.value += 1;
          if (count.value > 8) {
            count.value = 0;
          }
          i++;
          if (i > 25 && prizeList[count.value].probability >= random) {
            clearTimeout(timer);
            winPrize(count.value);
          } else {
            _handle();
          }
        }, i * 10);
      }
      _handle();
    };

当点击抽奖按钮时,首先会检查当前拥有的矿石数,少于200则警告矿石数量不足,无法进行抽奖;当抽奖开始时,会先通知父组件更新最新的矿石数量,然后获取0-99的随机数,因为每个奖品有一个对应的probability属性(0-100的数)代表具有百分之多少的概率会被抽到,然后定义i(用于计数)与timer(用于最后暂停setTimeout)。count表示最终抽到的奖品id,在循环时回进行递增匹配不同的奖品(每次递增都对应着奖品id)

i每次约会进行递增1,每次延迟的时间 i * 100 这样速度便会越来越慢,最后的停止条件为i > 25且目前的奖品的抽中概率大于产生的随机数;这样就实现了随机抽奖且奖品抽中概率可控

停止后,清除定时器并调用抽取成功的方法 winPrize 传入目前的count值,winPrize方法接受count值并获得对应的奖品通过自定义事件传递给父组件,并随后进行弹窗展示

父组件拿到抽中的奖品后传递给奖品展示组件进行展示

奖池

const prizeList = reactive([
    {
        id: 0,
        name: '66矿石',
        img: 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/32ed6a7619934144882d841761b63d3c~tplv-k3u1fbpfcp-no-mark:0:0:0:0.awebp',
        probability: 100
    },
    {
        id: 1,
        name: '随机限量徽章',
        img: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/71c68de6368548bd9bd6c8888542f911~tplv-k3u1fbpfcp-no-mark:0:0:0:0.awebp',
        probability: 20
    },
    {
        id: 2,
        name: '掘金新款T恤',
        img: 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5bf91038a6384fc3927dee294a38006b~tplv-k3u1fbpfcp-no-mark:0:0:0:0.awebp',
        probability: 20
    },
    {
        id: 7,
        name: 'BUG',
        img: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0a4ce25d48b8405cbf5444b6195928d4~tplv-k3u1fbpfcp-no-mark:0:0:0:0.awebp',
        probability: 20
    },
    {
        id: 8,
        name: '抽奖',
        price: 200
    },
    {
        id: 3,
        name: '乐高海洋巨轮',
        img: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aabe49b0d5c741fa8d92ff94cd17cb90~tplv-k3u1fbpfcp-no-mark:0:0:0:0.awebp',
        probability: 20
    },
    {
        id: 6,
        name: '掘金马克杯',
        img: 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ab31c183950541d4a0731c0b8765b173~tplv-k3u1fbpfcp-no-mark:0:0:0:0.awebp',
        probability: 20
    },
    {
        id: 5,
        name: 'YoYo抱枕',
        img: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/33f4d465a6a9462f9b1b19b3104c8f91~tplv-k3u1fbpfcp-no-mark:0:0:0:0.awebp',
        probability: 20
    },
    {
        id: 4,
        name: 'switch',
        img: 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4decbd721b2b48098a1ecf879cfca677~tplv-k3u1fbpfcp-no-mark:0:0:0:0.awebp',
        probability: 20
    }
])

手工设置奖池与中奖概率

直接替换数组中元素即可,需要注意的是 id 不要修改,中将概率为probability属性(0-100内的数字)代表百分之多少中奖概率