从零开始搭建博客网站(五)

从零开始搭建博客网站(五):文章列表。


文章列表

现状

PageContentHome.vue 中可以看到,当前的文章列表是通过手写 DOM 添加的,这种重复性的工作显然是不应该存在的。

vue
<script setup lang="ts">
import { useData } from "vitepress";

const { site } = useData();
</script>

<template>
  <div class="mx-auto max-w-[700px]">
    <h1 class="m-4 text-center text-4xl">{{ site.title }}</h1>
    <div class="m-4 min-h-[200px] text-center">
      <p>{{ site.description }}</p>
    </div>
    <ul class="flex flex-col space-y-2">
      <li class="text-blue-500"><a href="/markdown-examples.html">Markdown Examples</a></li>
      <li class="text-blue-500"><a href="/api-examples.html">API Examples</a></li>
    </ul>
  </div>
</template>

构建时数据加载|Build-Time Data Loading

如果您的创造性足够强,也可以直接去看 VitePress 官方文档。这里我会用我自己的方式来操作。

简要来说,对于目前的需求,我们需要提取所有文章的一些元数据,比如标题、发布时间、地址等,并渲染一个文章列表作目录使用。

所有的构建时数据加载都需要在 *.data.ts / *.data.js 文件中完成。我们在 theme/ 文件下新建 src/ 文件夹,并新建一个 posts.data.ts / posts.data.js 文件:

ts
import { 
createContentLoader
} from "vitepress";
export default
createContentLoader
("posts/*.md", {
transform
(
raw
) {
return
raw
.
map
(({
url
,
frontmatter
}) => ({
url
,
frontmatter
,
})); }, });

这里我们使用 VitePress 提供的 createContentLoader() 辅助函数来创建一个内容加载器。它的第一个参数是一个 glob 模式,用于匹配需要加载的文件。第二个参数是一个选项对象,用于配置加载器的行为。更细节的内容可以参考 官方文档

在这里,我们匹配了 posts/ 目录下的所有 .md 文件,并提取其 urlfrontmatter 属性。在 transform() 成员中可以对数据进行操作,这里先不进行任何处理。

当然,我们需要把除了 index.md 外的所有的 MD 文件都放在 ./docs/posts/ 目录下。

或者如果您自己有管理文档和路由的思路,请自由发挥。

现在我们可以在 PageContentHome.vue 中使用导出的数据:

vue
<!-- 这两行红的没用,只是用来正确显示下面的 error,如果要复制粘贴,记得删掉。 -->
<script>
</script>

<script setup lang="ts">
// ...
import { data as 
posts
} from "../src/posts.data";
Module '"../src/posts.data"' has no exported member 'data'. Did you mean to use 'import data from "../src/posts.data"' instead?
// ... </script> <template> <!-- ... --> </template>
vue
<script setup>
// ...
import { data as posts } from "../src/posts.data";
// ...
</script>

<template>
  <!-- ... -->
</template>

一些类型问题

不出意外的话,TS 用户又要出意外了。根据 VitePress 文档 所述,VitePress 在后台调用了 load() 方法,将 *.data.ts / *.data.js 的默认导出用 data 具名导出来隐式暴露。所以在使用具名导入的时候,编译器会认为这个模块没有名为 data 的具名导出。

解决方法也很简单,在 posts.data.ts 中导出相应类型即可:

ts
import { 
createContentLoader
} from "vitepress";
export interface Data { // ... } declare const
data
: Data[];
export {
data
};
export default
createContentLoader
("posts/*.md", {
transform
(
raw
) {
return
raw
.
map
(({
url
,
frontmatter
}) => ({
url
,
frontmatter
,
})); }, });

生成文章列表

Data 接口中,需要定义经过 transform() 后的属性。要做一个最基本的文章列表,我们需要每篇文章的链接和标题,其中标题可以在文章的 YAML 头中定义,从而通过上面的 frontmatter 访问:

ts
// ...
export interface Data {
  
url
: string;
frontmatter
:
Record
<string, any>;
} // ...
vue
<script setup lang="ts">
// ...
</script>

<template>
  <div class="mx-auto max-w-[700px]">
    <h1 class="m-4 text-center text-4xl">{{ site.title }}</h1>
    <div class="m-4 min-h-[200px] text-center">
      <p>{{ site.description }}</p>
    </div>
    <div>
      <div
        class="py-2 text-neutral-600 transition-colors duration-200 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
        v-for="post in posts"
        :key="post.url"
      >
        <a
          class="text-2xl"
          :href="post.url"
          >{{ post.frontmatter.title }}</a
        >
      </div>
    </div>
  </div>
</template>

当然,一定要确保每个 MD 文件都有 title 这个 frontmatter:

markdown
---
title: "Example Title"
---

效果如下图所示:

老是在改改改的主页