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.

2055 lines
45 KiB

1 month ago
4 weeks ago
4 weeks ago
4 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
4 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
4 weeks ago
1 month ago
1 month ago
4 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
4 weeks ago
4 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <div class="homepage">
  3. <img src="../../../assets/qilin.webp" alt="麒麟" class="hllogo" />
  4. <!-- 添加worldcup音频元素 -->
  5. <audio ref="worldcupAudioRef" :src="worldcup" preload="auto" loop></audio>
  6. <!-- 添加dong音频元素 -->
  7. <audio ref="dongAudioRef" :src="dong" preload="auto"></audio>
  8. <div ref="qipaoTextRef" class="qipao">{{ qipaoText }}</div>
  9. <div class="leftBar">
  10. <el-scrollbar id="prizeBar">
  11. <ul class="prize-list">
  12. <li
  13. v-for="item in prizes"
  14. :key="item.type"
  15. :id="`prize-item-${item.type}`"
  16. :class="['prize-item']"
  17. @click="lookPrize(item)"
  18. >
  19. <div
  20. v-if="item.isLook"
  21. style="display: flex; width: 100%; height: 100%"
  22. >
  23. <span></span><span></span><span></span><span></span>
  24. <div class="prize-img">
  25. <img :src="item.imageUrl" :alt="item.title" />
  26. </div>
  27. <div class="prize-text">
  28. <div class="prize-title">
  29. <div class="level">
  30. {{ item.gradeName }}
  31. </div>
  32. {{ item.prizeName }}
  33. </div>
  34. <div class="prize-count">
  35. <div class="progress">
  36. <div
  37. :id="`prize-bar-${item.type}`"
  38. class="progress-bar progress-bar-danger progress-bar-striped active"
  39. style="width: 100%"
  40. ></div>
  41. </div>
  42. <div
  43. :id="`prize-count-${item.type}`"
  44. class="prize-count-left"
  45. >
  46. {{ item.leftCount }}/{{ item.count }}
  47. </div>
  48. </div>
  49. </div>
  50. </div>
  51. <div v-else style="display: flex; width: 100%; height: 100%">
  52. <img
  53. src="../../../assets/img/待揭秘.png"
  54. alt="待揭秘"
  55. class="readyLook"
  56. />
  57. </div>
  58. </li>
  59. </ul>
  60. </el-scrollbar>
  61. <div v-if="!isOpen">
  62. <div class="getPrizeName" @click="openGetPrize()">
  63. <img src="../../../assets/img/展开.png" alt="展开" class="open" />
  64. 获奖名单
  65. </div>
  66. </div>
  67. <div v-else>
  68. <div class="dgetPrizeName">
  69. <img
  70. src="../../../assets/img/展开.png"
  71. alt="展开"
  72. class="close"
  73. @click="closeGetPrize()"
  74. />
  75. <div class="tableHead">
  76. <div class="tableHead1">HomilyID</div>
  77. <div class="tableHead2">奖项</div>
  78. <div class="gradient-line"></div>
  79. </div>
  80. <el-scrollbar class="tableBody">
  81. <div v-for="item in getPrizeUserList" :key="item">
  82. <div class="tableItem">
  83. <div class="tableItem1">
  84. {{ item.jwcode }}
  85. </div>
  86. <div class="tableItem2">
  87. {{ item.gradeName }}
  88. </div>
  89. </div>
  90. </div>
  91. </el-scrollbar>
  92. <div class="tableFoot">
  93. <span @click="leftPage()" style="cursor: pointer"><</span>
  94. {{ currentPage }}
  95. <span @click="rightPage()" style="cursor: pointer">></span>
  96. <span class="tableTotalPage">
  97. <span>{{ totalPage }}</span>
  98. </span>
  99. </div>
  100. </div>
  101. </div>
  102. </div>
  103. <div id="container">
  104. <div ref="threeContainer" class="three-container"></div>
  105. <!-- 分页指示器 -->
  106. <div v-if="pageMaxIndex > 1" class="page-indicator">
  107. {{ pageIndex + 1 }} / {{ pageMaxIndex }}
  108. </div>
  109. </div>
  110. <div id="menu">
  111. <div id="enter" ref="enterRef" @click="enterLottery()" class="btn">
  112. 进入抽奖
  113. </div>
  114. <div id="lotteryBar" ref="lotteryBarRef" class="none">
  115. <div id="lottery" ref="lotteryRef" @click="lotteryBtn()" class="btn">
  116. 开始抽奖
  117. </div>
  118. </div>
  119. </div>
  120. </div>
  121. </template>
  122. <script setup>
  123. import { ref, onMounted, reactive, nextTick } from "vue";
  124. import { useRouter } from "vue-router";
  125. import * as THREE from "three";
  126. import axios from "axios";
  127. import "../../../assets/css/animate.min.css";
  128. import { resetPrize } from "../../../utils/prizeList";
  129. import { NUMBER_MATRIX } from "../../../utils/config";
  130. import { CSS3DObject, CSS3DRenderer } from "../../../utils/CSS3DRenderer.js";
  131. import { TrackballControls } from "../../../utils/TrackballControls.js";
  132. import TWEEN from "@tweenjs/tween.js";
  133. import worldcup from "../../../assets/worldcup.mp3";
  134. import dong from "../../../assets/dong.mp3";
  135. import {
  136. getPrizeListApi,
  137. getUserListApi,
  138. getGetPrizeUserListApi,
  139. startLotteryApi,
  140. } from "../../../api/API";
  141. // 常量
  142. const ROTATE_TIME = 10000; //旋转动画的总时长
  143. const ROTATE_LOOP = 1000;
  144. const BASE_HEIGHT = 1080; //高度
  145. // 按钮控制
  146. const enterRef = ref(null); //进入抽奖按钮
  147. const lotteryBarRef = ref(null); //开始抽奖元素组(显示控制)
  148. const lotteryRef = ref(null); //开始抽奖按钮(文字控制 )
  149. //3D相关变量
  150. // 当前的比例
  151. let Resolution = 0.9;
  152. //总卡片数
  153. let TOTAL_CARDS;
  154. // 高度卡片数
  155. let ROW_COUNT = 7;
  156. // 宽度卡片数
  157. let COLUMN_COUNT = 21;
  158. // 高亮卡片数组
  159. let HIGHLIGHT_CELL = [];
  160. // 其他变量
  161. let camera,
  162. scene,
  163. renderer,
  164. controls,
  165. threeDCards = [],
  166. targets = {
  167. table: [],
  168. sphere: [],
  169. };
  170. // 动画对象
  171. let rotateObj;
  172. // 控制旋转
  173. let rotate = false;
  174. //奖品列表
  175. const prizes = ref([]);
  176. //每个奖品每次抽取的数目
  177. let EACH_COUNT = [];
  178. const users = ref([]);
  179. const getPrizeUsers = ref([]);
  180. //判断是否正在抽奖
  181. let isLotting = false;
  182. const setLotteryStatus = (status = false) => {
  183. isLotting = status;
  184. };
  185. let selectedCardIndex = [];
  186. let interval;
  187. // 当前抽的奖项,从最低奖开始抽,直到抽到大奖
  188. let currentPrizeIndex;
  189. const currentPrize = ref({});
  190. let currentLuckys = [];
  191. let prizeElement = {};
  192. const qipaoText = ref("");
  193. const qipaoTextRef = ref(null);
  194. const worldcupAudioRef = ref(null);
  195. const dongAudioRef = ref(null);
  196. // 播放worldcup音频
  197. const playWorldcupAudio = () => {
  198. if (worldcupAudioRef.value) {
  199. worldcupAudioRef.value.currentTime = 0; // 重置播放位置
  200. worldcupAudioRef.value.play().catch((error) => {
  201. console.log("音频播放失败:", error);
  202. });
  203. }
  204. };
  205. // 暂停worldcup音频
  206. const pauseWorldcupAudio = () => {
  207. if (worldcupAudioRef.value) {
  208. worldcupAudioRef.value.pause();
  209. }
  210. };
  211. const playDongAudio = () => {
  212. if (dongAudioRef.value) {
  213. dongAudioRef.value.currentTime = 0; // 重置播放位置
  214. dongAudioRef.value.play().catch((error) => {
  215. console.log("音频播放失败:", error);
  216. });
  217. }
  218. };
  219. // 暂停dong音频
  220. const pauseDongAudio = () => {
  221. if (dongAudioRef.value) {
  222. dongAudioRef.value.pause();
  223. }
  224. };
  225. const addQipao = (text) => {
  226. qipaoTextRef.value.style.display = "block";
  227. qipaoTextRef.value.style.opacity = "1";
  228. qipaoText.value = text;
  229. setTimeout(() => {
  230. // 设置过渡效果
  231. qipaoTextRef.value.style.transition = "opacity 0.5s ease-out";
  232. // 开始渐隐
  233. qipaoTextRef.value.style.opacity = "0";
  234. // 动画完成后隐藏元素
  235. setTimeout(() => {
  236. qipaoTextRef.value.style.display = "none";
  237. }, 800);
  238. }, 1000);
  239. };
  240. const getPrizeUserList = ref([]);
  241. const isOpen = ref(false);
  242. const pageObj = ref({
  243. pageNum: 1,
  244. pageSize: 14,
  245. });
  246. const currentPage = ref(1);
  247. const totalPage = ref(10);
  248. const openGetPrize = async () => {
  249. if (!prizes.value[prizes.value.length - 1].isLook) {
  250. addQipao("请先揭晓奖品。");
  251. return;
  252. }
  253. let res = await getGetPrizeUserListApi(pageObj.value);
  254. getPrizeUserList.value = res.data.list;
  255. currentPage.value = res.data.pageNum;
  256. totalPage.value = res.data.pages;
  257. isOpen.value = true;
  258. // console.log("currentPrize", currentPrize.value);
  259. prizes.value.forEach((item) => {
  260. // console.log("item", item);
  261. if (item.type != currentPrize.value.type) {
  262. let box = document.querySelector(`#prize-item-${item.type}`);
  263. box.style.display = "none";
  264. }
  265. });
  266. let scroll = document.getElementById("prizeBar");
  267. // scroll.style.height = "110px";
  268. };
  269. const closeGetPrize = () => {
  270. isOpen.value = false;
  271. prizes.value.forEach((item) => {
  272. // console.log("item", item);
  273. if (item.type != currentPrize.value.type) {
  274. let box = document.querySelector(`#prize-item-${item.type}`);
  275. box.style.display = "flex";
  276. }
  277. });
  278. // let scroll = document.getElementById("prizeBar");
  279. // scroll.style.height = "650px";
  280. };
  281. const leftPage = async (item) => {
  282. if (currentPage.value == 1) return;
  283. currentPage.value--;
  284. pageObj.value.pageNum = currentPage;
  285. let res = await getGetPrizeUserListApi(pageObj.value);
  286. getPrizeUserList.value = res.data.list;
  287. currentPage.value = res.data.pageNum;
  288. };
  289. const rightPage = async (item) => {
  290. if (currentPage.value == totalPage.value) return;
  291. currentPage.value++;
  292. pageObj.value.pageNum = currentPage;
  293. let res = await getGetPrizeUserListApi(pageObj.value);
  294. getPrizeUserList.value = res.data.list;
  295. currentPage.value = res.data.pageNum;
  296. };
  297. //揭示奖品
  298. const lookPrize = async (item) => {
  299. // 未进入抽奖禁止揭晓
  300. if (!joinLottery) {
  301. addQipao("请先进入抽奖。");
  302. return;
  303. }
  304. // 如果已经揭示,那返回
  305. if (item.isLook) return;
  306. //最低级的可以直接揭晓
  307. if (
  308. currentPrize.value.type == prizes.value[prizes.value.length - 1].type &&
  309. item.type == currentPrize.value.type
  310. ) {
  311. let currentBox = document.querySelector(`#prize-item-${item.type}`);
  312. currentBox && currentBox.classList.add("shine");
  313. //点击揭晓
  314. item.isLook = true;
  315. } else if (
  316. !isLotting && //未在抽奖状态
  317. currentPrize.value.isLook &&
  318. currentPrize.value.leftCount == 0 && //当前奖项已抽完
  319. prizes.value[currentPrizeIndex - 1] == item //点击的奖项是当前奖项的下一个
  320. ) {
  321. currentPrize.value = item;
  322. currentPrizeIndex--;
  323. let lastIndex = -1;
  324. for (let i = prizes.value.length - 1; i >= 0; i--) {
  325. if (prizes.value[i].type == item.type) {
  326. if (i != prizes.value.length - 1) {
  327. lastIndex = i + 1;
  328. }
  329. break;
  330. }
  331. }
  332. if (lastIndex != -1) {
  333. let lastPrize = prizes.value[lastIndex];
  334. // console.log("lastPrize", lastPrize);
  335. let lastBox = document.querySelector(`#prize-item-${lastPrize.type}`);
  336. lastBox.classList.remove("shine");
  337. lastBox.classList.add("done");
  338. let currentBox = document.querySelector(`#prize-item-${item.type}`);
  339. currentBox && currentBox.classList.add("shine");
  340. }
  341. //点击揭晓
  342. item.isLook = true;
  343. }
  344. await nextTick();
  345. setPrizeData(currentPrizeIndex);
  346. };
  347. function setPrizeData(currentPrizeIndex, isInit) {
  348. // console.log("prizes", prizes.value);
  349. // let currentPrize = prizes.value[currentPrizeIndex];
  350. let type = currentPrize.value.type;
  351. let elements = prizeElement[type];
  352. let count = currentPrize.value.leftCount;
  353. let totalCount = currentPrize.value.count;
  354. if (!elements || !elements.bar || !elements.text) {
  355. elements = {
  356. box: document.querySelector(`#prize-item-${type}`),
  357. bar: document.querySelector(`#prize-bar-${type}`),
  358. text: document.querySelector(`#prize-count-${type}`),
  359. };
  360. }
  361. if (isInit) {
  362. for (let i = prizes.value.length - 1; i > currentPrizeIndex; i--) {
  363. let type = prizes.value[i]["type"];
  364. document.querySelector(`#prize-item-${type}`).className =
  365. "prize-item done";
  366. document.querySelector(`#prize-bar-${type}`).style.width = "0";
  367. document.querySelector(`#prize-count-${type}`).textContent =
  368. "0" + "/" + prizes.value[i]["count"];
  369. }
  370. }
  371. console.log("count", count);
  372. console.log("totalCount", totalCount);
  373. let percent = (count / totalCount).toFixed(2);
  374. if (elements.bar) {
  375. elements.bar.style.width = percent * 100 + "%";
  376. }
  377. }
  378. /**
  379. * 初始化所有DOM
  380. */
  381. const pageIndex = ref(0);
  382. const pageMaxIndex = ref(0);
  383. const cardsPerPage = 10; // 每页显示的卡片数量
  384. let isPageTransitioning = false; // 防止页面切换时的重复操作
  385. // 全局变量存储当前选中的卡片索引数组
  386. let globalCardIndexes = [];
  387. const initAll = async () => {
  388. const [prizeList, userList] = await Promise.all([
  389. getPrizeListApi(),
  390. getUserListApi(),
  391. ]);
  392. console.log("hxl-cj调用一次接口", prizeList);
  393. // 奖品列表
  394. prizes.value = prizeList.data;
  395. // 用户列表
  396. users.value = userList.data;
  397. prizes.value.forEach((item, index) => {
  398. item.type = index;
  399. item.count = item.amount;
  400. item.leftCount = item.remainNum; //剩余次数(用于计算奖品下方的进度条的百分比)
  401. item.hasCount = item.amount; //已抽次数(用于计算出奖)
  402. item.isLook = false; //
  403. EACH_COUNT.push(item.perWin);
  404. });
  405. console.log("prizes", prizes.value);
  406. console.log("EACH_COUNT", EACH_COUNT);
  407. HIGHLIGHT_CELL = createHighlight();
  408. TOTAL_CARDS = ROW_COUNT * COLUMN_COUNT;
  409. currentPrizeIndex = prizes.value.length - 1;
  410. currentPrize.value = prizes.value[currentPrizeIndex];
  411. await nextTick();
  412. setPrizeData(currentPrizeIndex, true);
  413. initCards();
  414. // startMaoPao();
  415. animate();
  416. shineCard();
  417. // window.AJAX({
  418. // url: "/getUsers",
  419. // success(data) {},
  420. // });
  421. };
  422. const initCards = () => {
  423. let member = users.value.slice(),
  424. showCards = [],
  425. length = member.length;
  426. let isBold = false;
  427. let index = 0;
  428. let totalMember = member.length;
  429. let position = {
  430. x: (140 * COLUMN_COUNT - 20) / 2,
  431. y: (180 * ROW_COUNT - 20) / 2,
  432. };
  433. camera = new THREE.PerspectiveCamera(
  434. 40,
  435. window.innerWidth / window.innerHeight,
  436. 1,
  437. 10000
  438. );
  439. camera.position.z = 3000;
  440. scene = new THREE.Scene();
  441. for (let i = 0; i < ROW_COUNT; i++) {
  442. for (let j = 0; j < COLUMN_COUNT; j++) {
  443. isBold = HIGHLIGHT_CELL.includes(j + "-" + i);
  444. var element = createCard(member[index % length], isBold, index);
  445. var object = new CSS3DObject(element);
  446. object.position.x = Math.random() * 4000 - 2000;
  447. object.position.y = Math.random() * 4000 - 2000;
  448. object.position.z = Math.random() * 4000 - 2000;
  449. scene.add(object);
  450. threeDCards.push(object);
  451. //
  452. var object = new THREE.Object3D();
  453. object.position.x = j * 155 - position.x;
  454. object.position.y = -(i * 195) + position.y;
  455. targets.table.push(object);
  456. index++;
  457. }
  458. }
  459. // sphere
  460. var vector = new THREE.Vector3();
  461. for (var i = 0, l = threeDCards.length; i < l; i++) {
  462. var phi = Math.acos(-1 + (2 * i) / l);
  463. var theta = Math.sqrt(l * Math.PI) * phi;
  464. var object = new THREE.Object3D();
  465. object.position.setFromSphericalCoords(800 * Resolution, phi, theta);
  466. vector.copy(object.position).multiplyScalar(2);
  467. object.lookAt(vector);
  468. targets.sphere.push(object);
  469. }
  470. renderer = new CSS3DRenderer();
  471. renderer.setSize(window.innerWidth * 1, window.innerHeight * 0.9);
  472. renderer.domElement.style.margin = "7% 0 0 1%";
  473. // document.getElementById("container").appendChild(renderer.domElement);
  474. if (threeContainer.value) {
  475. threeContainer.value.appendChild(renderer.domElement);
  476. }
  477. //
  478. controls = new TrackballControls(camera, renderer.domElement);
  479. controls.rotateSpeed = 0.5;
  480. controls.minDistance = 500;
  481. controls.maxDistance = 6000;
  482. controls.addEventListener("change", render);
  483. switchScreen("enter");
  484. };
  485. // 判断是否进入抽奖
  486. let joinLottery = false;
  487. // 进入抽奖
  488. const enterLottery = () => {
  489. joinLottery = true;
  490. removeHighlight();
  491. // rotate = !rotate;
  492. rotate = true;
  493. switchScreen("lottery");
  494. };
  495. function switchScreen(type) {
  496. switch (type) {
  497. case "enter":
  498. if (enterRef.value) {
  499. enterRef.value.classList.remove("none");
  500. }
  501. if (lotteryBarRef.value) {
  502. lotteryBarRef.value.classList.add("none");
  503. }
  504. transform(targets.table, 2000);
  505. break;
  506. default:
  507. if (enterRef.value) {
  508. enterRef.value.classList.add("none");
  509. }
  510. if (lotteryBarRef.value) {
  511. lotteryBarRef.value.classList.remove("none");
  512. }
  513. transform(targets.sphere, 2000);
  514. break;
  515. }
  516. }
  517. /**
  518. * 创建元素
  519. */
  520. const createElement = (css, text) => {
  521. let dom = document.createElement("div");
  522. dom.className = css || "";
  523. dom.innerHTML = text || "";
  524. return dom;
  525. };
  526. /**
  527. * 创建名牌
  528. */
  529. const createCard = (user, isBold, id) => {
  530. var element = createElement();
  531. element.id = "card-" + id;
  532. element.style.display = "flex";
  533. element.style.alignItems = "center";
  534. element.style.justifyContent = "center";
  535. if (isBold) {
  536. element.className = "element lightitem";
  537. element.classList.add("highlight");
  538. } else {
  539. element.className = "element";
  540. // element.style.backgroundColor =
  541. // "rgba(255,170,22," + (Math.random() * 0.7 + 0.25) + ")";
  542. element.style.backgroundColor = "rgba(255,170,22,1)";
  543. element.style.border = "2px solid rgba(255, 255, 255, 1)";
  544. }
  545. element.appendChild(createElement("name", user.jwcode));
  546. return element;
  547. };
  548. function removeHighlight() {
  549. document.querySelectorAll(".highlight").forEach((node) => {
  550. node.classList.remove("highlight");
  551. });
  552. }
  553. function addHighlight() {
  554. document.querySelectorAll(".lightitem").forEach((node) => {
  555. node.classList.add("highlight");
  556. });
  557. }
  558. /**
  559. * 渲染地球等
  560. */
  561. function transform(targets, duration) {
  562. // TWEEN.removeAll();
  563. for (var i = 0; i < threeDCards.length; i++) {
  564. var object = threeDCards[i];
  565. var target = targets[i];
  566. new TWEEN.Tween(object.position)
  567. .to(
  568. {
  569. x: target.position.x,
  570. y: target.position.y,
  571. z: target.position.z,
  572. },
  573. Math.random() * duration + duration
  574. )
  575. .easing(TWEEN.Easing.Exponential.InOut)
  576. .start();
  577. new TWEEN.Tween(object.rotation)
  578. .to(
  579. {
  580. x: target.rotation.x,
  581. y: target.rotation.y,
  582. z: target.rotation.z,
  583. },
  584. Math.random() * duration + duration
  585. )
  586. .easing(TWEEN.Easing.Exponential.InOut)
  587. .start();
  588. }
  589. new TWEEN.Tween(this)
  590. .to({}, duration * 2)
  591. .onUpdate(render)
  592. .start();
  593. }
  594. function rotateBall() {
  595. return new Promise((resolve, reject) => {
  596. scene.rotation.y = 0;
  597. rotateObj = new TWEEN.Tween(scene.rotation);
  598. rotateObj
  599. .to(
  600. {
  601. y: Math.PI * 6 * ROTATE_LOOP,
  602. },
  603. ROTATE_TIME * ROTATE_LOOP
  604. )
  605. .onUpdate(render)
  606. // .easing(TWEEN.Easing.Linear)
  607. .start()
  608. .onStop(() => {
  609. scene.rotation.y = 0;
  610. resolve();
  611. })
  612. .onComplete(() => {
  613. resolve();
  614. });
  615. });
  616. }
  617. function animate() {
  618. // 让场景通过x轴或者y轴旋转
  619. // rotate && (scene.rotation.y += 0.088);
  620. requestAnimationFrame(animate);
  621. TWEEN.update();
  622. controls.update();
  623. // 渲染循环
  624. // render();
  625. }
  626. function render() {
  627. renderer.render(scene, camera);
  628. }
  629. const threeContainer = ref(null);
  630. function selectCard(duration = 600) {
  631. return new Promise((resolve) => {
  632. const width = 160;
  633. // 计算总页数
  634. pageMaxIndex.value = Math.ceil(currentLuckys.length / cardsPerPage);
  635. pageIndex.value = 0;
  636. // 为每页计算位置信息
  637. const pageLocates = [];
  638. for (let page = 0; page < pageMaxIndex.value; page++) {
  639. const startIndex = page * cardsPerPage;
  640. const endIndex = Math.min(
  641. (page + 1) * cardsPerPage,
  642. currentLuckys.length
  643. );
  644. const pageCount = endIndex - startIndex;
  645. const pageLocate = [];
  646. // 根据当前页的人数决定排列方式
  647. if (pageCount > 5) {
  648. // 大于5个分两排显示(与抽10人时的排列相同)
  649. const yPosition = [-75, 120];
  650. const mid = Math.ceil(pageCount / 2);
  651. let tag = -(mid - 1) / 2;
  652. for (let i = 0; i < mid; i++) {
  653. pageLocate.push({
  654. x: tag * width,
  655. y: yPosition[0],
  656. });
  657. tag++;
  658. }
  659. tag = -(pageCount - mid - 1) / 2;
  660. for (let i = mid; i < pageCount; i++) {
  661. pageLocate.push({
  662. x: tag * width,
  663. y: yPosition[1],
  664. });
  665. tag++;
  666. }
  667. } else {
  668. // 小于等于5个一排显示(与抽不足10人时的排列相同)
  669. let tag = -(pageCount - 1) / 2;
  670. for (let i = 0; i < pageCount; i++) {
  671. pageLocate.push({
  672. x: tag * width,
  673. y: 0,
  674. });
  675. tag++;
  676. }
  677. }
  678. pageLocates.push(pageLocate);
  679. }
  680. // console.log("pageLocates calculated:", pageLocates);
  681. // 初始化所有卡片位置(隐藏超出第一页的卡片)
  682. selectedCardIndex.forEach((cardIndex, index) => {
  683. changeSelectedCard(cardIndex, currentLuckys[index]);
  684. const object = threeDCards[cardIndex];
  685. // 计算卡片应该在第几页
  686. const cardPage = Math.floor(index / cardsPerPage);
  687. const isVisible = cardPage === 0;
  688. // 计算在当前页中的索引
  689. const pageNowIndex = index % cardsPerPage;
  690. const pageLocate = pageLocates[cardPage][pageNowIndex];
  691. new TWEEN.Tween(object.position)
  692. .to(
  693. {
  694. x: isVisible ? pageLocate.x : pageLocate.x,
  695. y: isVisible ? pageLocate.y : pageLocate.y + 1000, // 隐藏的卡片移到下方
  696. z: 2100,
  697. },
  698. Math.random() * duration + duration
  699. )
  700. .easing(TWEEN.Easing.Exponential.InOut)
  701. .start();
  702. new TWEEN.Tween(object.rotation)
  703. .to(
  704. {
  705. x: 0,
  706. y: 0,
  707. z: 0,
  708. },
  709. Math.random() * duration + duration
  710. )
  711. .easing(TWEEN.Easing.Exponential.InOut)
  712. .start();
  713. object.element.classList.add("prize");
  714. });
  715. // 保存页面位置信息到全局变量,供switchPage使用
  716. window.pageLocates = pageLocates;
  717. new TWEEN.Tween({})
  718. .to({}, duration * 2)
  719. .onUpdate(() => render())
  720. .start()
  721. .onComplete(() => {
  722. console.log("selectCard animation completed");
  723. // 如果有多页,添加鼠标滚轮事件监听
  724. if (pageMaxIndex.value > 1) {
  725. // console.log("pageMaxIndex添加滚轮", pageMaxIndex.value);
  726. // 添加滚轮事件监听
  727. addWheelListener();
  728. }
  729. setLotteryStatus();
  730. resolve();
  731. });
  732. });
  733. }
  734. // 分页切换函数
  735. function switchPage(direction) {
  736. if (isPageTransitioning || pageMaxIndex.value <= 1) return;
  737. const newPage =
  738. direction === "next" ? pageIndex.value + 1 : pageIndex.value - 1;
  739. if (newPage < 0 || newPage >= pageMaxIndex.value) return;
  740. isPageTransitioning = true;
  741. const duration = 800;
  742. // 使用保存的页面位置信息
  743. const pageLocates = window.pageLocates;
  744. if (!pageLocates) {
  745. console.error("页面位置信息未找到");
  746. isPageTransitioning = false;
  747. return;
  748. }
  749. // 动画切换卡片位置
  750. globalCardIndexes.forEach((cardIndex, index) => {
  751. const object = threeDCards[cardIndex];
  752. const cardPage = Math.floor(index / cardsPerPage);
  753. const isVisible = cardPage === newPage;
  754. const wasVisible = cardPage === pageIndex.value;
  755. // 计算在当前页中的索引
  756. const pageNowIndex = index % cardsPerPage;
  757. const pageLocate = pageLocates[cardPage][pageNowIndex];
  758. // console.log(
  759. // "cardPage",
  760. // cardPage,
  761. // "pageNowIndex",
  762. // pageNowIndex,
  763. // "pageLocate",
  764. // pageLocate
  765. // );
  766. // 根据切换方向决定动画效果
  767. let targetY;
  768. if (isVisible) {
  769. // 当前页要显示的卡片
  770. if (direction === "next") {
  771. // 索引增大:从下方飞出
  772. targetY = pageLocate.y;
  773. } else {
  774. // 索引减少:从上方飞出
  775. targetY = pageLocate.y;
  776. }
  777. } else {
  778. // 当前页要隐藏的卡片
  779. if (direction === "next") {
  780. // 索引增大:向上飞走
  781. targetY = pageLocate.y + 1000;
  782. } else {
  783. // 索引减少:向下飞走
  784. targetY = pageLocate.y - 1000;
  785. }
  786. }
  787. // 设置起始位置
  788. let startY;
  789. if (wasVisible) {
  790. // 当前页的卡片从当前位置开始
  791. startY = object.position.y;
  792. } else {
  793. // 非当前页的卡片从隐藏位置开始
  794. if (direction === "next") {
  795. // 索引增大:从下方开始
  796. startY = pageLocate.y - 1000;
  797. } else {
  798. // 索引减少:从上方开始
  799. startY = pageLocate.y + 1000;
  800. }
  801. }
  802. // 先设置起始位置
  803. object.position.y = startY;
  804. new TWEEN.Tween(object.position)
  805. .to(
  806. {
  807. x: pageLocate.x,
  808. y: targetY,
  809. z: 2100,
  810. },
  811. duration
  812. )
  813. .easing(TWEEN.Easing.Cubic.InOut)
  814. .start();
  815. });
  816. new TWEEN.Tween({})
  817. .to({}, duration)
  818. .onUpdate(() => render())
  819. .onComplete(() => {
  820. pageIndex.value = newPage;
  821. isPageTransitioning = false;
  822. console.log(
  823. `切换到第 ${pageIndex.value + 1} 页,共 ${pageMaxIndex.value}`
  824. );
  825. })
  826. .start();
  827. }
  828. // 鼠标滚轮事件处理
  829. function handleWheel(event) {
  830. if (isPageTransitioning || pageMaxIndex.value <= 1) return;
  831. event.preventDefault();
  832. if (event.deltaY > 0) {
  833. // 向下滚动,显示下一页
  834. switchPage("next");
  835. } else if (event.deltaY < 0) {
  836. // 向上滚动,显示上一页
  837. switchPage("prev");
  838. }
  839. }
  840. // 添加鼠标滚轮事件监听器
  841. function addWheelListener() {
  842. if (threeContainer.value) {
  843. threeContainer.value.addEventListener("wheel", handleWheel, {
  844. passive: false,
  845. });
  846. }
  847. }
  848. // 移除鼠标滚轮事件监听器
  849. function removeWheelListener() {
  850. if (threeContainer.value) {
  851. threeContainer.value.removeEventListener("wheel", handleWheel);
  852. }
  853. }
  854. /**
  855. * 重置抽奖牌内容
  856. */
  857. function resetCard(duration = 500) {
  858. if (currentLuckys.length === 0) {
  859. return Promise.resolve();
  860. }
  861. globalCardIndexes = [];
  862. // 移除鼠标滚轮事件监听器
  863. removeWheelListener();
  864. // 重置分页状态
  865. pageIndex.value = 0;
  866. pageMaxIndex.value = 0;
  867. isPageTransitioning = false;
  868. // 清理保存的页面位置信息
  869. if (window.pageLocates) {
  870. delete window.pageLocates;
  871. }
  872. selectedCardIndex.forEach((index) => {
  873. let object = threeDCards[index],
  874. target = targets.sphere[index];
  875. new TWEEN.Tween(object.position)
  876. .to(
  877. {
  878. x: target.position.x,
  879. y: target.position.y,
  880. z: target.position.z,
  881. },
  882. Math.random() * duration + duration
  883. )
  884. .easing(TWEEN.Easing.Exponential.InOut)
  885. .start();
  886. new TWEEN.Tween(object.rotation)
  887. .to(
  888. {
  889. x: target.rotation.x,
  890. y: target.rotation.y,
  891. z: target.rotation.z,
  892. },
  893. Math.random() * duration + duration
  894. )
  895. .easing(TWEEN.Easing.Exponential.InOut)
  896. .start();
  897. });
  898. return new Promise((resolve, reject) => {
  899. new TWEEN.Tween(this)
  900. .to({}, duration * 2)
  901. .onUpdate(render)
  902. .start()
  903. .onComplete(() => {
  904. selectedCardIndex.forEach((index) => {
  905. let object = threeDCards[index];
  906. object.element.classList.remove("prize");
  907. });
  908. resolve();
  909. });
  910. });
  911. }
  912. // 抽奖
  913. const lotteryBtn = () => {
  914. // console.log("isLotting", isLotting);
  915. // console.log("currentPrize.value", currentPrize.value);
  916. if (isLotting) {
  917. stopLottery();
  918. return;
  919. }
  920. if (!currentPrize.value.isLook) {
  921. addQipao("请先揭秘礼品。");
  922. return;
  923. }
  924. if (currentPrize.value.leftCount <= 0) {
  925. addQipao("该礼品已抽取完毕,请揭秘下一个礼品。");
  926. return;
  927. }
  928. // 播放worldcup音频
  929. playWorldcupAudio();
  930. getPrizeUsers.value = [];
  931. let params = {
  932. gradeId: currentPrize.value.gradeId,
  933. prizeId: currentPrize.value.prizeId,
  934. perWin: currentPrize.value.perWin,
  935. remainNum: currentPrize.value.leftCount,
  936. };
  937. // 异步调用API,不阻塞后续代码执行
  938. startLotteryApi(params)
  939. .then((res) => {
  940. // API返回结果时赋值
  941. getPrizeUsers.value = res.data.data || [];
  942. console.log("API返回结果:", res.data.data);
  943. })
  944. .catch((err) => {
  945. console.error("API调用失败:", err);
  946. getPrizeUsers.value = [];
  947. });
  948. setLotteryStatus(true);
  949. //更新剩余抽奖数目的数据显示
  950. changePrize();
  951. resetCard().then((res) => {
  952. // 抽奖
  953. lottery();
  954. });
  955. console.log("currentPrize", currentPrize.value);
  956. const text = "正在抽取[" + currentPrize.value.prizeName + "],调整好姿势";
  957. // addQipao(text);
  958. };
  959. /**
  960. * 抽奖
  961. */
  962. const lottery = () => {
  963. lotteryRef.value.innerHTML = "结束抽奖";
  964. rotateBall().then(() => {
  965. // 将之前的记录置空
  966. currentLuckys = [];
  967. selectedCardIndex = [];
  968. // 当前同时抽取的数目,当前奖品抽完还可以继续抽,但是不记录数据
  969. for (let i = 0; i < getPrizeUsers.value.length; i++) {
  970. console.log("111", getPrizeUsers.value[i]);
  971. currentLuckys.push(getPrizeUsers.value[i]);
  972. currentPrize.value.hasCount--;
  973. //避免中奖者出现在同一个卡片上
  974. let cardIndex = random(TOTAL_CARDS);
  975. while (selectedCardIndex.includes(cardIndex)) {
  976. cardIndex = random(TOTAL_CARDS);
  977. }
  978. selectedCardIndex.push(cardIndex);
  979. if (currentPrize.value.hasCount === 0) {
  980. break;
  981. }
  982. }
  983. selectCard(600);
  984. });
  985. };
  986. const stopLottery = () => {
  987. lotteryRef.value.innerHTML = "开始抽奖";
  988. rotateObj.stop();
  989. // 暂停worldcup音频
  990. pauseWorldcupAudio();
  991. playDongAudio();
  992. };
  993. const changePrize = async () => {
  994. let type = currentPrize.value.type;
  995. prizes.value[type].leftCount =
  996. prizes.value[type].leftCount - EACH_COUNT[type] < 0
  997. ? 0
  998. : prizes.value[type].leftCount - EACH_COUNT[type];
  999. await nextTick();
  1000. // 修改左侧prize的数目和百分比
  1001. setPrizeData(currentPrizeIndex);
  1002. };
  1003. /**
  1004. * 随机抽奖
  1005. */
  1006. function random(num) {
  1007. // Math.floor取到0-num-1之间数字的概率是相等的
  1008. return Math.floor(Math.random() * num);
  1009. }
  1010. /**
  1011. * 切换名牌人员信息
  1012. */
  1013. function changeCard(cardIndex, user) {
  1014. let card = threeDCards[cardIndex].element;
  1015. // console.log("user", user);
  1016. card.innerHTML = `<div class="name">${user.jwcode}</div>`;
  1017. }
  1018. function changeSelectedCard(cardIndex, user) {
  1019. // 保存到全局变量数组
  1020. if (!globalCardIndexes.includes(cardIndex)) {
  1021. globalCardIndexes.push(cardIndex);
  1022. }
  1023. let card = threeDCards[cardIndex].element;
  1024. card.innerHTML = `<div class="name">${user.jwcode}</div>`;
  1025. }
  1026. /**
  1027. * 切换名牌背景
  1028. */
  1029. function changeBackground(cardIndex, color) {
  1030. let card = threeDCards[cardIndex].element;
  1031. // card.style.backgroundColor =
  1032. // color || "rgba(255,170,22," + (Math.random() * 0.7 + 0.25) + ")";
  1033. card.style.backgroundColor = color || "rgba(255,170,22,1)";
  1034. card.style.border = "2px solid rgba(255, 255, 255, 1)";
  1035. }
  1036. /**
  1037. * 随机切换背景和人员信息
  1038. */
  1039. function shineCard() {
  1040. let maxCard = 10;
  1041. let maxUser;
  1042. let shineCard = 10 + random(maxCard);
  1043. setInterval(() => {
  1044. // 正在抽奖停止闪烁
  1045. if (isLotting) {
  1046. return;
  1047. }
  1048. maxUser = users.value.length;
  1049. for (let i = 0; i < shineCard; i++) {
  1050. let index = random(maxUser);
  1051. let cardIndex = random(TOTAL_CARDS);
  1052. // 当前显示的已抽中名单不进行随机切换
  1053. if (selectedCardIndex.includes(cardIndex)) {
  1054. continue;
  1055. }
  1056. changeBackground(cardIndex);
  1057. changeCard(cardIndex, users.value[index]);
  1058. }
  1059. }, 500);
  1060. }
  1061. const createHighlight = () => {
  1062. let text = "0123";
  1063. let step = 5;
  1064. let xoffset = 1;
  1065. let yoffset = 1;
  1066. let highlight = [];
  1067. text.split("").forEach((n) => {
  1068. highlight = highlight.concat(
  1069. NUMBER_MATRIX[n].map((item) => {
  1070. return `${item[0] + xoffset}-${item[1] + yoffset}`;
  1071. })
  1072. );
  1073. xoffset += step;
  1074. });
  1075. return highlight;
  1076. };
  1077. function onWindowResize() {
  1078. camera.aspect = window.innerWidth / window.innerHeight;
  1079. camera.updateProjectionMatrix();
  1080. renderer.setSize(window.innerWidth, window.innerHeight);
  1081. render();
  1082. }
  1083. onMounted(async () => {
  1084. initAll();
  1085. initMusicPlayer();
  1086. window.addEventListener("resize", onWindowResize, false);
  1087. });
  1088. </script>
  1089. <style>
  1090. html,
  1091. body,
  1092. #app {
  1093. height: 100vh;
  1094. width: 100vw;
  1095. }
  1096. .homepage {
  1097. width: 100%;
  1098. height: 100%;
  1099. display: flex;
  1100. background-image: url("../../../assets/bg@2x.png");
  1101. background-repeat: no-repeat;
  1102. background-size: 100% 100%;
  1103. justify-content: center;
  1104. }
  1105. .hllogo {
  1106. position: absolute;
  1107. right: 0;
  1108. bottom: 0;
  1109. }
  1110. .qipao {
  1111. width: 100%;
  1112. z-index: 100;
  1113. position: absolute;
  1114. top: 27vh;
  1115. left: 50%;
  1116. transform: translateX(-50%);
  1117. color: #db5c58;
  1118. font-weight: bold;
  1119. font-size: 42px;
  1120. width: auto;
  1121. text-align: center;
  1122. display: none;
  1123. white-space: nowrap;
  1124. }
  1125. a {
  1126. color: #ffffff;
  1127. }
  1128. .none {
  1129. display: none;
  1130. }
  1131. #container {
  1132. z-index: 3;
  1133. position: relative;
  1134. overflow: hidden;
  1135. }
  1136. .three-container {
  1137. width: 100%;
  1138. height: 100%;
  1139. }
  1140. /* 分页指示器样式 */
  1141. .page-indicator {
  1142. position: absolute;
  1143. bottom: 10%;
  1144. left: 50%;
  1145. transform: translateX(-50%);
  1146. z-index: 1000;
  1147. background: rgba(0, 0, 0, 0.7);
  1148. color: white;
  1149. padding: 8px 16px;
  1150. border-radius: 20px;
  1151. font-size: 14px;
  1152. pointer-events: none;
  1153. }
  1154. .rightPage {
  1155. width: 75px;
  1156. position: fixed;
  1157. transform: rotate(270deg);
  1158. top: 50%;
  1159. right: 15%;
  1160. display: none;
  1161. z-index: 999;
  1162. }
  1163. .leftPage {
  1164. width: 75px;
  1165. position: fixed;
  1166. transform: rotate(90deg);
  1167. top: 50%;
  1168. left: 23%;
  1169. display: none;
  1170. z-index: 999;
  1171. }
  1172. .canvas-box {
  1173. /* background-color: rgb(214, 0, 0); */
  1174. position: fixed;
  1175. left: 0;
  1176. top: 0;
  1177. z-index: -1;
  1178. }
  1179. #info {
  1180. position: absolute;
  1181. width: 100%;
  1182. color: #ffffff;
  1183. padding: 5px;
  1184. font-family: Monospace;
  1185. font-size: 13px;
  1186. font-weight: bold;
  1187. text-align: center;
  1188. z-index: 1;
  1189. }
  1190. #menu {
  1191. z-index: 4;
  1192. position: absolute;
  1193. bottom: 1vh;
  1194. width: 100%;
  1195. display: flex;
  1196. justify-content: center;
  1197. align-items: center;
  1198. }
  1199. .element {
  1200. width: 15vh;
  1201. height: 19vh;
  1202. /* box-shadow: 0 0 12px rgba(0, 255, 255, 0.5); */
  1203. border: 1px solid rgba(127, 255, 255, 0.25);
  1204. text-align: center;
  1205. cursor: default;
  1206. transition: background-color 0.3s ease-in;
  1207. }
  1208. .element:hover {
  1209. box-shadow: 0 0 12px rgba(255, 168, 38, 0.75);
  1210. border: 1px solid rgba(255, 255, 255, 1);
  1211. }
  1212. .element .name {
  1213. font-size: 2.9vh;
  1214. font-weight: bold;
  1215. color: rgba(255, 255, 255, 1);
  1216. /* text-shadow: 0 0 1vh rgba(0, 255, 255, 0.95); */
  1217. }
  1218. .btn {
  1219. background-image: url("../../../assets/img/抽奖按钮.png");
  1220. background-color: transparent;
  1221. background-repeat: no-repeat;
  1222. background-size: 100% 100%;
  1223. width: 180px;
  1224. height: 70px;
  1225. color: #fff;
  1226. border: 0;
  1227. font-size: 2.5vh;
  1228. font-weight: bold;
  1229. cursor: pointer;
  1230. text-align: center;
  1231. }
  1232. .highlight {
  1233. background: linear-gradient(360deg, #e23d26, #f49c27) !important;
  1234. box-shadow: 0 0 12px rgba(253, 105, 0, 0.95);
  1235. border: 2px solid rgba(255, 255, 255, 1);
  1236. }
  1237. .highlight.element .name {
  1238. text-shadow: 0 0 16px rgba(255, 255, 255, 0.95);
  1239. }
  1240. .prize.element .name {
  1241. text-shadow: none;
  1242. }
  1243. .prize.element {
  1244. transition: background-color 1.5s ease-in 0.3s;
  1245. background: linear-gradient(360deg, #e23d26, #f49c27) !important;
  1246. border: 2px solid rgba(255, 255, 255, 1);
  1247. }
  1248. .prize .company,
  1249. .prize .details,
  1250. .prize .name,
  1251. .highlight .company,
  1252. .highlight .name,
  1253. .highlight .details {
  1254. color: rgba(255, 255, 255, 0.85);
  1255. }
  1256. .dan-mu {
  1257. visibility: hidden;
  1258. position: fixed;
  1259. z-index: -1;
  1260. font-size: 12px;
  1261. top: 1vh;
  1262. left: 0;
  1263. padding: 0 1.2vh;
  1264. height: 2.2vh;
  1265. line-height: 2.2vh;
  1266. border-radius: 1vh;
  1267. box-sizing: border-box;
  1268. background-color: rgba(0, 127, 127, 0.37);
  1269. box-shadow: 0 0 4px rgba(0, 255, 255, 0.5);
  1270. border: 1px solid rgba(127, 255, 255, 0.25);
  1271. color: rgba(127, 255, 255, 0.75);
  1272. }
  1273. .dan-mu.active {
  1274. visibility: visible;
  1275. }
  1276. .leftBar {
  1277. position: fixed;
  1278. left: 0;
  1279. padding-left: 1.2vh;
  1280. top: 15vh;
  1281. z-index: 100;
  1282. }
  1283. #prizeBar {
  1284. max-height: 60vh;
  1285. width: 330px;
  1286. overflow-x: hidden;
  1287. overflow-y: auto;
  1288. /* 隐藏滚动条 */
  1289. scrollbar-width: none;
  1290. /* Firefox */
  1291. -ms-overflow-style: none;
  1292. /* IE */
  1293. }
  1294. #prizeBar::-webkit-scrollbar {
  1295. display: none;
  1296. /* Chrome, Safari */
  1297. }
  1298. .prize-list {
  1299. margin: 0;
  1300. padding: 0;
  1301. list-style: none;
  1302. }
  1303. .prize-item {
  1304. padding: 2px;
  1305. display: flex;
  1306. align-items: center;
  1307. justify-content: center;
  1308. flex-wrap: nowrap;
  1309. /* color: rgba(127, 255, 255, 0.75); */
  1310. width: 30vh;
  1311. height: 9.5vh;
  1312. box-sizing: border-box;
  1313. transition: transform 1s ease-in;
  1314. }
  1315. .getPrizeName {
  1316. color: #d5291f;
  1317. font-weight: bold;
  1318. font-size: 24px;
  1319. width: 27vh;
  1320. height: 5vh;
  1321. border: 2px solid rgb(255, 255, 255);
  1322. border-radius: 5px;
  1323. display: flex;
  1324. flex-direction: column;
  1325. justify-content: center;
  1326. align-items: center;
  1327. background-color: #ffd283;
  1328. opacity: 0.8;
  1329. margin-left: 2vh;
  1330. margin-top: 3.5vh;
  1331. cursor: pointer;
  1332. position: relative;
  1333. }
  1334. .dgetPrizeName {
  1335. color: #d5291f;
  1336. font-weight: bold;
  1337. font-size: 24px;
  1338. width: 27vh;
  1339. height: 60vh;
  1340. border: 2px solid rgb(255, 255, 255);
  1341. border-radius: 5px;
  1342. display: flex;
  1343. flex-direction: column;
  1344. /* justify-content: center; */
  1345. align-items: center;
  1346. background-color: #ffd283;
  1347. opacity: 0.8;
  1348. margin-left: 2vh;
  1349. margin-top: 3.5vh;
  1350. position: relative;
  1351. }
  1352. .open {
  1353. position: absolute;
  1354. top: -42px;
  1355. animation: bounce1 2s ease-in-out infinite;
  1356. transform: rotate(180deg);
  1357. }
  1358. .close {
  1359. position: absolute;
  1360. top: -32px;
  1361. animation: bounce2 2s ease-in-out infinite;
  1362. cursor: pointer;
  1363. }
  1364. @keyframes bounce1 {
  1365. 0%,
  1366. 100% {
  1367. transform: rotate(180deg) translateY(0);
  1368. }
  1369. 50% {
  1370. transform: rotate(180deg) translateY(-8px);
  1371. }
  1372. }
  1373. @keyframes bounce2 {
  1374. 0%,
  1375. 100% {
  1376. transform: translateY(0);
  1377. }
  1378. 50% {
  1379. transform: translateY(-8px);
  1380. }
  1381. }
  1382. .tableHead {
  1383. width: 100%;
  1384. display: flex;
  1385. justify-content: center;
  1386. font-size: 18px;
  1387. /* gap: 80px; */
  1388. margin-bottom: 10px;
  1389. position: absolute;
  1390. top: 10px;
  1391. }
  1392. .tableHead1 {
  1393. width: 50%;
  1394. text-align: center;
  1395. }
  1396. .tableHead2 {
  1397. width: 50%;
  1398. text-align: center;
  1399. }
  1400. .gradient-line {
  1401. width: 90%;
  1402. height: 3px;
  1403. border-radius: 150%;
  1404. background: linear-gradient(
  1405. to right,
  1406. transparent 0%,
  1407. #d5291f 45%,
  1408. #d5291f 55%,
  1409. transparent 100%
  1410. );
  1411. position: absolute;
  1412. bottom: -10px;
  1413. }
  1414. .tableBody {
  1415. display: flex;
  1416. margin-top: 50px;
  1417. width: 100%;
  1418. height: 50vh;
  1419. justify-content: center;
  1420. overflow-y: auto;
  1421. /* 启用垂直滚动 */
  1422. overflow-x: hidden;
  1423. /* 启用垂直滚动 */
  1424. /* 隐藏滚动条 */
  1425. scrollbar-width: none;
  1426. /* Firefox */
  1427. -ms-overflow-style: none;
  1428. /* IE */
  1429. }
  1430. .tableBody::-webkit-scrollbar {
  1431. display: none;
  1432. /* Chrome, Safari */
  1433. }
  1434. .tableItem {
  1435. display: flex;
  1436. font-size: 18px;
  1437. width: 27vh;
  1438. margin-bottom: 10px;
  1439. top: 10px;
  1440. }
  1441. .tableItem1 {
  1442. width: 50%;
  1443. text-align: center;
  1444. }
  1445. .tableItem2 {
  1446. width: 50%;
  1447. text-align: center;
  1448. }
  1449. .tableFoot {
  1450. color: black;
  1451. }
  1452. .tableTotalPage {
  1453. font-size: 10px;
  1454. }
  1455. .readyLook {
  1456. width: 100%;
  1457. height: 100%;
  1458. }
  1459. .prize-item .prize-img {
  1460. width: 7.5vh;
  1461. height: 7.5vh;
  1462. margin: auto 10px;
  1463. border-radius: 50%;
  1464. background-color: #fff;
  1465. text-shadow: 0 0 1vh rgba(0, 255, 255, 0.95);
  1466. overflow: hidden;
  1467. }
  1468. .prize-img img {
  1469. width: 90%;
  1470. height: 90%;
  1471. position: relative;
  1472. top: 50%;
  1473. left: 50%;
  1474. transform: translate(-50%, -50%);
  1475. }
  1476. .prize-text {
  1477. padding: 0 5px;
  1478. width: 100%;
  1479. height: 100%;
  1480. /* margin: auto 0; */
  1481. flex: 1;
  1482. display: flex;
  1483. flex-direction: column;
  1484. justify-content: center;
  1485. /* gap: 4px; */
  1486. }
  1487. .prize-title {
  1488. margin: 4px 0;
  1489. font-size: 1.4vh;
  1490. height: 30%;
  1491. border: 1px solid #e13726;
  1492. border-radius: 20px;
  1493. background-color: #ffffff;
  1494. display: flex;
  1495. color: #d5291f;
  1496. font-weight: bold;
  1497. display: flex;
  1498. align-items: center;
  1499. }
  1500. .level {
  1501. width: 38%;
  1502. height: 100%;
  1503. margin-right: 2%;
  1504. border-radius: 20px;
  1505. background: linear-gradient(360deg, #e23d26, #f49c27) !important;
  1506. color: #fff;
  1507. display: flex;
  1508. justify-content: center;
  1509. align-items: center;
  1510. }
  1511. .prize-count {
  1512. padding: 4px 0;
  1513. position: relative;
  1514. }
  1515. .prize-count .progress {
  1516. height: 1.8vh;
  1517. background: rgba(0, 0, 0, 0.5);
  1518. padding: 1px;
  1519. overflow: visible;
  1520. border-radius: 1vh;
  1521. }
  1522. .progress .progress-bar {
  1523. border-radius: 1.8vh;
  1524. position: relative;
  1525. animation: animate-positive 2s;
  1526. background-color: #d9534f;
  1527. height: 1.8vh;
  1528. -webkit-transition: width 0.6s ease;
  1529. -o-transition: width 0.6s ease;
  1530. transition: width 0.6s ease;
  1531. }
  1532. .progress-bar.active {
  1533. animation: reverse progress-bar-stripes 0.4s linear infinite,
  1534. animate-positive 2s;
  1535. }
  1536. .progress-bar-striped {
  1537. background-image: -webkit-linear-gradient(
  1538. 45deg,
  1539. rgba(255, 255, 255, 0.15) 25%,
  1540. transparent 25%,
  1541. transparent 50%,
  1542. rgba(255, 255, 255, 0.15) 50%,
  1543. rgba(255, 255, 255, 0.15) 75%,
  1544. transparent 75%,
  1545. transparent
  1546. );
  1547. background-image: -o-linear-gradient(
  1548. 45deg,
  1549. rgba(255, 255, 255, 0.15) 25%,
  1550. transparent 25%,
  1551. transparent 50%,
  1552. rgba(255, 255, 255, 0.15) 50%,
  1553. rgba(255, 255, 255, 0.15) 75%,
  1554. transparent 75%,
  1555. transparent
  1556. );
  1557. background-image: linear-gradient(
  1558. 45deg,
  1559. rgba(255, 255, 255, 0.15) 25%,
  1560. transparent 25%,
  1561. transparent 50%,
  1562. rgba(255, 255, 255, 0.15) 50%,
  1563. rgba(255, 255, 255, 0.15) 75%,
  1564. transparent 75%,
  1565. transparent
  1566. );
  1567. -webkit-background-size: 8px 8px;
  1568. background-size: 8px 8px;
  1569. }
  1570. @-webkit-keyframes animate-positive {
  1571. 0% {
  1572. width: 0;
  1573. }
  1574. }
  1575. @keyframes animate-positive {
  1576. 0% {
  1577. width: 0;
  1578. }
  1579. }
  1580. @-webkit-keyframes progress-bar-stripes {
  1581. from {
  1582. background-position: 8px 0;
  1583. }
  1584. to {
  1585. background-position: 0 0;
  1586. }
  1587. }
  1588. @-o-keyframes progress-bar-stripes {
  1589. from {
  1590. background-position: 8px 0;
  1591. }
  1592. to {
  1593. background-position: 0 0;
  1594. }
  1595. }
  1596. @keyframes progress-bar-stripes {
  1597. from {
  1598. background-position: 8px 0;
  1599. }
  1600. to {
  1601. background-position: 0 0;
  1602. }
  1603. }
  1604. .prize-count-left {
  1605. position: absolute;
  1606. color: #fff;
  1607. right: 35%;
  1608. font-size: 1.8vh;
  1609. line-height: 1.6vh;
  1610. top: 50%;
  1611. transform: translateY(-50%);
  1612. }
  1613. .shine {
  1614. background-color: #ffd283;
  1615. border: 1px solid rgba(255, 255, 255, 1);
  1616. border-radius: 5px;
  1617. box-shadow: 0 0 15px 0 #ffd283;
  1618. transform: scale(1.1);
  1619. transform-origin: left center;
  1620. position: relative;
  1621. overflow: hidden;
  1622. }
  1623. .done {
  1624. position: relative;
  1625. }
  1626. .done:after {
  1627. content: "";
  1628. position: absolute;
  1629. top: 0;
  1630. left: 0;
  1631. right: 0;
  1632. bottom: 0;
  1633. background-color: #ffd283;
  1634. border: 2px solid rgba(255, 255, 255, 1);
  1635. border-radius: 5px;
  1636. opacity: 0.3;
  1637. cursor: not-allowed;
  1638. }
  1639. .shine span {
  1640. position: absolute;
  1641. display: block;
  1642. }
  1643. .shine span:nth-child(1) {
  1644. top: 0;
  1645. left: 0;
  1646. width: 100%;
  1647. height: 2px;
  1648. background: linear-gradient(90deg, transparent, #f46303);
  1649. animation: animate1 1s linear infinite;
  1650. }
  1651. @keyframes animate1 {
  1652. 0% {
  1653. left: -100%;
  1654. }
  1655. 50%,
  1656. 100% {
  1657. left: 100%;
  1658. }
  1659. }
  1660. .shine span:nth-child(2) {
  1661. top: -100%;
  1662. right: 0;
  1663. width: 2px;
  1664. height: 100%;
  1665. background: linear-gradient(180deg, transparent, #f46303);
  1666. animation: animate2 1s linear infinite;
  1667. animation-delay: 0.25s;
  1668. }
  1669. @keyframes animate2 {
  1670. 0% {
  1671. top: -100%;
  1672. }
  1673. 50%,
  1674. 100% {
  1675. top: 100%;
  1676. }
  1677. }
  1678. .shine span:nth-child(3) {
  1679. bottom: 0;
  1680. right: 0;
  1681. width: 100%;
  1682. height: 2px;
  1683. background: linear-gradient(270deg, transparent, #f46303);
  1684. animation: animate3 1s linear infinite;
  1685. animation-delay: 0.5s;
  1686. }
  1687. @keyframes animate3 {
  1688. 0% {
  1689. right: -100%;
  1690. }
  1691. 50%,
  1692. 100% {
  1693. right: 100%;
  1694. }
  1695. }
  1696. .shine span:nth-child(4) {
  1697. bottom: -100%;
  1698. left: 0;
  1699. width: 2px;
  1700. height: 100%;
  1701. background: linear-gradient(360deg, transparent, #f46303);
  1702. animation: animate4 1s linear infinite;
  1703. animation-delay: 0.75s;
  1704. }
  1705. @keyframes animate4 {
  1706. 0% {
  1707. bottom: -100%;
  1708. }
  1709. 50%,
  1710. 100% {
  1711. bottom: 100%;
  1712. }
  1713. }
  1714. .shine.prize-item {
  1715. /* width: 24vh; */
  1716. margin: 1.2vh 0;
  1717. }
  1718. .prize-mess {
  1719. color: #fff;
  1720. line-height: 5vh;
  1721. font-size: 1.6vh;
  1722. margin: 2.4vh 0;
  1723. }
  1724. .music {
  1725. position: fixed;
  1726. top: 3vh;
  1727. right: 4vh;
  1728. z-index: 5;
  1729. }
  1730. .music-item {
  1731. display: block !important;
  1732. opacity: 0;
  1733. }
  1734. .music-box {
  1735. width: 5vh;
  1736. height: 5vh;
  1737. border-radius: 50%;
  1738. text-align: center;
  1739. line-height: 5vh;
  1740. font-size: 1.4vh;
  1741. color: #fff;
  1742. cursor: pointer;
  1743. background-color: rgba(253, 105, 0, 0.9);
  1744. border: 1px solid rgba(255, 255, 255, 0.5);
  1745. }
  1746. .rotate-active {
  1747. animation: rotate 4s linear infinite;
  1748. }
  1749. @keyframes rotate {
  1750. from {
  1751. transform: rotate(0);
  1752. }
  1753. to {
  1754. transform: rotate(360deg);
  1755. }
  1756. }
  1757. .margin-l-40 {
  1758. margin-left: 40px;
  1759. }
  1760. .fixed-bar {
  1761. position: fixed;
  1762. bottom: 20px;
  1763. right: 20px;
  1764. }
  1765. .fixed-btn {
  1766. margin: 20px 0 0;
  1767. width: 200px;
  1768. text-align: center;
  1769. display: block;
  1770. }
  1771. #lottery {
  1772. animation: breath 1.6s linear infinite;
  1773. /* box-shadow: 0px 0px 15px rgb(127 255 255 / 75%); */
  1774. }
  1775. @keyframes breath {
  1776. 0% {
  1777. transform: scale(1);
  1778. opacity: 0.8;
  1779. }
  1780. 25% {
  1781. transform: scale(1.1);
  1782. opacity: 1;
  1783. }
  1784. 50% {
  1785. transform: scale(1);
  1786. opacity: 1;
  1787. }
  1788. 75% {
  1789. transform: scale(0.9);
  1790. opacity: 1;
  1791. }
  1792. 100% {
  1793. transform: scale(1);
  1794. opacity: 0.8;
  1795. }
  1796. }
  1797. </style>