评论框: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>