497 lines
15 KiB
JavaScript
497 lines
15 KiB
JavaScript
/**
|
||
* Toast 消息提醒组件 - iOS风格
|
||
* 使用方法:
|
||
* Toast.show('消息内容')
|
||
* Toast.success('成功消息')
|
||
* Toast.error('错误消息')
|
||
* Toast.warning('警告消息')
|
||
* Toast.info('提示消息')
|
||
*/
|
||
|
||
const Toast = (function() {
|
||
// 消息类型配置
|
||
const TYPES = {
|
||
success: {
|
||
icon: '✓',
|
||
color: '#34C759',
|
||
iconBg: 'rgba(52, 199, 89, 0.15)'
|
||
},
|
||
error: {
|
||
icon: '✕',
|
||
color: '#FF3B30',
|
||
iconBg: 'rgba(255, 59, 48, 0.15)'
|
||
},
|
||
warning: {
|
||
icon: '!',
|
||
color: '#FF9500',
|
||
iconBg: 'rgba(255, 149, 0, 0.15)'
|
||
},
|
||
info: {
|
||
icon: 'i',
|
||
color: '#007AFF',
|
||
iconBg: 'rgba(0, 122, 255, 0.15)'
|
||
}
|
||
};
|
||
|
||
// 默认配置
|
||
const DEFAULT_OPTIONS = {
|
||
duration: 2500, // 显示时长(毫秒)
|
||
position: 'top-center', // 位置:top-center, top-right, bottom-center, center
|
||
showIcon: true, // 是否显示图标
|
||
animation: true // 是否使用动画
|
||
};
|
||
|
||
// 当前显示的toast队列
|
||
let toastQueue = [];
|
||
let container = null;
|
||
|
||
/**
|
||
* 初始化容器
|
||
*/
|
||
function initContainer() {
|
||
if (!container) {
|
||
container = document.createElement('div');
|
||
container.className = 'toast-container';
|
||
document.body.appendChild(container);
|
||
}
|
||
return container;
|
||
}
|
||
|
||
/**
|
||
* 创建Toast元素
|
||
*/
|
||
function createToast(message, type = 'info', options = {}) {
|
||
const opts = { ...DEFAULT_OPTIONS, ...options };
|
||
const config = TYPES[type] || TYPES.info;
|
||
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast toast-${type} toast-${opts.position}`;
|
||
|
||
if (opts.animation) {
|
||
toast.classList.add('toast-enter');
|
||
}
|
||
|
||
// 构建toast内容
|
||
let html = '<div class="toast-content">';
|
||
|
||
if (opts.showIcon) {
|
||
html += `
|
||
<div class="toast-icon" style="background-color: ${config.iconBg}; color: ${config.color}">
|
||
<span>${config.icon}</span>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
html += `
|
||
<div class="toast-message">${message}</div>
|
||
</div>`;
|
||
|
||
toast.innerHTML = html;
|
||
|
||
return { element: toast, duration: opts.duration };
|
||
}
|
||
|
||
/**
|
||
* 显示Toast
|
||
*/
|
||
function show(message, type = 'info', options = {}) {
|
||
const toastContainer = initContainer();
|
||
const { element, duration } = createToast(message, type, options);
|
||
|
||
// 添加到DOM
|
||
toastContainer.appendChild(element);
|
||
toastQueue.push(element);
|
||
|
||
// 触发入场动画
|
||
setTimeout(() => {
|
||
element.classList.remove('toast-enter');
|
||
element.classList.add('toast-visible');
|
||
}, 10);
|
||
|
||
// 自动移除
|
||
setTimeout(() => {
|
||
hide(element);
|
||
}, duration);
|
||
|
||
return element;
|
||
}
|
||
|
||
/**
|
||
* 隐藏Toast
|
||
*/
|
||
function hide(toastElement) {
|
||
if (!toastElement || !toastElement.parentNode) return;
|
||
|
||
toastElement.classList.remove('toast-visible');
|
||
toastElement.classList.add('toast-exit');
|
||
|
||
setTimeout(() => {
|
||
if (toastElement.parentNode) {
|
||
toastElement.parentNode.removeChild(toastElement);
|
||
}
|
||
|
||
// 从队列中移除
|
||
const index = toastQueue.indexOf(toastElement);
|
||
if (index > -1) {
|
||
toastQueue.splice(index, 1);
|
||
}
|
||
|
||
// 如果队列为空,移除容器
|
||
if (toastQueue.length === 0 && container) {
|
||
container.remove();
|
||
container = null;
|
||
}
|
||
}, 300);
|
||
}
|
||
|
||
/**
|
||
* 清除所有Toast
|
||
*/
|
||
function clear() {
|
||
toastQueue.forEach(toast => hide(toast));
|
||
toastQueue = [];
|
||
}
|
||
|
||
// 快捷方法
|
||
function success(message, options) {
|
||
return show(message, 'success', options);
|
||
}
|
||
|
||
function error(message, options) {
|
||
return show(message, 'error', options);
|
||
}
|
||
|
||
function warning(message, options) {
|
||
return show(message, 'warning', options);
|
||
}
|
||
|
||
function info(message, options) {
|
||
return show(message, 'info', options);
|
||
}
|
||
|
||
/**
|
||
* 提示对话框(iOS风格,只有一个确定按钮)
|
||
*/
|
||
function alert(options = {}) {
|
||
// 支持直接传字符串或对象
|
||
if (typeof options === 'string') {
|
||
options = { message: options };
|
||
}
|
||
|
||
return new Promise((resolve) => {
|
||
const opts = {
|
||
title: options.title || '',
|
||
message: options.message || '',
|
||
confirmText: options.confirmText || i18n?.t?.('confirm') || '确定',
|
||
confirmColor: options.confirmColor || '#007AFF',
|
||
...options
|
||
};
|
||
|
||
// 创建遮罩层
|
||
const overlay = document.createElement('div');
|
||
overlay.className = 'toast-overlay';
|
||
|
||
// 创建对话框
|
||
const dialog = document.createElement('div');
|
||
dialog.className = 'toast-dialog';
|
||
|
||
let html = '<div class="toast-dialog-content">';
|
||
|
||
if (opts.title) {
|
||
html += `<div class="toast-dialog-title">${opts.title}</div>`;
|
||
}
|
||
|
||
if (opts.message) {
|
||
html += `<div class="toast-dialog-message">${opts.message}</div>`;
|
||
}
|
||
|
||
// 只有一个确定按钮
|
||
html += `
|
||
<div class="toast-dialog-buttons toast-dialog-single-button">
|
||
<button class="toast-dialog-button toast-dialog-confirm" style="color: ${opts.confirmColor}">
|
||
${opts.confirmText}
|
||
</button>
|
||
</div>
|
||
</div>`;
|
||
|
||
dialog.innerHTML = html;
|
||
|
||
// 添加到DOM
|
||
overlay.appendChild(dialog);
|
||
document.body.appendChild(overlay);
|
||
|
||
// 触发动画
|
||
setTimeout(() => {
|
||
overlay.classList.add('toast-overlay-visible');
|
||
dialog.classList.add('toast-dialog-visible');
|
||
}, 10);
|
||
|
||
// 按钮事件
|
||
const confirmBtn = dialog.querySelector('.toast-dialog-confirm');
|
||
|
||
function close() {
|
||
overlay.classList.remove('toast-overlay-visible');
|
||
dialog.classList.remove('toast-dialog-visible');
|
||
setTimeout(() => {
|
||
overlay.remove();
|
||
}, 300);
|
||
}
|
||
|
||
confirmBtn.addEventListener('click', () => {
|
||
close();
|
||
resolve(true);
|
||
});
|
||
|
||
// 点击遮罩关闭
|
||
if (opts.closeOnClickOverlay !== false) {
|
||
overlay.addEventListener('click', (e) => {
|
||
if (e.target === overlay) {
|
||
close();
|
||
resolve(true);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 确认对话框(iOS风格)
|
||
*/
|
||
function confirm(options = {}) {
|
||
return new Promise((resolve, reject) => {
|
||
const opts = {
|
||
title: options.title || '',
|
||
message: options.message || '',
|
||
confirmText: options.confirmText || i18n.t('confirm') || '确定',
|
||
cancelText: options.cancelText || i18n.t('cancel') || '取消',
|
||
confirmColor: options.confirmColor || '#007AFF',
|
||
cancelColor: options.cancelColor || '#8E8E93',
|
||
...options
|
||
};
|
||
|
||
// 创建遮罩层
|
||
const overlay = document.createElement('div');
|
||
overlay.className = 'toast-overlay';
|
||
|
||
// 创建对话框
|
||
const dialog = document.createElement('div');
|
||
dialog.className = 'toast-dialog';
|
||
|
||
let html = '<div class="toast-dialog-content">';
|
||
|
||
if (opts.title) {
|
||
html += `<div class="toast-dialog-title">${opts.title}</div>`;
|
||
}
|
||
|
||
if (opts.message) {
|
||
html += `<div class="toast-dialog-message">${opts.message}</div>`;
|
||
}
|
||
|
||
html += `
|
||
<div class="toast-dialog-buttons">
|
||
<button class="toast-dialog-button toast-dialog-cancel" style="color: ${opts.cancelColor}">
|
||
${opts.cancelText}
|
||
</button>
|
||
<button class="toast-dialog-button toast-dialog-confirm" style="color: ${opts.confirmColor}">
|
||
${opts.confirmText}
|
||
</button>
|
||
</div>
|
||
</div>`;
|
||
|
||
dialog.innerHTML = html;
|
||
|
||
// 添加到DOM
|
||
overlay.appendChild(dialog);
|
||
document.body.appendChild(overlay);
|
||
|
||
// 触发动画
|
||
setTimeout(() => {
|
||
overlay.classList.add('toast-overlay-visible');
|
||
dialog.classList.add('toast-dialog-visible');
|
||
}, 10);
|
||
|
||
// 按钮事件
|
||
const confirmBtn = dialog.querySelector('.toast-dialog-confirm');
|
||
const cancelBtn = dialog.querySelector('.toast-dialog-cancel');
|
||
|
||
function close() {
|
||
overlay.classList.remove('toast-overlay-visible');
|
||
dialog.classList.remove('toast-dialog-visible');
|
||
setTimeout(() => {
|
||
overlay.remove();
|
||
}, 300);
|
||
}
|
||
|
||
confirmBtn.addEventListener('click', () => {
|
||
close();
|
||
resolve(true);
|
||
});
|
||
|
||
cancelBtn.addEventListener('click', () => {
|
||
close();
|
||
resolve(false);
|
||
});
|
||
|
||
// 点击遮罩关闭
|
||
if (opts.closeOnClickOverlay !== false) {
|
||
overlay.addEventListener('click', (e) => {
|
||
if (e.target === overlay) {
|
||
close();
|
||
resolve(false);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 输入对话框(iOS风格)
|
||
*/
|
||
function prompt(options = {}) {
|
||
return new Promise((resolve, reject) => {
|
||
const opts = {
|
||
title: options.title || '',
|
||
message: options.message || '',
|
||
placeholder: options.placeholder || '',
|
||
defaultValue: options.defaultValue || '',
|
||
inputType: options.inputType || 'text',
|
||
confirmText: options.confirmText || i18n.t('confirm') || '确定',
|
||
cancelText: options.cancelText || i18n.t('cancel') || '取消',
|
||
confirmColor: options.confirmColor || '#007AFF',
|
||
cancelColor: options.cancelColor || '#8E8E93',
|
||
maxLength: options.maxLength || null,
|
||
...options
|
||
};
|
||
|
||
// 创建遮罩层
|
||
const overlay = document.createElement('div');
|
||
overlay.className = 'toast-overlay';
|
||
|
||
// 创建对话框
|
||
const dialog = document.createElement('div');
|
||
dialog.className = 'toast-dialog toast-prompt-dialog';
|
||
|
||
let html = '<div class="toast-dialog-content">';
|
||
|
||
if (opts.title) {
|
||
html += `<div class="toast-dialog-title">${opts.title}</div>`;
|
||
}
|
||
|
||
if (opts.message) {
|
||
html += `<div class="toast-dialog-message">${opts.message}</div>`;
|
||
}
|
||
|
||
// 输入框
|
||
const maxLengthAttr = opts.maxLength ? `maxlength="${opts.maxLength}"` : '';
|
||
html += `
|
||
<div class="toast-input-wrapper">
|
||
<input
|
||
type="${opts.inputType}"
|
||
class="toast-input"
|
||
placeholder="${opts.placeholder}"
|
||
value="${opts.defaultValue}"
|
||
${maxLengthAttr}
|
||
autocomplete="off"
|
||
/>
|
||
</div>
|
||
`;
|
||
|
||
html += `
|
||
<div class="toast-dialog-buttons">
|
||
<button class="toast-dialog-button toast-dialog-cancel" style="color: ${opts.cancelColor}">
|
||
${opts.cancelText}
|
||
</button>
|
||
<button class="toast-dialog-button toast-dialog-confirm" style="color: ${opts.confirmColor}">
|
||
${opts.confirmText}
|
||
</button>
|
||
</div>
|
||
</div>`;
|
||
|
||
dialog.innerHTML = html;
|
||
|
||
// 添加到DOM
|
||
overlay.appendChild(dialog);
|
||
document.body.appendChild(overlay);
|
||
|
||
// 获取输入框
|
||
const input = dialog.querySelector('.toast-input');
|
||
const confirmBtn = dialog.querySelector('.toast-dialog-confirm');
|
||
const cancelBtn = dialog.querySelector('.toast-dialog-cancel');
|
||
|
||
// 触发动画
|
||
setTimeout(() => {
|
||
overlay.classList.add('toast-overlay-visible');
|
||
dialog.classList.add('toast-dialog-visible');
|
||
// 自动聚焦并选中文本
|
||
input.focus();
|
||
if (opts.defaultValue) {
|
||
input.select();
|
||
}
|
||
}, 10);
|
||
|
||
function close() {
|
||
overlay.classList.remove('toast-overlay-visible');
|
||
dialog.classList.remove('toast-dialog-visible');
|
||
setTimeout(() => {
|
||
overlay.remove();
|
||
}, 300);
|
||
}
|
||
|
||
function handleConfirm() {
|
||
const value = input.value;
|
||
close();
|
||
resolve(value);
|
||
}
|
||
|
||
function handleCancel() {
|
||
close();
|
||
resolve(null);
|
||
}
|
||
|
||
// 按钮事件
|
||
confirmBtn.addEventListener('click', handleConfirm);
|
||
cancelBtn.addEventListener('click', handleCancel);
|
||
|
||
// 回车确认
|
||
input.addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') {
|
||
handleConfirm();
|
||
}
|
||
});
|
||
|
||
// ESC取消
|
||
input.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') {
|
||
handleCancel();
|
||
}
|
||
});
|
||
|
||
// 点击遮罩关闭
|
||
if (opts.closeOnClickOverlay !== false) {
|
||
overlay.addEventListener('click', (e) => {
|
||
if (e.target === overlay) {
|
||
handleCancel();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// 导出API
|
||
return {
|
||
show,
|
||
success,
|
||
error,
|
||
warning,
|
||
info,
|
||
alert,
|
||
confirm,
|
||
prompt,
|
||
clear,
|
||
hide
|
||
};
|
||
})();
|
||
|
||
// 全局暴露
|
||
window.Toast = Toast;
|