Browse Source

Merge branch 'milestone-20250722-抽奖' of http://39.101.133.168:8807/hongxilin/activityLink into pangluotong/feature-20250712103401-抽奖

songtongtong/feature-20250717104937-众筹
pangluotong 4 weeks ago
parent
commit
70d6fd0bd3
  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. 32
      src/views/choujiang/index.vue
  6. 92
      src/views/choujiang/lottery/CardItem.vue
  7. 100
      src/views/choujiang/lottery/Lottery3D.vue
  8. 54
      src/views/choujiang/lottery/Mascot.vue
  9. 29
      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){
return request({
url: '/lottery/draw',
url: '/lottery/start',
method: 'post',
data: {
gradeName: data.gradeName,
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 lastRevealedIdx = ref(-1)
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) {
lotteryState.value = state
}
@ -18,12 +39,34 @@ export const useLotteryStore = defineStore('lottery', () => {
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 {
lotteryState,
setLotteryState,
lastRevealedIdx,
setLastRevealedIdx,
waitingForNextReveal,
setWaitingForNextReveal
setWaitingForNextReveal,
winners,
setWinners,
clearWinners,
allUsers,
isUsersLoaded,
setAllUsers,
getRandomUserName
}
})

32
src/views/choujiang/index.vue

@ -1,9 +1,7 @@
<template>
<div class="choujiang-main">
<Lottery3D ref="lottery3DRef" />
<PrizePanel
:prizes="dataManager.state.basicData.prizes"
/>
<PrizePanel :prizes="dataManager.state.basicData.prizes" />
<ControlBar
:lottery-state="lotteryState"
:is-disabled="isDisabled"
@ -12,6 +10,7 @@
@export="handleExport"
/>
<MusicPlayer />
<Mascot />
<!-- 透明弹窗 -->
<div v-if="showPrizeExhaustedModal" class="prize-exhausted-modal">
@ -39,6 +38,7 @@ import ControlBar from "./lottery/ControlBar.vue";
import MusicPlayer from "./lottery/MusicPlayer.vue";
import Qipao from "./lottery/Qipao.vue";
import UserList from "./lottery/UserList.vue";
import Mascot from "./lottery/Mascot.vue";
import { ref, onMounted, nextTick, computed, watch } from "vue";
import { useDataManager } from "./lottery/dataManager.js";
import { useLotteryEngine } from "./lottery/lotteryEngine.js";
@ -68,7 +68,6 @@ const waitingForNextReveal = computed({
set: (val) => lotteryStore.setWaitingForNextReveal(val),
});
const isDisabled = ref(false);
watch(isDisabled, (newVal, oldVal) => {
@ -100,7 +99,9 @@ function showLotteryQipao() {
const prize = dataManager.state.currentPrize;
if (!luckys || luckys.length === 0) return;
// 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 || ""}`;
showQipao.value = true;
setTimeout(() => {
@ -118,14 +119,16 @@ async function handleLotteryClick() {
switch (lotteryState.value) {
case "idle":
//
await lottery3DRef.value?.switchScreen?.("lottery");
console.log("lotteryState 变更前:", lotteryState.value, "-> ready");
// await new Promise((resolve) => setTimeout(resolve, 2000));
lotteryState.value = "ready";
console.log("lotteryState 变更后:", lotteryState.value);
await lottery3DRef.value?.switchScreen?.("lottery");
break;
case "ready":
if(waitingForNextReveal.value){
if (waitingForNextReveal.value) {
console.log("waitingForNextReveal.value", waitingForNextReveal.value);
//
showPrizeExhaustedModal.value = true;
@ -135,13 +138,11 @@ async function handleLotteryClick() {
break;
}
if(lastRevealed.value===-1){
if (lastRevealed.value === -1) {
console.log("lastRevealed.value", lastRevealed.value);
break;
}
//
console.log("lotteryState 变更前:", lotteryState.value, "-> rotating");
lotteryState.value = "rotating";
@ -167,7 +168,7 @@ async function handleLotteryClick() {
// result
await lottery3DRef.value?.switchScreen?.("lottery");
await new Promise(resolve => setTimeout(resolve, 2000));
await new Promise((resolve) => setTimeout(resolve, 2000));
//
lottery3DRef.value?.changeCard1?.();
@ -212,12 +213,13 @@ function handleNextPrize() {
.choujiang-main {
width: 100vw;
height: 100vh;
position: relative;
position: fixed;
top: 0;
left: 0;
overflow: hidden;
/* 添加背景图片 */
background: url('../../assets/登录.png') ;
background: url("../../assets/bg@2x.png");
background-size: 1920px 980px;
}
/* 透明弹窗样式 */

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

@ -9,12 +9,12 @@
>
<!-- <div class="company">{{ company }}</div> -->
<!-- <div class="name">{{ user[1] }}</div> -->
<div class="details">{{ (user[0] || "") + "\n" }}</div>
<div class="details">{{ displayText }}</div>
</div>
</template>
<script setup>
import { computed } from "vue";
import { computed, ref, onMounted, onBeforeUnmount, watch } from "vue";
import { useLotteryStore } from "../../../store/lottery"; //
@ -33,6 +33,61 @@ const props = defineProps({
highlight: 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 baseStyle = {
@ -61,6 +116,38 @@ const cardStyle = computed(() => {
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>
<style scoped>
@ -93,5 +180,6 @@ const cardStyle = computed(() => {
flex-direction: column;
justify-content: center;
height: 100%;
transition: opacity 0.3s ease;
}
</style>

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

@ -18,6 +18,7 @@
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, defineExpose, watch, computed } from "vue";
import { ref, onMounted, onBeforeUnmount, defineExpose, watch } from "vue";
import * as THREE from "three";
@ -31,6 +32,16 @@ import { NUMBER_MATRIX } from "../../../utils/config.js";
import CardItem from "./CardItem.vue";
import { createApp } from "vue";
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 swapInterval = ref(null);
const swapIntervalTime = ref(200)
const threeContainer = ref(null);
@ -702,93 +713,22 @@ function getTotalCards() {
COMPANY: "演示公司",
};
const userList = await getUserList();
console.log("userList", userList);
// lotteryStore.setWinners(userList);
// 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],
// ];
// 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: (160 * config.ROW_COUNT - 20) / 2,
y: (120 * config.ROW_COUNT - 20) / 2,
};
createCards(member, length, showTable, position, config); // 3.
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>

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

@ -169,7 +169,7 @@
<ul class="winner-list">
<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.jwcode }}</span>
<span>{{ user.prize }}</span>
</li>
</ul>
@ -182,8 +182,10 @@
</template>
<script setup>
import { ref, computed, nextTick } from "vue";
import { ref, computed, nextTick, watch } from "vue";
import { useLotteryStore } from "../../../store/lottery";
const props = defineProps({
prizes: Array,
});
@ -199,6 +201,18 @@ const waitingForNextReveal = computed({
get: () => lotteryStore.waitingForNextReveal,
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);
lastRevealedIdx.value = lastRevealed.value;
@ -267,13 +281,10 @@ function getLeftCount(prize) {
//
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() {
// showWinnerList.value = true;
if (!showWinnerList.value) {

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

@ -16,6 +16,10 @@
</template>
<script setup>
import { useLotteryStore } from "../../../store/lottery";
const lotteryStore = useLotteryStore();
const props = defineProps({
luckyUsers: {
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 { drawLottery } from '../../../api/API'; // 导入新的抽奖接口
@ -6,6 +6,19 @@ function getRandomInt(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) {
const isLotting = ref(false);
const lotteryStore = useLotteryStore(); // 只获取一次
@ -40,6 +53,11 @@ export function useLotteryEngine(dataManager, renderer3D) {
console.log('请求后端抽奖,参数:', 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);
if (response && response.data && Array.isArray(response.data)) {

2
src/views/homePage.vue

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

2
src/views/zhongchou/index.vue

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

Loading…
Cancel
Save