diff --git a/resources/views/chat/partials/layout/input-bar.blade.php b/resources/views/chat/partials/layout/input-bar.blade.php index 25cf6ea..eff8cf0 100644 --- a/resources/views/chat/partials/layout/input-bar.blade.php +++ b/resources/views/chat/partials/layout/input-bar.blade.php @@ -111,7 +111,31 @@ $welcomeMessages = [ style="font-size: 11px; padding: 1px 6px; background: #0f766e; color: #fff; border: none; border-radius: 2px; cursor: pointer;"> 📷 图片 - + +
+ + +
+ @if ( $user->user_level >= (int) \App\Models\Sysparam::getValue('level_announcement', '10') || $room->master == $user->username || diff --git a/resources/views/chat/partials/scripts.blade.php b/resources/views/chat/partials/scripts.blade.php index caebfd6..7e6ed23 100644 --- a/resources/views/chat/partials/scripts.blade.php +++ b/resources/views/chat/partials/scripts.blade.php @@ -10,6 +10,7 @@ 6. 钓鱼小游戏(startFishing / reelFish / autoFish) 7. 发送消息(sendMessage,IME 防重触发) 8. 特效控制(triggerEffect / applyFontSize / toggleSoundMute) + 9. 系统播报屏蔽(toggleBlockMenu / toggleBlockedSystemSender) 已拆分至独立文件: - window.chatBanner → chat-banner.blade.php @@ -36,6 +37,8 @@ const toUserSelect = document.getElementById('to_user'); const onlineCount = document.getElementById('online-count'); const onlineCountBottom = document.getElementById('online-count-bottom'); + const BLOCKED_SYSTEM_SENDERS_STORAGE_KEY = 'chat_blocked_system_senders'; + const BLOCKABLE_SYSTEM_SENDERS = ['钓鱼播报', '星海小博士']; // ── 消息区:手机端双触发打开用户名片(PC 端靠 ondblclick 内联属性)── // span[data-u] 由 clickableUser() 生成,touchend 委托至容器避免每条消息单独绑定 @@ -67,6 +70,133 @@ let onlineUsers = {}; let autoScroll = true; let _maxMsgId = 0; // 记录当前收到的最大消息 ID + let blockedSystemSenders = new Set(loadBlockedSystemSenders()); + + /** + * 从 localStorage 读取已屏蔽的系统播报发送者列表。 + * + * @returns {string[]} + */ + function loadBlockedSystemSenders() { + try { + const saved = JSON.parse(localStorage.getItem(BLOCKED_SYSTEM_SENDERS_STORAGE_KEY) || '[]'); + if (!Array.isArray(saved)) { + return []; + } + + // 仅允许白名单内的系统播报项进入配置,避免脏数据污染。 + return saved.filter(sender => BLOCKABLE_SYSTEM_SENDERS.includes(sender)); + } catch (error) { + return []; + } + } + + /** + * 将当前屏蔽配置持久化到 localStorage。 + */ + function persistBlockedSystemSenders() { + localStorage.setItem( + BLOCKED_SYSTEM_SENDERS_STORAGE_KEY, + JSON.stringify(Array.from(blockedSystemSenders)) + ); + } + + /** + * 同步屏蔽菜单中的复选框状态。 + */ + function syncBlockedSystemSenderCheckboxes() { + const fishingCheckbox = document.getElementById('block-sender-fishing'); + const doctorCheckbox = document.getElementById('block-sender-doctor'); + + if (fishingCheckbox) { + fishingCheckbox.checked = blockedSystemSenders.has('钓鱼播报'); + } + + if (doctorCheckbox) { + doctorCheckbox.checked = blockedSystemSenders.has('星海小博士'); + } + } + + /** + * 判断当前消息发送者是否已被用户屏蔽。 + * + * @param {string} fromUser 发送者名称 + * @returns {boolean} + */ + function isBlockedSystemSender(fromUser) { + return blockedSystemSenders.has(String(fromUser || '')); + } + + /** + * 从当前已渲染的聊天窗口中移除指定发送者的所有消息。 + * + * @param {string} sender 发送者名称 + */ + function removeRenderedMessagesBySender(sender) { + [container, container2].forEach(targetContainer => { + if (!targetContainer) { + return; + } + + targetContainer.querySelectorAll('[data-from-user]').forEach(node => { + if (node.dataset.fromUser === sender) { + node.remove(); + } + }); + }); + } + + /** + * 切换系统播报屏蔽菜单的显示状态。 + * + * @param {Event} event 点击事件 + */ + function toggleBlockMenu(event) { + event.stopPropagation(); + const menu = document.getElementById('block-menu'); + const welcomeMenu = document.getElementById('welcome-menu'); + const adminMenu = document.getElementById('admin-menu'); + + if (!menu) { + return; + } + + if (welcomeMenu) { + welcomeMenu.style.display = 'none'; + } + + if (adminMenu) { + adminMenu.style.display = 'none'; + } + + syncBlockedSystemSenderCheckboxes(); + menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; + } + + /** + * 更新指定系统播报项的屏蔽状态,并在勾选后立即清理当前窗口。 + * + * @param {string} sender 系统播报发送者 + * @param {boolean} blocked 是否屏蔽 + */ + function toggleBlockedSystemSender(sender, blocked) { + if (!BLOCKABLE_SYSTEM_SENDERS.includes(sender)) { + return; + } + + if (blocked) { + blockedSystemSenders.add(sender); + // 勾选后立刻移除聊天室窗口内已显示的对应播报内容。 + removeRenderedMessagesBySender(sender); + } else { + blockedSystemSenders.delete(sender); + } + + persistBlockedSystemSenders(); + syncBlockedSystemSenderCheckboxes(); + } + + syncBlockedSystemSenderCheckboxes(); /** * 转义会员横幅文本,避免横幅层被注入 HTML。 @@ -319,12 +449,16 @@ event.stopPropagation(); const menu = document.getElementById('welcome-menu'); const adminMenu = document.getElementById('admin-menu'); + const blockMenu = document.getElementById('block-menu'); if (!menu) { return; } if (adminMenu) { adminMenu.style.display = 'none'; } + if (blockMenu) { + blockMenu.style.display = 'none'; + } menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; } @@ -335,12 +469,16 @@ event.stopPropagation(); const menu = document.getElementById('admin-menu'); const welcomeMenu = document.getElementById('welcome-menu'); + const blockMenu = document.getElementById('block-menu'); if (!menu) { return; } if (welcomeMenu) { welcomeMenu.style.display = 'none'; } + if (blockMenu) { + blockMenu.style.display = 'none'; + } menu.style.display = menu.style.display === 'none' ? 'block' : 'none'; } @@ -441,6 +579,11 @@ if (adminMenu) { adminMenu.style.display = 'none'; } + + const blockMenu = document.getElementById('block-menu'); + if (blockMenu) { + blockMenu.style.display = 'none'; + } }); // ── 动作选择 ────────────────────────────────────── @@ -656,11 +799,19 @@ _maxMsgId = msg.id; } + // 用户勾选屏蔽后,历史消息和实时消息统一在这里拦截,不再进入渲染流程。 + if (isBlockedSystemSender(msg?.from_user)) { + return; + } + const isMe = msg.from_user === window.chatContext.username; const fontColor = msg.font_color || '#000000'; const div = document.createElement('div'); div.className = 'msg-line'; + if (msg?.from_user) { + div.dataset.fromUser = msg.from_user; + } const timeStr = msg.sent_at || ''; let timeStrOverride = false; @@ -1351,9 +1502,11 @@ }).catch(err => console.error('特效触发失败:', err)); } window.toggleAdminMenu = toggleAdminMenu; + window.toggleBlockMenu = toggleBlockMenu; window.runAdminAction = runAdminAction; window.selectEffect = selectEffect; window.triggerEffect = triggerEffect; + window.toggleBlockedSystemSender = toggleBlockedSystemSender; // ── 字号设置(持久化到 localStorage)───────────────── /** @@ -1391,6 +1544,7 @@ const muted = localStorage.getItem('chat_sound_muted') === '1'; const muteChk = document.getElementById('sound_muted'); if (muteChk) muteChk.checked = muted; + syncBlockedSystemSenderCheckboxes(); }); // ── 特效禁音开关 ───────────────────────────────────────────────── diff --git a/tests/Feature/ChatControllerTest.php b/tests/Feature/ChatControllerTest.php index 95163bc..d9d927f 100644 --- a/tests/Feature/ChatControllerTest.php +++ b/tests/Feature/ChatControllerTest.php @@ -60,6 +60,24 @@ class ChatControllerTest extends TestCase $response->assertDontSee('chatAvatarWidget'); } + /** + * 测试聊天室输入区会渲染系统播报屏蔽按钮与对应选项。 + */ + public function test_room_view_renders_block_system_sender_controls(): void + { + $room = Room::create(['room_name' => 'blockmenu']); + $user = User::factory()->create(); + + $response = $this->actingAs($user)->get(route('chat.room', $room->id)); + + $response->assertOk(); + $response->assertSee('🔕 屏蔽', false); + $response->assertSee('钓鱼播报'); + $response->assertSee('星海小博士'); + $response->assertSee('chat_blocked_system_senders'); + $response->assertSee('toggleBlockedSystemSender'); + } + /** * 测试用户可以发送普通文本消息。 */