Files
zjpb.net/templates/index_new.html
Jowe da30394ed7 feat: 添加热门工具排行榜功能
v2.5.0 - 新增首页热门工具展示

新增功能:
- 在首页顶部展示按浏览量排序的前10个热门工具
- 响应式网格布局(桌面5列、平板3列、手机2列)
- 视觉突出的排行榜徽章和排名标识(前三名金银铜色)
- 仅在首页显示,筛选和搜索时隐藏

技术实现:
- app.py: 添加popular_sites查询逻辑
- templates/index_new.html: 新增热门工具展示区域和样式

用户体验提升:
- 帮助用户快速发现最受欢迎的AI工具
- 视觉层级清晰,排名一目了然

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-04 00:19:28 +08:00

694 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'base_new.html' %}
{% block title %}{% if selected_tag %}{{ selected_tag.seo_title or (selected_tag.name + ' - ZJPB AI工具导航') }}{% else %}ZJPB - 自己品吧 | 发现最新最好用的AI工具和产品{% endif %}{% endblock %}
{% block extra_head %}
<!-- v2.4新增: 标签页SEO优化 -->
{% if selected_tag %}
<meta name="description" content="{{ selected_tag.seo_description or (selected_tag.description + ' - 发现最好用的' + selected_tag.name + '工具') }}">
<meta name="keywords" content="{{ selected_tag.seo_keywords or (selected_tag.name + ',AI工具,自己品吧,ZJPB') }}">
<link rel="canonical" href="{{ request.url }}">
{% else %}
<meta name="description" content="ZJPB(自己品吧) - 发现最新最好用的AI工具和产品导航站涵盖AI写作、AI绘画、AI编程等多个领域">
<meta name="keywords" content="AI工具,AI导航,自己品吧,ZJPB,人工智能,AI产品">
{% endif %}
{% endblock %}
{% block extra_css %}
<style>
/* Hero区域 */
.hero {
padding-bottom: 40px;
background: var(--bg-page);
}
.hero-content {
max-width: 1280px;
margin: 0 auto;
padding: 0 24px;
}
.hero-subtitle {
font-size: 24px;
font-weight: 400;
color: #475569;
line-height: 1.5;
margin-bottom: 8px;
}
.hero-subtitle-en {
font-size: 16px;
font-weight: 400;
color: #64748b;
line-height: 1.6;
margin-bottom: 0;
}
/* 热门工具排行榜 */
.popular-section {
padding: 0 0 32px 0;
}
.popular-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.popular-title {
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.popular-badge {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
color: white;
font-size: 18px;
padding: 2px 8px;
border-radius: 4px;
}
.popular-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 16px;
}
.popular-item {
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 16px;
text-decoration: none;
transition: all 0.3s;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
position: relative;
overflow: hidden;
}
.popular-item:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
border-color: var(--primary-blue);
}
.popular-rank {
position: absolute;
top: 8px;
left: 8px;
width: 24px;
height: 24px;
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
color: #b8860b;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.popular-rank.rank-2 {
background: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 100%);
color: #696969;
}
.popular-rank.rank-3 {
background: linear-gradient(135deg, #cd7f32 0%, #e9a76f 100%);
color: #654321;
}
.popular-rank.rank-other {
background: #f1f5f9;
color: #64748b;
}
.popular-logo {
width: 56px;
height: 56px;
border-radius: 12px;
object-fit: cover;
margin-bottom: 12px;
}
.popular-name {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.popular-views {
display: flex;
align-items: center;
gap: 4px;
color: #94a3b8;
font-size: 12px;
}
.popular-fire {
font-size: 16px;
}
/* 分类过滤 */
.categories {
padding: 32px 0;
}
.category-tabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
position: relative;
}
.category-tab {
padding: 10px 20px;
border: 1px solid var(--border-color);
border-radius: 50px;
background: var(--bg-white);
color: var(--text-secondary);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
display: inline-flex;
align-items: center;
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);
}
.category-tab.active {
background: var(--primary-blue);
border-color: var(--primary-blue);
color: white;
}
.category-tab .icon {
font-size: 16px;
}
/* 工具网格 */
.tools-section {
padding: 32px 0 48px;
}
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 48px;
}
.tool-card {
background: var(--bg-white);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
text-decoration: none;
color: var(--text-primary);
transition: all 0.2s;
position: relative;
display: flex;
flex-direction: column;
}
.tool-card:hover {
border-color: var(--primary-blue);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
transform: translateY(-4px);
}
.tool-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 12px;
}
.tool-logo {
width: 48px;
height: 48px;
border-radius: 10px;
object-fit: cover;
flex-shrink: 0;
}
.tool-link-icon {
color: #cbd5e1;
transition: all 0.2s;
font-size: 20px;
}
.tool-card:hover .tool-link-icon {
color: var(--primary-blue);
transform: translate(2px, -2px);
}
.tool-name {
font-size: 18px;
font-weight: 700;
margin-bottom: 8px;
margin-top: 0;
color: var(--text-primary);
}
.tool-description {
font-size: 14px;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 16px;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.tool-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding-top: 16px;
border-top: 1px solid var(--border-color);
}
.tool-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
flex: 1;
min-width: 0;
}
.tool-tag {
padding: 4px 10px;
background: #f1f5f9;
color: #64748b;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.tool-views {
display: flex;
align-items: center;
gap: 4px;
color: #94a3b8;
font-size: 13px;
white-space: nowrap;
}
/* 分页 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.pagination a,
.pagination span {
min-width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
background: var(--bg-white);
color: var(--text-secondary);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.pagination a:hover {
border-color: var(--primary-blue);
color: var(--primary-blue);
}
.pagination .active {
background: var(--primary-blue);
border-color: var(--primary-blue);
color: white;
}
.pagination .disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination .disabled:hover {
border-color: var(--border-color);
color: var(--text-secondary);
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 80px 20px;
}
.empty-state .icon {
font-size: 64px;
color: var(--text-muted);
margin-bottom: 16px;
}
.empty-state h3 {
font-size: 20px;
color: var(--text-primary);
margin-bottom: 8px;
}
.empty-state p {
color: var(--text-secondary);
}
/* 响应式 */
@media (max-width: 768px) {
.hero-title {
font-size: 40px;
}
.popular-grid {
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.popular-logo {
width: 48px;
height: 48px;
}
.tools-grid {
grid-template-columns: 1fr;
}
}
@media (min-width: 769px) and (max-width: 1024px) {
.popular-grid {
grid-template-columns: repeat(3, 1fr);
}
}
</style>
{% endblock %}
{% block content %}
<!-- Hero区域 -->
<div class="hero">
<div style="height: 40px;"></div>
<div class="hero-content">
<p class="hero-subtitle">发现最新最好用的AI工具和产品</p>
<p class="hero-subtitle-en">Discover the best AI tools tailored for your workflow</p>
</div>
</div>
<div class="main-content">
<!-- 热门工具排行榜 -->
{% if popular_sites and not selected_tag and not search_query %}
<div class="popular-section">
<div class="popular-header">
<h2 class="popular-title">
<span class="popular-badge">🔥</span>
热门工具排行榜
</h2>
</div>
<div class="popular-grid">
{% for site in popular_sites[:10] %}
<a href="/site/{{ site.code }}" class="popular-item">
<div class="popular-rank {% if loop.index == 1 %}rank-1{% elif loop.index == 2 %}rank-2{% elif loop.index == 3 %}rank-3{% else %}rank-other{% endif %}">
{{ loop.index }}
</div>
{% if site.logo %}
<img src="{{ site.logo }}" alt="{{ site.name }}" class="popular-logo">
{% else %}
<div class="popular-logo" style="background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%);"></div>
{% endif %}
<div class="popular-name">{{ site.name }}</div>
<div class="popular-views">
<span class="popular-fire">👁</span>
<span>{% if site.view_count >= 1000 %}{{ (site.view_count / 1000) | round(1) }}k{% else %}{{ site.view_count | default(0) }}{% endif %}</span>
</div>
</a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- 分类过滤 -->
<div class="categories" id="categories">
<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 %} {% 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>
<!-- 工具网格 -->
<div class="tools-section">
{% 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 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>
</div>
{% endif %}
{% if sites %}
<div class="tools-grid">
{% for site in sites %}
<a href="/site/{{ site.code }}" class="tool-card">
<div class="tool-card-header">
{% if site.logo %}
<img src="{{ site.logo }}" alt="{{ site.name }}" class="tool-logo">
{% else %}
<div class="tool-logo" style="background: linear-gradient(135deg, #0ea5e9 0%, #8b5cf6 100%);"></div>
{% endif %}
<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>
<div class="tool-footer">
<div class="tool-tags">
{% for tag in site.tags[:2] %}
<span class="tool-tag">{{ tag.name }}</span>
{% endfor %}
</div>
<div class="tool-views">
<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>
</a>
{% endfor %}
</div>
<!-- 分页 -->
{% if pagination and pagination.pages > 1 %}
<div class="pagination">
<!-- 上一页 -->
{% 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></span>
</a>
{% else %}
<a href="#" class="disabled">
<span></span>
</a>
{% endif %}
<!-- 页码显示 -->
{% set start_page = [1, pagination.page - 2]|max %}
{% set end_page = [pagination.pages, pagination.page + 2]|min %}
{% if start_page > 1 %}
<a href="?page=1{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">1</a>
{% if start_page > 2 %}
<span>...</span>
{% endif %}
{% endif %}
{% for page_num in range(start_page, end_page + 1) %}
{% if page_num == pagination.page %}
<span class="active">{{ page_num }}</span>
{% else %}
<a href="?page={{ page_num }}{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">{{ page_num }}</a>
{% endif %}
{% endfor %}
{% if end_page < pagination.pages %}
{% if end_page < pagination.pages - 1 %}
<span>...</span>
{% endif %}
<a href="?page={{ pagination.pages }}{% if selected_tag %}&tag={{ selected_tag.slug }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">{{ pagination.pages }}</a>
{% endif %}
<!-- 下一页 -->
{% 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></span>
</a>
{% else %}
<a href="#" class="disabled">
<span></span>
</a>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="empty-state">
<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 %}