利用Vue+ElementUi实现评论、回复功能-仿掘金

利用Vue+ElementUi实现评论功能-仿掘金

(在参考博客的基础上修改css语法为scss,并且实现了点赞和评论数字相应变化功能)

效果展示

你好! 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

实现代码

comment.vue

<template>
  <div>
    <div v-clickoutside="hideReplyBtn" @click="inputFocus" class="my-reply">
      <el-avatar class="header-img" :size="40" :src="myHeader"></el-avatar>
      <div class="reply-info">
        <div
          tabindex="0"
          contenteditable="true"
          id="replyInput"
          spellcheck="false"
          placeholder="输入评论..."
          class="reply-input"
          @focus="showReplyBtn"
          @input="onDivInput($event)"
        ></div>
      </div>
      <div class="reply-btn-box" v-show="btnShow">
        <el-button
          class="reply-btn"
          size="medium"
          @click="sendComment"
          type="primary"
        >
          发表评论
        </el-button>
      </div>
    </div>
    <div
      v-for="(item, i) in comments"
      :key="i"
      class="author-title reply-father"
    >
      <el-avatar class="header-img" :size="40" :src="item.headImg"></el-avatar>
      <div class="author-info">
        <span class="author-name">{{ item.name }}</span>
        <span class="author-time">{{ item.time }}</span>
      </div>
      <div class="icon-btn">
        <span @click="showReplyInput(i, '0',item.name, item.id)">
          <i class="iconfont el-icon-s-comment"></i>
          {{ item.commentNum }}
        </span>
        <!-- <span  @click="addlikeNumber(i, item.from, item.id)">
        <i class="iconfont el-icon-caret-top"></i>
        {{ item.like }}
        </span> -->
        <span class="xin" @click="countlikeNumber('1',i,item.id)">
          <i class="iconfont el-icon-caret-top " v-if="item.isLike">&#xe607;</i>
          <i class="iconfont el-icon-caret-top" v-else>&#xe68b;</i>
          {{ item.like }}
        </span>
      </div>
      <div class="talk-box">
        <p>
          <span class="reply">{{ item.comment }}</span>
        </p>
      </div>
      <div class="reply-box">
        <div v-for="(reply, j) in item.reply" :key="j" class="author-title">
          <el-avatar
            class="header-img"
            :size="40"
            :src="reply.fromHeadImg"
          ></el-avatar>
          <div class="author-info">
            <span class="author-name">{{ reply.from }}</span>
            <span class="author-time">{{ reply.time }}</span>
          </div>
          <div class="icon-btn">
            <span @click="showReplyInput(i,j, reply.from, reply.id)">
              <i class="iconfont el-icon-s-comment"></i>
              {{ reply.commentNum }}
            </span>
            <span  @click="countlikeNumber('2',i,j,reply)">
            <i class="iconfont el-icon-caret-top"></i>
            {{ reply.like }}
            </span>
          </div>
          <div class="talk-box">
            <p>
              <span>回复 {{ reply.to }}:</span>
              <span class="reply">{{ reply.comment }}</span>
            </p>
          </div>
          <div class="reply-box"></div>
        </div>
      </div>
      <div v-show="_inputShow(i)" class="my-reply my-comment-reply">
        <el-avatar class="header-img" :size="40" :src="myHeader"></el-avatar>
        <div class="reply-info">
          <div
            tabindex="0"
            contenteditable="true"
            spellcheck="false"
            placeholder="输入评论..."
            @input="onDivInput($event)"
            class="reply-input reply-comment-input"
          ></div>
        </div>
        <div class="reply-btn-box">
          <el-button
            class="reply-btn"
            size="medium"
            @click="sendCommentReply(i)"
            type="primary"
          >
            发表回复
          </el-button>
        </div>
      </div>
    </div>
  </div>
</template>

js

