blateyang / articles

personal blogs using github issue

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

使用Vue和github的issue快速搭建个人博客网站

blateyang opened this issue · comments

1 前言

自从2020年开始学习web开发后,一直有建立个人博客网站的想法,但迟迟没有时间(其实是自己决心不够强),直到今年12月,因个人需要,终于下决心开始行动。在个人博客网站方面的标杆是阮一峰的网络日志,阮大的博客内容丰富、涉猎广泛、通俗易懂。希望自己也能像阮大一样,用个人博客记录自己的学习、思考和感悟,如果能对他人有所帮助更好。在这个项目repo里的第一篇博客就准备写如何低成本的搭建个人博客网站。

2 技术选型

技术选型取决于个人目的,我的目的是希望低成本地搭建个人博客网站且能够自定义前端界面。通过网上搜索,在从零开始搭建个人博客 | 实现 React + Github Pages + Github Issue 极简个人博客这篇博客中,找到了我想要的技术方案,文中作者列举了几种常见方案:

  1. 成本最低的第三方博客平台
  2. 静态网站生成或手写前端+Git Pages/Coding展示
  3. 内容管理系统CMS或手写前后端并配置服务器和域名

我选择的就是第2种通过Vue+Github Pages+ Github Issue搭建个人博客,Vue实现前端页面,Github Pages实现静态网站托管, Github Issue实现后台的博客和评论管理

3 Vue项目介绍

3.1 项目需求

  • 代码高亮
  • 响应式
  • 文章锚点导航
  • 回到顶部
  • 评论跳转
  • 文章目录左侧悬浮

3.2 项目结构

vue-issue-blog
总共有三个页面,博客列表页Posts.vue、博客详情页Post.vue以及作者信息页About.vue

3.3 相关技术点

3.3.1 FetchApi和githubApi

下面代码是utils/githubApi.js的内容,使用到了FetchApi和githubApi

import { githubRepo } from "../config";

async function getIssues(query) {
  let label = "";
  if (query && query.label && query.label.trim().length > 0) {
    label = `+label:"${query.label}"`;
  }
  try {
    const data = await fetch(`https://api.github.com/search/issues?q=state:open+repo:${githubRepo}${label}&sort=created&order=desc`).then((res) => res.json());
    return await data.items;
  } catch (error) {
    console.log("GetIssues Failed", error);
  }
}
async function getIssue(id) {
  try {
    return await fetch(`https://api.github.com/repos/${githubRepo}/issues/${id}`).then((res) => res.json());
  } catch (error) {
    console.log("GetIssue Failed", error);
  }
}
async function getLabels() {
  try {
    const data = await fetch(`https://api.github.com/repos/${githubRepo}/labels`).then((res) => res.json());
    return await data.map((label) => label.name);
  } catch (error) {
    console.log("GetLabels Failed", error);
  }
}

export { getIssues, getIssue, getLabels };
  1. FetchApi

类似XMLHttpRequestApi,用于实现网络请求,但比后者用起来更方便,它的调用形式通常是fetch(url, params),返回的是Promise对象。基本用法如下:

// 发送get请求
fetch('http://example.com/movies.json')
.then(response => response.json()) // 注意fetch仅在网络故障或请求被阻止时才返回reject的promise
.then(data => console.log(data));
// 发送post请求
async function postData(url = '', data = {}) {
  // Default options are marked with *
  const response = await fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json'
      // 'Content-Type': 'application/x-www-form-urlencoded',
    },
    redirect: 'follow', // manual, *follow, error
    referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  });
  return response.json(); // parses JSON response into native JavaScript objects
}

postData('https://example.com/answer', { answer: 42 })
  .then(data => {
    console.log(data); // JSON data parsed by `data.json()` call
  });

详细用法参考MDN

  1. githubApi

项目中主要用到了三个githubApi

3.3.2 Vuex的使用

项目使用Vuex对activeLabel进行管理

computed: {
    ...mapGetters(["activeLabel"]), // 使用对象展开运算符将Vuex的getters中的activeLabel 混入 computed 对象中
}

3.3.3 VueRouter和NProgress的使用

  • 对于VueRouter,在router.js中定义路由规则,通过Posts.vue等vue文件中的router-link实现路由链接和跳转
<router-link :to="{ name: 'Post', params: { number: issue.number, issue: issue } }">
          {{ issue.title }}
</router-link>
  • 在router.js中,还使用了NProgress实现路由跳转时的进度条加载效果
router.beforeEach((to, from, next) => {
NProgress.start();
next();
});
router.afterEach(() => {
  NProgress.done();
});

3.3.4 markdown解析库marked和语法高亮库highlight.js的使用

  • marked库主要用于将markdown格式的文档解析为html文档
  • highlight.js则用来实现语法高亮
marked.setOptions({
      highlight: (code) => hljs.highlightAuto(code).value,
    });
let htmlContent = marked(issue.body);

3.3.5 CSS positoin的fixed和sticky布局

  • 使用position:fixed实现回到顶部功能
  #gotop-btn {
  position: fixed;
  bottom: 50px;
  left: 90vw;
  padding: 0 5px;
  border-radius: 4px;
  border: 1px solid #b7eb8f;
}
  • 使用position:sticky实现目录左侧悬浮功能
    .catalog {
    position: sticky;
    left: 10px;
    top: 10px;
    width: 300px;
    height: 100vh;
    overflow: auto;

}

#### 3.3.6 锚点导航

通过设置文章目录链接的href属性为对应正文章节标题的id值实现锚点导航
```html
<div v-for="catalog in catalogs" :key="catalog.title">
  <a :class="`article-catalog-h${catalog.level}`" :href="`#${catalog.title}`">{{ catalog.title }}</a>
</div>

相应的js处理

  //给html中的h加id
let tocHtml = htmlContent.match(/<(h\d).*?>.*?<\/h\d>/g);
tocHtml.forEach((item, index) => {
  // let _toc = `<div id='${catalogs[index].title}'>${item} </div>`;
  // 将原tocHtml中h标签的"0-引言"id属性换成"0 引言"id属性,以便能和目录a标签的href属性值对得上,实现锚点导航
  let _toc = item.replace(/".*"/g, `"${catalogs[index].title}"`);

  htmlContent = htmlContent.replace(item, _toc);
});

4 项目部署

  1. 将package.json中build命令值改为cross-env NODE_ENV=production vue-cli-service build,否则process.env.NODE_ENV默认是development,打出来的包文件引用路径会有问题)
  2. 依次执行deploy.sh中的命令

最后一句git push -f git@github.com:blateyang/blogs.git main:gh-pages的含义是将本地main分支强制更新到远程仓库"git@github.com:blateyang/blogs.git"的gh-pages分支