经过几个章节的折腾,文章的增删改查也完成得差不多了,这章紧接着开发最后一个大的模块:评论。
作为一个普通的博客,评论通常位于文章详情的末尾,以便读者发表对博主的赞赏之情。又因为评论模块和文章模块本身没太多交联,比较独立,因此可以不让它们的代码搅在一起。
因此修改 ArticleDetail.vue
,新增一个 Comments
组件:(注意此组件还没写)
<!-- frontend/src/views/ArticleDetail.vue -->
<template>
...
<Comments :article="article" />
<BlogFooter/>
</template>
<script>
...
import Comments from '@/components/Comments.vue'
export default {
...
components: {BlogHeader, BlogFooter, Comments},
...
}
</script>
...
Props 可以是数字、字符串等原生类型,也可以是如 article
这类自定义对象。传递文章对象是为了让评论组件获取到相关联文章的所有评论。
接下来正式写评论组件。
新建 frontend/src/components/Comments.vue
,写入代码:
<!-- frontend/src/components/Comments.vue -->
<template>
<br><br>
<hr>
<h3>发表评论</h3>
<!-- 评论多行文本输入控件 -->
<textarea
v-model="message"
:placeholder="placeholder"
name="comment"
id="comment-area"
cols="60"
rows="10"
></textarea>
<div>
<button @click="submit" class="submitBtn">发布</button>
</div>
<br>
<p>已有 {{ comments.length }} 条评论</p>
<hr>
<!-- 渲染所有评论内容 -->
<div
v-for="comment in comments"
:key="comment.id"
>
<div class="comments">
<div>
<span class="username">
{{ comment.author.username }}
</span>
于
<span class="created">
{{ formatted_time(comment.created) }}
</span>
<span v-if="comment.parent">
对
<span class="parent">
{{ comment.parent.author.username }}
</span>
</span>
说道:
</div>
<div class="content">
{{ comment.content }}
</div>
<div>
<button class="commentBtn" @click="replyTo(comment)">回复</button>
</div>
</div>
<hr>
</div>
</template>
<script>
import axios from 'axios';
import authorization from '@/utils/authorization';
export default {
name: 'Comments',
// 通过 props 获取当前文章
props: { article: Object },
data: function () {
return {
// 所有评论
comments: [],
// 评论控件绑定的文本和占位符
message: '',
placeholder: '说点啥吧...',
// 评论的评论
parentID: null
}
},
// 监听 article 对象
// 以便实时更新评论
watch: {
article() {
this.comments = this.article !== null ? this.article.comments : []
}
},
methods: {
// 提交评论
submit() {
const that = this;
authorization()
.then(function (response) {
if (response[0]) {
axios
.post('/api/comment/',
{
content: that.message,
article_id: that.article.id,
parent_id: that.parentID,
},
{
headers: {Authorization: 'Bearer ' + localStorage.getItem('access.myblog')}
})
.then(function (response) {
// 将新评论添加到顶部
that.comments.unshift(response.data);
that.message = '';
alert('留言成功')
})
}
else {
alert('请登录后评论。')
}
})
},
// 对某条评论进行评论
// 即二级评论
replyTo(comment) {
this.parentID = comment.id;
this.placeholder = '对' + comment.author.username + '说:'
},
// 修改日期显示格式
formatted_time: function (iso_date_string) {
const date = new Date(iso_date_string);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString()
},
}
}
</script>
<style scoped>
button {
cursor: pointer;
border: none;
outline: none;
color: whitesmoke;
border-radius: 5px;
}
.submitBtn {
height: 35px;
background: steelblue;
width: 60px;
}
.commentBtn {
height: 25px;
background: lightslategray;
width: 40px;
}
.comments {
padding-top: 10px;
}
.username {
font-weight: bold;
color: darkorange;
}
.created {
font-weight: bold;
color: darkblue;
}
.parent {
font-weight: bold;
color: orangered;
}
.content {
font-size: large;
padding: 15px;
}
</style>
实际上没有啥新知识,都是前面章节技巧的混合:
- 组件通过
Props
获取了文章对象,利用watch
监听此对象并实时更新关联评论。注意这里不能通过mounted()
去实现此逻辑,原因是因为挂载 Vue 实例的时候article
的初始值是null
。 - 提交评论用
submit()
方法,后端若返回成功则将最新的评论更新到this.comments
中。 replyTo()
方法用于记录评论的父级(即“评论的评论”),如果为null
则表示此评论自己就是第一级。formatted_time()
方法见过好几回了,用于格式化日期。
发表评论这就搞定了!来看看效果吧。
在文章详情页发表评论如下:
虽然简陋,但该有的功能都有,剩下的就是扩充和美化界面的工作了。
有的读者可能发现了,教程虽然使用了 Vue 3,但是里面用到的核心技术跟 Vue 2 基本没什么不同,甚至可以非常顺滑的互相迁移。那 Vue 3 究竟更新了什么?
下面一章让我们正式进入 Vue 3 最强大的新功能之一:组合式 API。
评论的删改的开发,由于和前面章节所用的技巧高度重合,就不赘述了,留给读者自行研究。