Hexo博客搭建

一、安装Hexo


  1. 全局安装hexo-cli[1]
1
npm i -g hexo-cli
  1. 创建项目
1
2
3
hexo init sherwood-blog
cd sherwood-blog
npm i
  1. 本地调试项目
    执行:
    1
    hexo s
    即可在localhost:4000中访问网站

二、安装Fluid


  1. 安装Fluid主题[2]
    由于我需要修改Fluid的底层逻辑,所以不采用npm安装的方案。
    而是将整个库从Github[3]上面下载下来,放到theme文件夹下面,并命名为fluid

三、配置config.yml


  1. 配置全局config.yml
    以下是改动的项目:
1
2
3
title: Sherwood的博客
language: zh-CN
theme: fluid
  1. 配置Fluid的config.yml
    以下是改动的项目:
1
2
3
4
5
6
7
8
9
dark_mode:
enable: false # 关闭切换主题的按钮
default: dark # 默认设置为dark在打包后无效,所以后面直接修改默认主题的颜色
lazyload:
enable: true # 开启图片懒加载
onlypost: true # 只在文章中使用懒加载
navbar:
ground_glass:
enable: true # 导航栏半透明

四、修改默认主题


  1. 修改主题颜色
    主题颜色只修改白天主题,黑夜主题在打包时会失效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
color:
body_bg_color: "#181c27"
navbar_bg_color: "#181c27"
navbar_text_color: "#fff"
text_color: "#3c4858"
sec_text_color: "#718096"
post_text_color: "#2c3e50"
post_heading_color: "#1a202c"
post_link_color: "#0366d6"
link_hover_color: "#30a9de"
link_hover_bg_color: "#f8f9fa"
board_color: "#fff"
scrollbar_color: "#c4c6c9"
scrollbar_hover_color: "#a6a6a6"
  1. 修改styl中的相关颜色,完善显示效果

  2. 导航栏可以设置logo
    nav.ejs中的添加如下语句:

1
<img src="<%= theme.navbar.blog_img %>" />

之后即可在config.yml中配置logo


五、将Word文档转换成Markdown文档

[4]


  1. 安装pandoc
1
brew install pandoc
  1. 执行转换
1
pandoc -o output.md --extract-media=./ inputfile.docx
  1. 批量转换docx
    因为js脚本调用命令行不够方便,所以批量转换的代码我就直接用shell[16]来做了。
    这里面比较头疼的是Mac的sed命令用的BSD版本,跟传统Linux的用法还不太一样。
    而且正则也是只用了最早的规则,像括号之类的都还没有实现相关功能。
    最后查阅了很多资料,实现了以下脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
!/bin/bash

# 传入参数为空时中止
path=$1 # shell的变量赋值等号左右不能有空格!
if [ ! -n $path ]
then
echo "未传入参数"
exit 1
fi

curTime=$2
tags=$3

# 参数不是目录直接中止
if [ ! -d $path]
then
echo "不是目录"
exit 1
fi

cd $path
files=$(ls *.docx) # 获得目录中所有docx文件
outBasePath="/xxx/xxx/xxx" # 文档输出根路径
outResBasePath="/xxx/xxx/xxx" # 资源输出根路径

for file in ${files}: [17]
do
prefix = $(echo $file | sed 's/\.[^.]*$//') # [^.]按理来说是匹配\n\r的,但实际上作用似乎跟.一样?[18]
outMDPath="$outBasePath/$prefix.md"
outResPath="$outResBasePath/$prefix"
pandoc -o "$outMDPath" --extract-media="$outResPath" "$file"
# 处理格式问题
## 添加头部信息
sed -i '' "1i\\ # 与linux不同的,mac的-i后面多了个参数用来指定备份文件的后缀[19]
---\\
title: $prefix\\
date: $curTime\\
tags: [$tags]\\
---\\
\\
" "$outMDPath"
sed -i '' "s/^> //g" "$outMDPath"
sed -i '' "s/^>//g" "$outMDPath"
sed -i '' "s/{width=.*//g" "$outMDPath"
sed -i '' "s/^height=.*}//g" "$outMDPath"
sed -i '' "s/\\\\//g" "$outMDPath"
# 给图片链接添加jsDelivr前缀
sed -i '' "s/!\[\](/![](https:\/\/cdn.jsdelivr.net\/gh\/Sherwood9257\/blog_img\/$prefix\//g" "$outMDPath"
done

