字节跳动前端青训营大作业
使用vite + vue3 来初始化项目
为什么选择vite呢
- 快速启动,
Vite会在本地启动一个开发服务器,来管理开发环境的资源请求 - 无需打包,热更新更快
- 原生ES module 需要什么就引入什么
$ npm init vite-app <project-name>
$ cd <project-name> //进入项目目录
$ npm install //安装项目所需依赖
$ npm run dev //启动项目
生成的项目目录
首先我们进入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内的数字)代表百分之多少中奖概率