// API基础地址 const API_BASE = ''; // 状态 let articles = []; let currentPage = 1; let pageSize = 20; let totalCount = 0; let selectedIds = new Set(); let currentArticleId = null; // 页面加载 document.addEventListener('DOMContentLoaded', () => { loadArticles(); loadCategories(); // 搜索防抖 let searchTimeout; document.getElementById('search-input').addEventListener('input', (e) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { currentPage = 1; loadArticles(); }, 300); }); // 分类筛选 document.getElementById('category-filter').addEventListener('change', () => { currentPage = 1; loadArticles(); }); }); // 加载文章列表 async function loadArticles() { const keyword = document.getElementById('search-input').value.trim(); const category = document.getElementById('category-filter').value; const params = new URLSearchParams({ limit: pageSize, offset: (currentPage - 1) * pageSize }); if (keyword) { const response = await fetch(`${API_BASE}/api/articles/search?q=${encodeURIComponent(keyword)}${category ? '&category=' + encodeURIComponent(category) : ''}`); const data = await response.json(); if (data.success) { articles = data.articles; totalCount = data.count; } } else { const response = await fetch(`${API_BASE}/api/articles?${params.toString()}`); const data = await response.json(); if (data.success) { articles = data.articles; totalCount = data.count; } } displayArticles(); updatePagination(); updateStats(); } // 加载分类列表 async function loadCategories() { try { const response = await fetch(`${API_BASE}/api/articles?limit=1000`); const data = await response.json(); if (data.success) { const categories = new Set(); data.articles.forEach(a => { if (a.category) categories.add(a.category); }); const select = document.getElementById('category-filter'); select.innerHTML = '' + Array.from(categories).map(c => ``).join(''); } } catch (error) { console.error('加载分类失败:', error); } } // 显示文章列表 function displayArticles() { const container = document.getElementById('articles-list'); if (articles.length === 0) { container.innerHTML = '
暂无文章
'; return; } container.innerHTML = articles.map(article => { const productNames = safeParseJSON(article.product_names, []); const keywords = safeParseJSON(article.keywords, []); return `
${escapeHtml(productNames.join(', ') || '未命名')}
${article.category ? `` : ''} ${escapeHtml(article.source || '未知来源')} ${formatDate(article.fetch_date)}
${escapeHtml(article.summary || '')}
${keywords.length > 0 ? `
${keywords.slice(0, 5).map(k => ``).join('')}
` : ''}
`; }).join(''); } // 更新分页 function updatePagination() { const totalPages = Math.ceil(totalCount / pageSize); document.getElementById('page-info').textContent = `第 ${currentPage} / ${totalPages || 1} 页`; document.getElementById('prev-btn').disabled = currentPage <= 1; document.getElementById('next-btn').disabled = currentPage >= totalPages; } // 更新统计 function updateStats() { document.getElementById('total-count').textContent = `${totalCount} 篇文章`; } // 上一页 function prevPage() { if (currentPage > 1) { currentPage--; loadArticles(); } } // 下一页 function nextPage() { const totalPages = Math.ceil(totalCount / pageSize); if (currentPage < totalPages) { currentPage++; loadArticles(); } } // 刷新列表 function refreshList() { loadArticles(); } // 全选/取消全选 function toggleSelectAll() { const checked = document.getElementById('select-all').checked; if (checked) { articles.forEach(a => selectedIds.add(a.id)); } else { selectedIds.clear(); } displayArticles(); updateBatchActions(); } // 切换单个选择 function toggleSelect(id) { if (selectedIds.has(id)) { selectedIds.delete(id); } else { selectedIds.add(id); } updateBatchActions(); } // 更新批量操作按钮 function updateBatchActions() { const batchActions = document.getElementById('batch-actions'); if (selectedIds.size > 0) { batchActions.style.display = 'flex'; document.getElementById('selected-count').textContent = `已选 ${selectedIds.size} 篇`; } else { batchActions.style.display = 'none'; } } // 显示详情 async function showDetail(id) { currentArticleId = id; const article = articles.find(a => a.id === id); if (!article) { const response = await fetch(`${API_BASE}/api/articles/${id}`); const data = await response.json(); if (data.success) { Object.assign(article, data.article); } } const productNames = safeParseJSON(article.product_names, []); const keywords = safeParseJSON(article.keywords, []); const body = document.getElementById('detail-body'); body.innerHTML = `

产品名称

${escapeHtml(productNames.join(', '))}

分类: ${escapeHtml(article.category || '未分类')}
来源: ${escapeHtml(article.source)}
时间: ${formatDate(article.fetch_date)}
${article.url ? `

原文链接

${escapeHtml(article.url)}
` : ''} ${keywords.length > 0 ? `

关键词

${escapeHtml(keywords.join(', '))}

` : ''}

摘要

${escapeHtml(article.summary || '无')}

内容

