Skip to content

集成评论系统

为你的站点添加评论功能,增强用户互动。

选择评论系统

系统特点推荐场景
Giscus基于 GitHub Discussions技术博客、开源项目
Waline自托管、功能丰富个人博客、社区
Twikoo腾讯云开发、简单国内用户为主

本教程以 Giscus 为例。

准备工作

1. 创建 GitHub 仓库

确保你的仓库是公开的。

2. 启用 Discussions

在仓库 Settings > Features 中启用 Discussions。

3. 安装 Giscus App

访问 giscus.app,按照指引安装 GitHub App。

4. 获取配置

在 giscus.app 上配置并获取:

  • data-repo: 仓库地址
  • data-repo-id: 仓库 ID
  • data-category: 讨论分类
  • data-category-id: 分类 ID

创建评论组件

vue
<!-- .vitepress/theme/components/GiscusComments.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'
import { ref, onMounted, watch } from 'vue'

const { isDark, page } = useData()
const container = ref<HTMLElement>()

const giscusConfig = {
  repo: 'your-username/your-repo',
  repoId: 'your-repo-id',
  category: 'Announcements',
  categoryId: 'your-category-id',
  mapping: 'pathname',
  reactionsEnabled: '1',
  emitMetadata: '0',
  inputPosition: 'top',
  lang: 'zh-CN'
}

const loadGiscus = (theme: string) => {
  if (!container.value) return
  
  container.value.innerHTML = ''
  
  const script = document.createElement('script')
  script.src = 'https://giscus.app/client.js'
  script.setAttribute('data-repo', giscusConfig.repo)
  script.setAttribute('data-repo-id', giscusConfig.repoId)
  script.setAttribute('data-category', giscusConfig.category)
  script.setAttribute('data-category-id', giscusConfig.categoryId)
  script.setAttribute('data-mapping', giscusConfig.mapping)
  script.setAttribute('data-reactions-enabled', giscusConfig.reactionsEnabled)
  script.setAttribute('data-emit-metadata', giscusConfig.emitMetadata)
  script.setAttribute('data-input-position', giscusConfig.inputPosition)
  script.setAttribute('data-theme', theme)
  script.setAttribute('data-lang', giscusConfig.lang)
  script.setAttribute('crossorigin', 'anonymous')
  script.async = true
  
  container.value.appendChild(script)
}

onMounted(() => {
  loadGiscus(isDark.value ? 'dark' : 'light')
})

watch(isDark, (dark) => {
  loadGiscus(dark ? 'dark' : 'light')
})
</script>

<template>
  <div class="giscus-container">
    <div ref="container" class="giscus" />
  </div>
</template>

<style scoped>
.giscus-container {
  margin-top: 3rem;
  padding-top: 2rem;
  border-top: 1px solid var(--vp-c-divider);
}

.giscus {
  min-height: 200px;
}

/* 隐藏 Giscus powered by */
:deep(.giscus) {
  max-width: 100%;
}
</style>

注册到布局

方式一:全局注册

ts
// .vitepress/theme/index.ts
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import GiscusComments from './components/GiscusComments.vue'

export default {
  extends: DefaultTheme,
  Layout: () => {
    return h(DefaultTheme.Layout, null, {
      'doc-after': () => h(GiscusComments)
    })
  }
}

方式二:条件渲染

vue
<script setup lang="ts">
import { useData } from 'vitepress'
import GiscusComments from './components/GiscusComments.vue'

const { frontmatter } = useData()
</script>

<template>
  <GiscusComments v-if="frontmatter.comments !== false" />
</template>

控制显示页面

禁用特定页面评论

yaml
---
title: 关于页面
comments: false
---

只在博客文章显示

vue
<script setup>
import { useData } from 'vitepress'
import GiscusComments from './components/GiscusComments.vue'

const { page } = useData()

const isBlogPost = page.value.relativePath.startsWith('blog/')
</script>

<template>
  <GiscusComments v-if="isBlogPost" />
</template>

Waline 集成(备选)

安装

bash
npm install @waline/client

组件

vue
<!-- .vitepress/theme/components/WalineComments.vue -->
<script setup lang="ts">
import { useData } from 'vitepress'
import { init, type WalineInstance } from '@waline/client'
import { onMounted, onUnmounted, watch, ref } from 'vue'

import '@waline/client/style'

const { page, isDark } = useData()
const container = ref<HTMLElement>()
let waline: WalineInstance | null = null

const initWaline = () => {
  if (!container.value) return
  
  waline?.destroy()
  
  waline = init({
    el: container.value,
    serverURL: 'https://your-waline-server.vercel.app',
    path: page.value.path,
    lang: 'zh-CN',
    dark: isDark.value ? 'dark' : '',
    login: 'enable',
    wordLimit: [0, 500],
    pageSize: 10
  })
}

onMounted(() => {
  initWaline()
})

watch(isDark, () => {
  initWaline()
})

onUnmounted(() => {
  waline?.destroy()
})
</script>

<template>
  <div class="waline-container">
    <div ref="container" />
  </div>
</template>

<style scoped>
.waline-container {
  margin-top: 3rem;
}
</style>

评论数统计

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useData } from 'vitepress'

const { page } = useData()
const commentCount = ref(0)

onMounted(async () => {
  const res = await fetch(
    `https://giscus.app/api/discussions?repo=your-repo&term=${page.value.path}`
  )
  const data = await res.json()
  commentCount.value = data.total_count || 0
})
</script>

<template>
  <span class="comment-count">
    💬 {{ commentCount }} 条评论
  </span>
</template>

学习检查清单

  • [ ] 配置了 GitHub Discussions
  • [ ] 安装了 Giscus App
  • [ ] 创建了评论组件
  • [ ] 注册到页面布局
  • [ ] 实现了深色模式适配

下一步

继续学习 实现多语言,为站点添加国际化支持。

贡献者

加载中...

想要成为贡献者?

在 CNB 上参与贡献