本文以《Hexo 博客哔哔更换记录》修改而成,代码根据我的需求进行一些微调,仅供学习参考。

部署

部署建议跟着官方文档就行,基本不会出什么问题。

这里给几点注意事项,我踩的坑:

  • 本项目两个仓库fork到自己仓库时,一定要把Copy the master branch only取消勾选(默认会勾选),将所有分支都fork到自己仓库。

    如图所示

  • 弄后台填写环境变量时直接复制粘贴,结果多了一个斜杠,后台会登陆不上,要注意一下
    例如让填:https://kkapi-open.vercel.app
    然后我填成了:https://kkapi-open.vercel.app/

  • 填写变量时,mongoDB的账号密码不需要填写
    mongodb的url里面已经有账号和密码了,所以变量只需要填url和那个加密的即可,加密的内容是随便填的。

然后根据文档完成相关的部署工作,最后进入后台,到这里后端就已经全部完成了。

前端

这里的前端部分我按照我之前的哔哔进行了一些修改,我的js和css仅适合我自己,你们可以去看一下原文,按照自己的需求来。

首页轮播

修改源码

为了方便,我们直接修改源码使固定页面引用css和js文件
修改butterfly\layout\index.pug

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

block content
include ./includes/mixins/post-ui.pug
#recent-posts.recent-posts
+ #bber-talk
+ script(src='/static/timeago.min.js')
+ script(src='/static/bbtalk.js')
+postUI
include includes/pagination.pug

bbtalk.css

由于本站的CSS文件都做了整合,所以将bbtalk.css建在了\butterfly\source\css\_custom目录下,没整合css的也可以直接引入,跟正常魔改的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
/* 以下颜色设置 */
[data-theme=light] {
--sianx_bb_color:#edf4ff;
--sianx_text_color:#6667ab;
}

[data-theme=dark] {
--sianx_bb_color: #edf0f70d;
--sianx_text_color:#ffffffb3;
}
/* 首页轮播 */

#bber-talk,
#bber-talk a {
color: var(--font-color);
}

#bber-talk {
cursor: pointer;
width: 100%;
min-height: 50px;
background: var(--card-bg);
padding: 0.5rem 1rem;
border-radius: 14px;
display: flex;
align-items: center;
overflow: hidden;
/* font-weight: bold; */
height: 25px;

}

#bber-talk .item i {
margin-left: 5px;
}

#bber-talk>i {
font-size: 1.1rem;
}

#bber-talk .talk-list {
text-align: center;/* 文本居中 */
max-height: 32px;
font-size: 16px;
overflow: hidden;
padding-left: 0px;/* 去掉时间后的后遗症解决方案 */
}

#bber-talk .talk-list :hover {
color: #49b1f5 !important;
transition: all .2s ease-in-out;
}

#bber-talk .talk-list li {
list-style: none;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
margin-left: 0px;/* 去掉时间后的后遗症解决方案 */
}

#bber-talk .bber-icon {
line-height: 25px;
margin-left: 8px;
transition: 0.3s;
}

#bber-talk .pass {
-webkit-animation: 1s passing infinite;
animation: 1s passing infinite;
}

