307 lines
7.3 KiB
JavaScript
307 lines
7.3 KiB
JavaScript
// toast.js - 统一 Toast 管理
|
|
|
|
// 全局 Toast 管理器
|
|
class ToastManager {
|
|
constructor() {
|
|
this.toasts = new Map();
|
|
this.nextId = 1;
|
|
this.setupParentListener();
|
|
}
|
|
|
|
// 设置父页面监听器
|
|
setupParentListener() {
|
|
if (this.isTopWindow()) {
|
|
window.addEventListener('message', this.handleMessage.bind(this));
|
|
}
|
|
}
|
|
|
|
// 处理跨页面消息
|
|
handleMessage(event) {
|
|
const { action, toastId, options } = event.data;
|
|
|
|
switch (action) {
|
|
case 'create':
|
|
this.createToast(options);
|
|
break;
|
|
case 'close':
|
|
this.closeToast(toastId);
|
|
break;
|
|
case 'closeAllFromSource':
|
|
this.closeAllFromSource(event.source);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 创建 Toast
|
|
createToast(options) {
|
|
const toastId = this.nextId++;
|
|
const container = this.getToastContainer();
|
|
const toast = document.createElement('div');
|
|
|
|
// 设置基本类
|
|
toast.className = `toast enter toast-${options.type || 'info'}`;
|
|
toast.dataset.toastId = toastId;
|
|
|
|
// 设置图标
|
|
const icons = {
|
|
success: 'fa-check-circle',
|
|
info: 'fa-info-circle',
|
|
error: 'fa-times-circle',
|
|
warning: 'fa-exclamation-circle',
|
|
debug: 'fa-bug',
|
|
unknown: 'fa-question-circle'
|
|
};
|
|
|
|
const icon = icons[options.type] || icons['unknown'];
|
|
|
|
// 构建 Toast 内容
|
|
toast.innerHTML = `
|
|
<div class="toast-header">
|
|
<div class="toast-title">
|
|
<i class="fas ${icon}"></i>
|
|
<span>${options.title || '通知'}</span>
|
|
</div>
|
|
<button class="toast-close">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
${options.subtitle ? `<div class="toast-subtitle">${options.subtitle}</div>` : ''}
|
|
<div class="toast-progress">
|
|
<div class="toast-progress-bar"></div>
|
|
</div>
|
|
`;
|
|
|
|
// 添加到容器
|
|
container.appendChild(toast);
|
|
|
|
// 强制重绘
|
|
toast.offsetHeight;
|
|
|
|
// 移除进入动画类
|
|
setTimeout(() => {
|
|
toast.classList.remove('enter');
|
|
}, 300);
|
|
|
|
// 自动关闭
|
|
let timeoutId;
|
|
const startAutoClose = () => {
|
|
if (options.duration !== 0) {
|
|
const duration = options.duration || 3000;
|
|
timeoutId = setTimeout(() => {
|
|
this.closeToast(toastId);
|
|
}, duration);
|
|
|
|
// 进度条动画
|
|
const progressBar = toast.querySelector('.toast-progress-bar');
|
|
progressBar.style.width = '0%';
|
|
}
|
|
};
|
|
|
|
// 存储 Toast 信息
|
|
this.toasts.set(toastId, {
|
|
element: toast,
|
|
timeoutId,
|
|
source: options.source,
|
|
close: () => {
|
|
toast.classList.add('exit');
|
|
|
|
setTimeout(() => {
|
|
toast.classList.add('hiding');
|
|
|
|
setTimeout(() => {
|
|
toast.remove();
|
|
this.toasts.delete(toastId);
|
|
}, 300);
|
|
}, 300);
|
|
}
|
|
});
|
|
|
|
// 开始自动关闭
|
|
startAutoClose();
|
|
|
|
// 点击关闭按钮
|
|
const closeButton = toast.querySelector('.toast-close');
|
|
closeButton.addEventListener('click', () => {
|
|
this.closeToast(toastId);
|
|
});
|
|
|
|
return toastId;
|
|
}
|
|
|
|
// 关闭 Toast
|
|
closeToast(toastId) {
|
|
const toastInfo = this.toasts.get(toastId);
|
|
if (toastInfo) {
|
|
clearTimeout(toastInfo.timeoutId);
|
|
toastInfo.close();
|
|
}
|
|
}
|
|
|
|
// 关闭来自特定源的所有 Toast
|
|
closeAllFromSource(source) {
|
|
for (const [toastId, toastInfo] of this.toasts) {
|
|
if (toastInfo.source === source) {
|
|
this.closeToast(toastId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 获取或创建 Toast 容器
|
|
getToastContainer() {
|
|
let container = document.getElementById('toast-container');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.className = 'toast-container';
|
|
container.id = 'toast-container';
|
|
document.body.appendChild(container);
|
|
}
|
|
return container;
|
|
}
|
|
|
|
// 检查是否是顶层窗口
|
|
isTopWindow() {
|
|
try {
|
|
return window.self === window.top;
|
|
} catch (e) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 全局 Toast 管理器实例
|
|
const toastManager = new ToastManager();
|
|
|
|
// 获取正确的父窗口
|
|
function getParentWindow() {
|
|
try {
|
|
// 尝试获取父窗口的父窗口
|
|
if (window.parent && window.parent.parent && window.parent.parent !== window) {
|
|
return window.parent.parent;
|
|
}
|
|
// 否则使用直接父窗口
|
|
return window.parent;
|
|
} catch (e) {
|
|
console.warn('无法访问父窗口,使用当前窗口:', e);
|
|
return window;
|
|
}
|
|
}
|
|
|
|
// Toast API
|
|
const Toast = {
|
|
// 显示 Toast
|
|
show: function(options) {
|
|
// 添加来源信息
|
|
const source = window.name || 'unknown';
|
|
options = { ...options, source };
|
|
|
|
if (toastManager.isTopWindow()) {
|
|
// 在顶层窗口直接创建
|
|
return toastManager.createToast(options);
|
|
} else {
|
|
try {
|
|
// 获取正确的父窗口
|
|
const parent = getParentWindow();
|
|
|
|
// 在子窗口通过消息传递创建
|
|
parent.postMessage({
|
|
action: 'create',
|
|
options
|
|
}, '*');
|
|
|
|
// 设置页面卸载时清理 Toast
|
|
window.addEventListener('beforeunload', () => {
|
|
parent.postMessage({
|
|
action: 'closeAllFromSource',
|
|
source
|
|
}, '*');
|
|
});
|
|
|
|
// 返回一个代理对象
|
|
return {
|
|
close: () => {
|
|
parent.postMessage({
|
|
action: 'close',
|
|
toastId: options.toastId
|
|
}, '*');
|
|
}
|
|
};
|
|
} catch (e) {
|
|
console.error('Toast跨页面通信失败:', e);
|
|
return toastManager.createToast(options);
|
|
}
|
|
}
|
|
},
|
|
|
|
// 快捷方法
|
|
success: function(title, subtitle, duration) {
|
|
return this.show({
|
|
type: 'success',
|
|
title,
|
|
subtitle,
|
|
duration
|
|
});
|
|
},
|
|
|
|
info: function(title, subtitle, duration) {
|
|
return this.show({
|
|
type: 'info',
|
|
title,
|
|
subtitle,
|
|
duration
|
|
});
|
|
},
|
|
|
|
error: function(title, subtitle, duration) {
|
|
return this.show({
|
|
type: 'error',
|
|
title,
|
|
subtitle,
|
|
duration
|
|
});
|
|
},
|
|
|
|
warning: function(title, subtitle, duration) {
|
|
return this.show({
|
|
type: 'warning',
|
|
title,
|
|
subtitle,
|
|
duration
|
|
});
|
|
},
|
|
|
|
debug: function(title, subtitle, duration) {
|
|
return this.show({
|
|
type: 'debug',
|
|
title,
|
|
subtitle,
|
|
duration
|
|
});
|
|
},
|
|
|
|
unknown: function(title, subtitle, duration) {
|
|
return this.show({
|
|
type: 'unknown',
|
|
title,
|
|
subtitle,
|
|
duration
|
|
});
|
|
}
|
|
};
|
|
|
|
// 导出 Toast API
|
|
window.Toast = Toast;
|
|
|
|
// 如果是在子页面,确保父页面加载了 Toast 脚本
|
|
if (!toastManager.isTopWindow()) {
|
|
try {
|
|
// 获取正确的父窗口
|
|
const parent = getParentWindow();
|
|
|
|
// 检查父页面是否有 Toast 管理器
|
|
if (!parent || !parent.Toast) {
|
|
console.warn('父页面未加载Toast脚本,Toast功能可能受限');
|
|
}
|
|
} catch (e) {
|
|
console.warn('无法访问父页面,Toast功能可能受限:', e);
|
|
}
|
|
} |