该教程不适合纯小白食用,建议有一定js基础后再阅读!!!
注意:阅读本篇教程前请先阅读前置内容!!!

如果你的主题不是Butterfly,教程中提到的pug文件目录和内容可能会和你的不一样,还请自行调整。(如果能在评论区发出来是最好的了。)
有一部分原本由配置文件控制的内容(如主页卡片数量等)我迁移到了JS中,不再受配置文件控制。

2022/08/13

pjax.loadUrl改为pjax.refresh

2022/08/02

发布初版

思路

  方案的总体思路就是把HTML代码复制到JS中,然后JS运行时对其中需要修改的数据进行修改,然后输出到DOM中。最后再修改源代码,将原本内置在HTML中的内容删除即可(不删也能正常工作,删了可以节省空间)。

  因为主题、配置等因素的影响,我们的HTML结构可能不太一样,除非你能保证一定兼容,否则建议读者直接去CV自己的HTML代码。

  实现起来起始也十分简单,我们给出几个示例:

侧边栏

公告栏

  引入JS:

1
2
3
4
5
6
7
8
document.addEventListener('kms:loaded', () => kms.readPostsJson().then(json => {
const content = json.config.doc
// 因为侧边栏的元素都没有id,所以只能通过className获取对象
// 如果你不能通过该方法获取到正确的对象,请自行修改
const div = document.querySelector('.aside-content>.card-widget.card-announcement>.announcement_content')
if (div) div.innerHTML = content
pjax?.refresh(div)
}))

  修改[theme]\layout\includes\widget\card_announcement.pug为:

1
2
3
4
5
6
if theme.aside.card_announcement.enable
.card-widget.card-announcement
.item-headline
i.fas.fa-bullhorn.fa-shake
span= _p('aside.card_announcement')
.announcement_content

文章页相关推荐

  引入JS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
document.addEventListener('kms:loaded', () => kms.readPostsJson().then(json => {
const div = document.getElementById('related-list')
if (!div) return
div.innerHTML = ''
function buildHTML(abbrlink, title, img, date) {
const url = `/posts/${abbrlink}/`
return `<div class="aside-list-item"><a class="thumbnail" href="${url}" title="${title}"><img alt="${title}" src="${img}"></a><div class="content"><a class="title" href="${url}" title="${title}">${title}</a><time title="发表于 ${date.toLocaleDateString()}">${date.toLocaleDateString()}</time></div></div>`
}
const href = location.pathname.substring(0, location.pathname.length - 1)
const abbrlink = href.substring(href.lastIndexOf('/') + 1)
const list = kms.readPostInfo(json, abbrlink).related
const date = json.config.related
for (let post of list) {
const info = kms.readPostInfo(json, post)
const html = buildHTML(post, info['title'], info['img'], new Date(info[date]))
div.insertAdjacentHTML("beforeend", html)
}
pjax?.refresh(div)
}))

  创建[theme]\layout\includes\widget\card-related-post.pug

1
2
3
4
5
.card-widget.card-recommend-post
.item-headline
i.fa.fa-book
span= _p('post.recommend')
.aside-list(id='related-list')

  修改[theme]\layout\includes\widget\index.pug:(注意缩进)

1
2
3
4
5
6
  .sticky_layout
if showToc
include ./card_post_toc.pug
if theme.related_post && theme.related_post.enable
+ !=partial('includes/widget/card-related-post', {}, {cache: true})
- != related_posts(page,site.posts)

文章卡片

  接下来我们来修改主页,这里要特别注意,就算是同一个主题,配置不一样主页博文卡片的HTML代码也会不同,一定要根据自己想要的样式生成HTML代码。

  因为这个JS代码只需要且只能在主页运行,所以我选择把JS代码内嵌在HTML中。

  修改[theme]\layout\index.pug为:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
extends includes/layout.pug

block content
//include ./includes/mixins/post-ui.pug
#recent-posts.recent-posts
//+postUI
include includes/pagination.pug

