vue 文章评论组件

2018-06-27  本文已影响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>
上一篇下一篇

猜你喜欢

热点阅读