release: v2.2.0 - 博查新闻搜索功能 (生产环境部署版)

核心功能:
  - 集成博查Web Search API自动获取网站相关新闻
  - 智能新闻更新机制(每日首次访问触发)
  - 精确新闻搜索(使用引号强制匹配网站名称)
  - News模型扩展(source_name, source_icon字段)
  - 网站详情页新闻展示模块
  - 新闻来源网站信息展示
  - 自动去重防止重复新闻

  技术实现:
  - NewsSearcher工具类封装博查API
  - 数据库迁移脚本migrate_news_fields.py
  - 测试脚本test_news_feature.py
  - 定期任务脚本fetch_news_cron.py
  - API路由:/api/fetch-site-news, /api/fetch-all-news

  配置优化:
  - 修复manage.sh路径和启动命令
  - 博查API配置(BOCHA_API_KEY, BOCHA_BASE_URL)
  - 新闻搜索参数配置

  界面优化:
  - 详情页新闻模块(左侧主栏)
  - 相似推荐模块(右侧边栏)
  - 首页标签图标修复
  - 后台添加修改密码功能
  - 登录页面优化

  部署信息:
  - 部署日期: 2025-12-30
  - 部署方式: 手动上传文件
  - 数据库: 已迁移(添加source_name和source_icon字段)
This commit is contained in:
ZJPB Admin
2025-12-30 23:44:27 +08:00
parent 9f5d006090
commit b00e52e1e0
12 changed files with 1255 additions and 107 deletions

View File

