feat: collapsible hierarchical category tags + category sort for shelf

- Backend: add parent_category param (matches '历史-*' sub-categories),
  sort=category mode (ORDER BY category ASC), return category_tree with
  parent→sub→count hierarchy
- Frontend: replace flat 63-tag list with collapsible parent tags showing
  book counts, click parent to expand sub-category pills with fade-in
  animation, click sub to filter to exact category
- Add '分类排序' toggle button: when active, books are grouped by
  category with colored section headers showing category name + count
- Clicking same parent again collapses it; '全部' resets everything
- Mobile responsive: smaller tag sizes, reduced indent on narrow screens
This commit is contained in:
admin committed 2026-07-17 12:21:36 +08:00
1 parent ec88707e6b
commit e578a90543
4 files changed
+312 -41

No files matched your search

+143 -5
View File
@@ -336,14 +336,119 @@ body {
}
/* ===== 书架分类标签 ===== */
/* 层级折叠分类标签 */
.shelf-categories {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 20px;
align-items: center;
}
.shelf-category-tag {
.shelf-cat-parent {
padding: 5px 14px;
border: 1px solid var(--border);
border-radius: 20px;
font-size: 13px;
color: var(--text-secondary);
background: var(--bg-card);
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
display: inline-flex;
align-items: center;
gap: 5px;
}
.shelf-cat-parent:hover {
border-color: var(--accent);
color: var(--accent);
background: var(--accent-bg);
}
.shelf-cat-parent.active {
border-color: var(--accent);
color: #fff;
background: var(--accent);
font-weight: 500;
}
.shelf-cat-count {
font-size: 11px;
padding: 1px 7px;
border-radius: 10px;
background: var(--accent-bg);
color: var(--accent-dark);
}
.shelf-cat-parent.active .shelf-cat-count {
background: rgba(255,255,255,0.25);
color: #fff;
}
.shelf-cat-arrow {
font-size: 9px;
margin-left: 2px;
transition: transform 0.2s;
}
.shelf-cat-subs {
display: flex;
flex-wrap: wrap;
gap: 6px;
width: 100%;
padding: 4px 0 4px 20px;
margin-bottom: 4px;
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.shelf-cat-sub {
padding: 3px 12px;
border: 1px solid var(--border);
border-radius: 16px;
font-size: 12px;
color: var(--text-light);
background: var(--bg-primary);
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
display: inline-flex;
align-items: center;
gap: 4px;
}
.shelf-cat-sub:hover {
border-color: var(--accent);
color: var(--accent);
}
.shelf-cat-sub.active {
border-color: var(--accent);
color: #fff;
background: var(--accent);
font-weight: 500;
}
.shelf-cat-count-sm {
font-size: 10px;
padding: 0 5px;
border-radius: 8px;
background: var(--accent-bg);
color: var(--accent-dark);
}
.shelf-cat-sub.active .shelf-cat-count-sm {
background: rgba(255,255,255,0.25);
color: #fff;
}
/* 分类排序按钮 */
.shelf-sort-btn {
padding: 5px 14px;
border: 1px solid var(--border);
border-radius: 20px;
@@ -355,19 +460,45 @@ body {
white-space: nowrap;
}
.shelf-category-tag:hover {
.shelf-sort-btn:hover {
border-color: var(--accent);
color: var(--accent);
background: var(--accent-bg);
}
.shelf-category-tag.active {
.shelf-sort-btn.active {
border-color: var(--accent);
color: #fff;
background: var(--accent);
font-weight: 500;
}
/* 分类分组标题 */
.shelf-group-header {
font-size: 15px;
font-weight: 700;
color: var(--text-primary);
margin: 24px 0 12px;
padding: 8px 16px;
border-radius: var(--radius-sm);
background: var(--accent-bg);
border-left: 4px solid var(--accent);
}
.shelf-group-header:first-child {
margin-top: 0;
}
.shelf-group-count {
font-size: 12px;
font-weight: 400;
color: var(--text-light);
margin-left: 8px;
}
.shelf-group-header + .book-grid {
margin-bottom: 8px;
}
/* ===== Hero 区 ===== */
.hero-section {
position: relative;
@@ -1906,10 +2037,17 @@ body {
.shelf-categories {
gap: 6px;
}
.shelf-category-tag {
.shelf-cat-parent {
font-size: 12px;
padding: 4px 10px;
}
.shelf-cat-sub {
font-size: 11px;
padding: 3px 9px;
}
.shelf-cat-subs {
padding-left: 10px;
}
.back-to-top {
bottom: 20px;
right: 20px;