Files
chatroom/resources/views/chat/partials/scripts.blade.php
T
2026-04-29 15:06:01 +08:00

302 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:聊天室核心前端交互脚本(Blade 模板形式)
已迁移至 Vite 模块(resources/js/chat-room/)的内容:
1. 消息渲染引擎 message-renderer.jsappendMessage / buildChatMessageContent / 批量渲染 / 裁剪)
2. 在线用户列表 user-list.jsrenderUserList / filterUserList / 徽标轮换)
3. WebSocket 事件监听 chat-events.jschat:here / chat:message / chat:muted / Echo 级监听等)
4. 管理操作 admin-commands.js + admin-menu.js
5. 存点心跳 heartbeat.jssaveExp / leaveRoom / notifyExpiredLeave
6. 发送消息 composer.jssendMessage / 草稿 / IME 防重 / 神秘箱子暗号)
7. 特效控制 admin-commands.js + message-utils.js
8. 系统播报屏蔽 preferences-status.js(屏蔽 / 禁音 / 每日状态 / 偏好)
9. 共享状态 chat-state.jsDOM 引用 / 在线用户 / 消息队列 / 所有可变状态)
10. 欢迎语菜单 welcome-menu.js
11. 每日签到 daily-sign-in.js
12. VIP 进退场 vip-presence.js
保留在 Blade 内的内容(依赖 Blade 模板语法 作为薄兼容桥):
1. switchTab / loadRoomsOnlineStatus(依赖 {{ route('chat.rooms-online-status') }}
2. sendWelcomeTpl(依赖 sendMessage 已迁至 Vite
3. 点击空白关闭浮层(简单 DOM 事件)
4. 自动滚屏复选框绑定
5. DOMContentLoaded 偏好恢复(调用 Vite 模块函数)
6. 各类 window.* 兼容桥声明
通过 @include('chat.partials.scripts') 引入到 frame.blade.php
@author ChatRoom Laravel
@version 3.0.0 大量逻辑已迁至 Vite 模块,此文件仅保留 Blade 依赖的薄包装
--}}
<script>
/**
* 聊天室前端交互逻辑(薄包装版本)
* 核心引擎已迁至 Vite 模块 resources/js/chat-room/,此脚本仅保留 Blade 模板依赖的桥接代码。
*/
// ── Blade 依赖变量(需要模板语法生成)──
const _currentRoomId = {{ $room->id }};
// ── Tab 切换 ──────────────────────────────────────
let _roomsRefreshTimer = null;
let _roomsOnlineStatusCache = null;
let _roomsOnlineStatusCacheAt = 0;
const ROOMS_ONLINE_STATUS_CACHE_TTL = 10000;
/**
* 切换右侧面板 Tab(用户列表 / 房间列表)。
* @param {string} tab 'users' | 'rooms'
*/
function switchTab(tab) {
['users', 'rooms'].forEach(t => {
document.getElementById('panel-' + t).style.display = t === tab ? 'block' : 'none';
document.getElementById('tab-' + t)?.classList.toggle('active', t === tab);
});
if (tab === 'rooms') {
loadRoomsOnlineStatus();
clearInterval(_roomsRefreshTimer);
_roomsRefreshTimer = setInterval(() => loadRoomsOnlineStatus(true), 30000);
} else {
clearInterval(_roomsRefreshTimer);
_roomsRefreshTimer = null;
}
}
window.switchTab = switchTab;
/**
* 拉取所有房间在线人数并渲染到右侧面板。
* @param {boolean} forceRefresh
*/
function loadRoomsOnlineStatus(forceRefresh = false) {
const container = document.getElementById('rooms-online-list');
if (!container) return;
if (!forceRefresh && _roomsOnlineStatusCache && Date.now() - _roomsOnlineStatusCacheAt < ROOMS_ONLINE_STATUS_CACHE_TTL) {
renderRoomsOnlineStatus(_roomsOnlineStatusCache, container);
return;
}
fetch('{{ route('chat.rooms-online-status') }}')
.then(r => r.json())
.then(data => {
_roomsOnlineStatusCache = data;
_roomsOnlineStatusCacheAt = Date.now();
renderRoomsOnlineStatus(data, container);
})
.catch(() => {
container.innerHTML = '<div style="text-align:center;color:#f00;padding:10px;font-size:11px;">加载失败</div>';
});
}
/**
* 渲染房间在线状态列表(优先使用 Vite 模块)。
*/
function renderRoomsOnlineStatus(data, container) {
if (window.ChatRoomTools?.renderRoomsOnlineStatusToContainer) {
window.ChatRoomTools.renderRoomsOnlineStatusToContainer(data, container, {
currentRoomId: _currentRoomId,
variant: 'desktop',
});
return;
}
container.innerHTML = '<div style="text-align:center;color:#bbb;padding:16px 0;font-size:11px;">暂无房间</div>';
}
// ── 发送欢迎语模板 ──────────────────────────────────
/**
* 将选中的欢迎语模板填入输入框并自动发送。
* @param {string} tpl 欢迎语模板,含 {name} 占位符
*/
function sendWelcomeTpl(tpl) {
const toUser = document.getElementById('to_user')?.value || '大家';
if (toUser === '大家') {
window.chatDialog?.alert('欢迎语不能对「大家」发送,请先选择具体用户。', '🚫 提示', '#cc4444');
return;
}
const name = toUser;
const prefix = window.chatContext?.welcomePrefix || window.chatContext?.username || '';
const body = tpl.replace(/\{name\}/g, name);
const msg = `${prefix}${body}`;
const input = document.getElementById('content');
if (input) input.value = msg;
const menu = document.getElementById('welcome-menu');
if (menu) menu.style.display = 'none';
const actionSel = document.getElementById('action');
const prevAction = actionSel?.value || '';
if (actionSel) actionSel.value = '欢迎';
// sendMessage 已由 composer.jsVite)挂载到 window
window.sendMessage?.(null)?.finally(() => {
if (actionSel) actionSel.value = prevAction;
});
}
window.sendWelcomeTpl = sendWelcomeTpl;
// ── 点击空白关闭浮层 ──────────────────────────────
document.addEventListener('click', function(event) {
const clickedInsideFloatingMenu = event.target instanceof Element
&& event.target.closest('[data-chat-welcome-menu], [data-chat-admin-menu], [data-chat-block-menu], [data-chat-feature-menu], [data-chat-feature-menu-toggle], [data-chat-block-menu-toggle], [data-chat-welcome-menu-toggle], [data-chat-admin-menu-toggle]');
if (clickedInsideFloatingMenu) return;
['welcome-menu', 'admin-menu', 'block-menu', 'feature-menu', 'daily-status-editor-overlay'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.display = 'none';
});
});
// ── 自动滚屏复选框绑定 ──────────────────────────────
const autoScrollEl = document.getElementById('auto_scroll');
if (autoScrollEl) {
autoScrollEl.addEventListener('change', function() {
window.setChatAutoScrollEnabled?.(this.checked);
});
}
// ── 页面加载后从 localStorage 恢复偏好 ──────────────────
document.addEventListener('DOMContentLoaded', () => {
window.ChatRoomTools?.restoreChatFontSize?.();
const CHAT_SOUND_MUTED_KEY = window.ChatRoomTools?.CHAT_SOUND_MUTED_STORAGE_KEY || 'chat_sound_muted';
const BLOCKED_SENDERS_KEY = window.ChatRoomTools?.BLOCKED_SYSTEM_SENDERS_STORAGE_KEY || 'chat_blocked_system_senders';
const BLOCKABLE_SENDERS = window.ChatRoomTools?.BLOCKABLE_SYSTEM_SENDERS || ['钓鱼播报', '猜成语', '星海小博士', '百家乐', '跑马', '神秘箱子', '五子棋', '老虎机', '双色球彩票'];
const initialChatPreferences = window.ChatRoomTools?.normalizeChatPreferences
? window.ChatRoomTools.normalizeChatPreferences(window.chatContext?.chatPreferences || {}, BLOCKABLE_SENDERS)
: { blocked_system_senders: [], sound_muted: false };
let storedBlockedSenders = [];
try {
const saved = JSON.parse(localStorage.getItem(BLOCKED_SENDERS_KEY) || '[]');
storedBlockedSenders = Array.isArray(saved)
? saved.filter(s => BLOCKABLE_SENDERS.includes(s))
: [];
} catch (_) { /* ignore */ }
const mutedFromLocal = localStorage.getItem(CHAT_SOUND_MUTED_KEY) === '1';
const shouldMigrate = window.ChatRoomTools?.shouldMigrateLocalChatPreferences
? window.ChatRoomTools.shouldMigrateLocalChatPreferences(initialChatPreferences, storedBlockedSenders, mutedFromLocal)
: !(initialChatPreferences.blocked_system_senders.length > 0 || initialChatPreferences.sound_muted)
&& (storedBlockedSenders.length > 0 || mutedFromLocal);
// 恢复屏蔽发送者
if (shouldMigrate && storedBlockedSenders.length > 0) {
window.chatState.blockedSystemSenders = new Set(storedBlockedSenders);
} else if (initialChatPreferences.blocked_system_senders.length > 0) {
window.chatState.blockedSystemSenders = new Set(initialChatPreferences.blocked_system_senders);
}
// 恢复后立即隐藏已有消息
if (window.chatState.blockedSystemSenders.size > 0) {
const setRendered = window.setRenderedMessagesVisibilityBySender;
if (typeof setRendered === 'function') {
window.chatState.blockedSystemSenders.forEach(function (sender) {
setRendered(sender, true);
});
}
}
// 恢复禁音状态
const muted = shouldMigrate ? mutedFromLocal : initialChatPreferences.sound_muted;
if (window.ChatRoomTools?.setSoundMuted) {
window.ChatRoomTools.setSoundMuted(muted);
} else {
const muteChk = document.getElementById('sound_muted');
if (muteChk) muteChk.checked = muted;
}
// 同步屏蔽复选框
if (typeof window.syncBlockedSystemSenderCheckboxes === 'function') {
window.syncBlockedSystemSenderCheckboxes();
}
if (shouldMigrate && typeof window.saveChatPreferences === 'function') {
void window.saveChatPreferences();
} else if (typeof window.persistChatPreferencesToLocal === 'function') {
window.persistChatPreferencesToLocal();
}
});
// ── window.* 兼容桥声明 ──────────────────────────────
// 以下函数已全部迁至 Vite 模块,这里仅做 window 引用声明,
// 确保 Blade 内仍可直接调用这些函数名(无需 window. 前缀)。
// 共享状态桥接:将 Vite 的 chatState 暴露为 Blade 兼容的本地引用
const chatState = window.chatState || {};
// 这些函数由 message-renderer.js 挂载到 window
const appendMessage = (...args) => window.appendMessage(...args);
const buildChatMessageContent = (...args) => window.buildChatMessageContent(...args);
const pruneMessageContainer = (...args) => window.pruneMessageContainer(...args);
const enqueueChatMessage = (...args) => window.enqueueChatMessage(...args);
const flushQueuedChatMessages = (...args) => window.flushQueuedChatMessages(...args);
const createChatMessageRenderBatch = (...args) => window.createChatMessageRenderBatch(...args);
const commitChatMessageRenderBatch = (...args) => window.commitChatMessageRenderBatch(...args);
// 这些函数由 user-list.js 挂载到 window
const renderUserList = (...args) => window.renderUserList(...args);
const scheduleRenderUserList = (...args) => window.scheduleRenderUserList(...args);
const filterUserList = (...args) => window.filterUserList(...args);
const scheduleFilterUserList = (...args) => window.scheduleFilterUserList(...args);
const refreshRenderedUserBadges = (...args) => window.refreshRenderedUserBadges(...args);
// 这些函数由 composer.js 挂载到 window
const sendMessage = (...args) => window.sendMessage(...args);
const collectChatComposerState = (...args) => window.collectChatComposerState(...args);
const buildChatMessageFormData = (...args) => window.buildChatMessageFormData(...args);
const handleChatImageSelected = (...args) => window.handleChatImageSelected(...args);
const clearSelectedChatImage = (...args) => window.clearSelectedChatImage(...args);
// 这些函数由 heartbeat.js 挂载到 window
const saveExp = (...args) => window.saveExp(...args);
const leaveRoom = (...args) => window.leaveRoom(...args);
const notifyExpiredLeave = (...args) => window.notifyExpiredLeave(...args);
// 以下函数由 preferences-status.js / message-utils.js / admin-commands.js 挂载
const toggleBlockMenu = (...args) => window.toggleBlockMenu?.(...args);
const toggleFeatureMenu = (...args) => window.toggleFeatureMenu?.(...args);
const closeFeatureMenu = (...args) => window.closeFeatureMenu?.(...args);
const openDailyStatusEditor = (...args) => window.openDailyStatusEditor?.(...args);
const closeDailyStatusEditor = (...args) => window.closeDailyStatusEditor?.(...args);
const toggleBlockedSystemSender = (...args) => window.toggleBlockedSystemSender?.(...args);
const updateDailyStatus = (...args) => window.updateDailyStatus?.(...args);
const clearDailyStatus = (...args) => window.clearDailyStatus?.(...args);
const handleFeatureLocalClear = (...args) => window.handleFeatureLocalClear?.(...args);
const normalizeChatPreferences = (...args) => window.ChatRoomTools?.normalizeChatPreferences?.(...args);
const parseDailyStatusExpiry = (...args) => window.ChatRoomTools?.parseDailyStatusExpiry?.(...args);
const normalizeDailyStatus = (...args) => window.ChatRoomTools?.normalizeDailyStatus?.(...args);
const syncDailyStatusUi = (...args) => window.syncDailyStatusUi?.() || window.ChatRoomTools?.syncDailyStatusUi?.();
const isSoundMuted = (...args) => window.ChatRoomTools?.isSoundMuted?.(...args) || false;
const toggleSoundMute = (...args) => window.toggleSoundMute?.(...args) || window.ChatRoomTools?.toggleSoundMute?.(...args);
const toggleAutoScroll = (...args) => window.ChatRoomTools?.toggleAutoScroll?.(...args);
const localClearScreen = (...args) => window.localClearScreen?.(...args);
const scrollToBottom = (...args) => window.ChatRoomTools?.scrollChatToBottom?.(...args);
// ── 挂载供其他 Blade partials 引用的函数 ────────────
window.toggleBlockMenu = toggleBlockMenu;
window.toggleFeatureMenu = toggleFeatureMenu;
window.closeFeatureMenu = closeFeatureMenu;
window.openDailyStatusEditor = openDailyStatusEditor;
window.closeDailyStatusEditor = closeDailyStatusEditor;
window.toggleBlockedSystemSender = toggleBlockedSystemSender;
window.updateDailyStatus = updateDailyStatus;
window.clearDailyStatus = clearDailyStatus;
window.handleFeatureLocalClear = handleFeatureLocalClear;
window.toggleSoundMute = toggleSoundMute;
// 同步每日状态 UI
if (typeof window.syncDailyStatusUi === 'function') {
window.syncDailyStatusUi();
}
// 绑定音效禁音控件
window.ChatRoomTools?.bindSoundMuteControl?.(() => window.saveChatPreferences?.());
</script>