写完之后记得给脚本增加可运行(x)权限。

有些文件中会提到花括号{},但花括号在HTML解析的时候会被视作模板变量,导致项目编译报错。[20]
因此需要将花括号替换成转义符,{:&lbrace; }:&rbrace;

  1. 处理文章内容的格式问题
    使用pandoc把Word文档转换成Markdown文档不可避免地出现了一些格式上的问题。
    其中问题最大的就是代码部分,缩进全都被取消掉的。
    我本来打算使用VSCode的宏功能来实现代码的自动美化,结果查阅了很多资料发现VSCode并没有办法实现替换功能。
    因此最终改用js-beautify[15]来实现目标。
    做法是将对应代码复制到target.html、target.js、target.css之中,然后运行以下脚本生成美化后的代码,再复制回去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const fs = require('fs')
const beautify = require('js-beautify')

function beautifyCode(type, file) {
let code = fs.readFile('./' + file)
code = code // g代表全局匹配,m代表多行匹配
.replace(/^ */gm, '') // 删除行首的空格
.replace(/^> /gm, '') // 删除行首的引用符
.replace(/^>/gm, '')
.replace(/\\/gm, '') // 删除斜杠
code = beautify[type](code)
code = '```\n' + code + '\n```'
fs.writeFile('./output/' + file, code)
}

beautifyCode('js', './target.js')
beautifyCode('html', './target.html')
beautifyCode('css', './target.css')

六、 修改热更新


安装browse-sync插件之后,即可在保存的同时看到修改结果。

1
npm i hexo-browsersync -S

七、 图片资源的处理


更新:新方案使用图床,不再需要本地新建资源文件夹

安装插件hexo-renderer-marked之后,进行如下配置:

1
2
3
4
post_asset_folder: true
marked:
prependRoot: true
postAsset: true

完成之后,执行命令hexo new post article,系统就会在文章页面旁边新建一个同名文件夹,用于存放相关资源。
在文章中只需直接引入图片,不用指明其实际路径。


八、 tocbot定位错位的问题


问题一:左侧浮标与真正的目标项不在同一个高度
问题原因:是左侧浮标使用absolute定位,但父级元素没有使用relative定位,导致浮标找错锚点
解决办法:在post.styl中给属性tocbot-list添加position:relative

问题二:整个定位错误
检查是不是使用了tab缩进


九、“关于”页面


查阅了很多资料后发现并不能通过new page新建一个ejs[7]来重写原页面中的内容部分,所以最终还是把相关代码写在about.ejs里面

  1. 实现词云[8]
    词云依赖echarts和echarts-wordcloud这两个库,在Fluid主题中需要先在_config.yml的最底下引入url。
    接着,项目传统的做法是在script.ejs中引入js库,但是这样的话,我就不得不将about.ejs中相关代码拆分到页面底部,不方便后续维护。
    因此我最终是把引入库的代码也放在about.ejs之中了,各种Hexo主题也采用这种做法来制作网页。
1
2
3
4
5
// file: _config.yml
static_prefix:
echarts: https://cdn.jsdelivr.net/npm/echarts@5.3.2/dist/echarts.min.js
echarts_wordcloud: https://cdn.jsdelivr.net/npm/echarts-wordcloud@2.0.0/dist/echarts-wordcloud.min.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// file: about.ejs
<article class="about-content page-content mt-5">
<div class="wordcloud">
<div class="title">
<i class="iconfont icon-books" />
我的知识库
</div>
<div id="wordcloud"></div>
</div>
</article>

