Files
chatroom/resources/views/chat/partials/toast-notification.blade.php
T
lkddi 3d7b86f06d 功能:奖励发放聊天室公告 + 右下角 Toast 通知卡片
后端(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
2026-03-01 12:15:18 +08:00

139 lines
4.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:全局右下角 Toast 小卡片通知组件
提供全局 JS API
window.chatToast.show({ title, message, icon?, color?, duration? })
- title: 卡片标题
- message: 卡片内容(支持 HTML
- icon: 左侧 Emoji 图标(可选,默认 💬)
- color: 强调色 HEX(可选,默认 #336699
- duration: 自动消失毫秒数(可选,默认 60000 = 不自动消失)
- action: { label, onClick } 可选操作按钮
使用示例:
window.chatToast.show({ title: '奖励金币', message: '你收到 100 枚金币!', icon: '🪙', color: '#f59e0b' });
多条 Toast 从右下角向上堆叠,各自独立计时。
@author ChatRoom Laravel
@version 1.0.0
--}}
{{-- Toast 容器(固定右下角,由 JS 动态填充) --}}
<div id="chat-toast-container"
style="position:fixed; bottom:24px; right:24px; z-index:999998;
display:flex; flex-direction:column-reverse; gap:10px; pointer-events:none;">
</div>
<script>
/**
* 全局右下角 Toast 小卡片通知系统。
*
* 可在聊天室任何 JS 代码中调用:
* window.chatToast.show({ title, message, icon, color, duration, action });
*/
window.chatToast = (function() {
const container = document.getElementById('chat-toast-container');
/**
* 显示一条 Toast 通知卡片。
*
* @param {object} opts
* @param {string} opts.title 标题文字
* @param {string} opts.message 内容(支持 HTML
* @param {string} [opts.icon] 左侧 Emoji(默认 💬)
* @param {string} [opts.color] 强调色(默认 #336699
* @param {number} [opts.duration] 自动消失毫秒,0 表示不自动消失(默认 6000)
* @param {object} [opts.action] 操作按钮 { label: string, onClick: function }
*/
function show({
title,
message,
icon = '💬',
color = '#336699',
duration = 6000,
action = null
}) {
const card = document.createElement('div');
card.style.cssText = `
background:#fff; border-radius:10px; overflow:hidden;
box-shadow:0 8px 32px rgba(0,0,0,.18);
min-width:260px; max-width:320px;
font-size:13px; color:#374151; line-height:1.6;
pointer-events:all;
animation:toastSlideIn .3s ease;
opacity:1; transition:opacity .4s;
`;
// 操作按钮 HTML
const actionHtml = action ? `
<div style="margin-top:8px;">
<button class="chat-toast-action-btn"
style="background:${color}; color:#fff; border:none; border-radius:6px;
padding:5px 14px; font-size:12px; font-weight:bold; cursor:pointer;">
${action.label}
</button>
</div>` : '';
card.innerHTML = `
<div style="background:${color}; padding:8px 14px;
display:flex; align-items:center; justify-content:space-between;">
<span style="color:#fff; font-weight:bold; font-size:13px;">
${icon} ${title}
</span>
<button class="chat-toast-close"
style="background:rgba(255,255,255,.25); border:none; color:#fff;
width:22px; height:22px; border-radius:50%; cursor:pointer;
font-size:14px; line-height:22px; text-align:center; padding:0;">×</button>
</div>
<div style="padding:12px 14px 10px;">
<div>${message}</div>
${actionHtml}
</div>
`;
// 关闭按钮
card.querySelector('.chat-toast-close').addEventListener('click', () => dismiss(card));
// 操作按钮
if (action) {
card.querySelector('.chat-toast-action-btn').addEventListener('click', () => {
action.onClick?.();
dismiss(card);
});
}
container.appendChild(card);
// 自动消失
if (duration > 0) {
setTimeout(() => dismiss(card), duration);
}
}
/** 淡出并移除 Toast 卡片 */
function dismiss(card) {
card.style.opacity = '0';
setTimeout(() => card.remove(), 400);
}
return {
show
};
})();
</script>
<style>
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(32px) scale(.96);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
</style>