const clickoutside = {
    // 初始化指令
    bind(el, binding, vnode) {
      function documentHandler(e) {
        // 这里判断点击的元素是否是本身,是本身,则返回
        if (el.contains(e.target)) {
          return false
        }
        // 判断指令中是否绑定了函数
        if (binding.expression) {
          // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
          binding.value(e)
        }
      }
      // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
      el.vueClickOutside = documentHandler
      document.addEventListener('click', documentHandler)
    },
    update() {},
    unbind(el, binding) {
      // 解除事件监听
      document.removeEventListener('click', el.vueClickOutside)
      delete el.vueClickOutside
    },
  }
  export default {
    name: 'ArticleComment',
    data() {
      return {
        btnShow: false,
        index: '0',
        replyComment: '',
        subIndex:'0',
        myName: 'Lana Del Rey',
        myHeader:
          'https://ae01.alicdn.com/kf/Hd60a3f7c06fd47ae85624badd32ce54dv.jpg',
        myId: 19870621,
        to: '',
        toId: -1,
        comments: [
          {
            name: 'Lana Del Rey',
            id: 19870621,
            headImg:
              'https://ae01.alicdn.com/kf/Hd60a3f7c06fd47ae85624badd32ce54dv.jpg',
            comment: '我发布一张新专辑Norman Fucking Rockwell,大家快来听啊',
            time: '2019年9月16日 18:43',
            commentNum: 2,
            like: 15,
            isLike:false,
            likeListId:[],//存放已点赞用户的id
            inputShow: false,
            reply: [
              {
                from: 'Taylor Swift',
                fromId: 19891221,
                fromHeadImg:
                  'https://ae01.alicdn.com/kf/H94c78935ffa64e7e977544d19ecebf06L.jpg',
                to: 'Lana Del Rey',
                toId: 19870621,
                comment: '我很喜欢你的新专辑!!',
                time: '2019年9月16日 18:43',
                commentNum: 1,
                like: 15,
                isLike:false,
                likeListId:[],
                inputShow: false,
              },
              {
                from: 'Ariana Grande',
                fromId: 1123,
                fromHeadImg:
                  'https://ae01.alicdn.com/kf/Hf6c0b4a7428b4edf866a9fbab75568e6U.jpg',
                to: 'Lana Del Rey',
                toId: 19870621,
                comment: '别忘记宣传我们的合作单曲啊',
                time: '2019年9月16日 18:43',
                commentNum: 0,
                like: 5,
                isLike:false,
                likeListId:[],
                inputShow: false,
              },
            ],
          },
          {
            name: 'Taylor Swift',
            id: 19891221,
            headImg:
              'https://ae01.alicdn.com/kf/H94c78935ffa64e7e977544d19ecebf06L.jpg',
            comment: '我发行了我的新专辑Lover',
            time: '2019年9月16日 18:43',
            commentNum: 1,
            like: 5,
            likeListId:[],
            inputShow: false,
            reply: [
              {
                from: 'Lana Del Rey',
                fromId: 19870621,
                fromHeadImg:
                  'https://ae01.alicdn.com/kf/Hd60a3f7c06fd47ae85624badd32ce54dv.jpg',
                to: 'Taylor Swift',
                toId: 19891221,
                comment: '新专辑和speak now 一样棒!',
                time: '2019年9月16日 18:43',
                commentNum: 25,
                like: 5,
                isLike:false,
                likeListId:[],
                inputShow: false,
              },
            ],
          },
          {
            name: 'Norman Fucking Rockwell',
            id: 20190830,
            headImg:
              'https://ae01.alicdn.com/kf/Hdd856ae4c81545d2b51fa0c209f7aa28Z.jpg',
            comment: 'Plz buy Norman Fucking Rockwell on everywhere',
            time: '2019年9月16日 18:43',
            commentNum: 0,
            like: 5,
            isLike:false,
            likeListId:[],
            inputShow: false,
            reply: [],
          },
        ],
      }
    },
    directives: { clickoutside },
    methods: {
      inputFocus() {
        var replyInput = document.getElementById('replyInput')
        replyInput.style.padding = '8px 8px'
        replyInput.style.border = '2px solid blue'
        replyInput.focus()
      },
      showReplyBtn() {
        this.btnShow = true
      },
      hideReplyBtn() {
        this.btnShow = false
        replyInput.style.padding = '10px'
        replyInput.style.border = 'none'
      },
      showReplyInput(i,j,name,id) {
        this.comments[this.index].inputShow = false
        this.index = i
        this.comments[i].inputShow = true
        this.to = name
        this.toId = id
        this.subIndex = j == '0' ? '0':j
      },
      addlikeNumber(i,id) {
        let list = this.comments[i].likeListId
        // console.log(i, name, id)
        if(list.length === 0){
          //在已经点赞的列表中未找到userId
          this.comments[i].isLike = true
          this.comments[i].like += 1
          this.comments[i].likeListId.push(id)
          console.log("点赞+1",this.comments[i].isLike,this.comments[i].like,this.comments[i].likeListId)        
        }
        else{
         const index = list.indexOf(this.myId)
         this.comments[i].isLike = false
         this.comments[i].like -= 1
         this.comments[i].likeListId.splice(index,1)
         console.log("点赞-1",this.comments[i].isLike,this.comments[i].likeListId)
        }
        
      },
      countlikeNumber(type,i,j,id) {
        const text =  type === '1' ? this.comments[i] : this.comments[i].reply[j]
        let list = text.likeListId     
        if(list.length === 0){
          //在已经点赞的列表中未找到userId
          text.isLike = true
          text.like += 1
          text.likeListId.push(id)
          console.log("点赞+1",text.isLike,text.like,text.likeListId)        
        }
        else{
         const index = list.indexOf(this.myId)
         text.isLike = false
         text.like -= 1
         text.likeListId.splice(index,1)
         console.log("点赞-1",text.isLike,text.likeListId)
        }
        
      },
      _inputShow(i) {
        return this.comments[i].inputShow
      },
      sendComment() {
        if (!this.replyComment) {
          this.$message({
            showClose: true,
            type: 'warning',
            message: '评论不能为空',
          })
        } else {
          let a = {}
          let input = document.getElementById('replyInput')
          let timeNow = new Date().getTime()
          let time = this.dateStr(timeNow)
          a.name = this.myName
          a.comment = this.replyComment
          a.headImg = this.myHeader
          a.time = time
          a.commentNum = 0
          a.like = 0
          a.id = this.myId
          a.reply = [],
          a.isLike = false,
          a.likeListId = [],
          this.comments.push(a)
          this.replyComment = ''
          input.innerHTML = ''
        }
      },
      sendCommentReply(i) {
        if (!this.replyComment) {
          this.$message({
            showClose: true,
            type: 'warning',
            message: '回复不能为空',
          })
        } else {
          let a = {}
          let timeNow = new Date().getTime()
          let time = this.dateStr(timeNow)
          a.from = this.myName
          a.to = this.to 
          a.fromHeadImg = this.myHeader
          a.comment = this.replyComment
          a.time = time
          a.commentNum = 0
          a.like = 0
          a.isLike = false
          a.likeListId = []
          console.log(" this.comments[i].reply+++++++++++", this.comments[i].reply,this.subIndex)
          if(this.subIndex === '0'){
            this.comments[i].commentNum += 1
            }else{
              this.comments[i].reply[this.subIndex].commentNum += 1
            }
          this.comments[i].reply.push(a)
          this.replyComment = ''
          document.getElementsByClassName('reply-comment-input')[i].innerHTML =
            ''
        }
      },
      onDivInput: function (e) {
        this.replyComment = e.target.innerHTML
      },
      dateStr(date) {
        //获取js 时间戳
        var time = new Date().getTime()
        //去掉 js 时间戳后三位,与php 时间戳保持一致
        time = parseInt((time - date) / 1000)
        //存储转换值
        var s
        if (time < 60 * 10) {
          //十分钟内
          return '刚刚'
        } else if (time < 60 * 60 && time >= 60 * 10) {
          //超过十分钟少于1小时
          s = Math.floor(time / 60)
          return s + '分钟前'
        } else if (time < 60 * 60 * 24 && time >= 60 * 60) {
          //超过1小时少于24小时
          s = Math.floor(time / 60 / 60)
          return s + '小时前'
        } else if (time < 60 * 60 * 24 * 30 && time >= 60 * 60 * 24) {
          //超过1天少于30天内
          s = Math.floor(time / 60 / 60 / 24)
          return s + '天前'
        } else {
          //超过30天ddd
          var date = new Date(parseInt(date))
          return (
            date.getFullYear() +
            '/' +
            (date.getMonth() + 1) +
            '/' +
            date.getDate()
          )
        }
      },
    },
  }