script.
document.addEventListener('kms:loaded', function fun() {
document.removeEventListener('kms:loaded', fun)
/**
* 将主页翻到指定页面
* @param index 页码
* @param json postsInfo.json
* @param back 是否滚动页面到列表顶部
* @param edit 是否修改hash
*/
function homePage(index, json, back = true, edit = true) {
if (back) btf.scrollToDest(btf.getEleTop(document.getElementById('content-inner')))
const isDateSort = true // 是否按发布日期排序
const postsList = isDateSort ? json.dateSort : json.updatedSort
const maxPage = Math.ceil(postsList.length / 10)
/**
* 构建博文卡片的HTML代码
* @param abbrlink {string} 博文的abbrlink
* @param direct {string} 封面图方向(`left` or `right`)
* @return {string} HTML代码
*/
const buildHTML = (abbrlink, direct) => {
const info = kms.readPostInfo(json, abbrlink)
// 根据配置获取日期(发布日期 or 更新日期)
const getTime = () => isDateSort ? `发表于 ${new Date(info.date).toLocaleDateString()}` : `更新于 ${new Date(info.updated).toLocaleDateString()}`
const url = `/posts/${abbrlink}`
// noinspection JSUnresolvedFunction,HtmlRequiredAltAttribute
let result = `<div class="recent-post-item ${direct}"><div class="post_cover ${direct}"><a href="${url}"><img class="post_bg" src="${info.img}"></a></div><div class="recent-post-info"><a class="article-title" href="${url}">${info.title}</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">${getTime()}</span></span>`
const divPoint = '<span class="article-meta-separator">|</span>' // 不同项目之间的分隔符
const spanPoint = '<span class="article-meta-link">•</span>' // 项目内部的分隔符
// 生成分类
result += `<span class="article-meta">${divPoint}<i class="fas fa-inbox"></i>`
for (let i = 0; i < info.categories.length; ++i) {
const category = info.categories[i]
if (i !== 0) result += spanPoint
// noinspection JSUnresolvedFunction
result += `<a class="article-meta__categories" onclick="pjax.loadUrl('/categories/${category}/')" data-pjax-state="">${category}</a>`
}
// 生成标签
result += `</span><span class="article-meta tags">${divPoint}<i class="fas fa-tag"></i>`
for (let i = 0; i < info.tags.length; ++i) {
const tag = info.tags[i]
if (i !== 0) result += spanPoint
// noinspection JSUnresolvedFunction
result += `<a class="article-meta__tags" onclick="pjax.loadUrl('/tags/${tag}/')">${tag}</a>`
}
// 博文描述
result += `</span></div><div class="content">${info.des}</div></div></div>`
return result
}
if (index < 1) index = 1
else if (index > maxPage) index = maxPage
const recentPosts = document.getElementById('recent-posts')
recentPosts.innerHTML = ''
for (let i = (index - 1) * 10, k = 0; k !== 10 && i !== postsList.length; ++i, ++k)
recentPosts.innerHTML += buildHTML(postsList[i], (k & 1) === 0 ? 'left' : 'right')
recentPosts.style.cssText = 'overflow-x:hidden;'
// 页尾的翻页栏
recentPosts.innerHTML += `<nav id="pagination"><div class="pagination"><a class="first"><i class="fa fa-fast-backward"></i></a><a class="prev"><i class="fas fa-chevron-left fa-fw"></i></a><input id="pagination-input" type="number" min="1" max="${maxPage}" value="${index}"><button class="jump">跳转</button><a class="next"><i class="fas fa-chevron-right fa-fw"></i></a><a class="end"><i class="fa fa-fast-forward"></i></a></div></nav>`
pjax?.refresh(recentPosts)
recentPosts.addEventListener('animationend', function fun() {
recentPosts.removeEventListener('animationend', fun)
recentPosts.style.cssText = ''
})
const initDiv = () => {
if (edit && back) location.hash = index
const pagination = document.getElementById('pagination')
pagination.onclick = event => {
const element = event.target.childNodes.length !== 0 ? event.target : event.target.parentNode
if (element.classList.contains('disable')) return
const classList = element.classList
if (classList.contains('prev')) {
homePage(index - 1, json)
} else if (classList.contains('next')) {
homePage(index + 1, json)
} else if (classList.contains('jump')) {
const newIndex = Number.parseInt(document.getElementById('pagination-input').value)
if (index !== newIndex) homePage(newIndex, json)
} else if (classList.contains('first')) {
homePage(1, json)
} else if (classList.contains('end')) {
homePage(maxPage, json)
}
}
const setDisable = name => pagination.getElementsByClassName(name)[0].classList.add('disable')
const removeDisable = name => pagination.getElementsByClassName(name)[0].classList.remove('disable')
if (index === 1) {
setDisable('prev')
setDisable('first')
removeDisable('next')
removeDisable('end')
} else if (index === maxPage) {
removeDisable('prev')
removeDisable('first')
setDisable('next')
setDisable('end')
}
}
initDiv()
}

kms.readPostsJson().then(json => {
const parseUrlIndex = (hash = location.hash) =>
hash.length === 0 ? 1 : Number.parseInt(hash.substring(1))
// 构建文章列表
homePage(parseUrlIndex(), json, false)
window.onhashchange = event => {
const oldUrl = event.oldURL
const splitIndex = oldUrl.indexOf('#')
const index = Number.parseInt(splitIndex < 0 ? 1 : oldUrl.substring(splitIndex))
const newIndex = parseUrlIndex()
if (index !== newIndex) homePage(newIndex, json, false, false)
}
})
})

  如果你想要修改卡片样式,修改builtHTML这个函数即可。

  为了编码方便,主页下方的翻页按钮我改成了我自己设计的样式,没有照搬原版的,不喜欢的小伙伴自行修改代码即可。

  同时,这段代码中并没有添加懒加载的支持,想要添加懒加载的小伙伴自行修改代码即可。

  编写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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
