Skip to content

基于vitepress的博客网站通过valine实现评论功能

博客网站经历了 wordpress、hexo、vuepress的时代。

而现在最流行的博客网站是 vitepress,当前vitepress当前已经更新到了1.0.0-alpha.4,然而还没有评论功能

但是作为一个擅长学(作)习(死)的前端开发者,怎么可以忍受?当然要自己搞一个。

话不多说,这就开始搞。

1、通过vitepress创建博客

mkdir vitepress-starter && cd vitepress-starter
yarn init
yarn add --dev vitepress vue
mkdir docs && echo '# Hello VitePress' > docs/index.md

添加命令

{
  ...
  "scripts": {
    "docs:dev": "vitepress dev docs",
    "docs:build": "vitepress build docs",
    "docs:serve": "vitepress serve docs"
  },
  ...
}
yarn docs:dev

这样一个简单的vitepress博客就创建好了

2、注册Valine

打开 valine

让我们先去注册一个 LeanCloud

按照 说明文档来,一步步操作,创建一个应用,然后获取到 APP ID和APP Key

3、添加valine

# 安装 valine
npm install valine --save

再创建一个评论组件

<template>
    <div id="vcomments"></div>
</template>

<script setup>
// 因为vite不支持require
// const Valine = require('valine');
// 改用 import
import Valine from 'valine';

new Valine({
    el:'#vcomments',
    appId: 'xxxxxxxx-your appId',// your appId
    appKey: 'xxxxxxxxxxxx-your appKey', // your appKey
    // other config
})
</script>

然后在我们想要的页面引入这个自定义组件

在md中 引入组件

<script setup>
import ValineComment from './.vitepress/theme/ValineComment.vue'
</script>

# Docs

This is a .md using a custom component

<ValineComment />

## More docs

这样就可以再这个页面 使用评论了

4、发布

执行 yarn docs:build 构建,发现报错了,原来valine中的代码 引用了window,在vite构建的时候 因为找不到 window报错了

这里我们改成 html引入 Valine

// docs/.vitepress/config.js
module.exports = {
  // 其他配置省略
  head: [
    [
      "script",
      { src: '/Valine.min.js'},
    ],
  ],
}

通过这个配置我们在header中增加了 js文件的引用,当然我们要在 public 下 增加 Valine.min.js 这个文件

5、找不到 Valine

改为 html 引入 Valine,再刷新下,发现 报错 找不到 Valine,发现 原来是 还没等 js文件下载 执行完,就走到了初始化阶段

这里我们把 初始化 增加 一个定时器

setTimeout(() => {
  new Valine()
}, 1000);

算是解决了问题

6、每篇文档下都有评论

上面做的只能在一个页面使用评论,我们肯定是想要在所有页面都使用评论

怎么搞呢,我们可以通过自定义主题的方式

创建一个自定义的布局组件

// docs/.vitepress/theme/MyLayout.vue
<script setup>
import DefaultTheme from 'vitepress/theme'
import ValineComment from './ValineComment.vue';
const { Layout } = DefaultTheme
</script>

<template>
  <Layout>
    <template #doc-after>
      <ValineComment />
    </template>
  </Layout>
</template>

将我们的评论组件塞入其中,并且注明是在文档的最后面插入

然后 将我们的这个自定义主题组件,覆盖默认主题

import DefaultTheme from 'vitepress/theme'
import MyLayout from './MyLayout.vue'

export default {
    ...DefaultTheme,
    // override the Layout with a wrapper component that injects the slots
    Layout: MyLayout,
}

这样我们在每个页面都有了 评论组件

7、切换页面没有重新初始化

我们在每个页面都有了评论,当我们做路由切换的时候,发现页面的评论没有重新刷新

这里我们需要监听路由,重新初始化数据

import { watch } from 'vue'
import { useRoute } from 'vitepress'

const route = useRoute()

let valine;

const initValine = () => {
  valine.init({
    el: '#vcomments',
    appId: '2GzP9zCeJjS8HzykzDRyptJf-gzGzoHsz',// your appId
    appKey: 'lduSByMe7OPglzonWDMAJrzX', // your appKey
    notify: false,
    verify: false,
    path: path,
    visitor: true,
    avatar: 'mm',
    placeholder: '来都来了,不留点啥吗'
  });
}

watch(() => route.path, () => {
  console.log('监听路由变化')
  initValine();
})

setTimeout(() => {
  valine = new Valine()
  initValine()
}, 1000);

这样我们每次在切换路由的时候,就可以重新初始化评论的数据了

8、访问数量

我们通过 visitor: true 开启了访问量的统计,为了自定义样式,我们需要 增加 leancloud-visitors 这个类

并且把 这个dom的id 设置为 当前路径

完整的代码

<template>
  <div class="page">
    <section class="page-edit">
      <div class="page-edit-read">
        <!-- id 将作为查询条件 -->
        <span class="leancloud-visitors" data-flag-title="Your Article Title">
          <em class="post-meta-item-text">阅读量: </em>
          <i class="leancloud-visitors-count"></i>
        </span>
      </div>
      <div id="vcomments"></div>
    </section>
  </div>
