前言

在刚开始玩静态博客的时候,就对静态博客缺失这个功能很失落,现在终于是有机会将它加到自己的静态博客中了,当然这缺少不了qexo,还得是有个后端会好很多呀。

预览效果

效果如下:
点赞位页面编辑点赞位删除点赞位

创建后台界面

创建 templates\home\post_like.html 文件,代码内容如下:

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
{% extends 'layouts/base.html' %}

{% block content %}

<div class="header bg-primary pb-6">
<div class="container-fluid">
<div class="header-body">
<div class="row align-items-center py-4">
<div class="col-lg-6 col-7">
<h6 class="h2 text-white d-inline-block mb-0">点赞位列表</h6>
<nav aria-label="breadcrumb" class="d-none d-md-inline-block ml-md-4">
<ol class="breadcrumb breadcrumb-links breadcrumb-dark">
<li class="breadcrumb-item"><a href="/"><i class="fas fa-home"></i></a>
</li>
<li class="breadcrumb-item active" aria-current="page">点赞位</li>
</ol>
</nav>
</div>
<div class="col-lg-6 col-5 text-right">
<a href="/" class="btn btn-sm btn-neutral">主页</a>
<a href="javascript:query_new()" class="btn btn-sm btn-neutral">新建点赞位</a>
</div>
</div>
</div>
</div>
</div>

<!-- Page content -->
<div class="container-fluid mt--6">
<div class="row">
<div class="col">
<div class="card">
<div class="card-header border-0">
<div class="row align-items-center">
<div class="col">
<h3 class="mb-0">{% if search %}搜索点赞位: {{ search }}{% else %}
全部点赞位{% endif %}
(<span id="post-number">{{ post_number }}</span>条)</h3>
</div>
</div>
</div>
<div class="table-responsive">
<!-- Projects table -->
<table class="table align-items-center table-flush">
<thead class="thead-light">
<tr>
<th scope="col">文章名称</th>
<th scope="col">点赞数</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody id="posts-list">
</tbody>
</table>
<div class="card-footer py-4">
<ul class="pagination justify-content-end mb-0">
<li class="page-item" id="prev-page">
<a class="page-link"
href="javascript:prev_page()">
<i class="fas fa-angle-left"></i>
<span class="sr-only">上一页</span>
</a>
</li>
{% for i in page_number|get_range %}
<li class="page-item" id="page-{{ i }}">
<a class="page-link"
href="javascript:change_page({{ i }})">{{ i }}</a>
</li>
{% endfor %}

<li class="page-item" id="next-page">
<a class="page-link"
href="javascript:next_page()">
<i class="fas fa-angle-right"></i>
<span class="sr-only">Next</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="query" tabindex="-1" aria-labelledby="queryLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="queryLabel"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="query-modal-body">
</div>
<div class="modal-footer" id="query-modal-footer">
</div>
</div>
</div>
</div>
{% include "includes/footer.html" %}

</div>

{% endblock content %}

<!-- Specific JS goes HERE -->
{% block javascripts %}
<script>
var del_file = "";
var posts = {{ posts|safe }};
var _page = 1;

var compare = function (x, y) {
if (x["postName"] < y["postName"]) {
return -1;
} else if (x["postName"] > y["postName"]) {
return 1;
} else {
return 0;
}
}

function scrollToTop() {
let timer = null;
cancelAnimationFrame(timer);
timer = requestAnimationFrame(function fn() {
let oTop = document.body.scrollTop || document.documentElement.scrollTop;
if (oTop > 0) {
document.body.scrollTop = document.documentElement.scrollTop = oTop - 30;
timer = requestAnimationFrame(fn);
} else {
cancelAnimationFrame(timer);
}
});
}