document.addEventListener('kms:loaded', function fun() {
document.removeEventListener('kms:loaded', fun)

/**
* 将主页翻到指定页面
* @param index 页码
* @param json postsInfo.json
* @param back 是否滚动页面到列表顶部
* @param edit 是否修改hash
*/
function homePage(index, json, back = true, edit = true) {
if (back) btf.scrollToDest(btf.getEleTop(document.getElementById('content-inner')))
const isDateSort = true // 是否按发布日期排序
const postsList = isDateSort ? json.dateSort : json.updatedSort
const maxPage = Math.ceil(postsList.length / 10)
/**
* 构建博文卡片的HTML代码
* @param abbrlink {string} 博文的abbrlink
* @param direct {string} 封面图方向(`left` or `right`)
* @return {string} HTML代码
*/
const buildHTML = (abbrlink, direct) => {
const info = kms.readPostInfo(json, abbrlink)
// 根据配置获取日期(发布日期 or 更新日期)
const getTime = () => isDateSort ? `发表于 ${new Date(info.date).toLocaleDateString()}` : `更新于 ${new Date(info.updated).toLocaleDateString()}`
const url = `/posts/${abbrlink}`
// noinspection JSUnresolvedFunction,HtmlRequiredAltAttribute
let result = `<div class="recent-post-item ${direct}"><div class="post_cover ${direct}"><a href="${url}"><img class="post_bg" src="${info.img}"></a></div><div class="recent-post-info"><a class="article-title" href="${url}">${info.title}</a><div class="article-meta-wrap"><span class="post-meta-date"><i class="far fa-calendar-alt"></i><span class="article-meta-label">${getTime()}</span></span>`
const divPoint = '<span class="article-meta-separator">|</span>' // 不同项目之间的分隔符
const spanPoint = '<span class="article-meta-link">•</span>' // 项目内部的分隔符
// 生成分类
result += `<span class="article-meta">${divPoint}<i class="fas fa-inbox"></i>`
for (let i = 0; i < info.categories.length; ++i) {
const category = info.categories[i]
if (i !== 0) result += spanPoint
// noinspection JSUnresolvedFunction
result += `<a class="article-meta__categories" onclick="pjax.loadUrl('/categories/${category}/')" data-pjax-state="">${category}</a>`
}
// 生成标签
result += `</span><span class="article-meta tags">${divPoint}<i class="fas fa-tag"></i>`
for (let i = 0; i < info.tags.length; ++i) {
const tag = info.tags[i]
if (i !== 0) result += spanPoint
// noinspection JSUnresolvedFunction
result += `<a class="article-meta__tags" onclick="pjax.loadUrl('/tags/${tag}/')">${tag}</a>`
}
// 博文描述
result += `</span></div><div class="content">${info.des}</div></div></div>`
return result
}
if (index < 1) index = 1
else if (index > maxPage) index = maxPage
const recentPosts = document.getElementById('recent-posts')
recentPosts.innerHTML = ''
for (let i = (index - 1) * 10, k = 0; k !== 10 && i !== postsList.length; ++i, ++k)
recentPosts.innerHTML += buildHTML(postsList[i], (k & 1) === 0 ? 'left' : 'right')
recentPosts.style.cssText = 'overflow-x:hidden;'
// 页尾的翻页栏
recentPosts.innerHTML += `<nav id="pagination"><div class="pagination"><a class="first"><i class="fa fa-fast-backward"></i></a><a class="prev"><i class="fas fa-chevron-left fa-fw"></i></a><input id="pagination-input" type="number" min="1" max="${maxPage}" value="${index}"><button class="jump">跳转</button><a class="next"><i class="fas fa-chevron-right fa-fw"></i></a><a class="end"><i class="fa fa-fast-forward"></i></a></div></nav>`
pjax?.refresh(recentPosts)
recentPosts.addEventListener('animationend', function fun() {
recentPosts.removeEventListener('animationend', fun)
recentPosts.style.cssText = ''
})
const initDiv = () => {
if (edit && back) location.hash = index
const pagination = document.getElementById('pagination')
pagination.onclick = event => {
const element = event.target.childNodes.length !== 0 ? event.target : event.target.parentNode
if (element.classList.contains('disable')) return
const classList = element.classList
if (classList.contains('prev')) {
homePage(index - 1, json)
} else if (classList.contains('next')) {
homePage(index + 1, json)
} else if (classList.contains('jump')) {
const newIndex = Number.parseInt(document.getElementById('pagination-input').value)
if (index !== newIndex) homePage(newIndex, json)
} else if (classList.contains('first')) {
homePage(1, json)
} else if (classList.contains('end')) {
homePage(maxPage, json)
}
}
const setDisable = name => pagination.getElementsByClassName(name)[0].classList.add('disable')
const removeDisable = name => pagination.getElementsByClassName(name)[0].classList.remove('disable')
if (index === 1) {
setDisable('prev')
setDisable('first')
removeDisable('next')
removeDisable('end')
} else if (index === maxPage) {
removeDisable('prev')
removeDisable('first')
setDisable('next')
setDisable('end')
}
}
initDiv()
}

kms.readPostsJson().then(json => {
const parseUrlIndex = (hash = location.hash) =>
hash.length === 0 ? 1 : Number.parseInt(hash.substring(1))
// 构建文章列表
homePage(parseUrlIndex(), json, false)
window.onhashchange = event => {
// 检测当前页面是否为主页,如果你的主页地址不在根目录,请自行修改判断方法
const root = location.protocol + '//' + location.host
if (Math.abs(root.length - event.newURL.length) > 1) return

const oldUrl = event.oldURL
const splitIndex = oldUrl.indexOf('#')
const index = Number.parseInt(splitIndex < 0 ? 1 : oldUrl.substring(splitIndex))
const newIndex = parseUrlIndex()
if (index !== newIndex) homePage(newIndex, json, false, false)
}
})
})

  注意这段代码中,使用document.removeEventListener移除了事件,如果启用了pjax的话这行代码是必须写的。因为启用pjax后切换页面时并不会移除document中注册的事件,如果不手动移除事件,这段代码就有可能在其它页面触发,这是我们所不期望的。

  修改[theme]\layout\index.pug(注意修改js的URL地址):

