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.

2144 lines
47 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
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
3 weeks ago
1 month ago
3 weeks ago
1 month ago
3 weeks ago
1 month 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
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
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
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. <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 = 3000;
  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. node.className = "";
  607. node.classList.add("element");
  608. node.style.backgroundColor = "rgb(255,170,22)";
  609. });
  610. };
  611. function addHighlight() {
  612. document.querySelectorAll(".lightitem").forEach((node) => {
  613. node.classList.add("highlight");
  614. });
  615. }
  616. /**
  617. * 渲染地球等
  618. */
  619. function transform(targets, duration) {
  620. // TWEEN.removeAll();
  621. for (var i = 0; i < threeDCards.length; i++) {
  622. var object = threeDCards[i];
  623. var target = targets[i];
  624. new TWEEN.Tween(object.position)
  625. .to(
  626. {
  627. x: target.position.x,
  628. y: target.position.y,
  629. z: target.position.z,
  630. },
  631. Math.random() * duration + duration
  632. )
  633. .easing(TWEEN.Easing.Exponential.InOut)
  634. .start();
  635. new TWEEN.Tween(object.rotation)
  636. .to(
  637. {
  638. x: target.rotation.x,
  639. y: target.rotation.y,
  640. z: target.rotation.z,
  641. },
  642. Math.random() * duration + duration
  643. )
  644. .easing(TWEEN.Easing.Exponential.InOut)
  645. .start();
  646. }
  647. new TWEEN.Tween(this)
  648. .to({}, duration * 2)
  649. .onUpdate(render)
  650. .start();
  651. }
  652. function rotateBall() {
  653. return new Promise((resolve, reject) => {
  654. scene.rotation.y = 0;
  655. rotateObj = new TWEEN.Tween(scene.rotation);
  656. rotateObj
  657. .to(
  658. {
  659. y: Math.PI * 6 * ROTATE_LOOP,
  660. },
  661. ROTATE_TIME * ROTATE_LOOP
  662. )
  663. .onUpdate(render)
  664. // .easing(TWEEN.Easing.Linear)
  665. .start()
  666. .onStop(() => {
  667. scene.rotation.y = 0;
  668. resolve();
  669. })
  670. .onComplete(() => {
  671. resolve();
  672. });
  673. });
  674. }
  675. function animate() {
  676. // 让场景通过x轴或者y轴旋转
  677. // rotate && (scene.rotation.y += 0.088);
  678. requestAnimationFrame(animate);
  679. TWEEN.update();
  680. controls.update();
  681. // 渲染循环
  682. // render();
  683. }
  684. function render() {
  685. renderer.render(scene, camera);
  686. }
  687. const threeContainer = ref(null);
  688. function selectCard(duration = 600) {
  689. return new Promise((resolve) => {
  690. const width = 160;
  691. // 计算总页数
  692. pageMaxIndex.value = Math.ceil(currentLuckys.length / cardsPerPage);
  693. pageIndex.value = 0;
  694. // 为每页计算位置信息
  695. const pageLocates = [];
  696. for (let page = 0; page < pageMaxIndex.value; page++) {
  697. const startIndex = page * cardsPerPage;
  698. const endIndex = Math.min(
  699. (page + 1) * cardsPerPage,
  700. currentLuckys.length
  701. );
  702. const pageCount = endIndex - startIndex;
  703. const pageLocate = [];
  704. // 根据当前页的人数决定排列方式
  705. if (pageCount > 5) {
  706. // 大于5个分两排显示(与抽10人时的排列相同)
  707. const yPosition = [-75, 120];
  708. const mid = Math.ceil(pageCount / 2);
  709. let tag = -(mid - 1) / 2;
  710. for (let i = 0; i < mid; i++) {
  711. pageLocate.push({
  712. x: tag * width,
  713. y: yPosition[0],
  714. });
  715. tag++;
  716. }
  717. tag = -(pageCount - mid - 1) / 2;
  718. for (let i = mid; i < pageCount; i++) {
  719. pageLocate.push({
  720. x: tag * width,
  721. y: yPosition[1],
  722. });
  723. tag++;
  724. }
  725. } else {
  726. // 小于等于5个一排显示(与抽不足10人时的排列相同)
  727. let tag = -(pageCount - 1) / 2;
  728. for (let i = 0; i < pageCount; i++) {
  729. pageLocate.push({
  730. x: tag * width,
  731. y: 0,
  732. });
  733. tag++;
  734. }
  735. }
  736. pageLocates.push(pageLocate);
  737. }
  738. // console.log("pageLocates calculated:", pageLocates);
  739. // 初始化所有卡片位置(隐藏超出第一页的卡片)
  740. selectedCardIndex.forEach((cardIndex, index) => {
  741. changeSelectedCard(cardIndex, currentLuckys[index]);
  742. const object = threeDCards[cardIndex];
  743. // 计算卡片应该在第几页
  744. const cardPage = Math.floor(index / cardsPerPage);
  745. const isVisible = cardPage === 0;
  746. // 计算在当前页中的索引
  747. const pageNowIndex = index % cardsPerPage;
  748. const pageLocate = pageLocates[cardPage][pageNowIndex];
  749. new TWEEN.Tween(object.position)
  750. .to(
  751. {
  752. x: isVisible ? pageLocate.x : pageLocate.x,
  753. y: isVisible ? pageLocate.y : pageLocate.y + 1000, // 隐藏的卡片移到下方
  754. z: 2100,
  755. },
  756. Math.random() * duration + duration
  757. )
  758. .easing(TWEEN.Easing.Exponential.InOut)
  759. .start();
  760. new TWEEN.Tween(object.rotation)
  761. .to(
  762. {
  763. x: 0,
  764. y: 0,
  765. z: 0,
  766. },
  767. Math.random() * duration + duration
  768. )
  769. .easing(TWEEN.Easing.Exponential.InOut)
  770. .start();
  771. object.element.classList.add("prize");
  772. });
  773. // 保存页面位置信息到全局变量,供switchPage使用
  774. window.pageLocates = pageLocates;
  775. new TWEEN.Tween({})
  776. .to({}, duration * 2)
  777. .onUpdate(() => render())
  778. .start()
  779. .onComplete(() => {
  780. console.log("selectCard animation completed");
  781. // 如果有多页,添加鼠标滚轮事件监听
  782. if (pageMaxIndex.value > 1) {
  783. // console.log("pageMaxIndex添加滚轮", pageMaxIndex.value);
  784. // 添加滚轮事件监听
  785. addWheelListener();
  786. }
  787. setLotteryStatus();
  788. resolve();
  789. });
  790. });
  791. }
  792. // 分页切换函数
  793. function switchPage(direction) {
  794. if (isPageTransitioning || pageMaxIndex.value <= 1) return;
  795. const newPage =
  796. direction === "next" ? pageIndex.value + 1 : pageIndex.value - 1;
  797. if (newPage < 0 || newPage >= pageMaxIndex.value) return;
  798. isPageTransitioning = true;
  799. const duration = 800;
  800. // 使用保存的页面位置信息
  801. const pageLocates = window.pageLocates;
  802. if (!pageLocates) {
  803. console.error("页面位置信息未找到");
  804. isPageTransitioning = false;
  805. return;
  806. }
  807. // 动画切换卡片位置
  808. globalCardIndexes.forEach((cardIndex, index) => {
  809. const object = threeDCards[cardIndex];
  810. const cardPage = Math.floor(index / cardsPerPage);
  811. const isVisible = cardPage === newPage;
  812. const wasVisible = cardPage === pageIndex.value;
  813. // 计算在当前页中的索引
  814. const pageNowIndex = index % cardsPerPage;
  815. const pageLocate = pageLocates[cardPage][pageNowIndex];
  816. // console.log(
  817. // "cardPage",
  818. // cardPage,
  819. // "pageNowIndex",
  820. // pageNowIndex,
  821. // "pageLocate",
  822. // pageLocate
  823. // );
  824. // 根据切换方向决定动画效果
  825. let targetY;
  826. if (isVisible) {
  827. // 当前页要显示的卡片
  828. if (direction === "next") {
  829. // 索引增大:从下方飞出
  830. targetY = pageLocate.y;
  831. } else {
  832. // 索引减少:从上方飞出
  833. targetY = pageLocate.y;
  834. }
  835. } else {
  836. // 当前页要隐藏的卡片
  837. if (direction === "next") {
  838. // 索引增大:向上飞走
  839. targetY = pageLocate.y + 1000;
  840. } else {
  841. // 索引减少:向下飞走
  842. targetY = pageLocate.y - 1000;
  843. }
  844. }
  845. // 设置起始位置
  846. let startY;
  847. if (wasVisible) {
  848. // 当前页的卡片从当前位置开始
  849. startY = object.position.y;
  850. } else {
  851. // 非当前页的卡片从隐藏位置开始
  852. if (direction === "next") {
  853. // 索引增大:从下方开始
  854. startY = pageLocate.y - 1000;
  855. } else {
  856. // 索引减少:从上方开始
  857. startY = pageLocate.y + 1000;
  858. }
  859. }
  860. // 先设置起始位置
  861. object.position.y = startY;
  862. new TWEEN.Tween(object.position)
  863. .to(
  864. {
  865. x: pageLocate.x,
  866. y: targetY,
  867. z: 2100,
  868. },
  869. duration
  870. )
  871. .easing(TWEEN.Easing.Cubic.InOut)
  872. .start();
  873. });
  874. new TWEEN.Tween({})
  875. .to({}, duration)
  876. .onUpdate(() => render())
  877. .onComplete(() => {
  878. pageIndex.value = newPage;
  879. isPageTransitioning = false;
  880. console.log(
  881. `切换到第 ${pageIndex.value + 1} 页,共 ${pageMaxIndex.value}`
  882. );
  883. })
  884. .start();
  885. }
  886. // 鼠标滚轮事件处理
  887. function handleWheel(event) {
  888. if (isPageTransitioning || pageMaxIndex.value <= 1) return;
  889. event.preventDefault();
  890. if (event.deltaY > 0) {
  891. // 向下滚动,显示下一页
  892. switchPage("next");
  893. } else if (event.deltaY < 0) {
  894. // 向上滚动,显示上一页
  895. switchPage("prev");
  896. }
  897. }
  898. // 添加鼠标滚轮事件监听器
  899. function addWheelListener() {
  900. if (threeContainer.value) {
  901. threeContainer.value.addEventListener("wheel", handleWheel, {
  902. passive: false,
  903. });
  904. }
  905. }
  906. // 移除鼠标滚轮事件监听器
  907. function removeWheelListener() {
  908. if (threeContainer.value) {
  909. threeContainer.value.removeEventListener("wheel", handleWheel);
  910. }
  911. }
  912. /**
  913. * 重置抽奖牌内容
  914. */
  915. function resetCard(duration = 500) {
  916. if (currentLuckys.length === 0) {
  917. return Promise.resolve();
  918. }
  919. globalCardIndexes = [];
  920. // 移除鼠标滚轮事件监听器
  921. removeWheelListener();
  922. // 重置分页状态
  923. pageIndex.value = 0;
  924. pageMaxIndex.value = 0;
  925. isPageTransitioning = false;
  926. // 清理保存的页面位置信息
  927. if (window.pageLocates) {
  928. delete window.pageLocates;
  929. }
  930. selectedCardIndex.forEach((index) => {
  931. let object = threeDCards[index],
  932. target = targets.sphere[index];
  933. new TWEEN.Tween(object.position)
  934. .to(
  935. {
  936. x: target.position.x,
  937. y: target.position.y,
  938. z: target.position.z,
  939. },
  940. Math.random() * duration + duration
  941. )
  942. .easing(TWEEN.Easing.Exponential.InOut)
  943. .start();
  944. new TWEEN.Tween(object.rotation)
  945. .to(
  946. {
  947. x: target.rotation.x,
  948. y: target.rotation.y,
  949. z: target.rotation.z,
  950. },
  951. Math.random() * duration + duration
  952. )
  953. .easing(TWEEN.Easing.Exponential.InOut)
  954. .start();
  955. });
  956. return new Promise((resolve, reject) => {
  957. new TWEEN.Tween(this)
  958. .to({}, duration * 2)
  959. .onUpdate(render)
  960. .start()
  961. .onComplete(() => {
  962. selectedCardIndex.forEach((index) => {
  963. let object = threeDCards[index];
  964. object.element.classList.remove("prize");
  965. });
  966. resolve();
  967. });
  968. });
  969. }
  970. const isBackApi = ref(false);
  971. const cjText = ref("开始抽奖");
  972. // 抽奖
  973. const lotteryBtn = () => {
  974. // console.log("isLotting", isLotting);
  975. // console.log("currentPrize.value", currentPrize.value);
  976. if (isLotting) {
  977. throttledStopLottery();
  978. return;
  979. }
  980. if (!currentPrize.value.isLook) {
  981. addQipao("请先揭秘礼品");
  982. return;
  983. }
  984. if (currentPrize.value.leftCount <= 0) {
  985. if (currentPrize.value.type == 0) {
  986. addQipao("所以礼品都已抽取完毕");
  987. } else {
  988. addQipao("该礼品已抽取完毕,请揭秘下一个礼品");
  989. }
  990. return;
  991. }
  992. // lotteryRef.value.innerHTML = "结束抽奖";
  993. cjText.value = "结束抽奖";
  994. isBackApi.value = true;
  995. // 播放worldcup音频
  996. playWorldcupAudio();
  997. getPrizeUsers.value = [];
  998. let params = {
  999. gradeId: currentPrize.value.gradeId,
  1000. prizeId: currentPrize.value.prizeId,
  1001. perWin: currentPrize.value.perWin,
  1002. remainNum: currentPrize.value.leftCount,
  1003. };
  1004. // 异步调用API,不阻塞后续代码执行
  1005. startLotteryApi(params)
  1006. .then((res) => {
  1007. // API返回结果时赋值
  1008. getPrizeUsers.value = res.data.data || [];
  1009. isBackApi.value = false;
  1010. console.log("API返回结果:", res.data.data);
  1011. })
  1012. .catch((err) => {
  1013. isBackApi.value = false;
  1014. console.error("API调用失败:", err);
  1015. getPrizeUsers.value = [];
  1016. });
  1017. setLotteryStatus(true);
  1018. //更新剩余抽奖数目的数据显示
  1019. changePrize();
  1020. resetCard().then((res) => {
  1021. // 抽奖
  1022. lottery();
  1023. });
  1024. console.log("currentPrize", currentPrize.value);
  1025. const text = "正在抽取[" + currentPrize.value.prizeName + "],调整好姿势";
  1026. // addQipao(text);
  1027. };
  1028. const throttledLotteryBtn = _.throttle(lotteryBtn, 1000, {
  1029. trailing: false,
  1030. });
  1031. const lottery = () => {
  1032. rotateBall().then(() => {
  1033. // 将之前的记录置空
  1034. currentLuckys = [];
  1035. selectedCardIndex = [];
  1036. // 当前同时抽取的数目,当前奖品抽完还可以继续抽,但是不记录数据
  1037. for (let i = 0; i < getPrizeUsers.value.length; i++) {
  1038. console.log("111", getPrizeUsers.value[i]);
  1039. currentLuckys.push(getPrizeUsers.value[i]);
  1040. currentPrize.value.hasCount--;
  1041. //避免中奖者出现在同一个卡片上
  1042. let cardIndex = random(TOTAL_CARDS);
  1043. while (selectedCardIndex.includes(cardIndex)) {
  1044. cardIndex = random(TOTAL_CARDS);
  1045. }
  1046. selectedCardIndex.push(cardIndex);
  1047. if (currentPrize.value.hasCount === 0) {
  1048. break;
  1049. }
  1050. }
  1051. selectCard(600);
  1052. });
  1053. };
  1054. const stopLottery = () => {
  1055. // lotteryRef.value.innerHTML = "开始抽奖";
  1056. cjText.value = "开始抽奖";
  1057. rotateObj.stop();
  1058. // 暂停worldcup音频
  1059. pauseWorldcupAudio();
  1060. playDongAudio();
  1061. };
  1062. const throttledStopLottery = _.throttle(stopLottery, 1000, {
  1063. trailing: false,
  1064. });
  1065. const changePrize = async () => {
  1066. let type = currentPrize.value.type;
  1067. prizes.value[type].leftCount =
  1068. prizes.value[type].leftCount - EACH_COUNT[type] < 0
  1069. ? 0
  1070. : prizes.value[type].leftCount - EACH_COUNT[type];
  1071. await nextTick();
  1072. // 修改左侧prize的数目和百分比
  1073. setPrizeData(currentPrizeIndex);
  1074. };
  1075. /**
  1076. * 随机抽奖
  1077. */
  1078. function random(num) {
  1079. // Math.floor取到0-num-1之间数字的概率是相等的
  1080. return Math.floor(Math.random() * num);
  1081. }
  1082. /**
  1083. * 切换名牌人员信息
  1084. */
  1085. function changeCard(cardIndex, user) {
  1086. let card = threeDCards[cardIndex].element;
  1087. // console.log("user", user);
  1088. card.innerHTML = `<div class="name">${user.jwcode}</div>`;
  1089. }
  1090. function changeSelectedCard(cardIndex, user) {
  1091. // 保存到全局变量数组
  1092. if (!globalCardIndexes.includes(cardIndex)) {
  1093. globalCardIndexes.push(cardIndex);
  1094. }
  1095. let card = threeDCards[cardIndex].element;
  1096. card.innerHTML = `<div class="name">${user.jwcode}</div>`;
  1097. }
  1098. /**
  1099. * 切换名牌背景
  1100. */
  1101. const changeBackground = async (cardIndex) => {
  1102. let card = threeDCards[cardIndex].element;
  1103. // card.style.backgroundColor =
  1104. // color || "rgba(255,170,22," + (Math.random() * 0.7 + 0.25) + ")";
  1105. card.style.backgroundColor = "rgb(255,170,22) !important";
  1106. card.style.border = "2px solid rgb(255, 255, 255)";
  1107. // await nextTick();
  1108. };
  1109. /**
  1110. * 随机切换背景和人员信息
  1111. */
  1112. function shineCard() {
  1113. let maxCard = 10;
  1114. let maxUser;
  1115. let shineCard = 10 + random(maxCard);
  1116. setInterval(() => {
  1117. // 正在抽奖停止闪烁
  1118. if (isLotting) {
  1119. return;
  1120. }
  1121. maxUser = users.value.length;
  1122. for (let i = 0; i < shineCard; i++) {
  1123. let index = random(maxUser);
  1124. let cardIndex = random(TOTAL_CARDS);
  1125. // 当前显示的已抽中名单不进行随机切换
  1126. if (selectedCardIndex.includes(cardIndex)) {
  1127. continue;
  1128. }
  1129. // changeBackground(cardIndex);
  1130. changeCard(cardIndex, users.value[index]);
  1131. }
  1132. }, 500);
  1133. }
  1134. const createHighlight = () => {
  1135. let text = "0123";
  1136. let step = 5;
  1137. let xoffset = 1;
  1138. let yoffset = 1;
  1139. let highlight = [];
  1140. text.split("").forEach((n) => {
  1141. highlight = highlight.concat(
  1142. NUMBER_MATRIX[n].map((item) => {
  1143. return `${item[0] + xoffset}-${item[1] + yoffset}`;
  1144. })
  1145. );
  1146. xoffset += step;
  1147. });
  1148. return highlight;
  1149. };
  1150. function onWindowResize() {
  1151. camera.aspect = window.innerWidth / window.innerHeight;
  1152. camera.updateProjectionMatrix();
  1153. renderer.setSize(window.innerWidth, window.innerHeight);
  1154. render();
  1155. }
  1156. onMounted(async () => {
  1157. initAll();
  1158. window.addEventListener("resize", onWindowResize, false);
  1159. });
  1160. </script>
  1161. <style>
  1162. html,
  1163. body,
  1164. #app {
  1165. height: 100vh;
  1166. width: 100vw;
  1167. }
  1168. .homepage {
  1169. width: 100%;
  1170. height: 100%;
  1171. display: flex;
  1172. background-image: url("../../../assets/bg@2x.png");
  1173. background-repeat: no-repeat;
  1174. background-size: 100% 100%;
  1175. justify-content: center;
  1176. }
  1177. .hllogo {
  1178. position: absolute;
  1179. right: 0;
  1180. bottom: 0;
  1181. }
  1182. .qipao {
  1183. width: 100%;
  1184. z-index: 100;
  1185. position: absolute;
  1186. top: 27vh;
  1187. left: 50%;
  1188. transform: translateX(-50%);
  1189. color: #db5c58;
  1190. font-weight: bold;
  1191. font-size: 42px;
  1192. width: auto;
  1193. text-align: center;
  1194. display: none;
  1195. white-space: nowrap;
  1196. }
  1197. a {
  1198. color: #ffffff;
  1199. }
  1200. .none {
  1201. display: none;
  1202. }
  1203. #container {
  1204. z-index: 3;
  1205. position: relative;
  1206. overflow: hidden;
  1207. }
  1208. .three-container {
  1209. width: 100%;
  1210. height: 100%;
  1211. }
  1212. /* 分页指示器样式 */
  1213. .page-indicator {
  1214. position: absolute;
  1215. bottom: 10%;
  1216. left: 50%;
  1217. transform: translateX(-50%);
  1218. z-index: 1000;
  1219. background: rgba(0, 0, 0, 0.7);
  1220. color: white;
  1221. padding: 8px 16px;
  1222. border-radius: 20px;
  1223. font-size: 14px;
  1224. pointer-events: none;
  1225. }
  1226. .canvas-box {
  1227. /* background-color: rgb(214, 0, 0); */
  1228. position: fixed;
  1229. left: 0;
  1230. top: 0;
  1231. z-index: -1;
  1232. }
  1233. #info {
  1234. position: absolute;
  1235. width: 100%;
  1236. color: #ffffff;
  1237. padding: 5px;
  1238. font-family: Monospace;
  1239. font-size: 13px;
  1240. font-weight: bold;
  1241. text-align: center;
  1242. z-index: 1;
  1243. }
  1244. #menu {
  1245. z-index: 4;
  1246. position: absolute;
  1247. bottom: 1vh;
  1248. width: 100%;
  1249. display: flex;
  1250. justify-content: center;
  1251. align-items: center;
  1252. }
  1253. .element {
  1254. width: 7.5vw;
  1255. height: 18.5vh;
  1256. /* box-shadow: 0 0 12px rgba(0, 255, 255, 0.5); */
  1257. border: 1px solid rgba(127, 255, 255, 0.25);
  1258. text-align: center;
  1259. cursor: default;
  1260. transition: background-color 0.3s ease-in;
  1261. }
  1262. .element:hover {
  1263. box-shadow: 0 0 12px rgba(255, 168, 38, 0.75);
  1264. border: 1px solid rgba(255, 255, 255, 1);
  1265. }
  1266. .element .name {
  1267. font-size: 2.9vh;
  1268. font-weight: bold;
  1269. color: rgba(255, 255, 255, 1);
  1270. /* text-shadow: 0 0 1vh rgba(0, 255, 255, 0.95); */
  1271. }
  1272. button {
  1273. border: none;
  1274. }
  1275. .btn {
  1276. background-image: url("../../../assets/img/抽奖按钮.png");
  1277. background-color: transparent;
  1278. background-repeat: no-repeat;
  1279. background-size: 100% 100%;
  1280. width: 180px;
  1281. height: 70px;
  1282. color: #fff;
  1283. border: 0;
  1284. font-size: 20px;
  1285. font-weight: bold;
  1286. cursor: pointer;
  1287. text-align: center;
  1288. padding-top: 12px;
  1289. }
  1290. .highlight {
  1291. background: linear-gradient(360deg, #e23d26, #f49c27) !important;
  1292. box-shadow: 0 0 12px rgba(253, 105, 0, 0.95);
  1293. border: 2px solid rgba(255, 255, 255, 1);
  1294. }
  1295. .highlight.element .name {
  1296. text-shadow: 0 0 16px rgba(255, 255, 255, 0.95);
  1297. }
  1298. .prize.element .name {
  1299. text-shadow: none;
  1300. }
  1301. .prize.element {
  1302. transition: background-color 1.5s ease-in 0.3s;
  1303. background: linear-gradient(360deg, #e23d26, #f49c27) !important;
  1304. border: 2px solid rgba(255, 255, 255, 1);
  1305. }
  1306. .prize .company,
  1307. .prize .details,
  1308. .prize .name,
  1309. .highlight .company,
  1310. .highlight .name,
  1311. .highlight .details {
  1312. color: rgba(255, 255, 255, 0.85);
  1313. }
  1314. .dan-mu {
  1315. visibility: hidden;
  1316. position: fixed;
  1317. z-index: -1;
  1318. font-size: 12px;
  1319. top: 1vh;
  1320. left: 0;
  1321. padding: 0 1.2vh;
  1322. height: 2.2vh;
  1323. line-height: 2.2vh;
  1324. border-radius: 1vh;
  1325. box-sizing: border-box;
  1326. background-color: rgba(0, 127, 127, 0.37);
  1327. box-shadow: 0 0 4px rgba(0, 255, 255, 0.5);
  1328. border: 1px solid rgba(127, 255, 255, 0.25);
  1329. color: rgba(127, 255, 255, 0.75);
  1330. }
  1331. .dan-mu.active {
  1332. visibility: visible;
  1333. }
  1334. .leftBar {
  1335. position: fixed;
  1336. left: 0;
  1337. padding-left: 1.2vh;
  1338. top: 15vh;
  1339. z-index: 100;
  1340. }
  1341. #prizeBar {
  1342. max-height: 60vh;
  1343. width: 330px;
  1344. overflow-x: hidden;
  1345. overflow-y: auto;
  1346. /* 隐藏滚动条 */
  1347. scrollbar-width: none;
  1348. /* Firefox */
  1349. -ms-overflow-style: none;
  1350. /* IE */
  1351. }
  1352. #prizeBar::-webkit-scrollbar {
  1353. display: none;
  1354. /* Chrome, Safari */
  1355. }
  1356. .prize-list {
  1357. margin: 0;
  1358. padding: 0;
  1359. list-style: none;
  1360. }
  1361. .prize-item {
  1362. padding: 2px;
  1363. display: flex;
  1364. align-items: center;
  1365. justify-content: center;
  1366. flex-wrap: nowrap;
  1367. /* color: rgba(127, 255, 255, 0.75); */
  1368. width: 29vh;
  1369. height: 9.5vh;
  1370. box-sizing: border-box;
  1371. transition: transform 1s ease-in;
  1372. }
  1373. .getPrizeName {
  1374. color: #d5291f;
  1375. font-weight: bold;
  1376. font-size: 24px;
  1377. width: 27vh;
  1378. height: 5vh;
  1379. border: 2px solid rgb(255, 255, 255);
  1380. border-radius: 5px;
  1381. display: flex;
  1382. flex-direction: column;
  1383. justify-content: center;
  1384. align-items: center;
  1385. background-color: #ffd283;
  1386. opacity: 0.8;
  1387. margin-left: 2vh;
  1388. margin-top: 3.5vh;
  1389. cursor: pointer;
  1390. position: relative;
  1391. }
  1392. .dgetPrizeName {
  1393. color: #d5291f;
  1394. font-weight: bold;
  1395. font-size: 24px;
  1396. width: 27vh;
  1397. height: 60vh;
  1398. border: 2px solid rgb(255, 255, 255);
  1399. border-radius: 5px;
  1400. display: flex;
  1401. flex-direction: column;
  1402. /* justify-content: center; */
  1403. align-items: center;
  1404. background-color: #ffd283;
  1405. opacity: 0.8;
  1406. margin-left: 2vh;
  1407. margin-top: 3.5vh;
  1408. position: relative;
  1409. }
  1410. .open {
  1411. position: absolute;
  1412. top: -42px;
  1413. animation: bounce1 2s ease-in-out infinite;
  1414. transform: rotate(180deg);
  1415. }
  1416. .close {
  1417. position: absolute;
  1418. top: -32px;
  1419. animation: bounce2 2s ease-in-out infinite;
  1420. cursor: pointer;
  1421. }
  1422. @keyframes bounce1 {
  1423. 0%,
  1424. 100% {
  1425. transform: rotate(180deg) translateY(0);
  1426. }
  1427. 50% {
  1428. transform: rotate(180deg) translateY(-8px);
  1429. }
  1430. }
  1431. @keyframes bounce2 {
  1432. 0%,
  1433. 100% {
  1434. transform: translateY(0);
  1435. }
  1436. 50% {
  1437. transform: translateY(-8px);
  1438. }
  1439. }
  1440. .tableHead {
  1441. width: 100%;
  1442. display: flex;
  1443. justify-content: center;
  1444. font-size: 18px;
  1445. /* gap: 80px; */
  1446. margin-bottom: 10px;
  1447. position: absolute;
  1448. top: 10px;
  1449. }
  1450. .tableHead1 {
  1451. width: 50%;
  1452. text-align: center;
  1453. }
  1454. .tableHead2 {
  1455. width: 50%;
  1456. text-align: center;
  1457. }
  1458. .gradient-line {
  1459. width: 90%;
  1460. height: 3px;
  1461. border-radius: 150%;
  1462. background: linear-gradient(
  1463. to right,
  1464. transparent 0%,
  1465. #d5291f 45%,
  1466. #d5291f 55%,
  1467. transparent 100%
  1468. );
  1469. position: absolute;
  1470. bottom: -10px;
  1471. }
  1472. .tableBody {
  1473. display: flex;
  1474. margin-top: 50px;
  1475. width: 100%;
  1476. height: 50vh;
  1477. justify-content: center;
  1478. overflow-y: auto;
  1479. /* 启用垂直滚动 */
  1480. overflow-x: hidden;
  1481. /* 启用垂直滚动 */
  1482. /* 隐藏滚动条 */
  1483. scrollbar-width: none;
  1484. /* Firefox */
  1485. -ms-overflow-style: none;
  1486. /* IE */
  1487. }
  1488. .tableBody::-webkit-scrollbar {
  1489. display: none;
  1490. /* Chrome, Safari */
  1491. }
  1492. .tableItem {
  1493. display: flex;
  1494. font-size: 18px;
  1495. width: 27vh;
  1496. margin-bottom: 10px;
  1497. top: 10px;
  1498. }
  1499. .tableItem1 {
  1500. width: 50%;
  1501. text-align: center;
  1502. }
  1503. .tableItem2 {
  1504. width: 50%;
  1505. text-align: center;
  1506. }
  1507. .tableFoot {
  1508. color: #e64f39;
  1509. display: flex;
  1510. gap: 10px;
  1511. }
  1512. .leftPage {
  1513. background-color: #ccc;
  1514. color: white;
  1515. cursor: not-allowed;
  1516. /* background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%); */
  1517. border-radius: 50%;
  1518. display: flex;
  1519. width: 35px;
  1520. height: 35px;
  1521. justify-content: center;
  1522. /* align-items: center; */
  1523. }
  1524. .rightPage {
  1525. color: white;
  1526. cursor: pointer;
  1527. background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%);
  1528. border-radius: 50%;
  1529. display: flex;
  1530. width: 35px;
  1531. height: 35px;
  1532. justify-content: center;
  1533. }
  1534. .readyLook {
  1535. width: 100%;
  1536. height: 100%;
  1537. }
  1538. .prize-item .prize-img {
  1539. width: 7.5vh;
  1540. height: 7.5vh;
  1541. margin: auto 10px;
  1542. border-radius: 50%;
  1543. background-color: #fff;
  1544. text-shadow: 0 0 1vh rgba(0, 255, 255, 0.95);
  1545. overflow: hidden;
  1546. }
  1547. .prize-img img {
  1548. width: 90%;
  1549. height: 90%;
  1550. position: relative;
  1551. top: 50%;
  1552. left: 50%;
  1553. transform: translate(-50%, -50%);
  1554. }
  1555. .prize-text {
  1556. padding: 0 5px;
  1557. width: 100%;
  1558. height: 100%;
  1559. /* margin: auto 0; */
  1560. flex: 1;
  1561. display: flex;
  1562. flex-direction: column;
  1563. justify-content: center;
  1564. /* gap: 4px; */
  1565. }
  1566. .prize-title {
  1567. margin: 4px 0;
  1568. font-size: 1.4vh;
  1569. height: 30%;
  1570. border: 1px solid #e13726;
  1571. border-radius: 20px;
  1572. background-color: #ffffff;
  1573. display: flex;
  1574. color: #d5291f;
  1575. font-weight: bold;
  1576. display: flex;
  1577. align-items: center;
  1578. }
  1579. .level {
  1580. width: 38%;
  1581. height: 100%;
  1582. margin-right: 2%;
  1583. border-radius: 20px;
  1584. background: linear-gradient(360deg, #e23d26, #f49c27) !important;
  1585. color: #fff;
  1586. display: flex;
  1587. justify-content: center;
  1588. align-items: center;
  1589. }
  1590. .prize-count {
  1591. padding: 4px 0;
  1592. position: relative;
  1593. }
  1594. .prize-count .progress {
  1595. height: 1.8vh;
  1596. background: rgba(0, 0, 0, 0.5);
  1597. padding: 1px;
  1598. overflow: visible;
  1599. border-radius: 1vh;
  1600. }
  1601. .progress .progress-bar {
  1602. border-radius: 1.8vh;
  1603. position: relative;
  1604. animation: animate-positive 2s;
  1605. background-color: #d9534f;
  1606. height: 1.8vh;
  1607. -webkit-transition: width 0.6s ease;
  1608. -o-transition: width 0.6s ease;
  1609. transition: width 0.6s ease;
  1610. }
  1611. .progress-bar.active {
  1612. animation: reverse progress-bar-stripes 0.4s linear infinite,
  1613. animate-positive 2s;
  1614. }
  1615. .progress-bar-striped {
  1616. background-image: -webkit-linear-gradient(
  1617. 45deg,
  1618. rgba(255, 255, 255, 0.15) 25%,
  1619. transparent 25%,
  1620. transparent 50%,
  1621. rgba(255, 255, 255, 0.15) 50%,
  1622. rgba(255, 255, 255, 0.15) 75%,
  1623. transparent 75%,
  1624. transparent
  1625. );
  1626. background-image: -o-linear-gradient(
  1627. 45deg,
  1628. rgba(255, 255, 255, 0.15) 25%,
  1629. transparent 25%,
  1630. transparent 50%,
  1631. rgba(255, 255, 255, 0.15) 50%,
  1632. rgba(255, 255, 255, 0.15) 75%,
  1633. transparent 75%,
  1634. transparent
  1635. );
  1636. background-image: linear-gradient(
  1637. 45deg,
  1638. rgba(255, 255, 255, 0.15) 25%,
  1639. transparent 25%,
  1640. transparent 50%,
  1641. rgba(255, 255, 255, 0.15) 50%,
  1642. rgba(255, 255, 255, 0.15) 75%,
  1643. transparent 75%,
  1644. transparent
  1645. );
  1646. -webkit-background-size: 8px 8px;
  1647. background-size: 8px 8px;
  1648. }
  1649. @-webkit-keyframes animate-positive {
  1650. 0% {
  1651. width: 0;
  1652. }
  1653. }
  1654. @keyframes animate-positive {
  1655. 0% {
  1656. width: 0;
  1657. }
  1658. }
  1659. @-webkit-keyframes progress-bar-stripes {
  1660. from {
  1661. background-position: 8px 0;
  1662. }
  1663. to {
  1664. background-position: 0 0;
  1665. }
  1666. }
  1667. @-o-keyframes progress-bar-stripes {
  1668. from {
  1669. background-position: 8px 0;
  1670. }
  1671. to {
  1672. background-position: 0 0;
  1673. }
  1674. }
  1675. @keyframes progress-bar-stripes {
  1676. from {
  1677. background-position: 8px 0;
  1678. }
  1679. to {
  1680. background-position: 0 0;
  1681. }
  1682. }
  1683. .prize-count-left {
  1684. position: absolute;
  1685. color: #fff;
  1686. right: 35%;
  1687. font-size: 1.8vh;
  1688. line-height: 1.6vh;
  1689. top: 50%;
  1690. transform: translateY(-50%);
  1691. }
  1692. .shine {
  1693. background-color: #ffd283;
  1694. border: 1px solid rgba(255, 255, 255, 1);
  1695. border-radius: 5px;
  1696. box-shadow: 0 0 15px 0 #ffd283;
  1697. transform: scale(1.1);
  1698. transform-origin: left center;
  1699. position: relative;
  1700. overflow: hidden;
  1701. }
  1702. .done {
  1703. position: relative;
  1704. }
  1705. .done:after {
  1706. content: "";
  1707. position: absolute;
  1708. top: 0;
  1709. left: 0;
  1710. right: 0;
  1711. bottom: 0;
  1712. background-color: #ffd283;
  1713. border: 2px solid rgba(255, 255, 255, 1);
  1714. border-radius: 5px;
  1715. opacity: 0.3;
  1716. cursor: not-allowed;
  1717. }
  1718. .shine span {
  1719. position: absolute;
  1720. display: block;
  1721. }
  1722. .shine span:nth-child(1) {
  1723. top: 0;
  1724. left: 0;
  1725. width: 100%;
  1726. height: 2px;
  1727. background: linear-gradient(90deg, transparent, #f46303);
  1728. animation: animate1 1s linear infinite;
  1729. }
  1730. @keyframes animate1 {
  1731. 0% {
  1732. left: -100%;
  1733. }
  1734. 50%,
  1735. 100% {
  1736. left: 100%;
  1737. }
  1738. }
  1739. .shine span:nth-child(2) {
  1740. top: -100%;
  1741. right: 0;
  1742. width: 2px;
  1743. height: 100%;
  1744. background: linear-gradient(180deg, transparent, #f46303);
  1745. animation: animate2 1s linear infinite;
  1746. animation-delay: 0.25s;
  1747. }
  1748. @keyframes animate2 {
  1749. 0% {
  1750. top: -100%;
  1751. }
  1752. 50%,
  1753. 100% {
  1754. top: 100%;
  1755. }
  1756. }
  1757. .shine span:nth-child(3) {
  1758. bottom: 0;
  1759. right: 0;
  1760. width: 100%;
  1761. height: 2px;
  1762. background: linear-gradient(270deg, transparent, #f46303);
  1763. animation: animate3 1s linear infinite;
  1764. animation-delay: 0.5s;
  1765. }
  1766. @keyframes animate3 {
  1767. 0% {
  1768. right: -100%;
  1769. }
  1770. 50%,
  1771. 100% {
  1772. right: 100%;
  1773. }
  1774. }
  1775. .shine span:nth-child(4) {
  1776. bottom: -100%;
  1777. left: 0;
  1778. width: 2px;
  1779. height: 100%;
  1780. background: linear-gradient(360deg, transparent, #f46303);
  1781. animation: animate4 1s linear infinite;
  1782. animation-delay: 0.75s;
  1783. }
  1784. @keyframes animate4 {
  1785. 0% {
  1786. bottom: -100%;
  1787. }
  1788. 50%,
  1789. 100% {
  1790. bottom: 100%;
  1791. }
  1792. }
  1793. .shine.prize-item {
  1794. /* width: 29.5vh; */
  1795. margin: 1.2vh 0;
  1796. }
  1797. .prize-mess {
  1798. color: #fff;
  1799. line-height: 5vh;
  1800. font-size: 1.6vh;
  1801. margin: 2.4vh 0;
  1802. }
  1803. .music {
  1804. position: fixed;
  1805. top: 3vh;
  1806. right: 4vh;
  1807. z-index: 5;
  1808. }
  1809. .music-item {
  1810. display: block !important;
  1811. opacity: 0;
  1812. }
  1813. .music-box {
  1814. width: 5vh;
  1815. height: 5vh;
  1816. border-radius: 50%;
  1817. text-align: center;
  1818. line-height: 5vh;
  1819. font-size: 1.4vh;
  1820. color: #fff;
  1821. cursor: pointer;
  1822. background-color: rgba(253, 105, 0, 0.9);
  1823. border: 1px solid rgba(255, 255, 255, 0.5);
  1824. }
  1825. .rotate-active {
  1826. animation: rotate 4s linear infinite;
  1827. }
  1828. @keyframes rotate {
  1829. from {
  1830. transform: rotate(0);
  1831. }
  1832. to {
  1833. transform: rotate(360deg);
  1834. }
  1835. }
  1836. .margin-l-40 {
  1837. margin-left: 40px;
  1838. }
  1839. .fixed-bar {
  1840. position: fixed;
  1841. bottom: 20px;
  1842. right: 20px;
  1843. }
  1844. .fixed-btn {
  1845. margin: 20px 0 0;
  1846. width: 200px;
  1847. text-align: center;
  1848. display: block;
  1849. }
  1850. #lottery {
  1851. animation: breath 1.6s linear infinite;
  1852. /* box-shadow: 0px 0px 15px rgb(127 255 255 / 75%); */
  1853. }
  1854. @keyframes breath {
  1855. 0% {
  1856. transform: scale(1);
  1857. opacity: 0.8;
  1858. }
  1859. 25% {
  1860. transform: scale(1.1);
  1861. opacity: 1;
  1862. }
  1863. 50% {
  1864. transform: scale(1);
  1865. opacity: 1;
  1866. }
  1867. 75% {
  1868. transform: scale(0.9);
  1869. opacity: 1;
  1870. }
  1871. 100% {
  1872. transform: scale(1);
  1873. opacity: 0.8;
  1874. }
  1875. }
  1876. </style>