function change_page(page) {
scrollToTop();
let page_posts;
if (posts.length > page * 15 + 1) {
page_posts = posts.slice(15 * (page - 1), page * 15);
} else {
page_posts = posts.slice(15 * (page - 1));
}
let list = "";
for (let i = 0; i < page_posts.length; i++) {
list += "<tr><th scope=\"row\">" + page_posts[i]["postName"] +
"</th><td>" + page_posts[i]["like"] + "</td><td> <a href=\"javascript:query_edit('" +
page_posts[i]["postName"] + "','" + page_posts[i]["postName"] + "')\"><i class=\"fa fa-edit mr-2\"></i></a>|&nbsp;&nbsp;" +
" <a href=\"javascript:query_delete('" + page_posts[i]["postName"] + "')\"><i " +
"class=\"fa fa-trash-alt mr-2\"></i></a></td></tr>";
}
$("#page-" + _page).removeClass("active");
$("#page-" + page).addClass("active");
$("#posts-list").html(list);
_page = page;
if (page <= 1) {
$("#prev-page").addClass("disabled");
} else {
$("#prev-page").removeClass("disabled");
}
if (page >={{ page_number }}) {
$("#next-page").addClass("disabled");
} else {
$("#next-page").removeClass("disabled");
}
}

function query_edit(name, content) {
let html = "<div class=\"col\"><label " +
"class=\"form-control-label\">" + "文章名称" + "</label><input type=\"text\" " +
"name=\"content\" id=\"edit-content\" " +
"class=\"form-control\" value=\"" + content.toString().replaceAll("\"", "&quot;") +"\"></div>";
$("#query-modal-body").html(html);
$("#queryLabel").html("编辑");
let footer = "<button type=\"button\" class=\"btn btn-secondary\" " +
"data-dismiss=\"modal\">取消</button><button type=\"button\" class=\"btn " +
"btn-primary\" data-dismiss=\"modal\" onclick=\"change_value('" + name +
"')\">确定</button>"
$("#query-modal-footer").html(footer);
$("#query").modal("show");
}

function query_delete(name) {
let html = "确认要删除 " + name + " 字段吗?该操作不可回退";
$("#query-modal-body").html(html);
$("#queryLabel").html("提示");
let footer = "<button type=\"button\" class=\"btn btn-secondary\" " +
"data-dismiss=\"modal\">取消</button><button type=\"button\" class=\"btn " +
"btn-primary\" data-dismiss=\"modal\" onclick=\"delete_value('" + name +
"')\">确定</button>"
$("#query-modal-footer").html(footer);
$("#query").modal("show");
}

function query_new() {
let html = "<div class=\"col\"><label " +
"class=\"form-control-label\">文章名称</label><input type=\"text\" " +
"name=\"name\" id=\"edit-name\" " +
"class=\"form-control\"></div>";
$("#query-modal-body").html(html);
$("#queryLabel").html("新建点赞位");
let footer = "<button type=\"button\" class=\"btn btn-secondary\" " +
"data-dismiss=\"modal\">取消</button><button type=\"button\" class=\"btn " +
"btn-primary\" data-dismiss=\"modal\" onclick=\"new_value()\">确定</button>"
$("#query-modal-footer").html(footer);
$("#query").modal("show");
}

function change_value(name) {
let loading = new KZ_Loading('正在保存中...');
loading.show();
let content = $("#edit-content").val();
console.log("name:", name);
console.log("content:", content);
$.ajax({
url: '/api/change_postlike/',
method: 'post',
data: {"postname": name, "content": content},
dataType: "json",
success: function (res) {
loading.destroy();
if (res.status) {
notyf.success(res.msg);
for (let i = 0; i < posts.length; i++) {
if (posts[i]["postName"] == name) {
posts[i]["postName"] = content;
change_page(_page);
break;
}
}
} else {
notyf.error(res.msg);
}
},
error: function (res) {
loading.destroy();
notyf.error("网络错误!");
}
})
}

