|
|
<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="throttledLotteryBtn()" 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 _ from "lodash";
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 = () => { const screenWidth = window.innerWidth; const isLaptop = screenWidth < 1600; // 判断
const baseSpacingX = isLaptop ? 120 : 140; const baseSpacingY = isLaptop ? 150 : 180;
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,
// };
let position = { x: (baseSpacingX * COLUMN_COUNT - 20) / 2, y: (baseSpacingY * ROW_COUNT - 20) / 2, };
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 ); camera.position.z = 3500;
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); //
// 在initCards函数开始处添加
const cardSpacingX = isLaptop ? 130 : 155; const cardSpacingY = isLaptop ? 160 : 195;
var object = new THREE.Object3D(); // object.position.x = j * 155 - position.x;
// object.position.y = -(i * 195) + position.y;
// 使用动态间距
object.position.x = j * cardSpacingX - position.x; object.position.y = -(i * cardSpacingY) + 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 = async () => { joinLottery = true; removeHighlight(); await nextTick(); // 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; };
const removeHighlight = async () => { document.querySelectorAll(".highlight").forEach((node) => { console.log(node.id); node.className = ""; node.classList.add("element"); node.style.backgroundColor = "rgb(255,170,22)"; }); };
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) { throttledStopLottery(); 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 throttledLotteryBtn = _.throttle(lotteryBtn, 1000, { trailing: false, });
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 throttledStopLottery = _.throttle(stopLottery, 1000, { trailing: false, });
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>`; } /** * 切换名牌背景 */ const changeBackground = async (cardIndex) => { let card = threeDCards[cardIndex].element; // card.style.backgroundColor =
// color || "rgba(255,170,22," + (Math.random() * 0.7 + 0.25) + ")";
card.style.backgroundColor = "rgb(255,170,22) !important"; card.style.border = "2px solid rgb(255, 255, 255)";
// await nextTick();
};
/** * 随机切换背景和人员信息 */ 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: 7.5vw; height: 18.5vh; /* 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: 20px; font-weight: bold; cursor: pointer; text-align: center; padding-top: 12px; }
.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>
|