</template>

<script setup>
import { watch } from 'vue'
import { useRoute } from 'vitepress'

const route = useRoute()

let valine;

const initValine = () => {
  let path = location.origin + location.pathname
  document.getElementsByClassName('leancloud-visitors')[0].id = path
  valine.init({
    el: '#vcomments',
    appId: '2GzP9zCeJjS8HzykzDRyptJf-gzGzoHsz',// your appId
    appKey: 'lduSByMe7OPglzonWDMAJrzX', // your appKey
    notify: false,
    verify: false,
    path: path,
    visitor: true,
    avatar: 'mm',
    placeholder: '来都来了,不留点啥吗'
  });
}

watch(() => route.path, () => {
  console.log('监听路由变化')
  initValine();
})

setTimeout(() => {
  valine = new Valine()
  initValine()
}, 1000);

</script>

<style scoped>
.page-edit-read {
  text-align: right;
  margin: 14px 0;
}
</style>

9、展示头像

评论默认没有头像,想要头像需要到 Gravatar,注册登录,然后替换成自己想要的头像

在评论时,使用注册登录的邮箱即可

如果你修改了头像后发现没有更新,请不要慌张,因为gravatar.cat.net 有七天的缓存期,安静的等待吧~

10、邮箱、网址过长展示不全

经过实测,邮箱和网址,如果过长,就会被截断,所以大家最好换个短点的邮箱和网址

网址 是用来点击头像时,跳转用的,目前来看,连github的个人页 长度都不够。。。

11、邮件通知

有人给我们留下了评论,如何第一时间获取通知呢?这里有个扩展的能力,点击进入 Valine-Admin

然后根据 教程一步步来

先到 云引擎 下的 WEB部署 页面,然后 选择 源码部署,将 git仓库地址 填上去 https://github.com/BillChen2k/Valine-Admin

然后 点击 部署

一会看到 部署成功

然后到 WEB 下的 设置这里,填写 必要的 环境变量

SITE_NAME : 网站名称。
SITE_URL : 网站地址, 最后不要加 / 。
SMTP_USER : SMTP 服务用户名,一般为邮箱地址。
SMTP_PASS : SMTP 密码,一般为授权码,而不是邮箱的登陆密码,请自行查询对应邮件服务商的获取方式
SMTP_SERVICE : 邮件服务提供商,支持 QQ、163、126、Gmail、"Yahoo"、...... ,全部支持请参考 : Nodemailer Supported services。 --- 如这里没有你使用的邮件提供商,请查看自定义邮件服务器
SENDER_NAME : 寄件人名称。

注意这里 126邮箱不行,我试过了,需要换成 163的才可以

保存后,点击部署,重启容器 才能生效

这样如果有人评论 就可以通过 上面的邮箱愉快的 接受邮件了 (^▽^)

至此 vitepress 的 评论系统的基本功能 就完成了

12、优化

因为我们初始化Valine的还是用的定时器,既不雅观也不可靠,因为可能定时器执行时,网络不好的情况下,js还没有加载完

这里我们封装一个异步加载js的函数

function remoteImport(url) {
  return new Promise((resolve) => {
    var head = document.getElementsByTagName("head")[0];
    var script = document.createElement("script");
    script.setAttribute("type", "text/javascript");
    script.setAttribute("src", url);
    head.appendChild(script);

    script.onload = function () {
      resolve();
    };
  });
}

然后在 vue的生命周期 onMounted 中,在异步加载js完成后,再去执行 Valine 的初始化函数

完整代码如下

import { watch, onMounted } from "vue";
import { useRoute } from "vitepress";

const route = useRoute();

const initValine = () => {
  let path = location.origin + location.pathname;
  document.getElementsByClassName("leancloud-visitors")[0].id = path;
  new Valine({
    el: "#vcomments",
    appId: "2GzP9zCeJjS8HzykzDRyptJf-gzGzoHsz", // your appId
    appKey: "lduSByMe7OPglzonWDMAJrzX", // your appKey
    notify: false,
    verify: false,
    path: path,
    visitor: true,
    avatar: "mm",
    placeholder:
      "请在这里留下你的留言,如果上面填写了邮箱还能收到邮件哟,地址是点击头像跳转的地址",
  });
};

watch(
  () => route.path,
  () => {
    console.log("监听路由变化");
    initValine();
  }
);

onMounted(() => {
  remoteImport('//unpkg.com/valine/dist/Valine.min.js').then(() => initValine());
});

function remoteImport(url) {
  return new Promise((resolve) => {
    var head = document.getElementsByTagName("head")[0];
    var script = document.createElement("script");
    script.setAttribute("type", "text/javascript");
    script.setAttribute("src", url);
    head.appendChild(script);

    script.onload = function () {
      resolve();
    };
  });
}