/** * UI管理模块 * 负责页面渲染、交互和状态管理 */ class UIManager { constructor() { this.elements = {}; this.isLoading = false; this.currentData = null; this.touchStartY = 0; this.pullThreshold = 80; this.isPulling = false; this.initElements(); this.bindEvents(); } /** * 初始化DOM元素引用 */ initElements() { this.elements = { loading: document.getElementById('loading'), error: document.getElementById('error'), hotList: document.getElementById('hotList'), topicsContainer: document.getElementById('topicsContainer'), refreshBtn: document.getElementById('refreshBtn'), updateTime: document.getElementById('updateTime'), toast: document.getElementById('toast') }; // 检查必需元素 const missingElements = Object.entries(this.elements) .filter(([key, element]) => !element) .map(([key]) => key); if (missingElements.length > 0) { console.error('[UI] 缺少必需的DOM元素:', missingElements); } } /** * 绑定事件监听器 */ bindEvents() { // 刷新按钮点击事件 if (this.elements.refreshBtn) { this.elements.refreshBtn.addEventListener('click', () => { this.handleRefresh(); }); } // 键盘快捷键 document.addEventListener('keydown', (e) => { if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) { e.preventDefault(); this.handleRefresh(); } }); // 移动端下拉刷新 this.initPullToRefresh(); // 页面可见性变化 document.addEventListener('visibilitychange', () => { if (!document.hidden && this.currentData) { // 页面重新可见时检查数据是否过期(5分钟) const lastUpdate = new Date(this.currentData.updateTime); const now = new Date(); if (now - lastUpdate > 5 * 60 * 1000) { this.handleRefresh(); } } }); } /** * 初始化下拉刷新功能 */ initPullToRefresh() { let startY = 0; let currentY = 0; let pullDistance = 0; document.addEventListener('touchstart', (e) => { if (window.scrollY === 0) { startY = e.touches[0].clientY; this.isPulling = true; } }, { passive: true }); document.addEventListener('touchmove', (e) => { if (!this.isPulling || this.isLoading) return; currentY = e.touches[0].clientY; pullDistance = currentY - startY; if (pullDistance > 0 && window.scrollY === 0) { e.preventDefault(); // 添加视觉反馈 const progress = Math.min(pullDistance / this.pullThreshold, 1); document.body.style.transform = `translateY(${pullDistance * 0.3}px)`; document.body.style.opacity = 1 - progress * 0.1; } }, { passive: false }); document.addEventListener('touchend', () => { if (this.isPulling) { document.body.style.transform = ''; document.body.style.opacity = ''; if (pullDistance > this.pullThreshold && !this.isLoading) { this.handleRefresh(); } this.isPulling = false; pullDistance = 0; } }); } /** * 处理刷新操作 */ async handleRefresh() { if (this.isLoading) return; this.showToast('正在刷新数据...'); // 触发自定义刷新事件 const refreshEvent = new CustomEvent('refreshData'); document.dispatchEvent(refreshEvent); } /** * 显示加载状态 */ showLoading() { this.isLoading = true; this.hideAllStates(); if (this.elements.loading) { this.elements.loading.style.display = 'flex'; } // 刷新按钮加载状态 if (this.elements.refreshBtn) { this.elements.refreshBtn.classList.add('loading'); this.elements.refreshBtn.disabled = true; } } /** * 显示错误状态 * @param {string} message - 错误消息 */ showError(message = '加载失败,请稍后重试') { this.isLoading = false; this.hideAllStates(); if (this.elements.error) { this.elements.error.style.display = 'flex'; const errorMessage = this.elements.error.querySelector('.error-message'); if (errorMessage) { errorMessage.textContent = message; } } this.resetRefreshButton(); } /** * 显示热搜列表 * @param {Object} data - 热搜数据 */ showHotList(data) { this.isLoading = false; this.currentData = data; this.hideAllStates(); if (this.elements.hotList) { this.elements.hotList.style.display = 'block'; } this.renderTopics(data.data); this.updateTime(data.updateTime); this.resetRefreshButton(); this.showToast(`已更新 ${data.total} 条热搜数据`); } /** * 隐藏所有状态 */ hideAllStates() { ['loading', 'error', 'hotList'].forEach(state => { if (this.elements[state]) { this.elements[state].style.display = 'none'; } }); } /** * 重置刷新按钮状态 */ resetRefreshButton() { if (this.elements.refreshBtn) { this.elements.refreshBtn.classList.remove('loading'); this.elements.refreshBtn.disabled = false; } } /** * 渲染热搜话题列表 * @param {Array} topics - 话题数组 */ renderTopics(topics) { if (!this.elements.topicsContainer) return; this.elements.topicsContainer.innerHTML = ''; topics.forEach((topic, index) => { const topicElement = this.createTopicElement(topic, index); this.elements.topicsContainer.appendChild(topicElement); }); } /** * 创建单个话题元素 * @param {Object} topic - 话题数据 * @param {number} index - 索引 * @returns {HTMLElement} 话题元素 */ createTopicElement(topic, index) { const item = document.createElement('div'); item.className = 'topic-item'; item.style.animationDelay = `${index * 0.1}s`; item.innerHTML = `