# 类别中的三个字母标题

**URL:** https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045
**Category:** Feature
**Created:** [2026年八月27日 09:05 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045 "2026-08-27T09:05:32Z")
**Posts on this page:** 10
**Page:** 1

<div class="post-metadata">

### Author: ![gilles](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/gilles/32/549022_2.png) [@gilles](https://meta.discourse.org/u/gilles)
#### Post date: [2026年八月27日 09:05 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/1 "2026-08-27T09:05:32Z")

</div>

我注意到，默认情况下无法按主题标题的字母顺序对分类进行排序。大家可能会说，只需通过筛选器来设置即可，但这会带来两个主要问题：

1. 操作步骤太多，并非所有人都能轻松完成。

2. 会失去通常的瓷砖（卡片）视觉效果。

能否为每个分类提供一个参数，以便默认按字母顺序对主题进行排序？我不确定这是否已在产品路线图（roadmap）中规划。

再说一遍，这只是我实际使用中的反馈，但我觉得很遗憾，Discourse 上本已标配的这一选项却被忽略了。

---

<div class="post-metadata">

### Author: ![chapoi](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/chapoi/32/537252_2.png) [@chapoi](https://meta.discourse.org/u/chapoi)
#### Post date: [2026年八月27日 10:32 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/2 "2026-08-27T10:32:12Z")

</div>

不过，你为什么想要按字母顺序排序呢？你设想的用例是什么？

---

<div class="post-metadata">

### Author: ![gilles](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/gilles/32/549022_2.png) [@gilles](https://meta.discourse.org/u/gilles)
#### Post date: [2026年八月27日 10:35 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/3 "2026-08-27T10:35:54Z")

</div>

实际上，在我的分类中，我有一些桌游的资料页。我希望当我进入这些特定分类时，主题能够按字母顺序排序。我尝试自己写了一小段代码，但没能实现。

---

<div class="post-metadata">

### Author: ![RGJ](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/rgj/32/523185_2.png) [@RGJ](https://meta.discourse.org/u/RGJ)
#### Post date: [2026年八月27日 10:51 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/4 "2026-08-27T10:51:40Z")

</div>

这对于课程等主题标题为“第 1 课”、“第 2 课”等内容的场景非常有用。

此外，我对 doc-categories 插件最大的抱怨之一是它仍然会显示主题列表，尤其是在移动设备上。如果能按字母顺序排序，至少可以缓解部分问题。

---

<div class="post-metadata">

### Author: ![satonotdead](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/satonotdead/32/447830_2.png) [@satonotdead](https://meta.discourse.org/u/satonotdead)
#### Post date: [2026年八月27日 11:31 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/5 "2026-08-27T11:31:18Z")

</div>

我们强制进行本地 A-Z 渲染，以保持术语表的整洁。在某些特定情况下，这样做确实是非常必要的。

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [2026年八月27日 11:35 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/6 "2026-08-27T11:35:17Z")

</div>

