Vue全家桶開發的CNode社區單頁web應用

寫這篇文章就是爲了記錄一下個人學習過程。在之後回顧此項目時,也能夠更方便地發現此項目中的不足和精華。在此,感謝VNode社區提供的API。css

源碼在此Vue-CNode
預覽地址使勁點我
你也能夠掃描下面的二維碼預覽線上項目:html

二維碼

1、需求分析

要作一個項目以前,我以爲首先要把功能作一個總結,根據需求來寫項目,從而作到有的放矢。vue

因此我根據API寫了項目的需求,可見下圖:node

CNode功能需求分析

2、技術棧

本項目使用的技術棧就是標準的Vue全家桶,即:webpack

Vue2.0: 構建項目,屬於底層框架。
Vue-Router: 經過hash值的變化,從而改變頁面結構的路由。
Vuex: Vue官方提供的狀態管理模式。
Axios, Vue-Axios: http請求模塊。
ES6: 新的Javascript語法。
Sass: CSS預編譯器。
Webpack: 用於打包項目。

3、功能實現狀況

  • [x] 首頁列表ios

  • [x] 無限懶加載文章列表git

  • [x] 切換內容主題github

  • [x] 文章詳情web

  • [x] 在文章詳情頁時,能夠後退至主頁vue-router

  • [x] 回到頂部功能,並添加動畫效果

  • [x] 關於

  • [x] 用戶登陸

  • [x] 用戶退出

  • [x] 我的主頁

  • [x] 個人收藏

  • [x] 點擊用戶頭像,能夠進入該用戶的簡介頁面

  • [x] 登錄後,可在文章詳情頁點贊和評論

  • [x] 登錄後,在主頁顯示發佈主題按鈕,能夠發佈主題

  • [x] 消息通知,消息設置已讀功能

  • [x] 對本身的文章能夠進行編輯更新

  • [x] 操做成功或失敗後的消息提醒

  • [ ] 增長markdown的編輯器組件和預覽器組件

  • [ ] 評論排序功能

4、項目初始化

利用Vue-cli提供的初始化工具,運行如下代碼:

# install vue-cli
$ npm install --global vue-cli
# create a new project using the "webpack" template
$ vue init webpack my-project
# install dependencies and go!
$ cd my-project
$ npm install
$ npm run dev

此時打開http://localhost:8080/就能夠訪問初始化後的頁面了。

5、項目編寫

注意:詳細內容能夠去源碼自行查看。

完成初始化以後呢,咱們就能夠開始編寫項目了。
代碼分爲四塊,分別是:components(組件)vue-router(路由)vuex(狀態管理模式)common(放置公共樣式,字體和通用的功能代碼)
在項目編寫以前,受限於要安裝依賴,代碼以下:

# 安裝vuex,vue-router,axios,vue-axios
$ npm install vuex vue-router axios vue-axios --save
// 安裝sass依賴
# npm install node-sass sass-loader --save-dev

1.common公用文件

包括樣式(style),字體(fonts)還有工具函數(utils, 包括時間格式化還有cookie存取功能)。

2.Components組件

如今暫時一共有14個組件,包括:

AboutMe
Article
ArticleCard
BackBar
BottomBar
Content
Loading
Login
MessageCard
MyCollect
navBar
Notification
Publish
UserDetail

具體內容能夠參見後面的項目目錄。

3. Vue-Router 路由配置

經過路由,分爲一下七個頁面:
① 主頁
② 文章詳情頁
③ 用戶詳情頁
④ 用戶登陸頁
⑤ 發佈文章頁
⑥ 用戶收藏頁
⑦ 個人通知頁

5. Vuex:狀態管理模式

狀態管理分爲六個模塊:content(主頁)、article(文章頁)、navbar(導航欄)、user(用戶詳情狀態)、login(用戶登陸狀態)和notification(通知)。

6、項目目錄

