添加评论功能

评论框:src/views/articles/Content.vue

 1 <!-- 评论框 -->
 2 <div id="reply-box" class="reply-box form box-block">
 3   <div class="form-group comment-editor">
 4     <textarea v-if="auth" id="editor"></textarea>
 5     <textarea v-else disabled class="form-control" placeholder="需要登录后才能发表评论." style="height:172px"></textarea>
 6   </div>
 7   <div class="form-group reply-post-submit">
 8     <button id="reply-btn" :disabled="!auth" @click="comment" class="btn btn-primary">回复</button>
 9     <span class="help-inline">Ctrl+Enter</span>
10   </div>
11   <div v-show="commentHtml" id="preview-box" class="box preview markdown-body" v-html="commentHtml"></div>
12 </div>

未登录时,我们显示一个被禁用的评论框:

HTML 格式的评论被保存在 commentHtml,我们使用 v-html 指令实时输出它:

2、在 created 钩子后面添加 mounted 钩子,我们在这里创建一个 SimpleMDE 编辑器的实例:

src/views/articles/Content.vue

 1 mounted() {
 2   // 已登录时,才开始创建
 3   if (this.auth) {
 4     // 自动高亮编辑器的内容
 5     window.hljs = hljs
 6 
 7     const simplemde = new SimpleMDE({
 8       element: document.querySelector('#editor'),
 9       placeholder: '请使用 Markdown 格式书写 ;-),代码片段黏贴时请注意使用高亮语法。',
10       spellChecker: false,
11       autoDownloadFontAwesome: false,
12       // 不显示工具栏
13       toolbar: false,
14       // 不显示状态栏
15       status: false,
16       renderingConfig: {
17         codeSyntaxHighlighting: true
18       }
19     })
20 
21     // 内容改变监听
22     simplemde.codemirror.on('change', () => {
23       // 更新 commentMarkdown 为编辑器的内容
24       this.commentMarkdown = simplemde.value()
25       // 更新 commentHtml,我们先替换原内容中的 emoji 标识,然后使用 markdown 方法将内容转成 HTML
26       this.commentHtml = simplemde.markdown(emoji.emojify(this.commentMarkdown, name => name))
27     })
28 
29     // 按键松开监听
30     simplemde.codemirror.on('keyup', (codemirror, event) => {
31       // 使用 Ctrl+Enter 时提交评论
32       if (event.ctrlKey && event.keyCode === 13) {
33         this.comment()
34       }
35     })
36 
37     // 将编辑器添加到当前实例
38     this.simplemde = simplemde
39   }
40 },

 

3.点击发表按钮时候的comment事件在 methods 选项中添加评论方法 comment

src/views/articles/Content.vue

 1 comment() {
 2   // 编辑器的内容不为空时
 3   if (this.commentMarkdown && this.commentMarkdown.trim() !== '') {
 4     // 分发 comment 事件以提交评论
 5     this.$store.dispatch('comment', {
 6       comment: { content: this.commentMarkdown },
 7       articleId: this.articleId
 8     }).then((comments) => {
 9       // 在浏览器的控制台打印返回的评论列表
10       console.log(comments)
11     })
12 
13     // 清空编辑器
14     this.simplemde.value('')
15     // 使回复按钮获得焦点
16     document.querySelector('#reply-btn').focus()
17   }
18 },

 

打开 src/store/actions.js 文件,在代码的最后面,导出评论事件 comment

src/store/actions.js

 1 .
 2 .
 3 .
 4 // 参数 articleId 是文章 ID;comment 是评论内容;commentId 是评论 ID
 5 export const comment = ({ commit, state }, { articleId, comment, commentId }) => {
 6   // 仓库的文章
 7   let articles = state.articles
 8   // 评论列表
 9   let comments = []
10 
11   if (!Array.isArray(articles)) articles = []
12 
13   for (let article of articles) {
14     // 找到对应文章时
15     if (parseInt(article.articleId) === parseInt(articleId)) {
16       // 更新评论列表
17       comments = Array.isArray(article.comments) ? article.comments : comments
18 
19       if (comment) {
20         // 获取用户传入的评论内容,设置用户 ID 的默认值为 1
21         const { uid = 1, content } = comment
22         const date = new Date()
23 
24         if (commentId === undefined) {
25           const lastComment = comments[comments.length - 1]
26 
27           // 新建 commentId
28           if (lastComment) {
29             commentId = parseInt(lastComment.commentId) + 1
30           } else {
31             commentId = comments.length + 1
32           }
33 
34           // 在评论列表中加入当前评论
35           comments.push({
36             uid,
37             commentId,
38             content,
39             date
40           })
41         }
42       }
43 
44       // 更新文章的评论列表
45       article.comments = comments
46       break
47     }
48   }
49 
50   // 提交 UPDATE_ARTICLES 以更新所有文章
51   commit('UPDATE_ARTICLES', articles)
52   // 返回评论列表
53   return comments
54 }

添加评论列表

1、打开 src/views/articles/Content.vue 文件,在 data 中添加 comments