@@ -41,6 +41,7 @@
display: flex;
gap: 8px;
flex-wrap: wrap;
position: relative;
}
.category-tab {
@@ -58,6 +59,74 @@
gap: 8px;
}
.category-tab .tag-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 20px;
padding: 0 6px;
background: rgba(100, 116, 139, 0.1);
color: var(--text-secondary);
font-size: 12px;
font-weight: 600;
border-radius: 10px;
transition: all 0.2s;
}
.category-tab.active .tag-count {
background: rgba(255, 255, 255, 0.25);
color: white;
}
.category-tab:hover .tag-count {
background: rgba(59, 130, 246, 0.15);
color: var(--primary-blue);
}
.category-tab.active:hover .tag-count {
background: rgba(255, 255, 255, 0.35);
color: white;
}
.category-tab.hidden-tag {
display: none;
}
.category-tab.show-all .hidden-tag {
display: inline-flex;
}
.more-tags-btn {
padding: 10px 20px;
border: 1px solid var(--border-color);
border-radius: 50px;
background: var(--bg-white);
color: var(--primary-blue);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 8px;
user-select: none;
}
.more-tags-btn:hover {
border-color: var(--primary-blue);
background: rgba(59, 130, 246, 0.05);
}
.more-tags-btn .expand-icon {
font-size: 14px;
transition: transform 0.2s;
}
.more-tags-btn.expanded .expand-icon {
transform: rotate(180deg);
}
.category-tab:hover {
border-color: var(--primary-blue);
color: var(--primary-blue);
@@ -69,8 +138,8 @@
color: white;
}
.category-tab .material-symbols-outlined {
font-size: 18px;
.category-tab .icon {
font-size: 16px;
}
/* 工具网格 */
@@ -187,10 +256,6 @@
white-space: nowrap;
}
.tool-views .material-symbols-outlined {
font-size: 16px;
}
/* 分页 */
.pagination {
display: flex;
@@ -243,7 +308,7 @@
padding: 80px 20px;
}
.empty-state .material-symbols-outlined {
.empty-state .icon {
font-size: 64px;
color: var(--text-muted);
margin-bottom: 16px;
@@ -286,25 +351,32 @@
<!-- 分类过滤 -->
<div class="categories" id="categories">
<div class="category-tabs">
<div class="category-tabs" id="categoryTabs">
<a href="/" class="category-tab {% if not selected_tag %}active{% endif %}">
All Tools
</a>
{% for tag in tags %}
<a href="/?tag={{ tag.slug }}" class="category-tab {% if selected_tag and selected_tag.id == tag.id %}active{% endif %}">
<span class="material-symbols-outlined">
{% if 'Chat' in tag.name or 'GPT' in tag.name %}chat
{% elif 'Image' in tag.name or '图' in tag.name %}image
{% elif 'Video' in tag.name or '视频' in tag.name %}videocam
{% elif 'Writing' in tag.name or '写作' in tag.name %}edit_note
{% elif 'Coding' in tag.name or '代码' in tag.name %}code
{% elif 'Audio' in tag.name or '音频' in tag.name %}graphic_eq
{% elif '3D' in tag.name %}view_in_ar
{% else %}label{% endif %}
<a href="/?tag={{ tag.slug }}" class="category-tab {% if selected_tag and selected_tag.id == tag.id %}active{% endif %} {% if loop.index > 10 %}hidden-tag{% endif %}" data-tag-index="{{ loop.index }}">
<span>
{% if 'Chat' in tag.name or 'GPT' in tag.name or '对话' in tag.name %}💬
{% elif 'Image' in tag.name or '图' in tag.name %}🖼️
{% elif 'Video' in tag.name or '视频' in tag.name %}🎬
{% elif 'Writing' in tag.name or '写作' in tag.name %}✍️
{% elif 'Coding' in tag.name or '代码' in tag.name %}💻
{% elif 'Audio' in tag.name or '音频' in tag.name %}🎵
{% elif '3D' in tag.name %}🎨
{% else %}🏷️{% endif %}
</span>
{{ tag.name }}
<span class="tag-count">{{ tag_counts.get(tag.id, 0) }}</span>
</a>
{% endfor %}
{% if tags|length > 10 %}
<div class="more-tags-btn" id="moreTagsBtn">
<span>更多</span>
<span class="expand-icon"></span>
</div>
{% endif %}
</div>
</div>
@@ -313,7 +385,7 @@
{% if search_query %}
<div style="margin-bottom: 24px; padding: 16px; background: #f1f5f9; border-radius: 8px;">
<p style="margin: 0; color: #64748b; font-size: 14px;">
<span class="material-symbols-outlined" style="font-size: 18px; vertical-align: middle;">search</span>
<span style="font-size: 18px; vertical-align: middle;">🔍</span>
搜索 "{{ search_query }}" 的结果:找到 {{ pagination.total if pagination else sites|length }} 个工具
<a href="/" style="margin-left: 12px; color: #0ea5e9; text-decoration: none;">清除搜索</a>
</p>
@@ -329,7 +401,7 @@
{% else %}
<div class="tool-logo" style="background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%);"></div>
{% endif %}
<span class="material-symbols-outlined tool-link-icon">north_east</span>
<span class="tool-link-icon"></span>
</div>
<h3 class="tool-name">{{ site.name }}</h3>
<p class="tool-description">{{ site.short_desc or site.description }}</p>
@@ -340,7 +412,7 @@
{% endfor %}
</div>
<div class="tool-views">
<span class="material-symbols-outlined">visibility</span>
<span>👁</span>
<span>{% if site.view_count >= 1000 %}{{ (site.view_count / 1000) | round(1) }}k{% else %}{{ site.view_count | default(0) }}{% endif %}</span>
</div>
</div>
@@ -354,11 +426,11 @@
<!-- 上一页 -->
{% if pagination.has_prev %}
<a href="?page={{ pagination.prev_num }}{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">
<span class="material-symbols-outlined">chevron_left</span>
<span></span>
</a>
{% else %}
<a href="#" class="disabled">
<span class="material-symbols-outlined">chevron_left</span>
<span></span>
</a>
{% endif %}
@@ -391,22 +463,52 @@
<!-- 下一页 -->
{% if pagination.has_next %}
<a href="?page={{ pagination.next_num }}{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">
<span class="material-symbols-outlined">chevron_right</span>
<span></span>
</a>
{% else %}
<a href="#" class="disabled">
<span class="material-symbols-outlined">chevron_right</span>
<span></span>
</a>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="empty-state">
<span class="material-symbols-outlined">search_off</span>
<span class="icon">🔍</span>
<h3>暂无工具</h3>
<p>{% if selected_tag %}该分类下还没有工具{% else %}还没有添加任何工具{% endif %}</p>
</div>
{% endif %}
</div>
</div>
<script>
// 标签展开/收起功能
document.addEventListener('DOMContentLoaded', function() {
const moreTagsBtn = document.getElementById('moreTagsBtn');
if (moreTagsBtn) {
moreTagsBtn.addEventListener('click', function() {
const categoryTabs = document.getElementById('categoryTabs');
const hiddenTags = categoryTabs.querySelectorAll('.hidden-tag');
const isExpanded = moreTagsBtn.classList.contains('expanded');
if (isExpanded) {
// 收起
hiddenTags.forEach(tag => {
tag.style.display = 'none';
});
moreTagsBtn.classList.remove('expanded');
moreTagsBtn.querySelector('span:first-child').textContent = '更多';
} else {
// 展开
hiddenTags.forEach(tag => {
tag.style.display = 'inline-flex';
});
moreTagsBtn.classList.add('expanded');
moreTagsBtn.querySelector('span:first-child').textContent = '收起';
}
});
}
});
</script>
{% endblock %}