



























⚠️ 该文章和项目已过时了,请查看它的继任者 BlogiNote
本文是 two-dish-one-fish 的使用说明,它是一个 VuePress-next 模板,用于搭建静态网站,演示 DEMO 可以查看这里,可以在 Github 仓库的 wiki 或掘金查看开发日志。
「一鱼两吃」是一道很「聪明」的菜品,利用一条鱼的不同部位做出两味,甚至多味菜。我由此获得启发,基于相同的一堆 Markdown 文件作为数据源,搭建一个静态网站,既可以是部落格也可以作为学习笔记管理系统,达到类似「一鱼两吃」的效果。
由于文章主要是文本,而且网站的交互需求不高,所以选择使用静态网页。现在有很多基于 Markdown 文档生成静态网页的工具,基于我目前掌握的技能和需求选择以 VuePress 作为静态网站生成器。
VuePress 已经内置了默认主题只需要进行简单的参数配置就可以做到开箱即用。我的主要工作是基于默认模板进行拓展,优化在检索、浏览文章和笔记时有更好的体验:
⚠️ 这也是作为我学习 Vue3 的一个练手小项目,所以使用的 VuePress 是 V2 版本(也称作 VuePress-next),该版本仍处于不稳定的 Beta 状态(后期可能会出现不兼容的版本更新),因此如果你需要将它应用到日常正式的开发中需要谨慎考量。根据 VuePress-next 的开发计划 Stable 版本可能会在 2021 年 Q2 末推出。由于是练手项目,项目可能有不少 Bugs 导致编译不成功,但静态网站是基于 Markdown 文件构建的,所以只要保存好 .md 后缀的文档和相关图片等静态资源,数据是不会丢失的,也可以将文档迁移到别处,使用其他支持 Markdown 文件的笔记软件管理知识体系或静态网站生成器构建博客。

首页的卡片按主题进行区分,作为部落格文章导航页和笔记导航页的统一入口:
/postslist/相应主题Shift+Ctrl 再点击相应的卡片,可以进入笔记导航页,其中 URL 路径格式是 /folderslist/相应主题💡 如果触动设备无法配合键盘点击卡片进入笔记导航页,可以先进入相应的部落格文章导航页,再手动更改浏览器地址栏的 URL,将路径中的 postslist 更改为 folderslist

部落格文章导航页提供 3 种布局,便于浏览和快速检索文章:
文章可以按照创建日期(默认)或更新日期 Updated ,进行降序 Descend(默认)或升序 Ascend 方式排序。
在顶部的导航栏可以点击主题浏览相关的文章,此外还支持基于标签 #tag 对文章进行筛选。
💡 点击顶部的导航栏按钮可以切换到相应的主题,如果按住 Shift+Ctrl 再点击按钮,则可以直接进入相应主题的笔记导航页。

笔记导航页提供 2 种布局,基于熟悉的文件夹嵌套的层级关系来检索大量琐碎笔记:
在网格布局下支持基于标签 #tag 对「当前文件夹」中的文章进行高亮,还提供面板屑 breadcrumb 导航栏,便于从深层嵌套的文件夹快速返回到其上游的文件夹。
在树状布局下支持对树图进行缩放平移操作,提供 Resize 按钮对树图一键复位。

除了通过以上展示的各种布局浏览检索文章,还可以基于当前文章查看其所属系列的相关文章。如果一篇文章属于一个系列,则会出现一个集合 Collection 按钮,点击后会弹出一个 modal 展示同属一个系列的所有文章。

在文章内部基于标题生成目录,方便了解文章的纲要和快速定位到所需段落。

