You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
12 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <div class="choujiang-main">
  3. <Lottery3D ref="lottery3DRef" />
  4. <PrizePanel :prizes="dataManager.state.basicData.prizes" />
  5. <ControlBar
  6. :lottery-state="lotteryState"
  7. :is-disabled="isDisabled"
  8. @lottery-click="handleLotteryClick"
  9. @reset="handleReset"
  10. @export="handleExport"
  11. />
  12. <!-- <MusicPlayer ref="musicPlayerRef" /> -->
  13. <Mascot />
  14. <!-- 透明弹窗 -->
  15. <div v-if="showPrizeExhaustedModal" class="prize-exhausted-modal">
  16. <div class="modal-content">
  17. <p class="modal-text">该礼品已抽取完毕请揭秘下一个礼品</p>
  18. </div>
  19. </div>
  20. <div v-if="showPrizeExhaustedModal1" class="prize-exhausted-modal">
  21. <div class="modal-content">
  22. <p class="modal-text">请先揭秘一个礼品</p>
  23. </div>
  24. </div>
  25. <div v-if="showPrizeExhaustedModal2" class="prize-exhausted-modal">
  26. <div class="modal-content">
  27. <p class="modal-text">该礼品已抽取完毕</p>
  28. </div>
  29. </div>
  30. <!-- <UserList
  31. :lucky-users="
  32. dataManager.state.basicData.luckyUsers[
  33. dataManager.state.currentPrize?.type
  34. ] || []
  35. "
  36. :left-users="dataManager.state.basicData.leftUsers"
  37. /> -->
  38. <!-- <Qipao :text="qipaoText" :show="showQipao" /> -->
  39. </div>
  40. <audio ref="audioRef" :src="musicSrc" loop preload="auto"></audio>
  41. <audio ref="audioRef1" :src="musicSrc1" preload="auto"></audio>
  42. </template>
  43. <script setup>
  44. import Lottery3D from "./lottery/Lottery3D.vue";
  45. import PrizePanel from "./lottery/PrizePanel.vue";
  46. import ControlBar from "./lottery/ControlBar.vue";
  47. // import MusicPlayer from "./lottery/MusicPlayer.vue";
  48. import Qipao from "./lottery/Qipao.vue";
  49. import UserList from "./lottery/UserList.vue";
  50. import Mascot from "./lottery/Mascot.vue";
  51. import { ref, onMounted, nextTick, computed, watch } from "vue";
  52. import { useDataManager } from "./lottery/dataManager.js";
  53. import { useLotteryEngine } from "./lottery/lotteryEngine.js";
  54. import { useLotteryStore } from "../../store/lottery"; // 路径根据实际情况调整
  55. import { drawLottery } from "../../api/API";
  56. const qipaoText = ref("");
  57. const showQipao = ref(false);
  58. const showPrizeExhaustedModal = ref(false);
  59. const showPrizeExhaustedModal1 = ref(false);
  60. const showPrizeExhaustedModal2 = ref(false);
  61. // const lotteryState = ref('idle'); // idle, ready, rotating, result
  62. // 新增
  63. const lotteryStore = useLotteryStore();
  64. const lotteryState = computed({
  65. get: () => lotteryStore.lotteryState,
  66. set: (val) => lotteryStore.setLotteryState(val),
  67. });
  68. const lastRevealed = computed({
  69. get: () => lotteryStore.lastRevealedIdx,
  70. set: (val) => lotteryStore.setLastRevealedIdx(val),
  71. });
  72. const waitingForNextReveal = computed({
  73. get: () => lotteryStore.waitingForNextReveal,
  74. set: (val) => lotteryStore.setWaitingForNextReveal(val),
  75. });
  76. const winnerList = computed({
  77. get: () => lotteryStore.winnerList,
  78. set: (val) => lotteryStore.setWinnerList(val),
  79. });
  80. const isDisabled = ref(false);
  81. watch(isDisabled, (newVal, oldVal) => {
  82. console.log("isDisabled 变化:", oldVal, "->", newVal);
  83. });
  84. // 数据与抽奖主流程
  85. const dataManager = useDataManager();
  86. let lottery3DRef = ref(null);
  87. let musicPlayerRef = ref(null);
  88. const lotteryEngine = useLotteryEngine(dataManager, {
  89. resetCard: (...args) => lottery3DRef.value?.resetCard?.(...args),
  90. addHighlight: (...args) => lottery3DRef.value?.addHighlight?.(...args),
  91. switchScreen: (...args) => lottery3DRef.value?.switchScreen?.(...args),
  92. rotateBallStart: (...args) => lottery3DRef.value?.rotateBallStart?.(...args),
  93. rotateBallStop: (...args) => lottery3DRef.value?.rotateBallStop?.(...args),
  94. selectCard: (...args) => lottery3DRef.value?.selectCard?.(...args),
  95. });
  96. onMounted(async () => {
  97. isDisabled.value = true;
  98. await dataManager.getBasicData();
  99. await dataManager.getUsers();
  100. setTimeout(() => {
  101. isDisabled.value = false;
  102. }, 3800);
  103. // 将 dataManager 挂载到 window 对象,供子组件使用
  104. window.dataManager = dataManager;
  105. // 预加载音频文件以减少播放延迟
  106. preloadAudio();
  107. });
  108. // 预加载音频文件
  109. function preloadAudio() {
  110. if (audioRef.value) {
  111. audioRef.value.load();
  112. // 设置音频缓冲
  113. audioRef.value.addEventListener('canplaythrough', () => {
  114. console.log('背景音乐预加载完成');
  115. });
  116. }
  117. if (audioRef1.value) {
  118. audioRef1.value.load();
  119. audioRef1.value.addEventListener('canplaythrough', () => {
  120. console.log('音效预加载完成');
  121. });
  122. }
  123. }
  124. function showLotteryQipao() {
  125. const luckys = dataManager.state.currentLuckys;
  126. const prize = dataManager.state.currentPrize;
  127. if (!luckys || luckys.length === 0) return;
  128. // 适配新的数据格式,支持 jwcode 和 username
  129. const names = luckys
  130. .map((item) => item.username || item[1] || item.jwcode || "")
  131. .join("、");
  132. qipaoText.value = `恭喜${names}获得${prize?.title || ""}`;
  133. showQipao.value = true;
  134. setTimeout(() => {
  135. showQipao.value = false;
  136. }, 3000);
  137. }
  138. import musicFile from "/src/assets/worldcup.mp3";
  139. import musicFile1 from "/src/assets/dong.mp3";
  140. const musicSrc = musicFile;
  141. const audioRef = ref(null);
  142. const playing = ref(false);
  143. const musicSrc1 = musicFile1;
  144. const audioRef1 = ref(null);
  145. const playing1 = ref(false);
  146. async function toggleMusic() {
  147. if (!audioRef.value) return;
  148. try {
  149. if (audioRef.value.paused) {
  150. // 确保音频已经准备好播放
  151. if (audioRef.value.readyState < 2) {
  152. await new Promise((resolve) => {
  153. audioRef.value.addEventListener('canplay', resolve, { once: true });
  154. audioRef.value.load();
  155. });
  156. }
  157. await audioRef.value.play();
  158. playing.value = true;
  159. } else {
  160. audioRef.value.pause();
  161. playing.value = false;
  162. }
  163. } catch (error) {
  164. console.error('播放音乐失败:', error);
  165. }
  166. }
  167. async function playMusic1() {
  168. if (!audioRef1.value) return;
  169. try {
  170. // 重置音频到开始位置
  171. audioRef1.value.currentTime = 0;
  172. // 确保音频已经准备好播放
  173. if (audioRef1.value.readyState < 2) {
  174. await new Promise((resolve) => {
  175. audioRef1.value.addEventListener('canplay', resolve, { once: true });
  176. audioRef1.value.load();
  177. });
  178. }
  179. await audioRef1.value.play();
  180. playing1.value = true;
  181. // 监听音频播放结束事件
  182. audioRef1.value.addEventListener('ended', () => {
  183. playing1.value = false;
  184. }, { once: true });
  185. } catch (error) {
  186. console.error('播放音效失败:', error);
  187. }
  188. }
  189. function isPlaying1() {
  190. return playing1.value;
  191. }
  192. // 检查是否正在播放
  193. function isPlaying() {
  194. return playing.value;
  195. }
  196. async function handleLotteryClick() {
  197. if (isDisabled.value) return; // 2秒内不能重复点击
  198. isDisabled.value = true;
  199. // setTimeout(() => {
  200. // isDisabled.value = false;
  201. // }, 2000);
  202. switch (lotteryState.value) {
  203. case "idle":
  204. setTimeout(() => {
  205. isDisabled.value = false;
  206. }, 2000);
  207. // 先切换到球体布局
  208. await lottery3DRef.value?.switchScreen?.("lottery");
  209. console.log("lotteryState 变更前:", lotteryState.value, "-> ready");
  210. // await new Promise((resolve) => setTimeout(resolve, 2000));
  211. lotteryState.value = "ready";
  212. console.log("lotteryState 变更后:", lotteryState.value);
  213. break;
  214. case "ready":
  215. if (waitingForNextReveal.value) {
  216. console.log("waitingForNextReveal.value", waitingForNextReveal.value);
  217. // 显示弹窗提示
  218. showPrizeExhaustedModal.value = true;
  219. setTimeout(() => {
  220. showPrizeExhaustedModal.value = false;
  221. }, 1000);
  222. isDisabled.value = false;
  223. break;
  224. }
  225. if (lastRevealed.value === -1) {
  226. console.log("lastRevealed.value", lastRevealed.value);
  227. showPrizeExhaustedModal1.value = true;
  228. setTimeout(() => {
  229. showPrizeExhaustedModal1.value = false;
  230. }, 1000);
  231. isDisabled.value = false;
  232. break;
  233. }
  234. if (
  235. lastRevealed.value === 0 &&
  236. dataManager.state.basicData.prizes[lastRevealed.value].remainNum === 0
  237. ) {
  238. // 如果是最后一个奖品且剩余数量为0,则跳出
  239. // const currentPrize = dataManager.state.basicData.prizes[lastRevealed.value];
  240. // if (currentPrize && currentPrize.remainNum === 0) {
  241. showPrizeExhaustedModal2.value = true;
  242. setTimeout(() => {
  243. showPrizeExhaustedModal2.value = false;
  244. }, 1000);
  245. isDisabled.value = false;
  246. break;
  247. // }
  248. }
  249. toggleMusic();
  250. console.log("lotteryState 变更前:", lotteryState.value, "-> rotating");
  251. lotteryState.value = "rotating";
  252. console.log("lotteryState 变更后:", lotteryState.value);
  253. const prize = dataManager.state.basicData.prizes[lastRevealed.value];
  254. console.log("准备调用 drawLottery,prize:", prize);
  255. console.log("lastRevealed.value:", lastRevealed.value);
  256. // 先让球转起来,不等它结束
  257. const rotatePromise = lottery3DRef.value?.rotateBallStart?.();
  258. // 同时请求接口
  259. try {
  260. winnerList.value = await drawLottery({
  261. perWin: prize.perWin,
  262. remainNum: prize.remainNum,
  263. gradeId: prize.gradeId,
  264. prizeId: prize.prizeId,
  265. });
  266. setTimeout(() => {
  267. isDisabled.value = false;
  268. }, 2000);
  269. console.log("drawLottery 调用成功,结果:", winnerList.value);
  270. // 开奖成功后更新获奖名单数据
  271. // if (window.dataManager && window.dataManager.updatePrizeList) {
  272. // try {
  273. // await window.dataManager.updatePrizeList();
  274. // console.log("开奖后获奖名单数据已更新");
  275. // } catch (error) {
  276. // console.error("更新获奖名单数据失败:", error);
  277. // }
  278. // }
  279. } catch (error) {
  280. console.error("drawLottery 调用失败:", error);
  281. }
  282. // 如果你还需要等球转完再做别的,可以 await rotatePromise
  283. break;
  284. case "rotating":
  285. setTimeout(() => {
  286. isDisabled.value = false;
  287. }, 2000);
  288. // toggleMusic();
  289. // toggleMusic();
  290. await lottery3DRef.value?.rotateBallStop?.();
  291. toggleMusic();
  292. playMusic1();
  293. await lotteryEngine.executeLottery();
  294. await nextTick();
  295. showLotteryQipao();
  296. console.log("lotteryState 变更前:", lotteryState.value, "-> idle");
  297. lotteryState.value = "result";
  298. console.log("lotteryState 变更后:", lotteryState.value);
  299. break;
  300. case "result":
  301. setTimeout(() => {
  302. isDisabled.value = false;
  303. }, 2800);
  304. // result 状态下点击不做任何事,或者你可以加提示
  305. await lottery3DRef.value?.switchScreen?.("lottery");
  306. await new Promise((resolve) => setTimeout(resolve, 2500));
  307. // 去除高光
  308. lottery3DRef.value?.changeCard1?.();
  309. //延迟2秒
  310. lotteryState.value = "ready";
  311. break;
  312. default:
  313. break;
  314. }
  315. }
  316. function handleReset() {
  317. lotteryEngine.resetLottery();
  318. }
  319. function handleExport() {
  320. dataManager.exportData();
  321. }
  322. function handlePrevPrize() {
  323. if (dataManager.state.currentPrizeIndex > 0) {
  324. dataManager.state.currentPrizeIndex--;
  325. dataManager.state.currentPrize =
  326. dataManager.state.basicData.prizes[dataManager.state.currentPrizeIndex];
  327. }
  328. }
  329. function handleNextPrize() {
  330. if (
  331. dataManager.state.currentPrizeIndex <
  332. dataManager.state.basicData.prizes.length - 1
  333. ) {
  334. dataManager.state.currentPrizeIndex++;
  335. dataManager.state.currentPrize =
  336. dataManager.state.basicData.prizes[dataManager.state.currentPrizeIndex];
  337. }
  338. }
  339. </script>
  340. <style scoped>
  341. .choujiang-main {
  342. width: 100vw;
  343. height: 100vh;
  344. position: fixed;
  345. top: 0;
  346. left: 0;
  347. overflow: hidden;
  348. /* 添加背景图片 */
  349. background: url("../../assets/bg@2x.png") ;
  350. background-size: 100% 100%;
  351. }
  352. /* 透明弹窗样式 */
  353. .prize-exhausted-modal {
  354. position: fixed;
  355. top: 0;
  356. left: 0;
  357. width: 100%;
  358. height: 100%;
  359. display: flex;
  360. justify-content: center;
  361. align-items: flex-start;
  362. padding-top: 20vh;
  363. z-index: 9999;
  364. pointer-events: none;
  365. }
  366. .modal-content {
  367. background: transparent;
  368. padding: 20px 30px;
  369. border-radius: 10px;
  370. animation: fadeInOut 1s ease-in-out;
  371. }
  372. .modal-text {
  373. position: fixed !important;
  374. top: 300px;
  375. left: 50% !important;
  376. transform: translate(-50%, -50%) !important;
  377. color: #ff0000;
  378. font-size: 50px;
  379. font-weight: bold;
  380. margin: 0;
  381. white-space: nowrap;
  382. }
  383. @keyframes fadeInOut {
  384. 0% {
  385. opacity: 0;
  386. }
  387. 20% {
  388. opacity: 1;
  389. }
  390. 80% {
  391. opacity: 1;
  392. }
  393. 100% {
  394. opacity: 0;
  395. }
  396. }
  397. </style>