Vue.js 建立一個 CNODE 社區(完)

實戰

通過了前面的 Vue 基礎的鋪墊,如今終於開始進行實戰部分了。javascript

代碼連接:GitHubcss

預覽連接: Git Pageshtml

圖片預覽:圖片預覽vue

cnode 社區基本架構

  • Header 頭部java

  • PostList 列表node

  • Article 文章詳情頁ios

  • SideBar 側邊欄git

  • UserInfo 我的信息github

  • Pagination 分頁組件web


安裝 vue-cli

安裝教程


項目 Header 組件,主要展現 logo 及 一級菜單。


PostList

項目中的 文章列表,其中包括做者、點擊量、評論量、文章標題、發表時間等。

  • 數據獲取

經過官方提供的 API : https://cnodejs.org/api/v1/topics 獲取帖子列表

而後經過 Chrome 的一個小插件 yformater 格式化 API 返回的 JSON 文件,分析須要獲取的數據。

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

使用 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 動態綁定樣式:

我的整理博客: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>

Article

文章詳情頁,其中包括文章標題、發佈日期、正文、評論等內容

API https://cnodejs.org/api/v1/topic/ + 帖子ID

  • router-link

主要利用 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


userInfo

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 獲取文章數據,渲染新的頁面。


Pagination

分頁器

  • 使用 :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)
      }
  },

完善

tab 菜單

能夠選擇不一樣的主題進行瀏覽:

tab菜單

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> 中。


Registered / Login

註冊和登陸頁

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。

相關文章
相關標籤/搜索