Skip to content

创建插件以实现首页文章的自动更新

TIP

在VitePress原版案例中,首页三个文本框由features定义,仅能实现静态文字展示,所以我想通过自己编写vue插件来实现首页文章的自动更新。

这个插件可以干什么?

  • 自动识别新增的文章并显示在首页指定的栏目中。

  • 每个栏目的标题、副标题、字号、行间距、分隔符、显示文章数量等皆可自定义。

安装依赖

该功能需要用到三个依赖:fast-globgray-matterdayjs

TIP

  • fast-glob:高性能递归读取文件(比 Node 原生快,支持通配符 **/*.md)。
  • gray-matter:把 Markdown 头部的 --- 区域解析成 JSON(拿标题、日期)。
  • dayjs:把不同格式的日期统一成时间戳,方便排序。

安装命令:

bash
pnpm add -D fast-glob gray-matter dayjs

创建插件

编辑DynamicFeatureBox.vue

js
<script setup lang="ts">
import { computed } from 'vue'
import { data } from '../../data/posts.data.mjs'

interface Props {
  title: string
  subTitle?: string
  folder: string
  max?: number
}
const props = withDefaults(defineProps<Props>(), { max: 3 })
const list = computed(() =>
  data
    .filter(p => p.file.startsWith(props.folder))
    .sort((a, b) => +new Date(b.date) - +new Date(a.date))
    .slice(0, props.max)
)
</script>

<template>
  <div class="feature-box">
    <!-- 主标题 -->
    <h3 class="feature-title">{{ title }}</h3>
    <!-- 副标题(选配) -->
    <p v-if="subTitle" class="feature-subtitle">{{ subTitle }}</p>

    <!-- 文章列表 -->
    <ul v-if="list.length" class="feature-list">
      <li v-for="(item, idx) in list" :key="item.url">
        <span v-if="idx === 0" class="new-badge">🔥‼️</span>
        <a :href="item.url">{{ item.title }}</a>
      </li>
    </ul>

    <!-- 空状态 -->
    <p v-else class="empty-tip">暂无文章</p>
  </div>
</template>

<style scoped>
.feature-box {
  flex: 1;
  padding: 1.5rem 1.25rem;
  border: 1px solid var(--vp-c-divider);
  border-radius: 12px;
  background: var(--vp-c-bg-soft);
}
.feature-title {
  margin: 0 0 0.25rem;
  font-size: 1.25rem;
  color: var(--vp-c-brand);
}
.feature-subtitle {
  margin: 0 0 0.75rem;
  font-size: 1.0rem;
  color: var(--vp-c-text-2);
}
.feature-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.feature-list li {
  margin: 0.2rem 0;
  font-size: 0.95rem;
  border-bottom: 1px solid rgb(219, 219, 219);
  padding-bottom: 0.1em;
}
.feature-list li:last-child {
  border-bottom: none;
}
.new-badge {
  color: #f43f5e;
  font-size: 0.8rem;
  margin-right: 0.25rem;
}
.empty-tip {
  margin: 0;
  font-size: 0.9rem;
  color: var(--vp-c-text-3);
  font-style: italic;
}
a {
  color: var(--vp-c-text-1);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
</style>

编辑posts.data.mts

TIP

posts.data.mts要放在/.vitepress/data中。

js
import { defineLoader } from 'vitepress'
import fg from 'fast-glob'
import matter from 'gray-matter'
import fs from 'fs'
import path from 'path'

export default defineLoader({
  watch: ['**/*.md'],                          // 监听根目录所有 md
  async load() {
    const files = await fg('**/*.md', {
      ignore: ['node_modules/**', '.vitepress/**', 'dist/**']
    })
    return files.map(file => {
      const src = fs.readFileSync(path.resolve(file), 'utf-8')
      const { data: front } = matter(src)
      return {
        title: front.title || path.basename(file, '.md'),
        date: front.date || fs.statSync(path.resolve(file)).mtime,
        file,
        url: '/' + file.replace(/(^|\/)index\.md$/, '').replace(/\.md$/, '')
      }
    })
  }
})

注册插件

js
import DynamicFeatureBox from './components/DynamicFeatureBox.vue'
app.component('DynamicFeatureBox', DynamicFeatureBox)

使用插件

替换掉原有的features

html
<script setup>
import DynamicFeatureBox from './.vitepress/theme/components/DynamicFeatureBox.vue'
</script>

<div class="features-container">
  <DynamicFeatureBox title="🥳 最新公告 📣" sub-title="⏱️ 站点动态与重要通知" folder="doc_notic" :max="3" />
  <DynamicFeatureBox title="📚 知识仓库 🧐" sub-title="⏳ 搜集各类知识点与小技巧" folder="doc_wiki" :max="3" />
  <DynamicFeatureBox title="💡 社区文章 📝" sub-title="🎞️ 用户分享与经验交流" folder="doc_doc" :max="3" />
</div>

<style scoped>
.features-container {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin-top: 2.5rem;
}

@media (max-width: 768px) {
  .features-container {
    flex-direction: column;
  }
}
</style>