# 八、文章评论

# 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>
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
33
34
35
36
37
38
39
40
41
42

article/index.vue导入、注册、使用组件

import CommentList from './components/comment-list'
1
components:{
    // 其他注册...
    CommentList
}
1
2
3
4
<van-divider>正文结束</van-divider>
<!------------------------------ 文章评论列表-------------------------------------->
<comment-list/>
<!------------------------------ /文章评论列表 ------------------------------------->
1
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
  })
}
1
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>

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
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'
1
 components: {
    CommentItem  // 注册组件
 }
1
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"/>

1
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>
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

# 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}`
  });
}
1
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  // 是否点赞中
    }
}
1
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>
1
2
3
4
5
6
7

3、在事件处理函数中



 


import {
   // 其他导入...
    addCommentLike,deleteCommentLik  
} from '@/api/comment'
1
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  
}
1
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 // 发布评论弹层控制
    }
}
1
2
3
4
5
6
  • 使用弹层组件


 
 
 
 
 

<!--/底部区域-->

<!--------------------------------  发布评论 -------------------------------------->
<van-popup v-model="isPostShow" position="bottom">
	测试内容
</van-popup>
<!-------------------------------- /发布评论 -------------------------------------->
1
2
3
4
5
6
7
  • 绑定事件打开弹层





 


<van-button
    class="comment-btn"
    type="default"
    round
    size="small"
    @click="isPostShow=true"   
    >写评论</van-button>
1
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>

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
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'
1
  components: {
    // 其他注册...
    CommentPost
  },
1
2
3
4
<!--------------------------------  发布评论 -------------------------------------->
<van-popup v-model="isPostShow"  position="bottom">
    <comment-post />
</van-popup>
<!---------------------------------- /发布评论 ------------------------------------>
1
2
3
4
5

# 3.3、请求发布

基本思路:

  • 找到数据接口
  • 封装请求方法
  • 注册发布点击事件
    • 请求发布
    • 成功:将发布的内容展示到列表中
    • 失败:提示失败

封装api请求方法

api/comment.js 中添加封装数据接口

/**
 * 发布文章评论或评论回复
 */
export const addComment = data=>{
  return request({
    method: "POST",
    url: "/v1_0/comments",
    data
  });
}
1
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>
<!-- 发布评论 -->
1
2
3
4
5

comment-post.vue 定义接收

  props: {
    // 目标id,接收文章id或者评论id   
    target: {
      type: [Number, String, Object],
      required: true
    }
  }
1
2
3
4
5
6
7

comment-post.vue导入请求方法、绑定事件、书写事件函数、发送请求

import { addComment } from '@/api/comment'
1


 


<van-button
      class="post-btn"
      @click="onPost"
    >发布</van-button>
1
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('发布失败')
      }
    }
1
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)
      
        
        // 其他代码...
    }
1
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>
<!-- 发布评论 -->
1
2
3
4
5
6
7
8
 onPostSuccess (data) {
     // 关闭弹出层
     this.isPostShow = false
     // 将发布内容显示到列表顶部
     // todo...
     console.log(data) // comment-post.vue 传递出来的数据
 }
1
2
3
4
5
6
7

至此,已经完成了,关闭弹层,和清空输入框操作,但是将数据放入到列表顶部还没有实现,这是一个难点,我们将开始操作

思路分析:

  • 我们已经实现了将comment-post.vue 里面的发布成功数据传递给了父组件article/index.vue

    image-20210713211726645

我们已经把数据传递给父组件了,现在最重要的就是如何放到列表组件comment-list中去

思考: 父组件如何将数据传递给子组件comment-list 呢?

回顾: 我们之前在 首页频道功能里面Tab的数据,和弹出层里面我的频道数据是一个内容,是通过props将这个数组传递进入,然后在弹出层里面点击我的频道删除的时候直接删除这个 数组里面的对应元素,可以实现同步更新。

image-20210713212348610

image-20210713212431553

