diff --git a/app/Http/Controllers/FriendController.php b/app/Http/Controllers/FriendController.php index 4ad6bf5..79558bf 100644 --- a/app/Http/Controllers/FriendController.php +++ b/app/Http/Controllers/FriendController.php @@ -267,19 +267,20 @@ class FriendController extends Controller return; } - // 根据操作类型和互相状态生成不同文案(含内联快捷操作链接) + // 根据操作类型和互相状态生成不同文案(含前端代理快捷操作链接) $btnStyle = 'font-weight:bold;text-decoration:underline;margin-left:6px;'; - $btnAdd = "➕ 回加好友"; - $btnRemove = "🗑️ 同步移除"; + $safeUsername = e($fromUsername); + $btnAdd = "➕ 回加好友"; + $btnRemove = "🗑️ 同步移除"; $content = match ($action) { 'added' => $mutual - ? "💚 {$fromUsername} 将你加为好友了!你们现在互为好友 🎉" - : "💚 {$fromUsername} 将你加为好友了!但你还没有添加对方为好友。{$btnAdd}", + ? "💚 {$safeUsername} 将你加为好友了!你们现在互为好友 🎉" + : "💚 {$safeUsername} 将你加为好友了!但你还没有添加对方为好友。{$btnAdd}", 'removed' => $mutual - ? "💔 {$fromUsername} 已将你从好友列表移除。你的好友列表中仍保留对方。{$btnRemove}" - : "💔 {$fromUsername} 已将你从他的好友列表移除。", - 'online' => "🟢 你的好友 {$fromUsername} 上线啦!", + ? "💔 {$safeUsername} 已将你从好友列表移除。你的好友列表中仍保留对方。{$btnRemove}" + : "💔 {$safeUsername} 已将你从他的好友列表移除。", + 'online' => "🟢 你的好友 {$safeUsername} 上线啦!", default => '', }; diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js index d2592f3..b503a71 100644 --- a/resources/js/chat-room.js +++ b/resources/js/chat-room.js @@ -6,7 +6,7 @@ export { bindGlobalDialogControls } from "./chat-room/dialog.js"; export { bindDailySignInControls } from "./chat-room/daily-sign-in.js"; export { applyFontSize, bindChatFontSizeControl, CHAT_FONT_SIZE_STORAGE_KEY, restoreChatFontSize } from "./chat-room/font-size.js"; export { bindChatImageUploadControl } from "./chat-room/image-upload.js"; -export { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel } from "./chat-room/friend-panel.js"; +export { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction } from "./chat-room/friend-panel.js"; export { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js"; export { bindMobileDrawerControls, @@ -75,7 +75,7 @@ import { bindGlobalDialogControls } from "./chat-room/dialog.js"; import { bindDailySignInControls } from "./chat-room/daily-sign-in.js"; import { applyFontSize, bindChatFontSizeControl, CHAT_FONT_SIZE_STORAGE_KEY, restoreChatFontSize } from "./chat-room/font-size.js"; import { bindChatImageUploadControl } from "./chat-room/image-upload.js"; -import { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel } from "./chat-room/friend-panel.js"; +import { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction } from "./chat-room/friend-panel.js"; import { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js"; import { bindMobileDrawerControls, @@ -154,6 +154,7 @@ if (typeof window !== "undefined") { friendSearch, loadFriends, openFriendPanel, + quickFriendAction, bindMobileDrawerControls, closeMobileDrawer, loadMobileRoomList, @@ -220,6 +221,7 @@ if (typeof window !== "undefined") { window.closeFriendPanel = closeFriendPanel; window.friendSearch = friendSearch; window.openFriendPanel = openFriendPanel; + window.quickFriendAction = quickFriendAction; window.closeMobileDrawer = closeMobileDrawer; window.loadMobileRoomList = loadMobileRoomList; window.openMobileDrawer = openMobileDrawer; diff --git a/resources/js/chat-room/friend-panel.js b/resources/js/chat-room/friend-panel.js index 26c65c4..6a2ce39 100644 --- a/resources/js/chat-room/friend-panel.js +++ b/resources/js/chat-room/friend-panel.js @@ -286,6 +286,59 @@ async function friendAction(action, username, button) { } } +/** + * 聊天消息和横幅内的快捷好友操作。 + * + * @param {"add"|"remove"|string} action 操作类型 + * @param {string} username 目标用户名 + * @param {HTMLElement} element 触发元素 + * @returns {Promise} + */ +export async function quickFriendAction(action, username, element) { + if (!["add", "remove"].includes(action) || !username || !(element instanceof HTMLElement)) { + return; + } + + if (element.dataset.done) { + return; + } + + element.dataset.done = "1"; + element.textContent = "处理中…"; + element.style.pointerEvents = "none"; + + try { + // 消息内链接来自后端 HTML,用户名进入 path 前仍必须编码。 + const response = await fetch(`/friend/${encodeURIComponent(username)}/${action}`, { + method: action === "add" ? "POST" : "DELETE", + headers: { + "Content-Type": "application/json", + "X-CSRF-TOKEN": csrf(), + Accept: "application/json", + }, + body: JSON.stringify({ + room_id: roomId(), + }), + }); + const data = await response.json(); + + if (data.status === "success") { + element.textContent = action === "add" ? "✅ 已回加" : "✅ 已移除"; + element.style.color = "#16a34a"; + element.style.textDecoration = "none"; + return; + } + + element.textContent = `❌ ${data.message || "操作失败"}`; + element.style.color = "#cc4444"; + } catch (error) { + element.textContent = "❌ 网络错误"; + element.style.color = "#cc4444"; + delete element.dataset.done; + element.style.pointerEvents = ""; + } +} + /** * 通过搜索框按用户名添加好友,具体校验仍交给后端。 * @@ -370,6 +423,18 @@ export function bindFriendPanelControls() { return; } + const quickAction = event.target.closest("[data-quick-friend-action]"); + if (quickAction) { + event.preventDefault(); + // 后端系统消息只输出 data 属性,具体请求仍统一走模块方法。 + void quickFriendAction( + quickAction.getAttribute("data-quick-friend-action") || "", + quickAction.getAttribute("data-quick-friend-username") || "", + quickAction, + ); + return; + } + const panel = event.target.closest("#friend-panel"); // 只在点击遮罩本身时关闭,避免点击内容区误关。 if (panel && event.target === panel) { diff --git a/resources/views/chat/partials/user-actions.blade.php b/resources/views/chat/partials/user-actions.blade.php index c527837..06aa7c6 100644 --- a/resources/views/chat/partials/user-actions.blade.php +++ b/resources/views/chat/partials/user-actions.blade.php @@ -1563,7 +1563,7 @@ label: '➕ 回加好友', color: '#10b981', onClick: async (btn, close) => { - await quickFriendAction('add', fromUsername, btn); + await window.quickFriendAction?.('add', fromUsername, btn); if (btn.textContent.startsWith('✅')) { setTimeout(close, 1500); } @@ -1579,52 +1579,4 @@ } } - /** - * 聊天区悄悄话内嵌链接的快捷好友操作。 - * 由后端生成的 onclick="quickFriendAction('add'/'remove', username, this)" 调用。 - * - * @param {string} act 'add' | 'remove' - * @param {string} username 目标用户名 - * @param {HTMLElement} el 被点击的 元素,用于更新显示状态 - */ - window.quickFriendAction = async function (act, username, el) { - if (el.dataset.done) { - return; - } - el.dataset.done = '1'; - - el.textContent = '处理中…'; - el.style.pointerEvents = 'none'; - - try { - const method = act === 'add' ? 'POST' : 'DELETE'; - const url = `/friend/${encodeURIComponent(username)}/${act === 'add' ? 'add' : 'remove'}`; - const csrf = document.querySelector('meta[name="csrf-token"]')?.content ?? ''; - const res = await fetch(url, { - method, - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': csrf, - 'Accept': 'application/json', - }, - body: JSON.stringify({ - room_id: window.chatContext?.roomId - }), - }); - const data = await res.json(); - if (data.status === 'success') { - el.textContent = act === 'add' ? '✅ 已回加' : '✅ 已移除'; - el.style.color = '#16a34a'; - el.style.textDecoration = 'none'; - } else { - el.textContent = '❌ ' + (data.message || '操作失败'); - el.style.color = '#cc4444'; - } - } catch (e) { - el.textContent = '❌ 网络错误'; - el.style.color = '#cc4444'; - delete el.dataset.done; - el.style.pointerEvents = ''; - } - };