|
文章顯示列表這里采用動態(tài)加載的方法,文章回復(fù)采用二級評論的方法來實現(xiàn)。
一、文章列表功能
1. urls.py配置
news/urls.py
from django.urls import path
from news import views
app_name = "news"
urlpatterns = [
path("article_list/", views.ArticleView.as_view(), name="article_list"),
]
2. 后端view邏輯處理
news/views.py
import logging
from django.views import View
from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from news import contains # 上一章已有
from news import models as _models
from utils.res_code.json_function import to_json_data
logger = logging.getLogger("django")
class ArticleView(View):
def get(self, request):
# 1. 獲取前端傳來的數(shù)據(jù)文章類型id
try:
tag_id = int(request.GET.get("tag_id", 0))
except Exception as e:
logger.error("文章標(biāo)簽id參數(shù)錯誤:{}".format(e))
tag_id = 0
# 2. 獲取前端傳來的數(shù)據(jù)頁碼編號 page_num
try:
page_num = int(request.GET.get("page", 1))
except Exception as e:
logger.error("文章頁碼參數(shù)錯誤:{}".format(e))
page_num = 1
# 3. 從數(shù)據(jù)庫中獲取文章表簽id為tag_id的數(shù)據(jù)
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. 對數(shù)據(jù)盡心分頁
pagen = Paginator(article_tag_queryset,contains.PER_PAGE_DATA_NUMBER) # 傳遞待分頁對象 、每頁多少個
# 5. 返回當(dāng)頁數(shù)據(jù)
try:
article_list = pagen.page(page_num)
except EmptyPage:
logger.error("訪問頁數(shù)超過總頁數(shù)")
article_list = pagen.page(pagen.num_pages)
# 6. 數(shù)據(jù)序列化
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)
3. 前端js實現(xiàn)
js/news/index.js
$(function () {
let iPage = 1; // 設(shè)定默認的page頁碼為1
let sCurrentTagId = 0; // 設(shè)定默認的tag_id為0
let $newsList = $(".news-nav ul li"); //獲取到標(biāo)簽欄
let iTotalPage = 1; //設(shè)定默認的總頁數(shù)為1
let bIsLoadData = true; //是否正在向后端傳遞參數(shù)
fn_load_content(); //調(diào)用向后端請求數(shù)據(jù)函數(shù)
$newsList.click(function () {
//點擊分類標(biāo)簽,則為點擊的標(biāo)簽加上一個class屬性為active
//冰衣櫥其他兄弟元素的上的值為active的class屬性
$(this).addClass("active").siblings("li").attr("active");
//獲取綁定在當(dāng)前選中分類上的data-id屬性值
let sClickTagId = $(this).children("a").attr("data-id");
if (sClickTagId !== sCurrentTagId) {
sCurrentTagId = sClickTagId; //記錄當(dāng)前的分類id
iPage = 1;
iTotalPage = 1;
fn_load_content()
}
});
// 滾動鼠標(biāo)動態(tài)加載數(shù)據(jù)
$(window).scroll(function () {
// 瀏覽器窗口高度
let showHeight = $(window).height();
//整個網(wǎng)頁的高度
let pageHeight = $(document).height();
//頁面可以滾動的距離
let canScrollHeight = pageHeight - showHeight;
// 頁面滾動了多少,整個是睡著頁面滾動實時變化的
let nowScroll = $(document).scrollTop();
if ((canScrollHeight - nowScroll) < 100) {
//判斷頁數(shù),去更新新聞數(shù)據(jù)
if (!bIsLoadData) {
bIsLoadData = true;
//如果當(dāng)前頁面數(shù)據(jù)如果小于總頁數(shù),那么才去加載數(shù)據(jù)
if (iPage < iTotalPage) {
iPage += 1;
$(".btn-more").remove(); //刪除標(biāo)簽
//去加載數(shù)據(jù)
fn_load_content()
} else {
alert("已經(jīng)全部加載,沒有更多內(nèi)容了!");
$(".btn-more").remove(); //刪除標(biāo)簽
$(".news-list").append($('<a href="javascript:void(0)" class="btn-more">已全部加載 </a>'))
}
}
}
});
// 向前端發(fā)送請求獲取數(shù)據(jù)
function fn_load_content() {
//構(gòu)建參數(shù)
let sDataParams = {
"page": iPage,
"tag_id": sCurrentTagId,
};
//發(fā)送ajax請求獲取數(shù)據(jù)
$.ajax({
url: "/news/article_list",
type: "GET",
data: sDataParams,
dataType: "json",
success: function (res) {
if (res.errno === "200") {
iTotalPage = res.data.total_page; //獲取到總頁數(shù)
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("服務(wù)器請求超時,請重試")
}
})
}
});
二、文章詳情
1. urls.py配置
news/urls.py
from django.urls import path
from news import views
app_name = "news"
urlpatterns = [
path("<int:article_id>/", views.ArticleDetailView.as_view(), name="article_detail"),
]
2. 后端view邏輯處理
news/views.py
from django.views import View
from django.shortcuts import render
from django.http import HttpResponseNotFound
from news import models as _models
class ArticleDetailView(View):
"""
news detail
"""
def get(self, request, article_id):
# 1. 從數(shù)據(jù)庫Articles中獲取id=article_id的數(shù)據(jù):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. 獲取文章評論數(shù)據(jù)
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中自定義的字典轉(zhuǎn)換
le = len(comment_list) # 文章評論數(shù)
# 3. 判斷是否取到文章數(shù)據(jù)
if article:
return render(request, "news/news_detail.html", locals())
else:
return HttpResponseNotFound("<h1>Page Not Found<h1>")
3. 前端html填充
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>
三、文章評論和回復(fù)
1. urls.py配置
news/urls.py
from django.urls import path
from news import views
app_name = "news"
urlpatterns = [
path("<int:article_id>/comments/", views.ArticleCommentView.as_view(), name="comments"),
]
2. 后端view邏輯處理
評論和回復(fù)用的是同一套處理邏輯
news/views.py
import json
from django.views import View
from news import models as _models
from utils.res_code.res_code import Code, error_map
from utils.res_code.json_function import to_json_data
class ArticleCommentView(View):
"""
news comment replay view
route: /news/<int:article_id>/comments
"""
# 1. 創(chuàng)建1個post,url帶有article_id參數(shù)
def post(self, request, article_id):
# 要先判斷是否用戶已經(jīng)登錄(必須登錄后才能進行評論)
if not request.user.is_authenticated:
return to_json_data(errno=Code.SESSIONERR, errmsg=error_map[Code.SESSIONERR])
# 2. 獲取前端傳來的參數(shù)
try:
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR, errmsg="參數(shù)錯誤,請重新輸入")
dict_data = json.loads(json_data)
except Exception as e:
return to_json_data(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR])
# 3. 獲取前端傳來的回復(fù)內(nèi)容
content = dict_data.get("content")
# 4. 判斷content是否為空
if not content:
return to_json_data(errno=Code.PARAMERR, errmsg="評論內(nèi)容為空,請輸入!")
# 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參數(shù)異常")
# 6. 保存到數(shù)據(jù)庫
article_comment = _models.Comments() # 獲取評論實例
article_comment.content = content # 保存評論內(nèi)容
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() # 評論條數(shù)實時加載
return to_json_data(data={
"data": article_comment.to_dict_data(),
"count": count,
}
)
3. 前端js實現(xiàn)
評論和回復(fù)的js實現(xiàn)基本一致
js/news/index.js
$(function () {
// 未登錄提示框
let $loginComment = $('.please-login-comment input'); //獲取請登錄框
let $sendComment = $('.logged-comment .comment-btn'); //獲取到評論按鈕
let $commentCount = $('.new-comment .comment-count'); //獲取到評論條數(shù)
$('.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();//交叉顯示,點擊時顯示,再次點擊就不顯示
}
//回復(fù)評論
if (sClassValue.indexOf('reply_btn') >= 0) {
//進行發(fā)送ajax請求數(shù)據(jù)給后端
let $this = $(this); //獲取當(dāng)前點擊回復(fù)
let article_id = $this.parent().attr("article_id"); //上一個評論的的文章id
let parent_id = $this.parent().attr("comment_id"); //上一個評論的id
let content = $this.prev().val();//評論內(nèi)容
//判斷評論內(nèi)容是是否為空
if (!content) {
alert("評論內(nèi)容為空,請重新輸入");
}
//構(gòu)造ajax請求參數(shù)
let sDataParams = {
"content": content,
"parent_id": parent_id,
};
//發(fā)送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(); //移除文本內(nèi)容
$commentCount.text(res.data.count); //添加新的評論數(shù)
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">回復(fù)</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="回復(fù)" 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(); //關(guān)閉評論框
} else if (res.errno = "4101") {
alert("請登錄后再評論");
setTimeout(function () {
//重定向到登錄界面
window.location.href = "/users/login"
}, 800)
} else {
alert(res.errmsg)
}
})
.fail(function () {
alert("服務(wù)器超時,請重試!")
})
}
});
//點擊評論框,重定向到用戶登錄頁面
$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("服務(wù)器超時,請重試!");
})
});
// 發(fā)表評論
$sendComment.click(function () {
//獲取到文章的id,評論的id,評論內(nèi)容
let $this = $(this);
let article_id = $this.parent().attr("article_id");
let content = $this.prev().val();
if (!content) {
alert("評論內(nèi)容為空,請重新輸入!");
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); //添加新的評論數(shù)
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">回復(fù)</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="回復(fù)" 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("服務(wù)器超時,請重試!")
})
});
// 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'));
}
}
});
});
4. 前端html填充
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="請?zhí)顚懺u論">
<button class="comment-btn">發(fā)表評論</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">發(fā)表評論</button>
</div>
{% endif %}
</div>
<ul class="comment-list">
{# 評論內(nèi)容 #}
{% 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>
{# 子評論內(nèi)容 #}
{% 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">回復(fù)</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="回復(fù)" class="reply_btn right_float">
<input type="reset" name="" value="取消" class="reply_cancel right_float">
</form>
</li>
{% endfor %}
</ul>
</div>
|