Skip to content

多作者博客搭建

当博客从个人项目发展为团队协作时,需要支持多作者体系:每篇文章归属特定作者,每个作者有独立的主页,首页展示团队阵容。本文在 博客搭建实战 基础上,扩展多作者功能。

项目规划

目录结构

text
docs/
├── .vitepress/
│   ├── config.mts
│   └── theme/
│       ├── index.ts
│       ├── components/
│       │   ├── AuthorCard.vue        # 作者卡片
│       │   ├── AuthorList.vue        # 作者列表
│       │   ├── AuthorPage.vue        # 作者主页
│       │   ├── ArticleMeta.vue       # 文章元信息(含作者)
│       │   └── TeamSection.vue       # 团队展示区
│       └── composables/
│           └── useAuthors.ts         # 作者数据管理
├── authors/                          # 作者主页
│   ├── zhang-san.md
│   ├── li-si.md
│   └── wang-wu.md
├── blog/                             # 博客文章
│   ├── post-1.md
│   ├── post-2.md
│   └── post-3.md
└── index.md                          # 首页

数据模型

ts
// 类型定义
interface Author {
  id: string
  name: string
  avatar: string
  bio: string
  social: {
    github?: string
    twitter?: string
    website?: string
  }
  title?: string       // 职位/头衔
  location?: string    // 所在地
  joinedAt: string     // 加入日期
}

interface PostWithAuthor {
  url: string
  title: string
  excerpt: string
  date: string
  author: Author
  tags: string[]
  readingTime: number
}

作者数据管理

创建作者数据文件

ts
// docs/.vitepress/theme/composables/useAuthors.ts
import { useData } from 'vitepress'

// 作者信息集中管理
const authors: Record<string, Author> = {
  'zhang-san': {
    id: 'zhang-san',
    name: '张三',
    avatar: '/images/authors/zhang-san.jpg',
    bio: '前端架构师,专注于 Vue 生态和工程化实践。热爱开源,长期贡献 VitePress 社区。',
    title: '前端架构师',
    location: '北京',
    joinedAt: '2025-01-15',
    social: {
      github: 'https://github.com/zhang-san',
      twitter: 'https://twitter.com/zhang-san',
      website: 'https://zhang-san.dev'
    }
  },
  'li-si': {
    id: 'li-si',
    name: '李四',
    avatar: '/images/authors/li-si.jpg',
    bio: '技术作家,专注开发者文档和知识管理。坚信好的文档能改变世界。',
    title: '技术作家',
    location: '上海',
    joinedAt: '2025-03-01',
    social: {
      github: 'https://github.com/li-si',
      website: 'https://li-si.me'
    }
  },
  'wang-wu': {
    id: 'wang-wu',
    name: '王五',
    avatar: '/images/authors/wang-wu.jpg',
    bio: '全栈工程师,热衷于分享实战经验。从 Vue 2 时代一路走来的老兵。',
    title: '全栈工程师',
    location: '深圳',
    joinedAt: '2025-06-10',
    social: {
      github: 'https://github.com/wang-wu'
    }
  }
}

export function useAuthors() {
  function getAuthor(id: string): Author | undefined {
    return authors[id]
  }

  function getAllAuthors(): Author[] {
    return Object.values(authors)
  }

  function getAuthorByPost(authorId: string): Author {
    return authors[authorId] || {
      id: 'unknown',
      name: '未知作者',
      avatar: '/images/authors/default.jpg',
      bio: '',
      joinedAt: '',
      social: {}
    }
  }

  return {
    getAuthor,
    getAllAuthors,
    getAuthorByPost
  }
}

文章 frontmatter 定义作者

yaml
---
title: VitePress 性能优化实战
date: 2026-04-20
author: zhang-san    # 引用作者 ID
tags:
  - VitePress
  - 优化
---

数据加载器

扩展文章加载器

ts
// docs/blog.data.ts
import { createContentLoader } from 'vitepress'
import type { PostWithAuthor } from './.vitepress/theme/composables/useAuthors'

