【Vue】高仿CSDN的评论区功能,全手敲!

1、效果

【Vue】高仿CSDN的评论区功能,全手敲!_第1张图片
体验地址:桂林高校社区

2、实现流程

2-1、当点击评论图标时,在右边弹出评论区,给该评论区设置一个div浮动标签,用v-if判断显示与否,本来想用el-drawer组件实现右边弹窗抽屉效果,但是那个遮罩层让我很不满,干脆自己实现一个抽屉效果。

    
    <div v-if="drawer" class="commentDrawer">
      <span>评论区span>
      <span @click="drawer = false" style="cursor: pointer;float: left;margin-left: 10px">关闭span>
      
      <div class="inputComment">
        <textarea v-model="textareaContent" placeholder="说点什么..." maxlength="200" @input="calcInput">
        textarea>
        <span style="font-size: 14px;float: left">还可以输入{{ canInputText }}个字符span>
        <span class="sent" @click="sentComment">发送span>
      div>
      
      <div class="comment-list">
        <ul class="comment-ul">
          <li class="comment-li" v-for="(com,index) in comments">
            <div class="comment-li-top">
              <img :src="com.avatarUrl"/>
              <span class="comment-nickName">{{ com.nickName }}span>
              <span style="font-size: 15px;margin-left: auto">{{ com.commentTimeRes }}span>
              <span v-if="!com.openReply"
                    style="cursor: pointer;margin-left:auto;font-size: 16px" @click="openReply(com,index)">
                回复span>
              <span v-if="com.openReply"
                    style="cursor: pointer;margin-left: auto;font-size: 16px" @click="closeReply(index)">
                收起span>
            div>

            <div class="comment-li-content" @click="openReply(com,index)">
              <span style="text-align: left;float: left;cursor: pointer">{{ com.content }}span>
            div>
            <div class="inputReply" v-if="com.openReply">
              <textarea v-model="com.replyContent" :placeholder="com.placeholder" maxlength="200"
                        @input="calcInputReply(index)">
              textarea>
              <span style="font-size: 14px;float: left">还可以输入{{ com.canInputReply }}个字符span>
              <span class="sent" @click="sentReply(com,index)">回复span>
            div>
            
            <ul class="reply-ul">
              <li class="reply-li" v-for="(reply,index1) in com.replyies">
                <div class="reply-li-top">
                  <img :src="reply.fromUserAvatarUrl"/>
                  <span class="reply-nickName">{{ reply.fromUserNickName }}
                    <span style="font-size: 15px;margin: 3px;color: var(--text-color)">回复span>
                    {{reply.toUserNickName}}span>
                  <span style="margin-left: auto;font-size: 13px">{{ reply.replyTimeRes }}span>
                  <span v-if="!reply.openReply"
                        style="cursor: pointer;margin-left:auto;font-size: 14px" @click="openReplySon(reply)">
                  回复span>
                  <span v-if="reply.openReply"
                        style="cursor: pointer;margin-left: auto;font-size: 14px" @click="closeReplySon(reply)">
                  收起span>
                div>
                <div class="reply-li-content" @click="openReplySon(reply)">
                  <span style="float: left">{{ reply.content }}span>
                div>
                
                <div class="inputReplySon" v-show="reply.openReply">
                  <textarea v-model="reply.replyContent" :placeholder="reply.placeholder" maxlength="200"
                        @input="calcReplySon(index,index1)">
                  textarea>
                  <span style="font-size: 12px;float: left">还可以输入{{ reply.canInputReply }}个字符span>
                  <span class="sent" @click="sentReplySon(com,reply)">回复span>
                div>
              li>
            ul>
          li>
        ul>
      div>
    div>

2-2、点击评论图标的时候,触发一个方法,打开弹窗,并根据文章id请求后台评论,后端用Java对评论和回复进行封装返回

    //跳转评论详情
    toComment(id) {
      this.drawer = false
      this.clickArticleId = id
      setTimeout(() => {
        this.drawer = true
      }, 100)
      //获取该动态所有评论
      this.$http.post('/circle/comment/getComment', {
        articleId: id,
        type: 'article'
      }).then(res => {
        console.log('评论', res)
        this.comments = res.data.comments
      })
    },

2-3、发表评论,将评论内容和用户id以及文章id发给后端保存,发表的时候,前端伪更新评论区,将新发表的评论push到comments里面,就不需要再次请求后端获取所有评论,除非用户主动刷新页面。

   //发表评论
    sentComment() {
      if (this.userInfo === null) {
        //没有登录
        this.$message({
          message: '请先登陆哦~',
          type: 'warning'
        })
        this.$loginToast.open()
        return
      }
      let articleId = this.clickArticleId
      let userId = this.userInfo.id
      let type = 'article'
      let content = this.textareaContent
      let comment = {
        type: type,
        composeId: articleId,
        content: content,
        fromUserid: userId
      }
      //请求
      this.$http.post('/circle/comment/addComment', comment).then(res => {
        if (res.data.code === 200) {
          this.$message({
            message: '发送成功',
            type: 'success'
          })
          this.textareaContent = ''
        } else {
          this.$message({
            message: '发送失败',
            type: 'error'
          })
        }
      })
      //用现有数据更新当前评论区
      let addComment = {
        avatarUrl: this.userInfo.avatarUrl,
        nickName: this.userInfo.nickName,
        content: this.textareaContent,
        replyies: [],
        openReply: false,
        commentTimeRes: '1秒前',
        placeholder: '',
        replyContent: '',
        canInputReply: 200,
      }
      this.comments.push(addComment)
    },

