Browse Source

样式适配+逻辑修复

songtongtong/feature-20250717104937-众筹
Ethereal 4 weeks ago
parent
commit
316876ce8d
  1. 98
      src/views/choujiang/index.vue
  2. 53
      src/views/choujiang/lottery/CardItem.vue
  3. 55
      src/views/choujiang/lottery/ControlBar.vue
  4. 335
      src/views/choujiang/lottery/Lottery3D.vue
  5. 141
      src/views/choujiang/lottery/PrizePanel.vue
  6. 217
      src/views/choujiang/lottery/dataManager.js
  7. 129
      src/views/choujiang/lottery/lotteryEngine.js

98
src/views/choujiang/index.vue

@ -12,6 +12,14 @@
@export="handleExport" @export="handleExport"
/> />
<MusicPlayer /> <MusicPlayer />
<!-- 透明弹窗 -->
<div v-if="showPrizeExhaustedModal" class="prize-exhausted-modal">
<div class="modal-content">
<p class="modal-text">该礼品已抽取完毕请揭秘下一个礼品</p>
</div>
</div>
<!-- <UserList <!-- <UserList
:lucky-users=" :lucky-users="
dataManager.state.basicData.luckyUsers[ dataManager.state.basicData.luckyUsers[
@ -39,6 +47,7 @@ import { useLotteryStore } from "../../store/lottery"; // 路径根据实际情
const qipaoText = ref(""); const qipaoText = ref("");
const showQipao = ref(false); const showQipao = ref(false);
const showPrizeExhaustedModal = ref(false);
// const lotteryState = ref('idle'); // idle, ready, rotating, result // const lotteryState = ref('idle'); // idle, ready, rotating, result
@ -49,6 +58,17 @@ const lotteryState = computed({
set: (val) => lotteryStore.setLotteryState(val), set: (val) => lotteryStore.setLotteryState(val),
}); });
const lastRevealed = computed({
get: () => lotteryStore.lastRevealedIdx,
set: (val) => lotteryStore.setLastRevealedIdx(val),
});
const waitingForNextReveal = computed({
get: () => lotteryStore.waitingForNextReveal,
set: (val) => lotteryStore.setWaitingForNextReveal(val),
});
const isDisabled = ref(false); const isDisabled = ref(false);
watch(isDisabled, (newVal, oldVal) => { watch(isDisabled, (newVal, oldVal) => {
@ -70,13 +90,17 @@ const lotteryEngine = useLotteryEngine(dataManager, {
onMounted(async () => { onMounted(async () => {
await dataManager.getBasicData(); await dataManager.getBasicData();
await dataManager.getUsers(); await dataManager.getUsers();
// dataManager window 使
window.dataManager = dataManager;
}); });
function showLotteryQipao() { function showLotteryQipao() {
const luckys = dataManager.state.currentLuckys; const luckys = dataManager.state.currentLuckys;
const prize = dataManager.state.currentPrize; const prize = dataManager.state.currentPrize;
if (!luckys || luckys.length === 0) return; if (!luckys || luckys.length === 0) return;
const names = luckys.map((item) => item[1]).join("、");
// jwcode username
const names = luckys.map((item) => item.username || item[1] || item.jwcode || "").join("、");
qipaoText.value = `恭喜${names}获得${prize?.title || ""}`; qipaoText.value = `恭喜${names}获得${prize?.title || ""}`;
showQipao.value = true; showQipao.value = true;
setTimeout(() => { setTimeout(() => {
@ -100,6 +124,24 @@ async function handleLotteryClick() {
await lottery3DRef.value?.switchScreen?.("lottery"); await lottery3DRef.value?.switchScreen?.("lottery");
break; break;
case "ready": case "ready":
if(waitingForNextReveal.value){
console.log("waitingForNextReveal.value", waitingForNextReveal.value);
//
showPrizeExhaustedModal.value = true;
setTimeout(() => {
showPrizeExhaustedModal.value = false;
}, 1000);
break;
}
if(lastRevealed.value===-1){
console.log("lastRevealed.value", lastRevealed.value);
break;
}
// //
console.log("lotteryState 变更前:", lotteryState.value, "-> rotating"); console.log("lotteryState 变更前:", lotteryState.value, "-> rotating");
lotteryState.value = "rotating"; lotteryState.value = "rotating";
@ -125,8 +167,12 @@ async function handleLotteryClick() {
// result // result
await lottery3DRef.value?.switchScreen?.("lottery"); await lottery3DRef.value?.switchScreen?.("lottery");
await new Promise(resolve => setTimeout(resolve, 2000));
// //
lottery3DRef.value?.changeCard1?.(); lottery3DRef.value?.changeCard1?.();
//2
lotteryState.value = "ready"; lotteryState.value = "ready";
@ -173,4 +219,54 @@ function handleNextPrize() {
background-size: 1920px 980px; background-size: 1920px 980px;
} }
/* 透明弹窗样式 */
.prize-exhausted-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 20vh;
z-index: 9999;
pointer-events: none;
}
.modal-content {
background: transparent;
padding: 20px 30px;
border-radius: 10px;
animation: fadeInOut 1s ease-in-out;
}
.modal-text {
color: #ff0000;
font-size: 18px;
font-weight: bold;
text-align: center;
margin: 0;
white-space: nowrap;
}
@keyframes fadeInOut {
0% {
opacity: 0;
transform: translateY(-20px);
}
20% {
opacity: 1;
transform: translateY(0);
}
80% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-20px);
}
}
</style> </style>

53
src/views/choujiang/lottery/CardItem.vue

@ -9,7 +9,7 @@
> >
<!-- <div class="company">{{ company }}</div> --> <!-- <div class="company">{{ company }}</div> -->
<!-- <div class="name">{{ user[1] }}</div> --> <!-- <div class="name">{{ user[1] }}</div> -->
<div class="details">{{ (user[0] || "") + "\n" + (user[2] || "") }}</div>
<div class="details">{{ (user[0] || "") + "\n" }}</div>
</div> </div>
</template> </template>
@ -34,45 +34,39 @@ const props = defineProps({
prize: Boolean, prize: Boolean,
}); });
const cardStyle = computed(() => { const cardStyle = computed(() => {
//
const baseStyle = {
width: "130px",
height: "170px",
border: "1px solid rgb(255,255,255)",
};
if (props.isBold && props.showTable) { if (props.isBold && props.showTable) {
if (lotteryState.value === "idle") { if (lotteryState.value === "idle") {
return { return {
// backgroundColor: "rgba(226, 60, 38, 1)",
background: 'linear-gradient(135deg, rgba(243,153,38,0.7) 0%, rgba(207,56,35,1) 100%)',
width: "130px",
height: "170px",
...baseStyle,
background: 'linear-gradient(180deg, rgba(243,153,38,0.7) 0%, rgba(207,56,35,1) 100%)',
}; };
} }
} }
// return {
// // background: 'linear-gradient(135deg,rgba(255, 170, 22, 100) 0%, rgba(255, 170, 22, 100) 100%)',
// backgroundColor:'rgba(254, 177, 48, 100)',
// width: '130px',
// height: '170px',
// border: '1px solid rgb(255,255,255)',
// };
if (lotteryState.value === "result") {
return {
background: 'linear-gradient(135deg,rgba(255, 170, 22, 100) 0%, rgba(255, 170, 22, 100) 100%)',
// backgroundColor: "rgba(254, 177, 48, 100)",
width: "130px",
height: "170px",
border: "1px solid rgb(255,255,255)",
};
} else {
return {
backgroundColor: "rgba(254, 177, 48, 100)",
width: "130px",
height: "170px",
border: "1px solid rgb(255,255,255)",
};
// resultprizeCSS
if (lotteryState.value === "result" && props.prize) {
return baseStyle;
} }
//
return {
...baseStyle,
backgroundColor: "rgba(254, 177, 48, 100)",
};
}); });
</script> </script>
<style scoped> <style scoped>
.element { .element {
transition: background 2s; transition: background 2s;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
/* 你的基础样式 */ /* 你的基础样式 */
} }
.lightitem { .lightitem {
@ -82,8 +76,8 @@ const cardStyle = computed(() => {
/* 响应式高亮样式 */ /* 响应式高亮样式 */
} }
.prize { .prize {
/* 中奖样式 */
background-color: #fc0202;
/* 中奖样式 - 使用更高优先级 */
background: linear-gradient(180deg, #F39B26 0%, #E13A26 100%) !important;
} }
.company { .company {
/* ... */ /* ... */
@ -93,6 +87,7 @@ const cardStyle = computed(() => {
} }
.details { .details {
font-size: 30px; font-size: 30px;
color: white;
text-align: center; text-align: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

55
src/views/choujiang/lottery/ControlBar.vue

@ -1,7 +1,10 @@
<template> <template>
<div class="control-bar"> <div class="control-bar">
<button :disabled="isDisabled"
@click="$emit('lottery-click')">
<button
:disabled="isDisabled"
@click="$emit('lottery-click')"
class="lottery-button"
>
{{ lotteryState === 'idle' ? '进入抽奖' : lotteryState === 'ready' ? '开始抽奖' : lotteryState === 'rotating' ? '结束抽奖' : '开始抽奖' }} {{ lotteryState === 'idle' ? '进入抽奖' : lotteryState === 'ready' ? '开始抽奖' : lotteryState === 'rotating' ? '结束抽奖' : '开始抽奖' }}
</button> </button>
<!-- <button @click="$emit('reset')">重置</button> --> <!-- <button @click="$emit('reset')">重置</button> -->
@ -27,17 +30,49 @@ defineEmits(['lottery-click', 'reset', 'export']);
gap: 24px; gap: 24px;
z-index: 10; z-index: 10;
} }
button {
padding: 10px 24px;
font-size: 18px;
border-radius: 6px;
.lottery-button {
padding: 12px 32px;
font-size: 20px;
font-weight: 700;
border-radius: 50px;
border: none; border: none;
background: #0078ff;
background: linear-gradient(90deg, #ff8c42 0%, #ff6b35 50%, #ff5722 100%);
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
transition: background 0.2s;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(2, 0, 0, 0.5);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
min-width: 160px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
} }
button:hover {
background: #005bb5;
.lottery-button:hover {
background: linear-gradient(90deg, #ff9a5a 0%, #ff7a4a 50%, #ff6b3a 100%);
box-shadow: 0 6px 20px rgba(255, 87, 34, 0.4);
transform: translateY(-2px);
}
.lottery-button:active {
transform: translateY(0);
box-shadow: 0 2px 10px rgba(255, 87, 34, 0.3);
}
.lottery-button:disabled {
background: linear-gradient(90deg, #ccc 0%, #bbb 50%, #aaa 100%);
cursor: not-allowed;
box-shadow: none;
transform: none;
opacity: 0.6;
}
.lottery-button:disabled:hover {
background: linear-gradient(90deg, #ccc 0%, #bbb 50%, #aaa 100%);
box-shadow: none;
transform: none;
} }
</style> </style>

335
src/views/choujiang/lottery/Lottery3D.vue

@ -29,6 +29,7 @@ import TWEEN from "@tweenjs/tween.js";
import { NUMBER_MATRIX } from "../../../utils/config.js"; import { NUMBER_MATRIX } from "../../../utils/config.js";
import CardItem from "./CardItem.vue"; import CardItem from "./CardItem.vue";
import { createApp } from "vue"; import { createApp } from "vue";
import { getUserList } from "../../../api/API";
const threeContainer = ref(null); const threeContainer = ref(null);
let renderer, scene, camera, animationId; let renderer, scene, camera, animationId;
// let controls; // controls // let controls; // controls
@ -247,11 +248,20 @@ function selectCard(selectedCardIndex, currentLuckys, duration = 600) {
const pageIndex = index % cardsPerPage; const pageIndex = index % cardsPerPage;
const pageLocate = pageLocates[cardPage][pageIndex]; const pageLocate = pageLocates[cardPage][pageIndex];
//
let initialY;
if (isVisible) {
initialY = pageLocate.y;
} else {
//
initialY = pageLocate.y - 1000;
}
new TWEEN.Tween(object.position) new TWEEN.Tween(object.position)
.to( .to(
{ {
x: isVisible ? pageLocate.x : pageLocate.x,
y: isVisible ? pageLocate.y : pageLocate.y + 1000, //
x: pageLocate.x,
y: initialY,
z: 2200, z: 2200,
}, },
Math.random() * duration + duration Math.random() * duration + duration
@ -315,16 +325,58 @@ function switchPage(direction) {
const object = threeDCards[cardIndex]; const object = threeDCards[cardIndex];
const cardPage = Math.floor(index / cardsPerPage); const cardPage = Math.floor(index / cardsPerPage);
const isVisible = cardPage === newPage; const isVisible = cardPage === newPage;
const wasVisible = cardPage === currentPage.value;
// //
const pageIndex = index % cardsPerPage; const pageIndex = index % cardsPerPage;
const pageLocate = pageLocates[cardPage][pageIndex]; 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) new TWEEN.Tween(object.position)
.to( .to(
{ {
x: pageLocate.x, x: pageLocate.x,
y: isVisible ? pageLocate.y : pageLocate.y + 1000,
y: targetY,
z: 2200, z: 2200,
}, },
duration duration
@ -345,58 +397,6 @@ function switchPage(direction) {
} }
// 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;
// }
// //
// globalCardIndexes.forEach((cardIndex, index) => {
// const object = threeDCards[cardIndex];
// const cardPage = Math.floor(index / cardsPerPage);
// const isVisible = cardPage === newPage;
// //
// const pageIndex = index % cardsPerPage;
// const pageLocate = pageLocates[cardPage][pageIndex];
// //
// new TWEEN.Tween(object.position)
// .to(
// {
// x: pageLocate.x,
// y: isVisible ? pageLocate.y : (direction === 'next' ? pageLocate.y - 1000 : pageLocate.y + 1000), //
// 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) { function handleWheel(event) {
if (isPageTransitioning || totalPages.value <= 1) return; if (isPageTransitioning || totalPages.value <= 1) return;
@ -443,6 +443,9 @@ function resetCard(selectedCardIndex, duration = 500) {
if (window.pageLocates) { if (window.pageLocates) {
delete window.pageLocates; delete window.pageLocates;
} }
//
globalCardIndexes = [];
selectedCardIndex.forEach((index) => { selectedCardIndex.forEach((index) => {
const object = threeDCards[index]; const object = threeDCards[index];
@ -481,6 +484,11 @@ function resetCard(selectedCardIndex, duration = 500) {
.onComplete(() => { .onComplete(() => {
selectedCardIndex.forEach((index) => { selectedCardIndex.forEach((index) => {
const object = threeDCards[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"); object.element.classList.remove("prize");
}); });
resolve(); resolve();
@ -495,19 +503,22 @@ function changeCard(cardIndex, user) {
} }
const card = threeDCards[cardIndex].element; const card = threeDCards[cardIndex].element;
// card.innerHTML = `<div class="company">${
// user.company || ""
// }</div><div class="name">${user[1]}</div><div class="details">${
// user[0] || ""
// }<br/>${user[2] || "PSST"}</div>`;
card.style.setProperty('background-color', '#ffffff', 'important');
// card.style.backgroundColor = '#ffffff';
// card.style.backgroundColor = '';
// 便
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: 16px; 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() { function changeCard1() {
@ -528,10 +539,15 @@ function changeCard1() {
globalCardIndexes.forEach(cardIndex => { globalCardIndexes.forEach(cardIndex => {
const card = threeDCards[cardIndex].element; const card = threeDCards[cardIndex].element;
console.log('取消卡片', cardIndex, '的高光');
card.style.setProperty('background-color', 'rgba(254, 177, 48, 1)', 'important');
// console.log('', cardIndex, '');
//
if (card.dataset.originalContent) {
card.innerHTML = card.dataset.originalContent;
delete card.dataset.originalContent;
}
// prizeCSSbackgroundColor
// prizeCardItem
card.classList.remove('prize'); card.classList.remove('prize');
}); });
// //
@ -623,7 +639,7 @@ function getTotalCards() {
return threeDCards.length; return threeDCards.length;
} }
onMounted(() => {
onMounted( async () => {
// 3D // 3D
scene = new THREE.Scene(); scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( camera = new THREE.PerspectiveCamera(
@ -650,88 +666,89 @@ onMounted(() => {
HIGHLIGHT_CELL: highlightCells, HIGHLIGHT_CELL: highlightCells,
COMPANY: "演示公司", COMPANY: "演示公司",
}; };
const member = [
[0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"],
[0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"],
[0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"],
[0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"],
];
const userList = await getUserList();
console.log("userList", userList);
// 3D
const member = userList.data.map(item => [item.jwcode, item.username, "PSST"]);
// const member = [
// [1],
// [2],
// [3],
// [4],
// [5],
// [6],
// [7],
// [8],
// [9],
// [10],
// [11],
// [12],
// [13],
// [14],
// [15],
// [16],
// [17],
// [18],
// [19],
// [20],
// [21],
// [22],
// [23],
// [24],
// [25],
// [26],
// [27],
// [28],
// [29],
// [30],
// [31],
// [32],
// [33],
// [34],
// [35],
// [36],
// [37],
// [38],
// [39],
// [40],
// [41],
// [42],
// [43],
// [44],
// [45],
// [46],
// [47],
// [48],
// [49],
// [50],
// [51],
// [52],
// [53],
// [54],
// [55],
// [56],
// [57],
// [58],
// [59],
// [60],
// [61],
// [62],
// [63],
// [64],
// [65],
// [66],
// [67],
// [68],
// [69],
// [70],
// [71],
// [72],
// [73],
// [74],
// [75],
// [76],
// ];
const length = member.length; const length = member.length;
const showTable = true; const showTable = true;
const position = { const position = {
@ -836,6 +853,18 @@ defineExpose({
animation: fadeInOut 2s ease-in-out infinite; 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 { @keyframes fadeInOut {
0%, 100% { opacity: 0.6; } 0%, 100% { opacity: 0.6; }
50% { opacity: 1; } 50% { opacity: 1; }

141
src/views/choujiang/lottery/PrizePanel.vue

@ -6,9 +6,17 @@
class="prize-panel-item" class="prize-panel-item"
v-for="(prize, idx) in prizes" v-for="(prize, idx) in prizes"
:key="prize.type || idx" :key="prize.type || idx"
:class="{ 'revealed-highlight': idx === lastRevealedIdx }"
:class="{
'revealed-highlight': idx === lastRevealedIdx,
disabled: idx === nextRevealIdx && !canRevealPrize(idx),
}"
@click="handleReveal(idx)" @click="handleReveal(idx)"
style="cursor: pointer"
:style="{
cursor:
idx === nextRevealIdx && !canRevealPrize(idx)
? 'not-allowed'
: 'pointer',
}"
> >
<div v-if="isRevealed(idx)" class="prize-card"> <div v-if="isRevealed(idx)" class="prize-card">
<div class="prize-img-wrap"> <div class="prize-img-wrap">
@ -26,7 +34,7 @@
:style="{ width: getProgressPercent(prize) + '%' }" :style="{ width: getProgressPercent(prize) + '%' }"
></div> ></div>
<span class="progress-bar-text"> <span class="progress-bar-text">
{{ prize.count - getLeftCount(prize) }}/{{ prize.count }}
{{ getLeftCount(prize) }}/{{ prize.count }}
</span> </span>
</div> </div>
</div> </div>
@ -84,7 +92,10 @@
<div v-else> <div v-else>
<div class="prize-panel-root"> <div class="prize-panel-root">
<div class="prize-panel-list" v-if="prizes && prizes.length && lastRevealedIdx >= 0">
<div
class="prize-panel-list"
v-if="prizes && prizes.length && lastRevealedIdx >= 0"
>
<div <div
class="prize-panel-item" class="prize-panel-item"
:key="prizes[lastRevealedIdx].type || lastRevealedIdx" :key="prizes[lastRevealedIdx].type || lastRevealedIdx"
@ -93,21 +104,33 @@
> >
<div class="prize-card"> <div class="prize-card">
<div class="prize-img-wrap"> <div class="prize-img-wrap">
<img class="prize-img" :src="prizes[lastRevealedIdx].img" :alt="prizes[lastRevealedIdx].title" />
<img
class="prize-img"
:src="prizes[lastRevealedIdx].img"
:alt="prizes[lastRevealedIdx].title"
/>
</div> </div>
<div class="prize-info"> <div class="prize-info">
<div class="prize-row prize-row-top"> <div class="prize-row prize-row-top">
<span class="prize-level">{{ prizes[lastRevealedIdx].title }}</span>
<span class="prize-name">{{ prizes[lastRevealedIdx].text }}</span>
<span class="prize-level">{{
prizes[lastRevealedIdx].title
}}</span>
<span class="prize-name">{{
prizes[lastRevealedIdx].text
}}</span>
</div> </div>
<div class="prize-row prize-row-bottom"> <div class="prize-row prize-row-bottom">
<div class="progress-bar-bg"> <div class="progress-bar-bg">
<div <div
class="progress-bar-fill" class="progress-bar-fill"
:style="{ width: getProgressPercent(prizes[lastRevealedIdx]) + '%' }"
:style="{
width: getProgressPercent(prizes[lastRevealedIdx]) + '%',
}"
></div> ></div>
<span class="progress-bar-text"> <span class="progress-bar-text">
{{ prizes[lastRevealedIdx].count - getLeftCount(prizes[lastRevealedIdx]) }}/{{ prizes[lastRevealedIdx].count }}
{{ getLeftCount(prizes[lastRevealedIdx]) }}/{{
prizes[lastRevealedIdx].count
}}
</span> </span>
</div> </div>
</div> </div>
@ -159,16 +182,27 @@
<script setup> <script setup>
import { ref, computed, nextTick } from "vue"; import { ref, computed, nextTick } from "vue";
import { useLotteryStore } from "../../../store/lottery";
const props = defineProps({ const props = defineProps({
prizes: Array, prizes: Array,
}); });
// //
const revealedCount = ref(0); const revealedCount = ref(0);
// //
const lotteryStore = useLotteryStore();
const lastRevealed = computed({
get: () => lotteryStore.lastRevealedIdx,
set: (val) => lotteryStore.setLastRevealedIdx(val),
});
const waitingForNextReveal = computed({
get: () => lotteryStore.waitingForNextReveal,
set: (val) => lotteryStore.setWaitingForNextReveal(val),
});
const lastRevealedIdx = ref(-1); const lastRevealedIdx = ref(-1);
const showOne = ref(true);
lastRevealedIdx.value = lastRevealed.value;
const showOne = ref(true);
// //
const isRevealed = (idx) => const isRevealed = (idx) =>
@ -177,11 +211,46 @@ const isRevealed = (idx) =>
const nextRevealIdx = computed( const nextRevealIdx = computed(
() => (props.prizes?.length || 0) - revealedCount.value - 1 () => (props.prizes?.length || 0) - revealedCount.value - 1
); );
//
function canRevealPrize(idx) {
//
if (lastRevealedIdx.value === -1) {
return true;
}
//
const lastPrize = props.prizes[lastRevealedIdx.value];
if (lastPrize) {
const leftCount = getLeftCount(lastPrize);
//
if (leftCount > 0) {
waitingForNextReveal.value = false;
return false;
}
waitingForNextReveal.value = true;
}
return true;
}
// //
function handleReveal(idx) { function handleReveal(idx) {
if (idx === nextRevealIdx.value) {
//
if (idx === nextRevealIdx.value && canRevealPrize(idx)) {
revealedCount.value++; revealedCount.value++;
lastRevealedIdx.value = idx; // lastRevealedIdx.value = idx; //
if(idx===0){
waitingForNextReveal.value = false;
}
console.log("lastRevealedIdx.value", lastRevealedIdx.value);
lastRevealed.value = idx;
console.log("lastRevealed.value", lastRevealed.value);
} else if (idx === nextRevealIdx.value && !canRevealPrize(idx)) {
//
console.log("上一个奖品还未抽完,不能揭秘下一个奖品");
// toast
// alert("");
} }
} }
// //
@ -228,19 +297,26 @@ function closeWinnerList() {
} }
} }
const winnerBtnRef = ref(null);
const winnerBtnRef = ref(null);
const modalLeft = ref(0); const modalLeft = ref(0);
const modalTop = ref(0); const modalTop = ref(0);
function toggleWinnerList() { function toggleWinnerList() {
showWinnerList.value = !showWinnerList.value; showWinnerList.value = !showWinnerList.value;
console.log('toggleWinnerList - showWinnerList:', showWinnerList.value, 'showOne:', showOne.value, 'lastRevealedIdx:', lastRevealedIdx.value);
console.log(
"toggleWinnerList - showWinnerList:",
showWinnerList.value,
"showOne:",
showOne.value,
"lastRevealedIdx:",
lastRevealedIdx.value
);
if (showWinnerList.value) { if (showWinnerList.value) {
// showOnefalse // showOnefalse
if (lastRevealedIdx.value >= 0) { if (lastRevealedIdx.value >= 0) {
showOne.value = false; showOne.value = false;
console.log('设置 showOne 为 false');
console.log("设置 showOne 为 false");
} }
// //
nextTick(() => { nextTick(() => {
@ -255,7 +331,7 @@ function toggleWinnerList() {
// showOnefalsetrue // showOnefalsetrue
if (!showOne.value) { if (!showOne.value) {
showOne.value = true; showOne.value = true;
console.log('设置 showOne 为 true');
console.log("设置 showOne 为 true");
} }
} }
} }
@ -263,8 +339,8 @@ function toggleWinnerList() {
function getProgressPercent(prize) { function getProgressPercent(prize) {
const total = prize.count || 1; const total = prize.count || 1;
const left = getLeftCount(prize); const left = getLeftCount(prize);
const got = total - left;
return Math.round((got / total) * 100);
//
return Math.round((left / total) * 100);
} }
</script> </script>
@ -501,10 +577,37 @@ function getProgressPercent(prize) {
border-radius: 8px 8px 8px 8px; border-radius: 8px 8px 8px 8px;
} }
.prize-panel-item.revealed-highlight { .prize-panel-item.revealed-highlight {
border: 3px solid #ff9800;
/* border: 3px solid #ff9800; */
box-shadow: 0 0 16px 4px #ff9800aa; box-shadow: 0 0 16px 4px #ff9800aa;
transform: scale(1.05); transform: scale(1.05);
z-index: 2; z-index: 2;
transition: all 0.3s; transition: all 0.3s;
} }
.prize-panel-item.disabled {
cursor: not-allowed !important;
position: relative;
}
.prize-panel-item.disabled::after {
content: "请先抽完上一个奖品";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
white-space: nowrap;
z-index: 10;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.prize-panel-item.disabled:hover::after {
opacity: 1;
}
</style> </style>

217
src/views/choujiang/lottery/dataManager.js

@ -1,5 +1,5 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { getPrizeList, getUserList } from '../../../api/API';
export function useDataManager() { export function useDataManager() {
const state = reactive({ const state = reactive({
basicData: { basicData: {
@ -12,94 +12,56 @@ export function useDataManager() {
currentPrize: null, currentPrize: null,
currentLuckys: [], currentLuckys: [],
isLotting: false, isLotting: false,
// 新增:轮次管理
currentRound: 1,
config: { config: {
prizes: [], prizes: [],
EACH_COUNT: [], EACH_COUNT: [],
COMPANY: '', COMPANY: '',
HIGHLIGHT_CELL: [], HIGHLIGHT_CELL: [],
Resolution: 1
Resolution: 1,
ROW_COUNT: 7,
COLUMN_COUNT: 20
} }
}); });
async function getBasicData() { async function getBasicData() {
// 假数据,后续可替换为接口
const fakePrizes = [
{ type: 0, title: '特别奖', text: 'iPad', count: 10, img: '/src/assets/lottery/ipad.jpg' },
{ type: 1, title: '一等奖', text: 'Kindle', count: 20, img: '/src/assets/lottery/kindle.jpg' },
{ type: 2, title: '二等奖', text: 'MacBook Pro', count: 10, img: '/src/assets/lottery/mbp.jpg' }
];
const fakeEachCount = [15, 17, 36];
// 获取奖品列表
const prizeList = await getPrizeList();
const fakePrizes = prizeList.data.map((item, index) => ({
type: index, // 使用索引作为type
title: item.gradeName,
text: item.prizeName,
count: item.amount,
img: item.imageUrl
}));
const fakeEachCount = prizeList.data.map(item => item.perWin);
console.log("fakeEachCount", fakeEachCount);
const fakeCompany = '前端假公司'; const fakeCompany = '前端假公司';
const fakeLuckyData = {}; const fakeLuckyData = {};
fakePrizes.forEach(prize => {
fakeLuckyData[prize.type] = [];
fakePrizes.forEach((prize, index) => {
fakeLuckyData[index] = [];
}); });
const fakeLeftUsers = [
[0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"], [0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"], [0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"],
];
// 获取真实用户数据
const userListResponse = await getUserList();
console.log("userList", userListResponse);
// 将后端返回的用户数据转换为兼容格式
const realUsers = userListResponse.data.map(item => ({
jwcode: item.jwcode,
username: item.username,
company: fakeCompany // 使用默认公司名称
}));
state.config.prizes = fakePrizes; state.config.prizes = fakePrizes;
state.config.EACH_COUNT = fakeEachCount; state.config.EACH_COUNT = fakeEachCount;
state.config.COMPANY = fakeCompany; state.config.COMPANY = fakeCompany;
state.config.HIGHLIGHT_CELL = []; state.config.HIGHLIGHT_CELL = [];
state.basicData.prizes = fakePrizes; state.basicData.prizes = fakePrizes;
state.basicData.leftUsers = fakeLeftUsers.slice();
state.basicData.users = realUsers; // 使用真实用户数据
state.basicData.leftUsers = realUsers.slice(); // 初始化剩余用户为所有用户
state.basicData.luckyUsers = fakeLuckyData; state.basicData.luckyUsers = fakeLuckyData;
determineCurrentPrize(); determineCurrentPrize();
return Promise.resolve({ return Promise.resolve({
@ -108,74 +70,24 @@ export function useDataManager() {
EACH_COUNT: fakeEachCount, EACH_COUNT: fakeEachCount,
COMPANY: fakeCompany COMPANY: fakeCompany
}, },
leftUsers: fakeLeftUsers.slice(),
leftUsers: realUsers.slice(),
luckyData: fakeLuckyData luckyData: fakeLuckyData
}); });
} }
async function getUsers() { async function getUsers() {
const fakeUsers = [
[0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"], [0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"], [0, "张三"],
[1, "李四"],
[2, "王五"],
[3, "赵六"],
[4, "孙七"],
[5, "周八"],
[6, "吴九"],
[7, "郑十"],
[8, "钱十一"],
[9, "孙十二"],
[10, "李十三"],
[11, "周十四"],
[12, "吴十五"],
[13, "郑十六"],
[14, "钱十七"],
[15, "孙十八"],
[16, "李十九"],
[17, "周二十"],
[18, "吴二一"],
[19, "郑二二"],
];
state.basicData.users = fakeUsers;
return Promise.resolve(fakeUsers);
const userList = await getUserList();
console.log("userList", userList);
// 将后端返回的用户数据转换为兼容格式
const realUsers = userList.data.map(item => ({
jwcode: item.jwcode,
username: item.username,
company: state.config.COMPANY || '前端假公司'
}));
state.basicData.users = realUsers;
return Promise.resolve(userList);
} }
function determineCurrentPrize() { function determineCurrentPrize() {
@ -213,6 +125,35 @@ export function useDataManager() {
} }
function updateCurrentPrize() { determineCurrentPrize(); } function updateCurrentPrize() { determineCurrentPrize(); }
// 新增:计算当前奖品的总轮次
function getTotalRounds(prizeIndex) {
const prize = state.basicData.prizes[prizeIndex];
const eachCount = state.config.EACH_COUNT[prizeIndex];
if (!prize || !eachCount) return 0;
return Math.ceil(prize.count / eachCount);
}
// 新增:计算当前轮次
function getCurrentRound(prizeIndex) {
const luckyUsers = state.basicData.luckyUsers[prizeIndex] || [];
const eachCount = state.config.EACH_COUNT[prizeIndex];
if (!eachCount) return 1;
return Math.floor(luckyUsers.length / eachCount) + 1;
}
// 新增:计算剩余数量
function getLeftCount(prizeIndex) {
const prize = state.basicData.prizes[prizeIndex];
const luckyUsers = state.basicData.luckyUsers[prizeIndex] || [];
if (!prize) return 0;
return prize.count - luckyUsers.length;
}
// 新增:更新当前轮次
function updateCurrentRound() {
state.currentRound = getCurrentRound(state.currentPrizeIndex);
}
return { return {
state, state,
getBasicData, getBasicData,
@ -224,6 +165,10 @@ export function useDataManager() {
getTotalCards, getTotalCards,
setLotteryStatus, setLotteryStatus,
resetAllData, resetAllData,
updateCurrentPrize
updateCurrentPrize,
getTotalRounds,
getCurrentRound,
getLeftCount,
updateCurrentRound
}; };
} }

129
src/views/choujiang/lottery/lotteryEngine.js

@ -1,5 +1,6 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { useLotteryStore } from '../../../store/lottery' // 路径根据实际情况调整 import { useLotteryStore } from '../../../store/lottery' // 路径根据实际情况调整
import { drawLottery } from '../../../api/API'; // 导入新的抽奖接口
function getRandomInt(max) { function getRandomInt(max) {
return Math.floor(Math.random() * max); return Math.floor(Math.random() * max);
@ -17,35 +18,113 @@ export function useLotteryEngine(dataManager, renderer3D) {
changePrize(); changePrize();
// 重置卡片动画 // 重置卡片动画
await renderer3D.resetCard([]); await renderer3D.resetCard([]);
// 生成中奖卡片索引和中奖用户
const perCount = dataManager.state.config.EACH_COUNT[dataManager.state.currentPrizeIndex] || 1;
const totalCards = renderer3D.getTotalCards ? renderer3D.getTotalCards() : 50;
const leftUsers = dataManager.state.basicData.leftUsers;
let selectedCardIndex = [];
let currentLuckys = [];
let leftCount = leftUsers.length;
console.log('executeLottery - perCount:', perCount, 'leftCount:', leftCount, 'totalCards:', totalCards);
// 计算本次应该抽奖的人数
const currentPrizeIndex = dataManager.state.currentPrizeIndex;
const prize = dataManager.state.basicData.prizes[currentPrizeIndex];
const luckyUsers = dataManager.state.basicData.luckyUsers[currentPrizeIndex] || [];
const remainingPrizeCount = prize.count - luckyUsers.length; // 奖品剩余数量
const basePerCount = dataManager.state.config.EACH_COUNT[currentPrizeIndex] || 1;
const actualPerCount = Math.min(basePerCount, remainingPrizeCount); // 取最小值
// 随机抽取中奖用户和卡片索引
for (let i = 0; i < perCount && leftCount > 0; i++) {
const luckyId = getRandomInt(leftCount);
currentLuckys.push(leftUsers.splice(luckyId, 1)[0]);
leftCount--;
let cardIndex = getRandomInt(totalCards);
while (selectedCardIndex.includes(cardIndex)) {
cardIndex = getRandomInt(totalCards);
console.log('executeLottery - currentPrizeIndex:', currentPrizeIndex, 'prize:', prize, 'basePerCount:', basePerCount, 'remainingPrizeCount:', remainingPrizeCount, 'actualPerCount:', actualPerCount);
// 请求后端进行抽奖
try {
const lotteryData = {
gradeName: prize.title,
prizeName: prize.text,
perWin: basePerCount,
round: dataManager.state.currentRound
};
console.log('请求后端抽奖,参数:', lotteryData);
const response = await drawLottery(lotteryData);
console.log('后端抽奖返回结果:', response);
if (response && response.data && Array.isArray(response.data)) {
// 后端返回中奖用户数据
const currentLuckys = response.data.map(item => ({
jwcode: item.jwcode,
username: item.username
}));
console.log('后端返回的中奖用户:', currentLuckys);
// 生成随机卡片索引用于显示
const totalCards = dataManager.getTotalCards();
let selectedCardIndex = [];
for (let i = 0; i < currentLuckys.length; i++) {
let cardIndex = getRandomInt(totalCards);
while (selectedCardIndex.includes(cardIndex)) {
cardIndex = getRandomInt(totalCards);
}
selectedCardIndex.push(cardIndex);
}
console.log('executeLottery - selectedCardIndex:', selectedCardIndex, 'currentLuckys:', currentLuckys);
dataManager.state.currentLuckys = currentLuckys;
// 保存中奖用户到对应奖品
if (!dataManager.state.basicData.luckyUsers[currentPrizeIndex]) {
dataManager.state.basicData.luckyUsers[currentPrizeIndex] = [];
}
dataManager.state.basicData.luckyUsers[currentPrizeIndex].push(...currentLuckys);
// 更新轮次信息
dataManager.updateCurrentRound();
// 展示中奖动画
console.log('executeLottery - calling selectCard');
await renderer3D.selectCard?.(selectedCardIndex, currentLuckys);
console.log('executeLottery - selectCard completed');
} else {
console.error('后端抽奖返回数据格式错误:', response);
throw new Error('抽奖失败:后端返回数据格式错误');
}
} catch (error) {
console.error('抽奖请求失败:', error);
// 如果后端请求失败,可以回退到前端随机抽奖逻辑
console.log('回退到前端随机抽奖逻辑');
const totalCards = dataManager.getTotalCards();
const leftUsers = dataManager.state.basicData.leftUsers;
let selectedCardIndex = [];
let currentLuckys = [];
let leftCount = leftUsers.length;
// 随机抽取中奖用户和卡片索引
for (let i = 0; i < actualPerCount && leftCount > 0; i++) {
const luckyId = getRandomInt(leftCount);
const selectedUser = leftUsers.splice(luckyId, 1)[0];
// 确保数据格式一致
currentLuckys.push({
jwcode: selectedUser.jwcode || selectedUser[0] || "",
username: selectedUser.username || selectedUser[1] || "",
company: selectedUser.company || selectedUser[2] || "PSST"
});
leftCount--;
let cardIndex = getRandomInt(totalCards);
while (selectedCardIndex.includes(cardIndex)) {
cardIndex = getRandomInt(totalCards);
}
selectedCardIndex.push(cardIndex);
}
console.log('executeLottery - selectedCardIndex:', selectedCardIndex, 'currentLuckys:', currentLuckys);
dataManager.state.currentLuckys = currentLuckys;
// 保存中奖用户到对应奖品
if (!dataManager.state.basicData.luckyUsers[currentPrizeIndex]) {
dataManager.state.basicData.luckyUsers[currentPrizeIndex] = [];
} }
selectedCardIndex.push(cardIndex);
dataManager.state.basicData.luckyUsers[currentPrizeIndex].push(...currentLuckys);
// 更新轮次信息
dataManager.updateCurrentRound();
// 展示中奖动画
console.log('executeLottery - calling selectCard');
await renderer3D.selectCard?.(selectedCardIndex, currentLuckys);
console.log('executeLottery - selectCard completed');
} }
console.log('executeLottery - selectedCardIndex:', selectedCardIndex, 'currentLuckys:', currentLuckys);
dataManager.state.currentLuckys = currentLuckys;
// 展示中奖动画
console.log('executeLottery - calling selectCard');
await renderer3D.selectCard?.(selectedCardIndex, currentLuckys);
console.log('executeLottery - selectCard completed');
dataManager.setLotteryStatus(false); dataManager.setLotteryStatus(false);
isLotting.value = false; isLotting.value = false;
} }
@ -66,6 +145,8 @@ export function useLotteryEngine(dataManager, renderer3D) {
lotteryStore.setLotteryState('idle'); // 直接用 lotteryStore.setLotteryState('idle'); // 直接用
await renderer3D.resetCard([]); await renderer3D.resetCard([]);
await dataManager.resetData(); await dataManager.resetData();
// 重置轮次
dataManager.state.currentRound = 1;
renderer3D.switchScreen && renderer3D.switchScreen('enter'); renderer3D.switchScreen && renderer3D.switchScreen('enter');
} }

Loading…
Cancel
Save