Files
zjpb.net/templates/index_new.html
Jowe 22efd8b31c fix: 修复标签页description为空时的TypeError
问题:
- 当selected_tag.description为None时,尝试与字符串相加导致TypeError
- 造成标签页访问时出现502错误

修复:
- 在index_new.html第8行添加or默认值处理
- 确保description为None时使用默认文案

测试:
- 本地验证标签页正常显示
- 修复v2.5部署502问题

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

597 lines
18 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 or '发现最好用的' + selected_tag.name + '工具') + ' - ZJPB AI工具导航') }}">
<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;
}
/* 分类过滤 */
.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);
}
/* 排序标签 */
.sort-tabs {
padding: 24px 0 0 0;
border-top: 1px solid var(--border-color);
margin-top: 24px;
}
.sort-tabs-container {
display: flex;
gap: 8px;
align-items: center;
}
.sort-tab {
padding: 8px 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: 6px;
cursor: pointer;
}
.sort-tab:hover {
border-color: var(--primary-blue);
color: var(--primary-blue);
background: rgba(59, 130, 246, 0.05);
}
.sort-tab.active {
background: var(--primary-blue);
border-color: var(--primary-blue);
color: white;
}
.sort-tab .icon {
font-size: 14px;
}
/* 响应式 */
@media (max-width: 768px) {
.hero-title {
font-size: 40px;
}
.tools-grid {
grid-template-columns: 1fr;
}
.sort-tabs-container {
flex-wrap: wrap;
}
}
</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">
<!-- 分类过滤 -->
<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 class="sort-tabs">
<div class="sort-tabs-container">
<a href="/?{% if selected_tag %}tag={{ selected_tag.slug }}&{% endif %}tab=latest"
class="sort-tab {% if not current_tab or current_tab == 'latest' %}active{% endif %}">
<span class="icon">🕐</span>
最新
</a>
<a href="/?{% if selected_tag %}tag={{ selected_tag.slug }}&{% endif %}tab=popular"
class="sort-tab {% if current_tab == 'popular' %}active{% endif %}">
<span class="icon">🔥</span>
热门
</a>
<a href="/?{% if selected_tag %}tag={{ selected_tag.slug }}&{% endif %}tab=recommended"
class="sort-tab {% if current_tab == 'recommended' %}active{% endif %}">
<span class="icon"></span>
推荐
</a>
</div>
</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 %}{% if current_tab and current_tab != 'latest' %}&tab={{ current_tab }}{% 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 %}{% if current_tab and current_tab != 'latest' %}&tab={{ current_tab }}{% 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 %}{% if current_tab and current_tab != 'latest' %}&tab={{ current_tab }}{% 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 %}{% if current_tab and current_tab != 'latest' %}&tab={{ current_tab }}{% 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 %}{% if current_tab and current_tab != 'latest' %}&tab={{ current_tab }}{% 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 %}