function delete_value(name) {
let loading = new KZ_Loading('正在删除中...');
loading.show();
$.ajax({
url: '/api/delete_postlike/',
method: 'post',
data: {"postname": name},
dataType: "json",
success: function (res) {
loading.destroy();
if (res.status) {
notyf.success(res.msg);
for (let i = 0; i < posts.length; i++) {
if (posts[i]["postName"] === name) {
posts.splice(i, 1);
change_page(_page);
break;
}
}
$("#count").html(posts.length);
} else {
notyf.error(res.msg);
}
},
error: function (res) {
loading.destroy();
notyf.error("网络错误!");
}
})
}

function new_value() {
let postname = $("#edit-name").val();
let loading = new KZ_Loading('正在保存中...');
loading.show();
$.ajax({
url: '/api/save_postlike/',
method: 'post',
data: {"postname": postname},
dataType: "json",
success: function (res) {
loading.destroy();
if (res.status) {
notyf.success(res.msg);
posts.push({"postName": postname, "like": 0});
posts.sort(compare);
change_page(_page);
$("#count").html(posts.length);
} else {
notyf.error(res.msg);
}
},
error: function (res) {
loading.destroy();
notyf.error("网络错误!");
}
})
}

change_page(1);
$.ajaxSetup({
data: {csrfmiddlewaretoken: '{{ csrf_token }}'},
});
</script>
{% endblock javascripts %}

创建点赞的对公api

找到 hexoweb\pub.py 文件,在文件末尾添加如下代码:

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
# 设置文章点赞
@csrf_exempt
def set_postlike(request):
if request.method != "POST":
return

try:
postname = json.loads(request.body).get('postname')
postlikemodel = PostLikeModel.objects.filter(postName=postname).first()
if (not postlikemodel):
return JsonResponse(safe=False, data={"msg": "文章未创建点赞功能!", "status": False})
postlikemodel.like += 1
count = postlikemodel.like
postlikemodel.save()
except Exception as error:
return JsonResponse(safe=False, data={"msg": "点赞发生错误,请检查数据库或网络!", "status": False})
return JsonResponse(safe=False, data={"msg": count, "status": True})

# 获取文章点赞数
@csrf_exempt
def get_postlike(request):
if request.method != "POST":
return

try:
postname = json.loads(request.body).get('postname')
postlikemodel = PostLikeModel.objects.filter(postName=postname).first()
if (not postlikemodel):
return JsonResponse(safe=False, data={"msg": "文章未创建点赞功能!", "status": False})
count = postlikemodel.like
except Exception as error:
return JsonResponse(safe=False, data={"msg": "获取点赞数失败,请检查数据库或网络!", "status": True})
return JsonResponse(safe=False, data={"msg": count, "status": True})

前端静态博客只需要在点赞按钮上调用上面两个相应的接口即可,set_postlike接口是用来给按钮点赞的时候增加点赞数用的,get_postlike接口是给获取文章点赞数用的,都有注释标明。

创建点赞位的私有api

找到 hexoweb\api.py 文件,在文件末尾添加如下代码:

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
# 保存点赞位
@login_required(login_url="/login/")
def save_postlike(request):
if request.method != "POST":
return

postname = request.POST.get("postname")
postlike = PostLikeModel.objects.filter(postName=postname).first() # 查看是否已经订阅过
if (not postlike):
try:
PostLikeModel.objects.create(postName=postname)
return JsonResponse(safe=False, data={"msg":"OK,创建点赞位成功。", "status":True})
except Exception as error:
return JsonResponse(safe=False, data={"msg":"数据库插入记录失败。", "status":False})
return JsonResponse(safe=False, data={"msg":"点赞位已经存在", "status":False})

# 删除点赞位
@login_required(login_url="/login/")
def delete_postlike(request):
if request.method != "POST":
return

postname = request.POST.get("postname")
postlike = PostLikeModel.objects.filter(postName=postname).first() # 查看是否已经订阅过
if (postlike):
try:
PostLikeModel.objects.filter(postName=postname).delete()
return JsonResponse(safe=False, data={"msg":"OK,删除点赞位成功。", "status":True})
except Exception as error:
return JsonResponse(safe=False, data={"msg":"数据库删除记录失败。", "status":False})
return JsonResponse(safe=False, data={"msg":"点赞位不存在!", "status":False})

