244 lines
7.0 KiB
JavaScript
244 lines
7.0 KiB
JavaScript
/**
|
||
* 全局应用函数
|
||
*/
|
||
|
||
// 核心工具函数
|
||
const App = {
|
||
/**
|
||
* 初始化应用
|
||
*/
|
||
init() {
|
||
this.setupEventListeners();
|
||
this.setupTheme();
|
||
},
|
||
|
||
/**
|
||
* 设置事件监听器
|
||
*/
|
||
setupEventListeners() {
|
||
// 全局事件委托
|
||
document.addEventListener('click', (e) => {
|
||
// 处理移动导航菜单切换
|
||
if (e.target.matches('#mobile-menu-toggle') || e.target.closest('#mobile-menu-toggle')) {
|
||
this.toggleMobileMenu();
|
||
}
|
||
|
||
// 处理通知关闭按钮
|
||
if (e.target.matches('.close-notification') || e.target.closest('.close-notification')) {
|
||
const notification = e.target.closest('.notification');
|
||
if (notification) this.closeNotification(notification);
|
||
}
|
||
|
||
// 处理模态框关闭
|
||
if (e.target.matches('.modal-overlay')) {
|
||
const modalId = e.target.dataset.modalId;
|
||
if (modalId) this.closeModal(modalId);
|
||
}
|
||
});
|
||
|
||
// 响应键盘事件
|
||
document.addEventListener('keydown', (e) => {
|
||
// ESC键关闭模态框
|
||
if (e.key === 'Escape') {
|
||
const modal = document.querySelector('.modal.is-active');
|
||
if (modal) {
|
||
const modalId = modal.id;
|
||
this.closeModal(modalId);
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 设置主题相关功能
|
||
*/
|
||
setupTheme() {
|
||
// 读取用户偏好
|
||
const savedTheme = localStorage.getItem('theme');
|
||
if (savedTheme) {
|
||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||
}
|
||
|
||
// 监听主题切换按钮
|
||
document.querySelectorAll('[data-toggle-theme]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
|
||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||
|
||
document.documentElement.setAttribute('data-theme', newTheme);
|
||
localStorage.setItem('theme', newTheme);
|
||
});
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 切换移动菜单
|
||
*/
|
||
toggleMobileMenu() {
|
||
const mobileMenu = document.getElementById('mobile-menu');
|
||
if (mobileMenu) {
|
||
const isActive = mobileMenu.classList.contains('active');
|
||
|
||
if (isActive) {
|
||
mobileMenu.classList.remove('active');
|
||
mobileMenu.classList.add('inactive');
|
||
} else {
|
||
mobileMenu.classList.remove('inactive');
|
||
mobileMenu.classList.add('active');
|
||
}
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 显示通知
|
||
* @param {string} message - 通知消息
|
||
* @param {string} type - 通知类型 (success, error, warning, info)
|
||
* @param {number} duration - 显示时长(ms),0为不自动关闭
|
||
*/
|
||
notify(message, type = 'info', duration = 5000) {
|
||
// 如果已存在通知容器则使用,否则创建
|
||
let container = document.getElementById('notification-container');
|
||
|
||
if (!container) {
|
||
container = document.createElement('div');
|
||
container.id = 'notification-container';
|
||
container.className = 'fixed top-4 right-4 z-50 flex flex-col items-end space-y-2';
|
||
document.body.appendChild(container);
|
||
}
|
||
|
||
// 创建通知元素
|
||
const id = 'notification-' + Date.now();
|
||
const notification = document.createElement('div');
|
||
|
||
// 设置通知样式
|
||
notification.id = id;
|
||
notification.className = `notification bg-white shadow-md rounded-md p-4 transform translate-x-full transition-transform duration-300 max-w-sm flex items-start border-l-4 ${this._getNotificationColorClass(type)}`;
|
||
|
||
// 设置通知内容
|
||
notification.innerHTML = `
|
||
<div class="${this._getNotificationIconClass(type)} w-5 h-5 mr-3 flex-shrink-0"></div>
|
||
<div class="flex-grow mr-2">
|
||
<p class="text-sm text-gray-800">${message}</p>
|
||
</div>
|
||
<button class="close-notification text-gray-400 hover:text-gray-600 flex-shrink-0 focus:outline-none">
|
||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||
</svg>
|
||
</button>
|
||
`;
|
||
|
||
// 添加到容器
|
||
container.appendChild(notification);
|
||
|
||
// 激活动画
|
||
setTimeout(() => {
|
||
notification.classList.remove('translate-x-full');
|
||
notification.classList.add('translate-x-0');
|
||
}, 10);
|
||
|
||
// 设置自动关闭
|
||
if (duration > 0) {
|
||
setTimeout(() => {
|
||
this.closeNotification(notification);
|
||
}, duration);
|
||
}
|
||
|
||
return id;
|
||
},
|
||
|
||
/**
|
||
* 关闭通知
|
||
* @param {HTMLElement|string} notification - 通知元素或ID
|
||
*/
|
||
closeNotification(notification) {
|
||
// 如果传入字符串ID,则获取对应元素
|
||
if (typeof notification === 'string') {
|
||
notification = document.getElementById(notification);
|
||
if (!notification) return;
|
||
}
|
||
|
||
// 动画关闭
|
||
notification.classList.remove('translate-x-0');
|
||
notification.classList.add('translate-x-full');
|
||
|
||
// 移除元素
|
||
setTimeout(() => {
|
||
if (notification && notification.parentNode) {
|
||
notification.parentNode.removeChild(notification);
|
||
}
|
||
}, 300);
|
||
},
|
||
|
||
/**
|
||
* 获取通知颜色类名
|
||
* @private
|
||
*/
|
||
_getNotificationColorClass(type) {
|
||
switch (type) {
|
||
case 'success': return 'border-green-500';
|
||
case 'error': return 'border-red-500';
|
||
case 'warning': return 'border-yellow-500';
|
||
default: return 'border-blue-500';
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 获取通知图标类名
|
||
* @private
|
||
*/
|
||
_getNotificationIconClass(type) {
|
||
const baseClass = 'text-white rounded-full p-1 flex items-center justify-center';
|
||
|
||
switch (type) {
|
||
case 'success':
|
||
return `${baseClass} bg-green-500`;
|
||
case 'error':
|
||
return `${baseClass} bg-red-500`;
|
||
case 'warning':
|
||
return `${baseClass} bg-yellow-500`;
|
||
default:
|
||
return `${baseClass} bg-blue-500`;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 打开模态框
|
||
* @param {string} id - 模态框ID
|
||
*/
|
||
openModal(id) {
|
||
const modal = document.getElementById(id);
|
||
if (!modal) return;
|
||
|
||
modal.classList.add('is-active');
|
||
document.body.classList.add('modal-open');
|
||
|
||
// 创建动画效果
|
||
modal.querySelector('.modal-content').classList.add('scale-100', 'opacity-100');
|
||
modal.querySelector('.modal-content').classList.remove('scale-95', 'opacity-0');
|
||
},
|
||
|
||
/**
|
||
* 关闭模态框
|
||
* @param {string} id - 模态框ID
|
||
*/
|
||
closeModal(id) {
|
||
const modal = document.getElementById(id);
|
||
if (!modal) return;
|
||
|
||
const content = modal.querySelector('.modal-content');
|
||
content.classList.remove('scale-100', 'opacity-100');
|
||
content.classList.add('scale-95', 'opacity-0');
|
||
|
||
// 等待动画完成后隐藏
|
||
setTimeout(() => {
|
||
modal.classList.remove('is-active');
|
||
document.body.classList.remove('modal-open');
|
||
}, 200);
|
||
}
|
||
};
|
||
|
||
// 初始化
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
App.init();
|
||
});
|