// API基础地址 const API_BASE = ''; // 当前选中的文章ID let currentArticleId = null; // 页面加载时初始化 document.addEventListener('DOMContentLoaded', () => { refreshData(); loadConfig(); // 搜索框事件 document.getElementById('article-search').addEventListener('input', (e) => { searchArticles(e.target.value); }); // 自动刷新(每30秒) setInterval(refreshData, 30000); }); // 刷新所有数据 async function refreshData() { try { await Promise.all([ loadStats(), loadPendingProducts(), loadHistory(), loadQuickStats() ]); updateSystemStatus(true); } catch (error) { console.error('刷新数据失败:', error); updateSystemStatus(false); } } // 更新系统状态显示 function updateSystemStatus(isHealthy) { const statusDot = document.getElementById('system-status'); const statusText = document.getElementById('status-text'); if (isHealthy) { statusDot.classList.remove('error'); statusText.textContent = '系统正常'; } else { statusDot.classList.add('error'); statusText.textContent = '系统异常'; } } // 加载统计数据 async function loadStats() { try { const response = await fetch(`${API_BASE}/api/system/stats`); const data = await response.json(); if (data.success) { document.getElementById('articles-count').textContent = data.stats.total_articles; document.getElementById('pending-count').textContent = data.stats.pending_products; document.getElementById('processing-count').textContent = data.stats.processing_products; document.getElementById('success-count').textContent = data.stats.recent_success; document.getElementById('failed-count').textContent = data.stats.recent_failed; } } catch (error) { console.error('加载统计失败:', error); } } // 加载快速统计信息 async function loadQuickStats() { try { // 加载文章统计 const articlesResponse = await fetch(`${API_BASE}/api/articles?limit=1000`); const articlesData = await articlesResponse.json(); if (articlesData.success) { document.getElementById('quick-articles-count').textContent = articlesData.count; // 计算分类数 const categories = new Set(articlesData.articles.map(a => a.category).filter(c => c)); document.getElementById('quick-categories-count').textContent = categories.size; // 计算今日新增 const today = new Date().toISOString().split('T')[0]; const todayCount = articlesData.articles.filter(a => a.fetch_date && a.fetch_date.startsWith(today)).length; document.getElementById('quick-today-count').textContent = todayCount; } // 加载失败URL数 const failedResponse = await fetch(`${API_BASE}/api/articles/failed-urls`); const failedData = await failedResponse.json(); if (failedData.success) { document.getElementById('quick-failed-count').textContent = failedData.count; } } catch (error) { console.error('加载快速统计失败:', error); } } // 加载待处理产品 async function loadPendingProducts() { try { const response = await fetch(`${API_BASE}/api/products/pending`); const data = await response.json(); const tbody = document.getElementById('pending-table'); if (data.success && data.products.length > 0) { tbody.innerHTML = data.products.map(product => ` ${escapeHtml(product.product_name)} ${escapeHtml(product.category || '-')} ${escapeHtml(product.subcategory || '-')} ${product.priority} ${escapeHtml(product.source)} ${formatDate(product.created_at)} `).join(''); } else { tbody.innerHTML = '暂无待处理产品'; } } catch (error) { console.error('加载待处理产品失败:', error); } } // 加载文章列表 async function loadArticles() { try { const response = await fetch(`${API_BASE}/api/articles?limit=50`); const data = await response.json(); const container = document.getElementById('articles-list'); if (data.success && data.articles.length > 0) { container.innerHTML = data.articles.map(article => `
${escapeHtml(article.product_names.join(', '))}
${escapeHtml(article.category || '未分类')} ${formatDate(article.fetch_date)} ${escapeHtml(article.source)}
`).join(''); } else { container.innerHTML = '
暂无文章
'; } } catch (error) { console.error('加载文章失败:', error); } } // 搜索文章 async function searchArticles(keyword) { if (!keyword.trim()) { loadArticles(); return; } try { const response = await fetch(`${API_BASE}/api/articles/search?q=${encodeURIComponent(keyword)}`); const data = await response.json(); const container = document.getElementById('articles-list'); if (data.success && data.articles.length > 0) { container.innerHTML = data.articles.map(article => `
${escapeHtml(article.product_names.join(', '))}
${escapeHtml(article.category || '未分类')} ${formatDate(article.fetch_date)} ${escapeHtml(article.source)}
`).join(''); } else { container.innerHTML = '
未找到相关文章
'; } } catch (error) { console.error('搜索文章失败:', error); } } // 加载处理历史 async function loadHistory() { try { const response = await fetch(`${API_BASE}/api/products/history?limit=20`); const data = await response.json(); const tbody = document.getElementById('history-table'); if (data.success && data.history.length > 0) { tbody.innerHTML = data.history.map(item => ` ${escapeHtml(item.product_name)} ${escapeHtml(item.category || '-')} ${escapeHtml(item.subcategory || '-')} ${getStatusText(item.status)} ${escapeHtml(item.review_id || '-')} ${formatDate(item.submitted_at)} `).join(''); } else { tbody.innerHTML = '暂无处理历史'; } } catch (error) { console.error('加载处理历史失败:', error); } } // 加载系统配置 async function loadConfig() { try { const response = await fetch(`${API_BASE}/api/system/config`); const data = await response.json(); if (data.success) { document.getElementById('auto-process-enabled').value = data.config.auto_process_enabled; document.getElementById('process-interval').value = data.config.process_interval; document.getElementById('batch-size').value = data.config.batch_size; } } catch (error) { console.error('加载配置失败:', error); } } // 保存配置 async function saveConfig() { const config = { auto_process_enabled: document.getElementById('auto-process-enabled').value, process_interval: document.getElementById('process-interval').value, batch_size: document.getElementById('batch-size').value }; try { const response = await fetch(`${API_BASE}/api/system/config`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const data = await response.json(); if (data.success) { showToast('配置已保存', 'success'); } else { showToast('保存失败: ' + data.error, 'error'); } } catch (error) { showToast('保存配置失败', 'error'); } } // 显示添加产品模态框 function showAddProductModal() { document.getElementById('add-product-modal').classList.add('active'); document.getElementById('add-product-form').reset(); } // 显示添加文章模态框 function showAddArticleModal() { document.getElementById('add-article-modal').classList.add('active'); document.getElementById('add-article-form').reset(); } // 关闭模态框 function closeModal(modalId) { document.getElementById(modalId).classList.remove('active'); } // 添加产品 async function addProduct() { const batchProducts = document.getElementById('batch-products').value.trim(); let productsData; if (batchProducts) { // 批量添加 try { productsData = JSON.parse(batchProducts); } catch (e) { showToast('JSON格式错误', 'error'); return; } } else { // 单个添加 const productName = document.getElementById('product-name').value.trim(); if (!productName) { showToast('请输入产品名称', 'error'); return; } productsData = { product_name: productName, category: document.getElementById('product-category').value.trim(), subcategory: document.getElementById('product-subcategory').value.trim(), priority: parseInt(document.getElementById('product-priority').value), source: 'manual' }; } try { const response = await fetch(`${API_BASE}/api/products/pending`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(productsData) }); const data = await response.json(); if (data.success) { showToast(`成功添加 ${data.added_count || 1} 个产品`, 'success'); closeModal('add-product-modal'); loadPendingProducts(); loadStats(); } else { showToast('添加失败: ' + data.error, 'error'); } } catch (error) { showToast('添加产品失败', 'error'); } } // 添加文章 async function addArticle() { 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()), summary: summary, content: content, source: source, url: document.getElementById('article-url').value.trim() }; try { 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('文章添加成功', 'success'); closeModal('add-article-modal'); loadArticles(); loadStats(); } else { showToast('添加失败: ' + data.error, 'error'); } } catch (error) { showToast('添加文章失败', 'error'); } } // 处理单个产品 async function processProduct(productName, category, subcategory) { if (!confirm(`确定要处理产品 "${productName}" 吗?`)) { return; } showToast('正在启动处理...', ''); try { const response = await fetch(`${API_BASE}/api/process/start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ product_name: productName, category: category || '', subcategory: subcategory || '' }) }); const data = await response.json(); if (data.success) { showToast('处理流程已启动', 'success'); // 跳转到处理监控页面 setTimeout(() => { window.location.href = '/process'; }, 1000); } else { const errorMsg = data.error || data.message || '未知错误'; showToast('处理失败: ' + errorMsg, 'error'); } refreshData(); } catch (error) { console.error('处理产品失败:', error); showToast('处理产品失败', 'error'); } } // 批量处理 async function processBatch() { if (!confirm('确定要批量处理待处理产品吗?')) { return; } try { const response = await fetch(`${API_BASE}/api/products/process/batch`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ limit: 5 }) }); const data = await response.json(); if (data.success) { showToast(`已处理 ${data.processed} 个产品`, 'success'); } else { showToast('批量处理失败', 'error'); } refreshData(); } catch (error) { showToast('批量处理失败', 'error'); } } // 移除待处理产品 async function removeProduct(productName) { if (!confirm(`确定要从待处理列表移除 "${productName}" 吗?`)) { return; } try { const response = await fetch(`${API_BASE}/api/products/pending/${encodeURIComponent(productName)}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { showToast('产品已移除', 'success'); loadPendingProducts(); loadStats(); } else { showToast('移除失败: ' + data.error, 'error'); } } catch (error) { showToast('移除产品失败', 'error'); } } // 显示文章详情 async function showArticleDetail(articleId) { currentArticleId = articleId; try { const response = await fetch(`${API_BASE}/api/articles/${articleId}`); const data = await response.json(); if (data.success) { const article = data.article; const body = document.getElementById('article-detail-body'); body.innerHTML = `
${escapeHtml(article.product_names.join(', '))}
${escapeHtml(article.category || '未分类')}
${escapeHtml(article.keywords.join(', ') || '无')}
${escapeHtml(article.source)}
${formatDate(article.fetch_date)}
${escapeHtml(article.url || '无')}

摘要总结

${escapeHtml(article.summary)}

具体内容

${escapeHtml(article.content)}

`; document.getElementById('article-detail-modal').classList.add('active'); } } catch (error) { showToast('加载文章详情失败', 'error'); } } // 删除文章 async function deleteArticle() { if (!currentArticleId) { return; } if (!confirm('确定要删除这篇文章吗?')) { return; } try { const response = await fetch(`${API_BASE}/api/articles/${currentArticleId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { showToast('文章已删除', 'success'); closeModal('article-detail-modal'); loadArticles(); loadStats(); } else { showToast('删除失败: ' + data.error, 'error'); } } catch (error) { showToast('删除文章失败', 'error'); } } // 显示处理详情 function showProcessDetail(details) { const body = document.getElementById('process-detail-body'); if (details) { if (details.error) { body.innerHTML = `