<script type="text/javascript" src="<%- theme.static_prefix.echarts %>" />
<script type="text/javascript" src="<%- theme.static_prefix.echarts_wordcloud %>" />
<script type="text/javascript">
const bestColors = ['#7E08EC', '#D82FFE', '#FF64FF', '#F3C84B', '#FD6894', '#507CFB'] [^21]
let wordCloud = echarts.init(document.getElementById('wordcloud'))
<%
let wcData = []
site.tags.forEach(tag => {
wcData.push({name: tag.name, value: tag.length})
})
wcData = JSON.stringify(wcData) // 由于<%- %>是以字符串形式来读取数据,所以要将wcData对象通过JSON转化成字符串
%>
let wcOption = {
tooltip: {
show: true
},
series: [{
name: '我的知识库',
type: 'wordCloud',
// top: '0',
// keepAspect: true,
// maskImage: maskImage,
// left: '0',
// right: '0',
// width: '100%',
// height: '100%',
sizeRange: [20, 50],//文字范围
//文本旋转范围,文本将通过rotationStep45在[-90,90]范围内随机旋转
rotationRange: [0, 90],
rotationStep: 90,
gridSize: 20 ,
// drawOutOfBound: true,
//形状
shape: 'circle',
// layoutAnimation: true,
textStyle: {
fontWeight: 'bold',
color: function() {//文字颜色的随机色
return bestColors[Math.round(Math.random() * (bestColor.length - 1))];
},
textShadowBlur: 4,
textShadowColor: '#666',
//悬停上去的字体的阴影设置
emphasis: {
focus: 'self',
shadowBlur: 10,
shadowColor: '#333'
}
},
data: <%- wcData %>
}]
wordcloud.setOption(wcOption)
</script>
1
2
3
4
5
6
7
8
9
10
11
// file: about.styl
.title
text-align center
font-size 1.75rem
i
font-size 1.6rem

#wordcloud
width 100%
height 30rem
margin auto

引入库时使用的是jsdelivr[10]提供的免费CDN加速服务,可以更快的获取脚本资源

  1. 实现月更新图表
    月更新的目标是展现截止到今天之前一个月每天的更新频率,要实现这个功能,最简单的做法是使用moment库[13]
    这个主题本身已经引入了moment库,所以无需做额外工作,直接用就行了。
    而具体的图表我参考了官网展示的效果图[11],具体参数使用参考Option文档[14]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// file: about.ejs
<div class="statics">
<div class="title">
<i class="iconfont icon-clipcheck" />
过去一个月更新频率
</div>
<div id="summary"></div>
<div>

<script type="text/javascript">
let summary = echarts.init(document.getElementById('summary'))
<%
let startDate = moment().substract(1, 'month')
let endDate = moment()

let monthMap = new Map()
const dayTime = 86400000

for (let time = startDate; time <= endDate; time += dayTime) {
let day = moment(time).format('MM-DD')
if (!monthMap.has(day)) monthMap.set(day, 0)
}

site.posts.forEach(post => {
let day = post.date.format('MM-DD')
if (monthMap.has(day)) monthMap.set(day, momthMap.get(day) + 1)
})

let monthArr = JSON.stringify([...monthMap.keys()])
let monthValueArr = JSON.stringify[...monthMap.values()]
%>

let postsOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
xAxis: {
type: 'category',
boundaryGap: false, // 标签与刻度居中
data: <%- monthArr %>,
axisLabel: {
hideOverlap: true // 重叠标签隐藏
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '文章篇数',
type: 'line',
smooth: true,
showSymbol: false,
data: <%- monthValueArr %>,
areaStyle: {
opacity: .8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ // 设置渐变,让图表更美观
{
offset: 0,
color: 'rgb(55, 162, 255)'
},
{
offset: 1,
color: 'rgb(116, 21, 219)'
}
])
}
}
]
}
summary.setOption(postsOption)

window.addEventListener('resize', () => {
wordcloud.resize()
summary.resize()
})
</script>
1
2
3
4
5
// file: about.styl
#summary
width 100%
height 35rem
margin auto
  1. 项目展示
    项目展示比较简单,只需要让项目分两列展示出来就行了,用space-around就可以实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// file: about.ejs
