BBS项目文章评论功能
BBS项目文章评论技术技术点分析
前端
- 允许根评论和子评论(评论评论的评论),可以评论自己的文章。
- 用户未登录不能评论且隐藏评论输入框(request.user.is_authenticated)。
- 评论内容有两种渲染方式:
- 刷新页面时,从后端取出评论数据,前端循环展示
- 评论后DOM操作临时将评论内容渲染到评论列表,使用的是js的模版字符串语法。
- 根评论朝后端提交的数据:文章主键、评论内容、
- 子评论朝后端提交的数据:文章主键、评论内容、父评论主键
- 获取父评论的方式:给回复按钮绑定一个自定义属性,属性值为父评论主键
- 区分子评论和根评论关键在于是否有父评论,这里面为了统一,提交根评论时也携带父评论(只不过值为null,因为数据库该字段支持为空)。
数据库
- 评论表的字段:评论人、文章、评论时间、内容、自关联字段(实现子评论)
后端
-
需要登录后才能评论,所以使用一个登录校验装饰器
-
后端逻辑比较简单,接收评论内容、文章主键、父评论主键
-
评论内容为空值,响应提示信息
-
使用事物同时更新文章表和评论表。
代码
前端
{# 评论列表展示区start#}
<div>
<h5>评论列表</h5>
<hr>
<ul class="list-group" id="comment_list">
{# #1楼 2020-05-06 19:04 立志做一个好的程序员#}
{% for comment in comment_list %}
<li class="list-group-item">
<span>#{{ forloop.counter }}楼 </span>
<span>{{ comment.comment_time|date:'Y-m-d H:i'}} </span>
<span><a href="/{{ comment.user }}/">{{ comment.user }}</a></span>
{# 自定义属性parentid,获取父评论主键#}
<span class="pull-right"><a class="btn_reply" parentid="{{ comment.pk }}" replyto="{{ comment.user }}">回复</a></span>
<p class="text-success">
{% if comment.parent %}
<span> @{{ comment.parent.user }}</span><br>
{% endif %}
<span> {{ comment.content }}</span>
</p>
</li>
{% endfor %}
</ul>
</div>
{# 评论列表展示区end#}
{# 评论区start#}
{% if request.user.is_authenticated %}
<div id="comment_zone">
<h5 class="glyphicon glyphicon-comment">发表评论</h5>
<div class="form-group">
<textarea name="" class="form-control" id="comment_body" cols="60" rows="10"></textarea>
</div>
<div class="form-group">
<button class="btn btn-primary" id="btn_comment_submit">提交评论</button>
<span style="color: red; margin-left: 8px" id="comment_back_info"></span>
</div>
</div>
{% else %}
<p><a href="{% url 'login' %}">登录</a>后才能评论哦</p>
{% endif %}
{# 评论区end#}
<script>
{# 全局变量 #}
let parentId = null;
$('#btn_comment_submit').click(function () {
let commentBody = $('#comment_body');
let content = commentBody.val(); // 获取评论内容
if (parentId){ // 截掉子评论的前缀
let splitIndex = content.indexOf('\n') + 1;
content = content.slice(splitIndex);
}
$.ajax({
url: '{% url "article_comment" %}',
type: 'post',
data: {
'article_id': '{{ article_id }}',
'content': content,
'parent_id': parentId, // 根评论时为null, 子评论时有值
},
success: function (args) {
if (args.code === 1000){
$('#comment_back_info').text(args.msg);
commentBody.val('');
let new_comment = `<li class="list-group-item">
<span class="glyphicon glyphicon-comment">{{ request.user }}</span>
<p class="small text-success"> ${content}</p>
</li>`;
$('#comment_list').append(new_comment); // 通过js的模版字符串语法临时渲染评论内容
parentId = null; // 重置, 避免子评论后无法根评论
}else{
$('#comment_back_info').text(args.msg);
}
}
})
});
{#点击回复按钮事件#}
$('.btn_reply').click(function () {
let replayToUser = $(this).attr('replyto'); //获取回复对象
parentId = $(this).attr('parentid'); //获取回复评论的id
let reply_msg = '@' + replayToUser + '\n'; //构造提示信息
$('#comment_body').val(reply_msg).focus();
})
</script>
后端
@login_required
def article_comment(request):
if request.is_ajax():
back_info = {'code': 1000}
article_id = request.POST.get('article_id')
content = request.POST.get('content')
parent_id = request.POST.get('parent_id')
if not content:
back_info['code'] = 2000
back_info['msg'] = '评论内容不能为空'
return JsonResponse(back_info)
with transaction.atomic():
models.Article.objects.filter(pk=article_id).update(comment_counts=F('comment_counts')+1)
models.Comment.objects.create(user=request.user, article_id=article_id, content=content, parent_id=parent_id)
back_info['msg'] = '评论成功'
return JsonResponse(back_info)
else:
return render(request, 'error404.html')