Browse Source

完成文字闪烁

songtongtong/feature-20250717104937-众筹
Ethereal 4 weeks ago
parent
commit
2873423dd4
  1. 6
      src/api/API.js
  2. BIN
      src/assets/bg@2x.png
  3. BIN
      src/assets/qilin.webp
  4. 45
      src/store/lottery.js
  5. 46
      src/views/choujiang/index.vue
  6. 92
      src/views/choujiang/lottery/CardItem.vue
  7. 103
      src/views/choujiang/lottery/Lottery3D.vue
  8. 54
      src/views/choujiang/lottery/Mascot.vue
  9. 31
      src/views/choujiang/lottery/PrizePanel.vue
  10. 4
      src/views/choujiang/lottery/UserList.vue
  11. 20
      src/views/choujiang/lottery/lotteryEngine.js
  12. 2
      src/views/homePage.vue
  13. 2
      src/views/zhongchou/index.vue

6
src/api/API.js

@ -32,13 +32,13 @@ export function startLottery(data){
// 新增:每轮抽奖接口 // 新增:每轮抽奖接口
export function drawLottery(data){ export function drawLottery(data){
return request({ return request({
url: '/lottery/draw',
url: '/lottery/start',
method: 'post', method: 'post',
data: { data: {
gradeName: data.gradeName, gradeName: data.gradeName,
prizeName: data.prizeName, prizeName: data.prizeName,
perWin: data.perWin,
round: data.round
perWin: data.perWin
// round: data.round
} }
}) })
} }

BIN
src/assets/bg@2x.png

After

Width: 3840  |  Height: 2160  |  Size: 5.6 MiB

BIN
src/assets/qilin.webp

45
src/store/lottery.js

@ -6,6 +6,27 @@ export const useLotteryStore = defineStore('lottery', () => {
const lotteryState = ref('idle') // idle, ready, rotating, result const lotteryState = ref('idle') // idle, ready, rotating, result
const lastRevealedIdx = ref(-1) const lastRevealedIdx = ref(-1)
const waitingForNextReveal = ref(false) const waitingForNextReveal = ref(false)
//设置中奖人数列表
const winners = ref([])
// 添加用户列表管理
const allUsers = ref([])
const isUsersLoaded = ref(false)
function setWinners(list) {
// 如果是数组,则添加到现有数组中;如果是单个项目,则直接添加
if (Array.isArray(list)) {
winners.value = [...winners.value, ...list]
} else {
winners.value = [...winners.value, list]
}
}
function clearWinners() {
winners.value = []
}
function setLotteryState(state) { function setLotteryState(state) {
lotteryState.value = state lotteryState.value = state
} }
@ -18,12 +39,34 @@ export const useLotteryStore = defineStore('lottery', () => {
waitingForNextReveal.value = val waitingForNextReveal.value = val
} }
// 设置用户列表
function setAllUsers(users) {
allUsers.value = users
isUsersLoaded.value = true
}
// 获取随机用户名称
function getRandomUserName() {
if (allUsers.value.length > 0) {
const randomIndex = Math.floor(Math.random() * allUsers.value.length)
return allUsers.value[randomIndex]
}
return ""
}
return { return {
lotteryState, lotteryState,
setLotteryState, setLotteryState,
lastRevealedIdx, lastRevealedIdx,
setLastRevealedIdx, setLastRevealedIdx,
waitingForNextReveal, waitingForNextReveal,
setWaitingForNextReveal
setWaitingForNextReveal,
winners,
setWinners,
clearWinners,
allUsers,
isUsersLoaded,
setAllUsers,
getRandomUserName
} }
}) })

46
src/views/choujiang/index.vue

