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.
 
 
 

872 lines
22 KiB

<template>
<div v-if="showOne">
<div class="prize-panel-root">
<div v-if="showNotification" class="auto-hide-notification">
{{ notificationMessage }}
</div>
<div class="prize-panel-list" v-if="prizes && prizes.length">
<div
class="prize-panel-item"
v-for="(prize, idx) in prizes"
:key="prize.type || idx"
:class="{
'revealed-highlight': idx === lastRevealedIdx,
disabled: idx === nextRevealIdx && !canRevealPrize(idx),
}"
@click="handleReveal(idx)"
:style="{
cursor:
idx === nextRevealIdx && !canRevealPrize(idx)
? 'not-allowed'
: 'pointer',
}"
>
<div v-if="isRevealed(idx)" class="prize-card">
<div class="prize-img-wrap">
<img class="prize-img" :src="prize.img" :alt="prize.title" />
</div>
<div class="prize-info">
<div class="prize-row prize-row-top">
<span class="prize-level">{{ prize.title }}</span>
<span class="prize-name">{{ prize.text }}</span>
</div>
<div class="prize-row prize-row-bottom">
<div class="progress-bar-bg">
<div
class="progress-bar-fill"
:style="{ width: getProgressPercent(prize) + '%' }"
></div>
<span class="progress-bar-text">
{{ getLeftCount(prize) }}/{{ prize.count }}
</span>
</div>
</div>
</div>
</div>
<div v-else class="prize-card prize-card-mask">
<img
src="../../../assets/daijiemi.png"
alt="待揭秘"
class="prize-mask-img"
/>
</div>
</div>
</div>
<div class="prize-panel-footer">
<div class="arrow-up" @click="openWinnerList"></div>
<button ref="winnerBtnRef" class="winner-btn" @click="openWinnerList">
获奖名单
</button>
</div>
</div>
</div>
<div v-else>
<div class="prize-panel-root">
<div
class="prize-panel-list1"
v-if="prizes && prizes.length && lastRevealedIdx >= 0"
>
<div
class="prize-panel-item"
:key="prizes[lastRevealedIdx].type || lastRevealedIdx"
:class="{ 'revealed-highlight': true }"
style="cursor: pointer"
>
<div class="prize-card">
<div class="prize-img-wrap">
<img
class="prize-img"
:src="prizes[lastRevealedIdx].img"
:alt="prizes[lastRevealedIdx].title"
/>
</div>
<div class="prize-info">
<div class="prize-row prize-row-top">
<span class="prize-level">{{
prizes[lastRevealedIdx].title
}}</span>
<span class="prize-name">{{
prizes[lastRevealedIdx].text
}}</span>
</div>
<div class="prize-row prize-row-bottom">
<div class="progress-bar-bg">
<div
class="progress-bar-fill"
:style="{
width: getProgressPercent(prizes[lastRevealedIdx]) + '%',
}"
></div>
<span class="progress-bar-text">
{{ getLeftCount(prizes[lastRevealedIdx]) }}/{{
prizes[lastRevealedIdx].count
}}
</span>
</div>
<div class="prize-panel-footer">
<div class="arrow-down" @click="toggleWinnerList"></div>
<div
v-if="showWinnerList"
class="winner-modal-mask"
@click="closeWinnerList"
>
<div
class="winner-modal"
:style="{
position: 'absolute',
left: modalLeft + 'px',
top: modalTop + 'px',
}"
@click.stop
>
<div class="winner-modal-title">
<span>Homily ID</span><span>奖项</span>
</div>
<div
style="
background: linear-gradient(
to left,
rgb(232 76 10),
rgb(195 6 6),
rgb(240 90 9)
);
height: 1px;
"
></div>
<ul class="winner-list">
<li
v-for="(user, idx) in paginatedWinners"
:key="idx"
style="
display: flex;
justify-content: space-between;
align-items: center;
"
>
<!-- <span>{{ user.id }}</span> - <span>{{ user.name }}</span> - -->
<span>{{ user.jwcode }}</span>
<span>{{ user.prizeName }}</span>
</li>
</ul>
<!-- 分页控件 -->
<div class="pagination-controls">
<button
class="pagination-btn prev-btn"
@click="prevPage"
:disabled="currentPage === 1"
>
&lt;
</button>
<span class="page-info">{{ currentPage }} / {{ totalPages }}</span>
<button
class="pagination-btn next-btn"
@click="nextPage"
:disabled="currentPage === totalPages"
>
&gt;
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, nextTick, watch, onMounted } from "vue";
import { useLotteryStore } from "../../../store/lottery";
import { useDataManager } from "../lottery/dataManager";
const showNotification = ref(false);
const notificationMessage = ref('');
// 显示提示并自动消失
const showAutoHideNotification = (message, duration = 1500) => {
notificationMessage.value = message;
showNotification.value = true;
setTimeout(() => {
showNotification.value = false;
}, duration);
};
const props = defineProps({
prizes: Array,
});
// 新增:控制已揭秘奖品数量
const revealedCount = ref(0);
// 新增:记录最新揭秘的奖品索引
const lotteryStore = useLotteryStore();
const lastRevealed = computed({
get: () => lotteryStore.lastRevealedIdx,
set: (val) => lotteryStore.setLastRevealedIdx(val),
});
const waitingForNextReveal = computed({
get: () => lotteryStore.waitingForNextReveal,
set: (val) => lotteryStore.setWaitingForNextReveal(val),
});
const winners = computed({
get: () => lotteryStore.winners,
set: (val) => lotteryStore.setWinners(val),
});
// 添加开奖状态的计算属性
const lotteryState = computed({
get: () => lotteryStore.lotteryState,
set: (val) => lotteryStore.setLotteryState(val),
});
// 用watch监听winners的变化
// watch(winners, (newVal) => {
// console.log("中奖人数列表winners", newVal);
// fakeWinners.value = newVal;
// });
// 监听开奖状态变化,当开奖完成后更新获奖名单
watch(lotteryState, (newState, oldState) => {
if (oldState === 'rotating' && newState === 'result') {
// 开奖完成,更新获奖名单数据
console.log("开奖完成,开始更新获奖名单数据");
updateWinners();
}
});
const lastRevealedIdx = ref(-1);
lastRevealedIdx.value = lastRevealed.value;
const showOne = ref(true);
// 计算哪些奖品已揭秘
const isRevealed = (idx) =>
idx >= (props.prizes?.length || 0) - revealedCount.value;
// 允许点击的卡片index
const nextRevealIdx = computed(
() => (props.prizes?.length || 0) - revealedCount.value - 1
);
// 检查指定奖品是否可以揭秘
function canRevealPrize(idx) {
// 如果这是第一个要揭秘的奖品,直接允许
if (lastRevealedIdx.value === -1) {
return true;
}
// 检查上一个揭秘的奖品是否已经抽完
const lastPrize = props.prizes[lastRevealedIdx.value];
if (lastPrize) {
const leftCount = getLeftCount(lastPrize);
// 如果上一个奖品还有剩余,则不能揭秘下一个
if (leftCount > 0) {
waitingForNextReveal.value = false;
return false;
}
waitingForNextReveal.value = true;
}
return true;
}
// 卡片点击事件
function handleReveal(idx) {
// 检查是否可以揭秘这个奖品
if (idx !== nextRevealIdx.value && !isRevealed(idx)) {
// alert("请按顺序揭秘奖品!");
showAutoHideNotification("请按顺序揭秘奖品!");
return;
}
if (idx === nextRevealIdx.value && canRevealPrize(idx)) {
revealedCount.value++;
lastRevealedIdx.value = idx; // 记录最新揭秘的索引
if (idx === 0) {
waitingForNextReveal.value = false;
}
console.log("lastRevealedIdx.value", lastRevealedIdx.value);
lastRevealed.value = idx;
console.log("lastRevealed.value", lastRevealed.value);
} else if (idx === nextRevealIdx.value && !canRevealPrize(idx)) {
// 可以在这里添加提示信息,告知用户上一个奖品还未抽完
console.log("上一个奖品还未抽完,不能揭秘下一个奖品");
// 可以添加一个提示弹窗或toast
// alert("上一个奖品还未抽完,请等待抽奖完成后再揭秘下一个奖品");
}
}
// 计算未抽取数量
function getLeftCount(prize) {
// 这里假设奖品有 type 字段,且 dataManager.state.basicData.luckyUsers 可用
// 由于本组件无 luckyUsers 数据,建议父组件传入或全局可访问
// 这里用 window.dataManager 兼容演示
let luckyUsers =
(window.dataManager && window.dataManager.state.basicData.luckyUsers) || {};
let got = luckyUsers[prize.type]?.length || 0;
return prize.remainNum;
}
// 新增部分
const showWinnerList = ref(false);
const fakeWinners = ref([]);
// fakeWinners.value = winners.value;
console.log("fakeWinners", fakeWinners.value);
// 分页相关状态
const currentPage = ref(1);
const pageSize = ref(16); // 每页显示20条记录,更好地利用列表空间
const totalPages = ref(1);
// 计算当前页的数据
const paginatedWinners = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return fakeWinners.value.slice(startIndex, endIndex);
});
// 计算总页数
const calculateTotalPages = () => {
totalPages.value = Math.ceil(fakeWinners.value.length / pageSize.value);
if (totalPages.value === 0) totalPages.value = 1;
};
// 上一页
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
// 下一页
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
import { getGetPrizeUserListApi } from "../../../api/API";
const updateWinners = async () => {
try {
const response = await getGetPrizeUserListApi({
pageSize: 1000000,
pageNum: 1,
});
console.log("updatePrizeList response", response);
// 按时间排序,后中奖的排序在前(假设有createTime字段,如果没有则按id倒序)
const sortedWinners = response.data.list.sort((a, b) => {
if (a.createTime && b.createTime) {
return new Date(b.createTime) - new Date(a.createTime);
}
// 如果没有时间字段,按id倒序排列
return (b.id || 0) - (a.id || 0);
});
fakeWinners.value = sortedWinners;
console.log("updateWinners fakeWinners", fakeWinners.value);
// 重置分页到第一页
currentPage.value = 1;
calculateTotalPages();
} catch (error) {
console.error("updatePrizeList error", error);
}
};
async function openWinnerList() {
// showWinnerList.value = true;
if (!showWinnerList.value) {
if (revealedCount.value === 0) {
// alert("请先揭晓奖品,并抽奖!");
showAutoHideNotification("请先揭晓奖品,并抽奖!");
}
// await updatePrizeList();
}
if (revealedCount.value > 0) {
showWinnerList.value = true;
}
// 当存在最新揭秘的奖品时,点击获奖名单将showOne设置为false
if (lastRevealedIdx.value >= 0) {
showOne.value = false;
}
}
function closeWinnerList() {
showWinnerList.value = false;
// 当关闭获奖名单且showOne为false时,将其切换回true
if (!showOne.value) {
showOne.value = true;
}
}
const winnerBtnRef = ref(null);
const modalLeft = ref(0);
const modalTop = ref(0);
async function toggleWinnerList() {
await updateWinners();
showWinnerList.value = !showWinnerList.value;
console.log(
"toggleWinnerList - showWinnerList:",
showWinnerList.value,
"showOne:",
showOne.value,
"lastRevealedIdx:",
lastRevealedIdx.value
);
if (!showWinnerList.value) {
if (revealedCount.value === 0) {
// alert("请先揭晓奖品,并抽奖!");
showAutoHideNotification("请先揭晓奖品,并抽奖!");
}
}
if (showWinnerList.value) {
// 当存在最新揭秘的奖品时,点击获奖名单将showOne设置为false
if (lastRevealedIdx.value > 0) {
showOne.value = false;
console.log("设置 showOne 为 false");
}
} else {
// 当关闭获奖名单且showOne为false时,将其切换回true
if (!showOne.value) {
showOne.value = true;
console.log("设置 showOne 为 true");
}
}
}
function getProgressPercent(prize) {
const total = prize.count || 1;
const left = getLeftCount(prize);
// 返回剩余数量的百分比,这样开始时是满的,抽完后是空的
return Math.round((left / total) * 100);
}
onMounted(() => {
updateWinners();
calculateTotalPages();
});
</script>
<style scoped>
.auto-hide-notification {
position: fixed;
top: 300px;
left: 50%;
transform: translateX(-50%);
color: #e61414;
padding: 8px 16px;
border-radius: 4px;
font-size: 50px;
z-index: 1000;}
.prize-panel-list {
position: relative;
background: none;
z-index: 10;
min-width: 300px;
max-width: 362px;
text-align: left;
display: flex;
flex-direction: column;
gap: 18px;
max-height: 700px;
overflow-x: hidden !important;
/* overflow-y: auto; */
padding-right: 10px;
padding-left: 10px;
scrollbar-width: thin;
scrollbar-color: #ffd283 rgba(255, 210, 131, 0.3); /* Firefox */
}
.prize-panel-list1 {
position: relative;
background: none;
z-index: 10;
min-width: 320px;
max-width: 342px;
padding-right: 10px;
padding-left: 10px;
text-align: left;
display: flex;
}
.prize-panel-list::-webkit-scrollbar {
width: 6px;
}
.prize-panel-list::-webkit-scrollbar-track {
background: rgba(255, 210, 131, 0.3);
border-radius: 3px;
}
.prize-panel-list::-webkit-scrollbar-thumb {
background-color: #ffd283;
border-radius: 3px;
}
.prize-panel-item {
background: #ffd283;
border-radius: 6px 6px 6px 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
display: flex;
align-items: center;
min-width: 320px;
}
.prize-card {
display: flex;
align-items: center;
width: 100%;
padding: 10px 18px;
}
.prize-img-wrap {
width: 64px;
height: 64px;
border-radius: 50%;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
margin-right: 18px;
border: 2px solid #fff3e0;
}
.prize-img {
width: 60px;
height: 60px;
object-fit: contain;
}
.prize-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.prize-row {
display: flex;
align-items: center;
background: #ffffff;
border-radius: 93px 93px 93px 93px;
}
.prize-row-top {
margin-bottom: 8px;
border: 1px solid #ea2b0a;
}
.prize-level {
background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%);
color: #fff;
border-radius: 15.71px 15.71px 15.71px 15.71px;
padding: 2px 18px;
font-size: 18px;
font-weight: bold;
margin-right: 12px;
}
.prize-name {
font-size: 18px;
color: #d84315;
font-weight: 500;
}
.prize-row-bottom {
/* margin-bottom: 8px; */
/* border: 1px solid #ea2b0a; */
}
.custom-arrow-icon {
font-size: 24px; /* 图标大小 */
color: #d84315; /* 图标颜色,使用项目主题橙色 */
margin: 5px; /* 外边距 */
cursor: pointer; /* 鼠标悬停样式 */
transition: transform 0.3s ease; /* 过渡动画 */
}
.custom-arrow-icon:hover {
transform: scale(1.1); /* 悬停放大效果 */
}
.prize-count {
font-size: 20px;
font-weight: bold;
}
.prize-divider {
margin: 0 4px;
font-size: 20px;
}
.prize-total {
font-size: 20px;
font-weight: bold;
}
.prize-panel-root {
position: absolute;
top: 20px;
left: 20px;
background: none;
z-index: 10;
min-width: 320px;
max-width: 362px;
display: flex;
flex-direction: column;
}
.prize-panel-footer {
position: relative;
/* width: 100%; */
display: flex;
flex-direction: column;
align-items: center;
z-index: 20;
padding: 10px 0;
border-radius: 0 0 6px 6px;
margin-top: 10px;
}
.arrow-up {
position: relative;
width: 50px;
height: 30px;
margin-bottom: 4px;
cursor: pointer;
background-image: url("../../../assets/展开.png");
background-size: cover;
background-position: center;
transform: rotate(180deg);
}
.arrow-down {
position: fixed;
top: 100px;
left: 150px;
width: 47px;
height: 28px;
margin-bottom: 4px;
cursor: pointer;
background-image: url("../../../assets/展开.png");
background-size: cover;
background-position: center;
}
.winner-btn {
background: rgba(255, 210, 131, 0.8);
color: #d5291f;
border: #fff;
border-radius: 8px;
padding: 15px 79px;
font-size: 20px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
outline: none;
}
.winner-modal-mask {
position: fixed;
top: 142px;
left: -4px;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.01);
z-index: 1000;
display: flex;
align-items: flex-start;
justify-content: center;
}
.winner-modal {
height: 700px;
background: rgba(255, 210, 131, 0.8);
border-radius: 12px;
padding-top: 12px;
padding-left: 25px;
padding-right: 25px;
padding-bottom: 10px;
min-width: 280px;
max-width: 90vw;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
position: relative;
margin-left: 10px;
}
.winner-modal-title {
font-size: 22px;
font-weight: bold;
color: #e64f39;
margin-bottom: 5px;
text-align: center;
display: flex;
justify-content: space-between; /* 左右对齐 */
align-items: center; /* 垂直居中对齐 */
/* 可添加padding或margin调整整体间距 */
padding: 5px 0;
}
.winner-modal-close {
position: absolute;
right: 18px;
top: 12px;
font-size: 26px;
color: #888;
cursor: pointer;
}
.winner-list {
height: 580px;
/* background: rgba(255, 210, 131, 0.8);/ */
overflow-y: auto;
padding: 0;
margin: 0;
list-style: none;
/* 隐藏滚动条但保留滚动功能 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
.winner-list::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
.winner-list li {
padding: 7px 0;
/* border-bottom: 1px solid #f2f2f2; */
font-size: 17px;
color: #d84315;
display: flex;
gap: 12px;
align-items: center;
justify-content: center;
text-align: center;
}
.progress-bar-bg {
position: relative;
width: 228px;
height: 30px;
background: #e9620e;
border-radius: 16px;
overflow: hidden;
display: flex;
align-items: center;
margin: 0 auto;
border: #e13726;
}
.progress-bar-fill {
position: absolute;
left: 0;
top: 0;
/* width: 100%; */
height: 100%;
/* background: linear-gradient(90deg, #ff9800 0%, #8a3500 100%); */
background: #8a3500;
border-radius: 16px;
transition: width 0.4s;
z-index: 1;
}
.progress-bar-text {
position: relative;
width: 100%;
text-align: center;
color: #ffffff;
font-size: 18px;
font-weight: bold;
z-index: 2;
letter-spacing: 1px;
}
.prize-card-mask {
position: relative;
width: 342px;
height: 88px;
display: flex;
/* align-items: center;
justify-content: center; */
padding: 0;
overflow: hidden;
}
.prize-mask-img {
object-fit: cover;
position: absolute;
width: 100%;
height: 98%;
object-fit: cover;
left: 0;
top: 0;
border-radius: 8px 8px 8px 8px;
}
.prize-panel-item.revealed-highlight {
/* border: 3px solid #ff9800; */
box-shadow: 0 0 16px 4px #ff9800ff, 0 0 20px 6px #ffcc80ff;
transform: scale(1.03);
z-index: 2;
transition: all 0.3s;
/* 确保放大的元素不被遮挡 */
position: relative;
/* margin: 8px 0; */
/* 为放大的元素留出空间 */
transform-origin: center center;
}
.prize-panel-item.disabled {
cursor: not-allowed !important;
position: relative;
}
.prize-panel-item.disabled::after {
content: "请先抽完上一个奖品";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
white-space: nowrap;
z-index: 10;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.prize-panel-item.disabled:hover::after {
opacity: 1;
}
/* 分页控件样式 */
.pagination-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-top: 15px;
padding: 10px 0;
}
.pagination-btn {
background: linear-gradient(90deg, #ff9800 0%, #ff5722 100%);
color: white;
border: none;
border-radius: 50%;
width: 35px;
height: 35px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.pagination-btn:hover:not(:disabled) {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.pagination-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.page-info {
font-size: 18px;
font-weight: bold;
color: #d84315;
min-width: 60px;
text-align: center;
}
</style>