744 lines
28 KiB
PHP
744 lines
28 KiB
PHP
{{--
|
||
文件功能:礼包红包弹窗面板(HTML + CSS + 交互脚本)
|
||
|
||
包含:
|
||
1. 红包遮罩弹窗 DOM(#red-packet-modal)
|
||
2. 红包弹窗样式(CSS)
|
||
3. 红包前端交互 JS(发包、抢包、倒计时、WebSocket 监听)
|
||
|
||
全局函数:
|
||
window.sendRedPacket() → superlevel 发包(弹 chatBanner 选类型)
|
||
window.showRedPacketModal(...) → 展示红包弹窗(收到 WebSocket 事件触发)
|
||
window.closeRedPacketModal() → 关闭红包弹窗
|
||
window.claimRedPacket() → 用户抢红包
|
||
window.updateRedPacketClaimsUI() → 更新领取名单(WebSocket 广播后调用)
|
||
|
||
注:依赖 window.chatBanner(chat-banner.blade.php)、window.chatDialog、window.chatToast。
|
||
|
||
从 scripts.blade.php 提取,与其他游戏面板(baccarat-panel、slot-machine 等)保持统一结构。
|
||
|
||
@author ChatRoom Laravel
|
||
@version 1.0.0
|
||
--}}
|
||
<style>
|
||
/* 红包弹窗遮罩 */
|
||
#red-packet-modal {
|
||
display: none;
|
||
position: fixed !important;
|
||
inset: 0 !important;
|
||
z-index: 10500 !important;
|
||
background: rgba(0, 0, 0, 0.6) !important;
|
||
justify-content: center !important;
|
||
align-items: center !important;
|
||
animation: rpFadeIn 0.25s ease;
|
||
}
|
||
|
||
@keyframes rpFadeIn {
|
||
from {
|
||
opacity: 0;
|
||
}
|
||
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* 红包卡片主体 */
|
||
#red-packet-card {
|
||
width: 300px;
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(220, 38, 38, 0.4);
|
||
animation: rpCardIn 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
position: relative;
|
||
}
|
||
|
||
@keyframes rpCardIn {
|
||
from {
|
||
transform: scale(0.7) translateY(40px);
|
||
opacity: 0;
|
||
}
|
||
|
||
to {
|
||
transform: scale(1) translateY(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* 红包顶部区 */
|
||
#rp-header {
|
||
background: linear-gradient(160deg, #dc2626 0%, #b91c1c 50%, #991b1b 100%);
|
||
padding: 24px 20px 20px;
|
||
text-align: center;
|
||
position: relative;
|
||
}
|
||
|
||
#rp-header::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(ellipse at 50% 0%, rgba(255, 100, 0, 0.3) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.rp-emoji {
|
||
font-size: 52px;
|
||
display: block;
|
||
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.3));
|
||
animation: rpBounce 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes rpBounce {
|
||
|
||
0%,
|
||
100% {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
50% {
|
||
transform: translateY(-6px);
|
||
}
|
||
}
|
||
|
||
.rp-sender {
|
||
color: #fde68a;
|
||
font-size: 13px;
|
||
margin-top: 8px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.rp-title {
|
||
color: #fff;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
margin-top: 4px;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.rp-amount {
|
||
color: #fde68a;
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
margin-top: 6px;
|
||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.rp-amount small {
|
||
font-size: 14px;
|
||
opacity: 0.85;
|
||
}
|
||
|
||
/* 红包底部区 */
|
||
#rp-body {
|
||
background: #fff8f0;
|
||
padding: 16px 20px;
|
||
}
|
||
|
||
.rp-info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 12px;
|
||
color: #92400e;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
/* 倒计时条 */
|
||
#rp-timer-bar-wrap {
|
||
background: #fee2e2;
|
||
border-radius: 4px;
|
||
height: 6px;
|
||
overflow: hidden;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
#rp-timer-bar {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #dc2626, #f97316);
|
||
border-radius: 4px;
|
||
transition: width 1s linear;
|
||
}
|
||
|
||
/* 领取按钮 */
|
||
#rp-claim-btn {
|
||
width: 100%;
|
||
padding: 13px;
|
||
background: linear-gradient(135deg, #dc2626, #ea580c);
|
||
color: #fff;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
border: none;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
letter-spacing: 1px;
|
||
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
|
||
transition: opacity .2s, transform .15s;
|
||
}
|
||
|
||
#rp-claim-btn:hover {
|
||
opacity: 0.9;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
#rp-claim-btn:active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
#rp-claim-btn:disabled {
|
||
background: #9ca3af;
|
||
box-shadow: none;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
/* 已领取名单 */
|
||
#rp-claims-list {
|
||
margin-top: 12px;
|
||
max-height: 100px;
|
||
overflow-y: auto;
|
||
border-top: 1px dashed #fca5a5;
|
||
padding-top: 8px;
|
||
}
|
||
|
||
.rp-claim-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 11px;
|
||
color: #555;
|
||
padding: 2px 0;
|
||
}
|
||
|
||
.rp-claim-item span:last-child {
|
||
color: #dc2626;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 关闭按钮 */
|
||
#rp-close-btn {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 12px;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
}
|
||
|
||
#rp-close-btn:hover {
|
||
color: #fff;
|
||
}
|
||
|
||
/* 状态提示 */
|
||
#rp-status-msg {
|
||
font-size: 12px;
|
||
text-align: center;
|
||
margin-top: 8px;
|
||
min-height: 16px;
|
||
color: #16a34a;
|
||
font-weight: bold;
|
||
}
|
||
</style>
|
||
|
||
{{-- 红包弹窗 DOM --}}
|
||
<div id="red-packet-modal">
|
||
<div id="red-packet-card">
|
||
{{-- 顶部标题区 --}}
|
||
<div id="rp-header">
|
||
<span id="rp-close-btn" onclick="closeRedPacketModal()">✕</span>
|
||
<span class="rp-emoji">🧧</span>
|
||
<div class="rp-sender" id="rp-sender-name">xxx 的礼包</div>
|
||
<div class="rp-title">聊天室专属礼包</div>
|
||
<div class="rp-amount"><small>总计 </small><span id="rp-total-amount">888</span><small id="rp-type-label">
|
||
金币</small></div>
|
||
</div>
|
||
{{-- 底部操作区 --}}
|
||
<div id="rp-body">
|
||
<div class="rp-info-row">
|
||
<span>剩余份数:<b id="rp-remaining">10</b> / <span id="rp-total-count">10</span> 份</span>
|
||
<span>倒计时:<b id="rp-countdown">120</b>s</span>
|
||
</div>
|
||
<div id="rp-timer-bar-wrap">
|
||
<div id="rp-timer-bar" style="width:100%;"></div>
|
||
</div>
|
||
<button id="rp-claim-btn" onclick="claimRedPacket()">🧧 立即抢红包</button>
|
||
<div id="rp-status-msg"></div>
|
||
{{-- 领取名单 --}}
|
||
<div id="rp-claims-list" style="display:none;">
|
||
<div style="font-size:11px; color:#92400e; margin-bottom:4px; font-weight:bold;">已领取名单:</div>
|
||
<div id="rp-claims-items"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
/**
|
||
* 礼包红包前端交互模块
|
||
*
|
||
* 功能:
|
||
* 1. sendRedPacket() — superlevel 点击「礼包」按钮后确认发包
|
||
* 2. showRedPacketModal() — 收到 RedPacketSent 事件后弹出红包卡片
|
||
* 3. claimRedPacket() — 用户点击「立即抢红包」
|
||
* 4. closeRedPacketModal() — 关闭红包弹窗
|
||
* 5. WebSocket 监听 — 监听 red-packet.sent / red-packet.claimed 广播事件
|
||
*/
|
||
(function() {
|
||
'use strict';
|
||
|
||
// 当前红包状态
|
||
let _rpEnvelopeId = null; // 当前红包 ID
|
||
let _rpExpireAt = null; // 过期时间戳(ms)
|
||
let _rpTotalSeconds = 120; // 总倒计时秒数
|
||
let _rpTimer = null; // 倒计时定时器
|
||
let _rpClaimed = false; // 本次会话是否已领取
|
||
let _rpType = 'gold'; // 当前红包类型(gold / exp)
|
||
|
||
// ── 发包确认 ───────────────────────────────────────
|
||
/**
|
||
* superlevel 点击「礼包」按钮,弹出 chatBanner 三按钮选择类型后发包。
|
||
*/
|
||
window.sendRedPacket = function() {
|
||
window.chatBanner.show({
|
||
icon: '🧧',
|
||
title: '发出礼包',
|
||
name: '选择礼包类型',
|
||
body: '将发出 <b>8888</b> 数量共 <b>10</b> 份的礼包,系统凭空发放,房间成员先到先得!',
|
||
gradient: ['#991b1b', '#dc2626', '#ea580c'],
|
||
titleColor: '#fde68a',
|
||
autoClose: 0,
|
||
buttons: [{
|
||
label: '💰 金币礼包',
|
||
color: '#d97706',
|
||
onClick(btn, close) {
|
||
close();
|
||
doSendRedPacket('gold');
|
||
},
|
||
},
|
||
{
|
||
label: '✨ 经验礼包',
|
||
color: '#7c3aed',
|
||
onClick(btn, close) {
|
||
close();
|
||
doSendRedPacket('exp');
|
||
},
|
||
},
|
||
{
|
||
label: '取消',
|
||
color: 'rgba(255,255,255,0.15)',
|
||
onClick(btn, close) {
|
||
close();
|
||
},
|
||
},
|
||
],
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 实际发包请求(由 chatBanner 按钮回调触发)。
|
||
*
|
||
* @param {'gold'|'exp'} type 货币类型
|
||
*/
|
||
async function doSendRedPacket(type) {
|
||
const btn = document.getElementById('red-packet-btn');
|
||
if (btn) {
|
||
btn.disabled = true;
|
||
btn.textContent = '发送中…';
|
||
}
|
||
|
||
try {
|
||
const res = await fetch('/command/red-packet/send', {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
room_id: window.chatContext.roomId,
|
||
type
|
||
}),
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok || data.status !== 'success') {
|
||
await window.chatDialog.alert(data.message || '发送失败', '操作失败', '#cc4444');
|
||
}
|
||
// 成功后 WebSocket 广播 RedPacketSent,前端自动弹出红包卡片
|
||
} catch (e) {
|
||
await window.chatDialog.alert('发送失败:' + e.message, '操作失败', '#cc4444');
|
||
} finally {
|
||
setTimeout(() => {
|
||
if (btn) {
|
||
btn.disabled = false;
|
||
btn.innerHTML = '🧧 礼包';
|
||
}
|
||
}, 3000);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 展示红包弹窗,并启动倒计时。
|
||
*
|
||
* @param {number} envelopeId 红包 ID
|
||
* @param {string} senderUsername 发包人用户名
|
||
* @param {number} totalAmount 总数量
|
||
* @param {number} totalCount 总份数
|
||
* @param {number} expireSeconds 有效秒数
|
||
* @param {'gold'|'exp'} type 货币类型
|
||
*/
|
||
window.showRedPacketModal = async function(envelopeId, senderUsername, totalAmount, totalCount,
|
||
expireSeconds,
|
||
type) {
|
||
try {
|
||
// 尝试获取点击按钮附带的发包真实时间戳(兼容历史数据)
|
||
let sentAtUnix = null;
|
||
if (window.event && window.event.currentTarget) {
|
||
const btn = window.event.currentTarget;
|
||
if (btn.dataset && btn.dataset.sentAt) {
|
||
sentAtUnix = parseInt(btn.dataset.sentAt, 10);
|
||
}
|
||
}
|
||
|
||
console.log('showRedPacketModal 触发,当前状态:', {
|
||
envelopeId,
|
||
senderUsername,
|
||
totalAmount,
|
||
totalCount,
|
||
expireSeconds,
|
||
type,
|
||
sentAtUnix,
|
||
oldId: _rpEnvelopeId
|
||
});
|
||
|
||
// 计算真实过期时间点
|
||
let calculatedExpireAt = Date.now() + expireSeconds * 1000;
|
||
if (sentAtUnix && !isNaN(sentAtUnix) && sentAtUnix > 0) {
|
||
calculatedExpireAt = (sentAtUnix + expireSeconds) * 1000;
|
||
}
|
||
|
||
// 【前置拦截1】如果有时间戳并算出已过期,直接杀死不弹窗
|
||
if (sentAtUnix && Date.now() >= calculatedExpireAt) {
|
||
window.chatToast?.show({
|
||
title: '⏰ 礼包已过期',
|
||
message: '该红包已过期,无法领取。',
|
||
icon: '⏰',
|
||
color: '#9ca3af',
|
||
duration: 4000,
|
||
});
|
||
console.log('红包已准确断定过期,拦截弹窗显示:', envelopeId);
|
||
return;
|
||
}
|
||
|
||
// 【统一前置拦截】无论新老红包、有无时间戳,为彻底杜绝闪现,强制上云查册生死再放行!
|
||
let currentRemaining = totalCount;
|
||
try {
|
||
const res = await fetch(`/red-packet/${envelopeId}/status`, {
|
||
headers: {
|
||
'Accept': 'application/json',
|
||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')
|
||
.content,
|
||
},
|
||
});
|
||
const initialStatusData = await res.json();
|
||
|
||
if (initialStatusData.status === 'success') {
|
||
if (initialStatusData.is_expired || initialStatusData.envelope_status ===
|
||
'expired') {
|
||
window.chatToast?.show({
|
||
title: '⏰ 礼包已过期',
|
||
message: '该红包已过期,无法领取。',
|
||
icon: '⏰',
|
||
color: '#9ca3af',
|
||
duration: 4000,
|
||
});
|
||
return; // 判定死亡,直接退出,永不渲染弹窗!
|
||
}
|
||
if (initialStatusData.remaining_count <= 0 || initialStatusData
|
||
.envelope_status === 'completed') {
|
||
window.chatToast?.show({
|
||
title: '😅 手慢了!',
|
||
message: '红包已被抢完,下次要快一点哦!',
|
||
icon: '🧧',
|
||
color: '#f59e0b',
|
||
duration: 4000,
|
||
});
|
||
return;
|
||
}
|
||
if (initialStatusData.has_claimed) {
|
||
window.chatToast?.show({
|
||
title: '✅ 已领取',
|
||
message: '您已成功领取过本次礼包!',
|
||
icon: '🧧',
|
||
color: '#10b981',
|
||
duration: 4000,
|
||
});
|
||
return; // 判定已领取,直接退出
|
||
}
|
||
// 记录真实的剩余倒计时以备展示
|
||
currentRemaining = initialStatusData.remaining_count;
|
||
totalCount = initialStatusData.total_count || totalCount;
|
||
}
|
||
} catch (e) {
|
||
console.error('红包状态前置预查失败:', e);
|
||
}
|
||
|
||
// --------- 到此,证实它不仅没死甚至还很活泼、也没领取过。开始安心布置并渲染弹窗 ---------
|
||
|
||
_rpEnvelopeId = envelopeId;
|
||
_rpClaimed = false;
|
||
_rpType = type || 'gold';
|
||
_rpExpireAt = calculatedExpireAt;
|
||
_rpTotalSeconds = expireSeconds;
|
||
|
||
// 根据类型调整配色和标签
|
||
const isExp = (type === 'exp');
|
||
const typeIcon = isExp ? '✨' : '💰';
|
||
const typeName = isExp ? '经验' : '金币';
|
||
const headerBg = isExp ?
|
||
'linear-gradient(160deg,#6d28d9 0%,#5b21b6 50%,#4c1d95 100%)' :
|
||
'linear-gradient(160deg,#dc2626 0%,#b91c1c 50%,#991b1b 100%)';
|
||
const claimBg = isExp ?
|
||
'linear-gradient(135deg,#7c3aed,#4f46e5)' :
|
||
'linear-gradient(135deg,#dc2626,#ea580c)';
|
||
|
||
const modalEl = document.getElementById('red-packet-modal');
|
||
if (!modalEl) {
|
||
window.chatDialog?.alert('致命错误:红包视图容器 #red-packet-modal 找不到!', '系统错误', '#cc4444');
|
||
return;
|
||
}
|
||
|
||
// 强制解除隐藏,赋予超高权限层级
|
||
modalEl.style.setProperty('display', 'flex', 'important');
|
||
modalEl.style.setProperty('z-index', '9999999', 'important');
|
||
modalEl.style.setProperty('opacity', '1', 'important');
|
||
modalEl.style.setProperty('visibility', 'visible', 'important');
|
||
|
||
// 应用配色
|
||
document.getElementById('rp-header').style.background = headerBg;
|
||
const claimBtn = document.getElementById('rp-claim-btn');
|
||
if (claimBtn) {
|
||
claimBtn.style.background = claimBg;
|
||
}
|
||
|
||
// 填入数据
|
||
document.getElementById('rp-sender-name').textContent = senderUsername + ' 的礼包';
|
||
document.getElementById('rp-total-amount').textContent = totalAmount;
|
||
document.getElementById('rp-total-count').textContent = totalCount;
|
||
document.getElementById('rp-remaining').textContent = currentRemaining;
|
||
document.getElementById('rp-countdown').textContent = expireSeconds;
|
||
document.getElementById('rp-timer-bar').style.width = '100%';
|
||
document.getElementById('rp-status-msg').textContent = '';
|
||
document.getElementById('rp-claims-list').style.display = 'none';
|
||
document.getElementById('rp-claims-items').innerHTML = '';
|
||
|
||
// 更新卡片标题信息
|
||
const emojiEl = modalEl.querySelector('.rp-emoji');
|
||
if (emojiEl) emojiEl.textContent = typeIcon;
|
||
|
||
const titleEl = modalEl.querySelector('.rp-title');
|
||
if (titleEl) titleEl.textContent = typeName + '礼包';
|
||
|
||
const typeLabel = document.getElementById('rp-type-label');
|
||
if (typeLabel) typeLabel.textContent = ' ' + typeName;
|
||
|
||
if (claimBtn) {
|
||
claimBtn.disabled = false;
|
||
claimBtn.textContent = typeIcon + ' 立即抢包';
|
||
}
|
||
} catch (err) {
|
||
console.error('showRedPacketModal 执行失败:', err);
|
||
window.chatDialog?.alert('红包弹窗初始化异常: ' + err.message, '系统错误', '#cc4444');
|
||
}
|
||
|
||
// 启动倒计时
|
||
clearInterval(_rpTimer);
|
||
_rpTimer = setInterval(() => {
|
||
const remaining = Math.max(0, Math.ceil((_rpExpireAt - Date.now()) / 1000));
|
||
document.getElementById('rp-countdown').textContent = remaining;
|
||
document.getElementById('rp-timer-bar').style.width =
|
||
(remaining / _rpTotalSeconds * 100) + '%';
|
||
|
||
if (remaining <= 0) {
|
||
clearInterval(_rpTimer);
|
||
document.getElementById('rp-claim-btn').disabled = true;
|
||
document.getElementById('rp-claim-btn').textContent = '礼包已过期';
|
||
document.getElementById('rp-status-msg').style.color = '#9ca3af';
|
||
document.getElementById('rp-status-msg').textContent = '红包已过期,即将关闭…';
|
||
// 3 秒后自动关闭弹窗
|
||
setTimeout(() => closeRedPacketModal(), 3000);
|
||
}
|
||
}, 1000);
|
||
};
|
||
|
||
// ── 抢包/关闭逻辑 ─────────────────────────────────────
|
||
window.closeRedPacketModal = function() {
|
||
console.trace('closeRedPacketModal 被调用');
|
||
document.getElementById('red-packet-modal').style.display = 'none';
|
||
if (_rpTimer) clearInterval(_rpTimer);
|
||
};
|
||
|
||
// 点击遮罩关闭
|
||
document.getElementById('red-packet-modal').addEventListener('click', function(e) {
|
||
if (e.target === this) {
|
||
closeRedPacketModal();
|
||
}
|
||
});
|
||
|
||
// ── 抢红包 ──────────────────────────────────────
|
||
/**
|
||
* 用户点击「立即抢红包」,调用后端 claim 接口。
|
||
*/
|
||
window.claimRedPacket = async function() {
|
||
if (!_rpEnvelopeId) {
|
||
return;
|
||
}
|
||
|
||
const btn = document.getElementById('rp-claim-btn');
|
||
btn.disabled = true;
|
||
btn.textContent = '抢包中…';
|
||
|
||
try {
|
||
const res = await fetch(`/red-packet/${_rpEnvelopeId}/claim`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
room_id: window.chatContext.roomId
|
||
}),
|
||
});
|
||
const data = await res.json();
|
||
|
||
const statusEl = document.getElementById('rp-status-msg');
|
||
const typeLabel = (_rpType === 'exp') ? '经验' : '金币';
|
||
if (res.ok && data.status === 'success') {
|
||
_rpClaimed = true;
|
||
btn.textContent = '🎉 已抢到!';
|
||
statusEl.style.color = '#16a34a';
|
||
statusEl.textContent = `恭喜!您抢到了 ${data.amount} ${typeLabel}!`;
|
||
const remainingEl = document.getElementById('rp-remaining');
|
||
if (remainingEl && typeof data.remaining_count === 'number') {
|
||
remainingEl.textContent = data.remaining_count;
|
||
}
|
||
|
||
// 弹出全局 Toast
|
||
window.chatToast.show({
|
||
title: '🧧 礼包到账',
|
||
message: `恭喜您抢到了礼包 <b>${data.amount}</b> ${typeLabel}!`,
|
||
icon: '🧧',
|
||
color: (_rpType === 'exp') ? '#7c3aed' : '#dc2626',
|
||
duration: 8000,
|
||
});
|
||
|
||
// 3 秒后自动关闭弹窗
|
||
setTimeout(() => closeRedPacketModal(), 3000);
|
||
} else {
|
||
statusEl.style.color = '#dc2626';
|
||
statusEl.textContent = data.message || '抢包失败';
|
||
const message = data.message || '';
|
||
const shouldAutoClose = message.includes('已过期')
|
||
|| message.includes('已被抢完')
|
||
|| message.includes('已抢完')
|
||
|| message.includes('红包已抢完或已过期');
|
||
|
||
// 若红包已经结束,则保持禁用并在 3 秒后自动关闭弹窗。
|
||
if (shouldAutoClose) {
|
||
btn.textContent = '礼包已结束';
|
||
setTimeout(() => closeRedPacketModal(), 3000);
|
||
} else if (message.includes('已经领过')) {
|
||
btn.textContent = '已参与';
|
||
} else {
|
||
btn.disabled = false;
|
||
btn.textContent = '🧧 立即抢红包';
|
||
}
|
||
}
|
||
} catch (e) {
|
||
document.getElementById('rp-status-msg').textContent = '网络异常,请重试';
|
||
document.getElementById('rp-status-msg').style.color = '#dc2626';
|
||
btn.disabled = false;
|
||
btn.textContent = '🧧 立即抢红包';
|
||
}
|
||
};
|
||
|
||
// ── 更新领取名单(被 WS 触发调用)───────────────
|
||
/**
|
||
* 收到「系统传音」中的领取播报时,同步更新弹窗内的名单与剩余数。
|
||
*
|
||
* @param {string} username 领取者用户名
|
||
* @param {number} amount 领取金额
|
||
* @param {number} remaining 剩余份数
|
||
* @param {'gold'|'exp'} [type] 红包类型
|
||
*/
|
||
window.updateRedPacketClaimsUI = function(username, amount, remaining, type = _rpType) {
|
||
const remainingEl = document.getElementById('rp-remaining');
|
||
if (remainingEl) {
|
||
remainingEl.textContent = remaining;
|
||
}
|
||
|
||
const listEl = document.getElementById('rp-claims-list');
|
||
const itemsEl = document.getElementById('rp-claims-items');
|
||
if (!listEl || !itemsEl) {
|
||
return;
|
||
}
|
||
|
||
listEl.style.display = 'block';
|
||
const typeLabel = type === 'exp' ? '经验' : '金币';
|
||
const item = document.createElement('div');
|
||
item.className = 'rp-claim-item';
|
||
item.innerHTML = `<span>${username}</span><span>+${amount} ${typeLabel}</span>`;
|
||
itemsEl.prepend(item);
|
||
|
||
// 若已全部领完,更新按钮状态
|
||
if (remaining <= 0) {
|
||
const btn = document.getElementById('rp-claim-btn');
|
||
if (btn && !_rpClaimed) {
|
||
btn.disabled = true;
|
||
btn.textContent = '礼包已被抢完!';
|
||
}
|
||
clearInterval(_rpTimer);
|
||
// 3 秒后自动关闭
|
||
setTimeout(() => closeRedPacketModal(), 3000);
|
||
}
|
||
};
|
||
|
||
// ── WebSocket 监听 red-packet.sent / red-packet.claimed ───────────────
|
||
/**
|
||
* 等待 Echo 就绪后注册红包相关事件监听,
|
||
* 新红包弹窗展示,领取成功后实时刷新剩余份数。
|
||
*/
|
||
function setupRedPacketListener() {
|
||
if (!window.Echo || !window.chatContext) {
|
||
setTimeout(setupRedPacketListener, 500);
|
||
return;
|
||
}
|
||
window.Echo.join(`room.${window.chatContext.roomId}`)
|
||
.listen('.red-packet.sent', (e) => {
|
||
// 收到红包事件,弹出卡片(type 决定金币/经验配色)
|
||
showRedPacketModal(
|
||
e.envelope_id,
|
||
e.sender_username,
|
||
e.total_amount,
|
||
e.total_count,
|
||
e.expire_seconds,
|
||
e.type || 'gold',
|
||
);
|
||
})
|
||
.listen('.red-packet.claimed', (e) => {
|
||
if (Number(e.envelope_id) !== Number(_rpEnvelopeId)) {
|
||
return;
|
||
}
|
||
|
||
window.updateRedPacketClaimsUI(
|
||
e.claimer_username,
|
||
e.amount,
|
||
e.remaining_count,
|
||
e.type || _rpType,
|
||
);
|
||
});
|
||
console.log('RedPacketSent 监听器已注册');
|
||
}
|
||
document.addEventListener('DOMContentLoaded', setupRedPacketListener);
|
||
|
||
})(); // end IIFE
|
||
</script>
|