</script>

css

<style lang="scss" scoped>
  .my-reply {
    padding: 10px;
    background-color: #fafbfc;
    .header-img {
      display: inline-block;
      vertical-align: top;
    }
    .reply-info {
      display: inline-block;
      margin-left: 5px;
      width: 90%;
      //根据屏幕宽度自适应
      @media screen and(max-width:1200px) {
        width: 80%;
      }
      .reply-input {
        min-height: 20px;
        line-height: 22px;
        padding: 10px 10px;
        color: #ccc;
        background-color: #fff;
        border-radius: 5px;
          // &:empty::before{
          //   content attr(placeholder);
          // }
        &:empty:before {
          content: attr(placeholder);
        }
        &:focus:before {
          content: none;
        }
        &:focus {
          padding: 8px 8px;
          border: 2px solid blue;
          box-shadow: none;
          outline: none;
          }
      }
    }
    .reply-btn-box {
      height: 25px;
      // margin: 10px 0;
      display: inline-block;
      .reply-btn {
        position: relative;
        float: right;
        margin-left: 15px;
        // margin-right: 15px;
        }
      }
  }
  .my-comment-reply {
    margin-left: 50px;
    .reply-input {
      width: flex;
    }
  }
  .author-title:not(:last-child) {
    border-bottom: 1px solid rgba(74, 136, 199, .3)
  }
  .reply-father {
    padding: 10px;
    .header-img {
      display: inline-block;
      vertical-align: top;
    }
    .author-info {
      display: inline-block;
      margin-left: 5px;
      width: 60%;
      height: 40px;
      line-height: 20px;
      span {
        display: block;
        cursor: pointer;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
      }
      .author-name {
        color: #000;
        font-size: 18px;
        font-weight: bold;
      }
      .author-time {
        font-size: 14px;
      }
    }
    .icon-btn {
      width: 30%;
      padding: 0 !important ;
      float: right;
      @media screen and (max-width: 1200px) {
        width: 20%;
        padding: 7px;
      }
      span {
        cursor: pointer;
      }
      .iconfont {
        margin: 0 5px;
      }
    }
    .talk-box {
      margin: 0 50px;
      p {
        margin: 0;
      }
      .reply {
        font-size: 16px;
        color: #000;
      }
    }
    .reply-box {
      margin: 10px 0 0 50px;
      background-color: #efefef;
    }
  }
</style>

参考博客

https://blog.csdn.net/zLanaDelRey/article/details/100997792

  • 11
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 28
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值