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.

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