> [@gilles](#):
>
> 1. 你会失去通常的磁贴布局。

我不太确定我是否理解这一点。在查看 [https://meta.discourse.org/filter?q=order%3Atitle-asc](https://meta.discourse.org/filter?q=order%3Atitle-asc) 时，具体是哪些磁贴丢失了？

---

<div class="post-metadata">

### Author: ![gilles](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/gilles/32/549022_2.png) [@gilles](https://meta.discourse.org/u/gilles)
#### Post date: [2026年八月27日 12:10 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/7 "2026-08-27T12:10:31Z")

</div>

我可能邮件里没表达清楚 😅 我以为我们会保留瓷砖的原有视觉设计

未筛选的分类

 ![image](https://global.discourse-cdn.com/meta/original/4X/2/0/0/2004b272721a2f0f6992e0f4ba458f8feb5f5afc.jpeg)

已筛选的分类

 ![image](https://global.discourse-cdn.com/meta/original/4X/c/e/1/ce113a98730cbc8c9f47e951a432efad509405ca.jpeg)

---

<div class="post-metadata">

### Author: ![satonotdead](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/satonotdead/32/447830_2.png) [@satonotdead](https://meta.discourse.org/u/satonotdead)
#### Post date: [2026年八月27日 12:45 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/8 "2026-08-27T12:45:42Z")

</div>

我可以确认，对于那些希望保持默认视图不变的用户来说，这个过滤器并不实用，尤其是当它应用于作为类别文档（如词汇表、CMS）使用的类别时。

它只是一个起点，但并未满足一个简单需求——也许从技术角度来看并非如此，但从我们用户的角度来看确实如此——即按字母顺序显示主题。

我认为，该过滤器以全面的方式满足了其他需求。

---

<div class="post-metadata">

### Author: ![satonotdead](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/satonotdead/32/447830_2.png) [@satonotdead](https://meta.discourse.org/u/satonotdead)
#### Post date: [2026年九月13日 23:28 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/9 "2026-09-13T23:28:56Z")

</div>

分享一下我过去几周一直在用的解决方案：

> [@satonotdead](#):
>
> 我最终为它使用了一个[自定义主题组件](https://github.com/somos-criptonautas/discourse-horizon-mod-nautas/blob/main/javascripts/api-initializers/init.gjs)：
> 
> ```plaintext
> // A-Z 排序需要获取整个分类：核心代码无法按标题对主题列表进行排序
> // (TopicQuery::SORTABLE_MAPPING 中没有 "title")，因此我们在排序前
> // 先遍历完分页列表。设置了上限——文档分类通常较小；如果更大，则回退到
> // 对已加载的内容进行排序，下方的 MutationObserver 会保持其最新状态。
> // ponytail: 如果核心代码将来支持服务端标题排序，请删除此整个循环。
> const MAX_DOC_PAGES = 12; // 每页 30 个主题，约 360 个主题
> let docLoadRun = 0;
> 
> async function loadAllDocTopics(api) {
> const run = ++docLoadRun;
> 
> // `controller:discovery/topics` 在当前 Discourse 版本中已弃用
> // (弃用 ID: discourse.discovery-topics-controller)。Discovery 现在为每个筛选器
> // 使用专用的控制器；文档分类始终是最新主题列表，因此我们直接查找该控制器。
> // 如果这里失败，只是意味着我们只排序第一页，因此每一步都使用了可选链。
> const list = api.container.lookup("controller:discovery/latest")?.model;
> if (!list?.loadMore) {
> return;
> }
> 
> for (let page = 0; page < MAX_DOC_PAGES; page++) {
> if (!list.more_topics_url) {
> return;
> }
> 
> const before = list.topics?.length ?? 0;
> await list.loadMore();
> 
> // 加载中途导航离开，或者列表停止增长——无论哪种情况，都停止。
> if (run !== docLoadRun || (list.topics?.length ?? 0) === before) {
> return;
> }
> }
> }
> 
> // 按 A-Z 顺序对文档主题列表进行排序，并使用页面自身的 <html lang> 进行排序规则匹配。
> let docSortObservers = new Map();
> 
> function docSortLocale() {
> const lang = document.documentElement.lang;
> if (!lang) {
> return undefined;
> }
> 
> try {
> Intl.getCanonicalLocales(lang); // 如果标签格式错误，会抛出 RangeError
> return lang;
> } catch {
> return undefined; // 回退到浏览器的默认区域设置
> }
> }
> 
> function sortDocTopicList(body, locale) {
> const rows = [...body.querySelectorAll(":scope > .topic-list-item")];
> if (rows.length < 2) {
> return;
> }
> 
> const sorted = [...rows].sort((a, b) => {
> const titleA = a.querySelector(".title")?.textContent.trim() ?? "";
> const titleB = b.querySelector(".title")?.textContent.trim() ?? "";
> return titleA.localeCompare(titleB, locale);
> });
> 
> if (sorted.every((row, i) => rows[i] === row)) {
> return; // 已经排序——防止观察器自我触发
> }
> 
> // 插入到行之后跟随的内容之前，保持任何“加载更多”哨兵在最后。
> const anchor = rows[rows.length - 1].nextSibling;
> 
> // 固定第一个可见行，以便在其上方插入时不会导致滚动跳动。
> const pinned = rows.find((row) => row.getBoundingClientRect().bottom > 0);
> const pinnedTop = pinned?.getBoundingClientRect().top;
> 
> // 暂停观察器，以便我们自己的移动不会重新进入此函数。
> const observer = docSortObservers.get(body);
> observer?.disconnect();
> sorted.forEach((row) => body.insertBefore(row, anchor));
> observer?.observe(body, { childList: true });
> 
> if (pinned) {
> window.scrollBy(0, pinned.getBoundingClientRect().top - pinnedTop);
> }
> }
> 
> function sortDocCategoryTopicLists() {
> docSortObservers.forEach((observer) => observer.disconnect());
> docSortObservers = new Map();
> 
> const locale = docSortLocale();
> 
> // 不是 flex/grid 容器，因此重新排序必须移动 DOM 节点——CSS `order` 会被忽略。
> document.querySelectorAll(".topic-list.doc-simple-mode .topic-list-body").forEach((body) => {
> // try/finally 确保即使排序抛出异常，列表也会始终显示。
> try {
> sortDocTopicList(body, locale);
> } finally {
> body.classList.add("docs-sorted");
> }
> 
> // 仅在加载平静后排序一次——加载中途重新排序会导致加载器停滞。
> let settleTimer = null;
> const observer = new MutationObserver(() => {
> window.clearTimeout(settleTimer);
> settleTimer = window.setTimeout(() => {
> sortDocTopicList(body, locale);
> }, 400);
> });
> observer.observe(body, { childList: true });
> docSortObservers.set(body, observer);
> });
> }
> 
> ```
> 
> 我认为这段代码很可能可以改进，我很乐意接受建议和/或 PR。

---

<div class="post-metadata">

### Author: ![gilles](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/gilles/32/549022_2.png) [@gilles](https://meta.discourse.org/u/gilles)
#### Post date: [2026年九月14日 06:25 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/10 "2026-09-14T06:25:00Z")

</div>

很棒的想法，谢谢 @satonotdead 👍