// 作者映射(构建时可用)
const authorMap: Record<string, { name: string; avatar: string }> = {
  'zhang-san': { name: '张三', avatar: '/images/authors/zhang-san.jpg' },
  'li-si': { name: '李四', avatar: '/images/authors/li-si.jpg' },
  'wang-wu': { name: '王五', avatar: '/images/authors/wang-wu.jpg' }
}

declare const data: PostWithAuthor[]
export { data }

export default createContentLoader('blog/*.md', {
  excerpt: ({ frontmatter }) => frontmatter.description || '',
  transform(raw): PostWithAuthor[] {
    return raw
      .map(({ url, frontmatter, excerpt }) => {
        const authorId = frontmatter.author || 'unknown'
        const authorInfo = authorMap[authorId] || { name: '未知作者', avatar: '/images/authors/default.jpg' }

        return {
          url,
          title: frontmatter.title || '',
          excerpt: excerpt || '',
          date: frontmatter.date || '',
          author: {
            id: authorId,
            name: authorInfo.name,
            avatar: authorInfo.avatar,
            bio: '',
            joinedAt: '',
            social: {}
          },
          tags: frontmatter.tags || [],
          readingTime: Math.ceil((frontmatter.wordCount || 500) / 200)
        }
      })
      .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
  }
})

按作者过滤加载器

ts
// docs/blog-author.data.ts
import { createContentLoader } from 'vitepress'

interface AuthorPosts {
  authorId: string
  posts: {
    url: string
    title: string
    date: string
    excerpt: string
    tags: string[]
  }[]
}

declare const data: Record<string, AuthorPosts>
export { data }

export default createContentLoader('blog/*.md', {
  transform(raw): Record<string, AuthorPosts> {
    const result: Record<string, AuthorPosts> = {}

    for (const { url, frontmatter, excerpt } of raw) {
      const authorId = frontmatter.author || 'unknown'
      if (!result[authorId]) {
        result[authorId] = { authorId, posts: [] }
      }
      result[authorId].posts.push({
        url,
        title: frontmatter.title || '',
        date: frontmatter.date || '',
        excerpt: excerpt || '',
        tags: frontmatter.tags || []
      })
    }

    // 按日期排序
    for (const key of Object.keys(result)) {
      result[key].posts.sort(
        (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
      )
    }

    return result
  }
})

组件开发

作者卡片组件

vue
<!-- .vitepress/theme/components/AuthorCard.vue -->
<script setup lang="ts">
interface Props {
  author: {
    id: string
    name: string
    avatar: string
    bio: string
    title?: string
    location?: string
    social?: {
      github?: string
      twitter?: string
      website?: string
    }
  }
  postCount?: number
  compact?: boolean
}

withDefaults(defineProps<Props>(), {
  postCount: 0,
  compact: false
})
</script>

<template>
  <div class="author-card" :class="{ compact }">
    <a :href="`/authors/${author.id}`" class="author-link">
      <img :src="author.avatar" :alt="author.name" class="author-avatar" />
    </a>
    <div class="author-info">
      <a :href="`/authors/${author.id}`" class="author-name">{{ author.name }}</a>
      <div v-if="author.title" class="author-title">{{ author.title }}</div>
      <p v-if="!compact && author.bio" class="author-bio">{{ author.bio }}</p>
      <div class="author-meta">
        <span v-if="author.location" class="meta-item">📍 {{ author.location }}</span>
        <span v-if="postCount > 0" class="meta-item">📝 {{ postCount }} 篇文章</span>
      </div>
      <div v-if="!compact && author.social" class="author-social">
        <a
          v-if="author.social.github"
          :href="author.social.github"
          target="_blank"
          rel="noopener"
          class="social-link"
        >
          GitHub
        </a>
        <a
          v-if="author.social.twitter"
          :href="author.social.twitter"
          target="_blank"
          rel="noopener"
          class="social-link"
        >
          Twitter
        </a>
        <a
          v-if="author.social.website"
          :href="author.social.website"
          target="_blank"
          rel="noopener"
          class="social-link"
        >
          个人网站
        </a>
      </div>
    </div>
  </div>
</template>

<style scoped>
.author-card {
  display: flex;
  gap: 16px;
  padding: 24px;
  border: 1px solid var(--vp-c-divider);
  border-radius: 12px;
  transition: border-color 0.25s, box-shadow 0.25s;
}

.author-card:hover {
  border-color: var(--vp-c-brand);
  box-shadow: 0 2px 12px rgba(var(--vp-c-brand-rgb), 0.1);
}

.author-card.compact {
  padding: 12px;
  gap: 12px;
}

.author-link {
  flex-shrink: 0;
}

.author-avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  object-fit: cover;
}

