/** * 古诗词阅读网 - 前端 JavaScript */ const API_BASE = window.location.origin; // 状态 let state = { poems: [], currentPage: 1, totalPages: 1, pageSize: 20, total: 0, filters: { view: 'all', search: '', categories: [] // 格式:[{category: 'genre', tag: '山水田园'}] }, currentPoem: null, categories: {} }; // 初始化 document.addEventListener('DOMContentLoaded', async () => { await loadCategories(); await loadStats(); await loadPoems(); setupEventListeners(); setupUpload(); }); // 加载分类体系 async function loadCategories() { try { const res = await fetch(`${API_BASE}/api/categories`); if (res.ok) { state.categories = await res.json(); renderCategoryTabs(); } } catch (err) { console.error('加载分类失败:', err); } } // 加载统计 async function loadStats() { try { const res = await fetch(`${API_BASE}/api/stats`); if (res.ok) { const stats = await res.json(); updateStats(stats); } } catch (err) { console.error('加载统计失败:', err); } } // 加载诗词 async function loadPoems() { showLoading(true); try { const params = new URLSearchParams({ page: state.currentPage, page_size: state.pageSize }); // 搜索 if (state.filters.search) { params.append('search', state.filters.search); } // 阅读状态 if (state.filters.view === 'read') { params.append('is_read', 'true'); } else if (state.filters.view === 'unread') { params.append('is_read', 'false'); } // 多类别筛选 if (state.filters.categories.length > 0) { const catStr = state.filters.categories .map(c => `${c.category}:${c.tag}`) .join(','); params.append('categories', catStr); } const res = await fetch(`${API_BASE}/api/poems?${params}`); if (res.ok) { const data = await res.json(); state.poems = data.poems; state.total = data.total; state.totalPages = data.total_pages; renderPoems(); updateResultCount(); } } catch (err) { console.error('加载诗词失败:', err); showToast('加载失败', 'error'); } finally { showLoading(false); } } // 渲染分类标签页 function renderCategoryTabs() { const container = document.getElementById('categoryTabs'); const categories = Object.entries(state.categories); // 显示所有分类 container.innerHTML = categories.map(([key, data]) => `
${escapeHtml(p)}
`).join(''); // 渲染标签 document.getElementById('modalTags').innerHTML = renderAllTags(poem.classifications); // 设置阅读状态 document.getElementById('modalReadCheck').checked = poem.is_read || false; // 显示弹窗 document.getElementById('poemModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } catch (err) { console.error('加载详情失败:', err); showToast('加载失败', 'error'); } } // 渲染所有标签 function renderAllTags(classifications) { if (!classifications) return ''; const categoryNames = state.categories; const categoryOrder = ['season', 'solar_terms', 'time_of_day', 'genre', 'emotion_tone', 'emotions', 'nature_scenery', 'plants', 'animals', 'buildings', 'philosophy', 'life_stage', 'social_role', 'technique', 'rhetoric', 'colors', 'sounds', 'location', 'festival']; return categoryOrder.map(cat => { if (!classifications[cat] || classifications[cat].length === 0) return ''; const catName = categoryNames[cat]?.name || cat; const tags = classifications[cat]; return `${escapeHtml(p)}
`).join(''); document.getElementById('modalTags').innerHTML = renderAllTags(poem.classifications); document.getElementById('modalReadCheck').checked = poem.is_read || false; document.getElementById('poemModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } } catch (err) { showToast('获取失败', 'error'); } } // 上传文件 async function uploadFile() { const fileInput = document.getElementById('fileInput'); const file = fileInput.files[0]; if (!file) { showToast('请选择文件', 'error'); return; } const formData = new FormData(); formData.append('file', file); const status = document.getElementById('uploadStatus'); status.textContent = '上传中...'; status.className = 'status-msg'; try { const res = await fetch(`${API_BASE}/api/poems/import`, { method: 'POST', body: formData }); const data = await res.json(); if (res.ok) { status.textContent = `成功导入 ${data.imported} 首,跳过 ${data.skipped} 首`; status.className = 'status-msg success'; fileInput.value = ''; await loadStats(); await loadPoems(); } else { status.textContent = data.detail || '导入失败'; status.className = 'status-msg error'; } } catch (err) { status.textContent = '上传失败:' + err.message; status.className = 'status-msg error'; } } // 更新统计显示 function updateStats(stats) { document.getElementById('totalPoems').textContent = stats.total_poems; document.getElementById('readCount').textContent = stats.read_count; document.getElementById('unreadCount').textContent = stats.unread_count; const percent = stats.reading_progress || 0; document.getElementById('progressPercent').textContent = percent; document.getElementById('progressText').textContent = `${stats.read_count}/${stats.total_poems}`; document.querySelector('.progress-fill').style.width = `${percent}%`; } // 更新结果数量 function updateResultCount() { document.getElementById('resultCount').textContent = `${state.total} 首`; updatePagination(); } // 更新分页控件 function updatePagination() { const pagination = document.getElementById('pagination'); const prevBtn = document.getElementById('prevPage'); const nextBtn = document.getElementById('nextPage'); const pageInfo = document.getElementById('pageInfo'); if (state.totalPages <= 1) { pagination.style.display = 'none'; return; } pagination.style.display = 'flex'; prevBtn.disabled = state.currentPage <= 1; nextBtn.disabled = state.currentPage >= state.totalPages; prevBtn.style.opacity = prevBtn.disabled ? '0.5' : '1'; nextBtn.style.opacity = nextBtn.disabled ? '0.5' : '1'; pageInfo.textContent = `第 ${state.currentPage} / ${state.totalPages} 页,共 ${state.total} 首`; } // 跳转到指定页 function goToPage(page) { if (page < 1 || page > state.totalPages) return; state.currentPage = page; loadPoems(); window.scrollTo({ top: 0, behavior: 'smooth' }); } // 设置事件监听 function setupEventListeners() { // 导航 document.querySelectorAll('.nav-item').forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); const view = item.dataset.view; const action = item.dataset.action; if (action === 'random') { showRandomPoem(); return; } if (view) { state.filters.view = view; state.currentPage = 1; document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); item.classList.add('active'); loadPoems(); } }); }); // 搜索 let searchTimeout; document.getElementById('searchInput').addEventListener('input', (e) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { state.filters.search = e.target.value.trim(); state.currentPage = 1; loadPoems(); }, 500); }); } // 设置上传区域 function setupUpload() { const area = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); const uploadStatus = document.getElementById('uploadStatus'); area.addEventListener('dragover', (e) => { e.preventDefault(); area.classList.add('dragover'); }); area.addEventListener('dragleave', () => { area.classList.remove('dragover'); }); area.addEventListener('drop', (e) => { e.preventDefault(); area.classList.remove('dragover'); const files = e.dataTransfer.files; if (files.length > 0) { fileInput.files = files; // 触发 change 事件以更新文件名显示 fileInput.dispatchEvent(new Event('change')); } }); // 文件选择后显示文件名 fileInput.addEventListener('change', () => { const file = fileInput.files[0]; if (file) { uploadStatus.textContent = `已选择:${file.name}`; uploadStatus.className = 'status-msg info'; } else { uploadStatus.textContent = ''; uploadStatus.className = 'status-msg'; } }); } // 工具函数 function showLoading(show) { document.getElementById('loading').style.display = show ? 'flex' : 'none'; } function showToast(message, type = 'info') { const toast = document.getElementById('toast'); const msg = document.getElementById('toastMessage'); msg.textContent = message; toast.style.display = 'block'; toast.className = `toast toast-${type}`; setTimeout(() => { toast.style.display = 'none'; }, 3000); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 导出全局函数 window.toggleRead = toggleRead; window.showPoemDetail = showPoemDetail; window.closeModal = closeModal; window.uploadFile = uploadFile; window.clearFilters = clearFilters; window.removeFilter = removeFilter; window.goToPage = goToPage;