初始化中...
本篇文章是关于Qexo魔改教程的内容,主要的讲解如何给文章页面添加一个点赞按钮,并且实现这个点赞的功能,还可以在Qexo后台中控制文章的点赞位是否添加,如果不给文章添加点赞位,那么该篇文章就没有点赞的功能,并且还可以在Qexo中查看所有文章的点赞数量,方便管理。
介绍自己
生成本文简介
推荐相关文章
AI实时简介
系列文章
前言
在刚开始玩静态博客的时候,就对静态博客缺失这个功能很失落,现在终于是有机会将它加到自己的静态博客中了,当然这缺少不了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>
<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"> <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">×</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 %}
{% 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>| " + " <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("\"", """) +"\"></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最后一个斜杠后面的字符串即可,或者你可以自行定义一个字符串,但必须前端也知道这个字符串,并使用这个字符串请求后端的接口即可。