|
@@ -0,0 +1,360 @@
|
|
|
+<template>
|
|
|
+ <page-layer>
|
|
|
+ <template #aside>
|
|
|
+ <div style="width: 300px;height: 100%;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-image: url('/src/assets/lottery/happyAside.jpg')">
|
|
|
+
|
|
|
+ <div style="height: 20%"></div>
|
|
|
+
|
|
|
+ <div style="width: 80%; margin-left: 10%">
|
|
|
+ <el-upload
|
|
|
+ drag
|
|
|
+ action="http://172.16.30.26:8706/lottery/uploadLotteryUsers"
|
|
|
+ :show-file-list="false"
|
|
|
+ :on-success="uploadSuccess"
|
|
|
+ >
|
|
|
+ <el-icon class="el-icon--upload" style="color: white">
|
|
|
+ <upload-filled/>
|
|
|
+ </el-icon>
|
|
|
+ <div style="color: white">
|
|
|
+ 导入抽奖名单
|
|
|
+ </div>
|
|
|
+ </el-upload>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div style="margin: 24px 0 12px 30px; font-size: 18px; color: #c4c4c4;">
|
|
|
+ 抽奖详情设置
|
|
|
+ <span class="reset-pool" @click="initLotteryPool">重置为初始状态</span>
|
|
|
+ </div>
|
|
|
+ <div style="margin-left: 50px">
|
|
|
+ <div v-for="item in lotteryPool" :key="item.code">
|
|
|
+ <div style="display: flex; line-height: 32px; font-size: 16px">
|
|
|
+ <div style="color: white; font-weight: bold">{{ item.name }}:</div>
|
|
|
+ <div style="width: 60px; color: lightgreen; font-weight: bold">{{ item.amount }} 份</div>
|
|
|
+ <div style="width: 40px; text-align: right">
|
|
|
+ <el-button circle icon="Edit" @click="editRule(item)"></el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="width: 300px; text-align: center; margin-top: 30px">
|
|
|
+ <el-button style="width: 200px" size="large" :disabled="duringLottery"
|
|
|
+ type="danger" icon="Star" @click="startLottery">开始抽奖
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+<!-- <div style="color: rgb(42,252,87)"></div>-->
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #main>
|
|
|
+ <div style="position:relative;width: 100%; height: 100%;
|
|
|
+ overflow-y: hidden;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-image: url('/src/assets/lottery/happyNewYear.jpg')">
|
|
|
+
|
|
|
+ <div style="height: 19%;width: 100%;display: flex;align-items: end;
|
|
|
+ justify-content: center;color: white;font-size: 56px;font-weight: bold">
|
|
|
+ 当前奖项:{{ currentLotteryName }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div style="height: 60%; width: 100%; display: flex;align-items: center;
|
|
|
+ justify-content: center;color: white;font-size: 152px;font-weight: bold">
|
|
|
+ <span id="winner-div">{{ currentUserName }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div style="position: absolute; top:0;right:0;bottom: 0">
|
|
|
+ <canvas id="canvas"></canvas>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </page-layer>
|
|
|
+</template>
|
|
|
+<script setup>
|
|
|
+import PageLayer from "@/layout/PageLayer.vue";
|
|
|
+import {ElMessage, ElMessageBox} from "element-plus";
|
|
|
+import {selectLotteryUsers, chooseWinner, recordLotteryResult} from "@/api/single-page/lottery";
|
|
|
+
|
|
|
+const lotteryPool = ref([])
|
|
|
+const lotteryUsers = ref([])
|
|
|
+const duringLottery = ref(false)
|
|
|
+
|
|
|
+const carousel = ref(null)
|
|
|
+
|
|
|
+const winner = reactive({
|
|
|
+ index: -1,
|
|
|
+ lotteryCode: '',
|
|
|
+ userCodeRs: '',
|
|
|
+ userName: ''
|
|
|
+})
|
|
|
+
|
|
|
+const currentLotteryName = ref('三等奖')
|
|
|
+const currentUserName = ref('等待抽奖')
|
|
|
+
|
|
|
+function uploadSuccess(response) {
|
|
|
+ ElMessage({
|
|
|
+ type: 'success',
|
|
|
+ message: '导入成功。',
|
|
|
+ duration: 2500
|
|
|
+ })
|
|
|
+ lotteryUsers.value = response.data
|
|
|
+}
|
|
|
+
|
|
|
+function initLotteryPool() {
|
|
|
+ lotteryPool.value = [
|
|
|
+ {code: 0, name: '特等奖', amount: 3, round: 0},
|
|
|
+ {code: 1, name: '一等奖', amount: 3, round: 0},
|
|
|
+ {code: 2, name: '二等奖', amount: 5, round: 0},
|
|
|
+ {code: 3, name: '三等奖', amount: 10, round: 0},
|
|
|
+ ]
|
|
|
+ localStorage.setItem('lotteryPool', JSON.stringify(lotteryPool.value))
|
|
|
+}
|
|
|
+
|
|
|
+let carouselInterval = null
|
|
|
+
|
|
|
+function moveToWinner() {
|
|
|
+ let timer = 0;
|
|
|
+ carouselInterval = setInterval(() => {
|
|
|
+ timer += 100
|
|
|
+ if (timer >= 5000) {
|
|
|
+ winnerExposed()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let index = Math.floor(Math.random() * lotteryUsers.value.length);
|
|
|
+ let tempUser = lotteryUsers.value[index]
|
|
|
+ currentUserName.value = tempUser.codeRs + ' - ' + tempUser.name
|
|
|
+ if (tempUser.codeRs === winner.userCodeRs && timer >= 3000) {
|
|
|
+ winnerExposed()
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+function winnerExposed() {
|
|
|
+ recordLotteryResult(winner).then(() => {
|
|
|
+
|
|
|
+ currentUserName.value = winner.userCodeRs + ' - ' + winner.userName
|
|
|
+ duringLottery.value = false;
|
|
|
+ clearInterval(carouselInterval)
|
|
|
+
|
|
|
+ startFirework()
|
|
|
+
|
|
|
+ const element = document.getElementById('winner-div')
|
|
|
+ element.style.transitionDuration = '.5s'
|
|
|
+ element.style.transitionTimingFunction = 'ease-in-out'
|
|
|
+ element.style.transformOrigin = 'center center'
|
|
|
+
|
|
|
+ element.style.transform = `scale(1.3, 1.3)`;
|
|
|
+
|
|
|
+ // 完成后重置样式
|
|
|
+ setTimeout(() => {
|
|
|
+ element.style.removeProperty('transition-duration');
|
|
|
+ element.style.removeProperty('transition-timing-function');
|
|
|
+ element.style.removeProperty('transform-origin');
|
|
|
+ element.style.removeProperty('transform');
|
|
|
+
|
|
|
+ }, 600);
|
|
|
+ })
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+function startLottery() {
|
|
|
+ duringLottery.value = true
|
|
|
+ getWinnableLottery().then(item => {
|
|
|
+ chooseWinner(item).then(res => {
|
|
|
+ winner.lotteryCode = item.code
|
|
|
+ winner.userCodeRs = res.codeRs
|
|
|
+ winner.userName = res.name
|
|
|
+ moveToWinner()
|
|
|
+ })
|
|
|
+ }).catch(() => {
|
|
|
+ duringLottery.value = false
|
|
|
+ ElMessageBox.alert('所有奖项均已抽完!', '提示', {
|
|
|
+ type: 'error',
|
|
|
+ showCancelButton: false
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function getWinnableLottery() {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ for (let i = lotteryPool.value.length - 1; i >= 0; i--) {
|
|
|
+ const item = lotteryPool.value[i]
|
|
|
+ if (item.amount > 0) {
|
|
|
+ item.amount -= 1
|
|
|
+ item.round += 1
|
|
|
+ currentLotteryName.value = item.name
|
|
|
+ localStorage.setItem('lotteryPool', JSON.stringify(lotteryPool.value))
|
|
|
+ resolve(item)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ reject()
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function editRule(item) {
|
|
|
+ ElMessageBox.prompt(`设置${item.name}数量:`, '提示', {
|
|
|
+ type: "warning",
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ inputPattern: /^100$|^(\d|[1-9]\d)$/,
|
|
|
+ inputErrorMessage: '请输入 0至100 的整数',
|
|
|
+ }).then(({value}) => {
|
|
|
+ item.amount = Number.parseInt(value)
|
|
|
+ localStorage.setItem('lotteryPool', JSON.stringify(lotteryPool.value))
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ let storage = localStorage.getItem('lotteryPool')
|
|
|
+ if (storage) {
|
|
|
+ lotteryPool.value = JSON.parse(storage);
|
|
|
+ } else {
|
|
|
+ initLotteryPool()
|
|
|
+ }
|
|
|
+ selectLotteryUsers().then(res => {
|
|
|
+ lotteryUsers.value = res
|
|
|
+ initFireworkCanvas()
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+let canvas, canvasContext, w, h, particles = [], probability = 0.04,
|
|
|
+ xPoint, yPoint;
|
|
|
+const fireworkRunnable = ref(false)
|
|
|
+
|
|
|
+function initFireworkCanvas() {
|
|
|
+ canvas = document.getElementById("canvas");
|
|
|
+ canvasContext = canvas.getContext("2d");
|
|
|
+ resizeCanvas();
|
|
|
+}
|
|
|
+
|
|
|
+function resizeCanvas() {
|
|
|
+ if (canvas) {
|
|
|
+ w = canvas.width = window.innerWidth;
|
|
|
+ h = canvas.height = window.innerHeight;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function startFirework() {
|
|
|
+ fireworkRunnable.value = true
|
|
|
+ window.requestAnimationFrame(updateWorld);
|
|
|
+ setTimeout(() => {
|
|
|
+ fireworkRunnable.value = false
|
|
|
+ canvasContext.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
+ }, 4000)
|
|
|
+}
|
|
|
+
|
|
|
+function updateWorld() {
|
|
|
+ if (fireworkRunnable.value) {
|
|
|
+ update();
|
|
|
+ paint();
|
|
|
+ window.requestAnimationFrame(updateWorld);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function update() {
|
|
|
+ if (particles.length < 500 && Math.random() < probability) {
|
|
|
+ createFirework();
|
|
|
+ }
|
|
|
+ let alive = [];
|
|
|
+ for (let i = 0; i < particles.length; i++) {
|
|
|
+ if (particles[i].move()) {
|
|
|
+ alive.push(particles[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ particles = alive;
|
|
|
+ canvasContext.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
+}
|
|
|
+
|
|
|
+function paint() {
|
|
|
+ canvasContext.globalCompositeOperation = 'source-over';
|
|
|
+ canvasContext.fillStyle = "transparent";
|
|
|
+
|
|
|
+ canvasContext.fillRect(0, 0, w, h);
|
|
|
+ canvasContext.globalCompositeOperation = 'lighter';
|
|
|
+ for (let i = 0; i < particles.length; i++) {
|
|
|
+ particles[i].draw(canvasContext);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function createFirework() {
|
|
|
+ xPoint = Math.random() * (w - 200) + 100;
|
|
|
+ yPoint = Math.random() * (h - 200) + 100;
|
|
|
+ let nFire = Math.floor(Math.random() * 100) + 100;
|
|
|
+
|
|
|
+ let c1 = 'rgb(255,' + Math.floor((Math.random() * 255)) + ',' + Math.floor((Math.random() * 255)) + ')';
|
|
|
+ let c2 = 'rgb(' + Math.floor((Math.random() * 255)) + ',255,' + Math.floor((Math.random() * 255)) + ')';
|
|
|
+ let c3 = 'rgb(' + Math.floor((Math.random() * 255)) + ',' + Math.floor((Math.random() * 255)) + ',255)';
|
|
|
+ let c=[c1,c2,c3]
|
|
|
+
|
|
|
+ for (let i = 0; i < nFire; i++) {
|
|
|
+ let particle = new Particle();
|
|
|
+ particle.color = c[Math.floor(Math.random() * 3)];
|
|
|
+ let vy = Math.sqrt(25 - particle.vx * particle.vx);
|
|
|
+ if (Math.abs(particle.vy) > vy) {
|
|
|
+ particle.vy = particle.vy > 0 ? vy : -vy;
|
|
|
+ }
|
|
|
+ particles.push(particle);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function Particle() {
|
|
|
+ this.w = this.h = Math.random() * 6 + 1;
|
|
|
+ this.x = xPoint - this.w / 2;
|
|
|
+ this.y = yPoint - this.h / 2;
|
|
|
+ this.vx = (Math.random() - 0.5) * 10;
|
|
|
+ this.vy = (Math.random() - 0.5) * 10;
|
|
|
+ this.alpha = Math.random() * .5 + .5;
|
|
|
+ this.color = '';
|
|
|
+}
|
|
|
+
|
|
|
+Particle.prototype = {
|
|
|
+ gravity: 0.05,
|
|
|
+ move: function () {
|
|
|
+ this.x += this.vx;
|
|
|
+ this.vy += this.gravity;
|
|
|
+ this.y += this.vy;
|
|
|
+ this.alpha -= 0.01;
|
|
|
+ return !(this.x <= -this.w || this.x >= screen.width ||
|
|
|
+ this.y >= screen.height ||
|
|
|
+ this.alpha <= 0);
|
|
|
+ },
|
|
|
+ draw: function (c) {
|
|
|
+ c.save();
|
|
|
+ c.beginPath();
|
|
|
+ c.translate(this.x + this.w / 2, this.y + this.h / 2);
|
|
|
+ c.arc(0, 0, this.w, 0, Math.PI * 2);
|
|
|
+ c.fillStyle = this.color;
|
|
|
+ c.globalAlpha = this.alpha;
|
|
|
+ c.closePath();
|
|
|
+ c.fill();
|
|
|
+ c.restore();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+:deep(.el-upload-dragger) {
|
|
|
+ background-color: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.reset-pool {
|
|
|
+ color: red;
|
|
|
+ font-size: 12px
|
|
|
+}
|
|
|
+
|
|
|
+.reset-pool:hover {
|
|
|
+ cursor: pointer;
|
|
|
+ text-decoration: underline;
|
|
|
+}
|
|
|
+</style>
|