文章显示列表这里采用动态加载的方法,文章回复采用二级评论的方法来实现。
news/urls.py
from django.urls import pathfrom news import viewsapp_name = "news"urlpatterns = [ path("article_list/", views.ArticleView.as_view(), name="article_list"),]
news/views.py
import loggingfrom django.views import Viewfrom django.shortcuts import renderfrom django.core.paginator import Paginator, EmptyPage, PageNotAnIntegerfrom news import contains # 上一章已有from news import models as _modelsfrom utils.res_code.json_function import to_json_datalogger = logging.getLogger("django")class ArticleView(View): def get(self, request): # 1. 获取前端传来的数据文章类型id try: tag_id = int(request.GET.get("tag_id", 0)) except Exception as e: logger.error("文章标签id参数错误:{}".format(e)) tag_id = 0 # 2. 获取前端传来的数据页码编号 page_num try: page_num = int(request.GET.get("page", 1)) except Exception as e: logger.error("文章页码参数错误:{}".format(e)) page_num = 1 # 3. 从数据库中获取文章表签id为tag_id的数据 article_queryset_total = _models.Articles.objects.select_related("author", "tag").only("id", "title", "digest", "update_time", "clicks","image_url", "author__username", "tag__name").filter(is_delete=False) article_tag_queryset = article_queryset_total.filter(is_delete=False, tag_id=tag_id) or article_queryset_total.filter(is_delete=False) # 4. 对数据尽心分页 pagen = Paginator(article_tag_queryset,contains.PER_PAGE_DATA_NUMBER) # 传递待分页对象 、每页多少个 # 5. 返回当页数据 try: article_list = pagen.page(page_num) except EmptyPage: logger.error("访问页数超过总页数") article_list = pagen.page(pagen.num_pages) # 6. 数据序列化 article_per_page_list = [] for article in article_list: article_per_page_list.append({ "id": article.id, "title": article.title, "digest": article.digest, "update_time": article.update_time.strftime("%Y年%m月%d日 %H:%M"), "clicks": article.clicks, "image_url": article.image_url, "author": article.author.username, "tag_name": article.tag.name, }) data = { "total_page": pagen.num_pages, "article": article_per_page_list } return to_json_data(data=data)
js/news/index.js
$(function () { let iPage = 1; // 设定默认的page页码为1 let sCurrentTagId = 0; // 设定默认的tag_id为0 let $newsList = $(".news-nav ul li"); //获取到标签栏 let iTotalPage = 1; //设定默认的总页数为1 let bIsLoadData = true; //是否正在向后端传递参数 fn_load_content(); //调用向后端请求数据函数 $newsList.click(function () { //点击分类标签,则为点击的标签加上一个class属性为active //冰衣橱其他兄弟元素的上的值为active的class属性 $(this).addClass("active").siblings("li").attr("active"); //获取绑定在当前选中分类上的data-id属性值 let sClickTagId = $(this).children("a").attr("data-id"); if (sClickTagId !== sCurrentTagId) { sCurrentTagId = sClickTagId; //记录当前的分类id iPage = 1; iTotalPage = 1; fn_load_content() } }); // 滚动鼠标动态加载数据 $(window).scroll(function () { // 浏览器窗口高度 let showHeight = $(window).height(); //整个网页的高度 let pageHeight = $(document).height(); //页面可以滚动的距离 let canScrollHeight = pageHeight - showHeight; // 页面滚动了多少,整个是睡着页面滚动实时变化的 let nowScroll = $(document).scrollTop(); if ((canScrollHeight - nowScroll) < 100) { //判断页数,去更新新闻数据 if (!bIsLoadData) { bIsLoadData = true; //如果当前页面数据如果小于总页数,那么才去加载数据 if (iPage < iTotalPage) { iPage += 1; $(".btn-more").remove(); //删除标签 //去加载数据 fn_load_content() } else { alert("已经全部加载,没有更多内容了!"); $(".btn-more").remove(); //删除标签 $(".news-list").append($('<a href="javascript:void(0)" class="btn-more">已全部加载 </a>')) } } } }); // 向前端发送请求获取数据 function fn_load_content() { //构建参数 let sDataParams = { "page": iPage, "tag_id": sCurrentTagId, }; //发送ajax请求获取数据 $.ajax({ url: "/news/article_list", type: "GET", data: sDataParams, dataType: "json", success: function (res) { if (res.errno === "200") { iTotalPage = res.data.total_page; //获取到总页数 if (iPage === 1) { $(".news-list").html(""); } res.data.article.forEach(function (one_article) { let content = ` <li class="news-item"> <a href="/news/${ one_article.id }/" class="news-thumbnail" target="_blank"> <img src="${ one_article.image_url }" alt="${ one_article.title }" title="${ one_article.title }"> </a> <div class="news-content"> <h4 class="news-title"> <a href="/news/${ one_article.id }/">${ one_article.title }</a> </h4> <p class="news-details">${ one_article.digest }</p> <div class="news-other"> <span class="news-type">${ one_article.tag_name }</span> <span class="news-clicks">${ one_article.clicks }</span> <span class="news-time">${ one_article.update_time}</span> <span class="news-author">${ one_article.author}</span> </div> </div> </li> `; $(".news-list").append(content) }); $(".news-list").append($('<a href="javascript:void(0);" class="btn-more">点击加载更多</a>')); bIsLoadData = false } else { alert(res.errmsg) } }, error: function () { alert("服务器请求超时,请重试") } }) }});
news/urls.py
from django.urls import pathfrom news import viewsapp_name = "news"urlpatterns = [ path("<int:article_id>/", views.ArticleDetailView.as_view(), name="article_detail"),]
news/views.py
from django.views import Viewfrom django.shortcuts import renderfrom django.http import HttpResponseNotFoundfrom news import models as _modelsclass ArticleDetailView(View): """ news detail """ def get(self, request, article_id): # 1. 从数据库Articles中获取id=article_id的数据:title、update_time、content、tag_name、author, article = _models.Articles.objects.select_related("author", "tag").only("id", "title", "update_time", "content", "tag__name", "author__username").filter(is_delete=False, id=article_id).first() # 2. 获取文章评论数据 comment_queryset_list = _models.Comments.objects.select_related("author", "parent").only("content", "update_time", "author__username", "parent__author__username", "parent__content", "parent__update_time").filter( is_delete=False, article_id=article_id) comment_list = [] for comment in comment_queryset_list: comment_list.append(comment.to_dict_data()) # 引用Comments中自定义的字典转换 le = len(comment_list) # 文章评论数 # 3. 判断是否取到文章数据 if article: return render(request, "news/news_detail.html", locals()) else: return HttpResponseNotFound("<h1>Page Not Found<h1>")
news/article_detail.html
<div class="news-info"> <div class="news-info-left"> <span class="news-author">{{ article.author.username }}</span> <span class="news-pub-time">{{ article.update_time }}</span> <span class="news-type">{{ article.tag.name }}</span> </div></div><br/><br/><article class="news-content"> {{ article.content | safe }}</article>
news/urls.py
from django.urls import pathfrom news import viewsapp_name = "news"urlpatterns = [ path("<int:article_id>/comments/", views.ArticleCommentView.as_view(), name="comments"),]
评论和回复用的是同一套处理逻辑
news/views.py
import jsonfrom django.views import Viewfrom news import models as _modelsfrom utils.res_code.res_code import Code, error_mapfrom utils.res_code.json_function import to_json_dataclass ArticleCommentView(View): """ news comment replay view route: /news/<int:article_id>/comments """ # 1. 创建1个post,url带有article_id参数 def post(self, request, article_id): # 要先判断是否用户已经登录(必须登录后才能进行评论) if not request.user.is_authenticated: return to_json_data(errno=Code.SESSIONERR, errmsg=error_map[Code.SESSIONERR]) # 2. 获取前端传来的参数 try: json_data = request.body if not json_data: return to_json_data(errno=Code.PARAMERR, errmsg="参数错误,请重新输入") dict_data = json.loads(json_data) except Exception as e: return to_json_data(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR]) # 3. 获取前端传来的回复内容 content = dict_data.get("content") # 4. 判断content是否为空 if not content: return to_json_data(errno=Code.PARAMERR, errmsg="评论内容为空,请输入!") # 5. 获取父评论id parent_id = dict_data.get("parent_id") try: if parent_id: parent_id = int(parent_id) # 判断文章id和父评论id是否和传来过同时满足 if not _models.Comments.objects.only("id").filter(is_delete=False, id=parent_id, article_id=article_id).exists(): return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.UNKOWNERR]) except Exception as e: logger.error("父评论id异常:{}".format(e)) return to_json_data(errno=Code.PARAMERR, errmsg="父评论ID参数异常") # 6. 保存到数据库 article_comment = _models.Comments() # 获取评论实例 article_comment.content = content # 保存评论内容 article_comment.article_id = article_id # 保存评论文章id article_comment.author = request.user article_comment.parent_id = parent_id if parent_id else None # 判断是否为空 article_comment.save() # 保存实例 count = _models.Comments.objects.only("id").filter(is_delete=False, article_id=article_id).count() # 评论条数实时加载 return to_json_data(data={ "data": article_comment.to_dict_data(), "count": count, } )
评论和回复的js实现基本一致
js/news/index.js
$(function () { // 未登录提示框 let $loginComment = $('.please-login-comment input'); //获取请登录框 let $sendComment = $('.logged-comment .comment-btn'); //获取到评论按钮 let $commentCount = $('.new-comment .comment-count'); //获取到评论条数 $('.comment-list').delegate('a,input', 'click', function () { //delegate委托事件 let sClassValue = $(this).prop('class'); //取出class属性值 if (sClassValue.indexOf('reply_a_tag') >= 0) { $(this).next().toggle(); //交叉显示,点击时显示,再次点击就不显示 } if (sClassValue.indexOf("reply_cancel") >= 0) { $(this).parent().toggle();//交叉显示,点击时显示,再次点击就不显示 } //回复评论 if (sClassValue.indexOf('reply_btn') >= 0) { //进行发送ajax请求数据给后端 let $this = $(this); //获取当前点击回复 let article_id = $this.parent().attr("article_id"); //上一个评论的的文章id let parent_id = $this.parent().attr("comment_id"); //上一个评论的id let content = $this.prev().val();//评论内容 //判断评论内容是是否为空 if (!content) { alert("评论内容为空,请重新输入"); } //构造ajax请求参数 let sDataParams = { "content": content, "parent_id": parent_id, }; //发送ajax请求 $.ajax({ url: '/news/' + article_id + "/comments/", type: "POST", contentType: "application/json, charset=utf-8", data: JSON.stringify(sDataParams), dataType: 'json', }) .done(function (res) { if (res.errno === "200") { let comment = res.data.data; let html_content = ``; $commentCount.empty(); //移除文本内容 $commentCount.text(res.data.count); //添加新的评论数 html_content += ` <li class="comment-item"> <div class="comment-info clearfix"> <img src="../../static/images/avatar.jpeg" alt="avatar" class="comment-avatar"> <span class="comment-user">${ comment.author }</span> <span class="comment-pub-time">${ comment.update_time } </div> <div class="comment-content">${ comment.content }</div> <div class="parent_comment_text"> <div class="parent_username">${ comment.parent.author }</div> <br/> <div class="parent_content_text">${ comment.parent.content }</div> </div> <div class="comment_time left_float">${comment.update_time}</div> <a href="javascript:void(0);" class="reply_a_tag right_float">回复</a> <form class="reply_form left_float" comment_id="${comment.comment_id}" article_id="${comment.article_id}"> <textarea class="reply_input"></textarea> <input type="button" value="回复" class="reply_btn right_float"> <input type="reset" name="" value="取消" class="reply_cancel right_float"> </form> </li>`; $('.comment-list').prepend(html_content); $this.prev().val(""); //清空输入框 $this.parent().hide(); //关闭评论框 } else if (res.errno = "4101") { alert("请登录后再评论"); setTimeout(function () { //重定向到登录界面 window.location.href = "/users/login" }, 800) } else { alert(res.errmsg) } }) .fail(function () { alert("服务器超时,请重试!") }) } }); //点击评论框,重定向到用户登录页面 $loginComment.click(function () { $.ajax({ url: "/news/" + $(".please-login-comment").attr("article_id") + "/comments/", type: "POST", contentType: "applications/json, charset=utf-8", dataType: "json", }) .done(function (res) { if (res.errno === "4101") { setTimeout(function () { window.location.href = "/users/login"; }, 800) } else { alert(res.message); } }) .fail(function () { alert("服务器超时,请重试!"); }) }); // 发表评论 $sendComment.click(function () { //获取到文章的id,评论的id,评论内容 let $this = $(this); let article_id = $this.parent().attr("article_id"); let content = $this.prev().val(); if (!content) { alert("评论内容为空,请重新输入!"); return } let sDataParams = { "content": content, }; $.ajax({ url: '/news/' + article_id + "/comments/", type: "POST", contentType: "application/json, charset=utf-8", data: JSON.stringify(sDataParams), dataType: 'json', }) .done(function (res) { if (res.errno === "200") { let comment = res.data.data; let html_content = ``; $commentCount.empty(); $commentCount.text(res.data.count); //添加新的评论数 html_content += ` <li class="comment-item"> <div class="comment-info clearfix"> <img src="../../static/images/avatar.jpeg" alt="avatar" class="comment-avatar"> <span class="comment-user">${ comment.author }</span> <span class="comment-pub-time">${ comment.update_time }</span> </div> <div class="comment-content">${ comment.content }</div> <div class="comment_time left_float">${ comment.update_time }</div> <a href="javascript:void(0);" class="reply_a_tag right_float">回复</a> <form class="reply_form left_float" comment_id="${comment.comment_id}" article_id="${comment.article_id}"> <textarea class="reply_input"></textarea> <input type="button" value="回复" class="reply_btn right_float"> <input type="reset" name="" value="取消" class="reply_cancel right_float"> </form> </li>`; $('.comment-list').prepend(html_content); $this.prev().val(""); //清空输入框 } else if (res.errno = "4101") { alert("请登录后再评论"); setTimeout(function () { //重定向到登录界面 window.location.href = "/users/login"; }, 800) } else { alert(res.errmsg) } }) .fail(function () { alert("服务器超时,请重试!") }) }); // get cookie using jQuery function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { let cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { let cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } // Setting the token on the AJAX request $.ajaxSetup({ beforeSend: function (xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } } });});
news/index.html
<div class="comment-contain"> <div class="comment-pub clearfix"> <div class="new-comment">文章评论(<span class="comment-count">{{ le }}</span>)</div> {# 用户登录后才能评论 #} {% if user.is_authenticated %} <div class="comment-control logged-comment" article_id="{{ article.id }}"> <input type="text" placeholder="请填写评论"> <button class="comment-btn">发表评论</button> </div> {% else %} <div class="comment-control please-login-comment" article_id="{{ article.id }}" style="display: none"> <input type="text" placeholder="请登录后参加评论" readonly> <button class="comment-btn">发表评论</button> </div> {% endif %} </div> <ul class="comment-list"> {# 评论内容 #} {% for comment in comment_list %} <li class="comment-item"> <div class="comment-info clearfix"> <img src="../../static/images/avatar.jpeg" alt="avatar" class="comment-avatar"> <span class="comment-user">{{ comment.author }}</span> <span class="comment-pub-time">{{ comment.update_time }}</span> </div> <div class="comment-content">{{ comment.content }}</div> {# 子评论内容 #} {% if comment.parent %} <div class="parent_comment_text"> <div class="parent_username">{{ comment.parent.author }}</div> <br/> <div class="parent_content_text">{{ comment.parent.content }}</div> </div> {% endif %} <div class="comment_time left_float">{{ comment.parent.update_time }}</div> <a href="javascript:void(0);" class="reply_a_tag right_float">回复</a> <form class="reply_form left_float" comment_id="{{ comment.comment_id }}" article_id=" {{ comment.article_id }}"> <textarea class="reply_input"></textarea> <input type="button" value="回复" class="reply_btn right_float"> <input type="reset" name="" value="取消" class="reply_cancel right_float"> </form> </li> {% endfor %} </ul></div>
联系客服