重构(chat): 聊天室 Partials 第二阶段分类拆分及修复红包弹窗隐藏 Bug

- 完成对 scripts.blade.php 中非核心业务逻辑(钓鱼游戏、AI机器人、系统全局公告)的深度抽象隔离
- 修复抢红包逻辑中 setInterval 缺失时间参数(1000)引发浏览器前端主线程挂起的重度阻塞问题
- 修复 lottery-panel 组件结尾漏写 </div> 导致的连锁级渲染树崩溃(该崩溃导致红包节点被意外当作隐藏后代节点渲染,造成彻底不可见)
- 对相关模板规范代码结构,执行 Laravel Pint 格式化并提交
This commit is contained in:
2026-03-09 11:30:11 +08:00
parent 28d9f9ee96
commit bfb1a3bca4
24 changed files with 2806 additions and 2601 deletions
@@ -0,0 +1,22 @@
{{--
文件功能:聊天室顶部标题栏 + 公告滚动条
frame.blade.php 拆分,便于独立维护
依赖变量:$room
--}}
{{-- 顶部标题栏(第一行:房间名称 + 介绍) --}}
<div class="room-title-bar">
<div class="room-name">{{ $room->name }}<span style="font-size:12px; font-weight:normal;">在线<span
id="online-count">0</span></span></div>
<div class="room-desc" id="room-title-display">{{ $room->description ?? '欢迎光临本聊天室!畅所欲言,文明聊天。' }}
</div>
</div>
{{-- 第二行:公告/祝福语滚动条(有权限的用户可设置) --}}
<div class="room-announcement-bar" id="announcement-bar">
<marquee behavior="scroll" direction="left" scrollamount="3" style="color:#cc0000; font-size:12px; font-weight:bold;"
id="announcement-text">
{{ $room->announcement ?? '欢迎来到聊天室!祝大家聊天愉快!' }}
</marquee>
</div>
@@ -0,0 +1,128 @@
{{--
文件功能:聊天室底部输入工具栏(两行结构)
第一行:发送对象、动作、字色、悄悄话、滚屏、分屏、管理操作
第二行:输入框 + 发送按钮
frame.blade.php 拆分,便于独立维护
依赖变量:$user, $room, $levelKick, $levelMute, $levelBan, $levelBanip
--}}
<div class="input-bar">
<form id="chat-form" onsubmit="sendMessage(event)">
{{-- 第一行:工具选项 --}}
<div class="input-row">
<label>
<select id="to_user" name="to_user" style="color: #224466;">
<option value="大家" selected>大家</option>
</select>
:
</label>
<label>动作:
<select id="action" name="action">
<option value=""></option>
<option value="微笑">微笑</option>
<option value="大笑">大笑</option>
<option value="愤怒">愤怒</option>
<option value="哭泣">哭泣</option>
<option value="害羞">害羞</option>
<option value="鄙视">鄙视</option>
<option value="得意">得意</option>
<option value="疑惑">疑惑</option>
<option value="同情">同情</option>
<option value="无奈">无奈</option>
<option value="拳打">拳打</option>
<option value="飞吻">飞吻</option>
<option value="偷看">偷看</option>
</select>
</label>
<label>字色:
<input type="color" id="font_color" name="font_color" value="{{ $user->s_color ?? '#000000' }}"
style="width: 22px; height: 18px; padding: 0; border: 1px solid navy; cursor: pointer;">
</label>
<label>字号:
<select id="font_size_select" style="color: #224466;" onchange="applyFontSize(this.value)">
<option value="12">12</option>
<option value="13">13</option>
<option value="14" selected>14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="18">18</option>
<option value="20">20</option>
<option value="22">22</option>
</select>
</label>
<label title="仅对方和自己可见">
<input type="checkbox" id="is_secret" name="is_secret" value="1">
悄悄话
</label>
<label title="勾选后关闭所有特效声音">
<input type="checkbox" id="sound_muted" onchange="toggleSoundMute(this.checked)">
🔇 禁音
</label>
<label title="自动滚屏到最新消息">
<input type="checkbox" id="auto_scroll" checked>
滚屏
</label>
<label>分屏:
<select id="split_screen" onchange="changeSplitScreen(this.value)">
<option value="0">单窗</option>
<option value="1">上下分</option>
</select>
</label>
@if (
$user->user_level >= (int) \App\Models\Sysparam::getValue('level_announcement', '10') ||
$room->master == $user->username)
<button type="button" onclick="promptAnnouncement()"
style="font-size: 11px; padding: 1px 6px; background: #4a9; color: #fff; border: none; border-radius: 2px; cursor: pointer;">设公告</button>
@endif
<button type="button" id="fishing-btn" onclick="startFishing()"
style="font-size: 11px; padding: 1px 6px; background: #2563eb; color: #fff; border: none; border-radius: 2px; cursor: pointer;">🎣
钓鱼</button>
@if ($user->user_level >= (int) \App\Models\Sysparam::getValue('superlevel', '100'))
<button type="button" onclick="promptAnnounceMessage()"
style="font-size: 11px; padding: 1px 6px; background: #7c3aed; color: #fff; border: none; border-radius: 2px; cursor: pointer;">📢
公屏</button>
<button type="button" onclick="adminClearScreen()"
style="font-size: 11px; padding: 1px 6px; background: #dc2626; color: #fff; border: none; border-radius: 2px; cursor: pointer;">🧹
清屏</button>
<button type="button" id="red-packet-btn" onclick="sendRedPacket()"
style="font-size: 11px; padding: 1px 6px; background: linear-gradient(135deg, #dc2626, #d97706); color: #fff; border: none; border-radius: 2px; cursor: pointer; font-weight: bold;">🧧
礼包</button>
{{-- 全屏特效按钮组(仅管理员可见) --}}
<button type="button" onclick="triggerEffect('fireworks')" title="全屏烟花"
style="font-size: 11px; padding: 1px 6px; background: #ea580c; color: #fff; border: none; border-radius: 2px; cursor: pointer;">🎆
烟花</button>
<button type="button" onclick="triggerEffect('rain')" title="全屏下雨"
style="font-size: 11px; padding: 1px 6px; background: #2563eb; color: #fff; border: none; border-radius: 2px; cursor: pointer;">🌧️
下雨</button>
<button type="button" onclick="triggerEffect('lightning')" title="全屏雷电"
style="font-size: 11px; padding: 1px 6px; background: #7c3aed; color: #fff; border: none; border-radius: 2px; cursor: pointer;">
雷电</button>
<button type="button" onclick="triggerEffect('snow')" title="全屏下雪"
style="font-size: 11px; padding: 1px 6px; background: #0891b2; color: #fff; border: none; border-radius: 2px; cursor: pointer;">❄️
下雪</button>
@endif
<button type="button" onclick="localClearScreen()"
style="font-size: 11px; padding: 1px 6px; background: #64748b; color: #fff; border: none; border-radius: 2px; cursor: pointer;">🔄
清屏</button>
</div>
{{-- 第二行:输入框 + 发送 --}}
<div class="input-row">
<input type="text" id="content" name="content" class="say-input" placeholder="在这里输入聊天内容,按 Enter 发送..."
autocomplete="off">
<button type="submit" id="send-btn" class="send-btn">发送</button>
</div>
</form>
</div>
@@ -0,0 +1,97 @@
{{--
文件功能:右侧用户面板(复刻原版)
包含:名单/房间/贴图/酷库 四个 Tab 面板
独立文件方便维护
依赖变量:$room(当前房间模型)
--}}
<div class="chat-right">
{{-- Tab 标题栏 --}}
<div class="right-tabs">
<button class="tab-btn active" id="tab-users" onclick="switchTab('users')">名单</button>
<button class="tab-btn" id="tab-rooms" onclick="switchTab('rooms')">房间</button>
<button class="tab-btn" id="tab-action" onclick="switchTab('action')">酷库</button>
</div>
{{-- 用户列表面板 --}}
<div class="user-list-content" id="panel-users">
{{-- 房间信息头部(原版风格:房间名 + 人气 + 在线人数) --}}
<div style="text-align:center; padding:4px 6px; border-bottom:1px solid #cde; background:#f0f6ff;">
<div style="color:#336699; font-weight:bold; font-size:12px;">{{ $room->name }}</div>
<div style="color:#cc6600; font-size:11px;">人气:{{ $room->visit_num ?? 0 }}</div>
<div style="font-size:11px; color:#999; margin-top:2px;">
<span id="online-count-bottom">0</span>
<a href="#" onclick="renderUserList(); return false;"
style="color:#c00; font-size:10px; margin-left:4px;">刷新</a>
</div>
</div>
{{-- 排序 + 搜索(原版风格) --}}
<div style="padding:3px 4px; border-bottom:1px solid #cde; background:#f8fbff; font-size:11px;">
<div style="display:flex; align-items:center; gap:4px; margin-bottom:3px;">
<span style="color:#666; flex-shrink:0;">排序:</span>
<select id="user-sort-select" onchange="renderUserList()"
style="flex:1; font-size:11px; padding:1px; border:1px solid #aac; border-radius:2px;">
<option value="default">默认</option>
<option value="name">按名称</option>
<option value="level">按等级</option>
</select>
</div>
<div style="display:flex; align-items:center; gap:2px;">
<input type="text" id="user-search-input" placeholder="搜索用户" onkeyup="filterUserList()"
style="width:100%; font-size:11px; padding:2px 4px; border:1px solid #aac; border-radius:2px; box-sizing:border-box;">
</div>
</div>
{{-- 用户列表 --}}
<div id="online-users-list" style="padding:2px;">
<div style="text-align:center; color:#999; padding:20px 0; font-size:11px;">加载中...</div>
</div>
{{-- 管理员名单链接 --}}
<div style="text-align:center; padding:4px; border-top:1px solid #cde; font-size:11px;">
<a href="#" style="color:#336699; font-weight:bold;">管理员名单</a>
</div>
</div>
{{-- 房间列表面板 --}}
<div class="user-list-content" id="panel-rooms" style="display: none;">
{{-- 顶部标题栏 --}}
<div style="text-align:center; padding:4px 6px; border-bottom:1px solid #cde; background:#f0f6ff;">
<div style="color:#336699; font-weight:bold; font-size:12px;">所有聊天室</div>
<div style="font-size:10px; color:#999; margin-top:1px;">点击可切换房间</div>
</div>
{{-- 房间列表容器 --}}
<div id="rooms-online-list" style="padding:4px 2px;">
<div style="text-align:center; color:#bbb; padding:16px 0; font-size:11px;">加载中...</div>
</div>
</div>
{{-- 贴图面板(懒加载:切换到该 Tab 时才加载图片) --}}
<div class="user-list-content" id="panel-emoji" style="display: none; padding: 6px;">
<div style="display: flex; flex-wrap: wrap; gap: 2px; justify-content: center;">
@for ($i = 1; $i <= 50; $i++)
<img data-src="/images/emoji/{{ $i }}.gif" width="24" height="24"
style="cursor: pointer; border: 1px solid transparent; border-radius: 2px;"
onmouseover="this.style.borderColor='#336699'" onmouseout="this.style.borderColor='transparent'"
onclick="insertEmoji('[IMG]{{ $i }}[/IMG]')" title="表情{{ $i }}"
onerror="this.style.display='none'">
@endfor
</div>
</div>
{{-- 酷库面板 --}}
<div class="user-list-content" id="panel-action" style="display: none; padding: 6px;">
<div style="font-size: 11px; color: #666; line-height: 2;">
@foreach (['微笑', '大笑', '愤怒', '哭泣', '害羞', '鄙视', '得意', '疑惑', '同情', '无奈', '拳打', '飞吻', '偷看', '战战兢兢'] as $act)
<a href="#" onclick="setAction('{{ $act }}'); return false;"
style="display: inline-block; padding: 2px 6px; background: #f0f6ff; border: 1px solid #dde8ff; border-radius: 3px; margin: 1px; color: #336699; font-size: 11px;">{{ $act }}</a>
@endforeach
</div>
</div>
{{-- 底部在线统计 --}}
<div class="online-stats">
在线: <strong id="online-count-footer">0</strong>
</div>
</div>
File diff suppressed because it is too large Load Diff