项目地址
https://gitee.com/biboheart/huip-vue/tree/master/src/mock/comments
今天用到文章评论的功能,觉得这样常用的功能适合写个组件。不多说,开始
最终效果图
建立文件夹
从外到内文件内容如下
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>
数据格式
有同学问数据格式,不去整理成json了,把项目中的java对象贴上来供参考,写这个组件的时候用了两个数据,后来我用了一个数据结构包含了评论与回复,即评论与回复的数据放在一起了,target为0时顶层评论,大于0时表示为id=target的评论的回复。
CommentService 取到的格式
/**
* 病历评论(点评)
* @author crj
*
*/
@Data
@Entity
@Table(name = "eber_main_record_record_comment")
public class RecordComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long recordId; // 病历ID
private Long uid; // 用户ID
private String uname; // 用户姓名
private Long time; // 时间
@Lob
private String content; // 评论内容
private String headimg; // 头像
private String oname; // 组织名称
private Long target; // 评论的目标,如果是0,评论病历,如果是大于0,评论对象是target指向的ID的评论
}