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.

726 lines
19 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. import { getGetPrizeUserListApi } from "../../../api/API";
  290. const updateWinners = async () => {
  291. try {
  292. const response = await getGetPrizeUserListApi({})
  293. console.log("updatePrizeList response", response);
  294. fakeWinners.value = response.data.list;
  295. console.log("updateWinners fakeWinners", fakeWinners.value);
  296. } catch (error) {
  297. console.error("updatePrizeList error", error);
  298. }
  299. };
  300. async function openWinnerList() {
  301. // showWinnerList.value = true;
  302. if (!showWinnerList.value) {
  303. if (revealedCount.value === 0) {
  304. alert("请先揭晓奖品,并抽奖!");
  305. }
  306. // await updatePrizeList();
  307. }
  308. if (revealedCount.value > 0) {
  309. showWinnerList.value = true;
  310. }
  311. // 当存在最新揭秘的奖品时,点击获奖名单将showOne设置为false
  312. if (lastRevealedIdx.value >= 0) {
  313. showOne.value = false;
  314. }
  315. // 设置弹窗位置
  316. // nextTick(() => {
  317. // const btn = winnerBtnRef.value;
  318. // if (btn) {
  319. // const rect = btn.getBoundingClientRect();
  320. // modalLeft.value = rect.left - 23;
  321. // modalTop.value = rect.bottom + 18; // 4px间距
  322. // }
  323. // });
  324. }
  325. function closeWinnerList() {
  326. showWinnerList.value = false;
  327. // 当关闭获奖名单且showOne为false时,将其切换回true
  328. if (!showOne.value) {
  329. showOne.value = true;
  330. }
  331. }
  332. const winnerBtnRef = ref(null);
  333. const modalLeft = ref(0);
  334. const modalTop = ref(0);
  335. async function toggleWinnerList() {
  336. await updateWinners();
  337. showWinnerList.value = !showWinnerList.value;
  338. console.log(
  339. "toggleWinnerList - showWinnerList:",
  340. showWinnerList.value,
  341. "showOne:",
  342. showOne.value,
  343. "lastRevealedIdx:",
  344. lastRevealedIdx.value
  345. );
  346. if (!showWinnerList.value) {
  347. if (revealedCount.value === 0) {
  348. alert("请先揭晓奖品,并抽奖!");
  349. }
  350. }
  351. if (showWinnerList.value) {
  352. // 当存在最新揭秘的奖品时,点击获奖名单将showOne设置为false
  353. if (lastRevealedIdx.value > 0) {
  354. showOne.value = false;
  355. console.log("设置 showOne 为 false");
  356. }
  357. // 设置弹窗位置
  358. // nextTick(() => {
  359. // const btn = winnerBtnRef.value;
  360. // if (btn) {
  361. // const rect = btn.getBoundingClientRect();
  362. // modalLeft.value = rect.left - 23;
  363. // modalTop.value = rect.bottom + 18; // 4px间距
  364. // }
  365. // });
  366. } else {
  367. // 当关闭获奖名单且showOne为false时,将其切换回true
  368. if (!showOne.value) {
  369. showOne.value = true;
  370. console.log("设置 showOne 为 true");
  371. }
  372. }
  373. }
  374. function getProgressPercent(prize) {
  375. const total = prize.count || 1;
  376. const left = getLeftCount(prize);
  377. // 返回剩余数量的百分比,这样开始时是满的,抽完后是空的
  378. return Math.round((left / total) * 100);
  379. }
  380. // onMounted(() => {
  381. // updateWinners();
  382. // });
  383. </script>
  384. <style scoped>
  385. .prize-panel-list {
  386. position: absolute;
  387. top: 20px;
  388. left: 20px;
  389. background: none;
  390. z-index: 10;
  391. min-width: 320px;
  392. max-width: 342px;
  393. text-align: left;
  394. display: flex;
  395. flex-direction: column;
  396. gap: 18px;
  397. overflow-y: auto;
  398. max-height: 700px;
  399. }
  400. .prize-panel-item {
  401. background: #ffd283;
  402. border-radius: 6px 6px 6px 6px;
  403. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  404. display: flex;
  405. align-items: center;
  406. min-width: 320px;
  407. }
  408. .prize-card {
  409. display: flex;
  410. align-items: center;
  411. width: 100%;
  412. padding: 10px 18px;
  413. }
  414. .prize-img-wrap {
  415. width: 64px;
  416. height: 64px;
  417. border-radius: 50%;
  418. background: #fff;
  419. display: flex;
  420. align-items: center;
  421. justify-content: center;
  422. overflow: hidden;
  423. margin-right: 18px;
  424. border: 2px solid #fff3e0;
  425. }
  426. .prize-img {
  427. width: 60px;
  428. height: 60px;
  429. object-fit: contain;
  430. }
  431. .prize-info {
  432. flex: 1;
  433. display: flex;
  434. flex-direction: column;
  435. justify-content: center;
  436. }
  437. .prize-row {
  438. display: flex;
  439. align-items: center;
  440. background: #ffffff;
  441. border-radius: 93px 93px 93px 93px;
  442. }
  443. .prize-row-top {
  444. margin-bottom: 8px;
  445. border: 1px solid #ea2b0a;
  446. }
  447. .prize-level {
  448. background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%);
  449. color: #fff;
  450. border-radius: 15.71px 15.71px 15.71px 15.71px;
  451. padding: 2px 18px;
  452. font-size: 18px;
  453. font-weight: bold;
  454. margin-right: 12px;
  455. }
  456. .prize-name {
  457. font-size: 18px;
  458. color: #d84315;
  459. font-weight: 500;
  460. }
  461. /* .prize-row-bottom {
  462. background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%);
  463. background: #8a3500;
  464. border-radius: 16px;
  465. color: #fff;
  466. font-size: 20px;
  467. font-weight: bold;
  468. padding: 2px 0 2px 0;
  469. justify-content: center;
  470. min-width: 80px;
  471. } */
  472. .custom-arrow-icon {
  473. font-size: 24px; /* 图标大小 */
  474. color: #d84315; /* 图标颜色,使用项目主题橙色 */
  475. margin: 5px; /* 外边距 */
  476. cursor: pointer; /* 鼠标悬停样式 */
  477. transition: transform 0.3s ease; /* 过渡动画 */
  478. }
  479. .custom-arrow-icon:hover {
  480. transform: scale(1.1); /* 悬停放大效果 */
  481. }
  482. .prize-count {
  483. font-size: 20px;
  484. font-weight: bold;
  485. }
  486. .prize-divider {
  487. margin: 0 4px;
  488. font-size: 20px;
  489. }
  490. .prize-total {
  491. font-size: 20px;
  492. font-weight: bold;
  493. }
  494. .prize-panel-footer {
  495. position: absolute;
  496. left: 0;
  497. bottom: -26px;
  498. width: 100%;
  499. display: flex;
  500. flex-direction: column;
  501. align-items: center;
  502. z-index: 20;
  503. /* 移除 move-up 相关 */
  504. }
  505. .arrow-up {
  506. position: relative;
  507. width: 36px;
  508. height: 24px;
  509. margin-bottom: 4px;
  510. cursor: pointer;
  511. background-image: url("../../../assets/展开.png");
  512. background-size: cover;
  513. background-position: center;
  514. }
  515. .arrow-down {
  516. position: fixed;
  517. top: 120px;
  518. left: 165px;
  519. width: 36px;
  520. height: 38px;
  521. margin-bottom: 4px;
  522. cursor: pointer;
  523. background-image: url("../../../assets/展开.png");
  524. background-size: cover;
  525. background-position: center;
  526. transform: rotate(180deg);
  527. }
  528. .winner-btn {
  529. background: rgba(255, 210, 131, 0.8);
  530. color: #d5291f;
  531. border: #fff;
  532. border-radius: 8px;
  533. padding: 15px 79px;
  534. font-size: 20px;
  535. font-weight: bold;
  536. cursor: pointer;
  537. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  538. }
  539. .winner-modal-mask {
  540. position: fixed;
  541. top: 155px;
  542. left: 10px;
  543. width: 100vw;
  544. height: 100vh;
  545. background: rgba(0, 0, 0, 0.01);
  546. z-index: 1000;
  547. display: flex;
  548. align-items: flex-start;
  549. justify-content: center;
  550. }
  551. .winner-modal {
  552. background: rgba(255, 210, 131, 0.8);
  553. border-radius: 12px;
  554. padding-top: 12px;
  555. padding-left: 25px;
  556. padding-right: 25px;
  557. padding-bottom: 10px;
  558. min-width: 280px;
  559. max-width: 90vw;
  560. box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
  561. position: relative;
  562. margin-left: 10px;
  563. }
  564. .winner-modal-title {
  565. font-size: 22px;
  566. font-weight: bold;
  567. color: #e64f39;
  568. margin-bottom: 5px;
  569. text-align: center;
  570. display: flex;
  571. justify-content: space-between; /* 左右对齐 */
  572. align-items: center; /* 垂直居中对齐 */
  573. /* 可添加padding或margin调整整体间距 */
  574. padding: 5px 0;
  575. }
  576. .winner-modal-close {
  577. position: absolute;
  578. right: 18px;
  579. top: 12px;
  580. font-size: 26px;
  581. color: #888;
  582. cursor: pointer;
  583. }
  584. .winner-list {
  585. max-height: 260px;
  586. /* background: rgba(255, 210, 131, 0.8);/ */
  587. overflow-y: auto;
  588. padding: 0;
  589. margin: 0;
  590. list-style: none;
  591. /* 隐藏滚动条但保留滚动功能 */
  592. scrollbar-width: none; /* Firefox */
  593. -ms-overflow-style: none; /* IE and Edge */
  594. }
  595. .winner-list::-webkit-scrollbar {
  596. display: none; /* Chrome, Safari and Opera */
  597. }
  598. .winner-list li {
  599. padding: 8px 0;
  600. /* border-bottom: 1px solid #f2f2f2; */
  601. font-size: 17px;
  602. color: #d84315;
  603. display: flex;
  604. gap: 12px;
  605. align-items: center;
  606. justify-content: center;
  607. text-align: center;
  608. }
  609. .progress-bar-bg {
  610. position: relative;
  611. width: 220px;
  612. height: 28px;
  613. background: #e9620e;
  614. border-radius: 16px;
  615. overflow: hidden;
  616. display: flex;
  617. align-items: center;
  618. margin: 0 auto;
  619. border: #e13726;
  620. }
  621. .progress-bar-fill {
  622. position: absolute;
  623. left: 0;
  624. top: 0;
  625. height: 100%;
  626. /* background: linear-gradient(90deg, #ff9800 0%, #8a3500 100%); */
  627. background: #8a3500;
  628. border-radius: 16px;
  629. transition: width 0.4s;
  630. z-index: 1;
  631. }
  632. .progress-bar-text {
  633. position: relative;
  634. width: 100%;
  635. text-align: center;
  636. color: #ffffff;
  637. font-size: 18px;
  638. font-weight: bold;
  639. z-index: 2;
  640. letter-spacing: 1px;
  641. }
  642. .prize-card-mask {
  643. position: relative;
  644. width: 342px;
  645. height: 88px;
  646. display: flex;
  647. /* align-items: center;
  648. justify-content: center; */
  649. padding: 0;
  650. overflow: hidden;
  651. }
  652. .prize-mask-img {
  653. object-fit: cover;
  654. position: absolute;
  655. width: 100%;
  656. height: 98%;
  657. object-fit: cover;
  658. left: 0;
  659. top: 0;
  660. border-radius: 8px 8px 8px 8px;
  661. }
  662. .prize-panel-item.revealed-highlight {
  663. /* border: 3px solid #ff9800; */
  664. box-shadow: 0 0 16px 4px #ff9800aa;
  665. transform: scale(1.05);
  666. z-index: 2;
  667. transition: all 0.3s;
  668. }
  669. .prize-panel-item.disabled {
  670. cursor: not-allowed !important;
  671. position: relative;
  672. }
  673. .prize-panel-item.disabled::after {
  674. content: "请先抽完上一个奖品";
  675. position: absolute;
  676. top: 50%;
  677. left: 50%;
  678. transform: translate(-50%, -50%);
  679. background: rgba(0, 0, 0, 0.8);
  680. color: white;
  681. padding: 8px 12px;
  682. border-radius: 6px;
  683. font-size: 14px;
  684. white-space: nowrap;
  685. z-index: 10;
  686. opacity: 0;
  687. transition: opacity 0.3s;
  688. pointer-events: none;
  689. }
  690. .prize-panel-item.disabled:hover::after {
  691. opacity: 1;
  692. }
  693. </style>