Nuxt Content 按目录分组:嵌套内容文件获取
2025-03-08 15:33:15
Nuxt Content:按目录分组获取嵌套内容文件
咱们手头有一个博客文章列表 articles
,存在 content 文件夹里。目前是通过以下代码获取的:
async asyncData ({ $content }) {
const articles = await $content('', { deep: true })
// .where({ cat: 'A' })
.only(['title', 'description', 'img', 'slug', 'cat'])
.sortBy('createdAt', 'asc')
.fetch()
return { articles }
}
这段代码会获取所有文章,返回一个列表。 但现在, 我希望把文章按照不同的分类,进行展示,类似于手风琴折叠组件那种形式,显示在页面上。代码结构如下:
<template>
<div class="container">
<div v-for="article in articles" :key="article">
{{ article.title }}
</div>
<pre> {{ articles }}</pre>
<div v-for="accordion in accordions" :key="accordion.title" class="l">
<Item>
<template #title>
{{ accordion.title }}
</template>
<template #content>
<div> {{ accordion.text }}</div>
</template>
</Item>
</div>
<!-- here goes R -->
<div class="r" />
</div>
</template>
<script>
import Item from '../components/List-item.vue'
export default {
components: { Item },
async asyncData ({ $content }) {
const articles = await $content('', { deep: true })
// .where({ cat: 'A' })
.only(['title', 'description', 'img', 'slug', 'cat'])
.sortBy('createdAt', 'asc')
.fetch()
return { articles }
},
data () {
return {
accordions: [
{
title: 'A',
text: 'Projects from content/A'
},
{
title: 'B',
text: 'Projects from content/B'
},
{
title: 'C',
text: 'Projects from content/C'
}
]
}
</script>
组件 List-item.vue
的结构是这样的:
<template>
<div class="wrapper">
<div
:class="{ active: isActive }"
@click="toggle"
>
<a class="title">
<slot name="title" />
</a>
<div v-show="show" :class="{ active: isActive }" class="content">
<slot name="content" />
</div>
</div>
</div>
</template>
我的articles
数组数据放在一个v-for
里面。目前的问题是,不知道怎么按照 URL 或者分类,把数组进行动态分组。
问题原因
简单来说,$content('', { deep: true })
获取的是一个扁平化的文章列表,没有目录结构信息。我们需要自己动手,根据文章的路径或自定义的分类字段(比如 cat
)来分组。
解决方案
下面提供两种常用的解决方案,分别根据文件路径和自定义分类字段进行分组。
方案一:根据文件路径分组
这个方案利用文章的路径信息(如 articles/A/post1.md
)来确定其所属的目录。
-
获取文章列表并处理路径:
async asyncData({ $content }) { const articles = await $content('', { deep: true }) .only(['title', 'description', 'img', 'slug', 'path']) // 包含 path 字段 .sortBy('createdAt', 'asc') .fetch(); const groupedArticles = {}; articles.forEach(article => { // 从路径中提取目录名, 例如: /articles/A/xxx.md -> A const dir = article.path.split('/')[2]; if (!groupedArticles[dir]) { groupedArticles[dir] = []; } groupedArticles[dir].push(article); }); console.log(groupedArticles); return { groupedArticles }; }
代码解释:
$content('', { deep: true }).only(['title', ..., 'path'])
: 获取文章时,除了标题等,还要获取path
字段。articles.forEach(...)
: 遍历每篇文章。const dir = article.path.split('/')[2]
: 从路径中提取目录名。假设文章路径是/articles/A/post1.md
,这里会提取出A
。 实际中你的目录层级可能不同,请按需调整索引[2]
这个数值。if (!groupedArticles[dir]) { groupedArticles[dir] = []; }
: 如果groupedArticles
对象中还没有这个目录对应的数组,就创建一个空数组。groupedArticles[dir].push(article)
: 把当前文章添加到对应的目录数组中。
-
在模板中使用分组后的数据:
<template> <div class="container"> <div v-for="(group, dir) in groupedArticles" :key="dir" class="l"> <Item> <template #title> {{ dir }} </template> <template #content> <div v-for="article in group" :key="article.slug"> {{ article.title }} </div> </template> </Item> </div> </div> </template>
代码解释:
v-for="(group, dir) in groupedArticles"
: 遍历groupedArticles
对象。dir
是目录名 (例如A
,B
),group
是该目录下所有文章组成的数组。- 内部的
v-for="article in group"
: 遍历当前目录下的所有文章,进行展示。
方案二:根据自定义分类字段(cat
)分组
如果你的文章 Markdown 文件中有自定义的分类字段(如 cat: A
),可以根据这个字段来分组。
-
获取文章列表并根据
cat
字段分组:async asyncData({ $content }) { const articles = await $content('', { deep: true }) .only(['title', 'description', 'img', 'slug', 'cat']) .sortBy('createdAt', 'asc') .fetch(); const groupedArticles = {}; articles.forEach(article => { const category = article.cat; //获取 cat 字段的值。 if (!groupedArticles[category]) { groupedArticles[category] = []; } groupedArticles[category].push(article); }); console.log(groupedArticles); return { groupedArticles }; }
代码解释:
$content().only()
中包含cat
字段.- 其余逻辑与方案一类似,只是将
article.path.split('/')[1]
替换为article.cat
来获取分组依据。
-
模板中使用 (与方案一相同):
<template> <div class="container"> <div v-for="(group, category) in groupedArticles" :key="category" class="l"> <Item> <template #title> {{ category }} </template> <template #content> <div v-for="article in group" :key="article.slug"> {{ article.title }} </div> </template> </Item> </div> </div> </template>
进阶技巧:使用计算属性
如果你的页面中多个地方需要用到分组后的数据, 或者你想对分组数据做进一步处理 (比如排序), 可以把分组逻辑放到计算属性中:
<script>
export default {
// ...
async asyncData({ $content }) {
const articles = await $content('', { deep: true })
.only(['title', 'description', 'img', 'slug', 'path', 'cat'])
.sortBy('createdAt', 'asc')
.fetch()
return { articles }
},
computed: {
groupedByPath() {
const grouped = {}
this.articles.forEach(article => {
const dir = article.path.split('/')[2]
if (!grouped[dir]) {
grouped[dir] = []
}
grouped[dir].push(article)
})
return grouped
},
groupedByCat() {
const grouped = {}
this.articles.forEach(article => {
const cat = article.cat
if (!grouped[cat]) {
grouped[cat] = []
}
grouped[cat].push(article)
})
// 按照字母顺序对分类进行排序
return Object.keys(grouped).sort().reduce(
(obj, key) => {
obj[key] = grouped[key];
return obj;
},
{}
);
}
},
// ...
}
</script>
然后,在模板中, 可以像这样使用:
<!-- 使用 groupedByPath -->
<div v-for="(group, dir) in groupedByPath" :key="dir">...</div>
<!-- 使用 groupedByCat -->
<div v-for="(group, cat) in groupedByCat" :key="cat">...</div>
这么做的好处是,分组逻辑只写一次, 避免重复代码,而且可以对分组结果做进一步处理。
希望这两种方案可以帮到你! 选择哪种方案,取决于你的具体需求和内容结构。如果文章已经按照目录存放, 方案一更直接; 如果你更喜欢用自定义字段来管理分类, 方案二更合适。