一、前言
最近開發專案需要用到評論模組,參考了多家平臺,對比之後覺得簡書的最漂亮,就它了。
前端當然要先放圖
簡書原圖
注意評論的輸入框預設是隱藏的,點選回覆才會顯示出來
本篇文章實現的效果
同樣輸入框需要點選回覆才會顯示
二、功能分析
從 UI 上看,整個評論模組大致分為四個版塊
- 評論人資訊。包括頭像,暱稱,評論時間
- 評論的內容。包括文字內容,讚的個數和回覆按鈕
- 他人的回覆。回覆可能有多條,所以這是個
v-for
迴圈。回覆包括回覆人及被回覆人的暱稱,回覆內容,時間,以及回覆按鈕- 評論輸入框。輸入框最開始是隱藏的,點選回覆按鈕或新增新評論時才會顯示,點取消隱藏。
回覆功能可以回覆當前評論本身,也可以回覆其他使用者對這條評論的評論,我們稱之為子評論。所有子評論都掛載最初的父評論下。
當點選子評論的回覆按鈕時,輸入框彈出的同時會自動填上 @
+ 被回覆者的暱稱,使邏輯更加合理。
點選新增新評論是新增對本條評論的子評論,不是對文章的新評論,所以文章末尾處應該還有一個輸入框,用來發表新評論。
三、資料結構設計
參照的效果圖有了,接下來就是設計資料了。資料庫設計就不在這裡說了,本專案把評論模組分了兩張表,分別存放評論和回覆。新建一個 mockdata.js
檔案,模擬伺服器返回的資料
//模擬評論資料
const comment = {
status: "成功",
code: 200,
data: [
{
id: 'comment0001', //主鍵id
date: '2018-07-05 08:30', //評論時間
ownerId: 'talents100020', //文章的id
fromId: 'errhefe232213', //評論者id
fromName: '犀利的評論家', //評論者暱稱
fromAvatar: 'http://ww4.sinaimg.cn/bmiddle/006DLFVFgy1ft0j2pddjuj30v90uvagf.jpg', //評論者頭像
likeNum: 3, //點贊人數
content: '非常靠譜的程式設計師', //評論內容
reply: [ //回覆,或子評論
{
id: '34523244545', //主鍵id
commentId: 'comment0001', //父評論id,即父親的id
fromId: 'observer223432', //評論者id
fromName: '夕陽紅', //評論者暱稱
fromAvatar: 'https://wx4.sinaimg.cn/mw690/69e273f8gy1ft1541dmb7j215o0qv7wh.jpg', //評論者頭像
toId: 'errhefe232213', //被評論者id
toName: '犀利的評論家', //被評論者暱稱
toAvatar: 'http://ww4.sinaimg.cn/bmiddle/006DLFVFgy1ft0j2pddjuj30v90uvagf.jpg', //被評論者頭像
content: '贊同,很靠譜,水平很高', //評論內容
date: '2018-07-05 08:35' //評論時間
},
{
id: '34523244545',
commentId: 'comment0001',
fromId: 'observer567422',
fromName: '清晨一縷陽光',
fromAvatar: 'http://imgsrc.baidu.com/imgad/pic/item/c2fdfc039245d688fcba1b80aec27d1ed21b245d.jpg',
toId: 'observer223432',
toName: '夕陽紅',
toAvatar: 'https://wx4.sinaimg.cn/mw690/69e273f8gy1ft1541dmb7j215o0qv7wh.jpg',
content: '大神一個!',
date: '2018-07-05 08:50'
}
]
},
{
id: 'comment0002',
date: '2018-07-05 08:30',
ownerId: 'talents100020',
fromId: 'errhefe232213',
fromName: '毒蛇郭德綱',
fromAvatar: 'http://ww1.sinaimg.cn/bmiddle/006DLFVFgy1ft0j2q2p8pj30v90uzmzz.jpg',
likeNum: 0,
content: '從沒見過這麼優秀的人',
reply: []
}
]
};
export {comment}
複製程式碼
資料包裹在 data
中,評論可能有多條所以 data
是一個陣列。每個欄位的含義備註裡寫的很清楚了,不再過多解釋。
所有關於母評論的子評論都掛載在母評論的 reply
欄位下。
四、程式碼封裝
新建 comment.vue
作為評論元件
<!--評論模組-->
<template>
<div class="container">
<div class="comment" v-for="item in comments">
<div class="info">
<img class="avatar" :src="item.fromAvatar" width="36" height="36"/>
<div class="right">
<div class="name">{{item.fromName}}</div>
<div class="date">{{item.date}}</div>
</div>
</div>
<div class="content">{{item.content}}</div>
<div class="control">
<span class="like" :class="{active: item.isLike}" @click="likeClick(item)">
<i class="iconfont icon-like"></i>
<span class="like-num">{{item.likeNum > 0 ? item.likeNum + '人贊' : '贊'}}</span>
</span>
<span class="comment-reply" @click="showCommentInput(item)">
<i class="iconfont icon-comment"></i>
<span>回覆</span>
</span>
</div>
<div class="reply">
<div class="item" v-for="reply in item.reply">
<div class="reply-content">
<span class="from-name">{{reply.fromName}}</span><span>: </span>
<span class="to-name">@{{reply.toName}}</span>
<span>{{reply.content}}</span>
</div>
<div class="reply-bottom">
<span>{{reply.date}}</span>
<span class="reply-text" @click="showCommentInput(item, reply)">
<i class="iconfont icon-comment"></i>
<span>回覆</span>
</span>
</div>
</div>
<div class="write-reply" v-if="item.reply.length > 0" @click="showCommentInput(item)">
<i class="el-icon-edit"></i>
<span class="add-comment">新增新評論</span>
</div>
<transition name="fade">
<div class="input-wrapper" v-if="showItemId === item.id">
<el-input class="gray-bg-input"
v-model="inputComment"
type="textarea"
:rows="3"
autofocus
placeholder="寫下你的評論">
</el-input>
<div class="btn-control">
<span class="cancel" @click="cancel">取消</span>
<el-button class="btn" type="success" round @click="commitComment">確定</el-button>
</div>
</div>
</transition>
</div>
</div>
</div>
</template>
<script>
import Vue from 'vue'
export default {
props: {
comments: {
type: Array,
required: true
}
},
components: {},
data() {
return {
inputComment: '',
showItemId: ''
}
},
computed: {},
methods: {
/**
* 點贊
*/
likeClick(item) {
if (item.isLike === null) {
Vue.$set(item, "isLike", true);
item.likeNum++
} else {
if (item.isLike) {
item.likeNum--
} else {
item.likeNum++
}
item.isLike = !item.isLike;
}
},
/**
* 點選取消按鈕
*/
cancel() {
this.showItemId = ''
},
/**
* 提交評論
*/
commitComment() {
console.log(this.inputComment);
},
/**
* 點選評論按鈕顯示輸入框
* item: 當前大評論
* reply: 當前回覆的評論
*/
showCommentInput(item, reply) {
if (reply) {
this.inputComment = "@" + reply.fromName + " "
} else {
this.inputComment = ''
}
this.showItemId = item.id
}
},
created() {
console.log(this.comments)
}
}
</script>
<style scoped lang="scss">
@import "../../public/scss/index";
.container {
padding: 0 10px;
box-sizing: border-box;
.comment {
display: flex;
flex-direction: column;
padding: 10px;
border-bottom: 1px solid $border-fourth;
.info {
display: flex;
align-items: center;
.avatar {
border-radius: 50%;
}
.right {
display: flex;
flex-direction: column;
margin-left: 10px;
.name {
font-size: 16px;
color: $text-main;
margin-bottom: 5px;
font-weight: 500;
}
.date {
font-size: 12px;
color: $text-minor;
}
}
}
.content {
font-size: 16px;
color: $text-main;
line-height: 20px;
padding: 10px 0;
}
.control {
display: flex;
align-items: center;
font-size: 14px;
color: $text-minor;
.like {
display: flex;
align-items: center;
margin-right: 20px;
cursor: pointer;
&.active, &:hover {
color: $color-main;
}
.iconfont {
font-size: 14px;
margin-right: 5px;
}
}
.comment-reply {
display: flex;
align-items: center;
cursor: pointer;
&:hover {
color: $text-333;
}
.iconfont {
font-size: 16px;
margin-right: 5px;
}
}
}
.reply {
margin: 10px 0;
border-left: 2px solid $border-first;
.item {
margin: 0 10px;
padding: 10px 0;
border-bottom: 1px dashed $border-third;
.reply-content {
display: flex;
align-items: center;
font-size: 14px;
color: $text-main;
.from-name {
color: $color-main;
}
.to-name {
color: $color-main;
margin-left: 5px;
margin-right: 5px;
}
}
.reply-bottom {
display: flex;
align-items: center;
margin-top: 6px;
font-size: 12px;
color: $text-minor;
.reply-text {
display: flex;
align-items: center;
margin-left: 10px;
cursor: pointer;
&:hover {
color: $text-333;
}
.icon-comment {
margin-right: 5px;
}
}
}
}
.write-reply {
display: flex;
align-items: center;
font-size: 14px;
color: $text-minor;
padding: 10px;
cursor: pointer;
&:hover {
color: $text-main;
}
.el-icon-edit {
margin-right: 5px;
}
}
.fade-enter-active, fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.input-wrapper {
padding: 10px;
.gray-bg-input, .el-input__inner {
/*background-color: #67C23A;*/
}
.btn-control {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 10px;
.cancel {
font-size: 16px;
color: $text-normal;
margin-right: 20px;
cursor: pointer;
&:hover {
color: $text-333;
}
}
.confirm {
font-size: 16px;
}
}
}
}
}
}
</style>
複製程式碼
資料由使用的時候通過 comments
使用 props
傳入。
佈局用到了 Element-ui 的一些元件,元件的屬性和方法請檢視官方文件。
css
用了前處理器 SASS/SCSS
編寫,$
開頭的為 SCSS
的變數,所有的顏色都放在了一個檔案下,便於日後維護修改
$color-main: #409EFF;
$color-success: #67C23A;
$color-warning: #E6A23C;
$color-danger: #F56C6C;
$color-info: #909399;
$text-main: #303133;
$text-normal: #606266;
$text-minor: #909399; //次要文字
$text-placeholder: #C0C4CC;
$text-333: #333;
$border-first: #DCDFE6;
$border-second: #E4E7ED;
$border-third: #EBEEF5;
$border-fourth: #F2F6FC;
$content-bg-color: #fff
複製程式碼
五、使用
封裝完之後,就可以愉快的使用啦!
在需要使用的檔案裡,先將第三步模擬好的資料匯入進來,再引入 comment
元件,再將模擬的資料賦值給元件的 :comments
屬性。
只留下核心程式碼,則使用方法如下:
<template>
<comment :comments="commentData"></comment>
</template>
<script>
import * as CommentData from '../mockdata'
import comment from '../components/Comment'
export default {
components: {
comment
},
data() {
return {
commentData: []
}
},
created() {
this.commentData = CommentData.comment.data;
},
}
<script>
複製程式碼
以上就是仿簡書的評論模組,功能很簡單,就是佈局稍微有點複雜。
歡迎大家交流分享~