1
2
3
4
5
6
7
8
9
  extends includes/layout.pug

block content
- include ./includes/mixins/post-ui.pug
#recent-posts.recent-posts
- +postUI
include includes/pagination.pug
+
+ script(defer src='[这里写上面那个JS的URL]')

  然后再引入CSS(这个CSS是给卡片添加的进入的动画,可以不添加):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@keyframes SlipIn2Right
from
transform translateX(-100%) rotateX(90deg)
to
transform translateX(0) rotateX(0)

@keyframes SlipIn2Left
from
transform translateX(100%) rotateX(90deg)
to
transform translateX(0) rotateX(0)

.recent-post-item
&.left
animation SlipIn2Right 1.5s ease-in-out

&.right
animation SlipIn2Left 1.5s ease-in-out

删除多余页面

  此时主页就编写好了,但是有一个隐藏的问题,就是Hexo会生成好几个主页(/,/page/[n]),有强迫症的小伙伴可以通过hexo scriptgulp删除这个目录。

  这里仅给出gulp的方案:

  如果没有安装gulp的话先安装gulp,在博客根目录运行下面的指令:

1
2
npm install --global gulp-cli
npm install gulp --save

  然后在根目录创建gulpfile.js文件,在其中键入下面的内容:

1
2
3
4
5
6
7
const remove = require('gulp-clean')

