2026-03-01 20:06:53 +08:00
|
|
|
|
{{--
|
|
|
|
|
|
文件功能:节日福利弹窗组件
|
|
|
|
|
|
|
|
|
|
|
|
后台配置的节日活动触发时,通过 WebSocket 广播到达前端,
|
|
|
|
|
|
弹出全屏福利领取弹窗,用户点击领取后金币自动入账。
|
|
|
|
|
|
|
|
|
|
|
|
WebSocket 监听:chat:holiday.started
|
|
|
|
|
|
领取接口:POST /holiday/{event}/claim
|
|
|
|
|
|
--}}
|
|
|
|
|
|
|
|
|
|
|
|
{{-- ─── 节日福利领取弹窗 ─── --}}
|
|
|
|
|
|
<div id="holiday-event-modal" x-data="holidayEventModal()" x-show="show" x-cloak>
|
|
|
|
|
|
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-95"
|
|
|
|
|
|
x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-200"
|
|
|
|
|
|
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
|
|
|
|
|
style="position:fixed; inset:0; background:rgba(0,0,0,.65); z-index:9950; display:flex; align-items:center; justify-content:center;">
|
|
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
style="width:400px; max-width:95vw; border-radius:24px; overflow:hidden; text-align:center;
|
|
|
|
|
|
box-shadow:0 24px 80px rgba(245,158,11,.4);">
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 顶部渐变区域 --}}
|
|
|
|
|
|
<div style="background:linear-gradient(145deg,#92400e,#b45309,#d97706); padding:28px 24px 20px;">
|
|
|
|
|
|
{{-- 主图标动效 --}}
|
|
|
|
|
|
<div style="font-size:56px; margin-bottom:10px; animation:holiday-bounce 1.2s infinite;">🎊</div>
|
|
|
|
|
|
<div style="color:#fef3c7; font-weight:bold; font-size:20px; margin-bottom:4px;" x-text="eventName">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="color:rgba(254,243,199,.8); font-size:13px;" x-text="eventDesc"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 主体内容 --}}
|
|
|
|
|
|
<div style="background:linear-gradient(160deg,#7c2d12,#9a3412); padding:20px 24px;">
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 奖池信息 --}}
|
|
|
|
|
|
<div style="background:rgba(0,0,0,.25); border-radius:14px; padding:12px 16px; margin-bottom:16px;">
|
2026-03-04 15:00:02 +08:00
|
|
|
|
<div style="color:rgba(254,243,199,.6); font-size:11px; margin-bottom:4px;">💰 本次节日总奖池</div>
|
2026-03-01 20:06:53 +08:00
|
|
|
|
<div style="color:#fcd34d; font-size:24px; font-weight:bold;"
|
|
|
|
|
|
x-text="totalAmount.toLocaleString() + ' 金币'"></div>
|
|
|
|
|
|
<div style="color:rgba(254,243,199,.5); font-size:11px; margin-top:4px;">
|
|
|
|
|
|
<span x-show="maxClaimants > 0" x-text="'前 ' + maxClaimants + ' 名在线用户可领取'"></span>
|
|
|
|
|
|
<span x-show="maxClaimants === 0">全体在线用户均可领取</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 有效期 --}}
|
|
|
|
|
|
<div style="color:rgba(254,243,199,.55); font-size:11px; margin-bottom:14px;">
|
|
|
|
|
|
领取有效期 <strong style="color:#fcd34d;" x-text="expiresIn"></strong>,过期作废
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 领取按钮 --}}
|
|
|
|
|
|
<div x-show="!claimed">
|
|
|
|
|
|
<div style="background:rgba(0,0,0,.3); border-radius:16px; padding:5px;">
|
|
|
|
|
|
<button x-on:click="doClaim()" :disabled="claiming"
|
|
|
|
|
|
style="display:block; width:100%; padding:14px 0; border:none; border-radius:12px;
|
|
|
|
|
|
font-size:16px; font-weight:bold; cursor:pointer; transition:all .15s;
|
|
|
|
|
|
letter-spacing:1px; color:#fff;"
|
|
|
|
|
|
:style="claiming
|
|
|
|
|
|
?
|
|
|
|
|
|
'background:#b45309; opacity:.65; cursor:not-allowed;' :
|
|
|
|
|
|
'background:#d97706; box-shadow:0 2px 12px rgba(0,0,0,.4);'"
|
|
|
|
|
|
onmouseover="if(!this.disabled) this.style.filter='brightness(1.12)'"
|
|
|
|
|
|
onmouseout="this.style.filter=''">
|
|
|
|
|
|
<span x-text="claiming ? '领取中…' : '🎁 立即领取福利'"></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 已领取 --}}
|
|
|
|
|
|
<div x-show="claimed" style="display:none;">
|
|
|
|
|
|
<div style="font-size:36px; margin-bottom:6px; color:#fcd34d; font-weight:bold;"
|
|
|
|
|
|
x-text="'+' + claimedAmount.toLocaleString() + ' 金币'"></div>
|
|
|
|
|
|
<div style="color:#fef3c7; font-size:13px;">🎉 恭喜!节日福利已入账!</div>
|
|
|
|
|
|
<div style="color:rgba(254,243,199,.6); font-size:11px; margin-top:4px;">金币已自动到账,新年快乐 🥳</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 关闭按钮 --}}
|
|
|
|
|
|
<div style="background:rgba(120,40,10,.95); padding:14px 20px;">
|
|
|
|
|
|
<button x-on:click="close()"
|
|
|
|
|
|
style="padding:9px 32px; background:rgba(0,0,0,.35); border:none; border-radius:30px;
|
|
|
|
|
|
font-size:12px; color:rgba(254,243,199,.7); cursor:pointer; transition:all .2s;"
|
|
|
|
|
|
onmouseover="this.style.background='rgba(0,0,0,.5)'"
|
|
|
|
|
|
onmouseout="this.style.background='rgba(0,0,0,.35)'">
|
|
|
|
|
|
<span x-text="claimed ? '收好了 ✨' : '关闭'"></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
@keyframes holiday-bounce {
|
|
|
|
|
|
|
|
|
|
|
|
0%,
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: scale(1) rotate(0deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
25% {
|
|
|
|
|
|
transform: scale(1.1) rotate(-5deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
75% {
|
|
|
|
|
|
transform: scale(1.1) rotate(5deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 节日福利弹窗组件
|
|
|
|
|
|
* 监听 WebSocket 事件 holiday.started,弹出领取弹窗
|
|
|
|
|
|
*/
|
|
|
|
|
|
function holidayEventModal() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
show: false,
|
|
|
|
|
|
claiming: false,
|
|
|
|
|
|
claimed: false,
|
|
|
|
|
|
|
|
|
|
|
|
// 活动数据
|
|
|
|
|
|
eventId: null,
|
|
|
|
|
|
eventName: '',
|
|
|
|
|
|
eventDesc: '',
|
|
|
|
|
|
totalAmount: 0,
|
|
|
|
|
|
maxClaimants: 0,
|
|
|
|
|
|
distributeType: 'random',
|
|
|
|
|
|
fixedAmount: null,
|
|
|
|
|
|
expiresAt: null,
|
|
|
|
|
|
expiresIn: '',
|
|
|
|
|
|
claimedAmount: 0,
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 打开弹窗并填充活动数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
open(detail) {
|
|
|
|
|
|
this.eventId = detail.event_id;
|
|
|
|
|
|
this.eventName = detail.name ?? '节日福利';
|
|
|
|
|
|
this.eventDesc = detail.description ?? '';
|
|
|
|
|
|
this.totalAmount = detail.total_amount ?? 0;
|
|
|
|
|
|
this.maxClaimants = detail.max_claimants ?? 0;
|
|
|
|
|
|
this.distributeType = detail.distribute_type ?? 'random';
|
|
|
|
|
|
this.fixedAmount = detail.fixed_amount;
|
|
|
|
|
|
this.expiresAt = detail.expires_at ? new Date(detail.expires_at) : null;
|
|
|
|
|
|
this.claimed = false;
|
|
|
|
|
|
this.claiming = false;
|
|
|
|
|
|
this.claimedAmount = 0;
|
|
|
|
|
|
this.updateExpiresIn();
|
|
|
|
|
|
this.show = true;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 关闭弹窗
|
|
|
|
|
|
*/
|
|
|
|
|
|
close() {
|
|
|
|
|
|
this.show = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新有效期显示文字
|
|
|
|
|
|
*/
|
|
|
|
|
|
updateExpiresIn() {
|
|
|
|
|
|
if (!this.expiresAt) {
|
|
|
|
|
|
this.expiresIn = '30 分钟';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const diff = Math.max(0, Math.round((this.expiresAt - Date.now()) / 60000));
|
|
|
|
|
|
this.expiresIn = diff > 0 ? diff + ' 分钟' : '即将过期';
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发起领取请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
async doClaim() {
|
|
|
|
|
|
if (this.claiming || this.claimed || !this.eventId) return;
|
|
|
|
|
|
this.claiming = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/holiday/${this.eventId}/claim`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.ok) {
|
|
|
|
|
|
this.claimed = true;
|
|
|
|
|
|
this.claimedAmount = data.amount || 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.chatDialog?.alert(data.message || '领取失败,请稍后重试。', '提示', '#f59e0b');
|
|
|
|
|
|
if (data.message?.includes('已结束') || data.message?.includes('过期')) {
|
|
|
|
|
|
this.close();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
window.chatDialog?.alert('网络异常,请稍后重试。', '错误', '#cc4444');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.claiming = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── WebSocket 事件监听:节日福利开始 ─────────────────────────
|
|
|
|
|
|
window.addEventListener('chat:holiday.started', (e) => {
|
|
|
|
|
|
const detail = e.detail;
|
|
|
|
|
|
|
|
|
|
|
|
// 公屏追加系统消息
|
|
|
|
|
|
if (typeof appendSystemMessage === 'function') {
|
|
|
|
|
|
const typeLabel = detail.distribute_type === 'fixed' ?
|
|
|
|
|
|
`每人固定 ${Number(detail.fixed_amount).toLocaleString()} 金币` :
|
|
|
|
|
|
'随机金额';
|
|
|
|
|
|
const quotaText = detail.max_claimants > 0 ? `前 ${detail.max_claimants} 名` : '全体';
|
|
|
|
|
|
appendSystemMessage(
|
2026-03-04 15:00:02 +08:00
|
|
|
|
`🎊 【${detail.name}】节日福利开始啦!总奖池 💰${Number(detail.total_amount).toLocaleString()} 金币,${typeLabel},${quotaText}在线用户可领取!`
|
2026-03-01 20:06:53 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 弹出全屏领取弹窗
|
|
|
|
|
|
const el = document.getElementById('holiday-event-modal');
|
|
|
|
|
|
if (el) Alpine.$data(el).open(detail);
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|