结论: 父子通信的props 如果是一个 数组,我们只要不重新赋值这个props里面的数组,就都不算修改props,而可以实现父子之间共享数据,实时变化。(其本质是父子用了一个引用数据类型的数据

实现: 既然如此,我们可以依然可以实现article/index.vuecomment-list.vue 去共享 评论列表数据

  • 子组件comment-listdata里面的变量list修改成 props里面的属性list 用于去接收外面的空数组commentList




     



    data () {
        return {
          // 其他变量...  
          // list: [],   // <===注释掉
        }
    }    
    
    1
    2
    3
    4
    5
    6


     
     
     
     
     


    props: {
      // 其他属性...
      // 定义自定义属性list,去接收外面的commentList 变量    
      list: {
          type: Array,
          default: () => []
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 父组件article/index.vuedata 中定义数组变量,去共享儿子的数据




     



    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

疑问??? 可能有同学会问,为什么接收空数组呢,其实父组件的空数组不是为了传递给子组件,反而是让子组件把数据列表共享给父组件,因为父子通信关联的是引用数据类型,任何人一方改变了,另外一方都会变化。

image-20210713213529540

测试: 通过vue调试器,去观察发现父组件article/index.vue里面的变量commentList 也有数据了

image-20210713213732033

已经实现了,父组件和评论列表组件共享列表数据,我们只需要将评论组件传递的数据插入到父组件的这个commentList中即可!

onPostSuccess (data) {
    // 关闭弹出层
    this.isPostShow = false
    // 将发布内容显示到列表顶部
    this.commentList.unshift(data.new_obj)  // <=== 精华之句!!!
    // 评论数量+1
    this.totalCommentCount++
    
}
1
2
3
4
5
6
7
8
9

总结:数据思路图如下

image-20210713225611052

# 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.vuedata添加数据用来控制展示回复弹层的显示状态

data () {
  return {
    // 其他变量...
    isReplyShow: false
  }
}
1
2
3
4
5
6

2、在详情页article/index.vue中添加使用弹层组件

<!------------------------ 评论回复 ------------------------------>
<van-popup
  v-model="isReplyShow"
  position="bottom"
  style="height: 100%"
>
  评论回复
</van-popup>
<!------------------------ /评论回复 ------------------------------>
1
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
    8
     
     
     
     
     

    onReplyClick (comment) {
       console.log(comment) // comment-item组件传递出来的数据
       // 显示评论回复弹出层
       this.isReplyShow = true
    }
    
    1
    2
    3
    4
    5
  • 思路图

    image-20210714213711299

# 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>
1
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'
1
components: {
  // 其他注册...
  CommentReply
}
1
2
3
4
<!-- 评论回复 -->
<van-popup v-model="isReplyShow"  position="bottom" style="height: 100%;">
   <comment-reply/>
</van-popup>
<!-- /评论回复 -->
1
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来调试数据,判断是否传递成功

image-20210714220605603

# 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>
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
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"  
/>
1
2
3
4
image-20210714223218110

# 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'
    }
}
1
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 // 获取的评论数据个数,不传表示采用后端服务设定的默认每页数据量
    })
    
    // 很多代码...
}
1
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'
1


 


components: {
    // 其他注册...
    CommentList
}
1
2
3
4


 
 



<!-- 评论的回复列表 -->
<comment-list
   :source="comment.com_id"
   type="c"
/>
<!-- /评论的回复列表 -->
1
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"
  >
1
2
3
4
5
6
7
8
9
10
11
12
  • 关闭了自动检测,需要在created 里面开启加载状态,和主动调用加载数据



 



  created () {
    // 当你手动初始 onLoad 的话,它不会自动开始初始的 loading
    // 所以我们要手动的开启初始 loading
    this.loading = true
    this.onLoad()
  }
1
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>
<!-- /评论回复 -->
1
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'
1
components: {
    // 其他注册...
    CommentPost
}
1
2
3
4
<!-- 发布评论 -->
<van-popup v-model="isPostShow" position="bottom">
    <comment-post 
         :target="comment.com_id"/>
</van-popup>
<!-- /发布评论 -->
1
2
3
4
5
6
  • 梳理 评论回复接口的时候我们发现comment-post.vue不仅需要评论id,还需要文章的对应id,这个时候 我们父组件comment-reply.vue并没有, 而 article/index.vue 组件是有的,这个时候就需要有一个祖先后代通信过程(不选择多层父子的原因是因为比较复杂)

    image-20210717231426713

    祖先后代通信

    article/index.vue 提供数据

    // 给所有的后代组件提供数据
    // 注意:不要滥用
    provide: function () {
        return {
            articleId: this.articleId  // 或者写成 this.$route.params.articleId  也可以
        }
    }
    
    1
    2
    3
    4
    5
    6
    7

    comment-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。对文章进行评论,不要传此参数。
})
1
2
3
4
5
6
7
8
9
10

注意: 对targetarticleId 都要进行toString 处理。


此刻发现请求可以发送了,实现了复用了comment-post.vue组件,但是有同学已经发现, 其实有问题,就是comment-post.vue 在作为文章评论的时候是充当article/index.vue 的子组件的,同样也接收到了 articleId,且也发送出去了,也就是在对文章进行评论的时候我们也提交了art_id ,这样如果后台严格的话是不行的,所以要优化

image-20210717231805872

所以要在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'
    }  
}
1
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() 
})
1
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>
<!-- /发布评论 -->
1
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>
<!-- /发布评论 -->
1
2
3
4
5
6
7
8
9

data 里面定义变量commentList , 实现comment-list.vuecomment-post.vuecomment-reply.vue 三者共享数据




 



data () {
    return {
        isPostShow: false,
        commentList: [] // 评论的回复列表  
    }
}
1
2
3
4
5
6

methods 定义方法


 
 
 
 
 
 
 
 
 



methods: {
	onPostSuccess (data) {
        // 更新回复的数量
        this.comment.reply_count++

        // 关闭弹层
        this.isPostShow = false

        // 将最新回复的内容展示到列表的顶部
        this.commentList.unshift(data.new_obj)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12




 



<!-- 评论的回复列表 -->
<comment-list
   :source="comment.com_id"
   type="c"
   :list="commentList"
/>
<!-- /评论的回复列表 -->
1
2
3
4
5
6
7

数据流图

image-20210717234635092

# 4.10、评论回复总结

image-20210717235958419