.compact .author-avatar {
  width: 48px;
  height: 48px;
}

.author-info {
  flex: 1;
  min-width: 0;
}

.author-name {
  font-size: 18px;
  font-weight: 600;
  color: var(--vp-c-text-1);
  text-decoration: none;
}

.author-name:hover {
  color: var(--vp-c-brand);
}

.author-title {
  font-size: 14px;
  color: var(--vp-c-text-3);
  margin-top: 2px;
}

.author-bio {
  font-size: 14px;
  color: var(--vp-c-text-2);
  margin-top: 8px;
  line-height: 1.6;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.author-meta {
  display: flex;
  gap: 12px;
  margin-top: 8px;
}

.meta-item {
  font-size: 13px;
  color: var(--vp-c-text-3);
}

.author-social {
  display: flex;
  gap: 12px;
  margin-top: 12px;
}

.social-link {
  font-size: 13px;
  color: var(--vp-c-brand);
  text-decoration: none;
  padding: 2px 8px;
  border: 1px solid var(--vp-c-brand);
  border-radius: 4px;
  transition: all 0.25s;
}

.social-link:hover {
  background: var(--vp-c-brand);
  color: var(--vp-c-white);
}
</style>

文章元信息组件

vue
<!-- .vitepress/theme/components/ArticleMeta.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'
import { useAuthors } from '../composables/useAuthors'
import { computed } from 'vue'

const { frontmatter } = useData()
const { getAuthorByPost } = useAuthors()

const author = computed(() => getAuthorByPost(frontmatter.value.author || 'unknown'))
const formattedDate = computed(() => {
  const date = frontmatter.value.date
  if (!date) return ''
  return new Date(date).toLocaleDateString('zh-CN', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  })
})
</script>

<template>
  <div class="article-meta">
    <a :href="`/authors/${author.id}`" class="meta-author">
      <img :src="author.avatar" :alt="author.name" class="author-avatar" />
      <div class="author-detail">
        <span class="author-name">{{ author.name }}</span>
        <span class="publish-date">{{ formattedDate }}</span>
      </div>
    </a>
    <div v-if="frontmatter.tags?.length" class="meta-tags">
      <a
        v-for="tag in frontmatter.tags"
        :key="tag"
        :href="`/tags.html#${tag}`"
        class="tag"
      >
        {{ tag }}
      </a>
    </div>
  </div>
</template>

<style scoped>
.article-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 0;
  border-bottom: 1px solid var(--vp-c-divider);
  margin-bottom: 24px;
}

.meta-author {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
}

.author-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
}

.author-detail {
  display: flex;
  flex-direction: column;
}

.author-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--vp-c-text-1);
}

.publish-date {
  font-size: 12px;
  color: var(--vp-c-text-3);
}