.
├── build                               // webpack設置
│   ├── build.js
│   ├── check-versions.js
│   ├── dev-client.js
│   ├── dev-server.js
│   ├── utils.js
│   ├── vue-loader.conf.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   └── webpack.prod.conf.js
├── config                              // 項目開發和打包設置
│   ├── dev.env.js
│   ├── index.js
│   └── prod.env.js
├── docs                                // 靜態資源地址
│   ├── index.html
│   └── static
│       ├── css
│       │   └── app.d99bca81a0eef77c7e0d8c70f520707c.css
│       ├── fonts
│       │   ├── iconfont.8553d3c.ttf
│       │   └── iconfont.b29ac85.eot
│       ├── img
│       │   └── iconfont.d4553f2.svg
│       └── js
│           ├── app.cb09e437ae0bec6205b9.js
│           ├── manifest.aa9548ef140031379c30.js
│           └── vendor.f3d0844a66c0c2cabe0b.js
├── src                                 // 項目文件位置
│   ├── App.vue                         // 組件總入口
│   ├── common                          // 通用文件
│   │   ├── fonts                       // 字體
│   │   │   ├── iconfont.eot
│   │   │   ├── iconfont.svg
│   │   │   ├── iconfont.ttf
│   │   │   └── iconfont.woff
│   │   ├── style                       // 樣式
│   │   │   ├── animation.scss          // 動畫
│   │   │   ├── base.scss               // 基本樣式
│   │   │   └── icon.scss               // iconfont的字體圖標樣式
│   │   └── utils                       // 工具函數
│   │       ├── cookie.js               // cookie存取和刪除
│   │       └── timeFormat.js           // 格式化時間函數
│   ├── components                      // 全部組件
│   │   ├── AboutMe                     // 關於
│   │   │   └── AboutMe.vue
│   │   ├── Article                     // 文章詳情頁
│   │   │   └── Article.vue
│   │   ├── ArticleCard                 // 文章列表的單個文章卡片
│   │   │   └── ArticleCard.vue
│   │   ├── BackBar                     // 頂部的返回欄(返回主頁和後退)
│   │   │   └── BackBar.vue
│   │   ├── BottomBar                   // 底部的回覆欄(還包含收藏和編輯文件)
│   │   │   └── BottomBar.vue
│   │   ├── Content                     // 主頁
│   │   │   └── Content.vue
│   │   ├── Loading                     // 正在加載組件
│   │   │   ├── Loading.vue
│   │   │   └── loading.svg
│   │   ├── Login                       // 登陸
│   │   │   └── Login.vue
│   │   ├── MessageCard                 // 單個通知的詳情卡片
│   │   │   └── MessageCard.vue
│   │   ├── MyCollect                   // 個人收藏頁
│   │   │   └── MyCollect.vue
│   │   ├── Notification                // 通知頁
│   │   │   └── Notification.vue
│   │   ├── Publish                     // 發佈文章和發佈更新頁
│   │   │   └── Publish.vue
│   │   ├── UserDetail                  // 用戶詳情頁
│   │   │   └── UserDetail.vue
│   │   └── navBar                      // 主頁的頂部導航欄
│   │       ├── cnodejs_light.svg
│   │       └── navBar.vue
│   ├── main.js                         // 項目的總入口
│   ├── pic                             // 和代碼無關,README.md中的圖片
│   │   ├── CNode�\212\237�\203��\234\200�\202�\210\206�\236\220.png
│   │   └── QR-Code.png
│   ├── router                          // 路由設置
│   │   └── index.js
│   └── store                           // 狀態管理
│       ├── modules
│       │   ├── article                 // 文章詳情頁
│       │   │   ├── article-mutation-types.js
│       │   │   └── article.js
│       │   ├── content                 // 主頁
│       │   │   ├── content-mutation-types.js
│       │   │   └── content.js
│       │   ├── login                   // 登陸頁
│       │   │   ├── login-mutation-types.js
│       │   │   └── login.js
│       │   ├── navbar                  // 主頁導航欄
│       │   │   ├── navbar-mutation-types.js
│       │   │   └── navbar.js
│       │   ├── notification            // 通知頁
│       │   │   ├── notification-mutation-types.js
│       │   │   └── notification.js
│       │   └── user                    // 用戶詳情頁
│       │       ├── user-mutation-types.js
│       │       └── user.js
│       └── store.js                    // 狀態管理總入口
├── README.md
├── index.html
└── package.json

