• vue 仿掘金评论列表


    先来个最终效果

    代码:

      template代码:
    <template>
      <div class="main">
        <div class="title">评论</div>
        <div class="comment-form">
          <div class="avatar-box"><el-avatar class="header-img"  :src="defaultAvatar"></el-avatar></div>
          <div class="form-box">
            <div class="input-box"  >
              <div tabindex="0" contenteditable="true" id="replyInput" spellcheck="false"
              placeholder="输入评论..." class="reply-input replyInput" @focus="showReplyBtn" @input="onDivInput($event)"></div>
            </div>
            <div class="action-box">
              <div class="emoji emoji-btn">
                <div class="emoji-box">
                  <span class="replyInput" @click="showEmoji($event)" v-clickoutside="hideReplyBtn">表情</span>
                  <VEmojiPicker :isShowEmoji="isShowEmoji" :inputAdd="inputAdd" @select="selectEmoji" />
                </div>
              </div>
              <div class="reply-btn-box">
                <el-button class="reply-btn" size="medium" @click="sendComment" type="primary" >发表评论</el-button>
              </div>
            </div>
          </div>
        </div>
        <div class="item" v-for="(item,i) in comments" :key="i">
          <div class="comment">
            <div class="user-popover-box">
              <el-avatar class="header-img" :src="item.headImg"></el-avatar>
            </div>
            <div class="content-box">
              <div class="meta-box">
                <span class="username">{{item.name}}</span>
              </div>
              <div class="content">{{item.comment}}</div>
              <div class="reply-stat">
                <span class="author-time">{{item.time}}</span>
                <div class="icon-btn">
                  <span @click="showReplyInput(i,item.name,item.id)">
                    <i class="iconfont el-icon-s-comment"></i>
                    回复({{item.commentNum}})
                  </span>
                  <span>
                    <i class="iconfont el-icon-thumb"></i>
                    点赞({{item.like}})
                  </span>
                </div>
              </div>
              <div class="reply-box">
                <div class="item" v-for="(reply,j) in item.reply" :key="j">
                  <div class="comment">
                    <div class="user-popover-box">
                      <el-avatar class="header-img" :src="reply.fromHeadImg"></el-avatar>
                    </div>
                    <div class="content-box">
                      <div class="meta-box">
                        <span class="username">{{reply.from}}</span>
                      </div>
                      <div class="content">
                        <span>回复<a>{{reply.to}}</a>:</span>
                        <span class="reply">{{reply.comment}}</span>
                      </div>
                      <div class="reply-stat">
                        <span class="author-time">{{reply.time}}</span>
                        <div class="icon-btn">
                          <span @click="showReplyInput(i,reply.from,reply.id)">
                            <i class="iconfont el-icon-s-comment"></i>
                            回复({{reply.commentNum}})
                          </span>
                          <span>
                            <i class="iconfont el-icon-thumb"></i>
                            点赞({{reply.like}})
                          </span>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div  v-show="_inputShow(i)" class="my-reply my-comment-reply comment-form">
                <div class="avatar-box"><el-avatar class="header-img"  :src="defaultAvatar"></el-avatar></div>
                <div class="form-box">
                  <div class="input-box" >
                    <div
                    tabindex="0"
                    contenteditable="true"
                    spellcheck="false"
                    placeholder="输入评论..."
                    @input="onDivInput($event)"
                    class="reply-input reply-comment-input"
                  ></div>
                  </div>
                  <div class="action-box">
                    <div class="reply-btn-box">
                      <el-button class="reply-btn" size="medium" @click="sendCommentReply(i)" type="primary">发表评论</el-button>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>

       js代码(注:这里引入了emoji表情,emoji的功能未实现,后面有时间加上):

    import VEmojiPicker from './emoji.vue';
    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 {
          inputAdd:"",
          isShowEmoji:false,
          replyInput:"",
          btnShow: false,
          index: "0",
          content:'',
          emojiContent: "",
          myName: "Lana Del Rey",
          defaultAvatar: "../../static/images/default-avatar.png",
          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,
              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: 0,
                  like: 15,
                  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,
                  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,
              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,
                  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,
              inputShow: false,
              reply: []
            }
          ]
        };
      },
      directives: { clickoutside },
       components: {
        VEmojiPicker
      },
      methods: {
        inputFocus() {
          var replyInput = document.getElementById("replyInput");
          replyInput.focus();
        },
        showReplyBtn() {
          this.btnShow = true;
        },
        hideReplyBtn() {
          this.inputAdd = '';
          this.isShowEmoji = false;
        },
        showReplyInput(i, name, id) {
          this.comments[this.index].inputShow = false;
          this.index = i;
          this.comments[i].inputShow = true;
          this.to = name;
          this.toId = id;
        },
        _inputShow(i) {
          return this.comments[i].inputShow;
        },
        _inputHide(i) {
           this.comments[i].inputShow = false;
        },
        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;
            this.comments.push(a);
            this.comments.reverse();
            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;
            this.comments[i].reply.push(a);
            this.comments[i].reply.reverse();
            this.replyComment = "";
            document.getElementsByClassName("reply-comment-input")[i].innerHTML = "";
            this._inputHide(i)
          }
        },
        onDivInput: function(e) {
          this.replyComment = e.target.innerHTML;
        },
        showEmoji(e){
           this.inputAdd = e.target.className;
          this.isShowEmoji = true;
          console.log(e.target.className)
        },
        selectEmoji(emoji) {
        
          this.emojiContent += `[${emoji}]`.replace(/[([A-Za-z0-9_]+)]/g, '<img src="../../static/images/$1.png" class="emoji" draggable="false">')//传过来的名字转为图片
          console.log(this.emojiContent)
         this.replyComment = this.emojiContent
        },
        customEmoji(text) { //这个函数就是服务器端把传过来的名称转化为图片的
          return text.replace(/[([A-Za-z0-9_]+)]/g, '<img src="../../static/images/$1.png" class="emoji" draggable="false">')
        },
        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()
            );
          }
        }
      }
    };

      css代码(注:这里使用了stylus):

    .main {
      width: 1000px;
      margin: 0 auto;
      .title{
        color: #8a9aa9;
        font-size: 16px;
        font-weight: 400;
        text-align: center;
        padding: 1.67rem 0 5px;
      }
      .comment-form{
        display: flex;
        position: relative;
        padding: 10px;
        background-color: #fafbfc;
        border-radius: 3px;
        margin: 1.333rem 0;
        
        .avatar-box{
          flex: 0 0 auto;
          .header-img{
            margin-right: 10px;
          }
        }
        .form-box{
          flex: 1 1 auto;
          position: relative;
          .input-box{
            position: relative;
            display: inline-block;
            width: 100%;
            .reply-input {
              min-height: 20px;
              line-height: 22px;
              padding: 10px 110px 10px 10px;
              color: #ccc;
              background-color: #fff;
              border: 1px solid #f1f1f1;
              border-radius: 3px;
              text-align: left;
    
              &:empty:before {
                content: attr(placeholder);
              }
    
              &:focus:before {
                content: none;
              }
              &:focus {
                border: 1px solid #007fff;
                outline: none;
              }
            }
          }
          .action-box{
            display: flex;
            align-items: center;
            margin: .65rem 0 0;
            .emoji{
              position: relative;
              .emoji-box {
                display: flex;
                align-items: center;
                position: relative;
                color: #027fff;
                cursor: pointer;
                font-size: 14px;
                span{
                  display:inline-block;
                  padding-left 20px;
                  height: 18px;
                  background: url('../../static/images/emoji_bg.svg')  left center no-repeat;
                  background-size:18px;
                }
                
              }
            }
            .reply-btn-box {
              flex: 0 0 auto;
              margin-left: auto;
            }
          }
    
        }
      }
      
      .item {
        margin-bottom: 15px;
        padding: 10px;
        .comment {
          display: flex;
    
          .user-popover-box {
            height: 40px;
          }
    
          .content-box {
            border-bottom: 1px solid #f1f1f1;
            margin-left: 15px;
            flex: 1 1 auto;
            text-align: left;
    
            .meta-box {
              display: flex;
              align-items: center;
              font-size: 14px;
              line-height: 1.2;
              white-space: nowrap;
            }
            .content {
              margin-top: 5px;
              font-size: 16px;
              line-height: 1.8;
              word-wrap: break-word;
              white-space: pre-wrap;
              word-break: break-all;
              color: #505050;
              a{
                display:inline-block;
                margin:0 4px;
                font-size: 14px;
                font-weight: 400;
                color: #406599;
                cursor:pointer;
              }
            }
            .reply-stat {
              display: flex;
              margin-top: 10px;
              font-weight: 400;
              .author-time {
                font-size: 14px;
                color: #8a9aa9;
                cursor: default;
              }
    
              .icon-btn {
                flex: 0 0 auto;
                display: flex;
                justify-content: space-between;
                margin-left: auto;
                min-width: 8.8rem;
                color: #8a93a0;
                user-select: none;
    
                span {
                  display: inline-block;
                  margin: 0 8px;
                  min-width: 100px;
                  padding: 0 3px;
                  cursor: pointer;
                }
              }
            }
    
            .reply-box {
              margin: 15px 0;
              background-color: #efefef;
              border-radius: 3px;
              .item{
                padding: 10px 0 10px 10px;
                &:not(:last-child) {
                  border-bottom: 1px solid rgba(178,186,194,0.3);
                }
              }
            }
          }
        }
      }
    }
    
    .my-reply {
      padding: 10px;
      background-color: #fafbfc;
    
      .header-img {
        display: inline-block;
        vertical-align: top;
      }
      .reply-info {
        position: relative;
        display: inline-block;
        margin-left: 5px;
        width: 90%;
        @media screen and (max-width: 1200px) {
           80%;
        }
    
        .reply-input {
          min-height: 20px;
          line-height: 22px;
          padding: 10px 110px 10px 10px;
          color: #ccc;
          background-color: #fff;
          border: 1px solid #f1f1f1;
          border-radius: 3px;
          text-align: left;
          &:empty:before {
            content: attr(placeholder);
          }
    
          &:focus:before {
            content: none;
          }
    
          &:focus {
            border: 1px solid #007fff;
            outline: none;
          }
        }
      }
    
      .reply-btn-box {
        .reply-btn {
          position: relative;
          float: right;
          height: 100%;
        }
      }
    }
    
    .my-comment-reply {
      margin-left: 50px;
    
      .reply-input {
        width: flex;
      }
    }
    
    .author-title:not(:last-child) {
      border-bottom: 1px solid rgba(178, 186, 194, 0.3);
    }
    
    .author-title {
      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) {
           20%;
          padding: 7px;
        }
    
        >span {
          cursor: pointer;
        }
    
        .iconfont {
          margin: 0 5px;
        }
      }
    
      .talk-box {
        margin: 0 50px;
    
        >p {
          margin: 0;
          text-align: left;
        }
    
        .reply {
          font-size: 16px;
          color: #000;
        }
      }
    
      .reply-box {
        margin: 10px 0 0 50px;
        background-color: #efefef;
      }
    }

    最后附上GitHub地址:https://github.com/ALazyTiger/vue-Comment-List

        
  • 相关阅读:
    分享网页相关好用小工具
    【转】Expire Google Drive Files 让Google Docs云盘共享连接在指定时间后自动失效
    习题 5: 更多的变量和打印 | 笨办法学 Python
    笨办法学 Python (Learn Python The Hard Way)
    【转】pycharm快捷键、常用设置、包管理
    ArcGIS学习推荐
    WPF 单实例应用程序
    推荐一个 HTML5在线的流程图工具——ProcessOn
    WPF Expander控件(扩展面板)
    WPF 的拖拽操作(DragDrop)
  • 原文地址:https://www.cnblogs.com/luobiao/p/12408623.html
Copyright © 2020-2023  润新知