@ -1,9 +1,7 @@
<template> <template>
<div class="choujiang-main"> <div class="choujiang-main">
<Lottery3D ref="lottery3DRef" /> <Lottery3D ref="lottery3DRef" />
<PrizePanel
:prizes="dataManager.state.basicData.prizes"
/>
<PrizePanel :prizes="dataManager.state.basicData.prizes" />
<ControlBar <ControlBar
:lottery-state="lotteryState" :lottery-state="lotteryState"
:is-disabled="isDisabled" :is-disabled="isDisabled"
@ -12,14 +10,15 @@
@export="handleExport" @export="handleExport"
/> />
<MusicPlayer /> <MusicPlayer />
<Mascot />
<!-- 透明弹窗 --> <!-- 透明弹窗 -->
<div v-if="showPrizeExhaustedModal" class="prize-exhausted-modal"> <div v-if="showPrizeExhaustedModal" class="prize-exhausted-modal">
<div class="modal-content"> <div class="modal-content">
<p class="modal-text">该礼品已抽取完毕请揭秘下一个礼品</p> <p class="modal-text">该礼品已抽取完毕请揭秘下一个礼品</p>
</div> </div>
</div> </div>
<!-- <UserList <!-- <UserList
:lucky-users=" :lucky-users="
dataManager.state.basicData.luckyUsers[ dataManager.state.basicData.luckyUsers[
@ -39,6 +38,7 @@ import ControlBar from "./lottery/ControlBar.vue";
import MusicPlayer from "./lottery/MusicPlayer.vue"; import MusicPlayer from "./lottery/MusicPlayer.vue";
import Qipao from "./lottery/Qipao.vue"; import Qipao from "./lottery/Qipao.vue";
import UserList from "./lottery/UserList.vue"; import UserList from "./lottery/UserList.vue";
import Mascot from "./lottery/Mascot.vue";
import { ref, onMounted, nextTick, computed, watch } from "vue"; import { ref, onMounted, nextTick, computed, watch } from "vue";
import { useDataManager } from "./lottery/dataManager.js"; import { useDataManager } from "./lottery/dataManager.js";
import { useLotteryEngine } from "./lottery/lotteryEngine.js"; import { useLotteryEngine } from "./lottery/lotteryEngine.js";
@ -68,7 +68,6 @@ const waitingForNextReveal = computed({
set: (val) => lotteryStore.setWaitingForNextReveal(val), set: (val) => lotteryStore.setWaitingForNextReveal(val),
}); });
const isDisabled = ref(false); const isDisabled = ref(false);
watch(isDisabled, (newVal, oldVal) => { watch(isDisabled, (newVal, oldVal) => {
@ -90,7 +89,7 @@ const lotteryEngine = useLotteryEngine(dataManager, {
onMounted(async () => { onMounted(async () => {
await dataManager.getBasicData(); await dataManager.getBasicData();
await dataManager.getUsers(); await dataManager.getUsers();
// dataManager window 使 // dataManager window 使
window.dataManager = dataManager; window.dataManager = dataManager;
}); });
@ -100,7 +99,9 @@ function showLotteryQipao() {
const prize = dataManager.state.currentPrize; const prize = dataManager.state.currentPrize;
if (!luckys || luckys.length === 0) return; if (!luckys || luckys.length === 0) return;
// jwcode username // jwcode username
const names = luckys.map((item) => item.username || item[1] || item.jwcode || "").join("、");
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(() => {
@ -118,14 +119,16 @@ async function handleLotteryClick() {
switch (lotteryState.value) { switch (lotteryState.value) {
case "idle": case "idle":
// //
await lottery3DRef.value?.switchScreen?.("lottery");
console.log("lotteryState 变更前:", lotteryState.value, "-> ready"); console.log("lotteryState 变更前:", lotteryState.value, "-> ready");
// await new Promise((resolve) => setTimeout(resolve, 2000));
lotteryState.value = "ready"; lotteryState.value = "ready";
console.log("lotteryState 变更后:", lotteryState.value); console.log("lotteryState 变更后:", lotteryState.value);
await lottery3DRef.value?.switchScreen?.("lottery");
break; break;
case "ready": case "ready":
if(waitingForNextReveal.value){
if (waitingForNextReveal.value) {
console.log("waitingForNextReveal.value", waitingForNextReveal.value); console.log("waitingForNextReveal.value", waitingForNextReveal.value);
// //
showPrizeExhaustedModal.value = true; showPrizeExhaustedModal.value = true;
@ -134,20 +137,18 @@ async function handleLotteryClick() {
}, 1000); }, 1000);
break; break;
} }
if(lastRevealed.value===-1){
if (lastRevealed.value === -1) {
console.log("lastRevealed.value", lastRevealed.value); console.log("lastRevealed.value", lastRevealed.value);
break; break;
} }
// //
console.log("lotteryState 变更前:", lotteryState.value, "-> rotating"); console.log("lotteryState 变更前:", lotteryState.value, "-> rotating");
lotteryState.value = "rotating"; lotteryState.value = "rotating";
console.log("lotteryState 变更后:", lotteryState.value); console.log("lotteryState 变更后:", lotteryState.value);
// //
// isRunning.value = false; //
// isRunning.value = false; //
await lottery3DRef.value?.rotateBallStart?.(); await lottery3DRef.value?.rotateBallStart?.();
break; break;
@ -166,14 +167,14 @@ async function handleLotteryClick() {
case "result": case "result":
// result // result
await lottery3DRef.value?.switchScreen?.("lottery"); await lottery3DRef.value?.switchScreen?.("lottery");
await new Promise(resolve => setTimeout(resolve, 2000));
await new Promise((resolve) => setTimeout(resolve, 2000));
// //
lottery3DRef.value?.changeCard1?.(); lottery3DRef.value?.changeCard1?.();
//2 //2
lotteryState.value = "ready"; lotteryState.value = "ready";
break; break;
@ -212,12 +213,13 @@ function handleNextPrize() {
.choujiang-main { .choujiang-main {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
position: relative;
position: fixed;
top: 0;
left: 0;
overflow: hidden; overflow: hidden;
/* 添加背景图片 */ /* 添加背景图片 */
background: url('../../assets/登录.png') ;
background: url("../../assets/bg@2x.png");
background-size: 1920px 980px; background-size: 1920px 980px;
} }
/* 透明弹窗样式 */ /* 透明弹窗样式 */

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

@ -9,12 +9,12 @@
> >
<!-- <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" }}</div>
<div class="details">{{ displayText }}</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { computed } from "vue";
import { computed, ref, onMounted, onBeforeUnmount, watch } from "vue";
import { useLotteryStore } from "../../../store/lottery"; // import { useLotteryStore } from "../../../store/lottery"; //
@ -33,6 +33,61 @@ const props = defineProps({
highlight: Boolean, highlight: Boolean,
prize: Boolean, prize: Boolean,
}); });
//
const displayText = ref("");
const textSwitchInterval = ref(null);
// store
const allUsers = computed(() => lotteryStore.allUsers);
const isUsersLoaded = computed(() => lotteryStore.isUsersLoaded);
//
const switchText = () => {
if (isUsersLoaded.value && allUsers.value.length > 0) {
displayText.value = lotteryStore.getRandomUserName();
} else {
displayText.value = props.user[0] || "";
}
};
//
const startTextSwitch = () => {
//
if (lotteryState.value === "idle" || lotteryState.value === "ready") {
const scheduleNextSwitch = () => {
// 1-4
const randomInterval = Math.random() * 20000 + 1000; // 10000-20000
textSwitchInterval.value = setTimeout(() => {
switchText();
//
scheduleNextSwitch();
}, randomInterval);
};
scheduleNextSwitch();
}
};
//
const stopTextSwitch = () => {
if (textSwitchInterval.value) {
clearTimeout(textSwitchInterval.value);
textSwitchInterval.value = null;
}
};
//
const handleLotteryStateChange = () => {
if (lotteryState.value === "rotating" || lotteryState.value === "result") {
//
stopTextSwitch();
displayText.value = props.user[0] || "";
} else {
//
startTextSwitch();
}
};
const cardStyle = computed(() => { const cardStyle = computed(() => {
// //
const baseStyle = { const baseStyle = {
@ -61,6 +116,38 @@ const cardStyle = computed(() => {
backgroundColor: "rgba(254, 177, 48, 100)", backgroundColor: "rgba(254, 177, 48, 100)",
}; };
}); });
//
onMounted(async () => {
//
displayText.value = props.user[0] || "";
//
setTimeout(() => {
startTextSwitch();
}, 1000);
//
handleLotteryStateChange();
});
//
watch(lotteryState, () => {
handleLotteryStateChange();
});
//
watch(isUsersLoaded, (loaded) => {
if (loaded) {
//
switchText();
}
});
//
onBeforeUnmount(() => {
stopTextSwitch();
});
</script> </script>
<style scoped> <style scoped>
@ -93,5 +180,6 @@ const cardStyle = computed(() => {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
transition: opacity 0.3s ease;
} }
</style> </style>

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

@ -18,7 +18,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onBeforeUnmount, defineExpose, watch } from "vue";
import { ref, onMounted, onBeforeUnmount, defineExpose, watch, computed } from "vue";
import * as THREE from "three"; import * as THREE from "three";
import { import {
CSS3DRenderer, CSS3DRenderer,
@ -30,6 +30,16 @@ 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"; import { getUserList } 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); const threeContainer = ref(null);
let renderer, scene, camera, animationId; let renderer, scene, camera, animationId;
// let controls; // controls // let controls; // controls
@ -667,93 +677,22 @@ function getTotalCards() {
COMPANY: "演示公司", COMPANY: "演示公司",
}; };
const userList = await getUserList();
console.log("userList", userList);
const userList = await getUserList();
// lotteryStore.setWinners(userList);
// console.log("userList", userList);
// 3D // 3D
const member = userList.data.map(item => [item.jwcode, item.username, "PSST"]); 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],
// ];
// store
const userNames = userList.data.map(item => item.jwcode || item.username || "");
lotteryStore.setAllUsers(userNames);
const length = member.length; const length = member.length;
const showTable = true; const showTable = true;
const position = { const position = {
x: (100 * config.COLUMN_COUNT - 20) / 2, x: (100 * config.COLUMN_COUNT - 20) / 2,
y: (160 * config.ROW_COUNT - 20) / 2,
y: (120 * config.ROW_COUNT - 20) / 2,
}; };
createCards(member, length, showTable, position, config); // 3. createCards(member, length, showTable, position, config); // 3.
createSphereTargets(); createSphereTargets();

54
src/views/choujiang/lottery/Mascot.vue

@ -0,0 +1,54 @@
<template>
<div class="mascot-container">
<img
src="../../../assets/qilin.webp"
alt="可爱的角色"
class="mascot-image"
/>
</div>
</template>
<script setup>
//
</script>
<style scoped>
.mascot-container {
position: fixed;
bottom: -10px;
right: -80px;
z-index: 1000;
pointer-events: none;
}
.mascot-image {
width: 400px;
height: 400px;
animation: bounce 2s ease-in-out infinite;
}
/* @keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-10px);
}
60% {
transform: translateY(-5px);
}
} */
/* 响应式设计 */
@media (max-width: 768px) {
.mascot-image {
width: 180px;
height: 180px;
}
.mascot-container {
bottom: 10px;
right: 10px;
}
}
</style>

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

@ -166,7 +166,7 @@
<ul class="winner-list"> <ul class="winner-list">
<li v-for="(user, idx) in fakeWinners" :key="idx" style="display: flex; justify-content: space-between; align-items: center;" > <li v-for="(user, idx) in fakeWinners" :key="idx" style="display: flex; justify-content: space-between; align-items: center;" >
<!-- <span>{{ user.id }}</span> - <span>{{ user.name }}</span> - --> <!-- <span>{{ user.id }}</span> - <span>{{ user.name }}</span> - -->
<span >{{ user.id }}</span>
<span >{{ user.jwcode }}</span>
<span>{{ user.prize }}</span> <span>{{ user.prize }}</span>
</li> </li>
</ul> </ul>
@ -179,8 +179,10 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, nextTick } from "vue";
import { useLotteryStore } from "../../../store/lottery";
import { ref, computed, nextTick, watch } from "vue";
import { useLotteryStore } from "../../../store/lottery";
const props = defineProps({ const props = defineProps({
prizes: Array, prizes: Array,
}); });
@ -196,6 +198,18 @@ const waitingForNextReveal = computed({
get: () => lotteryStore.waitingForNextReveal, get: () => lotteryStore.waitingForNextReveal,
set: (val) => lotteryStore.setWaitingForNextReveal(val), set: (val) => lotteryStore.setWaitingForNextReveal(val),
}); });
const winners = computed({
get: () => lotteryStore.winners,
set: (val) => lotteryStore.setWinners(val),
});
// watchwinners
watch(winners, (newVal) => {
console.log('中奖人数列表winners', newVal);
fakeWinners.value = newVal;
});
const lastRevealedIdx = ref(-1); const lastRevealedIdx = ref(-1);
lastRevealedIdx.value = lastRevealed.value; lastRevealedIdx.value = lastRevealed.value;
@ -264,13 +278,10 @@ function getLeftCount(prize) {
// //
const showWinnerList = ref(false); const showWinnerList = ref(false);
const fakeWinners = ref([
{ id: "90044065", name: "张三", prize: "六等奖" },
{ id: "90044066", name: "李四", prize: "六等奖" },
{ id: "90044067", name: "王五", prize: "六等奖" },
{ id: "90044068", name: "赵六", prize: "六等奖" },
{ id: "90044069", name: "小明", prize: "六等奖" },
]);
const fakeWinners = ref([]);
fakeWinners.value = winners.value;
console.log('fakeWinners', fakeWinners.value);
function openWinnerList() { function openWinnerList() {
// showWinnerList.value = true; // showWinnerList.value = true;
if (!showWinnerList.value) { if (!showWinnerList.value) {

4
src/views/choujiang/lottery/UserList.vue

@ -16,6 +16,10 @@
</template> </template>
<script setup> <script setup>
import { useLotteryStore } from "../../../store/lottery";
const lotteryStore = useLotteryStore();
const props = defineProps({ const props = defineProps({
luckyUsers: { luckyUsers: {
type: Array, type: Array,

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

@ -1,4 +1,4 @@
import { ref } from 'vue';
import { ref, computed, watch } from 'vue';
import { useLotteryStore } from '../../../store/lottery' // 路径根据实际情况调整 import { useLotteryStore } from '../../../store/lottery' // 路径根据实际情况调整
import { drawLottery } from '../../../api/API'; // 导入新的抽奖接口 import { drawLottery } from '../../../api/API'; // 导入新的抽奖接口
@ -6,6 +6,19 @@ function getRandomInt(max) {
return Math.floor(Math.random() * max); return Math.floor(Math.random() * max);
} }
const lotteryStore = useLotteryStore();
const winners = computed({
get: () => lotteryStore.winners,
set: (val) => lotteryStore.setWinners(val),
});
// 用watch监听winners的变化
// watch(winners, (newVal) => {
// console.log('winners', newVal);
// winners.value = newVal;
// });
export function useLotteryEngine(dataManager, renderer3D) { export function useLotteryEngine(dataManager, renderer3D) {
const isLotting = ref(false); const isLotting = ref(false);
const lotteryStore = useLotteryStore(); // 只获取一次 const lotteryStore = useLotteryStore(); // 只获取一次
@ -40,6 +53,11 @@ export function useLotteryEngine(dataManager, renderer3D) {
console.log('请求后端抽奖,参数:', lotteryData); console.log('请求后端抽奖,参数:', lotteryData);
const response = await drawLottery(lotteryData); const response = await drawLottery(lotteryData);
console.log('response', response);
winners.value = response.data;
console.log('抽奖的winners', winners.value);
// winners.value = response.data;
console.log('后端抽奖返回结果:', response); console.log('后端抽奖返回结果:', response);
if (response && response.data && Array.isArray(response.data)) { if (response && response.data && Array.isArray(response.data)) {

2
src/views/homePage.vue

@ -2,11 +2,13 @@
<div> <div>
<button @click="router.push('/choujiang')">抽奖</button> <button @click="router.push('/choujiang')">抽奖</button>
<button @click="router.push('/zhongchou')">众筹</button> <button @click="router.push('/zhongchou')">众筹</button>
<Mascot />
</div> </div>
</template> </template>
<script setup> <script setup>
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
// import Mascot from "../choujiang/lottery/Mascot.vue";
const router = useRouter(); const router = useRouter();

2
src/views/zhongchou/index.vue

@ -1,10 +1,12 @@
<template> <template>
<div> <div>
众筹 众筹
<Mascot />
</div> </div>
</template> </template>
<script setup> <script setup>
import Mascot from "../choujiang/lottery/Mascot.vue";
</script> </script>

Loading…
Cancel
Save