Chic主题分类页面优化
Chic主题默认情况是将所有分类依次展示,但Hexo本身是有父子分类之区分的。
例如,在front-matter中指定:
1 2 3
| categories: - 摸鱼的间隙总还是要工作 - 数据审阅
|
则”数据审阅”应为”摸鱼的间隙总还是要工作”的子分类。
但Chic将所有分类同一处理,实际展示为下图:

即,摸鱼的间隙总还是要工作分类下直接展示该分类的所有文章,并且还有一个独立的数据审阅分类下展示该分类的文章。这显然与我的期望不一致。
期望目标
在Categories页面展示时,若仅存在父分类,则展示该分类下文章。若父分类下存在子分类,则展示子分类名称,点击子分类跳转至该子分类页面。
步骤:修改 themes/Chic/layout/category.ejs
文件
由于category_item.path
会指示该分类的路径,进而以此得到其分类树情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| let categoryHierarchy = {};
site.categories.forEach(category_item => { let path = category_item.path; let pathParts = path.split('/').filter(part => part !== '');
pathParts = pathParts.map(part => decodeURIComponent(part));
if (pathParts[0] === 'categories') { pathParts = pathParts.slice(1); }
if (pathParts.length === 1) { let parentName = pathParts[0]; if (!categoryHierarchy[parentName]) { categoryHierarchy[parentName] = { name: parentName, path: category_item.path, posts: category_item.posts, children: {} }; } } else if (pathParts.length === 2) { let parentName = pathParts[0]; let childName = pathParts[1];
if (!categoryHierarchy[parentName]) { categoryHierarchy[parentName] = { name: parentName, path: null, posts: [], children: {} }; }
categoryHierarchy[parentName].children[childName] = { name: childName, path: category_item.path, posts: category_item.posts }; } });
|
随后,在显示时,若仅有父分类,则直接显示文章,否则显示其子分类列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <% Object.keys(categoryHierarchy).forEach(parentKey => { %> <% let parentCategory = categoryHierarchy[parentKey]; %> <div class="card-item"> <div class="categories"> <h3> <i class="iconfont icon-category" style="padding-right:3px"></i> <%- parentCategory.name %> </h3>
<% if (Object.keys(parentCategory.children).length > 0) { %> <div class="subcategories" style="margin-left: 15px; margin-top: 10px;"> <% Object.keys(parentCategory.children).forEach(childKey => { %> <% let childCategory = parentCategory.children[childKey]; %> <div class="subcategory-item" style="margin-bottom: 8px;"> <a href="<%- url_for(childCategory.path) %>" style="font-size: 0.9em; color: #666;"> <i class="iconfont icon-category" style="padding-right:3px; font-size: 0.8em;"></i> <%- childCategory.name %> (<%- childCategory.posts.length %>) </a> </div> <% }) %> </div> <% } else { %> <% parentCategory.posts.forEach((post_item, index = 0)=>{ %> <% if(++index <= 5){ %> <article class="archive-item"> <a class="archive-item-link" href="<%- url_for(post_item.path) %>"><%- post_item.title %></a> </article> <% } %> <% }) %> <% if(parentCategory.posts.length > 5){ %> <a class="more-post-link" href="<%- url_for(parentCategory.path) %>">More >></a> <% } %> <% } %> </div> </div> <% }) %>
|
嗯,其实这样已经可以简单实现了,虽然只支持到二级分类,不过已经实现我的需求了。
问题
但是当我点进单独的分类页面时,即便当前单独分类页面有其子分类,依然是只会显示全部文章,而不是显示子分类。
事实上我已经给不同页面分了很多类别了。
1 2 3 4 5
| categories: - 摸鱼的间隙总还是要工作 - 代码编辑 - 跑路 - 不玩了
|
但是其实际显示仍然是将该父分类下所有文章全部显示,无论该文章有无子分类:

