Hugo Pager.PageGroups 方法详解:对分页集合按分组渲染
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
PageGroups是 Hugo 中Pager对象提供的方法,用于在分页(pagination)场景下获取当前分页器(pager)的页面分组(page.PagesGroup)。它专为「先分组、再分页」的渲染模式设计:当你在列表页模板中先用GroupByDate、GroupBy等分组方法对页面集合分组,再调用.Paginate分页时,每个分页器内保存的不再是扁平页面列表,而是PagesGroup分组结构。通过PageGroups,你可以直接在模板中遍历分组键(如年份、月份)与分组内的页面,构建出「按年份/月份分块的博客归档页」这类经典布局。
读完本文你将掌握:PageGroups的返回类型与适用条件、它与Pages方法的互斥关系、与全部分组方法(GroupByDate、GroupBy、GroupByParam等)的组合用法、底层分页器对分组数据的切分原理,以及搭配内置分页导航模板的完整落地示例。
方法签名与返回类型
PageGroups方法的官方签名定义如下:
PAGER.PageGroups → page.PagesGroup- 适用对象:分页器对象(
Pager),即调用.Paginate或.Paginator后返回的对象。 - 返回类型:
page.PagesGroup,即一组PageGroup的列表。每个PageGroup由两部分组成——Key(分组键,通常是年份、月份等)和Pages(该分组下的页面集合)。该类型定义在 resources/page/pagegroup.go 中:
type PageGroup struct { // The key, typically a year or similar. Key any // The Pages in this group. Pages }从源码结构看,Key的类型是any,可以承载字符串(如日期格式化的"Jan 2006")、整数、前端参数值等任意分组键;Pages直接内嵌了页面集合,因此模板中可以沿用Pages上的一切方法(如.ByTitle、.Limit等)对分组内页面做进一步处理。
与 Pages 方法的互斥关系
Pager上有两个「二选一」的取数方法:Pages与PageGroups。两者不能同时返回非空结果,具体行为由底层存储的分页元素类型决定。在 resources/page/pagination.go 的实现中可以看到这段关键逻辑:
// Pages returns the Pages on this page. // Note: If this return a non-empty result, then PageGroups() will return empty. func (p *Pager) Pages() Pages { ... if pages, ok := p.element().(Pages); ok { return pages } return paginatorEmptyPages } // PageGroups return Page groups for this page. // Note: If this return non-empty result, then Pages() will return empty. func (p *Pager) PageGroups() PagesGroup { ... if groups, ok := p.element().(PagesGroup); ok { return groups } return paginatorEmptyPageGroups }也就是说:
- 当你把「普通页面集合」传给
.Paginate(如.Paginate $pages),每个分页器内部元素是Pages切片,此时用Pages()取数,PageGroups()返回空。 - 当你把「分组结果」传给
.Paginate(如.Paginate ($pages.GroupByDate "Jan 2006")),每个分页器内部元素是PagesGroup,此时用PageGroups()取数,Pages()返回空。
另外,当分页器没有任何元素时(例如对空集合分组后再分页),两个方法都会返回预定义的空值paginatorEmptyPages/paginatorEmptyPageGroups,模板中的range会安全地跳过,不会报错——这一点由 hugolib/paginator_test.go 中的TestPaginatorEmptyPageGroups测试用例(对应 Issue 10802)验证:对空集合执行GroupByPublishDate后再分页,len $pag.Pages为 0,页面正常渲染。
使用前置条件:分组方法
官方文档明确指出,PageGroups需要与任意的分组方法配合使用。Hugo 提供的分组方法全部定义在 resources/page/pagegroup.go 中,返回类型统一为PagesGroup:
| 方法 | 分组依据 | 签名 |
|---|---|---|
GroupByDate | 页面date字段(默认取前端元数据中的date) | PAGES.GroupByDate LAYOUT [SORT] |
GroupByPublishDate | 页面publishDate字段 | PAGES.GroupByPublishDate LAYOUT [SORT] |
GroupByExpiryDate | 页面expireDate字段 | PAGES.GroupByExpiryDate LAYOUT [SORT] |
GroupByLastmod | 页面lastmod字段 | PAGES.GroupByLastmod LAYOUT [SORT] |
GroupByParam | 页面指定参数key的值 | PAGES.GroupByParam KEY [SORT] |
GroupByParamDate | 页面参数中的日期值 | PAGES.GroupByParamDate KEY LAYOUT [SORT] |
GroupBy | 页面任意字段或方法的值 | PAGES.GroupBy KEY [SORT] |
所有方法都支持可选的排序参数:asc、desc、rev、reverse(后三者等价于降序)。日期类分组的默认顺序是降序(最新的在前),这一点在groupByDateField的实现中体现:除非显式传入asc、rev或reverse,否则分组前会先对页面集合执行Reverse()。
分组键的本地化
对于日期类分组,LAYOUT参数使用与time.Format相同的 Go 时间布局字符串(如"Jan 2006"、"2006"),分组键会根据当前站点的语言和地区进行本地化。从 resources/page/pagegroup.go 的源码可以看到,格式化器取自当前渲染站点的语言:
currentSite := firstPage.Site().Current() formatter := langs.GetTimeFormatter(currentSite.Language()) formatted := formatter.Format(date, format)这意味着多语言站点中,同一篇内容在不同语言列表页上会得到对应语言的分组键(例如英文站点显示January 2026,中文站点显示2026年1月)。
官方示例:按月分组的博客归档页
PageGroups最典型的使用场景是按时间分组的归档列表。官方文档 PageGroups 给出的完整示例:
{{ $pages := where site.RegularPages "Type" "posts" }} {{ $paginator := .Paginate ($pages.GroupByDate "Jan 2006") }} {{ range $paginator.PageGroups }} <h2>{{ .Key }}</h2> {{ range .Pages }} <h3><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h3> {{ end }} {{ end }} {{ partial "pagination.html" . }}这段模板的执行流程:
where site.RegularPages "Type" "posts"筛选出类型为posts的常规页面,构建待分组集合;$pages.GroupByDate "Jan 2006"按「年月」分组,得到PagesGroup(如Mar 2026、Feb 2026……);.Paginate (...)对分组结果进行分页,返回分页器对象;range $paginator.PageGroups遍历当前分页器的分组:外层输出分组键.Key(如Mar 2026),内层range .Pages输出该分组下的每篇文章标题与链接;partial "pagination.html" .调用 Hugo 内置的分页导航模板,渲染上一页/下一页及页码链接。
分页器对分组的切分原理
把分组结果交给.Paginate后,Hugo 是如何按页大小切分分组的?关键实现在 resources/page/pagination.go 的splitPageGroups函数中。其策略是:先把所有分组「展平」成键值对序列,再按页大小切成若干段,最后在每段内重建分组结构。
func splitPageGroups(pageGroups PagesGroup, size int) []paginatedElement { type keyPage struct { key any page Page } var ( split []paginatedElement flattened []keyPage ) for _, g := range pageGroups { for _, p := range g.Pages { flattened = append(flattened, keyPage{g.Key, p}) } } ... }这意味着分页边界可能出现在某个分组内部:如果pagerSize = 5,而某个月份有 8 篇文章,那么该月份可能被拆到相邻两个分页器上,每个分页器各自持有该月份的部分页面(键相同但页面不同)。因此,分页器上的分组键并不保证完整覆盖该分组的全部页面——这正是按PageGroups逐页渲染时需要注意的行为。
展平后的重建逻辑会保持组内页面相对顺序,并按页大小重新聚合:每遇到新的键值就新建一个PageGroup,把后续同键页面追加进去(见 resources/page/pagination.go)。此外,分页器内部的page()方法(resources/page/pagination.go)也支持从PagesGroup中按全局索引取页面,用于计算NumberOfElements等派生数据,因此你仍然可以正常使用Pager.NumberOfElements()等方法。
多语言项目中的分组键本地化实践
将PageGroups与多语言配置结合时,分组键会自动跟随当前渲染语言。你可以在项目配置中为每种语言分别设置分页参数,官方分页配置说明 configuration/pagination 给出的多语言示例:
[languages.en] contentDir = 'content/en' direction = 'ltr' label = 'English' locale = 'en-US' weight = 1 [languages.en.pagination] disableAliases = true pagerSize = 10 path = 'page' [languages.de] contentDir = 'content/de' direction = 'ltr' label = 'Deutsch' locale = 'de-DE' weight = 2 [languages.de.pagination] disableAliases = true pagerSize = 20 path = 'blatt'配合GroupByDate时,不同语言站点会使用各自的地区格式器生成分组键,模板无需任何改动即可输出本地化的年份/月份标题。
与内置分页导航模板的配合
PageGroups只负责渲染「当前页的分组内容」,分页导航(上一页、下一页、页码列表)通常由 Hugo 内置模板partial "pagination.html"提供,它支持两种格式:
{{ partial "pagination.html" . }} <!-- default 格式 --> {{ partial "pagination.html" (dict "page" . "format" "terse") }} <!-- terse 格式 -->default格式控件与页码槽位更多;terse格式占用更少空间,适合水平排列的紧凑导航。如需定制,可将内置模板源码复制为layouts/_partials/pagination.html后自行修改。
如果要完全自研导航,也可以组合使用Pager的其他方法(详见 methods/pager 下的各方法文档):PageNumber()(当前页码)、TotalPages()(总页数)、HasPrev()/HasNext()、Prev()/Next()、First()/Last()、URL()(分页器 URL)等,全部在 resources/page/pagination.go 中实现。
常见误区与注意事项
- 不要同时使用
Pages与PageGroups:二者按分页元素的类型互斥。对分组结果分页却调用Pages(),或对普通集合分页却调用PageGroups(),都会得到空结果。 - 分组后不要在
range中重复分页:与普通分页一样,同一列表页上首次调用分页方法的结果会被缓存,重复调用不会按预期重新执行(这是 Hugo 分页最常见的模板错误,详见 templates/pagination 的缓存说明)。 - 分组键跨页拆分:如前文源码分析所述,当某分组元素数超过每页容量时,该组可能被拆分到多个分页器,每个分页器只包含该组的子集,渲染时按当前分页器所见为准。
- 空集合安全:对空页面集合分组后再分页不会报错,
PageGroups()返回空分组,range自然跳过(参考测试 hugolib/paginator_test.go)。 - 分组键类型:
GroupBy/GroupByParam的键可以是任意类型(字符串、整数等),而日期类分组的键是本地化后的字符串;模板中输出.Key时请按实际类型处理。
小结
PageGroups是 Hugo 分页体系中连接「分组」与「分页」两个特性的桥梁。它让你能够先按日期、参数或任意字段把文章集合组织成分组,再对分组结果分页,最终在每个分页器内按「分组键 → 分组内页面」的两级结构渲染内容。其底层实现(resources/page/pagination.go 与 resources/page/pagegroup.go)清晰展示了PagesGroup的类型结构、分页切分算法与空值安全策略。掌握了PageGroups,你就能轻松实现博客按月归档、按标签分类的无限分页列表等常见实战布局。
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考