安之鱼主题在文章底部添加文章点赞按钮

今天给安知鱼主题的文章添加一个点赞按钮,主要是用来为文章点赞,让浏览者参与文章的评优,很多网站程序都有顶踩功能,算是添加一个小功能。

1.必须的JavaScript代码

点赞按钮的 JavaScript 样式,需要在 /themes/anzhiyu/source/js/ 文件夹下新建 zan.js 文件,或者整合到其他 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
// pjax适配
document.addEventListener("DOMContentLoaded", () => {
ctrl.getIp();
ctrl.refreshLikeCount();
}); //第一次加载

document.addEventListener("pjax:complete", () => {
ctrl.refreshLikeCount();
}) // pjax加载完成(切换页面)后再执行一次

var ipAddress = '';

var ctrl = {
getIp() {
fetch('https://www.hexoblog.cn/likecount/index.php?mode=getip')
.then(response => response.json())
.then(data => {
ipAddress = data.ip;
console.log('您的 IP 地址:' + ipAddress);
})
.catch(error => {
console.error('获取 IP 地址失败:', error);
});
},

refreshLikeCount() {
var p = window.location.pathname
var q = p.substring(1,9)
if (q == 'articles') {
var i = p.substring(10,18)
fetch(`https://www.hexoblog.cn/likecount/index.php?mode=get&id=${i}`)
.then(response => response.json())
.then(data => {
if (data.code == 200) {
var likeCount = data.content[0].count
document.querySelector("#article-like-box .like-count").innerText = likeCount
} else console.log(data.message)
})
.catch(error => {
console.error('获取点赞信息失败:', error)
})
}
},

sendArticleLike() {
var a = document.querySelector("#article-like-box .like-button")
var i = window.location.pathname.substring(10,18)
a.classList.add("loading")
fetch(`https://www.hexoblog.cn/likecount/index.php?mode=add&id=${i}&ip=${ipAddress}`)
.then(response => response.json())
.then(data => {
if (data.code == 200) {
var likeCount = data.content[0].count
a.querySelector(".like-count").innerText = likeCount
a.classList.remove("loading")
tools.showMessage("感谢您的认可!", "success", 2)
} else if(data.code == 205) {
a.classList.remove("loading")
tools.showMessage(data.message, "warning", 2)
} else {
a.classList.remove("loading")
console.log(data.message)
tools.showMessage(data.message, "error", 2)
}
})
.catch(error => {
console.error('获取点赞信息失败:', error)
a.classList.remove("loading")
})
}
}

// 弹窗消息
window.tools = window.tools || {};
window.tools.showMessage = function(text, type, duration) {
const oldMsg = document.querySelector('#article-like-box .custom-like-message');
if (oldMsg) oldMsg.remove();

const likeBtn = document.querySelector("#article-like-box");
if (!likeBtn) return;

const msgBox = document.createElement('div');
msgBox.className = 'custom-like-message';
msgBox.innerText = text;

const typeStyles = {
success: { background: 'rgba(67, 181, 129, 0.95)', color: '#fff' },
warning: { background: 'rgba(250, 173, 20, 0.95)', color: '#fff' },
error: { background: 'rgba(235, 87, 87, 0.95)', color: '#fff' }
};
const style = typeStyles[type] || typeStyles.success;
msgBox.style.background = style.background;
msgBox.style.color = style.color;

likeBtn.appendChild(msgBox);

setTimeout(() => {
msgBox.classList.add('show');
}, 10);

setTimeout(() => {
msgBox.classList.remove('show');
setTimeout(() => {
msgBox.remove();
}, 300);
}, (duration || 2) * 1000);
};

document.addEventListener('DOMContentLoaded', bindLikeBtn);
document.addEventListener('pjax:complete', bindLikeBtn);

function bindLikeBtn() {
const likeBtn = document.querySelector(".post-like .like-button");
if (likeBtn) {
likeBtn.style.position = 'relative';
likeBtn.onclick = () => ctrl.sendArticleLike();
}
}

1.1.需要结合容器id

上面的代码,需要结合下面 CSS 的容器标识来使用,例如

1
document.querySelector("#article-like-box .like-count").innerText = likeCount
1
var a = document.querySelector("#article-like-box .like-button")
1
a.querySelector(".like-count").innerText = likeCount

消息弹窗效果中也有类似的代码,这一点儿大家要注意。

1.2.需要结合文章链接特征