错误信息

${escapeHtml(details.error)}

`; } else if (details.data) { body.innerHTML = `

提交数据

${JSON.stringify(details.data, null, 2)}
                    
`; } else { body.innerHTML = '

无详细信息

'; } } else { body.innerHTML = '

无详细信息

'; } document.getElementById('process-detail-modal').classList.add('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; } // 辅助函数:日期格式化 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' }); } // 辅助函数:获取优先级样式类 function getPriorityClass(priority) { if (priority >= 8) return 'priority-high'; if (priority >= 5) return 'priority-medium'; return 'priority-low'; } // 辅助函数:获取状态文本 function getStatusText(status) { const statusMap = { 'submitted': '已提交', 'processing': '处理中', 'failed': '失败', 'error': '错误', 'completed': '已完成' }; return statusMap[status] || status; } // ========== 互联网搜索功能 ========== let currentSearchResult = null; // 执行互联网搜索 async function doInternetSearch() { const keyword = document.getElementById('internet-search-keyword').value.trim(); const maxResults = parseInt(document.getElementById('internet-search-count').value) || 10; if (!keyword) { showToast('请输入搜索关键词', 'error'); return; } // 显示搜索状态 const statusDiv = document.getElementById('internet-search-status'); const resultsDiv = document.getElementById('internet-search-results'); const searchBtn = document.getElementById('search-btn'); statusDiv.innerHTML = ' 正在搜索...'; resultsDiv.innerHTML = '
搜索中...
'; searchBtn.disabled = true; try { const response = await fetch(`${API_BASE}/api/articles/internet-search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ keyword, max_results: maxResults }) }); const data = await response.json(); if (data.success) { statusDiv.innerHTML = ` 搜索完成,找到 ${data.count} 条结果`; if (data.results.length > 0) { resultsDiv.innerHTML = data.results.map((r, i) => `
${i + 1}
${escapeHtml(r.title)}
来源: ${escapeHtml(r.source)}
`).join(''); // 存储搜索结果供后续使用 window.lastSearchResults = data.results; } else { resultsDiv.innerHTML = '
未找到相关结果
'; } } else { statusDiv.innerHTML = ` 搜索失败: ${escapeHtml(data.error)}`; resultsDiv.innerHTML = '
搜索失败
'; } } catch (error) { statusDiv.innerHTML = ' 搜索出错'; resultsDiv.innerHTML = '
搜索出错,请稍后重试
'; console.error('搜索错误:', error); } searchBtn.disabled = false; } // 显示搜索结果详情 function showSearchResultDetail(index) { if (!window.lastSearchResults || !window.lastSearchResults[index]) { return; } currentSearchResult = window.lastSearchResults[index]; const body = document.getElementById('search-result-body'); body.innerHTML = `
${escapeHtml(currentSearchResult.title)}
${escapeHtml(currentSearchResult.url)}
${escapeHtml(currentSearchResult.source)}

提示

点击"抓取内容"按钮可以获取页面详细内容,然后保存到内容库。

`; document.getElementById('search-result-modal').classList.add('active'); } // 抓取并显示搜索结果内容 async function fetchAndShowResult(index) { if (!window.lastSearchResults || !window.lastSearchResults[index]) { return; } const result = window.lastSearchResults[index]; currentSearchResult = result; showToast('正在抓取页面内容...', ''); try { const response = await fetch(`${API_BASE}/api/articles/fetch`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: result.url, product_names: [result.title], category: '' }) }); const data = await response.json(); if (data.success) { // 更新当前搜索结果,添加抓取的内容 currentSearchResult = { ...result, fetched: true, fetchedContent: data.data }; const body = document.getElementById('search-result-body'); body.innerHTML = `
${escapeHtml(data.data.title)}
${escapeHtml(result.url)}
${escapeHtml(data.data.description || '无')}

页面内容

${escapeHtml(data.data.content.substring(0, 2000))}${data.data.content.length > 2000 ? '\n... (内容过长,已截断)' : ''}
`; document.getElementById('search-result-modal').classList.add('active'); showToast('内容抓取成功', 'success'); } else { showToast('抓取失败: ' + data.error, 'error'); } } catch (error) { showToast('抓取出错', 'error'); console.error('抓取错误:', error); } } // 快速保存搜索结果到内容库 async function quickSaveResult(index) { if (!window.lastSearchResults || !window.lastSearchResults[index]) { return; } const result = window.lastSearchResults[index]; // 先抓取内容再保存 showToast('正在抓取并保存...', ''); try { const response = await fetch(`${API_BASE}/api/articles/fetch`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: result.url, product_names: [result.title], category: '' }) }); const data = await response.json(); if (data.success) { showToast('已保存到内容库', 'success'); loadArticles(); loadStats(); } else { showToast('保存失败: ' + data.error, 'error'); } } catch (error) { showToast('保存出错', 'error'); } } // 从详情模态框保存到内容库 async function saveSearchResultToLibrary() { if (!currentSearchResult) { return; } // 如果还没有抓取内容,先抓取 if (!currentSearchResult.fetched) { await fetchAndShowResult(window.lastSearchResults.findIndex(r => r.url === currentSearchResult.url)); return; } showToast('已保存到内容库', 'success'); closeModal('search-result-modal'); loadArticles(); loadStats(); }