Files
chatroom/resources/views/chat/partials/scripts.blade.php
T

302 lines
15 KiB
PHP
Raw Normal View History

{{--
文件功能:聊天室核心前端交互脚本(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);
2026-04-14 22:48:29 +08:00
} else {
clearInterval(_roomsRefreshTimer);
_roomsRefreshTimer = null;
2026-04-19 12:14:10 +08:00
}
}
window.switchTab = switchTab;
2026-04-19 12:14:10 +08:00
/**
* 拉取所有房间在线人数并渲染到右侧面板。
* @param {boolean} forceRefresh
2026-04-19 12:14:10 +08:00
*/
function loadRoomsOnlineStatus(forceRefresh = false) {
const container = document.getElementById('rooms-online-list');
if (!container) return;
2026-04-12 14:04:18 +08:00
if (!forceRefresh && _roomsOnlineStatusCache && Date.now() - _roomsOnlineStatusCacheAt < ROOMS_ONLINE_STATUS_CACHE_TTL) {
renderRoomsOnlineStatus(_roomsOnlineStatusCache, container);
2026-04-12 14:04:18 +08:00
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>';
});
2026-04-19 12:14:10 +08:00
}
/**
* 渲染房间在线状态列表(优先使用 Vite 模块)。
2026-04-19 12:14:10 +08:00
*/
function renderRoomsOnlineStatus(data, container) {
if (window.ChatRoomTools?.renderRoomsOnlineStatusToContainer) {
window.ChatRoomTools.renderRoomsOnlineStatusToContainer(data, container, {
currentRoomId: _currentRoomId,
variant: 'desktop',
});
2026-04-19 12:14:10 +08:00
return;
}
container.innerHTML = '<div style="text-align:center;color:#bbb;padding:16px 0;font-size:11px;">暂无房间</div>';
2026-04-19 12:14:10 +08:00
}
// ── 发送欢迎语模板 ──────────────────────────────────
/**
* 将选中的欢迎语模板填入输入框并自动发送。
* @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;
2026-04-19 12:14:10 +08:00
});
}
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';
2026-04-29 11:35:14 +08:00
const BLOCKABLE_SENDERS = window.ChatRoomTools?.BLOCKABLE_SYSTEM_SENDERS || ['钓鱼播报', '猜成语', '星海小博士', '百家乐', '跑马', '神秘箱子'];
2026-04-25 19:38:58 +08:00
const initialChatPreferences = window.ChatRoomTools?.normalizeChatPreferences
? window.ChatRoomTools.normalizeChatPreferences(window.chatContext?.chatPreferences || {}, BLOCKABLE_SENDERS)
: { blocked_system_senders: [], sound_muted: false };
2026-04-24 21:17:44 +08:00
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 */ }
2026-04-25 19:45:15 +08:00
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>