web
This commit is contained in:
496
web/assets/js/toast.js
Normal file
496
web/assets/js/toast.js
Normal file
@@ -0,0 +1,496 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user