评论:
1、评论列表默认加载10条,下拉加载下一页。使用的是vant的list组件load事件
2、回复默认只展示3条,超出隐藏,点击【展开全部评论】加载剩下的回复,点击【收起】回到默认状态
3、点击评论者可以回复当前评论,input中显示回复的谁,并聚焦 this.$refs.inputRef.focus()
4、点击【评论】按钮打开评论popup,而【操作】和【评论】是兄弟组件,所以这里使用observer传值的方式打开评论列表
Comment.vue
<template> <van-popup v-model="show" v-if="show" position="bottom" class="comment-popup" :style="{ height: '80%' }"> <header>评论</header> <van-list v-model="loadMoreLoading" @load="loadMore" ref='vanListRef' offset='50'> <template v-if="listData.length > 0"> <div class="crad" v-for="(item,index) in listData" :key="index"> <div class="commentRow" @click="onCommentRow(item,index)"> <div class="row head"> <div class="left">{{item.userName}}</div> <div class="right">{{item.createTime}}</div> </div> <div class="row"> <div class="content">{{item.content}}</div> </div> </div> <template v-if="item.replyVOList&&item.replyVOList.length"> <!-- 展开展示全部 收起只展示三条 --> <div v-for="(obj,oIndex) in item.isOpen?item.replyVOList : item.replyVOList.slice(0,3)" :key="oIndex" class="commentRow replyRow" @click="onCommentReply(item,obj,index)"> <div class="row head"> <div class="left">{{obj.userName}}</div> <div class="right">{{obj.createTime}}</div> </div> <div class="row"> <div class="content">回复<span class="contentSpan">@{{obj.replyUserName}}</span>:{{obj.content}}</div> </div> </div> </template> <van-loading v-if="item.loading" /> <template v-if="item.replyNum&&item.replyNum>3&&!item.loading"> <div class="shrink out replyRow" v-if="!item.isOpen" @click="onOpen(item,index,true)"> 展开全部评论 </div> <div class="shrink open replyRow" v-else @click="onOpen(item,index,false)"> 收起 </div> </template> </div> </template> <div v-else class="empty-data">暂无评论</div> </van-list> <!-- 底部 --> <footer> <van-field ref="inputRef" v-model="commentContent" maxlength="500" center clearable :placeholder="replyPlaceholder || '小小的鼓励,是团队的凝聚'" /> <van-button @click="send">发送</van-button> </footer> </van-popup> </template> <script> import { getToken } from '@/utils/token' import { getCommentList, insertComment, addReply, getLastReplyList } from '@/api/project' export default { components: {}, props: { commentParams: { type: Object, require: true } }, data() { return { show: false, queryParams: { page: 1, pageSize: 10, mid: this.commentParams.mid, // 项目报:flowTrackId 拜访计划/拜访反馈:id mtype: this.commentParams.mtype // 1=项目报 2=拜访计划 3=拜访反馈 }, totalPages: 0, // 总页数 listData: [], //评论列表 sendItem: {}, replyPlaceholder: '', // 评论输入框中的placeholder文案 commentContent: '', // 评论输入框中输入的内容 loadMoreLoading: false // 加载更多loading } }, computed: { userId: () => { const token = getToken() return Number(token.split('-')[0]) } }, created() { this.$observer.$on('openComment', () => { this.queryParams.page = 1 this.fetchProjectLog() this.show = true }) }, beforeDestroy() { this.$observer.$off('openComment') }, methods: { onOpen(item, index, isOpen) { this.setOpen({ index, isOpen }) }, onCommentRow(item, index) { let { userId } = this if (item.userId != userId) { item.commentId = item.id item.index = index this.setComment(item) } }, onCommentReply(item, obj, index) { let { userId } = this if (obj.userId != userId) { obj.commentId = item.id obj.index = index this.setComment(obj) } }, fetchProjectLog() { const { queryParams } = this this.$vux.loading.show('加载中...') return getCommentList(queryParams) .then(res => { this.$vux.loading.hide() let { data, totalPages } = res if (queryParams.page === 1) { if (data && data.length) { let arr = data.map(item => { item.isOpen = false item.loading = false return item }) this.listData = arr } else { this.listData = [] } } else { if (data && data.length) { let arr = data.map(item => { item.isOpen = false item.loading = false return item }) this.listData = [...this.listData, ...arr] } else { this.listData = [] } } this.totalPages = totalPages return res }) .catch(() => { this.$vux.loading.hide() }) }, setComment(item) { this.sendItem = { ...item } this.replyPlaceholder = '回复' + item.userName + ':' this.$refs.inputRef.focus() // 聚焦 }, setOpen(obj) { let { index, isOpen } = obj let item = this.listData[index] if (isOpen && item.replyVOList.length === 3 && item.replyNum > 3) { item.loading = true getLastReplyList({ commentId: item.id }).then(result => { if (result.data && result.data.length) { item.replyVOList = [...item.replyVOList, ...result.data] item.loading = false item.isOpen = isOpen } }) } else { item.isOpen = isOpen } }, send() { const { mid, mtype } = this.commentParams const { commentContent, replyPlaceholder } = this if (replyPlaceholder && commentContent) { let { commentId, userId, index } = this.sendItem const params = { content: commentContent, commentId, replyUser: userId } addReply(params) .then(res => { if (res.data) { this.commentContent = '' let listData = [...this.listData] listData[index].replyNum++ listData[index].replyVOList.push(res.data) this.$set(this, 'listData', listData) this.$vux.toast.text('回复成功') this.replyPlaceholder = '' } else { this.$vux.toast.text(res.msg || '回复失败') } }) .catch(() => { this.$vux.toast.text('回复失败') }) } else { const params = { mid, mtype, content: commentContent } insertComment(params) .then(res => { if (res.data) { this.commentContent = '' this.queryParams.page = 1 this.fetchProjectLog() this.$vux.toast.text('评论成功') } else { this.$vux.toast.text(res.msg || '评论失败') } }) .catch(() => { this.$vux.toast.text('评论失败') }) } }, loadMore() { const { totalPages } = this const { page } = this.queryParams if (totalPages > page) { this.queryParams.page++ this.fetchProjectLog().then(res => { if (res.success) { this.loadMoreLoading = false } }) } else { this.loadMoreLoading = false } } } } </script>
css:
<style lang='less' scoped> .comment-popup { line-height: 1; box-sizing: border-box; display: flex; flex-direction: column; > header { color: #4d5c82; font-size: 18px; font-family: PingFangSC, PingFangSC-Medium; font-weight: 600; padding: 15px 15px 20px 15px; } .van-list { flex: 1; overflow: auto; padding: 0 15px; .crad { margin-bottom: 15px; font-size: 14px; color: #4d5c82; .row { display: flex; .left { flex: 1; font-weight: 600; } .content { line-height: 22px; font-size: 12px; .contentSpan { color: #1288fe; } } } .replyRow { padding-left: 15px; } .head { margin-top: 5px; margin-bottom: 5px; } .shrink { margin-top: 10px; color: #1288fe; font-size: 12px; } .van-loading { display: flex; justify-content: center; } } } > footer { display: flex; align-items: center; padding: 20px 12px; .van-field { background-color: #f3f6f9; border-radius: 20px; height: 40px; margin-right: 15px; } .van-button { border: none; padding: 0; height: 30px; .van-button__content { width: 58px; background-color: #1288fe; border-radius: 4px; color: #fff; } &::before { border: none; } } } } </style>
MoreOperations.vue
<template> <!-- 更多操作:编辑,删除,评论 --> <div class="more-operations"> <transition name="popup"> <div class="popup" v-show="isShow" @click="handleClose"> <div class="btns"> <div @click="handleEdit">编辑</div> <div @click.stop="handleDelete">删除</div> <div @click="handleComment">评论</div> </div> </div> </transition> </div> </template> <script> export default { props: { visible: { type: Boolean, require: true } }, computed: { isShow: { get() { return this.visible }, set(flag) { this.$emit('update:visible', flag) } } }, methods: { handleEdit() { this.$emit('edit') }, handleDelete() { this.$emit('delete') }, handleClose() { this.isShow = false }, handleComment() { this.$observer.$emit('openComment') } } } </script>
css:
<style lang="less" scoped> .more-operations { .popup-enter-active, .popup-leave-active { transition: opacity 0.2s linear; } .popup-enter, .popup-leave-to { // transform: translate3d(100%, 0, 0); opacity: 0; } .popup-enter-to, .popup-leave { // transform: translate3d(0, 0, 0); opacity: 1; } .popup { position: fixed; width: 100%; height: 100%; top: 0; bottom: 0; left: 0; right: 0; background-color: rgba(#000, 0.6); .btns { position: absolute; bottom: 70px; left: 0; right: 0; display: flex; flex-direction: column; align-items: center; > div { width: calc(100% - 60px); line-height: 50px; text-align: center; border-radius: 25px; background-color: #fff; font-size: 18px; color: #1288fe; margin-top: 15px; &:first-of-type { margin-top: 0; } } > div:nth-child(2) { color: #fc5e5e; } } } } </style>
【拜访计划】使用【更多操作】和【评论】组件:
<!-- 更多操作 --> <MoreOperations :visible.sync='isShow' @edit='handleEdit' @delete='handleDelete' /> <!-- 评论 --> <Comment :commentParams='{mid:$route.query.id,mtype:2}' />
注:评论组件是可以重复使用的,在引入时传入不同的参数以供接口请求