${escapeHtml(article.content || '无内容')}
`; document.getElementById('detail-modal').classList.add('active'); } // 显示添加模态框 function showAddModal() { currentArticleId = null; document.getElementById('modal-title').innerHTML = ' 添加文章'; document.getElementById('article-form').reset(); document.getElementById('article-modal').classList.add('active'); } // 编辑文章 function editArticle(id) { const article = articles.find(a => a.id === id); if (!article) return; currentArticleId = id; document.getElementById('modal-title').innerHTML = ' 编辑文章'; const productNames = safeParseJSON(article.product_names, []); const keywords = safeParseJSON(article.keywords, []); document.getElementById('article-id').value = id; document.getElementById('article-products').value = productNames.join(', '); document.getElementById('article-category').value = article.category || ''; document.getElementById('article-keywords').value = keywords.join(', '); document.getElementById('article-summary').value = article.summary || ''; document.getElementById('article-content').value = article.content || ''; document.getElementById('article-source').value = article.source || ''; document.getElementById('article-url').value = article.url || ''; document.getElementById('article-modal').classList.add('active'); } // 编辑当前文章 function editCurrentArticle() { if (currentArticleId) { closeModal('detail-modal'); editArticle(currentArticleId); } } // 保存文章 async function saveArticle() { const id = document.getElementById('article-id').value; const products = document.getElementById('article-products').value.trim(); const summary = document.getElementById('article-summary').value.trim(); const content = document.getElementById('article-content').value.trim(); const source = document.getElementById('article-source').value.trim(); if (!products || !summary || !content || !source) { showToast('请填写必填字段', 'error'); return; } const articleData = { product_names: products.split(',').map(p => p.trim()), category: document.getElementById('article-category').value.trim(), keywords: document.getElementById('article-keywords').value.split(',').map(k => k.trim()).filter(k => k), summary: summary, content: content, source: source, url: document.getElementById('article-url').value.trim() }; try { if (id) { // 更新 - 先删除再添加 await fetch(`${API_BASE}/api/articles/${id}`, { method: 'DELETE' }); } const response = await fetch(`${API_BASE}/api/articles`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(articleData) }); const data = await response.json(); if (data.success) { showToast(id ? '文章已更新' : '文章已添加', 'success'); closeModal('article-modal'); loadArticles(); } else { showToast('保存失败: ' + data.error, 'error'); } } catch (error) { showToast('保存出错', 'error'); } } // 删除文章 async function deleteArticle(id) { if (!confirm('确定要删除这篇文章吗?')) return; try { const response = await fetch(`${API_BASE}/api/articles/${id}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { showToast('文章已删除', 'success'); loadArticles(); } else { showToast('删除失败', 'error'); } } catch (error) { showToast('删除出错', 'error'); } } // 删除当前文章 function deleteCurrentArticle() { if (currentArticleId) { deleteArticle(currentArticleId); closeModal('detail-modal'); } } // 批量删除 async function batchDelete() { if (selectedIds.size === 0) return; if (!confirm(`确定要删除选中的 ${selectedIds.size} 篇文章吗?`)) return; let deleted = 0; for (const id of selectedIds) { try { await fetch(`${API_BASE}/api/articles/${id}`, { method: 'DELETE' }); deleted++; } catch (error) { console.error(`删除 ${id} 失败:`, error); } } selectedIds.clear(); showToast(`已删除 ${deleted} 篇文章`, 'success'); loadArticles(); } // 导出文章 async function exportArticles() { const keyword = document.getElementById('search-input').value.trim(); const category = document.getElementById('category-filter').value; let exportArticles = articles; // 如果有搜索条件,获取全部匹配的 if (keyword || category) { const params = new URLSearchParams(); if (keyword) params.append('q', keyword); if (category) params.append('category', category); const response = await fetch(`${API_BASE}/api/articles/search?${params.toString()}`); const data = await response.json(); if (data.success) { exportArticles = data.articles; } } if (exportArticles.length === 0) { showToast('没有可导出的文章', 'error'); return; } // 导出为 JSON const exportData = exportArticles.map(a => ({ product_names: safeParseJSON(a.product_names, []), category: a.category, keywords: safeParseJSON(a.keywords, []), summary: a.summary, content: a.content, source: a.source, url: a.url })); const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `articles_${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); showToast(`已导出 ${exportArticles.length} 篇文章`, 'success'); } // 关闭模态框 function closeModal(modalId) { document.getElementById(modalId).classList.remove('active'); } // 显示提示 function showToast(message, type = '') { const toast = document.getElementById('toast'); toast.textContent = message; toast.className = `toast active ${type}`; setTimeout(() => { toast.classList.remove('active'); }, 3000); } // HTML转义 function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 安全解析JSON function safeParseJSON(str, defaultVal) { try { return JSON.parse(str || JSON.stringify(defaultVal)); } catch { return defaultVal; } } // 日期格式化 function formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); }