新增 屏蔽消息功能

This commit is contained in:
2026-04-14 22:25:16 +08:00
parent b76b6559ea
commit 0183de66dd
3 changed files with 197 additions and 1 deletions
@@ -111,7 +111,31 @@ $welcomeMessages = [
style="font-size: 11px; padding: 1px 6px; background: #0f766e; color: #fff; border: none; border-radius: 2px; cursor: pointer;">
📷 图片
</button>
<div style="position:relative;display:inline-block;" id="block-btn-wrap">
<button type="button" onclick="toggleBlockMenu(event)"
style="font-size:11px;padding:1px 6px;background:#475569;color:#fff;border:none;border-radius:2px;cursor:pointer;">
🔕 屏蔽
</button>
<div id="block-menu"
onclick="event.stopPropagation()"
style="display:none;position:absolute;bottom:calc(100% + 6px);left:0;z-index:10020;min-width:168px;padding:10px;background:#f8fafc;border:1px solid #cbd5e1;border-radius:10px;box-shadow:0 10px 24px rgba(15,23,42,.18);">
<div style="font-size:10px;color:#475569;padding:0 2px 8px;">选择后立即隐藏对应播报</div>
<label
style="display:flex;align-items:center;gap:6px;font-size:12px;color:#1e293b;cursor:pointer;padding:4px 2px;">
<input type="checkbox" id="block-sender-fishing"
onchange="toggleBlockedSystemSender('钓鱼播报', this.checked)">
钓鱼播报
</label>
<label
style="display:flex;align-items:center;gap:6px;font-size:12px;color:#1e293b;cursor:pointer;padding:4px 2px;">
<input type="checkbox" id="block-sender-doctor"
onchange="toggleBlockedSystemSender('星海小博士', this.checked)">
星海小博士
</label>
</div>
</div>
@if (
$user->user_level >= (int) \App\Models\Sysparam::getValue('level_announcement', '10') ||
$room->master == $user->username ||
@@ -10,6 +10,7 @@
6. 钓鱼小游戏(startFishing / reelFish / autoFish
7. 发送消息(sendMessageIME 防重触发)
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();
});
// ── 特效禁音开关 ─────────────────────────────────────────────────
+18
View File
@@ -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');
}
/**
* 测试用户可以发送普通文本消息。
*/