From 4d1fffccd1fc78b4f9fd8a9de620ca173761af46 Mon Sep 17 00:00:00 2001 From: hz4th_coder Date: Mon, 13 Jul 2026 17:35:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=8B=AC=E7=AB=8B=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=BA=93=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 /library 内容库独立页面 - 支持文章列表展示、搜索、分类筛选 - 支持查看详情、编辑、删除 - 支持批量选择和批量删除 - 支持导出为 JSON 文件 - 分页显示 - 主页内容库区域改为入口链接 --- app.py | 5 + static/css/library.css | 327 ++++++++++++++++++++++++++++ static/css/style.css | 33 +++ static/js/library.js | 482 +++++++++++++++++++++++++++++++++++++++++ templates/index.html | 18 +- templates/library.html | 162 ++++++++++++++ 6 files changed, 1016 insertions(+), 11 deletions(-) create mode 100644 static/css/library.css create mode 100644 static/js/library.js create mode 100644 templates/library.html diff --git a/app.py b/app.py index cd32590..df966eb 100644 --- a/app.py +++ b/app.py @@ -43,6 +43,11 @@ def index(): def search_page(): return render_template('search.html') +# 内容库页面 +@app.route('/library') +def library_page(): + return render_template('library.html') + # API首页 @app.route('/api') def api_index(): diff --git a/static/css/library.css b/static/css/library.css new file mode 100644 index 0000000..aa3cdb1 --- /dev/null +++ b/static/css/library.css @@ -0,0 +1,327 @@ +/* 内容库页面专用样式 */ + +.library-container { + max-width: 1400px; + margin: 0 auto; + padding: 20px; +} + +/* 头部 */ +.library-header { + background: white; + padding: 20px 30px; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.header-left { + display: flex; + align-items: center; + gap: 20px; +} + +.back-link { + color: #667eea; + text-decoration: none; + display: flex; + align-items: center; + gap: 5px; + font-size: 14px; +} + +.back-link:hover { + text-decoration: underline; +} + +.library-header h1 { + color: #333; + font-size: 24px; + display: flex; + align-items: center; + gap: 10px; +} + +.stats-info { + font-size: 14px; + color: #666; +} + +/* 工具栏 */ +.toolbar { + background: white; + padding: 15px 20px; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.toolbar-left { + display: flex; + gap: 15px; + align-items: center; +} + +.search-box { + position: relative; +} + +.search-box i { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #999; +} + +.search-box input { + padding: 10px 15px 10px 35px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; + width: 300px; +} + +.search-box input:focus { + outline: none; + border-color: #667eea; +} + +.category-select { + padding: 10px 15px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; + background: white; + cursor: pointer; +} + +.toolbar-right { + display: flex; + gap: 10px; +} + +/* 文章列表区域 */ +.articles-section { + background: white; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); +} + +.articles-header { + padding: 15px 20px; + background: #f8f9fa; + border-bottom: 1px solid #e9ecef; + display: flex; + justify-content: space-between; + align-items: center; +} + +.checkbox-wrapper { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 14px; +} + +.checkbox-wrapper input { + width: 18px; + height: 18px; + cursor: pointer; +} + +.batch-actions { + display: flex; + align-items: center; + gap: 15px; +} + +#selected-count { + font-size: 14px; + color: #666; +} + +.articles-body { + padding: 20px; +} + +.articles-list { + display: flex; + flex-direction: column; + gap: 15px; +} + +/* 文章卡片 */ +.article-card { + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 20px; + transition: all 0.2s; +} + +.article-card:hover { + border-color: #667eea; + background: #f8f9fa; +} + +.article-card.selected { + border-color: #667eea; + background: #f0f4ff; +} + +.article-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 10px; +} + +.article-select { + width: 18px; + height: 18px; + cursor: pointer; +} + +.article-title { + font-size: 16px; + font-weight: 500; + color: #333; + cursor: pointer; + flex: 1; +} + +.article-title:hover { + color: #667eea; +} + +.article-actions { + display: flex; + gap: 8px; +} + +.article-meta { + display: flex; + gap: 15px; + font-size: 13px; + color: #666; + margin-bottom: 10px; +} + +.article-meta span { + display: flex; + align-items: center; + gap: 4px; +} + +.article-category { + background: #e0e7ff; + color: #3730a3; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; +} + +.article-summary { + font-size: 14px; + color: #666; + line-height: 1.6; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.article-tags { + display: flex; + gap: 6px; + margin-top: 10px; +} + +.article-tag { + background: #f3f4f6; + color: #6b7280; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; +} + +/* 分页 */ +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; + margin-top: 20px; +} + +#page-info { + font-size: 14px; + color: #666; +} + +/* 空状态 */ +.empty-text { + text-align: center; + color: #999; + padding: 40px; +} + +.loading-text { + text-align: center; + color: #666; + padding: 40px; +} + +/* 详情模态框 */ +#detail-body .detail-section { + margin-bottom: 20px; +} + +#detail-body .detail-section h4 { + color: #667eea; + margin-bottom: 10px; + font-size: 16px; +} + +#detail-body .detail-content { + white-space: pre-wrap; + background: #f8f9fa; + padding: 15px; + border-radius: 8px; + max-height: 400px; + overflow-y: auto; +} + +/* 响应式 */ +@media (max-width: 768px) { + .toolbar { + flex-direction: column; + gap: 15px; + } + + .toolbar-left, .toolbar-right { + width: 100%; + flex-wrap: wrap; + } + + .search-box input { + width: 100%; + } + + .article-card-header { + flex-direction: column; + gap: 10px; + } + + .article-actions { + width: 100%; + justify-content: flex-end; + } +} \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 9a247a4..a504740 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -768,4 +768,37 @@ body { .search-entrance-link small { font-size: 13px; opacity: 0.8; +} + +/* 内容库入口链接 */ +.library-entrance-link { + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 15px; + padding: 40px 80px; + background: linear-gradient(135deg, #10b981 0%, #059669 100%); + color: white; + text-decoration: none; + border-radius: 12px; + transition: all 0.3s; +} + +.library-entrance-link:hover { + transform: translateY(-3px); + box-shadow: 0 10px 30px rgba(16, 185, 129, 0.4); +} + +.library-entrance-link i { + font-size: 48px; +} + +.library-entrance-link span { + font-size: 18px; + font-weight: 500; +} + +.library-entrance-link small { + font-size: 13px; + opacity: 0.8; } \ No newline at end of file diff --git a/static/js/library.js b/static/js/library.js new file mode 100644 index 0000000..0a42130 --- /dev/null +++ b/static/js/library.js @@ -0,0 +1,482 @@ +// 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(', ') || '未命名')} +
+
+ + +
+
+ +
${escapeHtml(article.summary || '')}
+ ${keywords.length > 0 ? ` + + ` : ''} +
+ `; + }).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' + }); +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index e6ff432..6b6b3e9 100644 --- a/templates/index.html +++ b/templates/index.html @@ -97,21 +97,17 @@ - +

内容库文章

-
- - -
-
diff --git a/templates/library.html b/templates/library.html new file mode 100644 index 0000000..ca497d1 --- /dev/null +++ b/templates/library.html @@ -0,0 +1,162 @@ + + + + + + 内容库 - 参数数据自动化管理系统 + + + + + +
+ +
+
+ + 返回主页 + +

内容库

+
+
+
+ 0 篇文章 +
+
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + +
+
+
+
加载中...
+
+
+
+ + + +
+ + +
+ +
+ + + + + +
+ + + + \ No newline at end of file