二十三.新闻详情页
注:该篇文章接上一篇 二十二.新闻主页
在上一篇文章我们实现了资讯模块中的新闻主页功能,在这一章实现点击新闻进入新闻详细页面.
一、功能需求分析
1.功能
- 新闻详情
- 加载评论功能
- 添加评论功能
二.新闻详细内容页面
通过新闻的id来从数据库取得新闻的详细内容,新闻的插图,新闻的作者的信息
1.业务流程分析
业务流程:
- 判断前端传递新闻id是否为空,是否为整数,是否存在
2.接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /news/int:news_id/ |
参数格式 | url路径参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
news_id | 整数 | 是 | 新闻id |
- 返回结果:
html页面,直接通过模板渲染的方式实现
3.后端页面
- 在news/views.py中添加信息详细显示代码
class NewDetailView(View):
"""
显示新闻页面的详细新闻信息
"""
def get(self,request,news_id):
# 通过get请求的以及每个新闻的id来获取详细的新闻信息
#select_related 直接把要从数据库中得到的值传到News中,下次再进行查询时可以直接在News中查询,不必进入数据库中
news = News.objects.select_related('tag','author').only('title','content','update_time','tag__name','author__username').filter(is_delete=False,id = news_id).first()
# 判断news是否存在
if news:
return render(request,'news/news_detail.html',context={'news':news})
else:
return HttpResponesNotFound('<h1>Page not found</h1>') # 报错404 自行定义的响应错误代码,记得导入
- 在news/urls.py文件中设置路由
from django.urls import path
from . import views
# url的命名空间
app_name = 'news'
urlpatterns = [
path('', views.index, name='index'), # 将这条路由命名为index
# 新闻列表
path('news/',views.NewsListViews.as_view(),name = 'news_list'),
#展示轮播图
path('news/banners/',views.NewBannerViews.as_view(),name = 'news_banners'),
# 访问新闻详情页面
path('news/<int:news_id>/',views.NewDetailView.as_view(),name = 'news_detail')
]
4.前端页面
- 在templates/news/new_detail.html文件下代码如下
{% extends 'base/base.html' %}
{% load static %}
{% block title %}文章详情{% endblock title %}
{% block link %}
<link rel="stylesheet" href="{% static '/css/news/news-detail.css' %}">
{% endblock link %}
<!-- main start -->
<main id="main">
<div class="w1200 clearfix">
{% block main_contain %}
<!-- news-contain start -->
<div class="news-contain">
<h1 class="news-title">{{ news.title }}</h1>
<div class="news-info">
<div class="news-info-left">
<span class="news-author">{{ new.author.username }}</span>
<span class="news-pub-time">{{ news.update_time }}</span>
<span class="news-type">{{ news_tag.name }}</span>
</div>
</div>
<article class="news-content">
{{ news.content|safe }}
</article>
<div class="comment-contain">
<div class="comment-pub clearfix">
<div class="new-comment">
文章评论(<span class="comment-count">0</span>)
</div>
<div class="comment-control please-login-comment" style="display:none;">
<input type="text" placeholder="请登录后参加评论">
</div>
<div class="comment-control logged-comment">
<input type="text" placeholder="请填写评论">
</div>
<button class="comment-btn">发表评论</button>
</div>
<ul class="comment-list">
<li class="comment-item">
<div class="comment-info clearfix">
<img src="../images/avatar.jpeg" alt="avatar" class="comment-avatar">
<span class="comment-user">评论人</span>
<span class="comment-pub-time">1小时前</span>
</div>
<div class="comment-content">这是一条评论</div>
</li>
<li class="comment-item">
<div class="comment-info clearfix">
<img src="../images/avatar.jpeg" alt="avatar" class="comment-avatar">
<span class="comment-user">评论人</span>
<span class="comment-pub-time">1小时前</span>
</div>
<div class="comment-content">这是一条评论</div>
</li>
</ul>
</div>
</div>
{% endblock %}
</div>
</main>
<!-- main end -->
{% block script%}
<script src="{% static 'js/news/news_detail.js' %}"></script>
{% endblock script %}
- 在static/css/news/new-detail.css中添加代码:
.news-content p{
font-size: 16px;
line-height: 26px;
text-align: justify;
word-wrap: break-word;
padding: 3px 0 ;
}
运行结果:
- 因为没有实现js代码无法实现点击跳转
- 在给定路由可以进入news的详情页面
- 由于暂时没有设置评论人,所以目前没有信息
- 在static/js/news中添加news_detail.js文件
三、加载新闻评论
1.接口设计
新闻详情页,直接渲染新闻评论
2.后端代码
模型代码
# 本项目设计二级评论,修改Comments模型,添加一个parent字段
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True)
修改模型后一定要及时迁移
导入数据
链接:https://pan.baidu.com/s/11-TMVONmDRCfygze3Sq-pQ
提取码:spmd
# 导入测试数据tb_comments_20181222.sql
# 一定要保证tb_users中有id为1,2,3的三个用户,不然导入测试数据会报错
mysql -u用户名 -p -D 数据库名< tb_comments_20181222.sql
视图代码
class NewDetailView(View):
"""
显示新闻页面的详细新闻信息
"""
def get(self,request, news_id):
# 通过get请求的以及每个新闻的id来获取详细的新闻信息
#select_related 直接把要从数据库中得到的值传到News中,下次再进行查询时可以直接在News中查询,不必进入数据库中
news = News.objects.select_related('tag','author').only('title','content','update_time','tag__name','author__username').filter(is_delete=False,id = news_id).first()
# 判断news是否存在
if news:
#获取父级评论
comments = Commments.objects.select_related('author', 'parent').only(
'comment', 'author__username', 'update_time', 'parent__author__username',
'parent__comment','parent__update_time').filter(is_delete=False, id = news_id)
return render(request,'news/news_detail.html',context={'news':news,'comments':comments})
else:
return HttpResponesNotFound('<h1>Page not found</h1>') # 报错404 自行定义的响应错误代码,记得导入
3. 前端代码
- html代码
<!-- 在templates/news/news_detail.html文件中class="comment-contain"里面加入如下代码: -->
<div class="comment-contain">
<div class="comment-pub clearfix">
<div class="new-comment">
文章评论(<span class="comment-count">0</span>)
</div>
<div class="comment-control please-login-comment" style="display:none;">
<input type="text" placeholder="请登录后参加评论">
</div>
<div class="comment-control logged-comment">
<input type="text" placeholder="请填写评论">
</div>
<button class="comment-btn">发表评论</button>
</div>
<ul class="comment-list">
{% for comment in comments %}
<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.username }}</span>
<span class="comment-pub-time">{{ comment.update_time }}</span>
</div>
<div class="comment-content">{{ comment.comment }}</div>
{% if comment.parent %}
<div class="parent_comment_text">
<div class="parent_username">{{ comment.parent.author }}</div>
<div class="comment_time">{{ comment.parent.update_time }}</div>
<div class="parent_content_text">
{{ comment.parent.comment }}
</div>
</div>
{% endif %}
<a href="javascript:void(0);" class="reply_a_tag right_float">回复</a>
<form class="reply_form left_float" comment-id="{{ comment.id }}"
news-id="{{ comment.news_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 comments %}
</ul>
</div>
- js代码
// 在static/js/news/news_detail.js中加入如下代码:
$(function () {
$('.comment-list').delegate('a,input', 'click', function () {
//获取回复按钮的class属性
let sClassValue = $(this).prop('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) {
// 评论
}
});
});
- 更改css改变回复框
/* 在static/css/news/news-detail.css中添加如下代码: */
.comment-list .comment-item {
/*把这条样式注释掉*/
/*border-bottom: 1px solid #ddd;*/
margin-bottom: 30px;
}
/* ========= 为父评论添加样式 start============ */
.left_float{
float:left;
}
.right_float{
float:right;
}
.parent_comment_text{
width:698px;
padding:8px;
background: #f4facf;
margin:10px 0 0 60px;
}
.comment_time{
font-size:12px;
color:#999;
margin:10px 0 0 60px;
}
.parent_comment_text .parent_username{
font-size:12px;
color:#000;
display:inline-block;
}
.parent_comment_text .comment_time{
display: inline-block;
float:right;
}
.parent_comment_text .parent_content_text{
color:#666;
font-size:14px;
margin-top: 20px;
}
.reply_a_tag{
font-size:12px;
color:#999;
text-indent:20px;
margin:10px 0 0 20px;
background:url('/static/images/content_icon.png') left center no-repeat;
}
.reply_form{
width:718px;
overflow:hidden;
margin:10px 0 0 60px;
display:none;
}
.reply_input{
float:left;
width:692px;
height:30px;
border-radius:4px;
padding:10px;
outline:none;
border:1px solid #2185ed;
}
.reply_btn,.reply_cancel{
width:40px;
height:23px;
background:#76b6f4;
border:0px;
border-radius:2px;
color:#fff;
margin:10px 5px 0 10px;
cursor:pointer;
}
.reply_cancel{
background:#fff;
color: #909090;
}
/* ========= 为父评论添加样式 end============ */
将content_icon.png图片放到static/images/中
4. 在news下的 index.html中修改如下代码 可以实现点击新闻列表的图片进入到新闻详情
在这里插入代码片
4.2 .在static/news/index.js文件中改写代码
目的:实现点击新闻跳转到详细页面
目前状况如下,只能够跳转到最上面的新闻页面
分析:经过分析需要改变a标签的链接
1.1 进入到static/news/index.js下面,将a标签的href 改为
<a href="/news/${one_news.id}/" class="news-thumbnail"
target="_blank">
四、添加新闻评论功能
1.业务流程分析
业务处理流程:
- 判断用户是否登录
- 判断前端传的新闻id是否为空,是否为整数,是否存在
- 判断评论内容是否为空
- 判断是否有父评论,父评论id是否与新闻id匹配
- 保存新闻评论
2.接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | POST |
url定义 | /news/int:news_id/comment/ |
参数格式 | url路径参数,表单参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
news_id | 整数 | 是 | 新闻id |
content | 字符串 | 是 | 新闻评论内容 |
parent_id | 整数 | 否 | 父评论id |
注意:post请求需要携带csrftoken
- 返回结果:
{
"errno": "0",
"errmsg": "",
"data": {
"news_id": 1170,
"content_id": 3569,
"content": "评论比较中肯。",
"author": "admin",
"update_time": "2019年08月19日 16:00",
"parent": {
"news_id": 1170,
"content_id": 893,
"content": "行文思路简单肤浅,文章结构平面呆板。",
"author": "xinlan",
"update_time": "2018年12月21日 11:17",
"parent": null
}
}
}
3.后端代码
视图代码
# 在news/views.py中编写如下视图
class NewsCommentView(View):
"""
添加评论视图
url: /news/<int:news_id>/comment/
"""
def post(self, request, news_id):
# 是否登录
if not request.user.is_authenticated: # is_authenticated 判断是否登录,通过base.html条件
return json_response(errno=Code.SESSIONERR, errmsg=error_map[Code.SESSIONERR]) #返回错误信息
# 新闻是否存在
if not News.objects.only('id').filter(is_delete=False, id=news_id).exists():
return json_response(errno=Code.PARAMERR, errmsg='新闻不存在!')
content = request.POST.get('content')
# 内容是否为空
if not content:
return json_response(errno=Code.PARAMERR, errmsg='评论内容不能为空!')
# 二级评论是否存在
parent_id = request.POST.get('parent_id')
if parent_id:
try:
parent_id = int(parent_id)
#先查id再按照父级评论和新闻id查看是否存在二级评论
if not Commments.objects.only('id').filter(is_delete=False, id=parent_id, news_id=news_id).exists():
return json_response(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
except Exception as e:
logger.info('前端传递过来的parent_id异常\n{}'.format(e))
return json_response(errno=Code.PARAMERR, errmsg='未知异常')
# 保存到数据库
new_comment = Commments() # 先创建一个空对象
new_comment.content = content
new_comment.news_id = news_id
new_comment.author = request.user
new_comment.parent_id = parent_id if parent_id else None #判断perent_id是否为空
new_comment.save()
return json_response(data=new_comment.to_dict_data())
序列化comment对象
# 在news/models.py的Comment模型中添加如下方法,用来序列化
def to_dict_data(self):
comment_dict = {
'news_id': self.news_id,
'content_id': self.id,
'content': self.content,
'author': self.author.username,
'update_time': self.update_time.astimezone().strftime('%Y年%m月%d日 %H:%M'),
'parent': self.parent.to_dict_data() if self.parent else None
}
return comment_dict
路由
# 在news/urls.py中添加如下路由
path('news/<int:news_id>/comment/', views.NewsCommentView.as_view(), name='news_comment'),
4.前端代码
html
<!-- 修改templates/news/news_detail.html中评论部分代码如下 -->
{% extends 'base/base.html' %}
{% load static %}
{% block title %}文章详情{% endblock title %}
{% block link %}
<link rel="stylesheet" href="{% static '/css/news/news-detail.css' %}">
{% endblock link %}
<!-- main start -->
<main id="main">
<div class="w1200 clearfix">
{% block main_contain %}
<!-- news-contain start -->
<div class="news-contain">
<h1 class="news-title">{{ news.title }}</h1>
<div class="news-info">
<div class="news-info-left">
<span class="news-author">{{ new.author.username }}</span>
<span class="news-pub-time">{{ news.update_time }}</span>
<span class="news-type">{{ news_tag.name }}</span>
</div>
</div>
<article class="news-content">
{{ news.content|safe }}
</article>
<div class="comment-contain">
<div class="comment-pub clearfix">
<div class="new-comment">
文章评论(<span class="comment-count">0</span>)
</div>
{% if user.is_authenticated %}
<div class="comment-control logged-comment">
<input type="text" placeholder="请填写评论">
</div>
{% else %}
<div class="comment-control please-login-comment" >
<input type="text" placeholder="请登录后参加评论">
</div>
{% endif %}
<button class="comment-btn">发表评论</button>
{% csrf_token %}
</div>
<ul class="comment-list">
{% for comment in comments %}
<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.username }}</span>
<span class="comment-pub-time">{{ comment.update_time }}</span>
</div>
<div class="comment-content">{{ comment.comment }}</div>
{% if comment.parent %}
<div class="parent_comment_text">
<div class="parent_username">{{ comment.parent.author }}</div>
<div class="comment_time">{{ comment.parent.update_time }}</div>
<div class="parent_content_text">
{{ comment.parent.comment }}
</div>
</div>
{% endif %}
<a href="javascript:void(0);" class="reply_a_tag right_float">回复</a>
<form class="reply_form left_float" comment-id="{{ comment.id }}"
news-id="{{ comment.news_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 comments %}
</ul>
</div>
</div>
{% endblock %}
</div>
</main>
<!-- main end -->
{% block script%}
<script src="{% static 'js/news/news_detail.js' %}"></script>
{% endblock script %}
js
$(function () {
// 对评论进行评论
$('.comment-list').delegate('a,input', 'click', function () {
//获取回复按钮的class属性
let sClassValue = $(this).prop('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) {
// 评论
let $this = $(this);
let news_id = $this.parent().attr('news-id');
let parent_id = $this.parent().attr('comment-id');
let content = $this.prev().val();
if (!content) {
message.showError('请输入评论内容!');
return
}
$
.ajax({
url: '/news/' + news_id + '/comment/',
type: 'POST',
data: {
content: content,
parent_id: parent_id
},
dataType: "json"
})
.done((res) => {
if (res.errno === '0') {
let comment = res.data;
let html_comment = `<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>
</div>
<div class="comment-content">${comment.content}</div>
<div class="parent_comment_text">
<div class="parent_username">${comment.parent.author}</div>
<div class="comment_time">${comment.parent.update_time}</div>
<div class="parent_content_text">
${comment.parent.content}
</div>
</div>
<div class="comment_time left_float">${comment.update_time}</div>
<a href="javascript:;" class="reply_a_tag right_float">回复</a>
<form class="reply_form left_float" comment-id="${comment.content_id}" news-id="${comment.news_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>`;
message.showSuccess('评论成功!');
setTimeout(() => {
$('.comment-list').prepend(html_comment);
}, 800);
$this.prev().val(''); // 清空输入框
$this.parent().hide(); // 关闭评论框
} else if (res.errno === '4101') {
// 用户未登录
message.showError(res.errmsg);
setTimeout(() => {
window.location.href = '/login/'
}, 800)
} else {
// 失败
message.showError(res.errmsg)
}
})
.fail(() => {
message.showError('服务器超时,请重试')
})
}
});
// 对新闻评论
let $newsComment = $('.logged-comment input'); // 新闻评论框
let $sendComment = $('.comment-pub .comment-btn'); // 新闻评论按钮
$sendComment.click(function () {
let $this = $(this);
if ($this.prev().hasClass('please-login-comment')) {
message.showError('未登录,请登录后再评论!');
setTimeout(() => {
window.location.href = '/login/'
}, 800);
return
}
let news_id = $this.prev().attr('news-id');
let content = $newsComment.val();
if (!content) {
message.showError('请输入评论内容!');
return
}
$
.ajax({
url: '/news/' + news_id + '/comment/',
type: 'POST',
data: {
content: content
},
dataType: 'json'
})
.done((res) => {
if (res.errno === '0') {
let comment = res.data;
let html_comment = `<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>
<a href="javascript:;" class="reply_a_tag right_float">回复</a>
<form class="reply_form left_float" comment-id="${comment.content_id}" news-id="${comment.news_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>`;
message.showSuccess('评论成功!');
setTimeout(() => {
$(".comment-list").prepend(html_comment);
}, 800);
// 清空
$newsComment.val('');
} else if (res.errno === '4101') {
// 用户未登录
message.showError(res.errmsg);
setTimeout(() => {
window.location.href = '/login/'
}, 800)
} else {
message.showError(res.errmsg);
}
})
.fail(() => {
message.showError('服务器超时,请重试!');
})
})
});