評論框: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>