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.

743 lines
19 KiB

4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
  1. <template>
  2. <div v-if="showOne">
  3. <div class="prize-panel-root">
  4. <div v-if="showNotification" class="auto-hide-notification">
  5. {{ notificationMessage }}
  6. </div>
  7. <div class="prize-panel-list" v-if="prizes && prizes.length">
  8. <div
  9. class="prize-panel-item"
  10. v-for="(prize, idx) in prizes"
  11. :key="prize.type || idx"
  12. :class="{
  13. 'revealed-highlight': idx === lastRevealedIdx,
  14. disabled: idx === nextRevealIdx && !canRevealPrize(idx),
  15. }"
  16. @click="handleReveal(idx)"
  17. :style="{
  18. cursor:
  19. idx === nextRevealIdx && !canRevealPrize(idx)
  20. ? 'not-allowed'
  21. : 'pointer',
  22. }"
  23. >
  24. <div v-if="isRevealed(idx)" class="prize-card">
  25. <div class="prize-img-wrap">
  26. <img class="prize-img" :src="prize.img" :alt="prize.title" />
  27. </div>
  28. <div class="prize-info">
  29. <div class="prize-row prize-row-top">
  30. <span class="prize-level">{{ prize.title }}</span>
  31. <span class="prize-name">{{ prize.text }}</span>
  32. </div>
  33. <div class="prize-row prize-row-bottom">
  34. <div class="progress-bar-bg">
  35. <div
  36. class="progress-bar-fill"
  37. :style="{ width: getProgressPercent(prize) + '%' }"
  38. ></div>
  39. <span class="progress-bar-text">
  40. {{ getLeftCount(prize) }}/{{ prize.count }}
  41. </span>
  42. </div>
  43. </div>
  44. </div>
  45. </div>
  46. <div v-else class="prize-card prize-card-mask">
  47. <img
  48. src="../../../assets/daijiemi.png"
  49. alt="待揭秘"
  50. class="prize-mask-img"
  51. />
  52. </div>
  53. </div>
  54. </div>
  55. <div class="prize-panel-footer">
  56. <div class="arrow-up" @click="openWinnerList"></div>
  57. <button ref="winnerBtnRef" class="winner-btn" @click="openWinnerList">
  58. 获奖名单
  59. </button>
  60. </div>
  61. </div>
  62. </div>
  63. <div v-else>
  64. <div class="prize-panel-root">
  65. <div
  66. class="prize-panel-list1"
  67. v-if="prizes && prizes.length && lastRevealedIdx >= 0"
  68. >
  69. <div
  70. class="prize-panel-item"
  71. :key="prizes[lastRevealedIdx].type || lastRevealedIdx"
  72. :class="{ 'revealed-highlight': true }"
  73. style="cursor: pointer"
  74. >
  75. <div class="prize-card">
  76. <div class="prize-img-wrap">
  77. <img
  78. class="prize-img"
  79. :src="prizes[lastRevealedIdx].img"
  80. :alt="prizes[lastRevealedIdx].title"
  81. />
  82. </div>
  83. <div class="prize-info">
  84. <div class="prize-row prize-row-top">
  85. <span class="prize-level">{{
  86. prizes[lastRevealedIdx].title
  87. }}</span>
  88. <span class="prize-name">{{
  89. prizes[lastRevealedIdx].text
  90. }}</span>
  91. </div>
  92. <div class="prize-row prize-row-bottom">
  93. <div class="progress-bar-bg">
  94. <div
  95. class="progress-bar-fill"
  96. :style="{
  97. width: getProgressPercent(prizes[lastRevealedIdx]) + '%',
  98. }"
  99. ></div>
  100. <span class="progress-bar-text">
  101. {{ getLeftCount(prizes[lastRevealedIdx]) }}/{{
  102. prizes[lastRevealedIdx].count
  103. }}
  104. </span>
  105. </div>
  106. <div class="prize-panel-footer">
  107. <div class="arrow-down" @click="toggleWinnerList"></div>
  108. <div
  109. v-if="showWinnerList"
  110. class="winner-modal-mask"
  111. @click="closeWinnerList"
  112. >
  113. <div
  114. class="winner-modal"
  115. :style="{
  116. position: 'absolute',
  117. left: modalLeft + 'px',
  118. top: modalTop + 'px',
  119. }"
  120. @click.stop
  121. >
  122. <div class="winner-modal-title">
  123. <span>Homily ID</span><span>奖项</span>
  124. </div>
  125. <div
  126. style="
  127. background: linear-gradient(
  128. to left,
  129. rgb(232 76 10),
  130. rgb(195 6 6),
  131. rgb(240 90 9)
  132. );
  133. height: 1px;
  134. "
  135. ></div>
  136. <ul class="winner-list">
  137. <li
  138. v-for="(user, idx) in fakeWinners"
  139. :key="idx"
  140. style="
  141. display: flex;
  142. justify-content: space-between;
  143. align-items: center;
  144. "
  145. >
  146. <!-- <span>{{ user.id }}</span> - <span>{{ user.name }}</span> - -->
  147. <span>{{ user.jwcode }}</span>
  148. <span>{{ user.prizeName }}</span>
  149. </li>
  150. </ul>
  151. </div>
  152. </div>
  153. </div>
  154. </div>
  155. </div>
  156. </div>
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. </template>
  162. <script setup>
  163. import { ref, computed, nextTick, watch, onMounted } from "vue";
  164. import { useLotteryStore } from "../../../store/lottery";
  165. import { useDataManager } from "../lottery/dataManager";
  166. const showNotification = ref(false);
  167. const notificationMessage = ref('');
  168. // 显示提示并自动消失
  169. const showAutoHideNotification = (message, duration = 1500) => {
  170. notificationMessage.value = message;
  171. showNotification.value = true;
  172. setTimeout(() => {
  173. showNotification.value = false;
  174. }, duration);
  175. };
  176. const props = defineProps({
  177. prizes: Array,
  178. });
  179. // 新增:控制已揭秘奖品数量
  180. const revealedCount = ref(0);
  181. // 新增:记录最新揭秘的奖品索引
  182. const lotteryStore = useLotteryStore();
  183. const lastRevealed = computed({
  184. get: () => lotteryStore.lastRevealedIdx,
  185. set: (val) => lotteryStore.setLastRevealedIdx(val),
  186. });
  187. const waitingForNextReveal = computed({
  188. get: () => lotteryStore.waitingForNextReveal,
  189. set: (val) => lotteryStore.setWaitingForNextReveal(val),
  190. });
  191. const winners = computed({
  192. get: () => lotteryStore.winners,
  193. set: (val) => lotteryStore.setWinners(val),
  194. });
  195. // 用watch监听winners的变化
  196. // watch(winners, (newVal) => {
  197. // console.log("中奖人数列表winners", newVal);
  198. // fakeWinners.value = newVal;
  199. // });
  200. const lastRevealedIdx = ref(-1);
  201. lastRevealedIdx.value = lastRevealed.value;
  202. const showOne = ref(true);
  203. // 计算哪些奖品已揭秘
  204. const isRevealed = (idx) =>
  205. idx >= (props.prizes?.length || 0) - revealedCount.value;
  206. // 允许点击的卡片index
  207. const nextRevealIdx = computed(
  208. () => (props.prizes?.length || 0) - revealedCount.value - 1
  209. );
  210. // 检查指定奖品是否可以揭秘
  211. function canRevealPrize(idx) {
  212. // 如果这是第一个要揭秘的奖品,直接允许
  213. if (lastRevealedIdx.value === -1) {
  214. return true;
  215. }
  216. // 检查上一个揭秘的奖品是否已经抽完
  217. const lastPrize = props.prizes[lastRevealedIdx.value];
  218. if (lastPrize) {
  219. const leftCount = getLeftCount(lastPrize);
  220. // 如果上一个奖品还有剩余,则不能揭秘下一个
  221. if (leftCount > 0) {
  222. waitingForNextReveal.value = false;
  223. return false;
  224. }
  225. waitingForNextReveal.value = true;
  226. }
  227. return true;
  228. }
  229. // 卡片点击事件
  230. function handleReveal(idx) {
  231. // 检查是否可以揭秘这个奖品
  232. if (idx !== nextRevealIdx.value && !isRevealed(idx)) {
  233. // alert("请按顺序揭秘奖品!");
  234. showAutoHideNotification("请按顺序揭秘奖品!");
  235. return;
  236. }
  237. if (idx === nextRevealIdx.value && canRevealPrize(idx)) {
  238. revealedCount.value++;
  239. lastRevealedIdx.value = idx; // 记录最新揭秘的索引
  240. if (idx === 0) {
  241. waitingForNextReveal.value = false;
  242. }
  243. console.log("lastRevealedIdx.value", lastRevealedIdx.value);
  244. lastRevealed.value = idx;
  245. console.log("lastRevealed.value", lastRevealed.value);
  246. } else if (idx === nextRevealIdx.value && !canRevealPrize(idx)) {
  247. // 可以在这里添加提示信息,告知用户上一个奖品还未抽完
  248. console.log("上一个奖品还未抽完,不能揭秘下一个奖品");
  249. // 可以添加一个提示弹窗或toast
  250. // alert("上一个奖品还未抽完,请等待抽奖完成后再揭秘下一个奖品");
  251. }
  252. }
  253. // 计算未抽取数量
  254. function getLeftCount(prize) {
  255. // 这里假设奖品有 type 字段,且 dataManager.state.basicData.luckyUsers 可用
  256. // 由于本组件无 luckyUsers 数据,建议父组件传入或全局可访问
  257. // 这里用 window.dataManager 兼容演示
  258. let luckyUsers =
  259. (window.dataManager && window.dataManager.state.basicData.luckyUsers) || {};
  260. let got = luckyUsers[prize.type]?.length || 0;
  261. return prize.remainNum;
  262. }
  263. // 新增部分
  264. const showWinnerList = ref(false);
  265. const fakeWinners = ref([]);
  266. // fakeWinners.value = winners.value;
  267. console.log("fakeWinners", fakeWinners.value);
  268. import { getGetPrizeUserListApi } from "../../../api/API";
  269. const updateWinners = async () => {
  270. try {
  271. const response = await getGetPrizeUserListApi({
  272. pageSize: 1000,
  273. pageNum: 1,
  274. });
  275. console.log("updatePrizeList response", response);
  276. fakeWinners.value = response.data.list;
  277. console.log("updateWinners fakeWinners", fakeWinners.value);
  278. } catch (error) {
  279. console.error("updatePrizeList error", error);
  280. }
  281. };
  282. async function openWinnerList() {
  283. // showWinnerList.value = true;
  284. if (!showWinnerList.value) {
  285. if (revealedCount.value === 0) {
  286. // alert("请先揭晓奖品,并抽奖!");
  287. showAutoHideNotification("请先揭晓奖品,并抽奖!");
  288. }
  289. // await updatePrizeList();
  290. }
  291. if (revealedCount.value > 0) {
  292. showWinnerList.value = true;
  293. }
  294. // 当存在最新揭秘的奖品时,点击获奖名单将showOne设置为false
  295. if (lastRevealedIdx.value >= 0) {
  296. showOne.value = false;
  297. }
  298. }
  299. function closeWinnerList() {
  300. showWinnerList.value = false;
  301. // 当关闭获奖名单且showOne为false时,将其切换回true
  302. if (!showOne.value) {
  303. showOne.value = true;
  304. }
  305. }
  306. const winnerBtnRef = ref(null);
  307. const modalLeft = ref(0);
  308. const modalTop = ref(0);
  309. async function toggleWinnerList() {
  310. await updateWinners();
  311. showWinnerList.value = !showWinnerList.value;
  312. console.log(
  313. "toggleWinnerList - showWinnerList:",
  314. showWinnerList.value,
  315. "showOne:",
  316. showOne.value,
  317. "lastRevealedIdx:",
  318. lastRevealedIdx.value
  319. );
  320. if (!showWinnerList.value) {
  321. if (revealedCount.value === 0) {
  322. // alert("请先揭晓奖品,并抽奖!");
  323. showAutoHideNotification("请先揭晓奖品,并抽奖!");
  324. }
  325. }
  326. if (showWinnerList.value) {
  327. // 当存在最新揭秘的奖品时,点击获奖名单将showOne设置为false
  328. if (lastRevealedIdx.value > 0) {
  329. showOne.value = false;
  330. console.log("设置 showOne 为 false");
  331. }
  332. } else {
  333. // 当关闭获奖名单且showOne为false时,将其切换回true
  334. if (!showOne.value) {
  335. showOne.value = true;
  336. console.log("设置 showOne 为 true");
  337. }
  338. }
  339. }
  340. function getProgressPercent(prize) {
  341. const total = prize.count || 1;
  342. const left = getLeftCount(prize);
  343. // 返回剩余数量的百分比,这样开始时是满的,抽完后是空的
  344. return Math.round((left / total) * 100);
  345. }
  346. onMounted(() => {
  347. updateWinners();
  348. });
  349. </script>
  350. <style scoped>
  351. .auto-hide-notification {
  352. position: fixed;
  353. top: 300px;
  354. left: 50%;
  355. transform: translateX(-50%);
  356. color: #e61414;
  357. padding: 8px 16px;
  358. border-radius: 4px;
  359. font-size: 50px;
  360. z-index: 1000;}
  361. .prize-panel-list {
  362. position: relative;
  363. background: none;
  364. z-index: 10;
  365. min-width: 300px;
  366. max-width: 362px;
  367. text-align: left;
  368. display: flex;
  369. flex-direction: column;
  370. gap: 18px;
  371. max-height: 700px;
  372. overflow-x: hidden !important;
  373. overflow-y: auto;
  374. padding-right: 10px;
  375. padding-left: 10px;
  376. scrollbar-width: thin;
  377. scrollbar-color: #ffd283 rgba(255, 210, 131, 0.3); /* Firefox */
  378. }
  379. .prize-panel-list1 {
  380. position: relative;
  381. background: none;
  382. z-index: 10;
  383. min-width: 320px;
  384. max-width: 342px;
  385. padding-right: 10px;
  386. padding-left: 10px;
  387. text-align: left;
  388. display: flex;
  389. }
  390. .prize-panel-list::-webkit-scrollbar {
  391. width: 6px;
  392. }
  393. .prize-panel-list::-webkit-scrollbar-track {
  394. background: rgba(255, 210, 131, 0.3);
  395. border-radius: 3px;
  396. }
  397. .prize-panel-list::-webkit-scrollbar-thumb {
  398. background-color: #ffd283;
  399. border-radius: 3px;
  400. }
  401. .prize-panel-item {
  402. background: #ffd283;
  403. border-radius: 6px 6px 6px 6px;
  404. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  405. display: flex;
  406. align-items: center;
  407. min-width: 320px;
  408. }
  409. .prize-card {
  410. display: flex;
  411. align-items: center;
  412. width: 100%;
  413. padding: 10px 18px;
  414. }
  415. .prize-img-wrap {
  416. width: 64px;
  417. height: 64px;
  418. border-radius: 50%;
  419. background: #fff;
  420. display: flex;
  421. align-items: center;
  422. justify-content: center;
  423. overflow: hidden;
  424. margin-right: 18px;
  425. border: 2px solid #fff3e0;
  426. }
  427. .prize-img {
  428. width: 60px;
  429. height: 60px;
  430. object-fit: contain;
  431. }
  432. .prize-info {
  433. flex: 1;
  434. display: flex;
  435. flex-direction: column;
  436. justify-content: center;
  437. }
  438. .prize-row {
  439. display: flex;
  440. align-items: center;
  441. background: #ffffff;
  442. border-radius: 93px 93px 93px 93px;
  443. }
  444. .prize-row-top {
  445. margin-bottom: 8px;
  446. border: 1px solid #ea2b0a;
  447. }
  448. .prize-level {
  449. background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%);
  450. color: #fff;
  451. border-radius: 15.71px 15.71px 15.71px 15.71px;
  452. padding: 2px 18px;
  453. font-size: 18px;
  454. font-weight: bold;
  455. margin-right: 12px;
  456. }
  457. .prize-name {
  458. font-size: 18px;
  459. color: #d84315;
  460. font-weight: 500;
  461. }
  462. .prize-row-bottom {
  463. /* margin-bottom: 8px; */
  464. /* border: 1px solid #ea2b0a; */
  465. }
  466. .custom-arrow-icon {
  467. font-size: 24px; /* 图标大小 */
  468. color: #d84315; /* 图标颜色,使用项目主题橙色 */
  469. margin: 5px; /* 外边距 */
  470. cursor: pointer; /* 鼠标悬停样式 */
  471. transition: transform 0.3s ease; /* 过渡动画 */
  472. }
  473. .custom-arrow-icon:hover {
  474. transform: scale(1.1); /* 悬停放大效果 */
  475. }
  476. .prize-count {
  477. font-size: 20px;
  478. font-weight: bold;
  479. }
  480. .prize-divider {
  481. margin: 0 4px;
  482. font-size: 20px;
  483. }
  484. .prize-total {
  485. font-size: 20px;
  486. font-weight: bold;
  487. }
  488. .prize-panel-root {
  489. position: absolute;
  490. top: 20px;
  491. left: 20px;
  492. background: none;
  493. z-index: 10;
  494. min-width: 320px;
  495. max-width: 362px;
  496. display: flex;
  497. flex-direction: column;
  498. }
  499. .prize-panel-footer {
  500. position: relative;
  501. /* width: 100%; */
  502. display: flex;
  503. flex-direction: column;
  504. align-items: center;
  505. z-index: 20;
  506. padding: 10px 0;
  507. border-radius: 0 0 6px 6px;
  508. margin-top: 10px;
  509. }
  510. .arrow-up {
  511. position: relative;
  512. width: 50px;
  513. height: 30px;
  514. margin-bottom: 4px;
  515. cursor: pointer;
  516. background-image: url("../../../assets/展开.png");
  517. background-size: cover;
  518. background-position: center;
  519. transform: rotate(180deg);
  520. }
  521. .arrow-down {
  522. position: fixed;
  523. top: 100px;
  524. left: 150px;
  525. width: 47px;
  526. height: 28px;
  527. margin-bottom: 4px;
  528. cursor: pointer;
  529. background-image: url("../../../assets/展开.png");
  530. background-size: cover;
  531. background-position: center;
  532. }
  533. .winner-btn {
  534. background: rgba(255, 210, 131, 0.8);
  535. color: #d5291f;
  536. border: #fff;
  537. border-radius: 8px;
  538. padding: 15px 79px;
  539. font-size: 20px;
  540. font-weight: bold;
  541. cursor: pointer;
  542. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  543. outline: none;
  544. }
  545. .winner-modal-mask {
  546. position: fixed;
  547. top: 142px;
  548. left: -4px;
  549. width: 100vw;
  550. height: 100vh;
  551. background: rgba(0, 0, 0, 0.01);
  552. z-index: 1000;
  553. display: flex;
  554. align-items: flex-start;
  555. justify-content: center;
  556. }
  557. .winner-modal {
  558. background: rgba(255, 210, 131, 0.8);
  559. border-radius: 12px;
  560. padding-top: 12px;
  561. padding-left: 25px;
  562. padding-right: 25px;
  563. padding-bottom: 10px;
  564. min-width: 280px;
  565. max-width: 90vw;
  566. box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
  567. position: relative;
  568. margin-left: 10px;
  569. }
  570. .winner-modal-title {
  571. font-size: 22px;
  572. font-weight: bold;
  573. color: #e64f39;
  574. margin-bottom: 5px;
  575. text-align: center;
  576. display: flex;
  577. justify-content: space-between; /* 左右对齐 */
  578. align-items: center; /* 垂直居中对齐 */
  579. /* 可添加padding或margin调整整体间距 */
  580. padding: 5px 0;
  581. }
  582. .winner-modal-close {
  583. position: absolute;
  584. right: 18px;
  585. top: 12px;
  586. font-size: 26px;
  587. color: #888;
  588. cursor: pointer;
  589. }
  590. .winner-list {
  591. max-height: 260px;
  592. /* background: rgba(255, 210, 131, 0.8);/ */
  593. overflow-y: auto;
  594. padding: 0;
  595. margin: 0;
  596. list-style: none;
  597. /* 隐藏滚动条但保留滚动功能 */
  598. scrollbar-width: none; /* Firefox */
  599. -ms-overflow-style: none; /* IE and Edge */
  600. }
  601. .winner-list::-webkit-scrollbar {
  602. display: none; /* Chrome, Safari and Opera */
  603. }
  604. .winner-list li {
  605. padding: 8px 0;
  606. /* border-bottom: 1px solid #f2f2f2; */
  607. font-size: 17px;
  608. color: #d84315;
  609. display: flex;
  610. gap: 12px;
  611. align-items: center;
  612. justify-content: center;
  613. text-align: center;
  614. }
  615. .progress-bar-bg {
  616. position: relative;
  617. width: 228px;
  618. height: 30px;
  619. background: #e9620e;
  620. border-radius: 16px;
  621. overflow: hidden;
  622. display: flex;
  623. align-items: center;
  624. margin: 0 auto;
  625. border: #e13726;
  626. }
  627. .progress-bar-fill {
  628. position: absolute;
  629. left: 0;
  630. top: 0;
  631. /* width: 100%; */
  632. height: 100%;
  633. /* background: linear-gradient(90deg, #ff9800 0%, #8a3500 100%); */
  634. background: #8a3500;
  635. border-radius: 16px;
  636. transition: width 0.4s;
  637. z-index: 1;
  638. }
  639. .progress-bar-text {
  640. position: relative;
  641. width: 100%;
  642. text-align: center;
  643. color: #ffffff;
  644. font-size: 18px;
  645. font-weight: bold;
  646. z-index: 2;
  647. letter-spacing: 1px;
  648. }
  649. .prize-card-mask {
  650. position: relative;
  651. width: 342px;
  652. height: 88px;
  653. display: flex;
  654. /* align-items: center;
  655. justify-content: center; */
  656. padding: 0;
  657. overflow: hidden;
  658. }
  659. .prize-mask-img {
  660. object-fit: cover;
  661. position: absolute;
  662. width: 100%;
  663. height: 98%;
  664. object-fit: cover;
  665. left: 0;
  666. top: 0;
  667. border-radius: 8px 8px 8px 8px;
  668. }
  669. .prize-panel-item.revealed-highlight {
  670. /* border: 3px solid #ff9800; */
  671. box-shadow: 0 0 16px 4px #ff9800aa;
  672. transform: scale(1.03);
  673. z-index: 2;
  674. transition: all 0.3s;
  675. /* 确保放大的元素不被遮挡 */
  676. position: relative;
  677. /* margin: 8px 0; */
  678. /* 为放大的元素留出空间 */
  679. transform-origin: center center;
  680. }
  681. .prize-panel-item.disabled {
  682. cursor: not-allowed !important;
  683. position: relative;
  684. }
  685. .prize-panel-item.disabled::after {
  686. content: "请先抽完上一个奖品";
  687. position: absolute;
  688. top: 50%;
  689. left: 50%;
  690. transform: translate(-50%, -50%);
  691. background: rgba(0, 0, 0, 0.8);
  692. color: white;
  693. padding: 8px 12px;
  694. border-radius: 6px;
  695. font-size: 14px;
  696. white-space: nowrap;
  697. z-index: 10;
  698. opacity: 0;
  699. transition: opacity 0.3s;
  700. pointer-events: none;
  701. }
  702. .prize-panel-item.disabled:hover::after {
  703. opacity: 1;
  704. }
  705. </style>