@-webkit-keyframes passing {
0% {
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
opacity: 0;
}
50% {
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
100% {
-webkit-transform: translateX(50%);
transform: translateX(50%);
opacity: 0;
}
}

@keyframes passing {
0% {
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
opacity: 0;
}
50% {
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
100% {
-webkit-transform: translateX(50%);
transform: translateX(50%);
opacity: 0;
}
}

bbtalk.js

source/static目录下创建bbtalk.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
let jsonUrl = 'https://kkapi-open-one.vercel.app/api/ispeak?author=个人id' // 在这修改api,改成自己的api地址https://kkapi.vercel.app/api/ispeak?author=个人id,这里的author为后台-个人设置里面的个人ID。

document.getElementById('bber-talk').addEventListener('click', () => {
window.location.pathname = '/time/' // 在这修改你的哔哔页面地址
})

bbtalk();

function bbtalk() {
let data = JSON.parse(localStorage.getItem('bibi'));
let nowTime = Date.now();
let ls;
if (data == null || nowTime - data.time >= 1800000) { // 设置缓存时长,单位毫秒,默认30分钟,建议10分钟以上,不能为0,想不缓存自己改代码。
getData();
return
} else {
ls = JSON.parse(data.ls)
};
let bberHtml = ''
ls.forEach((item, i) => {
let br = /[\s\uFEFF\xA0]+/g;
item.content = item.content.replace(br, '')
let d = new Date(item.createdAt)
let date = d.getFullYear() + '/' + (d.getMonth() + 1) + '/' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds()
let dataTime = timeago.format(date, 'zh_CN');
let newdataTime = ''

bberHtml += '<li class="item item-' + (i + 1) + '">' + newdataTime + ' ' + urlToLink(item.content) + '</li>'
});
document.getElementById("bber-talk").innerHTML += '<i style="margin-right: 10px;" class="fa-regular fa-message"></i><ul class="talk-list">' + bberHtml + '</ul><i class="fa-solid fa-angles-right pass bber-icon"></i>'
}

function getData() {
fetch(jsonUrl)
.then(res => res.json())
.then((data) => {
data = { time: Date.now(), ls: JSON.stringify(data.data.items) }
localStorage.setItem('bibi', JSON.stringify(data))
}).then(() => {
bbtalk();
}).catch(() => {
console.log('获取哔哔数据失败!');
});
}

function urlToLink(str) {
let re_forimg = /<img(.*?)src=[\"|\']?(.*?)[\"|\']?(.*?)>|!\[(.*?)\]\((.*?)\)/g;
str = str.replace(re_forimg, '<i class="fa-solid fa-image"></i>');
return str
}

function Roll() {
try {
let list_li = Array.prototype.slice.call(document.querySelectorAll('.talk-list li'));
let tmp = list_li[0];
list_li.splice(0, 1);
list_li.push(tmp);
let list = document.querySelector('ul.talk-list')
list_li.forEach((item) => {
list.appendChild(item)
});
} catch (error) {}
};
setInterval(Roll, 3000);// 首页轮播时间

timeago.min.js

source/static目录下创建timeago.js,这个直接创建文件粘贴就行,是一个工具文件。

1
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).timeago={})}(this,function(e){"use strict";var r=["second","minute","hour","day","week","month","year"];var a=["秒","分钟","小时","天","周","个月","年"];function t(e,t){n[e]=t}function i(e){return n[e]||n.en_US}var n={},f=[60,60,24,7,365/7/12,12];function o(e){return e instanceof Date?e:!isNaN(e)||/^\d+$/.test(e)?new Date(parseInt(e)):(e=(e||"").trim().replace(/\.\d+/,"").replace(/-/,"/").replace(/-/,"/").replace(/(\d)T(\d)/,"$1 $2").replace(/Z/," UTC").replace(/([+-]\d\d):?(\d\d)/," $1$2"),new Date(e))}function d(e,t){for(var n=e<0?1:0,r=e=Math.abs(e),a=0;e>=f[a]&&a<f.length;a++)e/=f[a];return(0===(a*=2)?9:1)<(e=Math.floor(e))&&(a+=1),t(e,a,r)[n].replace("%s",e.toString())}function l(e,t){return((t?o(t):new Date)-o(e))/1e3}var s="timeago-id";function h(e){return parseInt(e.getAttribute(s))}var p={},v=function(e){clearTimeout(e),delete p[e]};function m(e,t,n,r){v(h(e));var a=r.relativeDate,i=r.minInterval,o=l(t,a);e.innerText=d(o,n);var u,c=setTimeout(function(){m(e,t,n,r)},Math.min(1e3*Math.max(function(e){for(var t=1,n=0,r=Math.abs(e);e>=f[n]&&n<f.length;n++)e/=f[n],t*=f[n];return r=(r%=t)?t-r:t,Math.ceil(r)}(o),i||1),2147483647));p[c]=0,u=c,e.setAttribute(s,u)}t("en_US",function(e,t){if(0===t)return["just now","right now"];var n=r[Math.floor(t/2)];return 1<e&&(n+="s"),[e+" "+n+" ago","in "+e+" "+n]}),t("zh_CN",function(e,t){if(0===t)return["刚刚","片刻后"];var n=a[~~(t/2)];return[e+" "+n+"前",e+" "+n+"后"]}),e.cancel=function(e){e?v(h(e)):Object.keys(p).forEach(v)},e.format=function(e,t,n){return d(l(e,n&&n.relativeDate),i(t))},e.register=t,e.render=function(e,t,n){var r=e.length?e:[e];return r.forEach(function(e){m(e,e.getAttribute("datetime"),i(t),n||{})}),r},Object.defineProperty(e,"__esModule",{value:!0})});

哔哔页面

新建哔哔页面

使用 hexo n page 'bb' 创建页面,然后引入js

1
2
3
4
5
6
7
8
9
10
11
12
---
title:
date: 2022-04-13 22:06:17
aside: false
comments: false
type: time
---
+ <script src="/static/bibi.js"></script>

+ <div id="bibi">
+ <div class="bb-info"></div><div id="bb-main"></div>
+ </div>

bbtalk.css

在刚才上面创建的bbtalk.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
/* 哔哔页面 */