2-4、打开回复框,注意我们动态绑定了每条评论的openReply属性,每个回复框都是独立的,并不会相互影响,更新openReply属性不能简单地用this.comment[index].openReply = true ,这样打开并不能让页面重新渲染,要用this.$set(对象,属性,值)来动态渲染。

 //打开回复框
    openReply(com) {
      //打开该评论的回复框
      this.$set(com, 'openReply', true)
      this.$set(com, 'placeholder', '回复@' + com.nickName)
    },
    closeReply(index) {
      this.$set(this.comments[index], 'openReply', false)
    },
    //打开回复下面的回复框
    openReplySon(reply){
      this.$set(reply,'openReply',true)
      this.$set(reply,'placeholder','回复'+ reply.fromUserNickName)
    },
    closeReplySon(reply){
      this.$set(reply,'openReply',false)
    },

2-5、发送回复,动态更新评论区的回复区

    sentReply(com, index) {
      //先判断有没有登录
      if (this.userInfo === null) {
        //没有登录
        this.$message({
          message: '请先登陆哦~',
          type: 'warning'
        })
        this.$loginToast.open()
        return
      }
      let reply = {
        commentId: com.id,
        content: com.replyContent,
        replyTimeRes: '一秒前',
        fromUserid: this.userInfo.id,
        toUserid: com.fromUserid,
        fromUserAvatarUrl: this.userInfo.avatarUrl,
        fromUserNickName: this.userInfo.nickName,
        toUserNickName: com.nickName,
        canInputReply: 200,
        placeholder: '',
        replyContent: ''
      }
      //更新当前视图
      this.comments[index].replyies.push(reply)
      this.$http.post('/circle/reply/sentReply', reply).then(res => {
        console.log(res)
        if (res.data.code === 200){
          //关闭回复框
          this.$set(com,'replyContent','')
          this.$set(com,'openReply',false)
        }
      })
    },

2-6、评论区的样式表,回复区的样式比较多

.commentDrawer {
  background-color: var(--li-bg-color);
  color: var(--text-color);
  position: fixed;
  z-index: 2005;
  width: 30%;
  height: 100%;
  right: 0;
  top: 0;
  border-top-left-radius: 14px;
  border-bottom-left-radius: 14px;
}

