通過了前面的 Vue 基礎的鋪墊,如今終於開始進行實戰部分了。javascript
代碼連接:GitHubcss
預覽連接: Git Pageshtml
圖片預覽:vue
Header 頭部java
PostList 列表node
Article 文章詳情頁ios
SideBar 側邊欄git
UserInfo 我的信息github
Pagination 分頁組件web
項目 Header 組件,主要展現 logo 及 一級菜單。
項目中的 文章列表,其中包括做者、點擊量、評論量、文章標題、發表時間等。
經過官方提供的 API : https://cnodejs.org/api/v1/topics 獲取帖子列表
而後經過 Chrome 的一個小插件 yformater 格式化 API 返回的 JSON 文件,分析須要獲取的數據。
"id": "5baee8de9545eaf107b9c6f3", // 文章ID "author_id": "51f0f267f4963ade0e08f503", // 做者ID "tab": "share", // 文章分類-分享 表示除了置頂和精華以外的其他分區 share 分享 / ask 問答 / job 招聘 "content": ..., // 文章內容 "title": "Node 地下鐵第七期「深圳站」線下沙龍邀約 - Node.js 新生態", // 文章標題 "last_reply_at": "2018-10-12T00:40:26.741Z", // 文章最後回覆時間 "good": false, // 表明是否精華 "top": true, // 表明是否置頂 "reply_count": 13, // 回覆數量 "visit_count": 1420, // 瀏覽數量 "create_at": "2018-09-29T02:52:14.701Z", // 文章發表時間 "author": { "loginname": "lellansin", // 做者名稱 "avatar_url": "https://avatars2.githubusercontent.com/u/2081487?v=4&s=120" // 做者頭像 }
使用 axios 教程
把獲取的數據渲染到頁面上
運用 filter
使用 filter 對時間戳進行處理:
Vue.filter('formatDate', function (str) { if (!str) return '' var date = new Date(str) var time = new Date().getTime() - date.getTime() //如今的時間-傳入的時間 = 相差的時間(單位 = 毫秒) if (time < 0) { return '' } else if ((time / 1000 < 30)) { return '剛剛' } else if (time / 1000 < 60) { return parseInt((time / 1000)) + '秒前' } else if ((time / 60000) < 60) { return parseInt((time / 60000)) + '分鐘前' } else if ((time / 3600000) < 24) { return parseInt(time / 3600000) + '小時前' } else if ((time / 86400000) < 31) { return parseInt(time / 86400000) + '天前' } else if ((time / 2592000000) < 12) { return parseInt(time / 2592000000) + '月前' } else { return parseInt(time / 31536000000) + '年前' } })
使用過濾器來判斷帖子分類:
Vue.filter('tabFormatter',function (post) { if(post.good == true){ return '精華' }else if(post.top == true){ return '置頂' }else if(post.tab == 'ask'){ return '問答' }else if(post.tab == 'share'){ return '分享' }else{ return '招聘' } })
使用 v-bind 動態綁定樣式:
我的整理博客:v-bind
<span :class="[ {put_good:(post.good === true)}, {put_top:(post.top === true)}, {'topiclist-tab':(post.good !== true && post.top !== true)} ]"> {{ post | tabFormatter}} </span>
文章詳情頁,其中包括文章標題、發佈日期、正文、評論等內容
API https://cnodejs.org/api/v1/topic/ + 帖子ID
主要利用 router-link 從文章列表 PostList 跳轉到文章詳情頁 Article。
實現思路:
1.在 PostList.vue 中的每條文章添加 router-link:
<router-link :to="{name:'post_content',params:{id:post.id}}"> <span>{{ post.title}}</span> </router-link>
2.點擊後帶着參數 id:post.id
找到 router 中的 index.js 中設定的路徑 name:'post_content'
3.打開 url path:'/topic/:id'
,渲染組件 Article
4.Article 中經過 API 獲取了單篇文章的數據 this.$axios.get(`https://cnodejs.org/api/v1/topic/${this.$route.params.id}`)
,而後賦值給了組件中的 data,在頁面中渲染出來。
總得來講就是: router-link
-> router/index.js
-> router-view
API https://cnodejs.org/api/v1/user/ + username
而後就是重複 router-link 的套路
問題 說說 markdown-github.css
經過 API 返回了一篇文章的內容 content,content 是由 markdown 語法編寫的。
一開始的處理思路是,引入 assets 目錄下的 markdown-github.css ,而後在組件中引入 @import url('../assets/markdown-github.css');
,接着經過 v-html
在頁面中把內容渲染出來,可是發現沒有效果,樣式沒有起到變化。
而後從遇到一樣問題的同窗那裏獲得瞭解決方法:
1.在項目中安裝: cnpm i markdown-github-css
2.在 main.js 中引入:import markdown-github-css
3.在容器div添加類名 markdown-body
:
<div v-html="post.content" class="topic_content markdown-body"></div>
展現側邊欄,包括做者信息、最近主題,最近回覆等。
使用 computed 對取得的文章列表作一個篩選,只顯示前 5 條:
computed:{ topicLimitBy5(){ // 這裏不用 length 判斷是由於剛開始渲染的時候 userinfo 是空的,是沒有 length 的,因此會報錯 if(this.userinfo.recent_replies){ return this.userinfo.recent_replies.slice(0,5); } }, repliesLimitBy5(){ if(this.userinfo.recent_replies){ return this.userinfo.recent_replies.slice(0,5); } } },
問題 提示 [vue-router] missing param for named route "user_info": Expected "name" to be defined
點擊連接後 url 有變化,可是不跳轉。
<!-- SideBar.vue --> <li v-for="item in topicLimitBy5"> <router-link :to="{name:'post_content',params:{id:item.id,name:item.author.loginname}}"> {{item.title}} </router-link> </li>
緣由:沒有對路由進行檢測。
在 vue.js 的文檔中,他是這樣解釋的:
提醒一下,當使用路由參數時,例如從 /user/foo 導航到 /user/bar,原來的組件實例會被複用。由於兩個路由都渲染同個組件,比起銷燬再建立,複用則顯得更加高效。不過,這也意味着組件的生命週期鉤子不會再被調用。
複用組件時,想對路由參數的變化做出響應的話,你能夠簡單地 watch (監測變化) $route 對象
在這個錯誤中,由於點擊的連接路由名稱都是 /topic/...
,因此至關於複用了組件,沒有對路由參數的變化作出響應,所以作出修改:
// Article.vue watch:{ '$route'(to,from){ // 經過 id 獲取文章詳情 this.getArticleData() } }
每當 Article 檢測到路由發生變化,則執行方法,經過新的文章 id 獲取文章數據,渲染新的頁面。
分頁器
:class
綁定樣式,@click='changebBtn'
實現點擊不一樣頁碼後按鈕樣式切換,同時經過 $emit 向父組件發出信息,從 API 獲取不一樣頁碼的數據,渲染在頁面上。<!-- Pagination.vue --> <button v-for="btn in pagebtns" :class="[{currentPage:btn === currentPage},{pagebtn:true}]"> {{btn}} </button>
data() { return { // 先給分頁器一個固定的數組 'pagebtns':[1,2,3,4,5,'...'], // 給每一個按鈕一個「座標」 currentPage:1, isEllipsis:false }; }, methods:{ changeBtn(page){ if(typeof page !== 'number'){ switch (page.currentTarget.innerText){ case '首頁': this.pagebtns = [1,2,3,4,5,'...'] this.changeBtn(1) break; case '上一頁': $('button.currentPage').prev().click() break; case '下一頁': $('button.currentPage').next().click() break; default: break; } return } if(page >4){ this.isEllipsis = true }else{ this.isEllipsis = false } this.currentPage = page // 當點擊的按鈕是第5個時 if(page === this.pagebtns[4]){ this.pagebtns.shift() this.pagebtns.splice(4,0,this.pagebtns[3]+1 ) // 當點擊的按鈕是第1個時 }else if(page === this.pagebtns[0] && this.pagebtns[1]>2){ this.pagebtns.splice(4,1) this.pagebtns.unshift(this.pagebtns[0]-1) } // 傳遞數據給父組件 PostList this.$emit('handleList',this.currentPage) } },
能夠選擇不一樣的主題進行瀏覽:
1.tab 菜單中每一個選項綁定點擊事件,點擊後根據傳入參數的不一樣獲取不一樣主題的內容:
<!-- PostList.vue --> <span @click="changeTab('')">所有</span> <span @click="changeTab('good')">精華</span> <span @click="changeTab('share')">分享</span> <span @click="changeTab('ask')">問答</span> <span @click="changeTab('job')">招聘</span>
// PostList.vue changeTab(value){ this.tab = value this.getData() }
改變 tab,從新執行方法 getData(),獲取不一樣主題帖子的數據,在頁面中渲染出來。
2.點擊了 tab 菜單後,頁碼回到該主題的第1頁
若是隻停留在上一步,則會出現這樣的問題:點擊 問答
-> 跳轉到第6頁 -> 再點擊 首頁
-> 頁碼顯示停留在 首頁
的第6頁,可是內容其實是 首頁
的第1頁
也就是他的樣式沒有轉換過來。
解決方法:父組件把 tab 當成參數傳遞給子組件,子組件 watch 這個 tab,一旦這個 tab 發生變化,則回到這個 tab 對應的主題的第一頁:
<!-- PostList.vue --> <Pagination @handleList='renderList' :tab='tab'></Pagination>
// Pagination ... props:[ 'tab' ], ... watch:{ tab:function(val,oldVal){ this.pagebtns = [1,2,3,4,5,'...'] this.changeBtn(1) } }
對文章中同時包含了中英文的字符串的長度進行解析,限制字符串長度
Vue.filter('postListConversion',function(str,len){ var result = ""; var strlen = 0; for(var i = 0;i < str.length; i++){ if(str.charCodeAt(i) > 255){ strlen += 2; //若是是漢字,則字符串長度加2 } else { strlen++; } result += str.substr(i,1); if(strlen >= len){ break; } } if(strlen < len){ return result }else{ return `${result}...`; } })
如:
@media screen and (max-width: 979px){ .autherinfo{ float: none; position: absolute; bottom: -4px; left: 22px; } ul a{ max-width: 96%; -o-text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle; line-height: 30px; overflow: hidden; text-overflow: ellipsis; } }
當設備分辨率寬度小於 979px 時,樣式會生效。
原本打算是另寫一個 css 文件,存放在 /assets/css 的文件目錄下,而後在 main.js 中經過 import './assets/css/main.css'
引入的,可是查閱資料的時候看到說這樣作並很差,到時候須要修改樣式會很麻煩,因此就寫在了每一個組件的 <style>
中。
註冊和登陸頁
1.註冊頁:
使用 localStorage 存儲註冊用戶的用戶名和密碼,v-model 綁定輸入框的 value ,判斷 localStorage 裏有沒有 value:
有,能夠直接登陸;無,則註冊成功。
methods:{ submitInfo(){ //判斷是否存在此用戶名 if (localStorage.getItem(this.username) === null) { this.usernameIsRight = false //存入用戶名和密碼 localStorage.setItem(this.username,this.password) this.isWorks = true setTimeout(()=>{ // 跳轉到首頁 this.$router.push({path:'/login'}) },2000) }else{ this.usernameIsRight = true } } }
2.登陸頁:
經過 localStorage 判斷輸入框 value,匹配則轉到首頁,不匹配則提示密碼錯誤或者用戶未註冊。
methods:{ submitInfo(){ //判斷是否存在此用戶名 if (localStorage.getItem(this.username) === null) { this.usernameIsRight = true // 判斷用戶名和密碼是否匹配 }else if(localStorage.getItem(this.username) !== this.password){ this.passwordIsRight = true } // 用戶名和密碼匹配則帶着參數(用戶名)跳轉到 /user/ else if(localStorage.getItem(this.username) === this.password){ this.usernameIsRight = false this.$router.push({name:'user',params:{name:this.username}}) // 這裏先留個坑,若是不刷新的話,則頁面登陸狀態不會改變,應該是和組件的生命週期有關係,目前暫時沒有搞清楚 window.location.reload() } } }
3.首頁:
經過 url 參數拿到 用戶名:username:this.$route.params.name
而後把用戶名渲染到頁面中。
這裏說說沒有解決的 bug :
原本打算使用 eventBus 來傳遞數據,登陸的用戶名傳給首頁,而後首頁判斷用戶名是否存在 localStorage 中,再去渲染,這樣感受流程比較流暢;可是點擊提交按鈕後頁面會跳轉到首頁,組件的生命週期也會變化,全部沒有想到好的接收數據的方法。
我想我應該試試 vuex。