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.

722 lines
18 KiB

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