src/views/articles/Content.vue

 1 data() {
 2   return {
 3     title: '', // 文章标题
 4     content: '', // 文章内容
 5     date: '', // 文章创建时间
 6     uid: 1, // 用户 ID
 7     likeUsers: [], // 点赞用户列表
 8     likeClass: '', // 点赞样式
 9     showQrcode: false, // 是否显示打赏弹窗
10     commentHtml: '', // 评论 HTML
11     comments: [], // 评论列表
12   }
13 },

2、修改 created 钩子(注释部分是涉及的修改):

src/views/articles/Content.vue 在页面渲染的时候将评论渲染出来

 1 created() {
 2   const articleId = this.$route.params.articleId
 3   const article = this.$store.getters.getArticleById(articleId)
 4 
 5   if (article) {
 6     // 获取文章的 comments
 7     let { uid, title, content, date, likeUsers, comments } = article
 8 
 9     this.uid = uid
10     this.title = title
11     this.content = SimpleMDE.prototype.markdown(emoji.emojify(content, name => name))
12     this.date = date
13     this.likeUsers = likeUsers || []
14     this.likeClass = this.likeUsers.some(likeUser => likeUser.uid === 1) ? 'active' : ''
15     // 渲染文章的 comments
16     this.renderComments(comments)
17 
18     this.$nextTick(() => {
19       this.$el.querySelectorAll('pre code').forEach((el) => {
20         hljs.highlightBlock(el)
21       })
22     })
23   }
24 
25   this.articleId = articleId
26 },

3、在 methods 选项中添加渲染评论方法 renderComments

src/views/articles/Content.vue

 1 renderComments(comments) {
 2   if (Array.isArray(comments)) {
 3     // 深拷贝 comments 以不影响其原值
 4     const newComments = comments.map(comment => ({ ...comment }))
 5     const user = this.user || {}
 6 
 7     for (let comment of newComments) {
 8       comment.uname = user.name
 9       comment.uavatar = user.avatar
10       // 将评论内容从 Markdown 转成 HTML
11       comment.content = SimpleMDE.prototype.markdown(emoji.emojify(comment.content, name => name))
12     }
13 
14     // 更新实例的 comments
15     this.comments = newComments
16     // 将 Markdown 格式的评论添加到当前实例
17     this.commentsMarkdown = comments
18   }
19 },

注:深拷贝 comments 是为了不影响已保存的文章,其方法等价于:

const newComments = comments.map(function (comment) {
  return Object.assign({}, comment)
})

上面的方法只处理了对象的第一层数据,当对象能被 JSON 解析时,可以使用下面的方法进行完整的深拷贝:

JSON.parse(JSON.stringify(comments))

4、修改 comment 评论方法(注释部分是涉及的修改):

src/views/articles/Content.vue

 1 comment() {
 2   if (this.commentMarkdown && this.commentMarkdown.trim() !== '') {
 3     this.$store.dispatch('comment', {
 4       comment: { content: this.commentMarkdown },
 5       articleId: this.articleId
 6     }).then(this.renderComments) // 在 .then 的回调里,调用 this.renderComments 渲染评论
 7 
 8     this.simplemde.value('')
 9     document.querySelector('#reply-btn').focus()
10 
11     // 将最后的评论滚动到页面的顶部
12     this.$nextTick(() => {
13       const lastComment = document.querySelector('#reply-list li:last-child')
14       if (lastComment) lastComment.scrollIntoView(true)
15     })
16   }
17 },

5、查找 <Modal ,在其后面添加『评论列表』:

src/views/articles/Content.vue

 1 <Modal :show.sync="showQrcode" class="text-center">
 2   .
 3   .
 4   .
 5 </Modal>
 6 
 7 <!-- 评论列表 -->
 8 <div class="replies panel panel-default list-panel replies-index">
 9   <div class="panel-heading">
10     <div class="total">
11       回复数量: <b>{{ comments.length }}</b>
12     </div>
13   </div>
14   <div class="panel-body">
15     <ul id="reply-list" class="list-group row">
16       <li v-for="(comment, index) in comments" :key="comment.commentId" class="list-group-item media">
17         <div class="avatar avatar-container pull-left">
18           <router-link :to="`/${comment.uname}`">
19             <img :src="comment.uavatar" class="media-object img-thumbnail avatar avatar-middle">
20           </router-link>
21         </div>
22         <div class="infos">
23           <div class="media-heading">
24             <router-link :to="`/${comment.uname}`" class="remove-padding-left author rm-link-color">
25               {{ comment.uname }}
26             </router-link>
27             <div class="meta">
28               <a :id="`reply${index + 1}`" :href="`#reply${index + 1}`" class="anchor">#{{ index + 1 }}</a>
29               <span> ⋅ </span>
30               <abbr class="timeago">
31                 {{ comment.date | moment('from', { startOf: 'second' }) }}
32               </abbr>
33             </div>
34           </div>
35 
36           <div class="preview media-body markdown-reply markdown-body" v-html="comment.content"></div>
37         </div>
38       </li>
39     </ul>
40     <div v-show="!comments.length" class="empty-block">
41       暂无评论~~
42     </div>
43   </div>
44 </div>

 

posted @ 2018-07-17 10:38  前端极客  阅读(3601)  评论(1编辑  收藏  举报