From 3d7b86f06dd17328e5f8c6660e70de138e674e20 Mon Sep 17 00:00:00 2001 From: lkddi Date: Sun, 1 Mar 2026 12:15:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9A=E5=A5=96=E5=8A=B1?= =?UTF-8?q?=E5=8F=91=E6=94=BE=E8=81=8A=E5=A4=A9=E5=AE=A4=E5=85=AC=E5=91=8A?= =?UTF-8?q?=20+=20=E5=8F=B3=E4=B8=8B=E8=A7=92=20Toast=20=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E5=8D=A1=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端(AdminCommandController::reward): - 新增聊天室公开公告消息(系统公告,所有在场用户可见) - 接收者私信附带 toast_notification 字段触发前端小卡片 - 公告文案:「🪙 [职务人] 向 [目标] 发放了 [N] 枚奖励金币!」 前端: - 新建 chat/partials/toast-notification.blade.php: 全局右下角 Toast 组件,window.chatToast.show() API 支持 title/message/icon/color/duration/action 配置 多条 Toast 从右下角向上堆叠,独立计时、独立关闭 - chat:message 事件监听中检测 toast_notification 字段, 自动弹出右下角通知卡片(仅接收方可见) - showFriendToast 迁移至 window.chatToast.show(), 删除 80 行旧实现,代码量净减 - frame.blade.php 引入新 partial DEVELOPMENT.md: - 新增 §7.9 chatToast 完整文档(API、使用场景、迁移说明) - 原 chatBanner 章节编号改为 §7.10 --- DEVELOPMENT.md | 66 ++++++++- .../Controllers/AdminCommandController.php | 29 +++- resources/views/chat/frame.blade.php | 2 +- .../views/chat/partials/scripts.blade.php | 136 ++++++----------- .../partials/toast-notification.blade.php | 138 ++++++++++++++++++ 5 files changed, 275 insertions(+), 96 deletions(-) create mode 100644 resources/views/chat/partials/toast-notification.blade.php diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 9c35322..5aa0d07 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -711,7 +711,71 @@ if (ok) { --- -### 7.9 全局大卡片通知 `window.chatBanner` ⭐ +### 7.9 全局右下角 Toast 通知卡片 `window.chatToast` ⭐ + +> [!NOTE] +> `chatToast` 是固定在**右下角**的轻量通知卡片,适用于实时事件通知(奖励到账、好友动态等)。 +> 与 `chatDialog` 不同,它不阻断操作流程,自动消失,可堆叠多条。 + +#### 文件位置 + +``` +resources/views/chat/partials/toast-notification.blade.php ← Toast 组件 HTML + JS +resources/views/chat/frame.blade.php ← 已 @include,全页面可用 +``` + +#### API 说明 + +```javascript +window.chatToast.show({ + title: "标题文字", // 必填 + message: "正文内容", // 必填,支持 HTML + icon: "🪙", // 可选,左侧 Emoji,默认 💬 + color: "#f59e0b", // 可选,强调色,默认 #336699 + duration: 6000, // 可选,自动消失毫秒,0 = 不自动消失,默认 6000 + action: { + // 可选,操作按钮 + label: "操作文字", + onClick: () => { + /* ... */ + }, + }, +}); +``` + +#### 通过消息字段自动触发(后端控制) + +后端 broadcast 的消息中包含 `toast_notification` 字段,且接收方是当前用户时,前端脚本会自动弹出 Toast: + +```php +// AdminCommandController::reward() 示例 +$msg['toast_notification'] = [ + 'title' => '🪙 奖励金币到账', + 'message' => "{$admin->username} 向你发放了 {$amount} 枚金币!", + 'icon' => '🪙', + 'color' => '#f59e0b', + 'duration' => 8000, +]; +``` + +#### 使用场景 + +| 场景 | 颜色 | 图标 | +| ---------------- | --------------- | ---- | +| 奖励金币到账 | `#f59e0b`(橙) | 🪙 | +| 好友动态通知 | `#6b7280`(灰) | 👥 | +| 礼物收到 | `#e11d48`(玫) | 🎁 | +| 系统提示(普通) | `#336699`(蓝) | 💬 | +| 等级晋升 | `#7c3aed`(紫) | 🌟 | + +#### 原 `showFriendToast` 迁移说明 + +旧函数 `showFriendToast()` 已被 `window.chatToast.show()` 替代,好友删除通知已改用新 API。 +新增功能只需调用 `window.chatToast.show()`,**勿新增** `showFriendToast` 调用。 + +--- + +### 7.10 全局大卡片通知 `window.chatBanner` ⭐ > [!NOTE] > `chatBanner` 是居中弹出的沉浸式大卡片通知组件,适用于好友通知、任命公告、系统广播等重要事件。与 `chatDialog` 不同,它**不阻断操作流程**,支持自动消失和自定义按钮。 diff --git a/app/Http/Controllers/AdminCommandController.php b/app/Http/Controllers/AdminCommandController.php index a38309a..7bf4d28 100644 --- a/app/Http/Controllers/AdminCommandController.php +++ b/app/Http/Controllers/AdminCommandController.php @@ -545,17 +545,42 @@ class AdminCommandController extends Controller 'remark' => "发放奖励金币 {$amount} 枚给 {$targetUsername}", ]); - // 向聊天室发送悄悄话通知接收者 + // ① 聊天室公开公告(所有在场用户可见) + $publicMsg = [ + 'id' => $this->chatState->nextMessageId($roomId), + 'room_id' => $roomId, + 'from_user' => '系统公告', + 'to_user' => '', + 'content' => "🪙 {$admin->username}({$positionName})向 {$targetUsername} 发放了 {$amount} 枚奖励金币!", + 'is_secret' => false, + 'font_color' => '#d97706', + 'action' => '', + 'sent_at' => now()->toDateTimeString(), + ]; + $this->chatState->pushMessage($roomId, $publicMsg); + broadcast(new MessageSent($roomId, $publicMsg)); + SaveMessageJob::dispatch($publicMsg); + + // ② 接收者私信(含 toast_notification 触发右下角小卡片) + $freshJjb = $target->fresh()->jjb; $msg = [ 'id' => $this->chatState->nextMessageId($roomId), 'room_id' => $roomId, 'from_user' => '系统', 'to_user' => $targetUsername, - 'content' => "🎁 {$admin->username}({$position->name})向你发放了 {$amount} 枚金币奖励!当前金币:{$target->fresh()->jjb} 枚。", + 'content' => "🎁 {$admin->username}({$positionName})向你发放了 {$amount} 枚金币奖励!当前金币:{$freshJjb} 枚。", 'is_secret' => true, 'font_color' => '#d97706', 'action' => '', 'sent_at' => now()->toDateTimeString(), + // 前端 toast-notification 组件识别此字段,弹出右下角通知卡片 + 'toast_notification' => [ + 'title' => '🪙 奖励金币到账', + 'message' => "{$admin->username}({$positionName})向你发放了 {$amount} 枚金币!", + 'icon' => '🪙', + 'color' => '#f59e0b', + 'duration' => 8000, + ], ]; $this->chatState->pushMessage($roomId, $msg); broadcast(new MessageSent($roomId, $msg)); diff --git a/resources/views/chat/frame.blade.php b/resources/views/chat/frame.blade.php index 818a30e..4b3db9b 100644 --- a/resources/views/chat/frame.blade.php +++ b/resources/views/chat/frame.blade.php @@ -110,7 +110,7 @@ {{-- ═══════════ 全局自定义弹窗(替代原生 alert/confirm,全页面可用) ═══════════ --}} @include('chat.partials.global-dialog') - + @include('chat.partials.toast-notification') {{-- ═══════════ 聊天室交互脚本(独立文件维护) ═══════════ --}} @include('chat.partials.user-actions') diff --git a/resources/views/chat/partials/scripts.blade.php b/resources/views/chat/partials/scripts.blade.php index bc70c0f..617d58d 100644 --- a/resources/views/chat/partials/scripts.blade.php +++ b/resources/views/chat/partials/scripts.blade.php @@ -494,6 +494,17 @@ return; } appendMessage(msg); + // 若消息携带 toast_notification 字段且当前用户是接收者,弹右下角小卡片 + if (msg.toast_notification && msg.to_user === window.chatContext.username) { + const t = msg.toast_notification; + window.chatToast.show({ + title: t.title || '通知', + message: t.message || '', + icon: t.icon || '💬', + color: t.color || '#336699', + duration: t.duration ?? 8000, + }); + } }); window.addEventListener('chat:kicked', (e) => { @@ -673,20 +684,39 @@ .listen('.FriendRemoved', (e) => { if (e.had_added_back) { // 之前是互相好友,现在对方删除了我 → 提示可以同步删除 - showFriendToast( - `� ${e.from_username} 已将你从好友列表移除。
- 你的好友列表中仍保留对方,可点击同步移除。`, - '#6b7280', { + window.chatToast.show({ + title: '好友通知', + message: `${e.from_username} 已将你从好友列表移除。
你的好友列表中仍保留对方,可点击同步移除。`, + icon: '👥', + color: '#6b7280', + duration: 10000, + action: { label: `🗑️ 同步移除 ${e.from_username}`, - username: e.from_username, - action: 'remove' - } - ); + onClick: async () => { + const url = `/friend/${encodeURIComponent(e.from_username)}/remove`; + const csrf = document.querySelector('meta[name="csrf-token"]') + ?.content ?? ''; + await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': csrf, + 'Accept': 'application/json' + }, + body: JSON.stringify({ + room_id: window.chatContext?.roomId + }), + }); + } + }, + }); } else { - showFriendToast( - `� ${e.from_username} 已将你从他的好友列表移除。`, - '#9ca3af' - ); + window.chatToast.show({ + title: '好友通知', + message: `${e.from_username} 已将你从他的好友列表移除。`, + icon: '👥', + color: '#9ca3af', + }); } }); } @@ -1002,86 +1032,8 @@ } }; - function showFriendToast(html, color = '#16a34a', action = null) { - const toast = document.createElement('div'); - toast.style.cssText = ` - position: fixed; bottom: 24px; right: 24px; z-index: 999999; - background: #fff; border-left: 4px solid ${color}; - border-radius: 8px; padding: 14px 18px; min-width: 260px; max-width: 320px; - box-shadow: 0 8px 32px rgba(0,0,0,.18); - font-size: 13px; color: #374151; line-height: 1.6; - animation: fdSlideIn .3s ease; - `; - - // 操作按钮 HTML - let actionHtml = ''; - if (action) { - actionHtml = ` -
- -
`; - } - - toast.innerHTML = ` -
-
-
💬 好友通知
-
${html}
- ${actionHtml} -
- -
- `; - - document.body.appendChild(toast); - - // 绑定操作按钮事件 - if (action) { - const btn = toast.querySelector('button[id^="friend-toast-btn"]'); - if (btn) { - btn.addEventListener('click', async () => { - btn.disabled = true; - btn.textContent = '处理中…'; - try { - const method = action.action === 'add' ? 'POST' : 'DELETE'; - const url = - `/friend/${encodeURIComponent(action.username)}/${action.action === '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(); - btn.textContent = data.status === 'success' ? '✅ 已完成' : '❌ 失败'; - btn.style.background = data.status === 'success' ? '#16a34a' : '#cc4444'; - setTimeout(() => toast.remove(), 2000); - } catch (e) { - btn.textContent = '❌ 网络错误'; - } - }); - } - } - - // 5秒后自动消失 - setTimeout(() => { - toast.style.transition = 'opacity .5s'; - toast.style.opacity = '0'; - setTimeout(() => toast.remove(), 500); - }, 8000); - } + // showFriendToast 已迁移至 window.chatToast(toast-notification.blade.php) + // 保留空函数作向后兼容(移除时搜索 showFriendToast 确认无残余调用) // ── 全屏特效事件监听(烟花/下雨/雷电/下雪)───────── window.addEventListener('chat:effect', (e) => { diff --git a/resources/views/chat/partials/toast-notification.blade.php b/resources/views/chat/partials/toast-notification.blade.php new file mode 100644 index 0000000..d875601 --- /dev/null +++ b/resources/views/chat/partials/toast-notification.blade.php @@ -0,0 +1,138 @@ +{{-- + 文件功能:全局右下角 Toast 小卡片通知组件 + + 提供全局 JS API: + window.chatToast.show({ title, message, icon?, color?, duration? }) + - title: 卡片标题 + - message: 卡片内容(支持 HTML) + - icon: 左侧 Emoji 图标(可选,默认 💬) + - color: 强调色 HEX(可选,默认 #336699) + - duration: 自动消失毫秒数(可选,默认 6000;0 = 不自动消失) + - action: { label, onClick } 可选操作按钮 + + 使用示例: + window.chatToast.show({ title: '奖励金币', message: '你收到 100 枚金币!', icon: '🪙', color: '#f59e0b' }); + + 多条 Toast 从右下角向上堆叠,各自独立计时。 + + @author ChatRoom Laravel + @version 1.0.0 +--}} + +{{-- Toast 容器(固定右下角,由 JS 动态填充) --}} +
+
+ + + +