这就不能忍了,还是改吧。
分析
考虑到这似乎是一个递归的分类显示模型,让我们忘了前面的那些修改吧。TAT
无论是一级、二级还是更深层级的分类,在其分类路径中都遵循着相同的规则:对于任意一个分类路径:
- 该路径下是否包含子分类?若包含,则显示所有子分类。若不包含,则显示所有文章列表。
- 显示子分类卡片时,若该子分类下仍然包含下一级子分类,则显示为下一级子分类列表。若不包含,则显示该子分类的所有文章列表。
- 点击子分类卡片中的下一级子分类时,跳转到新的单分类显示页面,递归调用该逻辑。
因此,每个分类页面均可使用统一的递归逻辑,实现多级分类管理:
- 任意深度的分类都使用相同规则
- 子分类卡片中的递归显示逻辑
- 点击跳转后的递归调用
- 无需区分主分类页面和单分类页面
- 支持无限层级的分类嵌套
主分类页面精简
主分类页面精简至如下:
1 2 3 4 5 6 7 8 9 10 11 12
| <% if(is_category()){ %> <%- partial('_page/category', {pagination: config.category, index: true}) %> <% }else{ %> <% let virtualPage = { path: 'categories/index.html', category: 'All Categories' }; %>
<%- partial('_page/category', {page: virtualPage}) %> <% } %>
|
由于主页面只是一个特殊的根节点,其在显示逻辑上与任意的单分类页面并无区别,因此修改一下主页面path,使其与单页面一致,方便统一处理。
单页面配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <% let currentPath = page.path || 'categories/index.html'; let pathParts = currentPath.split('/').filter(part => part !== '');
pathParts = pathParts.map(part => decodeURIComponent(part));
if (pathParts[0] === 'categories') { pathParts = pathParts.slice(1); }
if (pathParts[pathParts.length - 1] === 'index.html') { pathParts = pathParts.slice(0, -1); }
let currentDepth = pathParts.length; let currentCategoryPath = pathParts.join('/');
let pageTitle = ''; if (currentDepth === 0) { pageTitle = 'Categories'; } else { pageTitle = 'Categories · ' + (page.category || pathParts[pathParts.length - 1]); }
|
递归获取子分类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| function getDirectSubCategories(categoryPath, depth) { let subCategories = [];
site.categories.forEach(category_item => { let catPath = category_item.path; let catPathParts = catPath.split('/').filter(part => part !== ''); catPathParts = catPathParts.map(part => decodeURIComponent(part));
if (catPathParts[0] === 'categories') { catPathParts = catPathParts.slice(1); }
if (catPathParts[catPathParts.length - 1] === 'index.html') { catPathParts = catPathParts.slice(0, -1); }
if (catPathParts.length === depth + 1) { if (depth === 0) { subCategories.push({ name: catPathParts[0], fullName: category_item.name, path: category_item.path, posts: category_item.posts, depth: catPathParts.length, fullPath: catPathParts.join('/') }); } else { let parentPath = catPathParts.slice(0, depth).join('/'); if (parentPath === categoryPath) { subCategories.push({ name: catPathParts[catPathParts.length - 1], fullName: category_item.name, path: category_item.path, posts: category_item.posts, depth: catPathParts.length, fullPath: catPathParts.join('/') }); } } } });
return subCategories; }
|
分类页面布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| <div class="container"> <div class="post-wrap categories"> <h2 class="post-title">- <%- pageTitle %> -</h2> </div>
<% let directSubCategories = getDirectSubCategories(currentCategoryPath, currentDepth); let hasDirectSubCategories = directSubCategories.length > 0; %>
<% if (hasDirectSubCategories) { %> <div class="post-wrap categories"> <div class="categories-card"> <% directSubCategories.forEach(subCategory => { %> <div class="card-item"> <div class="categories"> <a href="<%- url_for(subCategory.path) %>"> <h3> <i class="iconfont icon-category" style="padding-right:3px"></i> <%- subCategory.name %> </h3> </a>
<% let grandChildren = getDirectSubCategories(subCategory.fullPath, subCategory.depth); let hasGrandChildren = grandChildren.length > 0; %>
<% if (hasGrandChildren) { %> <div class="subcategories" style="margin-left: 15px; margin-top: 10px;"> <% grandChildren.forEach(grandChild => { %> <div class="subcategory-item" style="margin-bottom: 8px;"> <a href="<%- url_for(grandChild.path) %>" style="font-size: 1em; color: #000000;"> <i class="iconfont icon-category" style="padding-right:3px; font-size: 0.8em;"></i> <%- grandChild.name %> (<%- grandChild.posts.length %>) </a> </div> <% }) %> </div> <% } else { %> <% subCategory.posts.forEach((post_item, index = 0)=>{ %> <% if(++index <= 5){ %> <article class="archive-item"> <a class="archive-item-link" href="<%- url_for(post_item.path) %>"><%- post_item.title %></a> </article> <% } %> <% }) %> <% if(subCategory.posts.length > 5){ %> <a class="more-post-link" href="<%- url_for(subCategory.path) %>">More >></a> <% } %> <% } %> </div> </div> <% }) %> </div> </div> <% } else { %> <% if (currentDepth === 0) { %> <div class="container"> <div class="post-wrap categories"> <p style="text-align: center; color: #999; padding: 20px;">No Categories</p> </div> </div> <% } else { %> <%- partial('archive', {pagination: config.category, index: true}) %> <% } %> <% } %> </div>
|
效果
主页面分类显示

子分类多级嵌套显示1

子分类多级嵌套显示2

总结
以上修改实现了Chic主题分类页面的递归逻辑,支持多级分类嵌套展示,改进了原有分类页面的展示方式,使得子分类可以递归显示。通过统一的递归规则,任意深度的分类都能够正确展示子分类或文章列表。