.inputComment {
  margin-top: 5%;
  width: 88%;
  margin-left: 3%;
  position: relative;
  height: 185px;
  border-radius: 13px;
  padding: 3%;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputComment textarea {
  width: 98%;
  position: relative;
  height: 150px;
  border: 0 solid;
  outline: none;
  resize: none;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputComment .sent {
  border-radius: 30px;
  background-color: #ee464b;
  color: white;
  padding: 2px 15px;
  font-size: 16px;
  float: right;
  cursor: pointer;
  height: 25px;
}

.comment-list {
  width: 100%;
  border-radius: 14px;
  margin-top: 4%;
  height: 72%;
  position: relative;
}

.comment-ul {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 99%;
  position: relative;
  list-style-type: none;
  overflow: auto;
}

.comment-li {
  margin: 2%;
  float: left;
  width: 96%;
  position: relative;
  overflow: auto;
}

.comment-li-top {
  display: flex;
  float: left;
  align-items: center;
  width: 90%;
}

.comment-li-top span {
  margin-left: 8px;
}

.comment-li img {
  width: 35px;
  height: 35px;
  border-radius: 50%;
}

.comment-li-content {
  margin-left: 7%;
  width: 86%;
  font-size: 18px;
  clear: left;
  float: left;
  padding: 5px;
  cursor: pointer;
  overflow: auto;
}

.comment-nickName {
  font-size: 18px;
  color: #2073e3;
}

.inputReply {
  float: left;
  margin-top: 1%;
  width: 76%;
  margin-left: 8%;
  position: relative;
  height: 150px;
  border-radius: 13px;
  padding: 2%;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputReply textarea {
  width: 98%;
  position: relative;
  height: 120px;
  border: 0 solid;
  outline: none;
  resize: none;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputReply .sent {
  border-radius: 30px;
  background-color: #ee464b;
  color: white;
  padding: 2px 15px;
  font-size: 16px;
  float: right;
  cursor: pointer;
  height: 25px;
}
.reply-ul {
  margin: 0;
  padding: 0;
  width: 100%;
  position: relative;
  list-style-type: none;
  overflow: auto;
}

.reply-li {
  margin-left: 7%;
  margin-top: 1%;
  float: left;
  width: 92%;
  position: relative;
  overflow: auto;
}

.reply-li-top {
  display: flex;
  float: left;
  align-items: center;
  width: 90%;
}
.reply-li img {
  width: 35px;
  height: 35px;
  border-radius: 50%;
}

.reply-li-content {
  margin-left: 8%;
  width: 82%;
  font-size: 16px;
  clear: left;
  float: left;
  padding: 5px;
  cursor: pointer;
}

.reply-nickName {
  font-size: 16px;
  color: #2073e3;
}

.inputReplySon {
  float: left;
  margin-top: 1%;
  width: 69%;
  margin-left: 9%;
  position: relative;
  height: 150px;
  border-radius: 13px;
  padding: 2%;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputReplySon textarea {
  width: 93%;
  position: relative;
  height: 120px;
  border: 0 solid;
  outline: none;
  resize: none;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputReplySon .sent {
  border-radius: 30px;
  background-color: #ee464b;
  color: white;
  padding: 2px 13px;
  font-size: 13px;
  float: right;
  cursor: pointer;
  height: 22px;
}

2-7、整个页面代码,包括动态列表和评论区

<template>
  <div>
    <div class="main">
      <div>
        <el-button type="primary" @click="publish" style="position: fixed;right: 20%">发布动态el-button>
        <ul class="list" v-infinite-scroll="load" style="overflow:auto" infinite-scroll-immediate="false"
            v-loading="loading">
          <li v-for="(item,index) in articles" class="list-item">
            <div class="list-top">
              <img :src="item.publisherAvatarUrl"/>
              <div style="margin-left: 6px;color: #2073e3;float: left">
                <span style="float: left">{{ item.publisherNickName }}span>
                <div style="width: 200px">div>
                <span style="float: left;font-size: 15px;align-items: center;display: flex;color: #a426ce">
                  {{ item.publisherSchool }}
                  
                  <img v-if="item.publisherGender===1" style="width: 26px;height: 26px;margin-left: 5px"
                       src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/%E6%80%A7%E5%88%AB_%E7%94%B7.png"/>
                  <img v-if="item.publisherGender===2" style="width: 26px;height: 26px;margin-left: 5px"
                       src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/%E6%80%A7%E5%88%AB-%E5%A5%B3.png"/>
                span>
              div>
            div>

            <div class="list-main">
              <div class="list-main-text">
                <p>{{ item.content }}p>
              div>
              <div class="list-main-img">
                <div v-for="img in item.imgUrls">
                  <el-image :src="img" fit="cover"
                            :preview-src-list="item.imgUrls" @click="addClick(item.id)">el-image>
                div>
              div>
              <div style="width: 100%">div>
            div>

            <div class="list-bottom">
              <span style="font-size: 17px;text-align: left;">{{ item.publishTimeRes }}span>
              <span>
                浏览{{ item.click }}次
              span>
              
              <span @click="toComment(item.id)">
                <img
                    src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/icon/%E8%AF%84%E8%AE%BA%E5%8C%BA%20%281%29.png"/>
                <span style="font-size: 26px">{{ item.comment }}span>
              span>
              
              <span>
                <img v-if="!item.isLike" @click="doLike(item.id,index)"
                     src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/icon/%E7%82%B9%E8%B5%9E%20%281%29.png"/>
                <img v-if="item.isLike" @click="cancelLike(item.id,index)"
                     src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/icon/%E7%82%B9%E8%B5%9E_%E5%9D%97%20%281%29.png"/>
                <span style="font-size: 26px">{{ item.zan }}span>
              span>
            div>
          li>
        ul>
      div>
    div>

    <publish-dia ref="dia">publish-dia>
    
    
    <div v-if="drawer" class="commentDrawer">
      <span>评论区span>
      <span @click="drawer = false" style="cursor: pointer;float: left;margin-left: 10px">关闭span>
      
      <div class="inputComment">
        <textarea v-model="textareaContent" placeholder="说点什么..." maxlength="200" @input="calcInput">
        textarea>
        <span style="font-size: 14px;float: left">还可以输入{{ canInputText }}个字符span>
        <span class="sent" @click="sentComment">发送span>
      div>
      
      <div class="comment-list">
        <ul class="comment-ul">
          <li class="comment-li" v-for="(com,index) in comments">
            <div class="comment-li-top">
              <img :src="com.avatarUrl"/>
              <span class="comment-nickName">{{ com.nickName }}span>
              <span style="font-size: 15px;margin-left: auto">{{ com.commentTimeRes }}span>
              <span v-if="!com.openReply"
                    style="cursor: pointer;margin-left:auto;font-size: 16px" @click="openReply(com,index)">
                回复span>
              <span v-if="com.openReply"
                    style="cursor: pointer;margin-left: auto;font-size: 16px" @click="closeReply(index)">
                收起span>
            div>

            <div class="comment-li-content" @click="openReply(com,index)">
              <span style="text-align: left;float: left;cursor: pointer">{{ com.content }}span>
            div>
            <div class="inputReply" v-if="com.openReply">
              <textarea v-model="com.replyContent" :placeholder="com.placeholder" maxlength="200"
                        @input="calcInputReply(index)">
              textarea>
              <span style="font-size: 14px;float: left">还可以输入{{ com.canInputReply }}个字符span>
              <span class="sent" @click="sentReply(com,index)">回复span>
            div>
            
            <ul class="reply-ul">
              <li class="reply-li" v-for="(reply,index1) in com.replyies">
                <div class="reply-li-top">
                  <img :src="reply.fromUserAvatarUrl"/>
                  <span class="reply-nickName">{{ reply.fromUserNickName }}
                    <span style="font-size: 15px;margin: 3px;color: var(--text-color)">回复span>
                    {{reply.toUserNickName}}span>
                  <span style="margin-left: auto;font-size: 13px">{{ reply.replyTimeRes }}span>
                  <span v-if="!reply.openReply"
                        style="cursor: pointer;margin-left:auto;font-size: 14px" @click="openReplySon(reply)">
                  回复span>
                  <span v-if="reply.openReply"
                        style="cursor: pointer;margin-left: auto;font-size: 14px" @click="closeReplySon(reply)">
                  收起span>
                div>
                <div class="reply-li-content" @click="openReplySon(reply)">
                  <span style="float: left">{{ reply.content }}span>
                div>
                
                <div class="inputReplySon" v-show="reply.openReply">
                  <textarea v-model="reply.replyContent" :placeholder="reply.placeholder" maxlength="200"
                        @input="calcReplySon(index,index1)">
                  textarea>
                  <span style="font-size: 12px;float: left">还可以输入{{ reply.canInputReply }}个字符span>
                  <span class="sent" @click="sentReplySon(com,reply)">回复span>
                div>
              li>
            ul>
          li>
        ul>
      div>
    div>
  div>
template>

<script>
import publishDia from "@/views/gaoxiaoquan/publishDia";


export default {
  components: {
    publishDia,
  },
  name: "UniversityCircle",
  data() {
    return {
      articles: [],
      page: 0,
      limit: 20,
      loading: false,
      userInfo: {},
      clickArticleId: 0,
      drawer: false,
      textareaContent: '',
      canInputText: 200,
      comments: [
        {
          id: '0',
          replyies: [{
            fromUserNickName: '',
            toUserNickName: '',
            fromUserAvatarUrl: '',
            content: '',
            replyTimeRes: '',
            commentId: '',
            fromUserid: '',
            toUserid: '',
            replyContent: '',
            placeholder: '',
            openReply: false,
            canInputReply: 200
          }],
          commentTimeRes: '',
          openReply: false,
          placeholder: '',
          replyContent: '',
          canInputReply: 200,
        }
      ]
    }
  },
  created() {
    let userStatus = JSON.parse(localStorage.getItem('userInfo'))
    if (userStatus != null) {
      this.userInfo = userStatus.user
    } else {
      this.userInfo = null
    }
    this.load()
  },
  methods: {
    publish() {
      if(this.userInfo === null){
        this.$message({
          message: '请先登录',
          type: 'warning'
        })
        this.$loginToast.open()
        return
      }
      this.$refs.dia.open()
    },
    load() {
      //分页获取数据
      if (this.articles.length % 20 === 0) {
        this.loading = true
        this.$http.post('/circle/article/getArticles',
            {
              page: this.page,
              limit: this.limit,
              userId: this.userInfo === null ? '0' : this.userInfo.id
            }).then(res => {
          if (res.data.code === 200) {
            for (let i = 0; i < res.data.articles.length; i++) {
              this.articles.push(res.data.articles[i])
            }
            if (res.data.articles.length >= 20) {
              this.page += 20;
            }
          }
          console.log('返回', res)
          this.loading = false
        })
      } else {
        this.$message({
          message: '我是有底线哒~',
          type: 'warning',
          offset: 900,
          center: true
        })
      }
    },
    //点赞
    doLike(articleId, index) {
      //先判断有没有登录
      if (this.userInfo === null) {
        //没有登录
        this.$message({
          message: '登陆后才能点赞哦~',
          type: 'warning'
        })
        this.$loginToast.open()
      } else {
        console.log('articleId', articleId)
        let userId = this.userInfo.id
        this.$http.post('circle/zan/doLike', {
          articleId: articleId,
          userId: userId
        }).then(res => {
          console.log(res)
          this.articles[index].zan++
          this.articles[index].isLike = true
        })
      }
    },
    //取消点赞
    cancelLike(articleId, index) {
      //先判断有没有登录
      if (this.userInfo === null) {
        //没有登录
        this.$message({
          message: '请先登陆哦~',
          type: 'warning'
        })
        this.$loginToast.open()
      } else {
        let userId = this.userInfo.id
        this.$http.post('circle/zan/cancelLike', {
          articleId: articleId,
          userId: userId
        }).then(res => {
          console.log(res)
          this.articles[index].zan--
          this.articles[index].isLike = false
        })
      }
    },
    //增加浏览量
    addClick(id){
      console.log('id',id)
      this.$http.post('/circle/article/addClick',{
        articleId: id
      })
    },

    //跳转评论详情
    toComment(id) {
      this.drawer = false
      this.clickArticleId = id
      setTimeout(() => {
        this.drawer = true
      }, 100)
      //获取该动态所有评论
      this.$http.post('/circle/comment/getComment', {
        articleId: id,
        type: 'article'
      }).then(res => {
        console.log('评论', res)
        this.comments = res.data.comments
      })
    },
    //计算输入字数
    calcInput() {
      let len = this.textareaContent.length
      this.canInputText = 200 - len;
    },
    calcInputReply(index) {
      let len = this.comments[index].replyContent.length
      this.$set(this.comments[index], 'canInputReply', 200 - len)
    },
    calcReplySon(index,index1){
      let len = this.comments[index].replyies[index1].replyContent.length
      this.$set(this.comments[index].replyies[index1],'canInputReply',200 -len)
    },
    //发表评论
    sentComment() {
      if (this.userInfo === null) {
        //没有登录
        this.$message({
          message: '请先登陆哦~',
          type: 'warning'
        })
        this.$loginToast.open()
        return
      }
      let articleId = this.clickArticleId
      let userId = this.userInfo.id
      let type = 'article'
      let content = this.textareaContent
      let comment = {
        type: type,
        composeId: articleId,
        content: content,
        fromUserid: userId
      }
      //请求
      this.$http.post('/circle/comment/addComment', comment).then(res => {
        if (res.data.code === 200) {
          this.$message({
            message: '发送成功',
            type: 'success'
          })
          this.textareaContent = ''
        } else {
          this.$message({
            message: '发送失败',
            type: 'error'
          })
        }
      })
      //用现有数据更新当前评论区
      let addComment = {
        avatarUrl: this.userInfo.avatarUrl,
        nickName: this.userInfo.nickName,
        content: this.textareaContent,
        replyies: [],
        openReply: false,
        commentTimeRes: '1秒前',
        placeholder: '',
        replyContent: '',
        canInputReply: 200,
      }
      this.comments.push(addComment)
    },
    //打开回复框
    openReply(com) {
      //打开该评论的回复框
      this.$set(com, 'openReply', true)
      this.$set(com, 'placeholder', '回复@' + com.nickName)
    },
    closeReply(index) {
      this.$set(this.comments[index], 'openReply', false)
    },
    //打开回复下面的回复框
    openReplySon(reply){
      this.$set(reply,'openReply',true)
      this.$set(reply,'placeholder','回复'+ reply.fromUserNickName)
    },
    closeReplySon(reply){
      this.$set(reply,'openReply',false)
    },
    sentReply(com, index) {
      //先判断有没有登录
      if (this.userInfo === null) {
        //没有登录
        this.$message({
          message: '请先登陆哦~',
          type: 'warning'
        })
        this.$loginToast.open()
        return
      }
      let reply = {
        commentId: com.id,
        content: com.replyContent,
        replyTimeRes: '一秒前',
        fromUserid: this.userInfo.id,
        toUserid: com.fromUserid,
        fromUserAvatarUrl: this.userInfo.avatarUrl,
        fromUserNickName: this.userInfo.nickName,
        toUserNickName: com.nickName,
        canInputReply: 200,
        placeholder: '',
        replyContent: ''
      }
      //更新当前视图
      this.comments[index].replyies.push(reply)
      this.$http.post('/circle/reply/sentReply', reply).then(res => {
        console.log(res)
        if (res.data.code === 200){
          //关闭回复框
          this.$set(com,'replyContent','')
          this.$set(com,'openReply',false)
        }
      })
    },
    //发送回复下面的回复
    sentReplySon(com,parentReply){
      //先判断有没有登录
      if (this.userInfo === null) {
        //没有登录
        this.$message({
          message: '请先登陆哦~',
          type: 'warning'
        })
        this.$loginToast.open()
        return
      }
      let reply = {
        commentId: com.id,
        content: parentReply.replyContent,
        replyTimeRes: '一秒前',
        fromUserid: this.userInfo.id,
        toUserid: parentReply.fromUserid,
        fromUserAvatarUrl: this.userInfo.avatarUrl,
        fromUserNickName: this.userInfo.nickName,
        toUserNickName: parentReply.fromUserNickName,
        canInputReply: 200,
        placeholder: '',
        replyContent: ''
      }
      com.replyies.push(reply)
      this.$http.post('/circle/reply/sentReply',reply).then(res => {
        console.log(res)
        if (res.data.code === 200){
          //关闭回复框
          this.$set(parentReply,'replyContent','')
          this.$set(parentReply,'openReply',false)
        }
      })
    }
  }
}
script>

<style scoped>
.main {
  width: 70%;
  position: absolute;
  left: 15%;
  top: 4%;
  height: 950px;
  background-color: var(--main-bg-color);
  border-radius: 13px;
}

.list {
  margin-top: 0;
  margin-left: 15%;
  list-style-type: none;
  width: 83%;
  height: 940px;
}

.list-item {
  margin: 3px;
  color: var(--text-color);
  border-radius: 10px;
  padding: 5px;
  float: left;
  width: 90%;
}

.list-top img {
  width: 45px;
  height: 48px;
  border-radius: 50%;
  object-fit: fill;
}

.list-top {
  display: flex;
  align-items: center;
  width: 300px;
  flex-wrap: wrap;
}

.list-main {
  float: left;
  display: flex;
  flex-wrap: wrap;
}

.list-main-text {
  text-align: left;
  float: left;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 700px;
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
}

.list-main-img {
  margin-top: 6px;
  float: left;
  display: flex;
  flex-wrap: wrap;
  width: 600px;
}

.list-main-img .el-image {
  width: 195px;
  height: 195px;
  border-radius: 8px;
  margin: 2px;
}

.list-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  width: 600px;
  height: 40px;

}

.list-bottom img {
  width: 30px;
  height: 30px;
  margin-top: 5px;
}

.commentDrawer {
  background-color: var(--li-bg-color);
  color: var(--text-color);
  position: fixed;
  z-index: 2005;
  width: 30%;
  height: 100%;
  right: 0;
  top: 0;
  border-top-left-radius: 14px;
  border-bottom-left-radius: 14px;
}

.inputComment {
  margin-top: 5%;
  width: 88%;
  margin-left: 3%;
  position: relative;
  height: 185px;
  border-radius: 13px;
  padding: 3%;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputComment textarea {
  width: 98%;
  position: relative;
  height: 150px;
  border: 0 solid;
  outline: none;
  resize: none;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputComment .sent {
  border-radius: 30px;
  background-color: #ee464b;
  color: white;
  padding: 2px 15px;
  font-size: 16px;
  float: right;
  cursor: pointer;
  height: 25px;
}

.comment-list {
  width: 100%;
  border-radius: 14px;
  margin-top: 4%;
  height: 72%;
  position: relative;
}

.comment-ul {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 99%;
  position: relative;
  list-style-type: none;
  overflow: auto;
}

.comment-li {
  margin: 2%;
  float: left;
  width: 96%;
  position: relative;
  overflow: auto;
}

.comment-li-top {
  display: flex;
  float: left;
  align-items: center;
  width: 90%;
}

.comment-li-top span {
  margin-left: 8px;
}

.comment-li img {
  width: 35px;
  height: 35px;
  border-radius: 50%;
}

.comment-li-content {
  margin-left: 7%;
  width: 86%;
  font-size: 18px;
  clear: left;
  float: left;
  padding: 5px;
  cursor: pointer;
  overflow: auto;
}

.comment-nickName {
  font-size: 18px;
  color: #2073e3;
}

.inputReply {
  float: left;
  margin-top: 1%;
  width: 76%;
  margin-left: 8%;
  position: relative;
  height: 150px;
  border-radius: 13px;
  padding: 2%;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputReply textarea {
  width: 98%;
  position: relative;
  height: 120px;
  border: 0 solid;
  outline: none;
  resize: none;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputReply .sent {
  border-radius: 30px;
  background-color: #ee464b;
  color: white;
  padding: 2px 15px;
  font-size: 16px;
  float: right;
  cursor: pointer;
  height: 25px;
}
.reply-ul {
  margin: 0;
  padding: 0;
  width: 100%;
  position: relative;
  list-style-type: none;
  overflow: auto;
}

.reply-li {
  margin-left: 7%;
  margin-top: 1%;
  float: left;
  width: 92%;
  position: relative;
  overflow: auto;
}

.reply-li-top {
  display: flex;
  float: left;
  align-items: center;
  width: 90%;
}
.reply-li img {
  width: 35px;
  height: 35px;
  border-radius: 50%;
}

.reply-li-content {
  margin-left: 8%;
  width: 82%;
  font-size: 16px;
  clear: left;
  float: left;
  padding: 5px;
  cursor: pointer;
}

.reply-nickName {
  font-size: 16px;
  color: #2073e3;
}

.inputReplySon {
  float: left;
  margin-top: 1%;
  width: 69%;
  margin-left: 9%;
  position: relative;
  height: 150px;
  border-radius: 13px;
  padding: 2%;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputReplySon textarea {
  width: 93%;
  position: relative;
  height: 120px;
  border: 0 solid;
  outline: none;
  resize: none;
  background-color: var(--main-bg-color);
  color: var(--text-color);
}

.inputReplySon .sent {
  border-radius: 30px;
  background-color: #ee464b;
  color: white;
  padding: 2px 13px;
  font-size: 13px;
  float: right;
  cursor: pointer;
  height: 22px;
}
style>

3、后端代码,包括获取所有动态和动态评论区代码

3-1、与article 有关的代码

3-1-1、Controller类
package cn.us.guiyoucircle.controller;


import cn.hutool.core.lang.hash.Hash;
import cn.us.guiyoucircle.entity.Article;
import cn.us.guiyoucircle.mapper.ArticleMapper;
import cn.us.guiyoucircle.service.ArticleService;
import cn.us.guiyoucircle.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import org.springframework.stereotype.Controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 

* 前端控制器 *

* * @author 李石林 * @since 2022-08-20 */
@RestController @RequestMapping("//circle/article") public class ArticleController { @Autowired(required = false) private ArticleMapper articleMapper; @Autowired private ArticleService articleService; @PostMapping("/publish") public R publish(@RequestBody Article article){ boolean flag = articleService.publishArticle(article); if (!flag){ return R.error(); } return R.ok(); } @PostMapping("/getArticles") public R getArticles(@RequestBody HashMap<String,Object> map){ int page = (int) map.get("page"); int limit = (int) map.get("limit"); long userId =Long.parseLong((String) map.get("userId")); List<Article> articles = articleService.getArticlePage(page,limit,userId); return R.ok().put("articles",articles); } @PostMapping("addClick") public R addClick(@RequestBody HashMap<String,String> map){ long articleId = Long.parseLong(map.get("articleId")); this.articleMapper.addClick(articleId); return R.ok(); } @PostMapping("/getUserArticle") public R getUserArticle(@RequestBody Map<String,String> map){ long userId = Long.parseLong(map.get("userId")); List<Article> articles = this.articleService.getUserArticles(userId); return R.ok().put("articles",articles); } @PostMapping("/updateLock") public R updateLock(@RequestBody Map<String,String> map){ boolean lock = Boolean.parseBoolean(map.get("lock")); int flag = lock? 1:0; long articleId = Long.parseLong(map.get("articleId")); int f = this.articleMapper.updateLock(flag,articleId); if(f==0){ return R.error(); } return R.ok(); } @PostMapping("/deleteArticle") public R deleteArticle(@RequestBody Map<String,String> map){ long articleId = Long.parseLong(map.get("articleId")); this.articleService.removeById(articleId); return R.ok(); } }
3-1-2、Server实现类
package cn.us.guiyoucircle.service.impl;

import cn.us.guiyoucircle.entity.Article;
import cn.us.guiyoucircle.entity.ArticleImg;
import cn.us.guiyoucircle.entity.User;
import cn.us.guiyoucircle.mapper.ArticleImgMapper;
import cn.us.guiyoucircle.mapper.ArticleMapper;
import cn.us.guiyoucircle.mapper.CommentMapper;
import cn.us.guiyoucircle.mapper.ZanMapper;
import cn.us.guiyoucircle.service.ArticleImgService;
import cn.us.guiyoucircle.service.ArticleService;
import cn.us.guiyoucircle.service.UserService;
import cn.us.guiyoucircle.util.DateTimeResult;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 

* 服务实现类 *

* * @author 李石林 * @since 2022-08-20 */
@Service public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService { @Autowired(required = false) ArticleMapper articleMapper; @Autowired(required = false) ArticleImgMapper articleImgMapper; @Autowired UserService userService; @Autowired(required = false) CommentMapper commentMapper; @Autowired DateTimeResult dateTimeResult; @Autowired(required = false) ZanMapper zanMapper; @Autowired private ArticleImgService articleImgService; @Override public boolean publishArticle(Article article) { System.out.println(article); //取出文章的所以图片地址 List<String> imgUrls = article.getImgUrls(); System.out.println("图片"+imgUrls); //获取当前时间 LocalDateTime publishTime = LocalDateTime.now(); article.setPublishTime(publishTime); int f = this.baseMapper.insert(article); if (f != 0){ //插入成功,存图片 Long articleId = article.getId(); List<ArticleImg> list = new ArrayList<>(); if(!imgUrls.isEmpty()){ for(String s:imgUrls){ ArticleImg img = new ArticleImg(); img.setArticleId(articleId); img.setImgurl(s); list.add(img); } boolean flag = this.articleImgService.saveBatch(list); return flag; } return true; } return false; } @Override public List<Article> getArticlePage(int page, int limit, long userId) { //分页获取 List<Article> list = this.articleMapper.getArticlesPage(page,limit); return articleListRes(list,userId); } @Override public List<Article> getUserArticles(long userId) { //获取该用户的动态 List<Article> articles = this.articleMapper.getUserArticles(userId); return articleListRes(articles,userId); } //封装一个返回动态的方法 public List<Article> articleListRes(List<Article> list,long userId){ List<Article> newList = new ArrayList<>(); for(Article article : list){ //查这篇动态的图片 Long id = article.getId(); Long publisherId = article.getPublisherId(); List<String> imgUrls = this.articleImgMapper.getImgs(id); article.setImgUrls(imgUrls); String publishTimeRes = dateTimeResult.getTime(article.getPublishTime()); article.setPublishTimeRes(publishTimeRes); User user = userService.getById(publisherId); article.setPublisherNickName(user.getNickName()); article.setPublisherAvatarUrl(user.getAvatarUrl()); article.setPublisherSchool(user.getSchool()); article.setPublisherGender(user.getGender()); //查询这篇动态的点赞数 long zan = (long) this.zanMapper.getArticleZan(id); //查询评论数 long comment = (long) this.commentMapper.getCommentCount(id,"article"); //查询该用户是否点赞了该文章 Integer likeStatus = (Integer) this.zanMapper.isLike(id,userId); boolean isLike = (likeStatus != null && likeStatus == 1); article.setZan(zan); article.setComment(comment); article.setIsLike(isLike); newList.add(article); } return newList; } }

3-2、与评论有关的代码

3-2-1、Controller类
package cn.us.guiyoucircle.controller;


import cn.us.guiyoucircle.entity.Comment;
import cn.us.guiyoucircle.service.CommentService;
import cn.us.guiyoucircle.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

/**
 * 

* 前端控制器 *

* * @author 李石林 * @since 2022-08-20 */
@RestController @RequestMapping("//circle/comment") public class CommentController { @Autowired CommentService commentService; @PostMapping("/addComment") public R addComment(@RequestBody Comment comment){ comment.setCommentTime(LocalDateTime.now()); boolean f = this.commentService.save(comment); if(!f){ return R.error(); } return R.ok(); } @PostMapping("/getComment") public R getComment(@RequestBody Map<String,String> map){ long articleId = Long.parseLong(map.get("articleId")); System.out.println(articleId); String type = map.get("type"); List<Comment> comments = this.commentService.getComment(articleId,type); return R.ok().put("comments",comments); } }
3-2-2、server实现类
package cn.us.guiyoucircle.service.impl;

import cn.hutool.core.date.DateTime;
import cn.us.guiyoucircle.entity.Comment;
import cn.us.guiyoucircle.entity.Reply;
import cn.us.guiyoucircle.entity.User;
import cn.us.guiyoucircle.mapper.CommentMapper;
import cn.us.guiyoucircle.mapper.ReplyMapper;
import cn.us.guiyoucircle.service.CommentService;
import cn.us.guiyoucircle.service.UserService;
import cn.us.guiyoucircle.util.DateTimeResult;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 

* 服务实现类 *

* * @author 李石林 * @since 2022-08-20 */
@Service public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService { @Autowired UserService userService; @Autowired(required = false) CommentMapper commentMapper; @Autowired(required = false) ReplyMapper replyMapper; @Autowired DateTimeResult dateTimeResult; @Override public List<Comment> getComment(long articleId, String type) { List<Comment> comments = commentMapper.getComment(type,articleId); List<Comment> commentsList = new ArrayList<>(); //对每一条评论,查找该评论的所有回复 for(Comment c: comments){ long userId = c.getFromUserid(); User user = userService.getById(userId); c.setAvatarUrl(user.getAvatarUrl()); c.setNickName(user.getNickName()); //处理评论时间 String commentTimeRes = this.dateTimeResult.getTime(c.getCommentTime()); c.setCommentTimeRes(commentTimeRes); c.setCanInputReply(200); long commentId = c.getId(); //通过评论id区回复表找回复 List<Reply> replies = this.replyMapper.getReply(commentId); List<Reply> replyList = new ArrayList<>(); //对每一条回复,设置头像和昵称 for(Reply r: replies){ long fromUserid = r.getFromUserid(); long toUserid = r.getToUserid(); String replyTimeRes = this.dateTimeResult.getTime(r.getReplyTime()); r.setReplyTimeRes(replyTimeRes); User fromUser = this.userService.getById(fromUserid); User toUser = this.userService.getById(toUserid); r.setFromUserNickName(fromUser.getNickName()); r.setToUserNickName(toUser.getNickName()); r.setFromUserAvatarUrl(fromUser.getAvatarUrl()); r.setCanInputReply(200); replyList.add(r); } c.setReplyies(replyList); //将封装好的回复添加到list集合 commentsList.add(c); } //返回封装好的评论 return commentsList; } }

3-3、与回复有关的代码

3-3-1、Controller类
package cn.us.guiyoucircle.controller;

import cn.us.guiyoucircle.entity.Reply;
import cn.us.guiyoucircle.service.ReplyService;
import cn.us.guiyoucircle.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

/**
 * 

* 前端控制器 *

* * @author 李石林 * @since 2022-08-20 */
@RestController @RequestMapping("//circle/reply") public class ReplyController { @Autowired private ReplyService replyService; @PostMapping("/sentReply") public R sentReply(@RequestBody Reply reply){ reply.setReplyTime(LocalDateTime.now()); boolean f = this.replyService.save(reply); if (f){ return R.ok(); } return R.error(); } }

就这样。

你可能感兴趣的:(Vue,vue.js,javascript,前端)