返回

Nuxt Content 按目录分组:嵌套内容文件获取

vue.js

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)来确定其所属的目录。

  1. 获取文章列表并处理路径:

    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): 把当前文章添加到对应的目录数组中。
  2. 在模板中使用分组后的数据:

    <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),可以根据这个字段来分组。

  1. 获取文章列表并根据 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 来获取分组依据。
  2. 模板中使用 (与方案一相同):

    <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>

这么做的好处是,分组逻辑只写一次, 避免重复代码,而且可以对分组结果做进一步处理。

希望这两种方案可以帮到你! 选择哪种方案,取决于你的具体需求和内容结构。如果文章已经按照目录存放, 方案一更直接; 如果你更喜欢用自定义字段来管理分类, 方案二更合适。