.meta-tags {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.tag {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 4px;
  background: var(--vp-c-bg-soft);
  color: var(--vp-c-text-2);
  text-decoration: none;
  border: 1px solid var(--vp-c-divider);
  transition: all 0.25s;
}

.tag:hover {
  border-color: var(--vp-c-brand);
  color: var(--vp-c-brand);
}

@media (max-width: 640px) {
  .article-meta {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
}
</style>

团队展示组件

vue
<!-- .vitepress/theme/components/TeamSection.vue -->
<script setup lang="ts">
import { useAuthors } from '../composables/useAuthors'
import AuthorCard from './AuthorCard.vue'

const { getAllAuthors } = useAuthors()
const authors = getAllAuthors()
</script>

<template>
  <section class="team-section">
    <h2 class="section-title">作者团队</h2>
    <p class="section-desc">我们的团队成员来自不同领域,共同打造优质内容</p>
    <div class="team-grid">
      <AuthorCard
        v-for="author in authors"
        :key="author.id"
        :author="author"
        :post-count="0"
      />
    </div>
  </section>
</template>

<style scoped>
.team-section {
  padding: 64px 24px;
  max-width: 1152px;
  margin: 0 auto;
}

.section-title {
  font-size: 28px;
  font-weight: 700;
  text-align: center;
  margin-bottom: 8px;
}

.section-desc {
  text-align: center;
  color: var(--vp-c-text-2);
  margin-bottom: 48px;
}

.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 24px;
}

@media (max-width: 768px) {
  .team-grid {
    grid-template-columns: 1fr;
  }
}
</style>

作者主页组件

vue
<!-- .vitepress/theme/components/AuthorPage.vue -->
<script setup lang="ts">
import { useAuthors } from '../composables/useAuthors'
import { data as authorPosts } from '../../blog-author.data'
import AuthorCard from './AuthorCard.vue'

const props = defineProps<{
  authorId: string
}>()

const { getAuthor } = useAuthors()
const author = getAuthor(props.authorId)

const posts = authorPosts[props.authorId]?.posts || []
</script>

<template>
  <div v-if="author" class="author-page">
    <!-- 作者信息头部 -->
    <div class="author-header">
      <img :src="author.avatar" :alt="author.name" class="author-avatar" />
      <div class="author-info">
        <h1 class="author-name">{{ author.name }}</h1>
        <div v-if="author.title" class="author-title">{{ author.title }}</div>
        <p class="author-bio">{{ author.bio }}</p>
        <div class="author-meta">
          <span v-if="author.location">📍 {{ author.location }}</span>
          <span>📝 {{ posts.length }} 篇文章</span>
          <span v-if="author.joinedAt">🕐 加入于 {{ author.joinedAt }}</span>
        </div>
        <div class="author-social">
          <a v-if="author.social?.github" :href="author.social.github" target="_blank" rel="noopener">
            GitHub
          </a>
          <a v-if="author.social?.twitter" :href="author.social.twitter" target="_blank" rel="noopener">
            Twitter
          </a>
          <a v-if="author.social?.website" :href="author.social.website" target="_blank" rel="noopener">
            个人网站
          </a>
        </div>
      </div>
    </div>

    <!-- 文章列表 -->
    <div class="author-posts">
      <h2 class="posts-title">文章列表</h2>
      <div v-if="posts.length" class="post-list">
        <a v-for="post in posts" :key="post.url" :href="post.url" class="post-item">
          <span class="post-date">{{ post.date }}</span>
          <span class="post-title">{{ post.title }}</span>
          <div v-if="post.tags?.length" class="post-tags">
            <span v-for="tag in post.tags" :key="tag" class="tag">{{ tag }}</span>
          </div>
        </a>
      </div>
      <p v-else class="no-posts">该作者暂无文章</p>
    </div>
  </div>
  <div v-else class="not-found">
    <h1>作者不存在</h1>
    <a href="/blog/">返回博客首页</a>
  </div>
</template>

<style scoped>
.author-header {
  display: flex;
  gap: 32px;
  padding: 48px 0;
  border-bottom: 1px solid var(--vp-c-divider);
  margin-bottom: 32px;
}

.author-avatar {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.author-info {
  flex: 1;
}

.author-name {
  font-size: 32px;
  font-weight: 700;
  margin-bottom: 4px;
}

.author-title {
  font-size: 16px;
  color: var(--vp-c-text-3);
  margin-bottom: 12px;
}

.author-bio {
  font-size: 16px;
  color: var(--vp-c-text-2);
  line-height: 1.6;
  margin-bottom: 12px;
}

.author-meta {
  display: flex;
  gap: 16px;
  font-size: 14px;
  color: var(--vp-c-text-3);
  margin-bottom: 12px;
}

.author-social {
  display: flex;
  gap: 12px;
}

.author-social a {
  font-size: 13px;
  color: var(--vp-c-brand);
  text-decoration: none;
  padding: 4px 12px;
  border: 1px solid var(--vp-c-brand);
  border-radius: 6px;
  transition: all 0.25s;
}

.author-social a:hover {
  background: var(--vp-c-brand);
  color: var(--vp-c-white);
}

.posts-title {
  font-size: 20px;
  font-weight: 600;
  margin-bottom: 16px;
}

.post-item {
  display: block;
  padding: 16px;
  border: 1px solid var(--vp-c-divider);
  border-radius: 8px;
  margin-bottom: 12px;
  text-decoration: none;
  transition: all 0.25s;
}

.post-item:hover {
  border-color: var(--vp-c-brand);
  transform: translateX(4px);
}

.post-date {
  font-size: 13px;
  color: var(--vp-c-text-3);
}

.post-title {
  display: block;
  font-size: 16px;
  font-weight: 500;
  color: var(--vp-c-text-1);
  margin-top: 4px;
}

.post-tags {
  display: flex;
  gap: 6px;
  margin-top: 8px;
}

.tag {
  font-size: 12px;
  padding: 2px 6px;
  border-radius: 3px;
  background: var(--vp-c-bg-soft);
  color: var(--vp-c-text-2);
  border: 1px solid var(--vp-c-divider);
}

.no-posts {
  color: var(--vp-c-text-3);
  text-align: center;
  padding: 32px;
}

.not-found {
  text-align: center;
  padding: 64px 24px;
}

@media (max-width: 640px) {
  .author-header {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .author-meta {
    justify-content: center;
    flex-wrap: wrap;
  }

  .author-social {
    justify-content: center;
  }
}
</style>

作者主页页面

为每个作者创建独立的 Markdown 页面:

markdown
<!-- docs/authors/zhang-san.md -->
---
title: 张三
layout: page
---

<AuthorPage author-id="zhang-san" />
markdown
<!-- docs/authors/li-si.md -->
---
title: 李四
layout: page
---

<AuthorPage author-id="li-si" />

注册组件

ts
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import AuthorCard from './components/AuthorCard.vue'
import AuthorPage from './components/AuthorPage.vue'
import ArticleMeta from './components/ArticleMeta.vue'
import TeamSection from './components/TeamSection.vue'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    app.component('AuthorCard', AuthorCard)
    app.component('AuthorPage', AuthorPage)
    app.component('ArticleMeta', ArticleMeta)
    app.component('TeamSection', TeamSection)
  }
}

在文章中使用

文章模板自动显示作者

vue
<!-- .vitepress/theme/Layout.vue -->
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import ArticleMeta from './components/ArticleMeta.vue'
import { useData } from 'vitepress'
import { computed } from 'vue'

const { Layout } = DefaultTheme
const { frontmatter } = useData()

const isBlogPost = computed(() => frontmatter.value.layout === 'blog')
</script>

<template>
  <Layout>
    <template #doc-before>
      <ArticleMeta v-if="isBlogPost" />
    </template>
  </Layout>
</template>

文章 frontmatter

yaml
---
title: 使用 Vue 3 组合式 API 构建复杂组件
date: 2026-04-15
author: wang-wu
layout: blog
tags:
  - Vue
  - 组件
description: 深入探讨 Vue 3 组合式 API 在复杂场景下的应用模式
---

首页集成团队展示

markdown
<!-- docs/index.md -->
---
layout: home
---

<div class="home-team-section">
  <TeamSection />
</div>

或者通过布局插槽插入:

vue
<!-- .vitepress/theme/Layout.vue -->
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import TeamSection from './components/TeamSection.vue'

const { Layout } = DefaultTheme
</script>

<template>
  <Layout>
    <template #home-features-after>
      <TeamSection />
    </template>
  </Layout>
</template>

侧边栏配置

ts
// .vitepress/config.mts
export default defineConfig({
  themeConfig: {
    sidebar: {
      '/blog/': [
        {
          text: '博客',
          items: [
            { text: '所有文章', link: '/blog/' }
          ]
        },
        {
          text: '作者',
          items: [
            { text: '张三', link: '/authors/zhang-san' },
            { text: '李四', link: '/authors/li-si' },
            { text: '王五', link: '/authors/wang-wu' }
          ]
        }
      ]
    }
  }
})

高级功能

作者贡献统计

ts
// composables/useAuthorStats.ts
import { useAuthors } from './useAuthors'
import { data as authorPosts } from '../../blog-author.data'

export function useAuthorStats() {
  const { getAllAuthors } = useAuthors()

  function getAuthorPostCount(authorId: string): number {
    return authorPosts[authorId]?.posts.length || 0
  }

  function getAuthorRanking() {
    return getAllAuthors()
      .map(author => ({
        ...author,
        postCount: getAuthorPostCount(author.id)
      }))
      .sort((a, b) => b.postCount - a.postCount)
  }

  function getTotalStats() {
    const authors = getAllAuthors()
    const totalPosts = Object.values(authorPosts)
      .reduce((sum, author) => sum + author.posts.length, 0)
    return {
      totalAuthors: authors.length,
      totalPosts
    }
  }

  return {
    getAuthorPostCount,
    getAuthorRanking,
    getTotalStats
  }
}

RSS 订阅按作者

ts
// scripts/generate-author-rss.mjs
import { writeFileSync } from 'fs'
import { createContentLoader } from 'vitepress'

async function generateAuthorRss(authorId: string, authorName: string) {
  const posts = await createContentLoader('blog/*.md', {
    render: true,
    excerpt: true
  }).load()

  const authorPosts = posts.filter(p => p.frontmatter.author === authorId)

  const rss = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>${authorName} 的博客</title>
    <link>https://example.com/authors/${authorId}</link>
    <description>${authorName} 发布的文章</description>
    ${authorPosts.map(post => `
    <item>
      <title>${post.frontmatter.title}</title>
      <link>https://example.com${post.url}</link>
      <description>${post.excerpt || ''}</description>
      <pubDate>${new Date(post.frontmatter.date).toUTCString()}</pubDate>
    </item>`).join('')}
  </channel>
</rss>`

  writeFileSync(`docs/public/feed/${authorId}.xml`, rss)
}

// 为每个作者生成 RSS
generateAuthorRss('zhang-san', '张三')
generateAuthorRss('li-si', '李四')
generateAuthorRss('wang-wu', '王五')

最佳实践

实践说明
作者 ID 稳定作者 ID 一旦确定不要更改,会影响所有文章关联
头像统一尺寸建议使用 200×200 像素的正方形图片
Bio 控制长度建议在 100 字以内,避免卡片过长
数据加载优化作者数据使用单独的 data loader,避免重复加载
社交链接验证定期检查作者社交链接是否有效
默认头像为未设置头像的作者提供默认占位图

常见问题

Q: 如何动态添加新作者?

useAuthors.ts 中添加新作者信息,然后在 blog-author.data.tsauthorMap 中同步添加,最后创建对应的 authors/xxx.md 页面。

Q: 一篇文章可以有多个作者吗?

可以。将 author 字段改为 authors 数组:

yaml
---
authors:
  - zhang-san
  - li-si
---

然后修改组件以支持多作者遍历显示。

Q: 如何限制作者只能编辑自己的文章?

这需要配合 Git 权限管理或 CI/CD 检查。可在 GitHub Actions 中添加路径权限检查:

yaml
# .github/workflows/check-author.yml
name: Check Author
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check author permission
        run: |
          # 检查 PR 修改的文件是否属于该作者
          node scripts/check-author-permission.mjs

相关资源

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献