新增节日福利系统:①数据库表+模型 ②TriggerHolidayEventJob队列任务(在线用户筛选/金额分配/WebSocket广播) ③后台管理页面(列表/创建/手动触发) ④前台领取弹窗+WebSocket监听 ⑤定时调度每分钟扫描 ⑥CurrencySource补充HOLIDAY_BONUS
This commit is contained in:
@@ -135,6 +135,8 @@
|
||||
@include('chat.partials.user-actions')
|
||||
{{-- ═══════════ 婚姻系统弹窗组件 ═══════════ --}}
|
||||
@include('chat.partials.marriage-modals')
|
||||
{{-- ═══════════ 节日福利弹窗组件 ═══════════ --}}
|
||||
@include('chat.partials.holiday-modal')
|
||||
|
||||
{{-- 全屏特效系统:管理员烟花/下雨/雷电/下雪 --}}
|
||||
<script src="/js/effects/effect-sounds.js"></script>
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
{{--
|
||||
文件功能:节日福利弹窗组件
|
||||
|
||||
后台配置的节日活动触发时,通过 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;">
|
||||
<div style="color:rgba(254,243,199,.6); font-size:11px; margin-bottom:4px;">🪙 本次节日总奖池</div>
|
||||
<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(
|
||||
`🎊 【${detail.name}】节日福利开始啦!总奖池 🪙${Number(detail.total_amount).toLocaleString()} 金币,${typeLabel},${quotaText}在线用户可领取!`
|
||||
);
|
||||
}
|
||||
|
||||
// 弹出全屏领取弹窗
|
||||
const el = document.getElementById('holiday-event-modal');
|
||||
if (el) Alpine.$data(el).open(detail);
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user