// 清除指定目录
gulp.task('clean', () =>
gulp.src('./public/page', {read: false})
.pipe(remove())
)

  然后安装所需插件,在博客根目录运行下面的指令:

1
npm install gulp-clean --save-dev

  这时候执行完hexo g后再执行gulp clean就可以删除掉/page/[n]的页面了。

标签页 & 分类页

  编写并引入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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
const kms = {
/**
* 构建标签页或分类页
* @param pageName {string} 属性名称
* @return {Promise}
*/
buildTagOrCatsPage: (pageName) => kms.collectJInfo(pageName).then(json => {
// noinspection JSUnresolvedVariable
const list = json.cache[pageName]
const categories = document.getElementById(pageName)
const cardList = categories.getElementsByClassName('card')
const h2 = document.getElementById(`${pageName}-name`)
const getDate = date => new Date(date).toLocaleDateString()
let sortIndex = 'updated'
let sortDirect = 1
let preName = null

// 构建分类卡片列表
const buildList = () => {
let html = ''
for (let name in list) {
// noinspection HtmlUnknownAttribute,HtmlRequiredAltAttribute
html += `<a class="card" index="${name}"><div class="img"><img src="${kms.readPostInfo(json, list[name][0]).img}!/fw/230"></div><p>${name}</p></a>`
}
categories.innerHTML = html
}
/**
* 选择一个名称,传入`null`表明从URL中提取参数,如果名称无效则什么都不做
* @param name 名称
* @param scroll 是否滚动
*/
const selectFlag = (name, scroll = true) => {
if (name == null) {
if (location.hash.length !== 0) {
name = decodeURIComponent(location.hash.substring(1))
} else return
}
const flagList = list[name]
if (!flagList) return
preName = name
flagList.sort((a, b) => kms.readPostInfo(json, a)[sortIndex] <
kms.readPostInfo(json, b)[sortIndex] ? sortDirect : -sortDirect)
document.getElementById('details').classList.remove('close')
kms.setText(h2, name)
if (scroll) btf.scrollToDest(btf.getEleTop(h2))
let html = ''
for (let abbrlink of flagList) {
const info = kms.readPostInfo(json, abbrlink)
const href = `/posts/${abbrlink}`
// noinspection HtmlRequiredAltAttribute
html += `<div class="card"><a class="img" href="${href}"><img src="${info.img}!/fw/128"></a><div class="descr"><div class="time"><span class="date"><i class="far fa-calendar-alt"></i><p>${getDate(info.date)}</p></span><span class="updated"><i class="fa fa-pencil"></i><p>${getDate(info.updated)}</p></span></div><a class="title" href="${href}">${info.title}</a></div></div>`
}
const div = document.getElementById(`${pageName}-list`)
div.innerHTML = html
pjax?.refresh(div)
}
/** 监听排序方式的变化 */
const registryEvent = () => {
const select = document.getElementById('list-sort')
select.onchange = () => {
sortIndex = select.value
selectFlag(preName, false)
}
const button = document.getElementById('sort-direct')
button.onclick = () => {
sortDirect = -sortDirect
const className = sortDirect === 1 ? 'desc' : 'asc'
button.innerHTML = `<i class="fa fa-sort-amount-${className}"></i>`
selectFlag(preName, false)
}
}
registryEvent()
buildList()
selectFlag(null)

// 为每一个分类卡片添加点击事件
for (let element of cardList) {
const name = element.getAttribute('index')
element.onclick = () => selectFlag(name)
}
})
}

  修改[theme]\layout\includes\page\tags.pug为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#tags

