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.

2140 lines
47 KiB

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