因为我的文章链接,都是:articles/3f9e592b.html 的形式,所以截取字符的设置如下:

1
2
3
var q = p.substring(1,9)
if (q == 'articles') {
var i = p.substring(10,18)

同时还需要修改

1
var i = window.location.pathname.substring(10,18)

大家可以研究一下下面的索引:

a r t i c l e s / 3 f 9 e 5 9 2 b . h t m l
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

2.必须的 CSS 样式代码

点赞按钮的 CSS 样式,需要在 /themes/anzhiyu/source/css/ 文件夹下新建 zan.css 或者使用默认的 custom.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
.post-like#article-like-box {
width: 100% !important;
max-width: 180px !important;
display: block !important;
position: relative;
text-align: center;
margin-top: 0rem;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-pack: center;
-moz-box-pack: center;
-o-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
}
.post-like#article-like-box .like-button {
border-radius: 8px;
background: var(--anzhiyu-red);
color: var(--anzhiyu-white);
padding: 0;
margin-right: 0.5rem;
width: 126px;
height: 40px;
line-height: 39px;
-webkit-box-shadow: var(--anzhiyu-shadow-red);
box-shadow: var(--anzhiyu-shadow-red);
display: inline-block;
cursor: pointer;
-webkit-transition: all 0.4s ease 0s;
-moz-transition: all 0.4s ease 0s;
-o-transition: all 0.4s ease 0s;
-ms-transition: all 0.4s ease 0s;
transition: all 0.4s ease 0s;
}
.post-like#article-like-box .like-button:hover {
background: var(--anzhiyu-main) !important;
transform: translateY(-2px) !important;
}
.post-like#article-like-box .like-button.loading {
cursor: not-allowed !important;
pointer-events: none !important;
opacity: 0.8 !important;
}
.post-like#article-like-box .like-button.loading .like-count {
display: none !important;
}
.post-like#article-like-box .like-button.loading .load {
display: inline-block !important;
}
.post-like#article-like-box .like-count,
.post-like#article-like-box .load {
width: 20px !important;
display: inline-block !important;
text-align: center !important;
}
.post-like#article-like-box .load {
display: none !important;
}

/* 赞赏样式:原代码完全不动,无任何修改 */
.post-reward {
width: 100% !important;
max-width: 180px !important;
display: block !important;
}
@media (max-width: 767.98px) {
.post-reward .reward-main {
position: absolute;
bottom: 40px;
left: -120% !important;
z-index: 90;
display: none;
padding: 0px 0px 15px;
width: 100%;
}
}

/* 点赞弹窗样式:新增ID关联,定位更精准 */
#article-like-box .custom-like-message {
position: absolute;
left: 50%;
bottom: calc(100% + 10px); /* 点赞按钮正上方10px */
transform: translateX(-50%) translateY(10px);
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
z-index: 9999;
opacity: 0;
transition: all 0.3s ease;
pointer-events: none;
white-space: nowrap;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
#article-like-box .custom-like-message.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* 深色模式适配:仅针对点赞弹窗 */
[data-theme="dark"] #article-like-box .custom-like-message {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
border: 1px solid rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] #article-like-box .custom-like-message[style*="background: rgba(67, 181, 129, 0.95)"] {
background: rgba(52, 168, 83, 0.9) !important;
}
[data-theme="dark"] #article-like-box .custom-like-message[style*="background: rgba(250, 173, 20, 0.95)"] {
background: rgba(251, 191, 36, 0.9) !important;
}
[data-theme="dark"] #article-like-box .custom-like-message[style*="background: rgba(235, 87, 87, 0.95)"] {
background: rgba(239, 68, 68, 0.9) !important;
}

最后的响应式代码,主要是为了纠正手机端赞赏二维码错位被遮挡。

3.主题 pug 模板文件

安知鱼主题的点赞按钮的模板文件,默认路径为: /themes/anzhiyu/layout/includes/post/ 文件夹下面的 reward.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
.post-like#article-like-box
.like-button.button--animated(onclick='ctrl.sendArticleLike()' title='点赞文章')
i.anzhiyufont.anzhiyu-icon-thumbs-up
=_p('点个赞')
span.like-count= '0'
span.load
i.fas.fa-spinner.fa-spin