7、過程當中遇到的問題

本項目算是本人第一個完整的手機和pc都兼容,有關於文章展現的項目。整個項目作下來,遇到的Bug不少,天然收穫也是不少。總結下來以下:

1.很長的單詞會超出邊界,致使可視區域變寬。

解決辦法:經過word-wrap: break-word;實現打斷效果。

2.第二次進入文章時,會殘留(暫未解決)。

解決辦法:經過路由的鉤子函數beforeRouteEnter,來獲取數據,未成功獲取數據時,顯示Loading頁面,加載完成後,顯示文章詳情頁,從而解決這個問題。

3.回到首頁時,不能保留原來的狀態(暫未解決)。

解決辦法:

①此方法爲容易固定高度的解決辦法。(具體方法:用vuex和vue-router的鉤子函數來解決這個問題,即經過scroll事件動態保存此時的scrollTop直,當路由的beforeRouteEnter出發時,恢復其scrollTop的值。)

② 若是沒有固定高度,直接經過Vue自帶的keep-alive組件,保留組件狀態。

4.載入中的動畫效果如何作?

解決辦法:以前是經過CSS3繪製一個圖形,可是後來發現太醜了,就直接用了Iconfont上的svg圖,並添加了動畫效果。

5.如何實現主頁文章列表的懶加載?

解決辦法:判斷滑動的總高度 - 滑動距離頂部的距離 <= 屏幕的可用高度,也就是如下公式:

document.documentElement.offsetHeight - window.scrollY
<= window.screen.height

這裏會出現一個bug,知足條件時,繼續滑動,會加載屢次。在此能夠加入一個狀態,表示此時正在加載(詳細參見源代碼),從而解決此bug。

6.回到頂部的動畫怎麼作?

解決辦法:能夠把如今的window.scrollY分紅n份,而後再設置一個定時器,每隔m秒,向上滾動一份的高度,當window.scrollY >= 0時,再終止定時器。(其中的m, n爲任意數,根據狀況設定)

7.如何控制正在加載頁面的顯示?

解決辦法:由於加載數據是異步的,能夠在加載以前和加載以後,分別更改一個相似於isLoading(名稱本身設定)的狀態,從而控制加載頁面的顯示。

8.如何設置登陸功能?

解決辦法:由於官方只提供了access-token,因此能夠將此值和一些用戶相關的數值,存入document.cookie中,存入的函數我單獨寫了一個cookie的工具函數,代碼以下:

/**
* Created by jerryshen on 2017/7/15.
* 用戶本地cookie的存取以及清空
* 函數的功能分別是:
* 設置單個,獲取全部,獲取單個,刪除全部,刪除單個
*/

export function setCookie (name, value, exdays = 30) {
 var time = new Date()
 time.setTime(time.getTime() + exdays * 24 * 3600 * 1000)
 var expires = 'expires=' + time.toGMTString()
 document.cookie = name + '=' + value + ';' + expires
}

export function getAllCookies () {
 if (document.cookie === '') {
   return {}
 }
 const cookies = document.cookie.split(';')
 const newCookies = {}
 for (let i = 0; i < cookies.length; i++) {
   let cookie = cookies[i].trim()
   const splitCookie = cookie.split('=')
   newCookies[splitCookie[0]] = splitCookie[1]
 }
 return newCookies
}