#bibi button {
cursor: pointer;
color: #fff;
border: 0;
margin: 20px auto;
border-radius: 0.3125rem;
display: block;
padding: 0 1rem;
height: 40px;
font-weight: 500;
text-align: center;
transition: all 0.5s ease-out;
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 1000% 1000%;
animation: Gradient 60s linear infinite;
outline: 0;
}

#bibi .bb-info {
font-weight: 700;
font-size: 18px;
}

#bibi .bb-card {
padding: 10px 20px;
border-radius: 10px;
background: var(--sianx_bb_color);
overflow: hidden;
margin-top: 20px;
transition: all 0.25s;
user-select: none;
border: 2px solid var(--sianx_card-inner);
overflow: hidden;
}

#bibi .bb-card:hover {
transform: translateY(-3px);
/* border: 2px solid #9c9; */
overflow: hidden;
}

#bibi .card-header {
display: flex;
align-items: center;
}

#bibi .card-header .avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 10px;
border-radius: 20px;
overflow: hidden;
}

#bibi .card-header svg {
height: 20px;
width: 20px;
margin-left: 5px;
}

#bibi .card-header .card-time {
font-size: 12px;
text-shadow: #d9d9d9 0 0 1px, #fffffb 0 0 1px, #fffffb 0 0 2px;
margin-left: 10px;
}

#bibi .card-content {
padding: 10px 0;
white-space: pre-wrap;
color: var(--sianx_text_color);
font-weight: 700;
}

#bibi .card-footer {
display: flex;
padding-bottom: 10px;
}

#bibi .card-footer .card-label {
border-radius: 5px;
padding: 0 5px;
font-weight: 550;
border-radius: 3px;
box-shadow: inset 0 -1px 0 rgb(27 31 35 / 12%);
font-size: 14px;
cursor: pointer;
user-select: none;
margin-right: 10px;
}

#article-container .card-content img {
margin: 0;
}

@media screen and (min-width: 768px) {
.card-content .fancybox,
.card-content video {
display: inline-block;
max-width: 40%;
margin-right: 10px;
}
}

@media screen and (max-width: 768px) {
.card-content .fancybox,
.card-content video {
display: inline-block;
max-width: 48%;
margin: 1%;
}
}

@keyframes Gradient {
0% {
background-position: 0 50%;
}
50% {
background-position: 100% 50%;
}
to {
background-position: 0 50%;
}
}

bibi.js

source/static目录下创建bibi.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
let svg = '<svg  viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" class="is-badge"><path  d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z" fill="#1da1f2"></path></svg>'
let total = 0
let nowNum = 0
let items = []
let page = 1
let Url = 'https://kkapi.vercel.app/api/ispeak?author=个人id&page=' // 修改api,直接按照我给好的模板来,记得带参数page


window.addEventListener('DOMContentLoaded', () => {
getNew();
});

// 获取数据
function getNew() {
let bibi = document.getElementById('bibi');
try {
bibi.removeChild(document.getElementById('more'))
} catch (error) {}

bibi.innerHTML += '<div id="loading">正在加载中...</div>' // 可以自己写加载中的显示内容,可用Html。

fetch(Url + page).then(res => res.json()).then((res) => {
total = res.data.total
items = res.data.items
nowNum += items.length
if (page == 1) {
document.querySelector('.bb-info').innerHTML = '<i class="far fa-comment-alt"></i> My Time Machine(' + total + ')'
}
page += 1
}).then(() => {
bb();
if (nowNum < total) {
document.getElementById('bibi').innerHTML += '<button id="more" onclick="getNew()">再翻翻</button>'
}
document.getElementById('bibi').removeChild(document.getElementById('loading'))
})
}

// 渲染数据
function bb() {
let bb = document.getElementById('bb-main')
items.forEach((item) => {
let time = item.createdAt.substring(0, 10);
let div = document.createElement('div')
item.content = contentFormat(item.content)

div.className = 'bb-card'
div.innerHTML = '<div class="card-header"><div class="avatar"><img class="nofancybox"src="' + item.author.avatar + '"></div><div class="name">' + item.author.nickName + '</div>' + svg + '<div class="card-time">' + time + '</div></div><div class="card-content">' + item.content + ''
bb.appendChild(div)
})
}

// content格式化
function contentFormat(s) {
let br = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
let re_forimg = /<img(.*?)src=[\"|\']?(.*?)[\"|\']?(.*?)>|!\[(.*?)\]\((.*?)\)/g;
let getImgUrl = /(http(.*).[jpg|png|gif])/g;
let ls = s.match(getImgUrl)
s = s.replace(re_forimg, '')
s = s.replace(br, '')

let html = '<br>'
if (ls) {
ls.forEach((e) => {
html += '<a href="' + e + '" target="_blank" data-fancybox="group" class="fancybox"><img src="' + e + '"></a>'
})
}
s += html
return s
}