美文网首页
vue 文章评论组件

vue 文章评论组件

作者: 碧波之心 | 来源:发表于2018-06-27 21:57 被阅读2293次

    今天用到文章评论的功能,觉得这样常用的功能适合写个组件。不多说,开始

    最终效果图

    效果图

    建立文件夹

    目录结构目录结构

    从外到内文件内容如下

    BhComments/index.js

    import CommentsItem from './packages/comments-item/index.js'
    import ReplyItem from './packages/reply-item/index.js'
    
    const components = [
      CommentsItem,
      ReplyItem
    ]
    
    const install = function (Vue) {
      components.map(component => {
        Vue.component(component.name, component)
      })
    }
    
    if (typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
    
    export default {
      version: '1.0.1',
      name: 'BhComments',
      install,
      CommentsItem,
      ReplyItem
    }
    

    BhComments/packages/comments-item/index.js

    import CommentsItem from './src/main'
    
    CommentsItem.install = function (Vue) {
      Vue.component(CommentsItem.name, CommentsItem)
    }
    
    export default CommentsItem
    

    BhComments/packages/comments-item/src/main.vue

    <template>
      <div class="comments-item">
        <div class="pull-left">
          <img class="avatar-32" :src="avatar" alt="" v-if="avatar" @click="handleClickAvatar">
        </div>
        <div class="comments-box">
          <div class="comments-trigger">
            <div class="pull-right comments-option">
              <a href="javascript:void(0)" class="ml10" data-placement="top" :title="item.title" v-for="item in tools" :key="item.name" @click="handleClickTool($event, item)">
                <i :class="item.icon" v-if="item.icon"></i>
                <span v-if="item.text">{{item.text}}</span>
              </a>
            </div>
            <strong><a target="_blank" href="javascript:void(0)" @click="handleClickAuthor">{{author}}</a></strong>
            <span class="comments-date">  ·  {{time | filterTime}}</span>
          </div>
          <div class="comments-content">
            <p>{{content}}</p>
          </div>
          <p class="comments-ops">
            <span class="coments-ops-item ml15" v-for="item in ops" :key="item.name" v-if="item.name">
              <i :class="item.icon + ' coments-ops-icon'" v-if="item.icon"></i>
              <span class="coments-ops-text">{{item.name}}</span>
            </span>
            <span class="comments-reply-btn ml15" @click="handleAddReply">回复</span>
          </p>
          <div class="reply-list" v-show="hasReply">
            <slot></slot>
            <div class="reply-item reply-item--ops">
              <a class="reply-inner-btn" href="javascript:void(0);" @click="handleAddReply">添加回复</a>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'CommentsItem',
      props: {
        avatar: String,
        author: String,
        content: String,
        ops: Array,
        tools: Array,
        time: [String, Number],
        hasReply: Boolean
      },
      data () {
        return {
        }
      },
      computed: {
      },
      methods: {
        handleClickAvatar (event) {
          event.stopPropagation()
          this.$emit('clickAvatar', this)
        },
        handleClickTool (event, tool) {
          event.stopPropagation()
          this.$emit('clickTool', this, tool)
        },
        handleClickAuthor (event) {
          event.stopPropagation()
          this.$emit('clickAuthor', this)
        },
        handleAddReply (event) {
          event.stopPropagation()
          this.$emit('addReply', this)
        }
      },
      filters: {
        filterTime (value) {
          if (!value) {
            return '未知时间'
          }
          if (Object.prototype.toString.call(value) === '[object String]') {
            return value
          }
          if (value === '' || isNaN(value)) {
            return '未知时间'
          }
          if (value <= 0) {
            return '未知时间'
          }
          if (value < 10000000000) {
            value *= 1000
          }
          let time = new Date(value)
          let tY = time.getFullYear()
          let tM = time.getMonth() + 1 < 10 ? '0' + (time.getMonth() + 1) : time.getMonth() + 1
          let tD = time.getDate() < 10 ? '0' + time.getDate() : time.getDate()
          let th = time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
          let tm = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()
          let ts = time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
          let now = new Date()
          let nY = now.getFullYear()
          let nM = now.getMonth() + 1 < 10 ? '0' + (now.getMonth() + 1) : now.getMonth() + 1
          let nD = now.getDate() < 10 ? '0' + now.getDate() : now.getDate()
          let result = ''
          if (tY !== nY) {
            result += tY + '年'
          }
          if (tM !== nM || tD !== nD) {
            result += tM + '月'
            result += tD + '日'
          }
          if (result === '') {
            result = th + ':' + tm + ':' + ts
          }
          return result
        }
      }
    }
    </script>
    
    <style scoped>
    img {
      border: 0;
      vertical-align: middle;
    }
    .ml10 {
      margin-left: 10px !important;
    }
    .ml15 {
      margin-left: 15px !important;
    }
    .comments-item {
      padding: 15px 0;
      border-bottom: 1px solid rgba(0,0,0,0.09);
      box-sizing: border-box;
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      -o-box-sizing: border-box;
      -ms-box-sizing: border-box;
      font-size: 14px;
    }
    .pull-left {
      float: left !important;
    }
    .pull-right {
      float: right !important;
    }
    .avatar-32 {
      width: 32px;
      height: 32px;
      border-radius: 50%;
    }
    .comments-item a {
      color: #009a61;
      text-decoration: none;
      background: transparent;
    }
    .comments-item a:hover,
    .comments-item a:active,
    .comments-item a:focus {
      outline: 0;
    }
    .comments-box {
      padding-left: 47px;
    }
    .comments-box strong {
      font-weight: bold;
    }
    .comments-trigger {
      margin-bottom: 10px;
      color: #999;
      font-size: 13px;
    }
    .comments-option {
      /*visibility: hidden;*/
    }
    .comments-content {
      line-height: 1.6;
      word-wrap: break-word;
      margin-bottom: 10px !important;
    }
    .comments-content::before,
    .comments-content::after {
      display: table;
    }
    .comments-content::after {
      content: "";
      clear: both;
    }
    .comments-ops {
      margin: 0;
      color: #999;
      font-size: 13px;
    }
    .comments-reply-btn {
      cursor: pointer;
    }
    .reply-list {
      margin-top: 10px;
      font-size: 13px;
      background-color: #FAFAFA;
      padding: 0 10px;
      color: #666;
      box-sizing: border-box;
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      -o-box-sizing: border-box;
      -ms-box-sizing: border-box;
    }
    .reply-item--ops {
      border-bottom: none;
    }
    .reply-item {
      padding-bottom: 10px;
      padding-top: 10px;
      word-break: break-word;
    }
    </style>
    

    BhComments/packages/reply-item/index.js

    import ReplyItem from './src/main'
    
    ReplyItem.install = function (Vue) {
      Vue.component(ReplyItem.name, ReplyItem)
    }
    
    export default ReplyItem
    

    BhComments/packages/reply-item/src/main.vue

    <template>
      <div class="reply-item">
        <div class="reply-content-block">
          <div class="reply-content">
            <p>{{content}}</p>
          </div>
          <div class="comment-func inline-block">
            <span class="pull-right comment-tools ml15">
              <a href="javascript:void(0)" class="ml10" data-placement="top" :title="item.title" v-for="item in tools" :key="item.name" @click="handleClickTool($event, item)">
                <i :class="item.icon" v-if="item.icon"></i>
                <span v-if="item.text">{{item.text}}</span>
              </a>
            </span>
            <span class="comment-meta inline-block">
              <span> — </span>
              <a target="_blank" href="javascript:void(0)" @click="handleClickAuthor($event)">{{author}}</a>
              <span class="comments-date">  ·  {{time | filterTime}}</span>
            </span>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'ReplyItem',
      props: {
        author: String,
        content: String,
        tools: Array,
        time: [String, Number]
      },
      data () {
        return {
        }
      },
      computed: {
      },
      methods: {
        handleClickTool (event, tool) {
          event.stopPropagation()
          this.$emit('clickTool', this, tool)
        },
        handleClickAuthor (event) {
          event.stopPropagation()
          this.$emit('clickAuthor', this)
        }
      },
      filters: {
        filterTime (value) {
          if (!value) {
            return '未知时间'
          }
          if (Object.prototype.toString.call(value) === '[object String]') {
            return value
          }
          if (value === '' || isNaN(value)) {
            return '未知时间'
          }
          if (value <= 0) {
            return '未知时间'
          }
          if (value < 10000000000) {
            value *= 1000
          }
          let time = new Date(value)
          let tY = time.getFullYear()
          let tM = time.getMonth() + 1 < 10 ? '0' + (time.getMonth() + 1) : time.getMonth() + 1
          let tD = time.getDate() < 10 ? '0' + time.getDate() : time.getDate()
          let th = time.getHours() < 10 ? '0' + time.getHours() : time.getHours()
          let tm = time.getMinutes() < 10 ? '0' + time.getMinutes() : time.getMinutes()
          let ts = time.getSeconds() < 10 ? '0' + time.getSeconds() : time.getSeconds()
          let now = new Date()
          let nY = now.getFullYear()
          let nM = now.getMonth() + 1 < 10 ? '0' + (now.getMonth() + 1) : now.getMonth() + 1
          let nD = now.getDate() < 10 ? '0' + now.getDate() : now.getDate()
          let result = ''
          if (tY !== nY) {
            result += tY + '年'
          }
          if (tM !== nM || tD !== nD) {
            result += tM + '月'
            result += tD + '日'
          }
          if (result === '') {
            result = th + ':' + tm + ':' + ts
          }
          return result
        }
      }
    }
    </script>
    
    <style scoped>
    .ml10 {
      margin-left: 10px !important;
    }
    .ml15 {
      margin-left: 15px !important;
    }
    .pull-left {
      float: left !important;
    }
    .pull-right {
      float: right !important;
    }
    .reply-item {
      padding-bottom: 10px;
      padding-top: 10px;
      border-bottom: 1px dashed rgba(0,0,0,0.09);
      word-break: break-word;
    }
    .reply-item a {
      color: #009a61;
      text-decoration: none;
      background: transparent;
    }
    .reply-item a:hover,
    .reply-item a:active,
    .reply-item a:focus {
      outline: 0;
    }
    .reply-item p {
      margin-bottom: 5px;
    }
    .comment-tools {
      /*visibility: hidden;*/
    }
    .comment-meta {
      color: #999;
    }
    .inline-block {
      display: inline-block;
    }
    </style>
    

    使用组件

    <script>
    import Vue from 'vue'
    import BhComments from '@/components/BhComments'
    import CommentService from '@/request/comments/comment'
    import ReplyService from '@/request/comments/reply'
    
    Vue.use(BhComments)
    
    export default {
      name: 'Dashboard',
      data () {
        return {
          comments: [],
          replys: {}
        }
      },
      created: function () {
        this.listComments()
      },
      watch: {
      },
      methods: {
        listComments () {
          let self = this
          CommentService.list({
            target: 2
          }).then(data => {
            data = data.result
            self.comments = data ? [].concat(data) : []
            if (self.comments.length > 0) {
              self.listReply()
            }
          })
        },
        listReply () {
          let self = this
          self.replys = {}
          if (self.comments.length < 0) {
            return
          }
          for (let i = 0; i < self.comments.length; i++) {
            let value = self.comments[i]
            ReplyService.list({
              cid: value.id
            }).then(data => {
              data = data.result
              self.$set(self.replys, value.id, data ? [].concat(data) : [])
            })
          }
        },
        handleClickAvatar (item) {
          console.log('点击了头像')
        },
        handleClickAuthor (item) {
          console.log('点击了用户')
        },
        handleAddReply (item) {
          console.log(item)
        }
      }
    }
    </script>
    
    <template>
      <el-main>
        <comments-item
          v-for="comment in comments"
          :key="comment.id"
          :avatar="comment.headimg"
          :author="comment.author"
          :content="comment.content"
          :time="comment.createTime"
          :hasReply="replys[comment.id] && replys[comment.id].length > 0"
          @clickAvatar="handleClickAvatar(comment)"
          @clickAuthor="handleClickAuthor(comment)"
          @addReply="handleAddReply(comment)">
          <reply-item v-for="reply in replys[comment.id]" :key="reply.id" :author="reply.author" :content="reply.content" :time="reply.createTime">
          </reply-item>
        </comments-item>
      </el-main>
    </template>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped>
    </style>
    

    相关文章

      网友评论

          本文标题:vue 文章评论组件

          本文链接:https://www.haomeiwen.com/subject/oxbiyftx.html