feat: 神秘箱子系统完整实现 + 婚姻状态弹窗 + 工具栏优化
## 新功能 - 神秘箱子系统(MysteryBox)完整实现: - 新增 MysteryBox / MysteryBoxClaim 模型及迁移文件 - DropMysteryBoxJob / ExpireMysteryBoxJob 队列作业 - MysteryBoxController(/mystery-box/status + /mystery-box/claim) - 支持三种类型:普通箱(500~2000金)/ 稀有箱(5000~20000金)/ 黑化箱(陷阱扣200~1000金) - 调度器自动投放 + 管理员手动投放 - CurrencySource 新增 MYSTERY_BOX / MYSTERY_BOX_TRAP 枚举 - 婚姻状态弹窗(工具栏「婚姻」按钮): - 工具栏「呼叫」改为「婚姻」,点击打开婚姻状态弹窗 - 动态渲染三种状态:单身 / 求婚中 / 已婚 - 被求婚方可直接「答应 / 婉拒」;已婚可申请离婚(含二次确认) ## 优化修复 - frame.blade.php:Alpine.js CDN 补加 defer,修复所有组件初始化报错 - scripts.blade.php:神秘箱子暗号主动拦截(不依赖轮询),领取成功后弹 chatDialog 展示结果,更新金币余额 - MysteryBoxController:claim() 时 change() 补传 room_id 记录来源房间 - 后台游戏管理页(game-configs):投放箱子按钮颜色修复;弹窗替换为 window.adminDialog - admin/layouts:新增全局 adminDialog 弹窗组件(替代原生 alert/confirm) - baccarat-panel:FAB 拖动重构为 Alpine.js baccaratFab() 组件,与 slotFab 一致 - GAMES_TODO.md:神秘箱子移入已完成区,补全修复记录
This commit is contained in:
@@ -175,6 +175,136 @@
|
||||
@yield('content')
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{{-- ══════════════════════════════════════════════════════════
|
||||
全局弹窗组件:window.adminDialog.alert / window.adminDialog.confirm
|
||||
用法:
|
||||
window.adminDialog.alert('操作成功!', '✅ 提示');
|
||||
window.adminDialog.confirm('确定要删除?', '⚠️ 确认', () => { ... });
|
||||
══════════════════════════════════════════════════════════ --}}
|
||||
<div id="admin-dialog-overlay"
|
||||
style="display:none; position:fixed; inset:0; background:rgba(15,23,42,.55);
|
||||
backdrop-filter:blur(3px); z-index:99999; align-items:center; justify-content:center;">
|
||||
<div id="admin-dialog-box"
|
||||
style="background:#fff; border-radius:16px; box-shadow:0 24px 64px rgba(0,0,0,.22);
|
||||
min-width:320px; max-width:480px; width:90%; padding:32px 32px 24px; text-align:center;
|
||||
animation:admin-dialog-pop .25s cubic-bezier(.175,.885,.32,1.275);">
|
||||
<div id="admin-dialog-icon" style="font-size:36px; margin-bottom:10px;"></div>
|
||||
<div id="admin-dialog-title" style="font-size:16px; font-weight:800; color:#1e293b; margin-bottom:8px;">
|
||||
</div>
|
||||
<div id="admin-dialog-msg" style="font-size:14px; color:#475569; line-height:1.6; margin-bottom:20px;">
|
||||
</div>
|
||||
<div id="admin-dialog-btns" style="display:flex; gap:10px; justify-content:center;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
@keyframes admin-dialog-pop {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(.8);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
/**
|
||||
* 后台全局弹窗组件。
|
||||
*
|
||||
* 提供 alert / confirm 两种模式,替换原生 alert/confirm。
|
||||
*/
|
||||
window.adminDialog = (function() {
|
||||
const overlay = document.getElementById('admin-dialog-overlay');
|
||||
const box = document.getElementById('admin-dialog-box');
|
||||
const elIcon = document.getElementById('admin-dialog-icon');
|
||||
const elTitle = document.getElementById('admin-dialog-title');
|
||||
const elMsg = document.getElementById('admin-dialog-msg');
|
||||
const elBtns = document.getElementById('admin-dialog-btns');
|
||||
|
||||
/** 关闭弹窗 */
|
||||
function close() {
|
||||
overlay.style.display = 'none';
|
||||
}
|
||||
|
||||
/** 点击遮罩层关闭 */
|
||||
overlay.addEventListener('click', function(e) {
|
||||
if (e.target === overlay) close();
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建按钮元素
|
||||
*
|
||||
* @param {string} label 按钮文字
|
||||
* @param {string} color 按钮背景色
|
||||
* @param {Function} onClick 点击回调
|
||||
*/
|
||||
function makeBtn(label, color, onClick) {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = label;
|
||||
btn.style.cssText = `padding:9px 24px; border-radius:8px; border:none; cursor:pointer;
|
||||
font-size:14px; font-weight:700; color:#fff; background:${color};
|
||||
transition:opacity .15s; box-shadow:0 3px 10px rgba(0,0,0,.12);`;
|
||||
btn.onmouseover = () => btn.style.opacity = '.82';
|
||||
btn.onmouseout = () => btn.style.opacity = '1';
|
||||
btn.addEventListener('click', () => {
|
||||
close();
|
||||
if (onClick) onClick();
|
||||
});
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出提示框(仅「确定」按钮)
|
||||
*
|
||||
* @param {string} message 消息内容(支持 HTML)
|
||||
* @param {string} title 标题
|
||||
* @param {string} icon 图标 Emoji
|
||||
* @param {Function} onOk 确定回调
|
||||
*/
|
||||
function alert(message, title = '提示', icon = 'ℹ️', onOk = null) {
|
||||
elIcon.textContent = icon;
|
||||
elTitle.textContent = title;
|
||||
elMsg.innerHTML = message;
|
||||
elBtns.innerHTML = '';
|
||||
elBtns.appendChild(makeBtn('确定', '#4f46e5', onOk));
|
||||
overlay.style.display = 'flex';
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出确认框(「确定」+「取消」按钮)
|
||||
*
|
||||
* @param {string} message 消息内容
|
||||
* @param {string} title 标题
|
||||
* @param {Function} onConfirm 确认回调
|
||||
* @param {string} icon 图标 Emoji
|
||||
*/
|
||||
function confirm(message, title = '确认操作', onConfirm = null, icon = '⚠️') {
|
||||
elIcon.textContent = icon;
|
||||
elTitle.textContent = title;
|
||||
elMsg.innerHTML = message;
|
||||
elBtns.innerHTML = '';
|
||||
|
||||
const confirmBtn = makeBtn('确定', '#4f46e5', onConfirm);
|
||||
const cancelBtn = makeBtn('取消', '#94a3b8', null);
|
||||
elBtns.appendChild(confirmBtn);
|
||||
elBtns.appendChild(cancelBtn);
|
||||
overlay.style.display = 'flex';
|
||||
}
|
||||
|
||||
return {
|
||||
alert,
|
||||
confirm,
|
||||
close
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user