2026-03-01 12:15:18 +08:00
|
|
|
|
{{--
|
|
|
|
|
|
文件功能:全局右下角 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 } 可选操作按钮
|
|
|
|
|
|
|
|
|
|
|
|
使用示例:
|
2026-03-04 15:00:02 +08:00
|
|
|
|
window.chatToast.show({ title: '奖励金币', message: '你收到 100 枚金币!', icon: '💰', color: '#f59e0b' });
|
2026-03-01 12:15:18 +08:00
|
|
|
|
|
|
|
|
|
|
多条 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);
|
|
|
|
|
|
|
2026-03-01 13:28:19 +08:00
|
|
|
|
// 弹出时播放叮咚通知音
|
|
|
|
|
|
if (window.chatSound) window.chatSound.ding();
|
|
|
|
|
|
|
2026-03-01 12:15:18 +08:00
|
|
|
|
// 自动消失
|
|
|
|
|
|
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>
|