export function getCookie (name) {
 const cname = name + '='
 const cookies = document.cookie.split(';')
 for (let i = 0; i < cookies.length; i++) {
   let cookie = cookies[i].trim()
   if (cookie.indexOf(cname) === 0) {
     return {
       success: true,
       cookie: {
         name,
         value: cookie.split(cname)[1]
       }
     }
   } else {
     return {
       success: false,
       cookie: {
         name,
         value: undefined
       }
     }
   }
 }
}

export function deleteAllCookie () {
 document.cookie += ';expires=Thu, 01 Jan 1970 00:00:00 GMT'
}

export function deleteCookie (name) {
 document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT`
}

9.如何將API中的時間轉換成 => ..年前,..月前,..天前等等,這種類型的格式呢?

解決辦法:我本身寫了一個格式化的工具函數,代碼以下:

export default function timeFormat (date) {
  // 獲取當前時間和所傳時間的Date對象
  const nowTime = new Date()
  const inDate = new Date(date)
  if (nowTime.getYear() - inDate.getYear() > 0) {
    // 年份差值 > 0,返回年
    return `${nowTime.getFullYear() - inDate.getFullYear()}年前`
  } else if (nowTime.getMonth() - inDate.getMonth() > 0) {
    // 月份差值 > 0,返回月
    return `${nowTime.getMonth() - inDate.getMonth()}個月前`
  } else if (nowTime.getDate() - inDate.getDate() > 0) {
    // 日期差值 > 0,返回日
    return `${nowTime.getDate() - inDate.getDate()}天前`
  } else if (nowTime.getHours() - inDate.getHours() > 0) {
    // 小時差值 > 0,返回時
    return `${nowTime.getHours() - inDate.getHours()}個小時前`
  } else if (nowTime.getMinutes() - inDate.getMinutes() > 0) {
    // 分鐘差值 > 0,返回分鐘
    return `${nowTime.getMinutes() - inDate.getMinutes()}分鐘前`
  } else {
    // 其餘狀況,也就是秒數差值 > 0,返回秒鐘
    return `${nowTime.getSeconds() - inDate.getSeconds()}秒前`
  }
}

10.BUG:當進入其餘路由時,仍然會觸發主頁的scroll事件。

解決辦法:以前生命週期鉤子用的是mounted,所以進入其餘路由時,scroll事件仍然存在。因此如今改用beforeRouteEnterbeforeRouteLeave這兩個路由的生命週期鉤子,分別實現載入路由時的scroll事件掛載、離開路由時的scroll事件卸載。從而防止主頁內容的懶加載一直觸發。

11.發佈新文章或更新跳轉至文章詳情頁面後,再按後退,怎麼實現回到主頁?

解決辦法:如今初步是使用,路由跳轉的時候,先跳到主頁,再跳到文章詳情頁,再按後退時,就會回到主頁。

12.如何實現點擊評論右側的回覆按鈕,添加@信息,並focus輸入框?

解決辦法:經過vuex來實時記錄回覆相關的信息,並經過watch輸入框的value來判斷是否focus。

13.有一個很奇怪的bug:ios下,若是在文章詳情頁返回主頁時,此時的window.scrollY會保持文章詳情頁時的window.scrollY,若是此值知足異步加載更多數據的條件時,會致使異常加載數據。

解決辦法:不得已,只好在beforeRouteEnter鉤子中,綁定滾動事件的函數加一個定時器,使其在100ms後綁定事件,因此此時的window.scrollY就會變成以前的值。

14.如何實現全局的消息提醒?

解決辦法:我是經過一個和路由同級的組件Messages,而且建立了一個狀態管理的模塊messages,在其中經過state: messages存放如今顯示的的通知數據,利用Messages組件和vuexactions控制其顯示。

8、後記

本人新手一枚,還在苦逼的找工做中T_T。如過您在代碼中發現了bug,能夠經過評論和我交流,互相學習!有什麼好的想法,也能夠提出來,一塊兒討論。

相關文章
相關標籤/搜索