.post-reward(onclick='anzhiyu.addRewardMask()')
.reward-button.button--animated(title=_p('reward.title'))
i.anzhiyufont.anzhiyu-icon-hand-heart-fill
=_p('reward.button')
.reward-main
.reward-all
span.reward-title=_p('reward.thanks')
ul.reward-group
each item in theme.reward.QR_code
- var clickTo = item.link ? item.link : item.img
li.reward-item
a(href=url_for(clickTo) target='_blank')
img.post-qr-code-img(src=url_for(item.img) alt=item.text)
.post-qr-code-desc=item.text
a.reward-main-btn(href='/about/#about-reward' target='_blank')
.reward-text=_p('reward.list')
.reward-dec=_p('reward.list_desc')
#quit-box(onclick="anzhiyu.removeRewardMask()" style="display: none")

4.数据库表的创建

因为点赞的信息需要写入数据库,所以需要提前设置好数据库表,注意是数据库表,先新建数据库,在创建数据库表,用来存储获取到的信息,下面的数据库代码需要在数据库里面的 SQL 里面去执行,我使用的 MySQL 数据库,其他数据库对应创建就可以。

4.1.MySQL

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS `article_likes` (
`article_id` VARCHAR(64) NOT NULL COMMENT '文章唯一ID',
`like_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '点赞数(无符号,避免负数)',
`ip_list` TEXT NOT NULL COMMENT '点赞IP列表(逗号分隔)',
`update_time` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后更新时间戳',
PRIMARY KEY (`article_id`),
KEY `idx_update_time` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章点赞表(稳定版)';

4.2.PostgreSQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- PostgreSQL 文章点赞表(兼容9.5+,可直接复制粘贴执行)
CREATE TABLE IF NOT EXISTS article_likes (
article_id VARCHAR(64) NOT NULL, -- 文章唯一ID
like_count INT4 NOT NULL DEFAULT 0 CHECK (like_count >= 0), -- 点赞数(无符号)
ip_list TEXT NOT NULL, -- 点赞IP列表(逗号分隔)
update_time INT4 NOT NULL DEFAULT 0, -- 最后更新时间戳
PRIMARY KEY (article_id)
)
WITH (OIDS = FALSE)
TABLESPACE pg_default;

-- 添加字段注释(PostgreSQL 9.5+ 支持)
COMMENT ON COLUMN article_likes.article_id IS '文章唯一ID';
COMMENT ON COLUMN article_likes.like_count IS '点赞数(无符号,避免负数)';
COMMENT ON COLUMN article_likes.ip_list IS '点赞IP列表(逗号分隔)';
COMMENT ON COLUMN article_likes.update_time IS '最后更新时间戳';

-- 添加表注释
COMMENT ON TABLE article_likes IS '文章点赞表(稳定版)';

-- 单独创建索引(PostgreSQL 标准写法,避免语法报错)
CREATE INDEX IF NOT EXISTS idx_update_time ON article_likes (update_time);

5.必须的 PHP 文件

在网站根目录新建 likecount 文件夹,在这个文件夹里面新建 index.php 文件,复制粘贴下面的代码,

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
<?php
/**
* 点赞接口 - 极简稳定版(适配虚拟主机,无需赋权)
* 核心:兼容低权限数据库账号,点赞数持久化,刷新不消失
*/
header("Content-Type: application/json; charset=utf-8");
header("Access-Control-Allow-Origin: *");
error_reporting(E_ALL & ~E_NOTICE);
ini_set('display_errors', 0);

// 数据库配置(修改成你的数据库信息,必须改动)
$db_config = [
'host' => 'localhost',
'user' => 'sery2fct1p4bq',
'pass' => '46qhn44c0hjr',
'dbname' => 'sery2fct1p4bq',
'charset' => 'utf8mb4'
];

// 兼容str_contains函数(适配低版本PHP)
if (!function_exists('str_contains')) {
function str_contains($h, $n) {
return $n === '' ? true : (strpos($h, $n) !== false);
}
}

// 数据库连接(极简写法,兼容低权限)
$conn = mysqli_connect(
$db_config['host'],
$db_config['user'],
$db_config['pass'],
$db_config['dbname']
);
if (!$conn) {
echo json_encode(["code" => 500, "message" => "数据库连接失败"]);
exit;
}
mysqli_set_charset($conn, $db_config['charset']);

// 获取参数(极简过滤)
$mode = trim($_GET['mode'] ?? '');
$id = trim($_GET['id'] ?? '');
$ip = trim($_GET['ip'] ?? '');
$id = mysqli_real_escape_string($conn, $id);
$ip = mysqli_real_escape_string($conn, $ip);

// 获取IP接口(保留核心逻辑)
if ($mode === 'getip') {
$real_ip = $_SERVER['REMOTE_ADDR'];
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip_list = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$real_ip = trim($ip_list[0]);
}
echo json_encode(["code" => 200, "ip" => filter_var($real_ip, FILTER_VALIDATE_IP) ?: 'unknown']);
exit;
}

// 基础校验
if (empty($id)) {
echo json_encode(["code" => 400, "message" => "文章ID不能为空"]);
exit;
}

// 获取点赞数(适配article_likes表)
if ($mode === 'get') {
$sql = "SELECT `like_count` FROM `article_likes` WHERE `article_id` = '$id' LIMIT 1";
$res = mysqli_query($conn, $sql);
$count = 0;
if ($res && mysqli_num_rows($res) > 0) {
$row = mysqli_fetch_assoc($res);
$count = (int)$row['like_count'];
}
echo json_encode(["code" => 200, "content" => [["count" => $count]]]);
mysqli_free_result($res);
exit;
}

// 添加点赞(核心:低权限兼容写法)
if ($mode === 'add') {
if (empty($ip)) {
echo json_encode(["code" => 400, "message" => "IP地址不能为空"]);
exit;
}

// 检查记录(极简写法)
$sql_check = "SELECT `ip_list`, `like_count` FROM `article_likes` WHERE `article_id` = '$id' LIMIT 1";
$res_check = mysqli_query($conn, $sql_check);
$ips = '';
$current_count = 0;

if ($res_check && mysqli_num_rows($res_check) > 0) {
$row = mysqli_fetch_assoc($res_check);
$ips = $row['ip_list'];
$current_count = (int)$row['like_count'];
} else {
// 新增记录(虚拟主机兼容写法)
$sql_insert = "INSERT INTO `article_likes` (`article_id`, `like_count`, `ip_list`, `update_time`) VALUES ('$id', 0, '', " . time() . ")";
mysqli_query($conn, $sql_insert);
}

// IP重复判断(核心逻辑不变)
if (str_contains($ips, $ip)) {
echo json_encode(["code" => 205, "message" => "您已经点过赞啦!"]);
mysqli_free_result($res_check);
exit;
}

// 更新点赞数(确保写入数据库)
$new_ips = empty($ips) ? $ip : $ips . ',' . $ip;
$new_count = $current_count + 1;
$sql_update = "UPDATE `article_likes` SET `like_count` = $new_count, `ip_list` = '$new_ips', `update_time` = " . time() . " WHERE `article_id` = '$id'";
mysqli_query($conn, $sql_update);

// 返回结果(JS无需修改)
echo json_encode(["code" => 200, "content" => [["count" => $new_count]]]);
mysqli_free_result($res_check);
exit;
}

// 无效请求
echo json_encode(["code" => 400, "message" => "无效的请求模式"]);
mysqli_close($conn);
?>

大家也可以借助 ai 来进行修改,制作符合自己网站的 PHP 文件和 SQL 数据库

6.引入 CSS 和 JS 文件

在安知鱼主题的配置文件中搜索 inject ,就会找到如下代码

1
2
3
4
5
6
7
8
9
10
11
# Inject
# Insert the code to head (before '</head>' tag) and the bottom (before '</body>' tag)
# 插入代码到头部 </head> 之前 和 底部 </body> 之前
inject:
head:
# 自定义css
# - <link rel="stylesheet" href="/css/custom.css" media="defer" onload="this.media='all'">

bottom:
# 自定义js
# - <script src="/js/xxx"></script>

head 处进行自定义 CSS 的引入工作

1
- <link rel="stylesheet" href="/css/zan.css" media="defer" onload="this.media='all'">

bottom 处进行自定义 js 的引入工作:

1
- <script src="/js/zan.js"></script>

修改完之后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
# Inject
# Insert the code to head (before '</head>' tag) and the bottom (before '</body>' tag)
# 插入代码到头部 </head> 之前 和 底部 </body> 之前
inject:
head:
# 自定义css
# - <link rel="stylesheet" href="/css/custom.css" media="defer" onload="this.media='all'">
- <link rel="stylesheet" href="/css/zan.css" media="defer" onload="this.media='all'">

bottom:
# 自定义js
# - <script src="/js/xxx"></script>
- <script src="/js/zan.js"></script>

7.检查效果生成

最后 Hexo 三连(hexo c && hexo g && hexo s)就可以看到文章中截图所示的效果。