最近開發項目須要用到評論模塊,參考了多家平臺,對比以後以爲簡書的最漂亮,就它了。css
前端固然要先放圖前端
簡書原圖vue
注意評論的輸入框默認是隱藏的,點擊回覆纔會顯示出來程序員
本篇文章實現的效果數據庫
一樣輸入框須要點擊回覆纔會顯示數組
從 UI 上看,整個評論模塊大體分爲四個版塊bash
- 評論人信息。包括頭像,暱稱,評論時間
- 評論的內容。包括文字內容,讚的個數和回覆按鈕
- 他人的回覆。回覆可能有多條,因此這是個
v-for
循環。回覆包括回覆人及被回覆人的暱稱,回覆內容,時間,以及回覆按鈕- 評論輸入框。輸入框最開始是隱藏的,點擊回覆按鈕或添加新評論時纔會顯示,點取消隱藏。
回覆功能能夠回覆當前評論自己,也能夠回覆其餘用戶對這條評論的評論,咱們稱之爲子評論。全部子評論都掛載最初的父評論下。
當點擊子評論的回覆按鈕時,輸入框彈出的同時會自動填上 @
+ 被回覆者的暱稱,使邏輯更加合理。服務器
點擊添加新評論是添加對本條評論的子評論,不是對文章的新評論,因此文章末尾處應該還有一個輸入框,用來發表新評論。數據結構
參照的效果圖有了,接下來就是設計數據了。數據庫設計就不在這裏說了,本項目把評論模塊分了兩張表,分別存放評論和回覆。新建一個 mockdata.js
文件,模擬服務器返回的數據app
//模擬評論數據
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>
複製代碼
以上就是仿簡書的評論模塊,功能很簡單,就是佈局稍微有點複雜。
歡迎你們交流分享~