
為什么要寫這篇文章呢墓怀?其目的標(biāo)題寫的也很明了,就是為了記錄一下我的學(xué)習(xí)過程抬驴。在以后回顧此項目時炼七,也可以更方便地發(fā)現(xiàn)此項目中的不足和精華。在此布持,感謝VNode社區(qū)提供的API豌拙。
源碼在此:Vue-CNode
預(yù)覽地址:使勁點我
你也可以掃描下面的二維碼預(yù)覽線上項目:
一、需求分析
要做一個項目之前题暖,我覺得首先要把功能做一個總結(jié)按傅,根據(jù)需求來寫項目,從而做到有的放矢胧卤。
所以我根據(jù)API寫了項目的需求唯绍,可見下圖:

二、技術(shù)棧
本項目使用的技術(shù)棧就是標(biāo)準(zhǔn)的Vue全家桶
枝誊,即:
Vue2.0: 構(gòu)建項目况芒,屬于底層框架。
Vue-Router: 通過hash值的變化侧啼,從而改變頁面結(jié)構(gòu)的路由牛柒。
Vuex: Vue官方提供的狀態(tài)管理模式堪簿。
Axios, Vue-Axios: http請求模塊。
ES6: 應(yīng)用于生產(chǎn)環(huán)境皮壁,普及度較高的新Javascript語法椭更。
Sass: CSS預(yù)編譯器。
Webpack: 用于打包項目蛾魄。
三虑瀑、項目初始化
利用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/就可以訪問初始化后的頁面了滴须。
四舌狗、項目編寫
注意:詳細(xì)內(nèi)容可以去源碼自行查看。
完成初始化之后呢扔水,我們就可以開始編寫項目了痛侍。
代碼分為四塊,分別是:components(組件)魔市、vue-router(路由)主届、vuex(狀態(tài)管理模式)和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)還有工具函數(shù)(utils, 包括時間格式化還有cookie存取功能)印蔗。
2.Components組件
現(xiàn)在暫時一共有14個組件,包括:
AboutMe
Article
ArticleCard
BackBar
BottomBar
Content
Loading
Login
MessageCard
MyCollect
navBar
Notification
Publish
UserDetail
具體內(nèi)容可以參見后面的項目目錄燎潮。
3. Vue-Router 路由配置
通過路由喻鳄,分為一下七個頁面:
① 主頁
② 文章詳情頁
③ 用戶詳情頁
④ 用戶登錄頁
⑤ 發(fā)布文章頁
⑥ 用戶收藏頁
⑦ 我的通知頁
5. Vuex:狀態(tài)管理模式
狀態(tài)管理分為六個模塊:content
(主頁)扼倘、article
(文章頁)确封、navbar
(導(dǎo)航欄)、user
(用戶詳情狀態(tài))再菊、login
(用戶登錄狀態(tài))和notification
(通知)爪喘。
五、項目目錄
.
├── build // webpack設(shè)置
│ ├── 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 // 項目開發(fā)和打包設(shè)置
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── docs // 靜態(tài)資源地址
│ ├── 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 // 組件總?cè)肟?│ ├── common // 通用文件
│ │ ├── fonts // 字體
│ │ │ ├── iconfont.eot
│ │ │ ├── iconfont.svg
│ │ │ ├── iconfont.ttf
│ │ │ └── iconfont.woff
│ │ ├── style // 樣式
│ │ │ ├── animation.scss // 動畫
│ │ │ ├── base.scss // 基本樣式
│ │ │ └── icon.scss // iconfont的字體圖標(biāo)樣式
│ │ └── utils // 工具函數(shù)
│ │ ├── cookie.js // cookie存取和刪除
│ │ └── timeFormat.js // 格式化時間函數(shù)
│ ├── components // 所有組件
│ │ ├── AboutMe // 關(guān)于
│ │ │ └── AboutMe.vue
│ │ ├── Article // 文章詳情頁
│ │ │ └── Article.vue
│ │ ├── ArticleCard // 文章列表的單個文章卡片
│ │ │ └── ArticleCard.vue
│ │ ├── BackBar // 頂部的返回欄(返回主頁和后退)
│ │ │ └── BackBar.vue
│ │ ├── BottomBar // 底部的回復(fù)欄(還包含收藏和編輯文件)
│ │ │ └── BottomBar.vue
│ │ ├── Content // 主頁
│ │ │ └── Content.vue
│ │ ├── Loading // 正在加載組件
│ │ │ ├── Loading.vue
│ │ │ └── loading.svg
│ │ ├── Login // 登錄
│ │ │ └── Login.vue
│ │ ├── MessageCard // 單個通知的詳情卡片
│ │ │ └── MessageCard.vue
│ │ ├── MyCollect // 我的收藏頁
│ │ │ └── MyCollect.vue
│ │ ├── Notification // 通知頁
│ │ │ └── Notification.vue
│ │ ├── Publish // 發(fā)布文章和發(fā)布更新頁
│ │ │ └── Publish.vue
│ │ ├── UserDetail // 用戶詳情頁
│ │ │ └── UserDetail.vue
│ │ └── navBar // 主頁的頂部導(dǎo)航欄
│ │ ├── cnodejs_light.svg
│ │ └── navBar.vue
│ ├── main.js // 項目的總?cè)肟?│ ├── pic // 和代碼無關(guān)纠拔,README.md中的圖片
│ │ ├── CNode?\212\237?\203??\234\200?\202?\210\206?\236\220.png
│ │ └── QR-Code.png
│ ├── router // 路由設(shè)置
│ │ └── index.js
│ └── store // 狀態(tài)管理
│ ├── modules
│ │ ├── article // 文章詳情頁
│ │ │ ├── article-mutation-types.js
│ │ │ └── article.js
│ │ ├── content // 主頁
│ │ │ ├── content-mutation-types.js
│ │ │ └── content.js
│ │ ├── login // 登錄頁
│ │ │ ├── login-mutation-types.js
│ │ │ └── login.js
│ │ ├── navbar // 主頁導(dǎo)航欄
│ │ │ ├── navbar-mutation-types.js
│ │ │ └── navbar.js
│ │ ├── notification // 通知頁
│ │ │ ├── notification-mutation-types.js
│ │ │ └── notification.js
│ │ └── user // 用戶詳情頁
│ │ ├── user-mutation-types.js
│ │ └── user.js
│ └── store.js // 狀態(tài)管理總?cè)肟?├── README.md
├── index.html
└── package.json
六秉剑、過程中遇到的問題
本項目算是本人第一個完整的手機和pc都兼容,有關(guān)于文章展示的項目稠诲。整個項目做下來侦鹏,遇到的Bug很多诡曙,自然收獲也是很多÷运總結(jié)下來如下:
1.很長的單詞會超出邊界价卤,導(dǎo)致可視區(qū)域變寬。
解決辦法:通過word-wrap: break-word;
實現(xiàn)打斷效果渊涝。
2.第二次進(jìn)入文章時慎璧,會殘留(暫未解決)。
解決辦法:通過路由的鉤子函數(shù)beforeRouteEnter跨释,來獲取數(shù)據(jù)胸私,未成功獲取數(shù)據(jù)時,顯示Loading頁面鳖谈,加載完成后岁疼,顯示文章詳情頁,從而解決這個問題缆娃。
3.回到首頁時五续,不能保留原來的狀態(tài)(暫未解決)。
解決辦法:
①此方法為容易固定高度的解決辦法龄恋。(具體方法:用vuex和vue-router的鉤子函數(shù)來解決這個問題疙驾,即通過scroll事件動態(tài)保存此時的scrollTop直,當(dāng)路由的beforeRouteEnter出發(fā)時郭毕,恢復(fù)其scrollTop的值它碎。)
② 如果沒有固定高度,直接通過Vue自帶的keep-alive組件显押,保留組件狀態(tài)扳肛。
4.載入中的動畫效果如何做?
解決辦法:之前是通過CSS3繪制一個圖形乘碑,但是后來發(fā)現(xiàn)太丑了挖息,就直接用了Iconfont上的svg圖,并添加了動畫效果兽肤。
5.如何實現(xiàn)主頁文章列表的懶加載套腹?
解決辦法:判斷滑動的總高度 - 滑動距離頂部的距離 <= 屏幕的可用高度,也就是以下公式:
document.documentElement.offsetHeight - window.scrollY
<= window.screen.height
這里會出現(xiàn)一個bug资铡,滿足條件時电禀,繼續(xù)滑動,會加載多次笤休。在此可以加入一個狀態(tài)尖飞,表示此時正在加載(詳細(xì)參見源代碼),從而解決此bug。
6.回到頂部的動畫怎么做政基?
解決辦法:可以把現(xiàn)在的window.scrollY
分成n
份贞铣,然后再設(shè)置一個定時器,每隔m
秒沮明,向上滾動一份的高度咕娄,當(dāng)window.scrollY >= 0
時,再終止定時器珊擂。(其中的m, n
為任意數(shù)圣勒,根據(jù)情況設(shè)定)
7.如何控制正在加載頁面的顯示?
解決辦法:因為加載數(shù)據(jù)是異步的摧扇,可以在加載之前和加載之后圣贸,分別更改一個類似于isLoading
(名稱自己設(shè)定)的狀態(tài),從而控制加載頁面的顯示扛稽。
8.如何設(shè)置登錄功能吁峻?
解決辦法:因為官方只提供了access-token
,所以可以將此值和一些用戶相關(guān)的數(shù)值在张,存入document.cookie
中用含,存入的函數(shù)我單獨寫了一個cookie
的工具函數(shù),代碼如下:
/**
* Created by jerryshen on 2017/7/15.
* 用戶本地cookie的存取以及清空
* 函數(shù)的功能分別是:
* 設(shè)置單個帮匾,獲取所有啄骇,獲取單個,刪除所有瘟斜,刪除單個
*/
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中的時間轉(zhuǎn)換成 => ..年前锐涯,..月前委粉,..天前等等,這種類型的格式呢舔株?
解決辦法:我自己寫了一個格式化的工具函數(shù)广凸,代碼如下:
export default function timeFormat (date) {
// 獲取當(dāng)前時間和所傳時間的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 {
// 其他情況匆笤,也就是秒數(shù)差值 > 0,返回秒鐘
return `${nowTime.getSeconds() - inDate.getSeconds()}秒前`
}
}
10.BUG:當(dāng)進(jìn)入其他路由時勇凭,仍然會觸發(fā)主頁的scroll事件疚膊。
解決辦法:之前生命周期鉤子用的是mounted
义辕,因此進(jìn)入其他路由時虾标,scroll事件仍然存在。所以現(xiàn)在改用beforeRouteEnter
和beforeRouteLeave
這兩個路由的生命周期鉤子,分別實現(xiàn)載入路由時的scroll事件掛載璧函、離開路由時的scroll事件卸載傀蚌。從而防止主頁內(nèi)容的懶加載一直觸發(fā)。
11.發(fā)布新文章或更新跳轉(zhuǎn)至文章詳情頁面后蘸吓,再按后退善炫,怎么實現(xiàn)回到主頁?
解決辦法:現(xiàn)在初步是使用库继,路由跳轉(zhuǎn)的時候箩艺,先跳到主頁,再跳到文章詳情頁宪萄,再按后退時艺谆,就會回到主頁。
12.如何實現(xiàn)點擊評論右側(cè)的回復(fù)按鈕拜英,添加@信息静汤,并focus輸入框?
解決辦法:通過vuex來實時記錄回復(fù)相關(guān)的信息居凶,并通過watch輸入框的value來判斷是否focus虫给。
13.有一個很奇怪的bug:ios下,如果在文章詳情頁返回主頁時侠碧,此時的window.scrollY
會保持文章詳情頁時的window.scrollY
抹估,如果此值滿足異步加載更多數(shù)據(jù)的條件時,會導(dǎo)致異常加載數(shù)據(jù)弄兜。
解決辦法:不得已棋蚌,只好在beforeRouteEnter
鉤子中,綁定滾動事件的函數(shù)加一個定時器挨队,使其在100ms后綁定事件谷暮,所以此時的window.scrollY
就會變成之前的值。
七盛垦、后記
本人新手一枚湿弦,還在苦逼的找工作中T_T。如過您在代碼中發(fā)現(xiàn)了bug腾夯,可以通過評論和我交流颊埃,互相學(xué)習(xí)!有什么好的想法蝶俱,也可以提出來班利,一起討論。