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 = '';
- }
- };