# 修改点赞位
@login_required(login_url="/login/")
def change_postlike(request):
if request.method != "POST":
return

postname = request.POST.get("postname")
content = request.POST.get("content")
postlike = PostLikeModel.objects.filter(postName=postname).first() # 查看是否已经订阅过
if (postlike):
try:
PostLikeModel.objects.filter(postName=postname).update(postName=content)
return JsonResponse(safe=False, data={"msg":"OK,修改点赞位成功。", "status":True})
except Exception as error:
return JsonResponse(safe=False, data={"msg":"数据库修改记录失败。", "status":False})
return JsonResponse(safe=False, data={"msg":"点赞位不存在!", "status":False})

配置接口路由

找到 core\urls.py 文件,做如下的代码修改:

1
2
3
4
5
6
7
8
9
10
path('api/del_talk/', del_talk, name='del_talk'),
path('api/run_online_script/', run_online_script, name='run_online_script'),
+path('api/save_postlike/', save_postlike, name="save_postlike"),
+path('api/delete_postlike/', delete_postlike, name="delete_postlike"),
+path('api/change_postlike/', change_postlike, name="change_postlike"),
... #省略中间的代码
path('pub/save_talk/', pub.save_talk, name='pub_save_talk'),
path('pub/del_talk/', pub.del_talk, name='pub_del_talk'),
+path('pub/set_postlike/', pub.set_postlike, name='pub_set_postlike'),
+path('pub/get_postlike/', pub.get_postlike, name='pub_get_postlike'),

创建后台页面

找到 hexoweb\views.py 文件,添加如下代码:

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
elif "configs" in load_template:
if not request.user.is_staff:
logging.info(f"子用户{request.user.username}尝试访问{request.path}被拒绝")
return page_403(request, "您没有权限访问此页面")
search = request.GET.get("s")
if search:
cache = Cache.objects.filter(name="configs." + search)
if cache.count():
posts = json.loads(cache.first().content)
else:
posts = update_configs_cache(search)
else:
cache = Cache.objects.filter(name="configs")
if cache.count():
posts = json.loads(cache.first().content)
else:
posts = update_configs_cache(search)
context["posts"] = posts
context["post_number"] = len(posts)
context["page_number"] = ceil(context["post_number"] / 15)
context["search"] = search
+elif "post_like" in load_template:
+ posts = []
+ postlike = PostLikeModel.objects.all(
+ for i in postlike:
+ posts.append({"postName": i.postName,
+ "like": i.like,
+ }
+ context["posts"] = posts
+ context["post_number"] = len(posts)
+ context["page_number"] = ceil(context["post_number"] / 15)
elif "talks" in load_template:
search = request.GET.get("s")
posts = []
if search:
talks = TalkModel.objects.filter(content__contains=search)
for i in talks:
t = json.loads(i.like)

创建存储点赞数的数据表

找到 hexoweb\models.py 文件,在文件末尾添加如下代码:

1
2
3
class PostLikeModel(models.Model):
postName=models.CharField(primary_key=True, max_length=50, verbose_name='文章名称')
like=models.IntegerField(default=0)

Qexo四连

在cmd命令行中输入如下命令运行部署Qexo项目:

1
2
3
4
E:\Qexo> pip3 install -r requirements.txt
E:\Qexo> python3 manage.py makemigrations
E:\Qexo> python3 manage.py migrate
E:\Qexo> python3 manage.py runserver --noreload

用法

前端点赞按钮:完成后端的功能开发后,小伙伴们就可以去前端添加按钮并调用接口尝试啦。
后端创建点赞位:创建点赞位时,填写文章url最后一个斜杠后面的字符串即可,或者你可以自行定义一个字符串,但必须前端也知道这个字符串,并使用这个字符串请求后端的接口即可。