Files

198 lines
8.0 KiB
PHP
Raw Permalink 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.
{{--
文件功能:全局大卡片通知组件(chatBanner)
提供全局 JS API
window.chatBanner.show(opts) 显示居中大卡片
window.chatBanner.close(id?) 关闭指定卡片
opts 参数:
id: string (可选) ID 会替换旧弹窗,防止重叠
icon: string Emoji 图标(如 '🎉💚🎉'
title: string 小标题(如 '好友通知'
name: string 大名字行(可留空)
body: string 主内容(支持 HTML
sub: string 副内容(小字)
gradient: string[] 渐变颜色数组(3个颜色)
titleColor: string 小标题颜色
autoClose: number 自动关闭 ms0=不关闭,默认 5000
buttons: Array<{
label: string
color: string
onClick(btn, close): Function
}>
scripts.blade.php 拆分,确保页面全局组件独立维护。
@author ChatRoom Laravel
@version 1.0.0
--}}
<script>
/**
* 全局大卡片通知公共组件。
* 对标 window.chatDialog 和 window.chatToast 的设计风格,
* 用于展示任命公告、好友通知、红包选择等需要居中展示的大卡片。
*/
window.chatBanner = (function() {
/**
* 将任意文本转为 HTML 安全文本。
*/
function escapeBannerText(text) {
return String(text ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
/**
* 将多行纯文本转为带 <br> 的安全 HTML。
*/
function renderMultilineText(text) {
return escapeBannerText(text).replace(/\n/g, '<br>');
}
/** 注入入场/退场动画(全局只注入一次) */
function ensureKeyframes() {
if (document.getElementById('appoint-keyframes')) {
return;
}
const style = document.createElement('style');
style.id = 'appoint-keyframes';
style.textContent = `
@keyframes appoint-pop {
0% { opacity: 0; transform: translate(-50%,-50%) scale(0.5); }
70% { transform: translate(-50%,-50%) scale(1.05); }
100% { opacity: 1; transform: translate(-50%,-50%) scale(1); }
}
@keyframes appoint-fade-out {
from { opacity: 1; }
to { opacity: 0; transform: translate(-50%,-50%) scale(0.9); }
}
`;
document.head.appendChild(style);
}
/**
* 显示大卡片通知。
*
* @param {Object} opts 选项(见上方注释)
*/
function show(opts = {}) {
ensureKeyframes();
// 大卡片弹出时播放叮咚通知音
if (window.chatSound) {
window.chatSound.ding();
}
const id = opts.id || 'chat-banner-default';
const gradient = (opts.gradient || ['#4f46e5', '#7c3aed', '#db2777']).join(', ');
const titleColor = opts.titleColor || '#fde68a';
const autoClose = opts.autoClose ?? 5000;
// 移除同 ID 的旧弹窗,避免重叠
const old = document.getElementById(id);
if (old) {
old.remove();
}
// 构建按钮 HTML
const hasButtons = opts.buttons && opts.buttons.length > 0;
let buttonsHtml = '';
if (hasButtons) {
buttonsHtml = '<div style="margin-top:18px; display:flex; gap:10px; justify-content:center;">';
opts.buttons.forEach((btn, idx) => {
buttonsHtml += `<button data-banner-btn="${idx}"
style="background:${btn.color || '#10b981'}; color:#fff; border:none; border-radius:8px;
padding:8px 20px; font-size:13px; font-weight:bold; cursor:pointer;
box-shadow:0 4px 12px rgba(0,0,0,0.25);">
${escapeBannerText(btn.label || '确定')}
</button>`;
});
buttonsHtml += '</div>';
}
const banner = document.createElement('div');
banner.id = id;
banner.style.cssText = `
position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%);
z-index: 99999; text-align: center;
animation: appoint-pop 0.5s cubic-bezier(0.175,0.885,0.32,1.275);
${hasButtons ? 'pointer-events: auto;' : 'pointer-events: none;'}
`;
banner.innerHTML = `
<div style="background: linear-gradient(135deg, ${gradient});
border-radius: 20px; padding: 28px 44px;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
border: 2px solid rgba(255,255,255,0.25); backdrop-filter: blur(8px);
min-width: 260px;">
${opts.icon ? `<div style="font-size:40px; margin-bottom:8px;">${escapeBannerText(opts.icon)}</div>` : ''}
${opts.title ? `<div style="color:${titleColor}; font-size:13px; font-weight:bold; letter-spacing:3px; margin-bottom:12px;">
══ ${escapeBannerText(opts.title)} ══
</div>` : ''}
${opts.name ? `<div style="color:white; font-size:22px; font-weight:900; text-shadow:0 2px 8px rgba(0,0,0,0.3);">
${escapeBannerText(opts.name)}
</div>` : ''}
${opts.body ? `<div style="color:rgba(255,255,255,0.9); font-size:14px; margin-top:10px;">${renderMultilineText(opts.body)}</div>` : ''}
${opts.sub ? `<div style="color:rgba(255,255,255,0.6); font-size:12px; margin-top:6px;">${renderMultilineText(opts.sub)}</div>` : ''}
${buttonsHtml}
<div style="color:rgba(255,255,255,0.35); font-size:11px; margin-top:14px;">
${new Date().toLocaleTimeString('zh-CN')}
</div>
</div>
`;
document.body.appendChild(banner);
// 绑定按钮点击事件
if (hasButtons) {
const closeFn = () => {
banner.style.animation = 'appoint-fade-out 0.5s ease forwards';
setTimeout(() => banner.remove(), 500);
};
opts.buttons.forEach((btn, idx) => {
const el = banner.querySelector(`[data-banner-btn="${idx}"]`);
if (el && btn.onClick) {
el.addEventListener('click', () => btn.onClick(el, closeFn));
} else if (el) {
el.addEventListener('click', closeFn);
}
});
}
// 自动关闭
if (autoClose > 0) {
setTimeout(() => {
if (!document.getElementById(id)) {
return;
}
banner.style.animation = 'appoint-fade-out 0.5s ease forwards';
setTimeout(() => banner.remove(), 500);
}, autoClose);
}
}
/**
* 关闭指定 ID 的 banner。
*
* @param {string} id banner 的 DOM ID
*/
function close(id) {
const el = document.getElementById(id || 'chat-banner-default');
if (!el) {
return;
}
el.style.animation = 'appoint-fade-out 0.5s ease forwards';
setTimeout(() => el.remove(), 500);
}
return {
show,
close
};
})();
</script>