/** * 古诗词阅读网 - 前端 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]) => `
${data.name}
`).join(''); // 点击事件 container.querySelectorAll('.category-tab').forEach(tab => { tab.addEventListener('click', () => { const category = tab.dataset.category; showTagsForCategory(category); // 切换激活状态 container.querySelectorAll('.category-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); }); }); // 默认显示第一个分类的标签 if (categories.length > 0) { const firstCategory = categories[0][0]; showTagsForCategory(firstCategory); container.querySelector('.category-tab')?.classList.add('active'); } } // 显示某分类的标签 function showTagsForCategory(category) { const container = document.getElementById('tagCloud'); const data = state.categories[category]; if (!data) return; container.innerHTML = data.tags.map(tag => ` ${tag} `).join(''); // 点击事件 container.querySelectorAll('.tag-item').forEach(item => { item.addEventListener('click', (e) => { e.stopPropagation(); addFilter(category, item.dataset.tag); }); }); } // 添加筛选条件 function addFilter(category, tag) { // 检查是否已存在 const exists = state.filters.categories.some( c => c.category === category && c.tag === tag ); if (!exists) { state.filters.categories.push({ category, tag }); renderSelectedFilters(); state.currentPage = 1; loadPoems(); } } // 渲染已选筛选 function renderSelectedFilters() { const container = document.getElementById('selectedFilters'); const btn = document.getElementById('clearFiltersBtn'); if (state.filters.categories.length === 0) { container.innerHTML = ''; btn.style.display = 'none'; return; } container.innerHTML = state.filters.categories.map((f, i) => ` ${state.categories[f.category]?.name || f.category}: ${f.tag} `).join(''); btn.style.display = 'inline-block'; } // 移除筛选 function removeFilter(index) { state.filters.categories.splice(index, 1); renderSelectedFilters(); state.currentPage = 1; loadPoems(); } // 清除所有筛选 function clearFilters() { state.filters.categories = []; state.filters.search = ''; state.filters.view = 'all'; document.getElementById('searchInput').value = ''; renderSelectedFilters(); // 重置导航激活状态 document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); document.querySelector('[data-view="all"]').classList.add('active'); loadPoems(); } // 渲染诗词列表 function renderPoems() { const grid = document.getElementById('poemGrid'); const empty = document.getElementById('emptyState'); if (state.poems.length === 0) { grid.innerHTML = ''; empty.style.display = 'block'; return; } empty.style.display = 'none'; grid.innerHTML = ''; state.poems.forEach(poem => { const card = createPoemCard(poem); grid.appendChild(card); }); } // 创建诗词卡片 function createPoemCard(poem) { const isRead = poem.is_read || false; const paragraphs = poem.paragraphs || []; const excerpt = paragraphs.slice(0, 2).join('
'); const tags = getTopTags(poem.classifications); const card = document.createElement('div'); card.className = `poem-card ${isRead ? 'read' : ''}`; card.dataset.id = poem.id; card.innerHTML = `

${escapeHtml(poem.title)}

作者:${escapeHtml(poem.author)}
${excerpt || '暂无内容'}
${tags}
`; // 点击卡片显示详情(排除复选框和按钮) card.addEventListener('click', (e) => { if (!e.target.closest('.read-toggle') && !e.target.closest('button')) { showPoemDetail(poem.id); } }); return card; } // 获取主要标签 function getTopTags(classifications) { if (!classifications) return ''; // 显示所有分类标签 const priority = ['genre', 'emotion_tone', 'season', 'location', 'time_of_day', 'philosophy', 'nature_scenery', 'plants', 'animals']; const tags = []; const shown = new Set(); for (const cat of priority) { if (classifications[cat] && classifications[cat].length > 0) { // 每个分类最多显示 2 个标签 classifications[cat].slice(0, 2).forEach(tag => { if (!shown.has(tag)) { tags.push(`${tag}`); shown.add(tag); } }); if (tags.length >= 6) break; } } return tags.join(''); } // 显示诗词详情 async function showPoemDetail(poemId) { try { const res = await fetch(`${API_BASE}/api/poems/${poemId}`); if (!res.ok) { showToast('加载失败', 'error'); return; } const poem = await res.json(); state.currentPoem = poem; // 填充弹窗 document.getElementById('modalTitle').textContent = poem.title; document.getElementById('modalAuthor').textContent = poem.author; document.getElementById('modalContent').innerHTML = poem.paragraphs .map(p => `

${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 `
${catName}:
${tags.map(tag => `${tag}`).join('')}
`; }).filter(html => html.trim() !== '').join(''); } // 切换弹窗阅读状态 async function toggleModalRead() { if (!state.currentPoem) return; const isRead = document.getElementById('modalReadCheck').checked; try { const res = await fetch( `${API_BASE}/api/poems/${state.currentPoem.id}/read?is_read=${isRead}`, { method: 'PUT' } ); if (res.ok) { state.currentPoem.is_read = isRead; await loadStats(); await loadPoems(); } } catch (err) { console.error('更新状态失败:', err); } } // 关闭弹窗 function closeModal() { document.getElementById('poemModal').style.display = 'none'; document.body.style.overflow = ''; state.currentPoem = null; } // 切换阅读状态 async function toggleRead(poemId, isRead) { try { const res = await fetch( `${API_BASE}/api/poems/${poemId}/read?is_read=${isRead}`, { method: 'PUT' } ); if (res.ok) { await loadStats(); await loadPoems(); showToast(isRead ? '已标记为已读' : '已标记为未读', 'success'); } } catch (err) { console.error('更新失败:', err); showToast('更新失败', 'error'); } } // 随机一首 async function showRandomPoem() { try { const res = await fetch(`${API_BASE}/api/poems/random`); if (res.ok) { const poem = await res.json(); state.currentPoem = poem; document.getElementById('modalTitle').textContent = poem.title; document.getElementById('modalAuthor').textContent = poem.author; document.getElementById('modalContent').innerHTML = poem.paragraphs .map(p => `

${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;