# 八、文章评论
# 1、业务功能分析
# 2、展示文章评论列表
# 2.1、准备组件
为了更好的开发和维护,这里我们把文章评论单独封装到一个组件中来处理。
创建组件 src/views/article/components/comment-list.vue
<template>
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item" :title="item"></van-cell>
</van-list>
</template>
<script>
export default {
name: "CommentList",
components:{},
props: {},
data() {
return {
list: [], // 评论列表
loading: false, // 上拉加载更多的 loading
finished: false // 是否加载结束
};
},
methods: {
onLoad() {
// 异步更新数据
setTimeout(() => {
for (let i = 0; i < 10; i++) {
this.list.push(this.list.length + 1);
}
// 加载状态结束
this.loading = false;
// 数据全部加载完成
if (this.list.length >= 40) {
this.finished = true;
}
}, 500);
}
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
article/index.vue
导入、注册、使用组件
import CommentList from './components/comment-list'
components:{
// 其他注册...
CommentList
}
2
3
4
<van-divider>正文结束</van-divider>
<!------------------------------ 文章评论列表-------------------------------------->
<comment-list/>
<!------------------------------ /文章评论列表 ------------------------------------->
2
3
4
# 2.2、获取数据并展示
提示:有评论数据的文章 id :短文章==>1323981393964826624, 长文章==>138671
步骤:
- 封装接口
- 请求获取数据
- 处理模板
实现:
1、在 api/comment.js
中添加封装请求方法
/**
* 评论请求模块
*/
import request from '@/utils/request'
/**
* 获取文章评论列表
*/
export const getComments = params => {
return request({
method: 'GET',
url: '/v1_0/comments',
params
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2、请求获取数据
父组件
article/index.vue
传入文章id<comment-list :source="article.art_id"/>
1子组件
commnent-list
定义自定义属性接收props: { source: { type: [Number, String, Object], required: true } }
1
2
3
4
5
6导入请求方法
import { getComments } from '@/api/comment'
1定义相关变量
data(){ return{ //其他变量.. offset: null, // 获取下一页数据的标记 limit: 10, error: false } }
1
2
3
4
5
6
7
8书写处理事件函数
methods: { async onLoad () { try { // 1. 请求获取数据 const { data } = await getComments({ type: 'a', // 评论类型,a-对文章(article)的评论,c-对评论(comment)的回复 source: this.source.toString(), // 源id,文章id或评论id,【可能有大数字,所以执行一下toString 方法】 offset: this.offset, // 评论数据的偏移量,值为评论id,表示从此id的数据向后取,不传表示从第一页开始读取数据 limit: this.limit // 获取的评论数据个数,不传表示采用后端服务设定的默认每页数据量 }) // 2. 将数据添加到列表中(一定要注意是追加数据,否则列表高度不增加,形成死循环) const { results } = data.data this.list.push(...results) // 3. 将 loading 设置为 false this.loading = false // 4. 判断是否还有数据 if (results.length) { // 有就更新获取下一页的数据页码 this.offset = data.data.last_id } else { // 没有就将 finished 设置结束 this.finished = true } } catch (err) { this.error = true this.loading = false } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32显示评论内容
<van-cell v-for="(item,index) in list" :key="index" :title="item.content"></van-cell>
1
# 2.3、展示文章评论总数量
子组件comment-list.vue
触发自定义事件
onLoad
事件函数里面 触发自定义// 2. 将数据添加到列表中 const { results } = data.data this.list.push(...results) // 把文章评论的总数量传递到外部 this.$emit('onload-success', data.data) // <========== 增加这一句 // 3. 将 loading 设置为 false this.loading = false
1
2
3
4
5
6
7
8
9默认情况下只有滚动到了评论列表区域才会触发
onLoad
事件,所以我们需要再created
里面主动触发一次,保证一开始就触发created () { this.onLoad() }
1
2
3
父组件article/index.vue
中监听自定事件
定义变量存放总数量
data(){ //其他变量... totalCommentCount:0 // 文章评论总数量 }
1
2
3
4绑定给展示组件的属性
<van-icon class="comment-icon" name="comment-o" :info="totalCommentCount" />
1
2
3
4
5监听自定义事件,赋值给变量
<!-- 文章评论列表 --> <comment-list :source="article.art_id" @onload-success="totalCommentCount = $event.total_count" /> <!-- /文章评论列表 -->
1
2
3
4
5
6
# 2.4、文章评论项
创建 article/components/comment-item.vue
<template>
<van-cell class="comment-item">
<van-image
slot="icon"
class="avatar"
round
fit="cover"
src="https://img.yzcdn.cn/vant/cat.jpeg"
/>
<div slot="title" class="title-wrap">
<div class="user-name">用户名称</div>
<van-button
class="like-btn"
icon="good-job-o"
>赞</van-button>
</div>
<div slot="label">
<p class="comment-content">这是评论内容</p>
<div class="bottom-info">
<span class="comment-pubdate">4天前</span>
<van-button
class="reply-btn"
round
>回复 0</van-button>
</div>
</div>
</van-cell>
</template>
<script>
export default {
name: 'CommentItem',
props: {
//每行的评论信息
comment: {
type: Object,
required: true
}
},
methods: {}
}
</script>
<style scoped lang="less">
.comment-item {
.avatar {
width: 72px;
height: 72px;
margin-right: 25px;
}
.title-wrap {
display: flex;
justify-content: space-between;
align-items: center;
.user-name {
color: #406599;
font-size: 26px;
}
}
.comment-content {
font-size: 32px;
color: #222222;
word-break: break-all;
text-align: justify;
}
.comment-pubdate {
font-size: 19px;
color: #222;
margin-right: 25px;
}
.bottom-info {
display: flex;
align-items: center;
}
.reply-btn {
width: 135px;
height: 48px;
line-height: 48px;
font-size: 21px;
color: #222;
}
.like-btn {
height: 30px;
padding: 0;
border: none;
font-size: 19px;
line-height: 30px;
margin-right: 7px;
.van-icon {
font-size: 30px;
}
}
.liked{
background-color:orange;
}
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
在comment-list.vue
中导入、注册、使用
import CommentItem from './comment-item'
components: {
CommentItem // 注册组件
}
2
3
<!--删除 van-cell 组件-->
<!--<van-cell v-for="(item,index) in list" :key="index" :title="item.content"></van-cell>-->
<!--使用 comment-item组件-->
<comment-item
v-for="(item, index) in list"
:key="index"
:comment="item"/>
2
3
4
5
6
7
8
9
渲染展示数据信息
<template>
<van-cell class="comment-item">
<van-image
slot="icon"
class="avatar"
round
fit="cover"
:src="comment.aut_photo"
/>
<div slot="title" class="title-wrap">
<div class="user-name">{{ comment.aut_name }}</div>
<van-button
class="like-btn"
icon="good-job-o"
>{{ comment.like_count || '赞' }}</van-button>
</div>
<div slot="label">
<p class="comment-content">{{ comment.content }}</p>
<div class="bottom-info">
<span class="comment-pubdate">{{ comment.pubdate | relativeTime }}</span>
<van-button
class="reply-btn"
round
>回复 {{ comment.reply_count }}</van-button>
</div>
</div>
</van-cell>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 2.5、评论点赞
1、在 api/comment.js
中添加封装两个数据接口
/**
* 对评论或评论回复点赞
*/
export function addCommentLike(commentId) {
return request({
method: "POST",
url: "/v1_0/comment/likings",
data: {
target: commentId
}
});
}
/**
* 取消对评论或评论回复点赞
*/
export function deleteCommentLike(commentId) {
return request({
method: "DELETE",
url: `/v1_0/comment/likings/${commentId}`
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2、然后给评论项中的 like
图标注册点击事件
data(){
return{
commentLoading:true // 是否点赞中
}
}
2
3
4
5
<van-button
class="like-btn"
:class="{ liked:comment.is_liking }"
:icon="comment.is_liking ? 'good-job' : 'good-job-o'"
:loading="commentLoading"
@click="onCommentLike"
>{{ comment.like_count || '赞' }}</van-button>
2
3
4
5
6
7
3、在事件处理函数中
import {
// 其他导入...
addCommentLike,deleteCommentLik
} from '@/api/comment'
2
3
4
// 点赞或取消点赞事件
async onCommentLike () {
// loading 开启
this.commentLoading = true
try{
// 如果已经赞了则取消点赞
if (this.comment.is_liking) {
await deleteCommentLike(this.comment.com_id)
this.comment.like_count--
} else {
// 如果没有赞,则点赞
await addCommentLike(this.comment.com_id)
this.comment.like_count++
}
// 更新视图状态
this.comment.is_liking = !this.comment.is_liking
this.$toast('操作成功')
}catch(e){
this.$toast('操作失败,请重试')
}
// loading 关闭
this.commentLoading = false
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3、发布文章评论
# 3.1、准备弹出层
在article/index.vue
中准备弹出层,使用弹出层组件Popup (opens new window)
- 定义控制变量
data(){
return{
//其他变量...
isPostShow:false // 发布评论弹层控制
}
}
2
3
4
5
6
- 使用弹层组件
<!--/底部区域-->
<!-------------------------------- 发布评论 -------------------------------------->
<van-popup v-model="isPostShow" position="bottom">
测试内容
</van-popup>
<!-------------------------------- /发布评论 -------------------------------------->
2
3
4
5
6
7
- 绑定事件打开弹层
<van-button
class="comment-btn"
type="default"
round
size="small"
@click="isPostShow=true"
>写评论</van-button>
2
3
4
5
6
7
提示:不设置高度的时候,内容会自动撑开弹层高度
# 3.2、封装组件
- 创建
views/article/components/comment-post.vue
组件
<template>
<div class="comment-post">
<van-field
class="post-field"
v-model="message"
rows="2"
autosize
type="textarea"
maxlength="50"
placeholder="请输入留言"
show-word-limit
/>
<van-button
class="post-btn"
>发布</van-button>
</div>
</template>
<script>
export default {
name: 'CommentPost',
data () {
return {
message: ''
}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less">
.comment-post {
display: flex;
align-items: center;
padding: 32px 0 32px 32px;
.post-field {
background-color: #f5f7f9;
}
.post-btn {
width: 150px;
border: none;
padding: 0;
color: #6ba3d8;
&::before {
display: none;
}
}
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- 在
article/index.vue
中导入、注册、使用
import CommentPost from './components/comment-post'
components: {
// 其他注册...
CommentPost
},
2
3
4
<!-------------------------------- 发布评论 -------------------------------------->
<van-popup v-model="isPostShow" position="bottom">
<comment-post />
</van-popup>
<!---------------------------------- /发布评论 ------------------------------------>
2
3
4
5
# 3.3、请求发布
基本思路:
- 找到数据接口
- 封装请求方法
- 注册发布点击事件
- 请求发布
- 成功:将发布的内容展示到列表中
- 失败:提示失败
封装api请求方法
在 api/comment.js
中添加封装数据接口
/**
* 发布文章评论或评论回复
*/
export const addComment = data=>{
return request({
method: "POST",
url: "/v1_0/comments",
data
});
}
2
3
4
5
6
7
8
9
10
父子通信,传递文章id
article/index.vue
传递文章id
<!-- 发布评论 -->
<van-popup v-model="isPostShow" position="bottom">
<comment-post :target="article.art_id" />
</van-popup>
<!-- 发布评论 -->
2
3
4
5
comment-post.vue
定义接收
props: {
// 目标id,接收文章id或者评论id
target: {
type: [Number, String, Object],
required: true
}
}
2
3
4
5
6
7
comment-post.vue
导入请求方法、绑定事件、书写事件函数、发送请求
import { addComment } from '@/api/comment'
<van-button
class="post-btn"
@click="onPost"
>发布</van-button>
2
3
4
async onPost () {
this.$toast.loading({
message: '发布中...',
forbidClick: true, // 禁用背景点击
duration: 0 // 持续时间,默认 2000,0 表示持续展示不关闭
})
try {
const { data } = await addComment({
target: this.target.toString(), // 评论目标id(评论文章即文章id,对评论进行回复则为评论id) 防止有大数字最好也执行一下toString方法!
content: this.message, // 评论内容
art_id: null // 文章id,对评论内容发表回复时,需要传递此参数,表明所属文章id。对文章进行评论,不要传此参数。
})
this.$toast.success('发布成功')
// TODO==>
// 关闭弹出层
// 将发布内容显示到列表顶部
// 清空文本框
} catch (err) {
this.$toast.fail('发布失败')
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3.4、成功处理☆☆
- 核心要点
- 清空文本框
- 关闭弹出层
- 将发布内容显示到列表顶部
子组件comment-post.vue
中清除message
内容,通过自定义事件向外抛出 添加成功的数据
async onPost () {
// 其他代码...
// 关闭弹出层
// 将发布内容显示到列表顶部
// 清空文本框
this.message = ''
this.$emit('post-success',data.data)
// 其他代码...
}
2
3
4
5
6
7
8
9
10
11
12
父组件article/index.vue
中监听post-success
自定义事件,执行关闭弹层和将数据放入顶部的操作
<!-- 发布评论 -->
<van-popup v-model="isPostShow" position="bottom" >
<comment-post
:target="article.art_id"
@post-success="onPostSuccess"
/>
</van-popup>
<!-- 发布评论 -->
2
3
4
5
6
7
8
onPostSuccess (data) {
// 关闭弹出层
this.isPostShow = false
// 将发布内容显示到列表顶部
// todo...
console.log(data) // comment-post.vue 传递出来的数据
}
2
3
4
5
6
7
至此,已经完成了,关闭弹层,和清空输入框操作,但是将数据放入到列表顶部还没有实现,这是一个难点,我们将开始操作
思路分析:
我们已经实现了将
comment-post.vue
里面的发布成功数据传递给了父组件article/index.vue
我们已经把数据传递给父组件了,现在最重要的就是如何放到列表组件comment-list
中去
思考: 父组件如何将数据传递给子组件comment-list
呢?
回顾: 我们之前在 首页频道功能里面Tab
的数据,和弹出层里面我的频道
数据是一个内容,是通过props
将这个数组传递进入,然后在弹出层里面点击我的频道删除的时候直接删除这个 数组里面的对应元素,可以实现同步更新。
结论: 父子通信的props
如果是一个 数组,我们只要不重新赋值这个props
里面的数组,就都不算修改props
,而可以实现父子之间共享数据,实时变化。(其本质是父子用了一个引用数据类型的数据)
实现: 既然如此,我们可以依然可以实现article/index.vue
和 comment-list.vue
去共享 评论列表数据
子组件
comment-list
将data
里面的变量list
修改成props
里面的属性list
用于去接收外面的空数组commentList
data () { return { // 其他变量... // list: [], // <===注释掉 } }
1
2
3
4
5
6props: { // 其他属性... // 定义自定义属性list,去接收外面的commentList 变量 list: { type: Array, default: () => [] } }
1
2
3
4
5
6
7
8父组件
article/index.vue
在data
中定义数组变量,去共享儿子的数据data(){ return{ // 其他变量... commentList: [] // 评论列表 } }
1
2
3
4
5
6绑定给子组件
<!-- 文章评论列表 --> <comment-list :source="article.art_id" :list="commentList" @onload-success="totalCommentCount = $event.total_count" />
1
2
3
4
5
6
疑问??? 可能有同学会问,为什么接收空数组呢,其实父组件的空数组不是为了传递给子组件,反而是让子组件把数据列表共享给父组件,因为父子通信关联的是引用数据类型,任何人一方改变了,另外一方都会变化。
测试: 通过vue调试器,去观察发现父组件article/index.vue
里面的变量commentList
也有数据了
已经实现了,父组件和评论列表组件共享列表数据,我们只需要将评论组件传递的数据插入到父组件的这个commentList中即可!
onPostSuccess (data) {
// 关闭弹出层
this.isPostShow = false
// 将发布内容显示到列表顶部
this.commentList.unshift(data.new_obj) // <=== 精华之句!!!
// 评论数量+1
this.totalCommentCount++
}
2
3
4
5
6
7
8
9
总结:数据思路图如下
# 3.5、空内容处理
对输入的内容进行去除空格处理,增加
trim
修饰符<van-field class="post-field" v-model.trim="message" rows="2" autosize type="textarea" maxlength="50" placeholder="请输入留言" show-word-limit />
1
2
3
4
5
6
7
8
9
10内容没有输入的时候我们禁用 按钮
<van-button class="post-btn" @click="onPost" :disabled="!message.length" >发布</van-button>
1
2
3
4
5
# 4、评论回复
# 4.1、准备回复弹层
一、在详情页中使用弹层用来展示文章的回复
1、在article/index.vue
中data
添加数据用来控制展示回复弹层的显示状态
data () {
return {
// 其他变量...
isReplyShow: false
}
}
2
3
4
5
6
2、在详情页article/index.vue
中添加使用弹层组件
<!------------------------ 评论回复 ------------------------------>
<van-popup
v-model="isReplyShow"
position="bottom"
style="height: 100%"
>
评论回复
</van-popup>
<!------------------------ /评论回复 ------------------------------>
2
3
4
5
6
7
8
9
# 4.2、点击回复显示弹出层
comment-item.vue
组件里面给回复按钮绑定事件,触发向外传递点击行数据(自定义事件)<van-button class="reply-btn" round @click="$emit('reply-click', comment)" >回复 {{ comment.reply_count }}</van-button>
1
2
3
4
5父组件
comment-list.vue
监听自定义事件,继续向外传递收到的数据(自定义事件)<comment-item v-for="(item, index) in list" :key="index" :comment="item" @reply-click="$emit('reply-click', $event)" />
1
2
3
4
5
6爷爷组件
article/index.vue
监听自定事件,打开弹框,输出收到的数据<!-- 文章评论列表 --> <comment-list :source="article.art_id" :list="commentList" @onload-success="totalCommentCount = $event.total_count" @reply-click="onReplyClick" /> <!-- /文章评论列表 -->
1
2
3
4
5
6
7
8onReplyClick (comment) { console.log(comment) // comment-item组件传递出来的数据 // 显示评论回复弹出层 this.isReplyShow = true }
1
2
3
4
5思路图
# 4.3、封装内容组件
- 创建
views/article/components/comment-reply.vue
组件
<template>
<div class="comment-reply">
评论回复内容
</div>
</template>
<script>
export default {
name: 'CommentReply',
components: {},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less"></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 在
article/index.vue
中导入,注册,使用组件
import CommentReply from './components/comment-reply'
components: {
// 其他注册...
CommentReply
}
2
3
4
<!-- 评论回复 -->
<van-popup v-model="isReplyShow" position="bottom" style="height: 100%;">
<comment-reply/>
</van-popup>
<!-- /评论回复 -->
2
3
4
5
# 4.4、传递当前点击回复的评论项
思路: 回顾4.2节,我们已经在article/index.vue
中得到了comment-item.vue
里面回复按钮传递出来的数据,我们定义变量接收,然后通过父子通信传递给comment-reply.vue
组件
实现:
article/index.vue
定义变量存储 传递出来的数据data(){ return{ // 其他变量... currentComment: {} // 当前点击回复的评论项 } }
1
2
3
4
5
6存储数据
onReplyClick (comment) { // 存储起来 this.currentComment = comment // 显示评论回复弹出层 this.isReplyShow = true }
1
2
3
4
5
6
7将数据传递给
comment-reply.vue
组件<comment-reply :comment="currentComment"/>
1子组件
comment-reply.vue
中定义props
接收数据props: { // 点击的那行的评论信息 comment: { type: Object, required: true } }
1
2
3
4
5
6
7
通过
vue-devtools
来调试数据,判断是否传递成功
# 4.5、处理头部及当前评论项
comment-reply.vue
布局内容,绑定数据
<template>
<div class="comment-reply">
<van-nav-bar
:title="comment.reply_count > 0 ? `${comment.reply_count}条回复` : '暂无回复'"
>
<van-icon
slot="left"
name="cross"
@click="$emit('close')"
/>
</van-nav-bar>
<div class="scroll-wrap">
<!-- 当前评论项 -->
<comment-item :comment="comment" />
<!-- /当前评论项 -->
<!-- 评论的回复列表 -->
<!-- /评论的回复列表 -->
</div>
<!-- 底部区域 -->
<div class="reply-bottom">
<van-button
class="write-btn"
size="small"
round
>写评论</van-button>
</div>
<!-- /底部区域 -->
<!-- 发布评论 -->
<!-- /发布评论 -->
</div>
</template>
<script>
import CommentItem from './comment-item'
export default {
name: 'CommentReply',
components: {
CommentItem,
},
props: {
// 点击回复的那行评论信息
comment: {
type: Object,
required: true
}
},
data () {
return {
}
}
}
</script>
<style scoped lang="less">
.scroll-wrap {
position: fixed;
top: 92px;
left: 0;
right: 0;
bottom: 88px;
overflow-y: auto;
}
.reply-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 88px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
border-top: 1px solid #d8d8d8;
.write-btn {
width: 60%;
}
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
父组件article/index.vue
中监听关闭事件
<comment-reply
:comment="currentComment"
@close="isReplyShow = false"
/>
2
3
4
# 4.6、展示评论回复列表
基本思路:
- 回复列表和文章的评论列表几乎是一样的,所以我们复用
comment-list.vue
组件 - 修改
comment-list.vue
组件,因为既要让他满足文章列表要求又要满足评论列表要求
代码实现:
1、修改comment-list.vue
组件
定义自定义属性type
接收不同类型,判断是文章列表,还是评论列表,用于提供给接口
props: {
// 文章id或者评论id===> 此刻 接收的是评论id
source: {
type: [Number, String, Object],
required: true
},
// 用于和父组件共享数据,实现添加评论列表更新,插入
list: {
type: Array,
default: () => []
},
// 【新增这个type】判断是文章还是评论
type: {
type: String,
// 自定义 Prop 数据验证
validator (value) {
return ['a', 'c'].includes(value)
},
default: 'a'
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async onLoad(){
// 很多代码...
// 获取文章的评论和获取评论的回复是同一个接口
// 唯一的区别是接口参数不一样
// type
// a 文章的评论
// c 评论的回复
// source
// 文章的评论,则传递文章的 ID
// 评论的回复,则传递评论的 ID
// 1. 请求获取数据
const { data } = await getComments({
type: this.type, // 评论类型,a-对文章(article)的评论,c-对评论(comment)的回复
source: this.source.toString(), // 源id,文章id或评论id 【文章id或者评论id都有可能存在大数字】
offset: this.offset, // 获取评论数据的偏移量,值为评论id,表示从此id的数据向后取,不传表示从第一页开始读取数据
limit: this.limit // 获取的评论数据个数,不传表示采用后端服务设定的默认每页数据量
})
// 很多代码...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2、在comment-reply.vue
中导入comment-list.vue
,注册,使用
import CommentList from './comment-list'
components: {
// 其他注册...
CommentList
}
2
3
4
<!-- 评论的回复列表 -->
<comment-list
:source="comment.com_id"
type="c"
/>
<!-- /评论的回复列表 -->
2
3
4
5
6
# 4.7、回复列表数据重复数据问题
原因: van-list
组件有个特点,就是只有在可视范围内才会自动调用load
事件,所以我们之前文章内容过长,就不会触发请求文章评论列表,故而 我们手动在created
里面调用了onLoad
方法,但是现在我们这里的回复列表一开始就在可是范围内,故而会触发2次请求,1次是可视范围内自动触发,1次是我们主动调用触发。所以我们要关闭 自动检测可视范围自动调用。
实现:
- 给
van-list
组件设置immediate-check
属性,关闭可视范围自动调用检测
<!--
只有 List 在可视范围内才会检查滚动位置触发 onLoad
-->
<van-list
v-model="loading"
:finished="finished"
finished-text="已显示全部评论"
:error="error"
error-text="加载失败,请点击重试"
:immediate-check="false"
@load="onLoad"
>
2
3
4
5
6
7
8
9
10
11
12
- 关闭了自动检测,需要在
created
里面开启加载状态,和主动调用加载数据
created () {
// 当你手动初始 onLoad 的话,它不会自动开始初始的 loading
// 所以我们要手动的开启初始 loading
this.loading = true
this.onLoad()
}
2
3
4
5
6
# 4.8、回复列表内容不更新问题
弹层组件:
- 如果初始的条件是 false,则弹层的内容不会渲染
- 程序运行期间,当条件变为 true 的时候,弹层才渲染了内容
- 之后切换弹层的展示,弹层只是通过 CSS 控制隐藏和显示
原因: 弹层渲染出来以后就只是简单的切换显示和隐藏,里面的内容也不再重新渲染了,所以会导致我们的评论的回复列表不会动态更新了。解决办法就是在每次弹层显示的时候重新渲染组件。
<!-- 评论回复 -->
<!--
弹出层是懒渲染的:只有在第一次展示的时候才会渲染里面的内容,之后它的关闭和显示都是在切换内容的显示和隐藏
-->
<van-popup v-model="isReplyShow"position="bottom" style="height: 100%;">
<!--
v-if 条件渲染
true:渲染元素节点
false:不渲染
-->
<comment-reply
v-if="isReplyShow"
:comment="currentComment"
@close="isReplyShow = false"
/>
</van-popup>
<!-- /评论回复 -->
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4.9、发布回复
1、处理底部视图 comment-reply.vue
绘制回复按钮和回复弹框
data () { return { isPostShow: false // 弹框是否显示 } }
1
2
3
4
5<!-- 底部区域 --> <div class="reply-bottom"> <van-button class="write-btn" size="small" round @click="isPostShow = true" >写评论</van-button> </div> <!-- /底部区域 --> <!-- 发布评论 --> <van-popup v-model="isPostShow" position="bottom"> 评论回复评论回复 </van-popup> <!-- /发布评论 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16整理评论项和回复列表,用容器包裹
<div class="scroll-wrap"> <!-- 当前评论项 --> <!-- /当前评论项 --> <!-- 评论的回复列表 --> <!-- /评论的回复列表 --> </div>
1
2
3
4
5
6
7样式
<style scoped lang="less"> .scroll-wrap { position: fixed; top: 92px; left: 0; right: 0; bottom: 88px; overflow-y: auto; } .reply-bottom { position: fixed; bottom: 0; left: 0; right: 0; height: 88px; display: flex; align-items: center; justify-content: center; background-color: #fff; border-top: 1px solid #d8d8d8; .write-btn { width: 60%; } } </style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2、参数处理
- 在
comment-reply.vue
导入comment-post.vue
组件
import CommentPost from './comment-post'
components: {
// 其他注册...
CommentPost
}
2
3
4
<!-- 发布评论 -->
<van-popup v-model="isPostShow" position="bottom">
<comment-post
:target="comment.com_id"/>
</van-popup>
<!-- /发布评论 -->
2
3
4
5
6
梳理 评论回复接口的时候我们发现
comment-post.vue
不仅需要评论id,还需要文章的对应id,这个时候 我们父组件comment-reply.vue
并没有, 而article/index.vue
组件是有的,这个时候就需要有一个祖先后代通信过程(不选择多层父子的原因是因为比较复杂)祖先后代通信
article/index.vue
提供数据// 给所有的后代组件提供数据 // 注意:不要滥用 provide: function () { return { articleId: this.articleId // 或者写成 this.$route.params.articleId 也可以 } }
1
2
3
4
5
6
7comment-post.vue
注入数据// inject:['articleId'] inject: { articleId: { type: [Number, String, Object], default: null } }
1
2
3
4
5
6
7
改造发送请求方法
```js
const { data } = await addComment({
target: this.target.toString(), // 评论的目标id(评论文章即为文章id,对评论进行回复则为评论id)
content: this.message, // 评论内容
// art_id: this.articleId.toString()
art_id: this.articleId ? this.articleId.toString() : this.articleId // 文章id,对评论内容发表回复时,需要传递此参数,表明所属文章id。对文章进行评论,不要传此参数。
})
2
3
4
5
6
7
8
9
10
注意: 对target
和 articleId
都要进行toString
处理。
此刻发现请求可以发送了,实现了复用了comment-post.vue组件,但是有同学已经发现, 其实有问题,就是comment-post.vue 在作为文章评论的时候是充当article/index.vue 的子组件的,同样也接收到了 articleId,且也发送出去了,也就是在对文章进行评论的时候我们也提交了art_id ,这样如果后台严格的话是不行的,所以要优化
所以要在comment-post.vue
里面进行判断,如果是充当 评论回复的时候则 使用 articleId
,否则不使用。
props: {
// 目标ID(文章ID或评论ID)
target: {
type: [Number, String, Object],
required: true
},
// 增加一个type,进行判断,如果是a则说明是文章回复,不需要使用inject注入的数据, 不是a则使用。
type: {
type: String,
// 自定义 Prop 数据验证
validator (value) {
return ['a', 'c'].includes(value)
},
default: 'a'
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { data } = await addComment({
target: this.target.toString(), // 评论的目标id(评论文章即为文章id,对评论进行回复则为评论id)
content: this.message, // 评论内容
art_id: this.type === 'a' ? null : this.articleId.toString()
})
2
3
4
5
在comment-reply.vue
中传递一下type
<!-- 发布评论 -->
<van-popup v-model="isPostShow" position="bottom">
<comment-post
type="c"
:target="comment.com_id"/>
</van-popup>
<!-- /发布评论 -->
2
3
4
5
6
7
3、回复成功操作
comment-post.vue
回复成功会触发post-success
,所以只需要在comment-reply.vue
中监听即可
<!-- 发布评论 -->
<van-popup v-model="isPostShow" position="bottom">
<comment-post
type="c"
:target="comment.com_id"
@post-success="onPostSuccess"
/>
</van-popup>
<!-- /发布评论 -->
2
3
4
5
6
7
8
9
data
里面定义变量commentList
, 实现comment-list.vue
和 comment-post.vue
和 comment-reply.vue
三者共享数据
data () {
return {
isPostShow: false,
commentList: [] // 评论的回复列表
}
}
2
3
4
5
6
methods
定义方法
methods: {
onPostSuccess (data) {
// 更新回复的数量
this.comment.reply_count++
// 关闭弹层
this.isPostShow = false
// 将最新回复的内容展示到列表的顶部
this.commentList.unshift(data.new_obj)
}
}
2
3
4
5
6
7
8
9
10
11
12
<!-- 评论的回复列表 -->
<comment-list
:source="comment.com_id"
type="c"
:list="commentList"
/>
<!-- /评论的回复列表 -->
2
3
4
5
6
7
数据流图