news 2026/9/19 5:13:18

Hugo Pager.PageGroups 方法详解:对分页集合按分组渲染

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hugo Pager.PageGroups 方法详解:对分页集合按分组渲染

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)。它专为「先分组、再分页」的渲染模式设计:当你在列表页模板中先用GroupByDateGroupBy等分组方法对页面集合分组,再调用.Paginate分页时,每个分页器内保存的不再是扁平页面列表,而是PagesGroup分组结构。通过PageGroups,你可以直接在模板中遍历分组键(如年份、月份)与分组内的页面,构建出「按年份/月份分块的博客归档页」这类经典布局。

读完本文你将掌握:PageGroups的返回类型与适用条件、它与Pages方法的互斥关系、与全部分组方法(GroupByDateGroupByGroupByParam等)的组合用法、底层分页器对分组数据的切分原理,以及搭配内置分页导航模板的完整落地示例。

方法签名与返回类型

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上有两个「二选一」的取数方法:PagesPageGroups。两者不能同时返回非空结果,具体行为由底层存储的分页元素类型决定。在 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字段(默认取前端元数据中的datePAGES.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]

所有方法都支持可选的排序参数:ascdescrevreverse(后三者等价于降序)。日期类分组的默认顺序是降序(最新的在前),这一点在groupByDateField的实现中体现:除非显式传入ascrevreverse,否则分组前会先对页面集合执行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" . }}

这段模板的执行流程:

  1. where site.RegularPages "Type" "posts"筛选出类型为posts的常规页面,构建待分组集合;
  2. $pages.GroupByDate "Jan 2006"按「年月」分组,得到PagesGroup(如Mar 2026Feb 2026……);
  3. .Paginate (...)对分组结果进行分页,返回分页器对象;
  4. range $paginator.PageGroups遍历当前分页器的分组:外层输出分组键.Key(如Mar 2026),内层range .Pages输出该分组下的每篇文章标题与链接;
  5. 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 中实现。

常见误区与注意事项

  1. 不要同时使用PagesPageGroups:二者按分页元素的类型互斥。对分组结果分页却调用Pages(),或对普通集合分页却调用PageGroups(),都会得到空结果。
  2. 分组后不要在range中重复分页:与普通分页一样,同一列表页上首次调用分页方法的结果会被缓存,重复调用不会按预期重新执行(这是 Hugo 分页最常见的模板错误,详见 templates/pagination 的缓存说明)。
  3. 分组键跨页拆分:如前文源码分析所述,当某分组元素数超过每页容量时,该组可能被拆分到多个分页器,每个分页器只包含该组的子集,渲染时按当前分页器所见为准。
  4. 空集合安全:对空页面集合分组后再分页不会报错,PageGroups()返回空分组,range自然跳过(参考测试 hugolib/paginator_test.go)。
  5. 分组键类型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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 5:12:04

PTP协议故障诊断全攻略:从状态机到时延测量的排查路径

PTP协议精讲&#xff08;3.13&#xff09;&#xff1a;故障处理与诊断——PTP的“健康卫士”做网络时间同步这一行&#xff0c;最怕的不是配置复杂&#xff0c;而是故障藏得深。PTP协议本身设计得很精巧&#xff0c;收敛也快&#xff0c;但一旦出了问题&#xff0c;排查起来比普…

作者头像 李华
网站建设 2026/9/19 5:11:15

SpringBoot+Vue3构建农业设备租赁系统实战

1. 农业设备租赁系统概述农业设备租赁系统是针对现代农业发展需求设计的数字化管理平台。随着农业机械化程度不断提高&#xff0c;中小农户对大型农机设备的临时性需求日益增长&#xff0c;但传统租赁方式存在信息不对称、管理效率低下等问题。我们团队基于实际调研发现&#x…

作者头像 李华
网站建设 2026/9/19 5:11:07

把 Siri AI 的个人语境理解拆成调用链,TaoToken 接哪段

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 5:09:13

Playwright自建还是采购?自动化测试方案成本与决策指南

1. 先算一笔账&#xff1a;自建 Playwright 的真实成本结构很多小团队在评估自动化测试方案时&#xff0c;第一反应是“Playwright 开源免费&#xff0c;直接自己搭就行了”。这个判断本身没错&#xff0c;但“免费”和“零成本”是两回事。我在过去几年里帮三个不同规模的团队…

作者头像 李华