Files
chatroom/resources/views/admin/layouts/app.blade.php

319 lines
17 KiB
PHP
Raw Normal View History

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>后台管理 - 流星</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body class="bg-gray-100 flex h-screen text-gray-800">
<!-- 左侧侧边栏 -->
<aside class="w-64 bg-slate-900 text-white flex flex-col">
<div class="p-6 text-center border-b border-white/10">
<h2 class="text-2xl font-extrabold tracking-widest uppercase">Admin</h2>
<p class="text-xs text-slate-400 mt-2">控制台</p>
</div>
<nav class="flex-1 px-4 py-6 space-y-2 overflow-y-auto">
{{-- ──────── 所有有职务的人都可见 ──────── --}}
<a href="{{ route('admin.dashboard') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.dashboard') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
📊 仪表盘
</a>
<a href="{{ route('admin.currency-stats.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.currency-stats.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
📈 积分流水统计
</a>
<a href="{{ route('admin.users.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.users.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
👥 用户管理
</a>
{{-- ──────── 部门职务任命系统 ──────── --}}
<div class="border-t border-white/10 my-2"></div>
<a href="{{ route('admin.appointments.my-duty-logs') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.appointments.my-duty-logs') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
📝 我的履职记录
</a>
<a href="{{ route('admin.appointments.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.appointments.*') && !request()->routeIs('admin.appointments.my-duty-logs') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
🎖️ 任命管理
</a>
{{-- superlevel 及以上可查看只读标注以下模块id=1 可编辑 --}}
@php $superLvl = (int) \App\Models\Sysparam::getValue('superlevel', '100'); @endphp
@if (Auth::user()->user_level >= $superLvl)
<div class="border-t border-white/10 my-2"></div>
<p class="px-4 text-xs text-slate-500 uppercase tracking-widest mb-1">
{{ Auth::id() === 1 ? '站长功能' : '查看' }}</p>
<a href="{{ route('admin.system.edit') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.system.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '⚙️ 聊天室参数' !!}
</a>
<a href="{{ route('admin.wechat_bot.edit') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.wechat_bot.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🤖 微信机器人' !!}
</a>
<a href="{{ route('admin.currency-logs.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.currency-logs.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '💴 用户流水' !!}
</a>
<a href="{{ route('admin.rooms.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.rooms.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🏠 房间管理' !!}
</a>
<a href="{{ route('admin.autoact.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.autoact.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🎲 随机事件' !!}
</a>
<a href="{{ route('admin.vip.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.vip.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '👑 VIP 会员等级' !!}
</a>
<a href="{{ route('admin.shop.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.shop.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🛒 商店管理' !!}
</a>
<a href="{{ route('admin.marriages.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.marriages.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '💒 婚姻管理' !!}
</a>
<a href="{{ route('admin.holiday-events.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.holiday-events.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🎊 节日福利' !!}
</a>
<a href="{{ route('admin.game-configs.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.game-configs.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🎮 游戏管理' !!}
</a>
<a href="{{ route('admin.fishing.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.fishing.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🎣 钓鱼事件' !!}
</a>
<a href="{{ route('admin.departments.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.departments.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🏛️ 部门管理' !!}
</a>
<a href="{{ route('admin.positions.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.positions.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '📋 职务管理' !!}
</a>
{{-- 以下纯写操作:仅 id=1 可见 --}}
@if (Auth::id() === 1)
<div class="border-t border-white/10 my-2"></div>
<p class="px-4 text-xs text-slate-500 uppercase tracking-widest mb-1">系统配置</p>
<a href="{{ route('admin.smtp.edit') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.smtp.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
📧 邮件 SMTP 配置
</a>
<a href="{{ route('admin.ai-providers.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.ai-providers.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
🤖 AI 厂商配置
</a>
<a href="{{ route('admin.ops.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.ops.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
🛠️ 运维工具
</a>
<a href="{{ route('admin.changelogs.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.changelogs.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
📋 开发日志
</a>
<a href="{{ route('admin.feedback.index') }}"
class="flex items-center justify-between px-4 py-3 rounded-md transition {{ request()->routeIs('admin.feedback.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
<span>💬 用户反馈</span>
@php $pendingFeedback = \App\Models\FeedbackItem::pending()->count(); @endphp
@if ($pendingFeedback > 0)
<span
class="bg-orange-500 text-white text-xs px-1.5 py-0.5 rounded-full font-bold">{{ $pendingFeedback }}</span>
@endif
</a>
<a href="{{ route('admin.forbidden-usernames.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.forbidden-usernames.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
🚫 禁用用户名
</a>
@endif
@endif
</nav>
<div class="p-4 border-t border-white/10">
<a href="{{ route('rooms.index') }}"
class="block w-full text-center px-4 py-2 bg-slate-800 hover:bg-slate-700 rounded transition text-sm">
返回前台大厅
</a>
</div>
</aside>
<!-- 右侧主体内容 -->
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
<!-- 顶栏 -->
<header class="bg-white shadow relative z-20 flex items-center justify-between px-6 py-4">
<h1 class="text-xl font-bold text-gray-700">@yield('title', '控制台')</h1>
<div class="flex items-center space-x-4">
<span class="text-sm font-medium">当前操作人: <span
class="text-indigo-600">{{ Auth::user()->username }}</span></span>
</div>
</header>
<!-- 内容滚动区 -->
<div class="flex-1 overflow-y-auto p-6 relative z-10">
@if (session('success'))
<div class="mb-6 bg-emerald-100 border-l-4 border-emerald-500 text-emerald-700 p-4 rounded shadow-sm">
{{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="mb-6 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-sm">
{{ session('error') }}
</div>
@endif
@if ($errors->any())
<div class="mb-6 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-sm">
<ul class="list-disc list-inside text-sm">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@yield('content')
</div>
</main>
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:神秘箱子移入已完成区,补全修复记录
2026-03-03 19:29:43 +08:00
{{-- ══════════════════════════════════════════════════════════
全局弹窗组件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>