安装写作和开发软件:
下载 VuePress 模板 two-dishes-one-fish,有两种方法:
Enter 下载git clone https://github.com/Benbinbin/two-dishes-one-fish.git
安装 VuePress 依赖:
two-dishes-one-fish查看(V) -> 终端 打开终端(或使用快捷键 Ctrl+` 打开终端)npm install💡 如果安装较慢或失败,可以用这条命令替代(使用淘宝提供的资源加速安装) npm install --registry https://registry.npm.taobao.org
完成以上步骤后,就获得了一个完整的项目结构:
📁 two-dishes-one-fish:项目的根文件夹(可进行重命名,不影响后续操作),其中包含项目的相关配置文件,还有一个文件夹 📁 docs
📁 docs:该文件夹中的 Markdown 文件(文件后缀为 .md)会自动编译为网页文件,如图中标注文件夹 📁 folder1 和 📁 folder2 里就存放了文章示例
📁 .vuepress:该文件夹在 📁 docs 中,但是它不是存放写作文章,而是存放 VuePress 相关的配置文件
📁 public:该文件夹在 📁 .vuepress 中,它是用于存放一些静态资源
avatar.pngfavicon.icoimages/home 中images/covers 中images/icons 中
一般只需要对文档 📄 config.js 进行一次修改,然后日常就可以在文件夹 📁 docs 中通过创建 Markdown 文件进行写作:
config.js:在文件夹 📁 .vuepress 下的文档,它是用于配置网站大部分参数folder1 和 📁 folder2:在文件夹 📁 docs 下用于存放示例文章的,可以重命名或创建更多的文件夹,也允许创建嵌套文件夹结构,在它们中创建的每一个 Markdown 文件会相应生成一篇文章💡 如果要添加首页的主题卡片的背景图,以及部落格文章导航页中一些文章的配图,还有页尾社交媒体图标,记得将这些图片/图标资源放置在文件夹 📁 public 的相应子文件夹中。
使用 VS Code 代码编辑器打开文件夹 📁 .vuepress 下的文档 📄 config.js 以对网页参数进行配置(根据 // 中文注释 进行修改)
const { path } = require('@vuepress/utils')
module.exports = {
open: true,
lang: 'zh-CN',
// 属性 base 的值 "repo" 是指需要部署到你的 Github 上的仓库名称
// 例如我将网站部署到路径为 https://github.com/Benbinbin/Collection 这个仓库中,那么我的 Github 用户名是 Benbinbin,部署仓库名是 Collection,则属性 base 的值就应该是 "/Collection/"
// 如果部署到 https://github.com/username/username.github.io 这个特殊的 Github 仓库(其中 username 是指你的 Github 用户名),则属性 base 的值应该是 "/"(也可以不设置,因为该属性的默认值为 "/")
base: "/repo/",
// 属性 title 是站点的标题,会作为所有页面的标题后缀,即显示在浏览器标签页上
title: "Blog",
// 属性 description 是站点的描述,便于搜索引擎进行识别
description: 'A blog shows some of the notes I took while learning skills.',
head: [
// 以下用于设置网站的图标,即显示在浏览器标签栏左侧的图标
// 下面的 repo 要设置为仓库名,例如我将网站部署到路径为 https://github.com/Benbinbin/Collection 这个仓库中,则属性 href 的值为 '/Collection/images/favicon.ico'
// 如果要将网站部署在 https://github.com/username/username.github.io 这个特殊的 Github 仓库,则属性 href 的值为 '/images/favicon.ico'
// 模板已内置了图标文件,如果需要修改请见新的图标文件 favicon.ico 放在静态资源文件夹 public 中,覆盖原来的图标文件
['link', { rel: 'icon', href: '/repo/images/favicon.ico' }],
['link', { rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/katex@0.13.5/dist/katex.min.css' }],
],
bundler: '@vuepress/vite',
bundlerConfig: {
viteOptions: {
css: {
postcss: {
plugins: [
require('tailwindcss'),
require('autoprefixer')
]
}
},
}
},
plugins: [
require('./plugins/addTime.js'),
[require('./plugins/createHomePage.js'),
{
// 属性 cards 是用于设置首页主题卡片
// 它的值是一个数组,其中每一个元素对应的是一个主题,也就文件夹 docs 中包含的下一级子文件夹。
// 每一个元素都有两个属性 name 和 image,分别表示卡片主题名称(和文件夹名称相同),和卡片背景图片文件
// 例如模板中就有文件夹 folder1 和 folder2,卡片背景图片分别是 folder1-cover.svg 和 folder2-cover.svg
// 记得将卡片背景图文件放在静态资源文件夹 public 的子文件夹 images/home 中
cards: [
{
name: "Folder1",
image: "folder1-cover.svg"
},
{
name: "Folder2",
image: "folder2-cover.svg"
},
]
}],
[require('./plugins/generateListPages.js'),
{
// 属性 postClassification 是指部落格文章的主题,用于部落格文章导航页
// 它的值是一个数组,其中每一个元素对应的是一个主题,也就是文件夹 docs 中所包含的下一级子文件夹
// 例如模板中就有文件夹 folder1 和 folder2,这些文件夹中就是用户写作生成的许多 markdown 文件
postClassifications: ['folder1', 'folder2']
}],
[require('./plugins/generateFolderPages.js'),
{
// 属性 postFolders 是指笔记的主题,它类似于属性 postClassification,一般两个属性的值是一样的,因为共用一个文件系统(一鱼两吃)
// 它的值是一个数组,其中每一个元素对应的是一个主题,也就是文件夹 docs 中所包含的子文件夹
// 例如刚下载的模板中就就有文件夹 folder1 和 folder2,这些文件夹中就是包含了许多 markdown 文件
postFolders: ['folder1', 'folder2']
}],
],
theme: path.resolve(__dirname, './theme/index.js'),
themeConfig: {
navbar: false,
sidebar: false,
contributors: false,
lastUpdatedText: '更新时间',
themePlugins: {
backToTop: false,
nprogress: false,
}
},
markdown: {
links: {
externalIcon: false
}
},
extendsMarkdown: (md) => {
md.use(require('@neilsustc/markdown-it-katex'), {output: 'html'})
},
// 属性 define 中的有不少属性和前面设置相关,这是为了开发方便设置的全局变量,请对照着进行修改
define: {
// 属性 __BASE__ 和前面的属性 base 一致,如果是部署到 https://github.com/username/username.github.io 这个特殊的 Github 仓库,该属性值请设置为 "/"(不可省略)
__BASE__: "/repo/",
// 属性 __HOME_PAGE_TITLE__ 的值是指首页的标题
__HOME_PAGE_TITLE__: "Blog",
// 属性 __HOME_PAGE_ICON__ 的值是指首页的标题和下方分割线中的图标文件,记得将图标文件放置在静态资源文件 public 的子文件夹 images/home 中
__HOME_PAGE_ICON__: "home_icon.svg",
// 属性 __HOME_DESCRIPTION__ 的值是指首页的标题下方的描述,支持纯文本和 HTML
__HOME_DESCRIPTION__: "我是 Ben,这是我的部落格和知识管理系统。",
// 属性 __HOME_PAGE_COLOR__ 的值是指首页的颜色
__HOME_PAGE_COLOR__: '#9CA3AF',
// 属性 __AVATAR__ 的值是指展示在导航栏和页尾的头像的图像文件名称,请记得将这个图像文件放在静态资源文件夹 public 中
__AVATAR__: 'avatar.png',
// 属性 __CLASSIFICATIONS__ 用于生成部落格文章导航页的顶部导航栏
// 它的值是一个数组,和前面的属性 postClassifications 类似,但是在添加一个元素 'All',这样可以添加一个导航页面显示所有部落格文章
__CLASSIFICATIONS__: ['All', 'Folder1', 'Folder2'],
// 属性 __FOLDERS__ 用于生成笔记导航页的顶部导航栏
// 它的值是一个数组,和前面的属性 postFolders 类似
__FOLDERS__: ['Folder1', 'Folder2'],
// 属性 __FOOTER_AVATAR_LINK__ 的值是指页尾的头像点击后跳转的链接,可以设置为自己 portfolio 页面
__FOOTER_AVATAR_LINK__: 'https://www.google.com/',
// 属性 __AUTHOR__ 的值是指页尾标注的该网站文章作品的许可权利所属者
__AUTHOR__: 'Benbinbin',
// 属性 __FOOTER_LICENSE__ 的值是指页尾标注的该网站文章遵循的许可协议,默认遵循 CC-BY-SA-4.0 署名-相同方式共享 4.0 国际 方式进行共享
__FOOTER_LICENSE__: 'CC-BY-SA-4.0',
// 属性 __FOOTER_LICENSE_LINK__ 的值是指页尾标注的该网站文章准许的许可协议具体内容的链接
__FOOTER_LICENSE_LINK__: 'https://creativecommons.org/licenses/by-sa/4.0/deed.en',
// 属性 __SOCIAL_MEDIA__ 用于生成页尾社交媒体图标
// 它的值是一个数组,每一个元素都是一个社交媒体账号相关信息,你可以根据进行需要增删
// 每一个元素都包含 3 个属性:
// 属性 name 表示社交媒体的名称,它值应该是唯一且不重复的
// 属性 logo 是指显示在页尾的图标的文件,请记得将这个图标文件放在静态资源文件夹 public 的子文件夹 images/icons 中,当前已预设了 6 种社交媒体图标
// 属性 url 是指社交媒体的链接,以用点击图标进行访问
// 如果社交媒体是邮件账号,则属性 url 格式是 mailto: 作为前缀,再接着写邮箱地址
__SOCIAL_MEDIA__: [
{
name: 'email',
logo: 'email.svg',
url: 'mailto:[example]@gmail.com'
},
{
name: 'github',
logo: 'github.svg',
url: 'https://github.com/[username]'
},
{
name: 'juejin',
logo: 'juejin.svg',
url: 'https://juejin.cn/user/[userid]/posts'
},
{
name: 'dribbble',
logo: 'dribbble.svg',
url: 'https://dribbble.com/[username]'
},
{
name: 'twitter',
logo: 'twitter.svg',
url: 'https://twitter.com/[username]'
},
{
name: 'weibo',
logo: 'weibo.svg',
url: 'https://weibo.com/[username]'
},
],
},
}💡 除了上述中文注释标注的修改以外,由于该模板基于 VuePress-next 的默认主题进行修改,因此默认主题的其他配置也同样适用。
💡 如果需要进行深度定制,对其他文档进行修改,可以参考 VuePress-next 的官方文档和我的开发日志。
在文件夹 📁 docs 下依照主题创建文件夹(允许创建嵌套文件夹结构,方便进行文章的分类管理),在这些文件夹内创建 Markdown 文件,然后使用 Typora 进行写作创作了。
每一个后缀为 .md 文档都是一篇文章,通过 VuePress 引擎编译会生成一个网页。
以下是示例文章,左边是一个 Markdown 文档,列出了常见的语法,右边是编译出来的相应文章。更多关于 markdown 语法的介绍可以查看这一篇文章 Mastering Markdown。
💡 该模板还支持插入 katex 数学公式

💡 如果插入本地图片,推荐在与当前 Markdown 文档同级的目录下创建一个文件夹 📁 images 存放图片文件,然后就可以在该 Markdown 文件中使用相对路径插入相应的图片。如在 Markdown 文件中插入图片 img1.png
 💡 可以通过 <iframe> 的形式插入视频,为了解决视频的高宽比例和网页响应式适配问题,可以调整元素 style 属性的 width 和 aspect-ratio 选项值
通过以下代码嵌入一个 Youtube 视频
<iframe
style="width: 100%; aspect-ratio: 16/9;"
src="https://www.youtube.com/embed/Y50_RSWpWkA?start=3403&end=3441&modestbranding=1&rel=0"
allowfullscreen
loading="lazy">
</iframe>更多关于 YouTube 嵌入式播放器及播放器参数可以参考官方文档
start 参数设置播放的开始时间end 参数设置播放的结束时间modestbranding 参数设置为 1 可以阻止 YouTube 徽标显示在控制栏中rel 参数设置为 0 在播放结束显示的相关视频来自于相同的频道allowfullscreen 属性允许播放器全屏显示通过以下代码嵌入一个 Bilibili 视频
<iframe
style="width: 100%;aspect-ratio: 16/9;"
src="//player.bilibili.com/player.html?aid=759111032&bvid=BV1W64y1X7ok&cid=366423332&page=1&high_quality=1&t=60"
allowfullscreen="true"
loading="lazy">
</iframe>由于 Bilibili 官方未给出关于嵌入式播放器的参数,参考相关文章尝试进行以下设置
high_quality 参数设置为 1 是为了将视频设置为片源可用的最高清晰度src 属性里面有很多参数,可以只保留 bvid 这个参数page 参数指定选集里第几个视频t 参数设置视频开始播放的时间,单位为秒loading 属性设置为 lazy 可以实现懒加载更多可能的参数设置可以参考 Bilibili 视频适应页面宽度 这一篇文章。
在 Markdown 文件中除了正文内容,还可以在文章的顶部添加一些额外的信息,称为 YAML Frontmatter,作为参数传递给 VuePress 控制引擎如何编译生成相应网页。
在 Frontmatter 中可以设置的字段(注意大小写,采用驼峰式命名法):
show:设置当前 Markdown 文档是否作为部落格文章。如果其值为 true 就显示在部落格文章导航页和笔记导航页中;如果其值为 false 文章就不会显示在部落格文章导航页中。cover:设置文章的封面图,该图片是显示在部落导航页的相应的文章卡片上(因此文章的 Frontmatter 字段 show 需要设置为 true),记得将图片文件放置在静态资源文件夹 📁 public 的子文件夹 📁 images/covers 中collection:设置文章所属系列collectionOrder:设置当前文章在所属系列中的排序(因此需要先设置文章的 Frontmatter 字段 collection)tags:设置文章的标签,其值是一个数组,每个元素以 - 开始summary: 设置当文章的概述,它会显示在部落格导航页的相应文章卡片上(因此文章的 Frontmatter 字段 show 需要设置为 true)Frontmatter 写在文章的顶部,被包裹在一对三短划线中,以下是一个示例:
img1.png(文件放置在 📁 public/images/covers 中)linear algebra 这个系列,在系列中排序为 5math 和 linear algebra---
show: true
cover: img1.png
collection: linear algebra
collection: 5
summary: A summary of this article will show on navigator page
tags:
- math
- linear algebra
---
# 正文标题一
以下是正文内容💡 除了以上提到的字段,VuePress 默认主题还支持设置其他 Frontmatter 字段,具体参考官方文档。
⚠️ 由于 Frontmatter 设置的参数优先级更高,当 Frontmatter 中的字段(针对当前文章) 与 VuePress 配置文件 📄 config.js 中的字段(针对整个网页)一致时,Frontmatter 的字段值会覆盖 config.js 相应的字段值,对当前文章起作用。
设置好参数后,或每次写作完成后,推荐通过启动本地服务器,预览生成的网站,在本地编译成功后再将代码推送到 Github 远程仓库可以提高部署的成功率。
在终端输入以下命令启动本地服务器,编译完成后,会自动打开浏览器加载生成的网页,可以进行实时的开发调试
npm run docs:dev💡 如果未自动打开网页,可以按住 Ctrl 键点击终端输出的 URL 中任意一条链接,即可使用系统默认的浏览器预览网页;也可以复制其中一条链接,输入到浏览器地址栏中进行访问

如果需要停止本地服务器,点击终端面板以便激活响应输入,然后按下快捷键 Ctrl+C,终端会弹出「终止批处理操作」提示,按下 y 后再按回车键 Enter 确定即可

💡 如果编译没有错误提示,但是无法正常预览,可以尝试停止本地服务器后再重启服务器。
当写作完成保存了 Markdown 文件后(推荐进一步通过启动本地服务器,预览生成的网页查看效果),还需要通过 Git 对项目进行版本控管。保存 Markdown 文件是对当前文章的保存,而版本控管就可以理解为对当前整个项目的状态的保存,只有保存了这个状态才可以将本地项目的增删同步到远程仓库。
使用 VS Code 代码编辑器打开本地项目文件夹 📁 two-dishes-one-fish,点击编辑器左侧的 源代码管理 按钮,然后按照下图提示操作,保存项目当前的状态。

💡 如果项目模板是通过在 Github 仓库下载代码压缩包得到的,那么项目可能并未进行版本控管的初始化,可以按照图中提示,点击 初始化存储库 按钮实现初始化,然后就可以对项目的状态变动进行追踪。

为了使用 Github 托管项目,并利用 Github Page 展示生成的网页,需要将本地项目与远程的 Github 仓库进行同步:
根据下图操作提示,在 Github 上创建一个仓库

为本地项目添加远程仓库。在 Github 上新建一个仓库后,页面会跳转到仓库的主页,其中 …or push an existing repository from the command line 下的代码,就是将本地仓库与该 Github 仓库相连接的命令。

two-dishes-one-fish查看(V) -> 终端 打开终端(或使用快捷键 Ctrl+`)git remote add origin https://github.com/[username]/[repo].git⚠️ 由于本地的项目是基于从 Github 下载的模板 two-dishes-on-fish 而创建的,因此本地项目原本就已经与这个模板的 Github 远程仓库现连。如果直接输入以上命令是无法成功执行的,会输出错误提示 fatal: remote origin already exists. 表示名为 origin 的远程仓库已存在,因此需要先在终端输入以下命令移除这个已存在的远程仓库 git remote remove origin,然后再输入以上的命令才能够执行成功。
💡 可以在终端输入命令 git remote -v 来查看本地项目已连接的远程仓库,更多的 Git 命令可以参考官方指南。
main 的分支与远程仓库同步。在终端输入以下命令,将本地项目代码同步到远端仓库
git push -u origin main💡 前面的两个步骤只需要配置一次即可,而这一步是本地项目与远程仓库同步,应该在每次版本控管,即每次保存完项目的状态后都执行一次。而且这一次已经设置了同步的分支,下一次同步就可以使用更简洁的命令 git push 即可。
项目模板已经内置了部署脚本 .github/workflows/docs.yml,每当将本地项目代码同步到远程仓库时,Github 监听到仓库的变化就会自动执行脚本,将 Markdown 文件编译为的静态网页,并将网页部署到仓库的分支 gh-pages。
只需要进入仓库的 Github Pages 设置页,指定一次网页的数据源为分支 gh-pages 即可。

💡 首次指定网页数据源后,可能要等待一段短时间,待上图的 这个就是该仓库的 Github Page 方框变绿,才能成功浏览到网页。
💡 每次将本地项目代码同步到 Github 远程仓库后,都要等待 Github Action 执行部署脚本,成功后才能够浏览到新页面,可以到仓库主页的 Actions 标签栏内查看执行情况。如果部署成功但网页并不更改,可以按快捷键 Shift + Ctrl + R 或 Shift + F5 清除网页缓存强制刷新。

💡 如果希望优化网页的访问速度,可以注册一个 Gitee 账号,并创建一个 Github 远程仓库的仓库镜像,然后开启 Gitee Page。
💡 VuePress 提供了更多部署选项,请参考官方文档。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。