文章点赞功能(Ajax)
一、文章点赞样式构建
1、将base.html的css样式改为外部引入
将base.html的内嵌样式删除,改为使用 HTML 头部的 <head>
标签对中使用<link>标签来引入外部的 CSS 文件。
base.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 | <! DOCTYPE html> < html lang="en"> < head > < meta charset="UTF-8"> < title >Title</ title > <!-- 引入 Bootstrap 核心 CSS 文件 --> < link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css"> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) --> < script src="/static/js/jquery-3.3.1.js"></ script > <!-- 引入 Bootstrap 核心 JavaScript 文件 --> < script src="/static/blog/bootstrap-3.3.7/js/bootstrap.js"></ script > <!--依赖jquery--> < link rel="stylesheet" href="/static/blog/css/home_site.css"> < link rel="stylesheet" href="/static/blog/css/article_detail.css"> </ head > < body > < div class="header"> < div class="content"> <!--站点标题--> < p class="title"> < span >{{ blog.title }}</ span > < a href="" class="backend">管理</ a > </ p > </ div > </ div > < div class="container"> < div class="row"> < div class="col-md-3"> <!--添加bootstrap面板--> {% load my_tags %} {% get_classification_style username %} </ div > < div class="col-md-9"> {% block content %} {% endblock %} </ div > </ div > </ div > </ body > </ html > |
个人站点的样式——home_site.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 | * { margin : 0 ; padding : 0 ; } .header { width : 100% ; height : 60px ; background-color : #369 ; } .header .title { font-size : 18px ; /* 字体大小 */ font-weight : 100 ; /* 字体粗细 */ line-height : 60px ; /* 行高与页头一致,完成居中 */ color : white ; margin-left : 15px ; margin-top : -10px ; } .backend { float : right ; /* 浮动到右边 */ color : white ; text-decoration : none ; /* 去除下划线 */ font-size : 16px ; margin-right : 12px ; margin-top : 10px ; } .pub_info { margin-top : 10px ; color : darkgray; } .menu { margin-top : 20px ; } |
文章详情页的样式——article_detail.css:
1 2 3 | .article_info .title { margin-bottom : 20px ; } |
上述css代码是将标题部分和文字主体部分错开20像素。
2、构建点赞样式
根据博客园代码,在article_detail.html引入文章推荐踩灭:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | {% extends "base.html" %} {% block content %} < h3 class="text-center">{{ article_obj.title }}</ h3 > < div class="cont"> {{ article_obj.content|safe }} </ div > {# 文章点赞 #} < div id="div_digg"> < div class="diggit"> < span class="diggnum" id="digg_count">1</ span > </ div > < div class="buryit"> < span class="diggnum" id="bury_count">0</ span > </ div > < div class="clear"></ div > < div class="diggword" id="digg_tips" style="color: red;"></ div > </ div > {% endblock %} |
将点赞的css样式写入article_detail.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 | .article_info .title { margin-bottom : 20px ; } #div_digg { float : right ; margin-bottom : 10px ; margin-right : 30px ; font-size : 12px ; width : 125px ; text-align : center ; margin-top : 10px ; } /* 推荐 */ .diggit { float : left ; width : 46px ; height : 52px ; background : url ( '/static/font/upup.gif' ) no-repeat ; text-align : center ; cursor : pointer ; margin-top : 2px ; padding-top : 5px ; } /* 反对 */ .buryit { float : right ; margin-left : 20px ; width : 46px ; height : 52px ; background : url ( '/static/font/downdown.gif' ) no-repeat ; text-align : center ; cursor : pointer ; margin-top : 2px ; padding-top : 5px ; } .clear { clear : both ; /* 清除浮动,解决塌陷问题 */ } |
显示效果:
二、文章点赞事件绑定 (Ajax)
给推荐和反对的这两个标签绑定事件,一点击就发送ajax请求。
另外查看blog_articleupdown表:
其中is_up字段是存的是一个布尔值。点击推荐则为True,点击反对则为False.在这里将两个标签合在一个事件中,仅仅是做判断点击的是哪个标签。注意文章点赞的script代码应写在article_detail.html模板中。
由于这两个标签有不同的类,一个包含diggit,一个包含buryit。因此只需要判断点击的标签class名就可以,article_detail.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 | {% extends "base.html" %} {% block content %} <h3 class = "text-center" >{{ article_obj.title }}</h3> < div class = "cont" > {{ article_obj.content|safe }} </ div > {# 文章点赞 #} < div id= "div_digg" > {# 推荐 #} < div class = "diggit action" > <span class = "diggnum" id= "digg_count" >1</span> </ div > {# 点灭 #} < div class = "buryit action" > <span class = "diggnum" id= "bury_count" >0</span> </ div > < div class = "clear" ></ div > < div class = "diggword" id= "digg_tips" style= "color: red;" ></ div > </ div > <script> $( '#div_digg .action' ).click(function () { var is_up = $( this ).hasClass( "diggit" ); alert(is_up); }) </script> {% endblock %} |
三、文章点赞保存
1、在article_detail.html模板初步构建ajax请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <script> $( '#div_digg .action' ).click( function () { var is_up = $( this ).hasClass( "diggit" ); $.ajax({ url: "/digg/" , type: "post" , data: { 'csrfmiddlewaretoken' : $( "[name= 'csrfmiddlewaretoken']" ).val(), "is_up" : is_up, "article_id" : "{{ article_obj.pk }}" , }, success: function (data) { console.log(data); } }) }) </script> |
需要注意:文章点赞人、评论人都应是当前登录人。因此这里不需要传入user_id到ajax中。
由于这里是post请求,因此需要添加csrf_token避免forbidden错误,因此需要构建一条新路由:
1 2 3 4 5 | urlpatterns = [ path( 'admin/' , admin.site.urls), ... path( 'digg/' , views.digg), # 点赞<br> ... ] |
创建点赞视图函数:
1 2 3 4 5 6 7 8 9 | def digg(request): """ 点赞视图函数 :param request: :return: """ print (request.POST) return HttpResponse( "OK" ) |
访问页面,点击推荐,视图函数request.POST输出:
1 | <QueryDict: { 'csrfmiddlewaretoken' : [ 'hBlBWfxGFhDXaqDCfkSMFhKd6ZhZsbuqM8TEj3upzwe2NynenybodHgQyFHQAvZ0' ], 'is_up' : [ 'true' ], 'article_id' : [ '1' ]}> |
同时页面控制台输出:OK。
2、生成赞记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import json def digg(request): """ 点赞视图函数 :param request: :return: """ print (request.POST) # <QueryDict: {'csrfmiddlewaretoken': ['hBlBWfxGFhDXaqDCfkSMFhKd6ZhZsbuqM8TEj3upzwe2NynenybodHgQyFHQAvZ0'], # 'is_up': ['true'], 'article_id': ['1']}> article_id = request.POST.get( "article_id" ) # is_up = request.POST.get("is_up") # 拿到的是一个字符串 "true" is_up = json.loads(request.POST.get( "is_up" )) # 反序列化,拿到一个bool值 # 点赞人即当前登录人 user_id = request.user.pk # 创建一条新记录 ard = models.ArticleUpDown.objects.create(user_id = user_id, article_id = article_id, is_up = is_up) return HttpResponse( "OK" ) |
在这里需要注意:
(1)生成一条赞记录根据models中的类:
因此拿到article_id、user_id、is_up这三条就可以生成一个新记录。
(2)request.POST.get("is_up") 拿到的结果是一个字符串。因为它在js中虽然也是bool值,但是在传递时,没有设置ContentType,默认使用urlencoded编码,在组装键值的时候,is_up = True就按照字符串发送出去了。因此需要json来进行反序列化。
(3)点击赞后,查看blog_articleupdwon表记录,确认新记录是否生成:
四、文章点赞数的数据同步
生成一条赞记录,就应该把赞记录对应的文章up_count+1,如果是踩灭,则将down_count+1;保持这种同步。
涉及到up_count自加一,需要用到Django的F函数。
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 | import json from django.db.models import F # F函数 def digg(request): """ 点赞视图函数 :param request: :return: """ print (request.POST) # <QueryDict: {'csrfmiddlewaretoken': ['hBlBWfxGFhDXaqDCfkSMFhKd6ZhZsbuqM8TEj3upzwe2NynenybodHgQyFHQAvZ0'], # 'is_up': ['true'], 'article_id': ['1']}> article_id = request.POST.get( "article_id" ) # is_up = request.POST.get("is_up") # 拿到的是一个字符串 "true" is_up = json.loads(request.POST.get( "is_up" )) # 反序列化,拿到一个bool值 # 点赞人即当前登录人 user_id = request.user.pk # 创建一条新记录 ard = models.ArticleUpDown.objects.create(user_id = user_id, article_id = article_id, is_up = is_up) queryset = models.Article.objects. filter (pk = article_id) if is_up: queryset.update(up_count = F( "up_count" ) + 1 ) else : queryset.update(up_count = F( "down_count" ) + 1 ) return HttpResponse( "OK" ) |
工作原理是,直接在数据库中查出数据,计数后更改数据库。点赞后刷新页面,点赞次数已经更新:
五、点赞提示重复操作
博客园文章点赞规则:用户对一篇文章点赞后,不允许再对文章进行点赞或踩灭操作。
1、根据用户在点赞是否有记录进行判断
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 | import json from django.http import JsonResponse from django.db.models import F # F函数 def digg(request): """ 点赞视图函数 :param request: :return: """ print (request.POST) # <QueryDict: {'csrfmiddlewaretoken': ['hBlBWfxGFhDXaqDCfkSMFhKd6ZhZsbuqM8TEj3upzwe2NynenybodHgQyFHQAvZ0'], # 'is_up': ['true'], 'article_id': ['1']}> article_id = request.POST.get( "article_id" ) # is_up = request.POST.get("is_up") # 拿到的是一个字符串 "true" is_up = json.loads(request.POST.get( "is_up" )) # 反序列化,拿到一个bool值 # 点赞人即当前登录人 user_id = request.user.pk # 用户只要在点赞表存有记录就不能再存了 obj = models.ArticleUpDown.objects. filter (user_id = user_id, article_id = article_id).first() response = { "state" : True } if not obj: # 创建一条新记录 ard = models.ArticleUpDown.objects.create(user_id = user_id, article_id = article_id, is_up = is_up) queryset = models.Article.objects. filter (pk = article_id) if is_up: queryset.update(up_count = F( "up_count" ) + 1 ) else : queryset.update(down_count = F( "down_count" ) + 1 ) else : # 重复点赞提示,告诉ajax已经推荐过了 response[ "state" ] = False response[ "handled" ] = obj.is_up # True:推荐过了, Flase: 踩过了 return JsonResponse(response) # 返回response字典 |
注意:
(1)通过models.ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first(),拿到当前用户在点赞表中针对该文章的点赞记录。if not obj则可以判定没有点赞过,可以正常操作。
(2)提前创建response字典,将需要传递给ajax的信息放入其中。然后引入JsonResponse传递字典。
2、在ajax中处理重复错误信息
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 | <script> $( '#div_digg .action' ).click( function () { var is_up = $( this ).hasClass( "diggit" ); $.ajax({ url: "/digg/" , type: "post" , data: { 'csrfmiddlewaretoken' : $( "[name= 'csrfmiddlewaretoken']" ).val(), "is_up" : is_up, "article_id" : "{{ article_obj.pk }}" }, success: function (data) { console.log(data); if (data.state) { } else { if (data.handled) { $( "#digg_tips" ).html( "您已经推荐过!" ) } else { $( "#digg_tips" ).html( "您已经反对过!" ) } setTimeout( function () { $( "#digg_tips" ).html( "" ) }, 1000) } }, }) }) </script> |
注意:
(1)在data.state为false时,判断data.handled字典是True/False,在id="digg_tips"标签添加对应的内容。
(2)运用setTimeout函数,在提示消息显示1秒后,自动隐藏。
(3)显示效果如下:
六、点赞数ajax更新
修改article_detail.html内script代码:
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 | <script> $( '#div_digg .action' ).click( function () { var is_up = $( this ).hasClass( "diggit" ); $.ajax({ url: "/digg/" , type: "post" , data: { 'csrfmiddlewaretoken' : $( "[name= 'csrfmiddlewaretoken']" ).val(), "is_up" : is_up, "article_id" : "{{ article_obj.pk }}" }, success: function (data) { console.log(data); if (data.state) { if (is_up){ var val = parseInt($( "#digg_count" ).text()); // parseInt() 函数可解析一个字符串,并返回一个整数。 $( "#digg_count" ).text(val+1); } else { var val = parseInt($( "#bury_count" ).text()); $( "#bury_count" ).text(val+1); } } else { if (data.handled) { $( "#digg_tips" ).html( "您已经推荐过!" ) } else { $( "#digg_tips" ).html( "您已经反对过!" ) } setTimeout( function () { $( "#digg_tips" ).html( "" ) }, 1000) } }, }) }) </script> |
这样在点击推荐或踩灭后,不用刷新页面,第一时间就显示了数字加1。
七、代码优化
主要针对article_detail.html中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 | {% extends "base.html" %} {% block content %} {% csrf_token %} <h3 class = "text-center" >{{ article_obj.title }}</h3> <div class = "cont" > {{ article_obj.content|safe }} </div> { # 文章点赞 #} <div id= "div_digg" > { # 推荐 #} <div class = "diggit action" > <span class = "diggnum" id= "digg_count" >{{ article_obj.up_count }}</span> </div> { # 点灭 #} <div class = "buryit action" > <span class = "diggnum" id= "bury_count" >{{ article_obj.down_count }}</span> </div> <div class = "clear" ></div> <div class = "diggword" id= "digg_tips" style= "color: red;" ></div> </div> <script> $( '#div_digg .action' ).click( function () { var is_up = $( this ).hasClass( "diggit" ); $obj = $( this ).children( "span" ); $.ajax({ url: "/digg/" , type: "post" , data: { 'csrfmiddlewaretoken' : $( "[name= 'csrfmiddlewaretoken']" ).val(), "is_up" : is_up, "article_id" : "{{ article_obj.pk }}" }, success: function (data) { console.log(data); if (data.state) { var val = parseInt($obj.text()); // parseInt() 函数可解析一个字符串,并返回一个整数。 $obj.text(val+1); } else { // 三元表达式 var val = data.handled? "您已经推荐过!" : "您已经反对过!" ; $( "#digg_tips" ).html(val); setTimeout( function () { $( "#digg_tips" ).html( "" ) }, 1000) } }, }) }) </script> {% endblock %} |
注意:
(1)(this).children("span"); 变量定义,取消了is_up的判断。
(2)应用三元表达式true对应已经推荐过,false对应已经反对过
1 2 | // 三元表达式 var val = data.handled? "您已经推荐过!" : "您已经反对过!" ; |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术