2025-04-02 14:29:57 +08:00

244 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 全局应用函数
*/
// 核心工具函数
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();
});