前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Thymeleaf目录页原理 发布于

Thymeleaf目录页原理 发布于

作者头像
DioxideCN
发布2023-10-21 11:40:41
2560
发布2023-10-21 11:40:41
举报

简介

为Halo搭建的博客配上如同《新华字典》那样的目录是一个不错的主意,不仅能让分类更加清晰,还能帮助读者更轻松地查找和理解文章的内容。在这篇文章中,《Thymeleaf目录页原理》将深入探讨如何实现这种目录结构的设计,从基本原理开始,逐步深入到实际操作。

问题背景

在给定如下的关于CategoryVoPostVo的方法和关系中,选择最为合适的算法与方案来实现一个文章分类目录表。与之相关的查找类为PostFinderCategoryFinder(更详细的可以前往Halo文档进行查阅):

  1. postFinder.listByCategory(page, size, categoryName);
  2. categoryFinder.listAll()

解决方案

因为Halo官方并不直接提供“获取分类下的所有文章”的相关方法,那么最好的解决方案就是三次for循环来拆解每个分类下的文章(在接下来的代码中每次都是结合了前面步骤中的完整代码)。

第一层for循环,遍历所有文章分类,并构建完整的Thymeleaf骨架

代码语言:javascript
复制
	<th:block
	    th:fragment="directory"
	    th:with="categories = ${categoryFinder.listAll()}">
	<div class="categories-container">
	    <th:block th:each="category : ${categories}" th:if="${category.status.visiblePostCount > 0}">
	        <!-- ... -->
	    </th:block>
	</div>
	</th:block>
  • ${categoryFinder.listAll()}负责罗列出所有的分类分类并在循环遍历中将每个对象分配给category对象
  • 同时为了让没有任何文章的分类不显示出来需要加上条件判断th:if="${category.status.visiblePostCount > 0}"
  • 这里的th:each是由Thymeleaf提供的一种For循环标签(可以对比到Vue中的v-for

第二层for循环,用来处理目录列分页 什么意思呢?如果不分页则会导致一列中出现大量元素标签,这使得同一行其他的文章数较少的列中会出现很大一片空白区域。

代码语言:javascript
复制
	<th:block
	    th:fragment="directory"
	    th:with="categories = ${categoryFinder.listAll()}">
	<div class="categories-container">
	    <th:block th:each="category : ${categories}" th:if="${category.status.visiblePostCount > 0}">
	        <th:block th:each="i : ${#numbers.sequence(0, (category.status.visiblePostCount - 1) / site.post.postPageSize)}">
		        <!-- ... -->
	        </th:block>
	    </th:block>
	</div>
	</th:block>
  • ${#numbers.sequence(0, (category.status.visiblePostCount - 1) / site.post.postPageSize)}在有了当前分类下(已发布的)文章总数和每列最大显示文章数后,通过除法运算就可以得到这个分类一共需要多少列了
  • ${i}变量i为迭代的列数,用来后面分页获取文章集合

第三层for循环,正式开始分页获取文章

代码语言:javascript
复制
	<th:block
	    th:fragment="directory"
	    th:with="categories = ${categoryFinder.listAll()}">
	<div class="categories-container">
	    <th:block th:each="category : ${categories}" th:if="${category.status.visiblePostCount > 0}">
	        <th:block th:each="i : ${#numbers.sequence(0, (category.status.visiblePostCount - 1) / site.post.postPageSize)}">
	        <div class="category" th:with="posts = ${postFinder.listByCategory(i + 1, site.post.postPageSize, category.metadata.name)}" th:attr="data-collection=${category.spec.displayName}">
	            <h2 th:if="${i == 0}" class="category-title" th:text="${category.spec.displayName}"></h2>
	            <div th:if="${i != 0}" class="category-block"></div>
	            <ol class="posts-container" th:attr="data-collection=${category.spec.displayName}">
	                <li th:each="post,it : ${posts}" th:attr="data-order=${i * site.post.postPageSize + it.index + 1}">
	                    <a th:href="@{${post.status.permalink}}" class="post" th:text="${post.spec.title}"></a>
	                </li>
	            </ol>
	        </div>
	        </th:block>
	    </th:block>
	</div>
	</th:block>
  • {i + 1}上一次的for循环中的变量{i}指代了页码由于是从0开始的所以页码需要加1处理
  • {postFinder.listByCategory(i + 1, site.post.postPageSize, category.metadata.name)}这个方法获取了category.metadata.name分类中第{i + 1}页的site.post.postPageSize条数据,并另外存储为
  • data-collection=${category.spec.displayName}这里使用category.spec.displayName来标记元素标签,在后面它可以帮助我们来对这些目录进行首字母排序
  • th:each="post,it : ${posts}"这里就是遍历posts中的所有文章了,这些结果会逐个存储到<li>元素标签对中
  • th:if="

样式处理

在前面的解决方案中我们提到了一个关于样式的问题:如果不分页则会导致一列中出现大量元素标签,这使得同一行其他的文章数较少的列中会出现很大一片空白区域。同样的我们还需要来解决不同屏幕尺寸下一行只显示1个<div class="category">、以及<div class="category-block">的对齐作用。在明确了需求后就可以开始写入css样式了:

代码语言:javascript
复制
/****** category start ******/
.categories-container {display: flex; flex-wrap: wrap;}
.categories-container .category {
    margin: 0;
    padding: 10px;
    max-width: calc(50% - 20px); /* 去除padding的影响 */
    flex: 1 1 calc(100% - 20px); /* 去除padding的影响 */
}
/* 让目录的头部对齐 */
.categories-container .category .category-block {
    width: 100%;
    height: 27.59px;
    margin-top: 20px;
    margin-bottom: 10px;
}
.categories-container .category .category-title {
    font-weight: normal;
    color: #bbbbbb;
    margin-top: 20px;
    margin-bottom: 10px;
}
.categories-container .category .posts-container {
    display: flex; /* 使用flex弹性布局 */
    flex-direction: column;
    padding-left: 0px;
    margin: 0px;
}
.categories-container .category .posts-container li {
    display: flex;
    align-items: flex-start;
    list-style-type: none;
    margin-bottom: 7px;
}
/* before伪元素实现有序列表 */
.categories-container .category .posts-container li:before {
    padding: 4px 0;
    content: attr(data-order) ". "; /* 有序列表的标号来源 */
    color: #bbbbbb;
    counter-increment: li;
    margin-right: 5px;
}
.categories-container .category .posts-container a {
    padding: 4px 0;
    border-radius: 4px;
}
.categories-container .category .posts-container a:hover {
    color: #fff;
    padding: 4px 6px;
    background-color: #0084FF;
    box-shadow: 0 8px 12px -3px #0084FF23;
}
.categories-container .category .posts-container .post {flex: 1;}
.categories-container .category .posts-container .post {margin: 0;}
/* 屏幕尺寸大于等于1600px */
@media screen and (min-width: 1600px) {
    .categories-container .category {
        max-width: calc(100% / 3 - 20px); /* 占用1/3的宽度 */
        flex: 1 1 calc(100% / 3 - 20px);  /* 占用1/3的宽度 */
    }
}
/* 屏幕尺寸在750px到1600px之间 */
@media screen and (min-width: 751px) and (max-width: 1599px) {
    .categories-container .category {
        max-width: calc(50% - 20px); /* 占用1/2的宽度 */
        flex: 1 1 calc(50% - 20px);  /* 占用1/2的宽度 */
    }
}
/* 屏幕尺寸小于等于750px */
@media screen and (max-width: 750px) {
	/* 占用100%的宽度 */
    .categories-container .category {flex: 1 1 calc(100% - 20px);}
    .categories-container .category .category-block {
        display: none;
    }
}
/****** category end ******/

排序函数

在样式处理的.categories-container .category .posts-container li:before的class中,使用了content: attr(data-order) ". ";,这一段的作用是让before伪元素使用data-order属性的值来进行头部内容。那么我们就需要为一个分类下的所有文章进行这个属性的编号。

同时为了方便读者或博客博主能更快速的查找到分类,需要引入更有效的首字母排序功能,让英文与中文部分都分开按照A-Za-z0-9的顺序进行排序并重新组合。

代码语言:javascript
复制
(function() {
	window.onload = function() {
        var { pinyin } = pinyinPro;
        var container = document.querySelector('.categories-container');
        if (container !== null) {
            var categories = Array.from(container.children);
            // 对categories中的元素进行排序
            categories.sort(function(a, b) {
                var displayNameA = a.querySelector('.posts-container').dataset.collection;
                var displayNameB = b.querySelector('.posts-container').dataset.collection;
                
                // 把"其它"分类移至最后
                if (displayNameA === "其它") return 1;
                if (displayNameB === "其它") return -1;
                
                var isLetterA = isLetterOrNumber(displayNameA.charAt(0));
                var isLetterB = isLetterOrNumber(displayNameB.charAt(0));
    
                // 如果 displayNameA 的首字符是字母或数字,但 displayNameB 的首字符不是,则 displayNameA 排在前面
                if (isLetterA && !isLetterB) return -1;
                
                // 如果 displayNameB 的首字符是字母或数字,但 displayNameA 的首字符不是,则 displayNameB 排在前面
                if (isLetterB && !isLetterA) return 1;
    
                // 如果 displayNameA 或 displayNameB 不满足全是英文字母的正则表达式,则将其转换为拼音
                if (!isAllLetterOrNumber(displayNameA)) displayNameA = pinyin(displayNameA, { toneType: 'none' }).replaceAll(" ", "");
                if (!isAllLetterOrNumber(displayNameB)) displayNameB = pinyin(displayNameB, { toneType: 'none' }).replaceAll(" ", "");
    
                // 最后按照字母和数字的顺序排序进行组合
                return displayNameA.localeCompare(displayNameB);
            });
    
            categories.forEach(function(category) {
                container.appendChild(category);
            });
    
            // 定义一个函数,判断一个字符是否为字母或数字
            function isLetterOrNumber(char) {
                return /^[a-zA-Z0-9]$/.test(char);
            }
    
            // 定义一个函数,判断一个字符串是否全是英文字母或数字
            function isAllLetterOrNumber(str) {
                return /^[a-zA-Z0-9]+$/.test(str);
            }
        }
    }
})();

在这个函数中,使用了一个第三方库pinyin-pro,开发者在尝试时可以在script标签中引入这个CDN库https://cdn.jsdelivr.net/gh/zh-lx/pinyin-pro@latest/dist/pinyin-pro.js

细节处理

到这里其实主要的功能都已经实现完成了,但是在样式处理中当屏幕尺寸在750px以下后,每个<div class="category-block">之间仍然存在20px的padding,既然css已经无法解决,那么仍然需要引入JavaScript来动态控制这些padding:

代码语言:javascript
复制
(function() {
	window.addEventListener('resize', adjustPadding);
    function adjustPadding() {
        let adjustPadding = window.innerWidth < 750 ? "0" : "10px";
        // 以data-collection值为键将元素分组
        const collectionGroups = {};
        document.querySelectorAll('.category').forEach(el => {
            const collection = el.getAttribute('data-collection');
            if(!collectionGroups[collection]) {
                collectionGroups[collection] = [];
            }
            collectionGroups[collection].push(el);
        });

        // 遍历每个分组并调整padding
        for(let collection in collectionGroups) {
            collectionGroups[collection].forEach((el, index, array) => {
                if(index === 0) {
                    el.style.paddingBottom = adjustPadding;
                } else if(index === array.length - 1) {
                    el.style.paddingTop = adjustPadding;
                } else {
                    el.style.paddingTop = adjustPadding;
                    el.style.paddingBottom = adjustPadding;
                }
            });
        }
    }
    // 页面加载完后就调用一次来适应padding
    adjustPadding();
})();

注意事项

至此,这个可排序的目录功能就完成了。值得读者注意的是,每列的文章数这里是直接取了Halo的全局变量site.post.postPageSize,读者可以将其进行扩展到其他变量中,具体请参考:全局变量 | Halo Documents

目录演示
目录演示
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 问题背景
  • 解决方案
  • 样式处理
  • 排序函数
  • 细节处理
  • 注意事项
相关产品与服务
内容分发网络 CDN
内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档