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.
886 lines
23 KiB
886 lines
23 KiB
<template>
|
|
<div class="lottery-3d-container">
|
|
<div ref="threeContainer" class="three-container"></div>
|
|
|
|
<!-- 分页指示器 -->
|
|
<div v-if="totalPages > 1" class="page-indicator">
|
|
第 {{ currentPage + 1 }} 页 / 共 {{ totalPages }} 页
|
|
</div>
|
|
|
|
<!-- 滚动提示 -->
|
|
<!-- <div v-if="totalPages > 1 && currentPage === 0" class="scroll-hint">
|
|
向下滚动查看更多
|
|
</div>
|
|
<div v-if="totalPages > 1 && currentPage === totalPages - 1" class="scroll-hint">
|
|
向上滚动查看上一页
|
|
</div> -->
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
ref,
|
|
onMounted,
|
|
onBeforeUnmount,
|
|
defineExpose,
|
|
watch,
|
|
computed,
|
|
} from "vue";
|
|
import * as THREE from "three";
|
|
import {
|
|
CSS3DRenderer,
|
|
CSS3DObject,
|
|
} from "three/examples/jsm/renderers/CSS3DRenderer.js";
|
|
// import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'; // 移除拖拽控件
|
|
import TWEEN from "@tweenjs/tween.js";
|
|
import { NUMBER_MATRIX } from "../../../utils/config.js";
|
|
import CardItem from "./CardItem.vue";
|
|
import { createApp } from "vue";
|
|
import { getUserListApi } from "../../../api/API";
|
|
import { useLotteryStore } from "../../../store/lottery";
|
|
const lotteryStore = useLotteryStore();
|
|
|
|
const winners = computed({
|
|
get: () => lotteryStore.winners,
|
|
set: (val) => lotteryStore.setWinners(val),
|
|
});
|
|
|
|
const threeContainer = ref(null);
|
|
let renderer, scene, camera, animationId;
|
|
// let controls; // 移除controls
|
|
|
|
// 全局变量存储当前选中的卡片索引数组
|
|
let globalCardIndexes = [];
|
|
|
|
// 分页相关变量
|
|
const currentPage = ref(0);
|
|
const totalPages = ref(0);
|
|
const cardsPerPage = 10; // 每页显示的卡片数量
|
|
let isPageTransitioning = false; // 防止页面切换时的重复操作
|
|
|
|
// 3D卡片与目标
|
|
const threeDCards = [];
|
|
const targets = {
|
|
table: [],
|
|
sphere: [],
|
|
};
|
|
function swapCardContents() {
|
|
// 确保有足够卡片且不在抽奖状态
|
|
if (threeDCards.length < 2 || globalCardIndexes.length > 0) return;
|
|
|
|
// 随机选择两张不同的卡片
|
|
let indexA = Math.floor(Math.random() * threeDCards.length);
|
|
let indexB = Math.floor(Math.random() * threeDCards.length);
|
|
while (indexA === indexB) {
|
|
indexB = Math.floor(Math.random() * threeDCards.length);
|
|
}
|
|
|
|
const cardA = threeDCards[indexA].element;
|
|
const cardB = threeDCards[indexB].element;
|
|
|
|
// 保存原始内容(如果尚未保存)
|
|
if (!cardA.dataset.originalContent) {
|
|
cardA.dataset.originalContent = cardA.innerHTML;
|
|
}
|
|
if (!cardB.dataset.originalContent) {
|
|
cardB.dataset.originalContent = cardB.innerHTML;
|
|
}
|
|
|
|
// 交换内容并添加动画效果
|
|
[cardA.innerHTML, cardB.innerHTML] = [cardB.innerHTML, cardA.innerHTML];
|
|
cardA.classList.add('swap-animation');
|
|
cardB.classList.add('swap-animation');
|
|
|
|
// 动画结束后移除动画类
|
|
setTimeout(() => {
|
|
cardA.classList.remove('swap-animation');
|
|
cardB.classList.remove('swap-animation');
|
|
}, 500);
|
|
}
|
|
function createElement(css = "", text = "") {
|
|
const dom = document.createElement("div");
|
|
dom.className = css;
|
|
dom.innerHTML = text;
|
|
return dom;
|
|
}
|
|
|
|
function createCard(user, isBold, id, showTable, company) {
|
|
// 使用 CardItem 组件动态渲染为 DOM 节点
|
|
const container = document.createElement("div");
|
|
const app = createApp(CardItem, {
|
|
id,
|
|
user,
|
|
isBold,
|
|
showTable,
|
|
company,
|
|
// highlight, prize 可后续补充
|
|
});
|
|
app.mount(container);
|
|
return container.firstElementChild;
|
|
}
|
|
|
|
function createCards(member, length, showTable, position, config) {
|
|
let index = 0;
|
|
for (let i = 0; i < config.ROW_COUNT; i++) {
|
|
for (let j = 0; j < config.COLUMN_COUNT; j++) {
|
|
// 4. 判断是否高亮
|
|
const isBold = (config.HIGHLIGHT_CELL || []).includes(j + "-" + i);
|
|
const element = createCard(
|
|
member[index % length],
|
|
isBold,
|
|
index,
|
|
showTable,
|
|
config.COMPANY
|
|
);
|
|
const 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);
|
|
const targetObject = new THREE.Object3D();
|
|
targetObject.position.x = j * 140 - position.x;
|
|
targetObject.position.y = -(i * 180) + position.y;
|
|
targets.table.push(targetObject);
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createSphereTargets() {
|
|
const vector = new THREE.Vector3();
|
|
for (let i = 0, l = threeDCards.length; i < l; i++) {
|
|
const phi = Math.acos(-1 + (2 * i) / l);
|
|
const theta = Math.sqrt(l * Math.PI) * phi;
|
|
const object = new THREE.Object3D();
|
|
object.position.setFromSphericalCoords(600, phi, theta);
|
|
object.position.y -= 200; // 向下偏移200px
|
|
|
|
// 修正朝向计算:让卡牌朝向球体中心点
|
|
vector.set(0, -200, 0); // 球体中心点,Y轴偏移与上面保持一致
|
|
object.lookAt(vector);
|
|
targets.sphere.push(object);
|
|
}
|
|
}
|
|
|
|
// 动画与切换相关方法
|
|
function switchScreen(type) {
|
|
if (highlightTimeout) {
|
|
clearTimeout(highlightTimeout);
|
|
highlightTimeout = null;
|
|
}
|
|
// 示例:enter/table/sphere 切换
|
|
if (type === "enter") {
|
|
transform(targets.table, 2000, () => {
|
|
addHighlight();
|
|
highlightTimeout = null;
|
|
}); // 动画结束后加高亮
|
|
} else {
|
|
transform(targets.sphere, 2000, () => removeHighlight()); // 动画结束后移除高亮
|
|
}
|
|
}
|
|
|
|
function transform(targetsArr, duration, onComplete) {
|
|
for (let i = 0; i < threeDCards.length; i++) {
|
|
const object = threeDCards[i];
|
|
const target = targetsArr[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({})
|
|
.to({}, duration * 2)
|
|
.onUpdate(() => render())
|
|
.onComplete(() => {
|
|
if (onComplete) onComplete();
|
|
})
|
|
.start();
|
|
}
|
|
|
|
function selectCard(selectedCardIndex, currentLuckys, duration = 600) {
|
|
if (highlightTimeout) {
|
|
clearTimeout(highlightTimeout);
|
|
highlightTimeout = null;
|
|
}
|
|
removeHighlight(); // 开始抽奖前移除高亮
|
|
console.log("selectCard called:", {
|
|
selectedCardIndex,
|
|
currentLuckys,
|
|
duration,
|
|
});
|
|
return new Promise((resolve) => {
|
|
const width = 140;
|
|
|
|
// 计算总页数
|
|
totalPages.value = Math.ceil(currentLuckys.length / cardsPerPage);
|
|
currentPage.value = 0;
|
|
|
|
// 为每页计算位置信息
|
|
const pageLocates = [];
|
|
|
|
for (let page = 0; page < totalPages.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 = [-87, 87];
|
|
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) => {
|
|
changeCard(cardIndex, currentLuckys[index]);
|
|
const object = threeDCards[cardIndex];
|
|
|
|
// 计算卡片应该在第几页
|
|
const cardPage = Math.floor(index / cardsPerPage);
|
|
const isVisible = cardPage === 0;
|
|
|
|
// 计算在当前页中的索引
|
|
const pageIndex = index % cardsPerPage;
|
|
const pageLocate = pageLocates[cardPage][pageIndex];
|
|
|
|
// 设置初始位置:第一页的卡片正常显示,其他页的卡片从下方隐藏
|
|
let initialY;
|
|
if (isVisible) {
|
|
initialY = pageLocate.y;
|
|
} else {
|
|
// 非第一页的卡片从下方隐藏(为后续向上飞出做准备)
|
|
initialY = pageLocate.y - 1000;
|
|
}
|
|
|
|
new TWEEN.Tween(object.position)
|
|
.to(
|
|
{
|
|
x: pageLocate.x,
|
|
y: initialY,
|
|
z: 2200,
|
|
},
|
|
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 (totalPages.value > 1) {
|
|
addWheelListener();
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
// 分页切换函数
|
|
function switchPage(direction) {
|
|
if (isPageTransitioning || totalPages.value <= 1) return;
|
|
|
|
const newPage =
|
|
direction === "next" ? currentPage.value + 1 : currentPage.value - 1;
|
|
if (newPage < 0 || newPage >= totalPages.value) return;
|
|
|
|
isPageTransitioning = true;
|
|
const duration = 800;
|
|
|
|
// 使用保存的页面位置信息
|
|
const pageLocates = window.pageLocates;
|
|
if (!pageLocates) {
|
|
console.error("页面位置信息未找到");
|
|
isPageTransitioning = false;
|
|
return;
|
|
}
|
|
console.log("globalCardIndexes", globalCardIndexes);
|
|
// 动画切换卡片位置
|
|
globalCardIndexes.forEach((cardIndex, index) => {
|
|
const object = threeDCards[cardIndex];
|
|
const cardPage = Math.floor(index / cardsPerPage);
|
|
const isVisible = cardPage === newPage;
|
|
const wasVisible = cardPage === currentPage.value;
|
|
|
|
// 计算在当前页中的索引
|
|
const pageIndex = index % cardsPerPage;
|
|
const pageLocate = pageLocates[cardPage][pageIndex];
|
|
|
|
// 根据切换方向决定动画效果
|
|
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: 2200,
|
|
},
|
|
duration
|
|
)
|
|
.easing(TWEEN.Easing.Cubic.InOut)
|
|
.start();
|
|
});
|
|
|
|
new TWEEN.Tween({})
|
|
.to({}, duration)
|
|
.onUpdate(() => render())
|
|
.onComplete(() => {
|
|
currentPage.value = newPage;
|
|
isPageTransitioning = false;
|
|
console.log(
|
|
`切换到第 ${currentPage.value + 1} 页,共 ${totalPages.value} 页`
|
|
);
|
|
})
|
|
.start();
|
|
}
|
|
|
|
// 鼠标滚轮事件处理
|
|
function handleWheel(event) {
|
|
if (isPageTransitioning || totalPages.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(selectedCardIndex, duration = 500) {
|
|
if (!selectedCardIndex || selectedCardIndex.length === 0) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
// 移除鼠标滚轮事件监听器
|
|
removeWheelListener();
|
|
|
|
// 重置分页状态
|
|
currentPage.value = 0;
|
|
totalPages.value = 0;
|
|
isPageTransitioning = false;
|
|
|
|
// 清理保存的页面位置信息
|
|
if (window.pageLocates) {
|
|
delete window.pageLocates;
|
|
}
|
|
|
|
// 清空全局卡片索引数组
|
|
globalCardIndexes = [];
|
|
|
|
selectedCardIndex.forEach((index) => {
|
|
const object = threeDCards[index];
|
|
const 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) => {
|
|
new TWEEN.Tween({})
|
|
.to({}, duration * 2)
|
|
.onUpdate(() => render())
|
|
.start()
|
|
.onComplete(() => {
|
|
selectedCardIndex.forEach((index) => {
|
|
const object = threeDCards[index];
|
|
// 恢复原始内容
|
|
if (object.element.dataset.originalContent) {
|
|
object.element.innerHTML = object.element.dataset.originalContent;
|
|
delete object.element.dataset.originalContent;
|
|
}
|
|
object.element.classList.remove("prize");
|
|
});
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
function changeCard(cardIndex, user) {
|
|
// 保存到全局变量数组
|
|
if (!globalCardIndexes.includes(cardIndex)) {
|
|
globalCardIndexes.push(cardIndex);
|
|
}
|
|
|
|
const card = threeDCards[cardIndex].element;
|
|
|
|
// 保存原始内容,以便后续恢复
|
|
if (!card.dataset.originalContent) {
|
|
card.dataset.originalContent = card.innerHTML;
|
|
}
|
|
|
|
// 设置中奖内容 - 适配后端返回的数据格式
|
|
// 后端返回的数据格式: { jwcode: "5412", username: "猪八戒22" }
|
|
const jwcode = user.jwcode || user[0] || "";
|
|
const username = user.username || user[1] || "";
|
|
const company = user.company || user[2] || "PSST";
|
|
|
|
card.innerHTML = `<div style="font-size: 20px; font-weight: bold; color: #ffffff; text-align: center; display: flex; justify-content: center; align-items: center; width: 100%; height: 100%;">${jwcode}</div>`;
|
|
|
|
// 添加中奖样式类
|
|
card.classList.add("prize");
|
|
}
|
|
|
|
function changeCard1() {
|
|
console.log("执行取消高光,当前卡片索引:", globalCardIndexes);
|
|
|
|
// 移除鼠标滚轮事件监听器
|
|
removeWheelListener();
|
|
|
|
// 重置分页状态
|
|
currentPage.value = 0;
|
|
totalPages.value = 0;
|
|
isPageTransitioning = false;
|
|
|
|
// 清理保存的页面位置信息
|
|
if (window.pageLocates) {
|
|
delete window.pageLocates;
|
|
}
|
|
|
|
globalCardIndexes.forEach((cardIndex) => {
|
|
const card = threeDCards[cardIndex].element;
|
|
// console.log('取消卡片', cardIndex, '的高光');
|
|
|
|
// 恢复原始内容
|
|
if (card.dataset.originalContent) {
|
|
card.innerHTML = card.dataset.originalContent;
|
|
delete card.dataset.originalContent;
|
|
}
|
|
|
|
// 移除prize类,让CardItem组件的样式重新生效
|
|
card.classList.remove("prize");
|
|
});
|
|
// 清空数组
|
|
globalCardIndexes = [];
|
|
}
|
|
|
|
function shine(cardIndex, color) {
|
|
const card = threeDCards[cardIndex].element;
|
|
card.style.backgroundColor =
|
|
color || `rgba(0,127,127,${Math.random() * 0.7 + 0.25})`;
|
|
}
|
|
|
|
// 响应式高亮索引
|
|
const highlightedIndexes = ref([]);
|
|
|
|
// 替换 addHighlight 和 removeHighlight 为响应式写法
|
|
function addHighlight(indexes = null) {
|
|
if (indexes) {
|
|
highlightedIndexes.value = [...indexes];
|
|
} else {
|
|
// 默认高亮所有 .lightitem
|
|
highlightedIndexes.value = threeDCards
|
|
// .map((obj, idx) =>
|
|
// obj.element.classList.contains("lightitem") ? idx : null
|
|
// )
|
|
.filter((idx) => idx !== null);
|
|
}
|
|
}
|
|
|
|
function removeHighlight(indexes = null) {
|
|
if (indexes) {
|
|
highlightedIndexes.value = highlightedIndexes.value.filter(
|
|
(i) => !indexes.includes(i)
|
|
);
|
|
} else {
|
|
highlightedIndexes.value = [];
|
|
}
|
|
}
|
|
|
|
// 监听高亮索引变化并同步到DOM
|
|
watch(highlightedIndexes, (newVal) => {
|
|
threeDCards.forEach((cardObj, idx) => {
|
|
if (newVal.includes(idx)) {
|
|
cardObj.element.classList.add("highlight");
|
|
} else {
|
|
cardObj.element.classList.remove("highlight");
|
|
}
|
|
});
|
|
});
|
|
|
|
let rotateObj = null;
|
|
let highlightTimeout = null;
|
|
|
|
function rotateBallStart() {
|
|
return new Promise((resolve) => {
|
|
if (!scene) return resolve();
|
|
scene.rotation.y = 0;
|
|
rotateObj = new TWEEN.Tween(scene.rotation)
|
|
.to({ y: Math.PI * 6 * 1000 }, 3000 * 1000)
|
|
.onUpdate(() => render())
|
|
.onComplete(() => resolve())
|
|
.start();
|
|
});
|
|
}
|
|
|
|
function rotateBallStop() {
|
|
return new Promise((resolve) => {
|
|
if (!scene || !rotateObj) return resolve();
|
|
rotateObj.stop();
|
|
// 完全还原原生补偿动画逻辑
|
|
const currentY = scene.rotation.y;
|
|
const targetY = Math.ceil(currentY / (2 * Math.PI)) * 2 * Math.PI;
|
|
const deltaY = Math.abs(targetY - currentY);
|
|
const duration = 500 + 1000 * (deltaY / Math.PI);
|
|
new TWEEN.Tween(scene.rotation)
|
|
.to({ y: targetY }, duration)
|
|
.easing(TWEEN.Easing.Cubic.Out)
|
|
.onUpdate(() => render())
|
|
.onComplete(() => {
|
|
scene.rotation.y = 0;
|
|
render();
|
|
resolve();
|
|
})
|
|
.start();
|
|
});
|
|
}
|
|
|
|
function getTotalCards() {
|
|
return threeDCards.length;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
// 初始化 3D 场景
|
|
scene = new THREE.Scene();
|
|
camera = new THREE.PerspectiveCamera(
|
|
40,
|
|
window.innerWidth / window.innerHeight,
|
|
1,
|
|
10000
|
|
);
|
|
camera.position.z = 3000;
|
|
// camera.position.y = 250; // 整体上移10px
|
|
// 或
|
|
// scene.position.y = 10; // 整体上移10px
|
|
renderer = new CSS3DRenderer();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
if (threeContainer.value) {
|
|
threeContainer.value.appendChild(renderer.domElement);
|
|
}
|
|
|
|
// 2. 生成高亮坐标(以数字8为例)
|
|
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;
|
|
});
|
|
// const highlightCells = NUMBER_MATRIX["0"].map(([x, y]) => `${x}-${y}`);
|
|
const config = {
|
|
ROW_COUNT: 7, // 数字矩阵是5行
|
|
COLUMN_COUNT: 21, // 数字矩阵是4列
|
|
HIGHLIGHT_CELL: highlight,
|
|
COMPANY: "演示公司",
|
|
};
|
|
|
|
const userList = await getUserListApi();
|
|
console.log("3D调用一次接口", userList);
|
|
// lotteryStore.setWinners(userList);
|
|
// console.log("userList", userList);
|
|
// 将用户数据转换为兼容格式,用于3D卡片显示
|
|
const member = userList.data.map((item) => [
|
|
item.jwcode,
|
|
item.username,
|
|
"PSST",
|
|
]);
|
|
|
|
// 将用户列表存储到store中,用于卡牌文字切换
|
|
const userNames = userList.data.map(
|
|
(item) => item.jwcode || item.username || ""
|
|
);
|
|
lotteryStore.setAllUsers(userNames);
|
|
|
|
const length = member.length;
|
|
const showTable = true;
|
|
const position = {
|
|
x: (100 * config.COLUMN_COUNT - 20) / 2,
|
|
y: (120 * config.ROW_COUNT - 20) / 2,
|
|
};
|
|
createCards(member, length, showTable, position, config); // 3. 传递高亮配置
|
|
createSphereTargets();
|
|
|
|
// 先渲染散落状态
|
|
render();
|
|
animate();
|
|
|
|
// 延迟后自动聚集到界面中间
|
|
setTimeout(() => {
|
|
switchScreen("enter");
|
|
}, 500);
|
|
|
|
window.addEventListener("resize", onWindowResize);
|
|
// swapInterval.value = setInterval(swapCardContents, swapIntervalTime.value);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener("resize", onWindowResize);
|
|
removeWheelListener(); // 移除鼠标滚轮事件监听器
|
|
if (animationId) cancelAnimationFrame(animationId);
|
|
if (highlightTimeout) {
|
|
clearTimeout(highlightTimeout);
|
|
highlightTimeout = null;
|
|
}
|
|
// if (swapInterval.value) {
|
|
// clearInterval(swapInterval.value);
|
|
// swapInterval.value = null;
|
|
// }
|
|
});
|
|
|
|
function render() {
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
function animate() {
|
|
animationId = requestAnimationFrame(animate);
|
|
TWEEN.update();
|
|
// controls.update(); // 移除controls
|
|
render();
|
|
}
|
|
|
|
function onWindowResize() {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
render();
|
|
}
|
|
|
|
defineExpose({
|
|
resetCard,
|
|
addHighlight,
|
|
switchScreen,
|
|
rotateBallStart,
|
|
rotateBallStop,
|
|
selectCard,
|
|
getTotalCards,
|
|
changeCard1,
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.lottery-3d-container {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
.three-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* 分页指示器样式 */
|
|
.page-indicator {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
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;
|
|
}
|
|
|
|
/* 滚动提示样式 */
|
|
.scroll-hint {
|
|
position: absolute;
|
|
bottom: 60px;
|
|
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: 12px;
|
|
pointer-events: none;
|
|
animation: fadeInOut 2s ease-in-out infinite;
|
|
}
|
|
|
|
.price-font {
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
color: #ffffff;
|
|
text-align: center;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
@keyframes fadeInOut {
|
|
0%,
|
|
100% {
|
|
opacity: 0.6;
|
|
}
|
|
50% {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
</style>
|