.close#details
.name
h2#tags-name
.block
button#sort-direct
i.fa.fa-sort-amount-desc
select#list-sort
option(value='updated') 更新时间
option(value='date') 发布时间
option(value='title') 标题
#tags-list

script.
document.addEventListener('kms:loaded', function fun() {
document.removeEventListener('kms:loaded', fun)
kms.buildTagOrCatsPage('tags')
})

  修改[theme]\layout\includes\page\categories.pug为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#categories

.close#details
.name
h2#categories-name
.block
button#sort-direct
i.fa.fa-sort-amount-desc
select#list-sort
option(value='updated') 更新时间
option(value='date') 发布时间
option(value='title') 标题
#categories-list

script.
document.addEventListener('kms:loaded', function fun() {
document.removeEventListener('kms:loaded', fun)
kms.buildTagOrCatsPage('categories')
})

  引入CSS:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#page
#categories, #tags
text-align center
border 2px dashed var(--km-button-dark-bg)
border-radius 10px

.img
height 120px
max-width 100%
overflow hidden
border-radius 10px
transition all .3s

img
width 100%
height 100%
object-fit cover

.card
width calc(100% / 4 - 16px)
display inline-block
margin 8px
text-align center

p
display block
font-weight bold
font-size large
margin-top 12px

&:hover
border-radius 5px
background var(--km-card-bg)

.img
transform scale(.9)

#tags
.card
width unset
margin 0
padding 8px
margin-top 5px
margin-bottom 5px

p
margin 0

.img
display none

@media screen and (max-width: 700px)
#categories
.card
width calc(100% / 3 - 16px)

@media screen and (max-width: 500px)
#categories
.card
width calc(100% / 2 - 16px)

@media screen and (max-width: 300px)
#categories
.card
width calc(100% - 16px)

#details
&.close
display none

.name
h2
display inline-block

.block
display inline-block
float right
margin-top 27px

select
margin-left 10px
padding 5px
background var(--btn-bg)
color var(--btn-color)
border-radius 5px
border none

button
background var(--btn-bg)
color var(--btn-color)
border-radius 5px
padding 6px

&:hover
background var(--btn-hover-color)

.card
margin-bottom 15px

.img
display inline-block
height 80px
width 80px
vertical-align middle
border-radius 8px
overflow hidden
margin-right 15px

img
width 100%
height 100%
object-fit cover
transition all .2s

&:hover
transform scale(1.1)

.descr
display inline-block
vertical-align middle
max-width calc(100% - 110px)

.time
display inline-block
margin 0
color #858585

span
white-space nowrap

p
display inline-block
width 80px
margin 0
margin-left 6px

.date
margin-right 12px

.title
display block
margin 0
font-weight bold
font-size large
overflow hidden
text-overflow ellipsis
white-space nowrap

  不喜欢我写的页面样式的可以自行修改。

修改页面链接

  这时候分类页和标签页已经写好了,但是页面内除了顶部导航栏内的链接,其它地方仍然会跳转到Hexo生成的分类页/标签页中,我们需要把它们改正过来(不想修改的可以跳过)。

  找到并修改下列文件,将其中的链接/tags/[name]//categories/[name]/分别改为/tags/#name/categories/#name即可:

  • [theme]\layout\index.pug
  • [theme]\layout\post.pug
  • 自己编写的JS和HTML中的链接

  此时,Hexo自己生成的分类页和标签页其实还在,但是我们没有添加对它们的引用,读者可以自行决断是否删除这些页面。

  个人建议保留这些页面,然后在归档页中添加对这些页面的引用,方便搜索引擎爬取,因为我们的方案本身就不利于搜索引擎爬取页面了,留一个接口给搜索引擎也不错。


  到这里,读者应该差不多能够明白整个过程是什么样子了,试一下自己把一个元素改为JS生成吧。