You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2106 lines
46 KiB

<template>
<div class="homepage">
<img src="../../../assets/qilin.webp" alt="麒麟" class="hllogo" />
<!-- 添加worldcup音频元素 -->
<audio ref="worldcupAudioRef" :src="worldcup" preload="auto" loop></audio>
<!-- 添加dong音频元素 -->
<audio ref="dongAudioRef" :src="dong" preload="auto"></audio>
<div ref="qipaoTextRef" class="qipao">{{ qipaoText }}</div>
<div class="leftBar">
<el-scrollbar id="prizeBar">
<ul class="prize-list">
<li
v-for="item in prizes"
:key="item.type"
:id="`prize-item-${item.type}`"
:class="['prize-item']"
@click="lookPrize(item)"
>
<div
v-if="item.isLook"
style="display: flex; width: 100%; height: 100%"
>
<span></span><span></span><span></span><span></span>
<div class="prize-img">
<img :src="item.imageUrl" :alt="item.title" />
</div>
<div class="prize-text">
<div class="prize-title">
<div class="level">
{{ item.gradeName }}
</div>
{{ item.prizeName }}
</div>
<div class="prize-count">
<div class="progress">
<div
:id="`prize-bar-${item.type}`"
class="progress-bar progress-bar-danger progress-bar-striped active"
style="width: 100%"
></div>
</div>
<div
:id="`prize-count-${item.type}`"
class="prize-count-left"
>
{{ item.leftCount }}/{{ item.count }}
</div>
</div>
</div>
</div>
<div v-else style="display: flex; width: 100%; height: 100%">
<img
src="../../../assets/img/待揭秘.png"
alt="待揭秘"
class="readyLook"
/>
</div>
</li>
</ul>
</el-scrollbar>
<div v-if="!isOpen">
<div class="getPrizeName" @click="openGetPrize()">
<img src="../../../assets/img/展开.png" alt="展开" class="open" />
获奖名单
</div>
</div>
<div v-else>
<div class="dgetPrizeName">
<img
src="../../../assets/img/展开.png"
alt="展开"
class="close"
@click="closeGetPrize()"
/>
<div class="tableHead">
<div class="tableHead1">HomilyID</div>
<div class="tableHead2">奖项</div>
<div class="gradient-line"></div>
</div>
<el-scrollbar class="tableBody">
<div v-for="item in getPrizeUserList" :key="item">
<div class="tableItem">
<div class="tableItem1">
{{ item.jwcode }}
</div>
<div class="tableItem2">
{{ item.gradeName }}
</div>
</div>
</div>
</el-scrollbar>
<div class="tableFoot">
<span @click="leftPage()" id="leftPage" class="leftPage"><</span>
{{ currentPage }}/<span>{{ totalPage }}</span>
<span @click="rightPage()" id="rightPage" class="rightPage">></span>
</div>
</div>
</div>
</div>
<div id="container">
<div ref="threeContainer" class="three-container"></div>
<!-- 分页指示器 -->
<div v-if="pageMaxIndex > 1" class="page-indicator">
第 {{ pageIndex + 1 }} 页 / 共 {{ pageMaxIndex }} 页
</div>
</div>
<div id="menu">
<div id="enter" ref="enterRef" @click="enterLottery()" class="btn">
进入抽奖
</div>
<div id="lotteryBar" ref="lotteryBarRef" class="none">
<div v-if="isBackApi">
<div class="btn">{{ cjText }}</div>
</div>
<div v-else>
<div id="lottery" ref="lotteryRef" @click="lotteryBtn()" class="btn">
{{ cjText }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive, nextTick } from "vue";
import { useRouter } from "vue-router";
import * as THREE from "three";
import axios from "axios";
import "../../../assets/css/animate.min.css";
import { resetPrize } from "../../../utils/prizeList";
import { NUMBER_MATRIX } from "../../../utils/config";
import { CSS3DObject, CSS3DRenderer } from "../../../utils/CSS3DRenderer.js";
import { TrackballControls } from "../../../utils/TrackballControls.js";
import TWEEN from "@tweenjs/tween.js";
import worldcup from "../../../assets/worldcup.mp3";
import dong from "../../../assets/dong.mp3";
import {
getPrizeListApi,
getUserListApi,
getGetPrizeUserListApi,
startLotteryApi,
} from "../../../api/API";
// 常量
const ROTATE_TIME = 10000; //旋转动画的总时长
const ROTATE_LOOP = 1000;
const BASE_HEIGHT = 1080; //高度
// 按钮控制
const enterRef = ref(null); //进入抽奖按钮
const lotteryBarRef = ref(null); //开始抽奖元素组(显示控制)
const lotteryRef = ref(null); //开始抽奖按钮(文字控制 )
//3D相关变量
// 当前的比例
let Resolution = 0.9;
//总卡片数
let TOTAL_CARDS;
// 高度卡片数
let ROW_COUNT = 7;
// 宽度卡片数
let COLUMN_COUNT = 21;
// 高亮卡片数组
let HIGHLIGHT_CELL = [];
// 其他变量
let camera,
scene,
renderer,
controls,
threeDCards = [],
targets = {
table: [],
sphere: [],
};
// 动画对象
let rotateObj;
// 控制旋转
let rotate = false;
//奖品列表
const prizes = ref([]);
//每个奖品每次抽取的数目
let EACH_COUNT = [];
const users = ref([]);
const getPrizeUsers = ref([]);
//判断是否正在抽奖
let isLotting = false;
const setLotteryStatus = (status = false) => {
isLotting = status;
};
let selectedCardIndex = [];
let interval;
// 当前抽的奖项,从最低奖开始抽,直到抽到大奖
let currentPrizeIndex;
const currentPrize = ref({});
let currentLuckys = [];
let prizeElement = {};
const qipaoText = ref("");
const qipaoTextRef = ref(null);
const worldcupAudioRef = ref(null);
const dongAudioRef = ref(null);
// 播放worldcup音频
const playWorldcupAudio = () => {
if (worldcupAudioRef.value) {
worldcupAudioRef.value.currentTime = 0; // 重置播放位置
worldcupAudioRef.value.play().catch((error) => {
console.log("音频播放失败:", error);
});
}
};
// 暂停worldcup音频
const pauseWorldcupAudio = () => {
if (worldcupAudioRef.value) {
worldcupAudioRef.value.pause();
}
};
const playDongAudio = () => {
if (dongAudioRef.value) {
dongAudioRef.value.currentTime = 0; // 重置播放位置
dongAudioRef.value.play().catch((error) => {
console.log("音频播放失败:", error);
});
}
};
// 暂停dong音频
const pauseDongAudio = () => {
if (dongAudioRef.value) {
dongAudioRef.value.pause();
}
};
const addQipao = (text) => {
qipaoTextRef.value.style.display = "block";
qipaoTextRef.value.style.opacity = "1";
qipaoText.value = text;
setTimeout(() => {
// 设置过渡效果
qipaoTextRef.value.style.transition = "opacity 0.5s ease-out";
// 开始渐隐
qipaoTextRef.value.style.opacity = "0";
// 动画完成后隐藏元素
setTimeout(() => {
qipaoTextRef.value.style.display = "none";
}, 800);
}, 1000);
};
const getPrizeUserList = ref([]);
const isOpen = ref(false);
const pageObj = ref({
pageNum: 1,
pageSize: 14,
});
const currentPage = ref(1);
const totalPage = ref(10);
const openGetPrize = async () => {
if (!prizes.value[prizes.value.length - 1].isLook) {
addQipao("请先揭晓奖品。");
return;
}
let res = await getGetPrizeUserListApi(pageObj.value);
getPrizeUserList.value = res.data.list;
currentPage.value = res.data.pageNum;
totalPage.value = res.data.pages;
isOpen.value = true;
// console.log("currentPrize", currentPrize.value);
prizes.value.forEach((item) => {
// console.log("item", item);
if (item.type != currentPrize.value.type) {
let box = document.querySelector(`#prize-item-${item.type}`);
box.style.display = "none";
}
});
let scroll = document.getElementById("prizeBar");
// scroll.style.height = "110px";
};
const closeGetPrize = () => {
isOpen.value = false;
prizes.value.forEach((item) => {
// console.log("item", item);
if (item.type != currentPrize.value.type) {
let box = document.querySelector(`#prize-item-${item.type}`);
box.style.display = "flex";
}
});
// let scroll = document.getElementById("prizeBar");
// scroll.style.height = "650px";
};
const leftPage = async (item) => {
if (currentPage.value == 1) {
return;
}
if (currentPage.value == totalPage.value) {
const rightPageBtn = document.getElementById("rightPage");
rightPageBtn.style.cursor = "pointer";
rightPageBtn.style.setProperty(
"background",
"linear-gradient(90deg, #ff9800 0%, #ff5722 100%)",
"important"
);
}
currentPage.value--;
pageObj.value.pageNum = currentPage;
let res = await getGetPrizeUserListApi(pageObj.value);
getPrizeUserList.value = res.data.list;
currentPage.value = res.data.pageNum;
if (currentPage.value == 1) {
const leftPageBtn = document.getElementById("leftPage");
leftPageBtn.style.cursor = "not-allowed";
leftPageBtn.style.background = "#ccc";
}
};
const rightPage = async (item) => {
if (currentPage.value == totalPage.value) {
return;
}
if (currentPage.value == 1) {
const leftPageBtn = document.getElementById("leftPage");
console.log("leftPageBtn", leftPageBtn);
leftPageBtn.style.cursor = "pointer";
leftPageBtn.style.setProperty(
"background",
"linear-gradient(90deg, #ff9800 0%, #ff5722 100%)",
"important"
);
}
currentPage.value++;
pageObj.value.pageNum = currentPage;
let res = await getGetPrizeUserListApi(pageObj.value);
getPrizeUserList.value = res.data.list;
currentPage.value = res.data.pageNum;
if (currentPage.value == totalPage.value) {
const rightPageBtn = document.getElementById("rightPage");
rightPageBtn.style.cursor = "not-allowed";
rightPageBtn.style.background = "#ccc";
}
};
//揭示奖品
const lookPrize = async (item) => {
// 未进入抽奖禁止揭晓
if (!joinLottery) {
addQipao("请先进入抽奖。");
return;
}
// 如果已经揭示,那返回
if (item.isLook) return;
//最低级的可以直接揭晓
if (
currentPrize.value.type == prizes.value[prizes.value.length - 1].type &&
item.type == currentPrize.value.type
) {
let currentBox = document.querySelector(`#prize-item-${item.type}`);
currentBox && currentBox.classList.add("shine");
//点击揭晓
item.isLook = true;
} else if (
!isLotting && //未在抽奖状态
currentPrize.value.isLook &&
currentPrize.value.leftCount == 0 && //当前奖项已抽完
prizes.value[currentPrizeIndex - 1] == item //点击的奖项是当前奖项的下一个
) {
currentPrize.value = item;
currentPrizeIndex--;
let lastIndex = -1;
for (let i = prizes.value.length - 1; i >= 0; i--) {
if (prizes.value[i].type == item.type) {
if (i != prizes.value.length - 1) {
lastIndex = i + 1;
}
break;
}
}
if (lastIndex != -1) {
let lastPrize = prizes.value[lastIndex];
// console.log("lastPrize", lastPrize);
let lastBox = document.querySelector(`#prize-item-${lastPrize.type}`);
lastBox.classList.remove("shine");
lastBox.classList.add("done");
let currentBox = document.querySelector(`#prize-item-${item.type}`);
currentBox && currentBox.classList.add("shine");
}
//点击揭晓
item.isLook = true;
}
await nextTick();
setPrizeData(currentPrizeIndex);
};
function setPrizeData(currentPrizeIndex, isInit) {
// console.log("prizes", prizes.value);
// let currentPrize = prizes.value[currentPrizeIndex];
let type = currentPrize.value.type;
let elements = prizeElement[type];
let count = currentPrize.value.leftCount;
let totalCount = currentPrize.value.count;
if (!elements || !elements.bar || !elements.text) {
elements = {
box: document.querySelector(`#prize-item-${type}`),
bar: document.querySelector(`#prize-bar-${type}`),
text: document.querySelector(`#prize-count-${type}`),
};
}
if (isInit) {
for (let i = prizes.value.length - 1; i > currentPrizeIndex; i--) {
let type = prizes.value[i]["type"];
document.querySelector(`#prize-item-${type}`).className =
"prize-item done";
document.querySelector(`#prize-bar-${type}`).style.width = "0";
document.querySelector(`#prize-count-${type}`).textContent =
"0" + "/" + prizes.value[i]["count"];
}
}
console.log("count", count);
console.log("totalCount", totalCount);
let percent = (count / totalCount).toFixed(2);
if (elements.bar) {
elements.bar.style.width = percent * 100 + "%";
}
}
/**
* 初始化所有DOM
*/
const pageIndex = ref(0);
const pageMaxIndex = ref(0);
const cardsPerPage = 10; // 每页显示的卡片数量
let isPageTransitioning = false; // 防止页面切换时的重复操作
// 全局变量存储当前选中的卡片索引数组
let globalCardIndexes = [];
const initAll = async () => {
const [prizeList, userList] = await Promise.all([
getPrizeListApi(),
getUserListApi(),
]);
console.log("hxl-cj调用一次接口", prizeList);
// 奖品列表
prizes.value = prizeList.data;
// 用户列表
users.value = userList.data;
prizes.value.forEach((item, index) => {
item.type = index;
item.count = item.amount;
item.leftCount = item.remainNum; //剩余次数(用于计算奖品下方的进度条的百分比)
item.hasCount = item.amount; //已抽次数(用于计算出奖)
item.isLook = false; //
EACH_COUNT.push(item.perWin);
});
console.log("prizes", prizes.value);
console.log("EACH_COUNT", EACH_COUNT);
HIGHLIGHT_CELL = createHighlight();
TOTAL_CARDS = ROW_COUNT * COLUMN_COUNT;
currentPrizeIndex = prizes.value.length - 1;
currentPrize.value = prizes.value[currentPrizeIndex];
await nextTick();
setPrizeData(currentPrizeIndex, true);
initCards();
// startMaoPao();
animate();
shineCard();
// window.AJAX({
// url: "/getUsers",
// success(data) {},
// });
};
const initCards = () => {
let member = users.value.slice(),
showCards = [],
length = member.length;
let isBold = false;
let index = 0;
let totalMember = member.length;
let position = {
x: (140 * COLUMN_COUNT - 20) / 2,
y: (180 * ROW_COUNT - 20) / 2,
};
camera = new THREE.PerspectiveCamera(
40,
window.innerWidth / window.innerHeight,
1,
10000
);
camera.position.z = 3000;
scene = new THREE.Scene();
for (let i = 0; i < ROW_COUNT; i++) {
for (let j = 0; j < COLUMN_COUNT; j++) {
isBold = HIGHLIGHT_CELL.includes(j + "-" + i);
var element = createCard(member[index % length], isBold, index);
var object = new CSS3DObject(element);
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
scene.add(object);
threeDCards.push(object);
//
var object = new THREE.Object3D();
object.position.x = j * 155 - position.x;
object.position.y = -(i * 195) + position.y;
targets.table.push(object);
index++;
}
}
// sphere
var vector = new THREE.Vector3();
for (var i = 0, l = threeDCards.length; i < l; i++) {
var phi = Math.acos(-1 + (2 * i) / l);
var theta = Math.sqrt(l * Math.PI) * phi;
var object = new THREE.Object3D();
object.position.setFromSphericalCoords(800 * Resolution, phi, theta);
vector.copy(object.position).multiplyScalar(2);
object.lookAt(vector);
targets.sphere.push(object);
}
renderer = new CSS3DRenderer();
renderer.setSize(window.innerWidth * 1, window.innerHeight * 0.9);
renderer.domElement.style.margin = "7% 0 0 1%";
// document.getElementById("container").appendChild(renderer.domElement);
if (threeContainer.value) {
threeContainer.value.appendChild(renderer.domElement);
}
//
controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 0.5;
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener("change", render);
switchScreen("enter");
};
// 判断是否进入抽奖
let joinLottery = false;
// 进入抽奖
const enterLottery = () => {
joinLottery = true;
removeHighlight();
// rotate = !rotate;
rotate = true;
switchScreen("lottery");
};
function switchScreen(type) {
switch (type) {
case "enter":
if (enterRef.value) {
enterRef.value.classList.remove("none");
}
if (lotteryBarRef.value) {
lotteryBarRef.value.classList.add("none");
}
transform(targets.table, 2000);
break;
default:
if (enterRef.value) {
enterRef.value.classList.add("none");
}
if (lotteryBarRef.value) {
lotteryBarRef.value.classList.remove("none");
}
transform(targets.sphere, 2000);
break;
}
}
/**
* 创建元素
*/
const createElement = (css, text) => {
let dom = document.createElement("div");
dom.className = css || "";
dom.innerHTML = text || "";
return dom;
};
/**
* 创建名牌
*/
const createCard = (user, isBold, id) => {
var element = createElement();
element.id = "card-" + id;
element.style.display = "flex";
element.style.alignItems = "center";
element.style.justifyContent = "center";
if (isBold) {
element.className = "element lightitem";
element.classList.add("highlight");
} else {
element.className = "element";
// element.style.backgroundColor =
// "rgba(255,170,22," + (Math.random() * 0.7 + 0.25) + ")";
element.style.backgroundColor = "rgba(255,170,22,1)";
element.style.border = "2px solid rgba(255, 255, 255, 1)";
}
element.appendChild(createElement("name", user.jwcode));
return element;
};
function removeHighlight() {
document.querySelectorAll(".highlight").forEach((node) => {
node.classList.remove("highlight");
});
}
function addHighlight() {
document.querySelectorAll(".lightitem").forEach((node) => {
node.classList.add("highlight");
});
}
/**
* 渲染地球等
*/
function transform(targets, duration) {
// TWEEN.removeAll();
for (var i = 0; i < threeDCards.length; i++) {
var object = threeDCards[i];
var target = targets[i];
new TWEEN.Tween(object.position)
.to(
{
x: target.position.x,
y: target.position.y,
z: target.position.z,
},
Math.random() * duration + duration
)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
new TWEEN.Tween(object.rotation)
.to(
{
x: target.rotation.x,
y: target.rotation.y,
z: target.rotation.z,
},
Math.random() * duration + duration
)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
new TWEEN.Tween(this)
.to({}, duration * 2)
.onUpdate(render)
.start();
}
function rotateBall() {
return new Promise((resolve, reject) => {
scene.rotation.y = 0;
rotateObj = new TWEEN.Tween(scene.rotation);
rotateObj
.to(
{
y: Math.PI * 6 * ROTATE_LOOP,
},
ROTATE_TIME * ROTATE_LOOP
)
.onUpdate(render)
// .easing(TWEEN.Easing.Linear)
.start()
.onStop(() => {
scene.rotation.y = 0;
resolve();
})
.onComplete(() => {
resolve();
});
});
}
function animate() {
// 让场景通过x轴或者y轴旋转
// rotate && (scene.rotation.y += 0.088);
requestAnimationFrame(animate);
TWEEN.update();
controls.update();
// 渲染循环
// render();
}
function render() {
renderer.render(scene, camera);
}
const threeContainer = ref(null);
function selectCard(duration = 600) {
return new Promise((resolve) => {
const width = 160;
// 计算总页数
pageMaxIndex.value = Math.ceil(currentLuckys.length / cardsPerPage);
pageIndex.value = 0;
// 为每页计算位置信息
const pageLocates = [];
for (let page = 0; page < pageMaxIndex.value; page++) {
const startIndex = page * cardsPerPage;
const endIndex = Math.min(
(page + 1) * cardsPerPage,
currentLuckys.length
);
const pageCount = endIndex - startIndex;
const pageLocate = [];
// 根据当前页的人数决定排列方式
if (pageCount > 5) {
// 大于5个分两排显示(与抽10人时的排列相同)
const yPosition = [-75, 120];
const mid = Math.ceil(pageCount / 2);
let tag = -(mid - 1) / 2;
for (let i = 0; i < mid; i++) {
pageLocate.push({
x: tag * width,
y: yPosition[0],
});
tag++;
}
tag = -(pageCount - mid - 1) / 2;
for (let i = mid; i < pageCount; i++) {
pageLocate.push({
x: tag * width,
y: yPosition[1],
});
tag++;
}
} else {
// 小于等于5个一排显示(与抽不足10人时的排列相同)
let tag = -(pageCount - 1) / 2;
for (let i = 0; i < pageCount; i++) {
pageLocate.push({
x: tag * width,
y: 0,
});
tag++;
}
}
pageLocates.push(pageLocate);
}
// console.log("pageLocates calculated:", pageLocates);
// 初始化所有卡片位置(隐藏超出第一页的卡片)
selectedCardIndex.forEach((cardIndex, index) => {
changeSelectedCard(cardIndex, currentLuckys[index]);
const object = threeDCards[cardIndex];
// 计算卡片应该在第几页
const cardPage = Math.floor(index / cardsPerPage);
const isVisible = cardPage === 0;
// 计算在当前页中的索引
const pageNowIndex = index % cardsPerPage;
const pageLocate = pageLocates[cardPage][pageNowIndex];
new TWEEN.Tween(object.position)
.to(
{
x: isVisible ? pageLocate.x : pageLocate.x,
y: isVisible ? pageLocate.y : pageLocate.y + 1000, // 隐藏的卡片移到下方
z: 2100,
},
Math.random() * duration + duration
)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
new TWEEN.Tween(object.rotation)
.to(
{
x: 0,
y: 0,
z: 0,
},
Math.random() * duration + duration
)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
object.element.classList.add("prize");
});
// 保存页面位置信息到全局变量,供switchPage使用
window.pageLocates = pageLocates;
new TWEEN.Tween({})
.to({}, duration * 2)
.onUpdate(() => render())
.start()
.onComplete(() => {
console.log("selectCard animation completed");
// 如果有多页,添加鼠标滚轮事件监听
if (pageMaxIndex.value > 1) {
// console.log("pageMaxIndex添加滚轮", pageMaxIndex.value);
// 添加滚轮事件监听
addWheelListener();
}
setLotteryStatus();
resolve();
});
});
}
// 分页切换函数
function switchPage(direction) {
if (isPageTransitioning || pageMaxIndex.value <= 1) return;
const newPage =
direction === "next" ? pageIndex.value + 1 : pageIndex.value - 1;
if (newPage < 0 || newPage >= pageMaxIndex.value) return;
isPageTransitioning = true;
const duration = 800;
// 使用保存的页面位置信息
const pageLocates = window.pageLocates;
if (!pageLocates) {
console.error("页面位置信息未找到");
isPageTransitioning = false;
return;
}
// 动画切换卡片位置
globalCardIndexes.forEach((cardIndex, index) => {
const object = threeDCards[cardIndex];
const cardPage = Math.floor(index / cardsPerPage);
const isVisible = cardPage === newPage;
const wasVisible = cardPage === pageIndex.value;
// 计算在当前页中的索引
const pageNowIndex = index % cardsPerPage;
const pageLocate = pageLocates[cardPage][pageNowIndex];
// console.log(
// "cardPage",
// cardPage,
// "pageNowIndex",
// pageNowIndex,
// "pageLocate",
// pageLocate
// );
// 根据切换方向决定动画效果
let targetY;
if (isVisible) {
// 当前页要显示的卡片
if (direction === "next") {
// 索引增大:从下方飞出
targetY = pageLocate.y;
} else {
// 索引减少:从上方飞出
targetY = pageLocate.y;
}
} else {
// 当前页要隐藏的卡片
if (direction === "next") {
// 索引增大:向上飞走
targetY = pageLocate.y + 1000;
} else {
// 索引减少:向下飞走
targetY = pageLocate.y - 1000;
}
}
// 设置起始位置
let startY;
if (wasVisible) {
// 当前页的卡片从当前位置开始
startY = object.position.y;
} else {
// 非当前页的卡片从隐藏位置开始
if (direction === "next") {
// 索引增大:从下方开始
startY = pageLocate.y - 1000;
} else {
// 索引减少:从上方开始
startY = pageLocate.y + 1000;
}
}
// 先设置起始位置
object.position.y = startY;
new TWEEN.Tween(object.position)
.to(
{
x: pageLocate.x,
y: targetY,
z: 2100,
},
duration
)
.easing(TWEEN.Easing.Cubic.InOut)
.start();
});
new TWEEN.Tween({})
.to({}, duration)
.onUpdate(() => render())
.onComplete(() => {
pageIndex.value = newPage;
isPageTransitioning = false;
console.log(
`切换到第 ${pageIndex.value + 1} 页,共 ${pageMaxIndex.value} 页`
);
})
.start();
}
// 鼠标滚轮事件处理
function handleWheel(event) {
if (isPageTransitioning || pageMaxIndex.value <= 1) return;
event.preventDefault();
if (event.deltaY > 0) {
// 向下滚动,显示下一页
switchPage("next");
} else if (event.deltaY < 0) {
// 向上滚动,显示上一页
switchPage("prev");
}
}
// 添加鼠标滚轮事件监听器
function addWheelListener() {
if (threeContainer.value) {
threeContainer.value.addEventListener("wheel", handleWheel, {
passive: false,
});
}
}
// 移除鼠标滚轮事件监听器
function removeWheelListener() {
if (threeContainer.value) {
threeContainer.value.removeEventListener("wheel", handleWheel);
}
}
/**
* 重置抽奖牌内容
*/
function resetCard(duration = 500) {
if (currentLuckys.length === 0) {
return Promise.resolve();
}
globalCardIndexes = [];
// 移除鼠标滚轮事件监听器
removeWheelListener();
// 重置分页状态
pageIndex.value = 0;
pageMaxIndex.value = 0;
isPageTransitioning = false;
// 清理保存的页面位置信息
if (window.pageLocates) {
delete window.pageLocates;
}
selectedCardIndex.forEach((index) => {
let object = threeDCards[index],
target = targets.sphere[index];
new TWEEN.Tween(object.position)
.to(
{
x: target.position.x,
y: target.position.y,
z: target.position.z,
},
Math.random() * duration + duration
)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
new TWEEN.Tween(object.rotation)
.to(
{
x: target.rotation.x,
y: target.rotation.y,
z: target.rotation.z,
},
Math.random() * duration + duration
)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
});
return new Promise((resolve, reject) => {
new TWEEN.Tween(this)
.to({}, duration * 2)
.onUpdate(render)
.start()
.onComplete(() => {
selectedCardIndex.forEach((index) => {
let object = threeDCards[index];
object.element.classList.remove("prize");
});
resolve();
});
});
}
const isBackApi = ref(false);
const cjText = ref("开始抽奖");
// 抽奖
const lotteryBtn = () => {
// console.log("isLotting", isLotting);
// console.log("currentPrize.value", currentPrize.value);
if (isLotting) {
stopLottery();
return;
}
if (!currentPrize.value.isLook) {
addQipao("请先揭秘礼品。");
return;
}
if (currentPrize.value.leftCount <= 0) {
addQipao("该礼品已抽取完毕,请揭秘下一个礼品。");
return;
}
// lotteryRef.value.innerHTML = "结束抽奖";
cjText.value = "结束抽奖";
isBackApi.value = true;
// 播放worldcup音频
playWorldcupAudio();
getPrizeUsers.value = [];
let params = {
gradeId: currentPrize.value.gradeId,
prizeId: currentPrize.value.prizeId,
perWin: currentPrize.value.perWin,
remainNum: currentPrize.value.leftCount,
};
// 异步调用API,不阻塞后续代码执行
startLotteryApi(params)
.then((res) => {
// API返回结果时赋值
getPrizeUsers.value = res.data.data || [];
isBackApi.value = false;
console.log("API返回结果:", res.data.data);
})
.catch((err) => {
isBackApi.value = false;
console.error("API调用失败:", err);
getPrizeUsers.value = [];
});
setLotteryStatus(true);
//更新剩余抽奖数目的数据显示
changePrize();
resetCard().then((res) => {
// 抽奖
lottery();
});
console.log("currentPrize", currentPrize.value);
const text = "正在抽取[" + currentPrize.value.prizeName + "],调整好姿势";
// addQipao(text);
};
/**
* 抽奖
*/
const lottery = () => {
rotateBall().then(() => {
// 将之前的记录置空
currentLuckys = [];
selectedCardIndex = [];
// 当前同时抽取的数目,当前奖品抽完还可以继续抽,但是不记录数据
for (let i = 0; i < getPrizeUsers.value.length; i++) {
console.log("111", getPrizeUsers.value[i]);
currentLuckys.push(getPrizeUsers.value[i]);
currentPrize.value.hasCount--;
//避免中奖者出现在同一个卡片上
let cardIndex = random(TOTAL_CARDS);
while (selectedCardIndex.includes(cardIndex)) {
cardIndex = random(TOTAL_CARDS);
}
selectedCardIndex.push(cardIndex);
if (currentPrize.value.hasCount === 0) {
break;
}
}
selectCard(600);
});
};
const stopLottery = () => {
// lotteryRef.value.innerHTML = "开始抽奖";
cjText.value = "开始抽奖";
rotateObj.stop();
// 暂停worldcup音频
pauseWorldcupAudio();
playDongAudio();
};
const changePrize = async () => {
let type = currentPrize.value.type;
prizes.value[type].leftCount =
prizes.value[type].leftCount - EACH_COUNT[type] < 0
? 0
: prizes.value[type].leftCount - EACH_COUNT[type];
await nextTick();
// 修改左侧prize的数目和百分比
setPrizeData(currentPrizeIndex);
};
/**
* 随机抽奖
*/
function random(num) {
// Math.floor取到0-num-1之间数字的概率是相等的
return Math.floor(Math.random() * num);
}
/**
* 切换名牌人员信息
*/
function changeCard(cardIndex, user) {
let card = threeDCards[cardIndex].element;
// console.log("user", user);
card.innerHTML = `<div class="name">${user.jwcode}</div>`;
}
function changeSelectedCard(cardIndex, user) {
// 保存到全局变量数组
if (!globalCardIndexes.includes(cardIndex)) {
globalCardIndexes.push(cardIndex);
}
let card = threeDCards[cardIndex].element;
card.innerHTML = `<div class="name">${user.jwcode}</div>`;
}
/**
* 切换名牌背景
*/
function changeBackground(cardIndex, color) {
let card = threeDCards[cardIndex].element;
// card.style.backgroundColor =
// color || "rgba(255,170,22," + (Math.random() * 0.7 + 0.25) + ")";
card.style.backgroundColor = color || "rgba(255,170,22,1)";
card.style.border = "2px solid rgba(255, 255, 255, 1)";
}
/**
* 随机切换背景和人员信息
*/
function shineCard() {
let maxCard = 10;
let maxUser;
let shineCard = 10 + random(maxCard);
setInterval(() => {
// 正在抽奖停止闪烁
if (isLotting) {
return;
}
maxUser = users.value.length;
for (let i = 0; i < shineCard; i++) {
let index = random(maxUser);
let cardIndex = random(TOTAL_CARDS);
// 当前显示的已抽中名单不进行随机切换
if (selectedCardIndex.includes(cardIndex)) {
continue;
}
changeBackground(cardIndex);
changeCard(cardIndex, users.value[index]);
}
}, 500);
}
const createHighlight = () => {
let text = "0123";
let step = 5;
let xoffset = 1;
let yoffset = 1;
let highlight = [];
text.split("").forEach((n) => {
highlight = highlight.concat(
NUMBER_MATRIX[n].map((item) => {
return `${item[0] + xoffset}-${item[1] + yoffset}`;
})
);
xoffset += step;
});
return highlight;
};
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
}
onMounted(async () => {
initAll();
window.addEventListener("resize", onWindowResize, false);
});
</script>
<style>
html,
body,
#app {
height: 100vh;
width: 100vw;
}
.homepage {
width: 100%;
height: 100%;
display: flex;
background-image: url("../../../assets/bg@2x.png");
background-repeat: no-repeat;
background-size: 100% 100%;
justify-content: center;
}
.hllogo {
position: absolute;
right: 0;
bottom: 0;
}
.qipao {
width: 100%;
z-index: 100;
position: absolute;
top: 27vh;
left: 50%;
transform: translateX(-50%);
color: #db5c58;
font-weight: bold;
font-size: 42px;
width: auto;
text-align: center;
display: none;
white-space: nowrap;
}
a {
color: #ffffff;
}
.none {
display: none;
}
#container {
z-index: 3;
position: relative;
overflow: hidden;
}
.three-container {
width: 100%;
height: 100%;
}
/* 分页指示器样式 */
.page-indicator {
position: absolute;
bottom: 10%;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
pointer-events: none;
}
.canvas-box {
/* background-color: rgb(214, 0, 0); */
position: fixed;
left: 0;
top: 0;
z-index: -1;
}
#info {
position: absolute;
width: 100%;
color: #ffffff;
padding: 5px;
font-family: Monospace;
font-size: 13px;
font-weight: bold;
text-align: center;
z-index: 1;
}
#menu {
z-index: 4;
position: absolute;
bottom: 1vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.element {
width: 15vh;
height: 19vh;
/* box-shadow: 0 0 12px rgba(0, 255, 255, 0.5); */
border: 1px solid rgba(127, 255, 255, 0.25);
text-align: center;
cursor: default;
transition: background-color 0.3s ease-in;
}
.element:hover {
box-shadow: 0 0 12px rgba(255, 168, 38, 0.75);
border: 1px solid rgba(255, 255, 255, 1);
}
.element .name {
font-size: 2.9vh;
font-weight: bold;
color: rgba(255, 255, 255, 1);
/* text-shadow: 0 0 1vh rgba(0, 255, 255, 0.95); */
}
button {
border: none;
}
.btn {
background-image: url("../../../assets/img/抽奖按钮.png");
background-color: transparent;
background-repeat: no-repeat;
background-size: 100% 100%;
width: 180px;
height: 70px;
color: #fff;
border: 0;
font-size: 2.5vh;
font-weight: bold;
cursor: pointer;
text-align: center;
padding-top: 5px;
}
.highlight {
background: linear-gradient(360deg, #e23d26, #f49c27) !important;
box-shadow: 0 0 12px rgba(253, 105, 0, 0.95);
border: 2px solid rgba(255, 255, 255, 1);
}
.highlight.element .name {
text-shadow: 0 0 16px rgba(255, 255, 255, 0.95);
}
.prize.element .name {
text-shadow: none;
}
.prize.element {
transition: background-color 1.5s ease-in 0.3s;
background: linear-gradient(360deg, #e23d26, #f49c27) !important;
border: 2px solid rgba(255, 255, 255, 1);
}
.prize .company,
.prize .details,
.prize .name,
.highlight .company,
.highlight .name,
.highlight .details {
color: rgba(255, 255, 255, 0.85);
}
.dan-mu {
visibility: hidden;
position: fixed;
z-index: -1;
font-size: 12px;
top: 1vh;
left: 0;
padding: 0 1.2vh;
height: 2.2vh;
line-height: 2.2vh;
border-radius: 1vh;
box-sizing: border-box;
background-color: rgba(0, 127, 127, 0.37);
box-shadow: 0 0 4px rgba(0, 255, 255, 0.5);
border: 1px solid rgba(127, 255, 255, 0.25);
color: rgba(127, 255, 255, 0.75);
}
.dan-mu.active {
visibility: visible;
}
.leftBar {
position: fixed;
left: 0;
padding-left: 1.2vh;
top: 15vh;
z-index: 100;
}
#prizeBar {
max-height: 60vh;
width: 330px;
overflow-x: hidden;
overflow-y: auto;
/* 隐藏滚动条 */
scrollbar-width: none;
/* Firefox */
-ms-overflow-style: none;
/* IE */
}
#prizeBar::-webkit-scrollbar {
display: none;
/* Chrome, Safari */
}
.prize-list {
margin: 0;
padding: 0;
list-style: none;
}
.prize-item {
padding: 2px;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
/* color: rgba(127, 255, 255, 0.75); */
width: 30vh;
height: 9.5vh;
box-sizing: border-box;
transition: transform 1s ease-in;
}
.getPrizeName {
color: #d5291f;
font-weight: bold;
font-size: 24px;
width: 27vh;
height: 5vh;
border: 2px solid rgb(255, 255, 255);
border-radius: 5px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #ffd283;
opacity: 0.8;
margin-left: 2vh;
margin-top: 3.5vh;
cursor: pointer;
position: relative;
}
.dgetPrizeName {
color: #d5291f;
font-weight: bold;
font-size: 24px;
width: 27vh;
height: 60vh;
border: 2px solid rgb(255, 255, 255);
border-radius: 5px;
display: flex;
flex-direction: column;
/* justify-content: center; */
align-items: center;
background-color: #ffd283;
opacity: 0.8;
margin-left: 2vh;
margin-top: 3.5vh;
position: relative;
}
.open {
position: absolute;
top: -42px;
animation: bounce1 2s ease-in-out infinite;
transform: rotate(180deg);
}
.close {
position: absolute;
top: -32px;
animation: bounce2 2s ease-in-out infinite;
cursor: pointer;
}
@keyframes bounce1 {
0%,
100% {
transform: rotate(180deg) translateY(0);
}
50% {
transform: rotate(180deg) translateY(-8px);
}
}
@keyframes bounce2 {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-8px);
}
}
.tableHead {
width: 100%;
display: flex;
justify-content: center;
font-size: 18px;
/* gap: 80px; */
margin-bottom: 10px;
position: absolute;
top: 10px;
}
.tableHead1 {
width: 50%;
text-align: center;
}
.tableHead2 {
width: 50%;
text-align: center;
}
.gradient-line {
width: 90%;
height: 3px;
border-radius: 150%;
background: linear-gradient(
to right,
transparent 0%,
#d5291f 45%,
#d5291f 55%,
transparent 100%
);
position: absolute;
bottom: -10px;
}
.tableBody {
display: flex;
margin-top: 50px;
width: 100%;
height: 50vh;
justify-content: center;
overflow-y: auto;
/* 启用垂直滚动 */
overflow-x: hidden;
/* 启用垂直滚动 */
/* 隐藏滚动条 */
scrollbar-width: none;
/* Firefox */
-ms-overflow-style: none;
/* IE */
}
.tableBody::-webkit-scrollbar {
display: none;
/* Chrome, Safari */
}
.tableItem {
display: flex;
font-size: 18px;
width: 27vh;
margin-bottom: 10px;
top: 10px;
}
.tableItem1 {
width: 50%;
text-align: center;
}
.tableItem2 {
width: 50%;
text-align: center;
}
.tableFoot {
color: #e64f39;
display: flex;
gap: 10px;
}
.leftPage {
background-color: #ccc;
color: white;
cursor: not-allowed;
/* background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%); */
border-radius: 50%;
display: flex;
width: 35px;
height: 35px;
justify-content: center;
/* align-items: center; */
}
.rightPage {
color: white;
cursor: pointer;
background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%);
border-radius: 50%;
display: flex;
width: 35px;
height: 35px;
justify-content: center;
}
.readyLook {
width: 100%;
height: 100%;
}
.prize-item .prize-img {
width: 7.5vh;
height: 7.5vh;
margin: auto 10px;
border-radius: 50%;
background-color: #fff;
text-shadow: 0 0 1vh rgba(0, 255, 255, 0.95);
overflow: hidden;
}
.prize-img img {
width: 90%;
height: 90%;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.prize-text {
padding: 0 5px;
width: 100%;
height: 100%;
/* margin: auto 0; */
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
/* gap: 4px; */
}
.prize-title {
margin: 4px 0;
font-size: 1.4vh;
height: 30%;
border: 1px solid #e13726;
border-radius: 20px;
background-color: #ffffff;
display: flex;
color: #d5291f;
font-weight: bold;
display: flex;
align-items: center;
}
.level {
width: 38%;
height: 100%;
margin-right: 2%;
border-radius: 20px;
background: linear-gradient(360deg, #e23d26, #f49c27) !important;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
}
.prize-count {
padding: 4px 0;
position: relative;
}
.prize-count .progress {
height: 1.8vh;
background: rgba(0, 0, 0, 0.5);
padding: 1px;
overflow: visible;
border-radius: 1vh;
}
.progress .progress-bar {
border-radius: 1.8vh;
position: relative;
animation: animate-positive 2s;
background-color: #d9534f;
height: 1.8vh;
-webkit-transition: width 0.6s ease;
-o-transition: width 0.6s ease;
transition: width 0.6s ease;
}
.progress-bar.active {
animation: reverse progress-bar-stripes 0.4s linear infinite,
animate-positive 2s;
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(
45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent
);
background-image: -o-linear-gradient(
45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent
);
background-image: linear-gradient(
45deg,
rgba(255, 255, 255, 0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0.15) 75%,
transparent 75%,
transparent
);
-webkit-background-size: 8px 8px;
background-size: 8px 8px;
}
@-webkit-keyframes animate-positive {
0% {
width: 0;
}
}
@keyframes animate-positive {
0% {
width: 0;
}
}
@-webkit-keyframes progress-bar-stripes {
from {
background-position: 8px 0;
}
to {
background-position: 0 0;
}
}
@-o-keyframes progress-bar-stripes {
from {
background-position: 8px 0;
}
to {
background-position: 0 0;
}
}
@keyframes progress-bar-stripes {
from {
background-position: 8px 0;
}
to {
background-position: 0 0;
}
}
.prize-count-left {
position: absolute;
color: #fff;
right: 35%;
font-size: 1.8vh;
line-height: 1.6vh;
top: 50%;
transform: translateY(-50%);
}
.shine {
background-color: #ffd283;
border: 1px solid rgba(255, 255, 255, 1);
border-radius: 5px;
box-shadow: 0 0 15px 0 #ffd283;
transform: scale(1.1);
transform-origin: left center;
position: relative;
overflow: hidden;
}
.done {
position: relative;
}
.done:after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ffd283;
border: 2px solid rgba(255, 255, 255, 1);
border-radius: 5px;
opacity: 0.3;
cursor: not-allowed;
}
.shine span {
position: absolute;
display: block;
}
.shine span:nth-child(1) {
top: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, #f46303);
animation: animate1 1s linear infinite;
}
@keyframes animate1 {
0% {
left: -100%;
}
50%,
100% {
left: 100%;
}
}
.shine span:nth-child(2) {
top: -100%;
right: 0;
width: 2px;
height: 100%;
background: linear-gradient(180deg, transparent, #f46303);
animation: animate2 1s linear infinite;
animation-delay: 0.25s;
}
@keyframes animate2 {
0% {
top: -100%;
}
50%,
100% {
top: 100%;
}
}
.shine span:nth-child(3) {
bottom: 0;
right: 0;
width: 100%;
height: 2px;
background: linear-gradient(270deg, transparent, #f46303);
animation: animate3 1s linear infinite;
animation-delay: 0.5s;
}
@keyframes animate3 {
0% {
right: -100%;
}
50%,
100% {
right: 100%;
}
}
.shine span:nth-child(4) {
bottom: -100%;
left: 0;
width: 2px;
height: 100%;
background: linear-gradient(360deg, transparent, #f46303);
animation: animate4 1s linear infinite;
animation-delay: 0.75s;
}
@keyframes animate4 {
0% {
bottom: -100%;
}
50%,
100% {
bottom: 100%;
}
}
.shine.prize-item {
/* width: 24vh; */
margin: 1.2vh 0;
}
.prize-mess {
color: #fff;
line-height: 5vh;
font-size: 1.6vh;
margin: 2.4vh 0;
}
.music {
position: fixed;
top: 3vh;
right: 4vh;
z-index: 5;
}
.music-item {
display: block !important;
opacity: 0;
}
.music-box {
width: 5vh;
height: 5vh;
border-radius: 50%;
text-align: center;
line-height: 5vh;
font-size: 1.4vh;
color: #fff;
cursor: pointer;
background-color: rgba(253, 105, 0, 0.9);
border: 1px solid rgba(255, 255, 255, 0.5);
}
.rotate-active {
animation: rotate 4s linear infinite;
}
@keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
.margin-l-40 {
margin-left: 40px;
}
.fixed-bar {
position: fixed;
bottom: 20px;
right: 20px;
}
.fixed-btn {
margin: 20px 0 0;
width: 200px;
text-align: center;
display: block;
}
#lottery {
animation: breath 1.6s linear infinite;
/* box-shadow: 0px 0px 15px rgb(127 255 255 / 75%); */
}
@keyframes breath {
0% {
transform: scale(1);
opacity: 0.8;
}
25% {
transform: scale(1.1);
opacity: 1;
}
50% {
transform: scale(1);
opacity: 1;
}
75% {
transform: scale(0.9);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0.8;
}
}
</style>