<div class="project">
<div class="title">
<i class="iconfont icon-briefcase" />
我的项目
</div>
<div class="content">
<a id="project-1" href="/project/eshopcms/">
<img src="/img/image2.png" /> <!-- 编译后项目根目录中的img跟主题中的img会统一放到一起 -->
<div class="name">电商CMS系统</div>
</a>
<a id="project-2" href="javascript:void(0)"> <!-- 这一个是小程序,只展示二维码,无需跳转页面 -->
<img src="/img/image4.png" />
<div class="name">电商小程序</div>
</a>
<a id="project-3" href="/project/planeechart">
<img src="/img/image1.png" />
<div class="name">全国航班数据大屏</div>
</a>
<a id="project-4" href="https://fygame.com">
<img src="/img/image3.png" />
<div class="name">企业官网</div>
</a>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// file: about.styl
a
text-decroration none
.content
margin 2rem
display flex
justify-content space-around
flex-wrap wrap
a
margin .5rem 0
&:hover
color #000
transition transform .5s ease
transform scale(1.2)
img
width 350px
height 175px
border-radius 10px
box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;
.name
text-align center
line-height 4rem
height 4rem

#project-2 /* 单独设置小程序的样式 */
position relative
&::before /* 在before中挂小程序码 */
position absolute
top 40% /* 40%刚好在图片中间 */
left 50%
transform translate(-50%, -50%)
box-shadow rgba(0, 0, 0, 0.35) 0px 5px 15px;
width 128px
height 128px
background url(/img/eshop.png) no-repeat center
background-size 100% 100% /* 让小程序码与盒子宽高一致 */
border-radius 20px
content ''
opacity 0

&:hover
&::before /* 当鼠标移到整个project-2时,修改before的样式 */
transition opacity .2s ease
opacity 1

图片都使用了宽度512,高度自适应的尺寸,让缩略图尺寸不会太大

  1. 滚动显示效果
    之前在做企业官网的时候实现过相关功能,其原理就是监听scroll事件,滚动时检测当前组件与窗口顶部的距离,如果小于一定值就播放动画效果
    这里为了方便直接使用外部库aos[12]
    aos的引入使用传统的办法:
1
2
3
// file: _config.yml
aos_js: https://cdn.jsdelivr.net/npm/aos@2.3.4/dist/aos.js
aos_css: https://cdn.jsdelivr.net/npm/aos@2.3.4/dist/aos.css
1
2
// file: css.ejs
<% css_ex(theme.static_prefix.aox_css, '') %>
1
2
3
4
// flie: aos.ejs
<script type="text/javascript">
AOS.init()
</scirpt>
1
2
3
4
5
// file: script.ejs
<% js_ex(theme.static_prefix.aox_js, '') %>

//在最底下
<%- partial('_partial/plugins/aos.ejs') %>
1
2
3
4
// file: about.ejs
<div class="wordcloud" data-aos="zoom-in-up">...</div>
<div class="statics" data-aos="zoom-in-up">...</div>
<div class="project" data-aos="zoom-in-up">...</div>

十、部署到服务器


这里采用deploy+git方案实现自动部署,具体操作如下:

  1. 安装hexo-deployer-git插件
1
npm install hexo-deployer-git --save
  1. 连接服务器,创建git仓库
1
2
cd /home/git
git init --bare sherwood-blog-hexo.git
  1. 在仓库的hooks文件夹下面添加钩子post-receive,并加入以下内容[5]
1
git --work-tree=/usr/share/nginx/html --git-dir=/home/git/sherwood-blog-hexo.git checkout -f

接着个post-receive添加可运行的权限:

1
chmod +x post-receive

其中work-tree就是即将部署的网站路径

  1. 设置权限
1
2
chown -R git:git /home/git/sherwood-blog-hexo.git
chmod -R 777 /usr/share/nginx/html
  1. 配置本地的config.yml
1
2
3
4
deploy:
type: git
repo: ssh://git@[IP]:[PORT]/home/git/sherwood-blog-hexo.git
branch: master

由于我服务器的端口不是默认的22,所以需要采用ssh来指定特定端口[6]

  1. 执行命令实现部署
    将package.json中的deploy属性的值修改成以下内容
1
hexo clean && hexo g -d

执行以下指令

1
npm run deploy



参考:
Fluid效果展示
使用 ECharts 插件绘制炫酷图表
How I created an action role-playing game
闪烁之狐


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!