- 新增 src/views/PrizeDraw 抽奖视图及抽奖配置 store - 更新 defaultPersonList 为 82 位真实参与者名单 - 调整主页、路由、i18n 及音乐播放以支持抽奖入口 - 附抽奖需求及实现报告文档 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
833 lines
24 KiB
Vue
833 lines
24 KiB
Vue
<template>
|
||
<div class="prize-draw-page">
|
||
<!-- 星空背景 -->
|
||
<StarsBackground :home-background="homeBackground" />
|
||
|
||
<!-- 顶部标题 -->
|
||
<div class="header-wrapper">
|
||
<h2
|
||
class="page-title"
|
||
:class="{ 'animate-pulse bg-linear-to-r from-primary via-secondary to-accent bg-clip-text text-transparent': !isTextColor }"
|
||
:style="titleStyle"
|
||
>
|
||
{{ topTitle }}
|
||
</h2>
|
||
</div>
|
||
|
||
<!-- 抽奖进度和重置按钮 -->
|
||
<div class="prize-info">
|
||
<div class="progress-text" :style="{ color: textColor }">
|
||
已抽奖:{{ prizeDrawStore.currentDrawIndex }} / 88
|
||
</div>
|
||
<button class="reset-button" @click="handleReset">
|
||
重置抽奖
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 3D容器 -->
|
||
<div id="prize-container" ref="containerRef" class="container-3d" />
|
||
|
||
<!-- 操作按钮 -->
|
||
<OptionButton
|
||
:current-status="currentStatus"
|
||
:table-data="tableData"
|
||
:enter-lottery="enterLottery"
|
||
:start-lottery="startLottery"
|
||
:stop-lottery="stopLottery"
|
||
:continue-lottery="continueLottery"
|
||
:quit-lottery="quitLottery"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { nextTick, onMounted, onUnmounted, ref, computed } from 'vue'
|
||
import { storeToRefs } from 'pinia'
|
||
import { PerspectiveCamera, Scene } from 'three'
|
||
import { CSS3DObject, CSS3DRenderer } from 'three-css3d'
|
||
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
|
||
import * as TWEEN from '@tweenjs/tween.js'
|
||
import useStore from '@/store'
|
||
import { usePrizeDrawStore } from '@/store/prizeDrawConfig'
|
||
import type { DrawResult } from '@/store/prizeDrawConfig'
|
||
import StarsBackground from '@/views/Home/components/StarsBackground/index.vue'
|
||
import OptionButton from '@/views/Home/components/OptionsButton/index.vue'
|
||
import { useElementStyle, useElementPosition } from '@/hooks/useElement'
|
||
import { createTableVertices, createSphereVertices, confettiFire, initTableData, getRandomElements } from '@/views/Home/utils'
|
||
import { rgba, rgbToHex } from '@/utils/color'
|
||
import { selectCard } from '@/utils'
|
||
import { LotteryStatus } from '@/views/Home/type'
|
||
import type { IPersonConfig } from '@/types/storeType'
|
||
|
||
// Store
|
||
const { personConfig, globalConfig, prizeConfig } = useStore()
|
||
const prizeDrawStore = usePrizeDrawStore()
|
||
const {
|
||
getAllPersonList: allPersonList,
|
||
getNotPersonList: notPersonList,
|
||
getNotThisPrizePersonList: notThisPrizePersonList,
|
||
} = storeToRefs(personConfig)
|
||
const { getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
|
||
const {
|
||
getBackground: homeBackground,
|
||
getCardColor: cardColor,
|
||
getPatterColor: patternColor,
|
||
getPatternList: patternList,
|
||
getTextColor: textColor,
|
||
getLuckyColor: luckyColor,
|
||
getCardSize: cardSize,
|
||
getTextSize: textSize,
|
||
getRowCount: rowCount,
|
||
getTitleFont: titleFont,
|
||
getTitleFontSyncGlobal: titleFontSyncGlobal,
|
||
getTopTitle: topTitle,
|
||
} = storeToRefs(globalConfig)
|
||
|
||
// 标题样式计算
|
||
const isTextColor = computed(() => {
|
||
return rgbToHex(textColor.value) !== '#00000000'
|
||
})
|
||
|
||
const titleStyle = computed(() => {
|
||
const style: any = {
|
||
fontSize: `${textSize.value * 1.5}px`,
|
||
}
|
||
if (!titleFontSyncGlobal.value) {
|
||
style.fontFamily = titleFont.value
|
||
}
|
||
if (isTextColor.value) {
|
||
style.color = textColor.value
|
||
}
|
||
return style
|
||
})
|
||
|
||
// Three.js 相关
|
||
const containerRef = ref<HTMLElement>()
|
||
const scene = ref<Scene>()
|
||
const camera = ref<PerspectiveCamera>()
|
||
const renderer = ref<CSS3DRenderer>()
|
||
const controls = ref<TrackballControls>()
|
||
const objects = ref<any[]>([])
|
||
const targets: any = {
|
||
table: [],
|
||
sphere: [],
|
||
}
|
||
const animationFrameId = ref<number>()
|
||
|
||
// 页面状态
|
||
const currentStatus = ref<LotteryStatus>(LotteryStatus.init)
|
||
const canOperate = ref(true)
|
||
const isInitialDone = ref(false)
|
||
const tableData = ref<IPersonConfig[]>([])
|
||
const luckyTargets = ref<IPersonConfig[]>([])
|
||
const luckyCardList = ref<number[]>([])
|
||
const luckyCount = ref(1) // 每次抽1个
|
||
const personPool = ref<IPersonConfig[]>([])
|
||
const intervalTimer = ref<any>(null)
|
||
const currentDrawResult = ref<DrawResult | null>(null) // 保存当前抽奖结果
|
||
|
||
// 初始化 Three.js
|
||
function initThreeJs() {
|
||
console.log('初始化 Three.js 场景...')
|
||
|
||
const width = window.innerWidth
|
||
const height = window.innerHeight
|
||
|
||
scene.value = new Scene()
|
||
camera.value = new PerspectiveCamera(40, width / height, 1, 10000)
|
||
camera.value.position.z = 3000
|
||
|
||
renderer.value = new CSS3DRenderer()
|
||
renderer.value.setSize(width, height * 0.9)
|
||
renderer.value.domElement.style.position = 'absolute'
|
||
renderer.value.domElement.style.paddingTop = '50px'
|
||
renderer.value.domElement.style.top = '50%'
|
||
renderer.value.domElement.style.left = '50%'
|
||
renderer.value.domElement.style.transform = 'translate(-50%, -50%)'
|
||
containerRef.value!.appendChild(renderer.value.domElement)
|
||
|
||
controls.value = new TrackballControls(camera.value, renderer.value.domElement)
|
||
controls.value.minDistance = 500
|
||
controls.value.maxDistance = 6000
|
||
controls.value.addEventListener('change', render)
|
||
|
||
console.log('创建卡片...')
|
||
|
||
// 创建卡片
|
||
for (let i = 0; i < tableData.value.length; i++) {
|
||
const element = document.createElement('div')
|
||
element.className = 'element-card'
|
||
|
||
const number = document.createElement('div')
|
||
number.className = 'card-id'
|
||
number.textContent = tableData.value[i].uid
|
||
element.appendChild(number)
|
||
|
||
const symbol = document.createElement('div')
|
||
symbol.className = 'card-name'
|
||
symbol.textContent = tableData.value[i].name
|
||
element.appendChild(symbol)
|
||
|
||
const details = document.createElement('div')
|
||
details.className = 'card-detail'
|
||
details.innerHTML = `${tableData.value[i].department}<br/>${tableData.value[i].identity}`
|
||
element.appendChild(details)
|
||
|
||
// 第4个子元素:avatar(空div,因为不显示头像)
|
||
const avatarEmpty = document.createElement('div')
|
||
avatarEmpty.style.display = 'none'
|
||
element.appendChild(avatarEmpty)
|
||
|
||
const styledElement = useElementStyle({
|
||
element,
|
||
person: tableData.value[i],
|
||
index: i,
|
||
patternList: patternList.value,
|
||
patternColor: patternColor.value,
|
||
cardColor: cardColor.value,
|
||
cardSize: { width: cardSize.value.width, height: cardSize.value.height },
|
||
scale: 1,
|
||
textSize: textSize.value,
|
||
mod: 'default',
|
||
})
|
||
|
||
const objectCSS = new CSS3DObject(styledElement)
|
||
objectCSS.position.x = Math.random() * 4000 - 2000
|
||
objectCSS.position.y = Math.random() * 4000 - 2000
|
||
objectCSS.position.z = Math.random() * 4000 - 2000
|
||
|
||
scene.value.add(objectCSS)
|
||
objects.value.push(objectCSS)
|
||
}
|
||
|
||
// 创建目标位置
|
||
targets.table = createTableVertices({
|
||
tableData: tableData.value,
|
||
rowCount: rowCount.value,
|
||
cardSize: { width: cardSize.value.width, height: cardSize.value.height },
|
||
})
|
||
|
||
targets.sphere = createSphereVertices({ objectsLength: objects.value.length })
|
||
|
||
window.addEventListener('resize', onWindowResize, false)
|
||
|
||
// 初始聚合到表格
|
||
transform(targets.table, 1000)
|
||
|
||
// 开始动画循环
|
||
animation()
|
||
|
||
isInitialDone.value = true
|
||
console.log('Three.js 初始化完成')
|
||
}
|
||
|
||
function render() {
|
||
if (renderer.value && scene.value && camera.value) {
|
||
renderer.value.render(scene.value, camera.value)
|
||
}
|
||
}
|
||
|
||
function animation() {
|
||
animationFrameId.value = requestAnimationFrame(animation)
|
||
TWEEN.update()
|
||
controls.value?.update()
|
||
}
|
||
|
||
function transform(targetPositions: any[], duration: number) {
|
||
TWEEN.removeAll()
|
||
|
||
for (let i = 0; i < objects.value.length; i++) {
|
||
const object = objects.value[i]
|
||
const target = targetPositions[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)
|
||
.start()
|
||
}
|
||
|
||
function rollBall(rotateY: number, duration: number) {
|
||
TWEEN.removeAll()
|
||
|
||
return new Promise((resolve) => {
|
||
if (!scene.value) {
|
||
resolve('')
|
||
return
|
||
}
|
||
|
||
scene.value.rotation.y = 0
|
||
const ballRotationY = Math.PI * rotateY * 1000
|
||
|
||
new TWEEN.Tween(scene.value.rotation)
|
||
.to({ x: 0, y: ballRotationY, z: 0 }, duration * 1000)
|
||
.onUpdate(render)
|
||
.start()
|
||
.onStop(() => resolve(''))
|
||
.onComplete(() => resolve(''))
|
||
})
|
||
}
|
||
|
||
function onWindowResize() {
|
||
if (!camera.value || !renderer.value)
|
||
return
|
||
|
||
camera.value.aspect = window.innerWidth / window.innerHeight
|
||
camera.value.updateProjectionMatrix()
|
||
renderer.value.setSize(window.innerWidth, window.innerHeight)
|
||
render()
|
||
}
|
||
|
||
// 抽奖流程
|
||
async function enterLottery() {
|
||
if (!canOperate.value)
|
||
return
|
||
|
||
canOperate.value = false
|
||
await transform(targets.sphere, 1000)
|
||
currentStatus.value = LotteryStatus.ready
|
||
canOperate.value = true
|
||
}
|
||
|
||
function startLottery() {
|
||
if (!canOperate.value)
|
||
return
|
||
|
||
console.log('开始抽奖')
|
||
|
||
// 检查是否还有奖品
|
||
if (prizeDrawStore.prizePool.length === 0) {
|
||
console.log('所有奖品已抽完')
|
||
return
|
||
}
|
||
|
||
// 使用 prizeDrawStore 执行抽奖
|
||
prizeDrawStore.executeDraw().then((result: DrawResult | null) => {
|
||
if (!result) {
|
||
console.log('抽奖失败或已完成')
|
||
return
|
||
}
|
||
|
||
console.log('抽奖结果:', result)
|
||
|
||
// 保存抽奖结果
|
||
currentDrawResult.value = result
|
||
|
||
// 随机选择一张卡片来展示中奖奖品
|
||
const randomPerson = tableData.value[Math.floor(Math.random() * tableData.value.length)]
|
||
luckyTargets.value = [randomPerson]
|
||
|
||
console.log('展示卡片:', randomPerson.name, '中奖:', result.prizeName)
|
||
|
||
currentStatus.value = LotteryStatus.running
|
||
rollBall(10, 3000)
|
||
})
|
||
}
|
||
|
||
async function stopLottery() {
|
||
if (!canOperate.value)
|
||
return
|
||
|
||
console.log('停止抽奖,展示结果')
|
||
console.log('中奖结果:', currentDrawResult.value)
|
||
|
||
if (!currentDrawResult.value) {
|
||
console.error('没有抽奖结果')
|
||
return
|
||
}
|
||
|
||
canOperate.value = false
|
||
|
||
// 停止旋转并等待完成
|
||
TWEEN.removeAll()
|
||
|
||
// 立即将场景旋转归零
|
||
if (scene.value) {
|
||
scene.value.rotation.y = 0
|
||
scene.value.rotation.x = 0
|
||
scene.value.rotation.z = 0
|
||
}
|
||
|
||
// 重置相机和控制器到初始位置
|
||
if (camera.value && controls.value) {
|
||
camera.value.position.set(0, 0, 3000)
|
||
camera.value.lookAt(0, 0, 0)
|
||
controls.value.target.set(0, 0, 0)
|
||
controls.value.update()
|
||
}
|
||
|
||
render()
|
||
|
||
// 等待一小段时间确保场景稳定
|
||
await new Promise(resolve => setTimeout(resolve, 100))
|
||
|
||
const windowSize = { width: window.innerWidth, height: window.innerHeight }
|
||
|
||
luckyTargets.value.forEach((person: IPersonConfig, index: number) => {
|
||
const cardIndex = selectCard(luckyCardList.value, tableData.value.length, person.id)
|
||
luckyCardList.value.push(cardIndex)
|
||
|
||
console.log('中奖卡片索引:', cardIndex)
|
||
|
||
const item = objects.value[cardIndex]
|
||
|
||
// 使用更大的 scale 来显示中奖卡片
|
||
const luckyScale = 2.5
|
||
const { xTable, yTable } = useElementPosition(
|
||
item,
|
||
rowCount.value,
|
||
luckyTargets.value.length,
|
||
{ width: cardSize.value.width, height: cardSize.value.height },
|
||
windowSize,
|
||
index,
|
||
)
|
||
|
||
console.log('中奖卡片位置:', { xTable, yTable, scale: luckyScale })
|
||
|
||
new TWEEN.Tween(item.position)
|
||
.to({ x: xTable, y: yTable, z: 1000 }, 1200)
|
||
.easing(TWEEN.Easing.Exponential.InOut)
|
||
.onStart(() => {
|
||
console.log('开始移动中奖卡片,更新为奖品信息')
|
||
|
||
// CSS3DObject 的 element 属性
|
||
const element = (item as any).element
|
||
|
||
if (!element) {
|
||
console.error('element 不存在!')
|
||
return
|
||
}
|
||
|
||
// 更新卡片内容为奖品信息
|
||
const prizeResult = currentDrawResult.value!
|
||
|
||
console.log('更新卡片内容:', prizeResult)
|
||
console.log('element 子元素数量:', element.children.length)
|
||
|
||
// 更新卡片文本
|
||
element.children[0].textContent = '' // 不显示序号
|
||
element.children[1].textContent = prizeResult.prizeName // 奖品名称
|
||
element.children[2].innerHTML = prizeResult.prizeDescription // 奖品描述
|
||
|
||
// 更新样式 - 使用更大的尺寸
|
||
element.style.backgroundColor = rgba(luckyColor.value, 0.9)
|
||
element.style.border = `2px solid ${rgba(luckyColor.value, 0.85)}`
|
||
element.style.boxShadow = `0 0 20px ${rgba(luckyColor.value, 0.8)}`
|
||
element.style.width = `${cardSize.value.width * luckyScale}px`
|
||
element.style.height = `${cardSize.value.height * luckyScale}px`
|
||
element.style.padding = '20px'
|
||
element.style.display = 'flex'
|
||
element.style.flexDirection = 'column'
|
||
element.style.justifyContent = 'center'
|
||
element.style.alignItems = 'center'
|
||
element.style.overflow = 'hidden'
|
||
element.className = 'lucky-element-card'
|
||
|
||
// 隐藏序号
|
||
element.children[0].style.display = 'none'
|
||
|
||
// 判断是否是红包奖项,调整描述文字大小
|
||
const isRedPacket = prizeResult.prizeName.includes('红包')
|
||
const descFontScale = isRedPacket ? 1.0 : 0.5
|
||
|
||
element.children[1].style.fontSize = `${textSize.value * luckyScale * 1.0}px`
|
||
element.children[1].style.lineHeight = '1.4'
|
||
element.children[1].style.fontWeight = 'bold'
|
||
element.children[1].style.color = '#fff'
|
||
element.children[1].style.textShadow = `0 0 15px ${rgba(luckyColor.value, 0.95)}`
|
||
element.children[1].style.marginBottom = '15px'
|
||
element.children[1].style.textAlign = 'center'
|
||
element.children[1].style.wordWrap = 'break-word'
|
||
element.children[1].style.maxWidth = '100%'
|
||
element.children[1].style.whiteSpace = 'normal'
|
||
element.children[1].style.overflow = 'visible'
|
||
element.children[1].style.opacity = '1' // 显示文字
|
||
|
||
element.children[2].style.fontSize = `${textSize.value * luckyScale * descFontScale}px`
|
||
element.children[2].style.color = '#fff'
|
||
element.children[2].style.lineHeight = '1.5'
|
||
element.children[2].style.textAlign = 'center'
|
||
element.children[2].style.wordWrap = 'break-word'
|
||
element.children[2].style.maxWidth = '100%'
|
||
element.children[2].style.overflow = 'hidden'
|
||
element.children[2].style.display = '-webkit-box'
|
||
element.children[2].style.webkitLineClamp = '3'
|
||
element.children[2].style.webkitBoxOrient = 'vertical'
|
||
element.children[2].style.opacity = '1' // 显示文字
|
||
|
||
console.log('卡片内容已更新:', prizeResult.prizeName)
|
||
})
|
||
.onUpdate(render)
|
||
.start()
|
||
.onComplete(() => {
|
||
console.log('中奖卡片移动完成')
|
||
canOperate.value = true
|
||
currentStatus.value = LotteryStatus.end
|
||
})
|
||
|
||
new TWEEN.Tween(item.rotation)
|
||
.to({ x: 0, y: 0, z: 0 }, 900)
|
||
.easing(TWEEN.Easing.Exponential.InOut)
|
||
.onUpdate(render)
|
||
.start()
|
||
.onComplete(() => {
|
||
console.log('开始礼花')
|
||
confettiFire(0, 5)
|
||
})
|
||
})
|
||
}
|
||
|
||
async function continueLottery() {
|
||
// 检查是否还有奖品可抽
|
||
if (prizeDrawStore.prizePool.length === 0) {
|
||
console.log('所有奖品已抽完')
|
||
currentStatus.value = LotteryStatus.init
|
||
await transform(targets.table, 1000)
|
||
return
|
||
}
|
||
|
||
console.log('继续抽奖,重置卡片')
|
||
|
||
// 恢复中奖卡片的原始样式
|
||
luckyCardList.value.forEach((cardIndex) => {
|
||
const item = objects.value[cardIndex]
|
||
const element = (item as any).element
|
||
|
||
if (element) {
|
||
const person = tableData.value[cardIndex]
|
||
|
||
// 恢复原始内容
|
||
element.children[0].textContent = person.uid
|
||
element.children[1].textContent = person.name
|
||
element.children[2].innerHTML = `${person.department}<br/>${person.identity}`
|
||
|
||
// 恢复原始样式
|
||
element.style.backgroundColor = rgba(cardColor.value, 0.8)
|
||
element.style.border = `1px solid ${rgba(cardColor.value, 0.75)}`
|
||
element.style.boxShadow = ''
|
||
element.style.width = `${cardSize.value.width}px`
|
||
element.style.height = `${cardSize.value.height}px`
|
||
element.style.padding = ''
|
||
element.style.display = ''
|
||
element.style.flexDirection = ''
|
||
element.style.justifyContent = ''
|
||
element.style.alignItems = ''
|
||
element.style.overflow = ''
|
||
element.className = 'element-card'
|
||
|
||
// 恢复序号显示
|
||
element.children[0].style.display = ''
|
||
|
||
// 恢复文字样式
|
||
element.children[0].style.fontSize = `${textSize.value * 0.5}px`
|
||
element.children[0].style.fontWeight = ''
|
||
element.children[0].style.color = textColor.value
|
||
element.children[0].style.marginBottom = ''
|
||
element.children[0].style.whiteSpace = ''
|
||
element.children[0].style.overflow = ''
|
||
element.children[0].style.textOverflow = ''
|
||
|
||
element.children[1].style.fontSize = `${textSize.value}px`
|
||
element.children[1].style.lineHeight = `${textSize.value * 3}px`
|
||
element.children[1].style.fontWeight = ''
|
||
element.children[1].style.color = textColor.value
|
||
element.children[1].style.textShadow = ''
|
||
element.children[1].style.marginBottom = ''
|
||
element.children[1].style.textAlign = ''
|
||
element.children[1].style.wordWrap = ''
|
||
element.children[1].style.maxWidth = ''
|
||
element.children[1].style.whiteSpace = ''
|
||
element.children[1].style.overflow = ''
|
||
|
||
element.children[2].style.fontSize = `${textSize.value * 0.5}px`
|
||
element.children[2].style.color = textColor.value
|
||
element.children[2].style.lineHeight = ''
|
||
element.children[2].style.textAlign = ''
|
||
element.children[2].style.wordWrap = ''
|
||
element.children[2].style.maxWidth = ''
|
||
element.children[2].style.overflow = ''
|
||
element.children[2].style.display = ''
|
||
element.children[2].style.webkitLineClamp = ''
|
||
element.children[2].style.webkitBoxOrient = ''
|
||
}
|
||
})
|
||
|
||
// 重置状态
|
||
luckyTargets.value = []
|
||
luckyCardList.value = []
|
||
currentDrawResult.value = null
|
||
|
||
// 先将所有卡片移回表格位置,确保整齐
|
||
await transform(targets.table, 800)
|
||
|
||
// 等待一小段时间
|
||
await new Promise(resolve => setTimeout(resolve, 200))
|
||
|
||
// 再将所有卡片移到球体
|
||
await transform(targets.sphere, 1000)
|
||
|
||
// 等待球体动画完全完成
|
||
await new Promise(resolve => setTimeout(resolve, 1200))
|
||
|
||
currentStatus.value = LotteryStatus.ready
|
||
|
||
// 自动开始下一轮抽奖
|
||
setTimeout(() => {
|
||
startLottery()
|
||
}, 300)
|
||
}
|
||
|
||
async function quitLottery() {
|
||
console.log('取消抽奖,恢复卡片')
|
||
|
||
// 恢复中奖卡片的原始样式
|
||
luckyCardList.value.forEach((cardIndex) => {
|
||
const item = objects.value[cardIndex]
|
||
const element = (item as any).element
|
||
|
||
if (element) {
|
||
const person = tableData.value[cardIndex]
|
||
|
||
// 恢复原始内容
|
||
element.children[0].textContent = person.uid
|
||
element.children[1].textContent = person.name
|
||
element.children[2].innerHTML = `${person.department}<br/>${person.identity}`
|
||
|
||
// 恢复原始样式
|
||
element.style.backgroundColor = rgba(cardColor.value, 0.8)
|
||
element.style.border = `1px solid ${rgba(cardColor.value, 0.75)}`
|
||
element.style.boxShadow = ''
|
||
element.style.width = `${cardSize.value.width}px`
|
||
element.style.height = `${cardSize.value.height}px`
|
||
element.style.padding = ''
|
||
element.style.display = ''
|
||
element.style.flexDirection = ''
|
||
element.style.justifyContent = ''
|
||
element.style.alignItems = ''
|
||
element.style.overflow = ''
|
||
element.className = 'element-card'
|
||
|
||
// 恢复序号显示
|
||
element.children[0].style.display = ''
|
||
|
||
// 恢复文字样式并隐藏文字
|
||
element.children[0].style.fontSize = `${textSize.value * 0.5}px`
|
||
element.children[0].style.fontWeight = ''
|
||
element.children[0].style.color = textColor.value
|
||
element.children[0].style.marginBottom = ''
|
||
element.children[0].style.whiteSpace = ''
|
||
element.children[0].style.overflow = ''
|
||
element.children[0].style.textOverflow = ''
|
||
element.children[0].style.opacity = '0' // 隐藏文字
|
||
|
||
element.children[1].style.fontSize = `${textSize.value}px`
|
||
element.children[1].style.lineHeight = `${textSize.value * 3}px`
|
||
element.children[1].style.fontWeight = ''
|
||
element.children[1].style.color = textColor.value
|
||
element.children[1].style.textShadow = ''
|
||
element.children[1].style.marginBottom = ''
|
||
element.children[1].style.textAlign = ''
|
||
element.children[1].style.wordWrap = ''
|
||
element.children[1].style.maxWidth = ''
|
||
element.children[1].style.whiteSpace = ''
|
||
element.children[1].style.overflow = ''
|
||
element.children[1].style.opacity = '0' // 隐藏文字
|
||
|
||
element.children[2].style.fontSize = `${textSize.value * 0.5}px`
|
||
element.children[2].style.color = textColor.value
|
||
element.children[2].style.lineHeight = ''
|
||
element.children[2].style.textAlign = ''
|
||
element.children[2].style.wordWrap = ''
|
||
element.children[2].style.maxWidth = ''
|
||
element.children[2].style.overflow = ''
|
||
element.children[2].style.display = ''
|
||
element.children[2].style.webkitLineClamp = ''
|
||
element.children[2].style.webkitBoxOrient = ''
|
||
element.children[2].style.opacity = '0' // 隐藏文字
|
||
}
|
||
})
|
||
|
||
// 重置状态
|
||
luckyTargets.value = []
|
||
luckyCardList.value = []
|
||
currentDrawResult.value = null
|
||
|
||
await transform(targets.table, 1000)
|
||
currentStatus.value = LotteryStatus.init
|
||
}
|
||
|
||
function setDefaultPersonList() {
|
||
personConfig.setDefaultPersonList()
|
||
window.location.reload()
|
||
}
|
||
|
||
function handleReset() {
|
||
// 弹出确认对话框
|
||
const confirmed = window.confirm('确定要重置抽奖吗?这将清除所有抽奖记录并重新开始。')
|
||
|
||
if (confirmed) {
|
||
console.log('重置抽奖系统')
|
||
|
||
// 重置 prizeDrawStore
|
||
prizeDrawStore.reset()
|
||
|
||
// 重新加载页面
|
||
window.location.reload()
|
||
}
|
||
}
|
||
|
||
function cleanup() {
|
||
TWEEN.removeAll()
|
||
|
||
if (animationFrameId.value) {
|
||
cancelAnimationFrame(animationFrameId.value)
|
||
}
|
||
|
||
if (intervalTimer.value) {
|
||
clearInterval(intervalTimer.value)
|
||
}
|
||
|
||
if (scene.value) {
|
||
scene.value.clear()
|
||
}
|
||
|
||
if (controls.value) {
|
||
controls.value.removeEventListener('change', render)
|
||
controls.value.dispose()
|
||
}
|
||
|
||
window.removeEventListener('resize', onWindowResize)
|
||
}
|
||
|
||
const init = () => {
|
||
console.log('初始化页面')
|
||
|
||
// 初始化 prizeDrawStore
|
||
if (!prizeDrawStore.isInitialized) {
|
||
prizeDrawStore.init()
|
||
}
|
||
|
||
// 使用 initTableData 来正确设置 x, y 坐标
|
||
tableData.value = initTableData({
|
||
allPersonList: prizeDrawStore.personPool,
|
||
rowCount: rowCount.value,
|
||
})
|
||
|
||
console.log('卡片数据:', tableData.value.length)
|
||
console.log('第一张卡片坐标:', tableData.value[0]?.x, tableData.value[0]?.y)
|
||
|
||
initThreeJs()
|
||
containerRef.value!.style.color = textColor.value
|
||
isInitialDone.value = true
|
||
}
|
||
|
||
onMounted(() => {
|
||
init()
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
nextTick(() => {
|
||
cleanup()
|
||
})
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.prize-draw-page {
|
||
width: 100%;
|
||
height: 100vh;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.container-3d {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.header-wrapper {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 100;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.page-title {
|
||
padding-top: 48px;
|
||
margin: 0;
|
||
margin-bottom: 48px;
|
||
letter-spacing: 0.05em;
|
||
text-align: center;
|
||
line-height: 3rem;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.prize-info {
|
||
position: absolute;
|
||
top: 100px;
|
||
right: 40px;
|
||
z-index: 100;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||
padding: 10px 20px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 8px;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.reset-button {
|
||
padding: 12px 24px;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
|
||
}
|
||
|
||
.reset-button:hover {
|
||
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
||
box-shadow: 0 6px 16px rgba(239, 68, 68, 0.6);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.reset-button:active {
|
||
transform: translateY(0);
|
||
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.4);
|
||
}
|
||
</style>
|