From e13e4074f158ce1916d8014cc3fd50982e8419c0 Mon Sep 17 00:00:00 2001 From: hz4th_coder Date: Mon, 13 Jul 2026 16:38:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=92=E8=81=94=E7=BD=91=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E9=A1=B5=E9=9D=A2=20+=20=E4=B8=80=E9=94=AE?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E5=85=A8=E9=83=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增独立搜索页面 /search - 支持一键保存所有搜索结果到内容库 - 支持自动抓取和自动保存选项 - 显示保存进度条 - 主页添加搜索入口链接 - 搜索结果状态实时显示(已抓取/已保存) --- app.py | 5 + static/css/search.css | 334 ++++++++++++++++++++++++++++++++++++ static/css/style.css | 33 ++++ static/js/search.js | 384 ++++++++++++++++++++++++++++++++++++++++++ templates/index.html | 20 +-- templates/search.html | 116 +++++++++++++ 6 files changed, 879 insertions(+), 13 deletions(-) create mode 100644 static/css/search.css create mode 100644 static/js/search.js create mode 100644 templates/search.html diff --git a/app.py b/app.py index 6018d28..cd32590 100644 --- a/app.py +++ b/app.py @@ -38,6 +38,11 @@ app.register_blueprint(system_bp) def index(): return render_template('index.html') +# 搜索页面 +@app.route('/search') +def search_page(): + return render_template('search.html') + # API首页 @app.route('/api') def api_index(): diff --git a/static/css/search.css b/static/css/search.css new file mode 100644 index 0000000..192ba0b --- /dev/null +++ b/static/css/search.css @@ -0,0 +1,334 @@ +/* 搜索页面专用样式 */ + +.search-container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* 头部 */ +.search-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; +} + +.search-header h1 { + color: #333; + font-size: 24px; + display: flex; + align-items: center; + gap: 10px; +} + +/* 搜索框 */ +.search-box { + background: white; + padding: 30px; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + margin-bottom: 20px; +} + +.search-input-group { + display: flex; + gap: 15px; + margin-bottom: 15px; +} + +.search-input-large { + flex: 1; + padding: 15px 20px; + border: 2px solid #e9ecef; + border-radius: 8px; + font-size: 18px; +} + +.search-input-large:focus { + outline: none; + border-color: #667eea; +} + +.count-input { + width: 80px; + padding: 15px; + border: 2px solid #e9ecef; + border-radius: 8px; + font-size: 16px; + text-align: center; +} + +.btn-large { + padding: 15px 30px; + font-size: 16px; +} + +.search-options { + display: flex; + gap: 20px; +} + +.checkbox-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 14px; + color: #666; +} + +.checkbox-label input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; +} + +/* 搜索进度 */ +.search-progress { + background: white; + padding: 20px; + border-radius: 12px; + margin-bottom: 20px; +} + +.progress-bar { + height: 8px; + background: #e9ecef; + border-radius: 4px; + overflow: hidden; + margin-bottom: 10px; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #667eea, #764ba2); + width: 0%; + transition: width 0.3s; +} + +.progress-text { + text-align: center; + color: #666; + font-size: 14px; +} + +/* 结果区域 */ +.results-section { + background: white; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); +} + +.results-header { + padding: 20px; + background: #f8f9fa; + border-bottom: 1px solid #e9ecef; + display: flex; + justify-content: space-between; + align-items: center; +} + +.results-header h2 { + color: #667eea; + font-size: 18px; + display: flex; + align-items: center; + gap: 8px; +} + +.results-actions { + display: flex; + gap: 15px; + align-items: center; +} + +.results-count { + color: #666; + font-size: 14px; +} + +.results-body { + padding: 20px; +} + +/* 搜索结果列表 */ +.search-results { + display: flex; + flex-direction: column; + gap: 15px; +} + +.result-item { + display: flex; + align-items: flex-start; + gap: 15px; + padding: 20px; + border: 1px solid #e9ecef; + border-radius: 8px; + transition: all 0.2s; +} + +.result-item:hover { + border-color: #667eea; + background: #f8f9fa; +} + +.result-item.saved { + border-color: #10b981; + background: #f0fdf4; +} + +.result-item.fetched { + border-color: #3b82f6; +} + +.result-number { + width: 40px; + height: 40px; + background: #667eea; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 16px; +} + +.result-item.saved .result-number { + background: #10b981; +} + +.result-main { + flex: 1; +} + +.result-title { + font-size: 16px; + color: #333; + cursor: pointer; + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 10px; +} + +.result-title:hover { + color: #667eea; +} + +.result-title .saved-icon { + color: #10b981; +} + +.result-url { + font-size: 13px; + color: #666; + margin-bottom: 8px; +} + +.result-url a { + color: #3b82f6; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 4px; +} + +.result-url a:hover { + text-decoration: underline; +} + +.result-content-preview { + font-size: 13px; + color: #999; + max-height: 60px; + overflow: hidden; + text-overflow: ellipsis; +} + +.result-actions { + display: flex; + gap: 10px; +} + +/* 保存进度 */ +.save-progress { + position: fixed; + bottom: 80px; + left: 50%; + transform: translateX(-50%); + background: white; + padding: 20px 40px; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 100; + min-width: 300px; +} + +.progress-info { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; + font-size: 14px; + color: #333; +} + +.progress-info i { + animation: spin 1s linear infinite; +} + +/* 响应式 */ +@media (max-width: 768px) { + .search-input-group { + flex-direction: column; + } + + .search-options { + flex-direction: column; + gap: 10px; + } + + .results-header { + flex-direction: column; + gap: 15px; + } + + .results-actions { + flex-wrap: wrap; + } + + .result-item { + flex-direction: column; + } + + .result-actions { + width: 100%; + justify-content: space-between; + } +} \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 0d1c7d4..9a247a4 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -735,4 +735,37 @@ body { #internet-search-status i { font-size: 16px; +} + +/* 搜索入口链接 */ +.search-entrance-link { + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 15px; + padding: 40px 80px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + text-decoration: none; + border-radius: 12px; + transition: all 0.3s; +} + +.search-entrance-link:hover { + transform: translateY(-3px); + box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4); +} + +.search-entrance-link i { + font-size: 48px; +} + +.search-entrance-link span { + font-size: 18px; + font-weight: 500; +} + +.search-entrance-link small { + font-size: 13px; + opacity: 0.8; } \ No newline at end of file diff --git a/static/js/search.js b/static/js/search.js new file mode 100644 index 0000000..9a2c8e4 --- /dev/null +++ b/static/js/search.js @@ -0,0 +1,384 @@ +// API基础地址 +const API_BASE = ''; + +// 搜索结果存储 +let searchResults = []; +let currentResultIndex = -1; + +// 页面加载初始化 +document.addEventListener('DOMContentLoaded', () => { + // 回车搜索 + document.getElementById('search-keyword').addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + doSearch(); + } + }); +}); + +// 执行搜索 +async function doSearch() { + const keyword = document.getElementById('search-keyword').value.trim(); + const maxResults = parseInt(document.getElementById('search-count').value) || 10; + const autoFetch = document.getElementById('auto-fetch').checked; + const autoSave = document.getElementById('auto-save').checked; + + if (!keyword) { + showToast('请输入搜索关键词', 'error'); + return; + } + + // 更新状态 + updateStatus('searching', '搜索中...'); + document.getElementById('search-btn').disabled = true; + + // 显示进度 + const progress = document.getElementById('search-progress'); + progress.style.display = 'block'; + document.getElementById('progress-fill').style.width = '0%'; + document.getElementById('progress-text').textContent = '正在搜索...'; + + 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) { + searchResults = data.results.map(r => ({ + ...r, + fetched: false, + saved: false, + content: '' + })); + + displayResults(); + updateStatus('success', `找到 ${searchResults.length} 条结果`); + document.getElementById('results-count').textContent = `${searchResults.length} 条结果`; + document.getElementById('save-all-btn').disabled = searchResults.length === 0; + + document.getElementById('progress-fill').style.width = '100%'; + document.getElementById('progress-text').textContent = '搜索完成!'; + + // 自动抓取和保存 + if (autoFetch && searchResults.length > 0) { + setTimeout(() => fetchAllResults(autoSave), 500); + } + } else { + updateStatus('error', '搜索失败'); + showToast('搜索失败: ' + data.error, 'error'); + } + } catch (error) { + updateStatus('error', '搜索出错'); + showToast('搜索出错', 'error'); + console.error(error); + } + + setTimeout(() => { + progress.style.display = 'none'; + }, 1000); + + document.getElementById('search-btn').disabled = false; +} + +// 显示搜索结果 +function displayResults() { + const container = document.getElementById('search-results'); + + if (searchResults.length === 0) { + container.innerHTML = '
未找到相关结果
'; + return; + } + + container.innerHTML = searchResults.map((r, i) => ` +
+
${i + 1}
+
+
+ ${r.saved ? '' : ''} + ${escapeHtml(r.title)} +
+ + ${r.content ? `
${escapeHtml(r.content.substring(0, 100))}...
` : ''} +
+
+ + +
+
+ `).join(''); +} + +// 抓取单个结果 +async function fetchResult(index) { + const result = searchResults[index]; + if (!result || result.fetched) return; + + const btn = document.getElementById(`fetch-btn-${index}`); + btn.disabled = true; + btn.innerHTML = ' 抓取中...'; + + 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] + }) + }); + + const data = await response.json(); + + if (data.success) { + searchResults[index].fetched = true; + searchResults[index].content = data.data.content; + searchResults[index].title = data.data.title || result.title; + + btn.innerHTML = ' 已抓取'; + + // 更新结果显示 + const item = document.getElementById(`result-${index}`); + item.classList.add('fetched'); + + showToast('抓取成功', 'success'); + displayResults(); + } else { + btn.disabled = false; + btn.innerHTML = ' 抓取'; + showToast('抓取失败: ' + data.error, 'error'); + } + } catch (error) { + btn.disabled = false; + btn.innerHTML = ' 抓取'; + showToast('抓取出错', 'error'); + } +} + +// 保存单个结果 +async function saveResult(index) { + const result = searchResults[index]; + if (!result) return; + + // 如果未抓取,先抓取 + if (!result.fetched) { + await fetchResult(index); + if (!searchResults[index].fetched) return; + } + + const btn = document.getElementById(`save-btn-${index}`); + btn.disabled = true; + btn.innerHTML = ' 保存中...'; + + try { + const response = await fetch(`${API_BASE}/api/articles`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + product_names: [searchResults[index].title], + category: '', + keywords: [], + summary: searchResults[index].content.substring(0, 200), + content: searchResults[index].content, + source: searchResults[index].url, + url: searchResults[index].url + }) + }); + + const data = await response.json(); + + if (data.success) { + searchResults[index].saved = true; + btn.innerHTML = ' 已保存'; + + const item = document.getElementById(`result-${index}`); + item.classList.add('saved'); + + showToast('保存成功', 'success'); + displayResults(); + } else { + btn.disabled = false; + btn.innerHTML = ' 保存'; + showToast('保存失败: ' + data.error, 'error'); + } + } catch (error) { + btn.disabled = false; + btn.innerHTML = ' 保存'; + showToast('保存出错', 'error'); + } +} + +// 一键保存全部 +async function saveAllResults() { + const unsaved = searchResults.filter(r => !r.saved); + if (unsaved.length === 0) { + showToast('没有需要保存的结果', 'error'); + return; + } + + const progress = document.getElementById('save-progress'); + progress.style.display = 'block'; + + let saved = 0; + const total = unsaved.length; + + for (let i = 0; i < searchResults.length; i++) { + if (searchResults[i].saved) continue; + + document.getElementById('save-progress-text').textContent = + `正在保存 ${saved + 1}/${total}...`; + document.getElementById('save-fill').style.width = + `${(saved / total) * 100}%`; + + await saveResult(i); + saved++; + } + + document.getElementById('save-fill').style.width = '100%'; + document.getElementById('save-progress-text').textContent = '保存完成!'; + + setTimeout(() => { + progress.style.display = 'none'; + }, 1500); + + showToast(`成功保存 ${saved} 条结果`, 'success'); +} + +// 抓取所有结果 +async function fetchAllResults(autoSave) { + const progress = document.getElementById('search-progress'); + progress.style.display = 'block'; + + const total = searchResults.length; + let fetched = 0; + + for (let i = 0; i < searchResults.length; i++) { + if (searchResults[i].fetched) { + fetched++; + continue; + } + + document.getElementById('progress-text').textContent = + `正在抓取 ${fetched + 1}/${total}...`; + document.getElementById('progress-fill').style.width = + `${(fetched / total) * 100}%`; + + await fetchResult(i); + fetched++; + } + + document.getElementById('progress-fill').style.width = '100%'; + document.getElementById('progress-text').textContent = '抓取完成!'; + + setTimeout(() => { + progress.style.display = 'none'; + }, 1000); + + // 自动保存 + if (autoSave) { + setTimeout(() => saveAllResults(), 500); + } +} + +// 显示结果详情 +function showResultDetail(index) { + currentResultIndex = index; + const result = searchResults[index]; + + const body = document.getElementById('result-detail-body'); + body.innerHTML = ` +
+
+ + ${escapeHtml(result.title)} +
+ +
+ + ${result.fetched ? '已抓取' : '未抓取'} / ${result.saved ? '已保存' : '未保存'} +
+
+ ${result.content ? ` +
+

页面内容

+
${escapeHtml(result.content.substring(0, 3000))}${result.content.length > 3000 ? '\n...(内容过长,已截断)' : ''}
+
+ ` : '

尚未抓取内容,点击"保存到内容库"将自动抓取并保存

'} + `; + + document.getElementById('result-detail-modal').classList.add('active'); +} + +// 保存当前结果 +async function saveCurrentResult() { + if (currentResultIndex >= 0) { + await saveResult(currentResultIndex); + closeModal('result-detail-modal'); + } +} + +// 清空结果 +function clearResults() { + searchResults = []; + displayResults(); + document.getElementById('results-count').textContent = '0 条结果'; + document.getElementById('save-all-btn').disabled = true; + updateStatus('ready', '就绪'); +} + +// 更新状态 +function updateStatus(status, text) { + const dot = document.getElementById('search-status'); + const statusText = document.getElementById('status-text'); + + dot.className = 'status-dot'; + if (status === 'searching') { + dot.style.background = '#f59e0b'; + } else if (status === 'success') { + dot.style.background = '#10b981'; + } else if (status === 'error') { + dot.style.background = '#ef4444'; + } else { + dot.style.background = '#10b981'; + } + + statusText.textContent = text; +} + +// 关闭模态框 +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; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 8cb47e6..e6ff432 100644 --- a/templates/index.html +++ b/templates/index.html @@ -116,23 +116,17 @@ - +

互联网搜索

-
- - - -
- diff --git a/templates/search.html b/templates/search.html new file mode 100644 index 0000000..a59d20a --- /dev/null +++ b/templates/search.html @@ -0,0 +1,116 @@ + + + + + + 互联网搜索 - 参数数据自动化管理系统 + + + + + +
+ +
+
+ + 返回主页 + +

互联网搜索

+
+
+
+ + 就绪 +
+
+
+ + + + + + + + +
+
+

搜索结果

+
+ 0 条结果 + + +
+
+
+
+
输入关键词开始搜索
+
+
+
+ + + +
+ + + + + +
+ + + + \ No newline at end of file