diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js index 2c204c5..f52d53b 100644 --- a/resources/js/chat-room.js +++ b/resources/js/chat-room.js @@ -12,6 +12,7 @@ export { bindChatImageUploadControl } from "./chat-room/image-upload.js"; export { bindChatComposerControls } from "./chat-room/composer.js"; export { bindChatToast } from "./chat-room/toast.js"; export { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction } from "./chat-room/friend-panel.js"; +export { bindFriendNotificationControls, setupBannerNotification, setupFriendNotification, showFriendBanner } from "./chat-room/friend-notifications.js"; export { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js"; export { bindMobileDrawerControls, @@ -93,6 +94,7 @@ import { bindChatImageUploadControl } from "./chat-room/image-upload.js"; import { bindChatComposerControls } from "./chat-room/composer.js"; import { bindChatToast } from "./chat-room/toast.js"; import { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction } from "./chat-room/friend-panel.js"; +import { bindFriendNotificationControls, setupBannerNotification, setupFriendNotification, showFriendBanner } from "./chat-room/friend-notifications.js"; import { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js"; import { bindMobileDrawerControls, @@ -182,11 +184,15 @@ if (typeof window !== "undefined") { bindChatComposerControls, bindChatToast, bindFriendPanelControls, + bindFriendNotificationControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction, + setupBannerNotification, + setupFriendNotification, + showFriendBanner, bindMobileDrawerControls, closeMobileDrawer, loadMobileRoomList, @@ -265,6 +271,9 @@ if (typeof window !== "undefined") { window.friendSearch = friendSearch; window.openFriendPanel = openFriendPanel; window.quickFriendAction = quickFriendAction; + window.setupBannerNotification = setupBannerNotification; + window.setupFriendNotification = setupFriendNotification; + window.showFriendBanner = showFriendBanner; window.closeMobileDrawer = closeMobileDrawer; window.loadMobileRoomList = loadMobileRoomList; window.openMobileDrawer = openMobileDrawer; @@ -305,6 +314,7 @@ if (typeof window !== "undefined") { bindChatComposerControls(); bindChatToast(); bindFriendPanelControls(); + bindFriendNotificationControls(); bindToolbarControls(); bindUserTargetActions(); bindAdminMenuControls(); diff --git a/resources/js/chat-room/friend-notifications.js b/resources/js/chat-room/friend-notifications.js new file mode 100644 index 0000000..1c9ab36 --- /dev/null +++ b/resources/js/chat-room/friend-notifications.js @@ -0,0 +1,188 @@ +// 聊天室好友与通用大卡片广播通知监听,集中管理 Echo 订阅和弹窗渲染。 + +import { escapeHtml } from "./html.js"; + +/** + * 读取页面 CSRF Token。 + * + * @returns {string} + */ +function getCsrfToken() { + return document.querySelector('meta[name="csrf-token"]')?.content ?? ""; +} + +/** + * 等待 Echo 与聊天上下文就绪后再订阅频道。 + * + * @param {Function} callback + * @returns {void} + */ +function whenEchoReady(callback) { + if (!window.Echo || !window.chatContext) { + window.setTimeout(() => whenEchoReady(callback), 500); + return; + } + + callback(); +} + +/** + * 同步移除对方好友关系。 + * + * @param {string} username + * @returns {Promise} + */ +async function removeFriendBack(username) { + await fetch(`/friend/${encodeURIComponent(username)}/remove`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "X-CSRF-TOKEN": getCsrfToken(), + "Accept": "application/json", + }, + body: JSON.stringify({ + room_id: window.chatContext?.roomId, + }), + }); +} + +/** + * 显示好友添加居中大卡弹窗。 + * + * @param {string} fromUsername + * @param {boolean} hasAddedBack + * @returns {void} + */ +export function showFriendBanner(fromUsername, hasAddedBack) { + if (!window.chatBanner) { + return; + } + + if (hasAddedBack) { + window.chatBanner.show({ + id: "friend-banner", + icon: "🎉💚🎉", + title: "好友通知", + name: fromUsername, + body: "将你加为好友了!", + sub: "你们现在互为好友 🎊", + gradient: ["#065f46", "#059669", "#10b981"], + titleColor: "#a7f3d0", + autoClose: 5000, + }); + return; + } + + window.chatBanner.show({ + id: "friend-banner", + icon: "💚📩", + title: "好友申请", + name: fromUsername, + body: "将你加为好友了!", + sub: "但你还没有回加对方为好友", + gradient: ["#1e3a5f", "#1d4ed8", "#0891b2"], + titleColor: "#bae6fd", + autoClose: 0, + buttons: [ + { + label: "➕ 回加好友", + color: "#10b981", + onClick: async (button, close) => { + await window.quickFriendAction?.("add", fromUsername, button); + if (button.textContent.startsWith("✅")) { + window.setTimeout(close, 1500); + } + }, + }, + { + label: "稍后再说", + color: "rgba(255,255,255,0.15)", + onClick: (button, close) => close(), + }, + ], + }); +} + +/** + * 订阅好友私有频道通知。 + * + * @returns {void} + */ +export function setupFriendNotification() { + whenEchoReady(() => { + const myId = window.chatContext.userId; + + window.Echo.private(`user.${myId}`) + .listen(".FriendAdded", (event) => { + showFriendBanner(event.from_username, event.has_added_back); + }) + .listen(".FriendRemoved", (event) => { + const fromUsername = String(event.from_username ?? ""); + const safeUsername = escapeHtml(fromUsername); + + if (event.had_added_back) { + window.chatToast?.show?.({ + title: "好友通知", + message: `${safeUsername} 已将你从好友列表移除。
你的好友列表中仍保留对方,可点击同步移除。`, + icon: "👥", + color: "#6b7280", + duration: 10000, + action: { + label: `🗑️ 同步移除 ${fromUsername}`, + onClick: async () => removeFriendBack(fromUsername), + }, + }); + return; + } + + window.chatToast?.show?.({ + title: "好友通知", + message: `${safeUsername} 已将你从他的好友列表移除。`, + icon: "👥", + color: "#9ca3af", + }); + }); + }); +} + +/** + * 订阅通用大卡片通知广播。 + * + * @returns {void} + */ +export function setupBannerNotification() { + whenEchoReady(() => { + const myId = window.chatContext.userId; + const roomId = window.chatContext.roomId; + const showBanner = (event) => { + if (event.options && typeof event.options === "object") { + window.chatBanner?.show?.(event.options); + } + }; + + // 私有频道只推送给指定用户,房间频道推送给当前房间在线用户。 + window.Echo.private(`user.${myId}`).listen(".BannerNotification", showBanner); + + if (roomId) { + window.Echo.join(`room.${roomId}`).listen(".BannerNotification", showBanner); + } + }); +} + +/** + * 绑定好友与大卡片通知监听,并暴露旧全局函数。 + * + * @returns {void} + */ +export function bindFriendNotificationControls() { + if (typeof window === "undefined") { + return; + } + + window.showFriendBanner = showFriendBanner; + window.setupFriendNotification = setupFriendNotification; + window.setupBannerNotification = setupBannerNotification; + + setupFriendNotification(); + setupBannerNotification(); +} diff --git a/resources/views/chat/partials/user-actions.blade.php b/resources/views/chat/partials/user-actions.blade.php index 3541c31..3f1e0d2 100644 --- a/resources/views/chat/partials/user-actions.blade.php +++ b/resources/views/chat/partials/user-actions.blade.php @@ -1361,153 +1361,4 @@ {{-- openRewardModal 已迁移到 resources/js/chat-room/reward-modal.js --}} {{-- ═══════════ 好友系统通知监听 ═══════════ --}} -{{-- 监听好友 WebSocket 事件,与好友操作逻辑集中在同一文件维护 --}} - +{{-- 好友通知与 BannerNotification 监听已迁移到 resources/js/chat-room/friend-notifications.js --}}