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;
|