轉載整理自http://blog.csdn.net/fungleo/article/details/77575077
vue構建單頁應用原理
SPA
不是指水療。是 single page web application 的縮寫。中文翻譯為 單頁應用程序 或 單頁Web應用。更多解釋清參看 百度百科:SPA
所有的前端人員都應該明白我們的頁面的 url 構成:
1 http://www.fengcms.com/index.html?name=fungleo&old=32#mylove/is/world/peace
如上的 url 由以下部分組成:
http://規定了頁面采用的協議。www.fengcms.com為頁面所屬的域名。index.html為讀取的文件名稱。?name=fungleo&old=32給頁面通過GET方式傳送的參數#mylove/is/world/peace為頁面的錨點區域
前面四個發生改變的時候,會觸發瀏覽器的跳轉亦或是刷新行為,而更改 url 中的錨點,並不會出現這種行為,因此,幾乎所有的 spa 應用都是利用錨點的這個特性來實現路由的轉換。以我們的 vue 項目為例,它的本地 url 結構一般為以下格式:
1 http://localhost:8080/#/setting/task
可以明顯的看到我們所謂的路由地址是在 # 號后面的,也就是利用了錨點的特性。
RESTful 風格接口
實際情況是,我們在前后端在約定接口的時候,可以約定各種風格的接口,但是,RESTful 接口是目前來說比較流行的,並且在運用中比較方便和常見的接口。
雖然它有一些缺陷,目前 github 也在主推 GraphQL 這種新的接口風格,但目前國內來說還是 RESTful 接口風格比較普遍。
並且,在掌握了 RESTful 接口風格之后,會深入的理解這種接口的優缺點,到時候,你自然會去想解決方案,並且在項目中實行新的更好的理念,所以,我這系列的博文,依然采用 http://cnodejs.org/ 網站提供的 RESTful 接口來實戰。
了解程序開發的都應該知道,我們所做的大多數操作都是對數據庫的四格操作 “增刪改查” 對應到我們的接口操作分別是:
post插入新數據delete刪除數據put修改數據get查詢數據
注意,這里是我們約定,並非這些動作只能干這件事情。從表層來說,除
get外的其他方法,沒有什么區別,都是一樣的。從深層來說包括get在內的所有方法都是一模一樣的,沒有任何區別。但是,我們約定,每種動作對應不同的操作,這樣方便我們統一規范我們的所有操作。
假設,我們的接口是 /api/v1/love 這樣的接口,采用 RESTful 接口風格對應操作是如下的:
get 操作 /api/v1/love
獲取 /api/v1/love 的分頁列表數據,得到的主體,將是一個數組,我們可以用數據來遍歷循環列表
post 操作 /api/v1/love
我們會往 /api/v1/love 插入一條新的數據,我們插入的數據,將是JOSN利用對象傳輸的。
get 操作 /api/v1/love/1
我們獲取到一個 ID 為 1 的的數據,數據一般為一個對象,里面包含了 1 的各項字段信息。
put 操作 /api/v1/love/1
我們向接口提交了一個新的信息,來修改 ID 為 1 的這條信息
delete 操作 /api/v1/love/1
我們向接口請求,刪除 ID 為 1 的這一條數據
由上述例子可知,我們實現了5種操作,但只用了兩個接口地址, /api/v1/love 和 /api/v1/love/1 。所以,采用這種接口風格,可以大幅的簡化我們的接口設計。
一、安裝 nodejs 環境
你可以在 https://nodejs.org/ nodejs 官方網站下載安裝包,然后進行安裝。如果是 linux 或者 mac 命令行的用戶,也可以使用命令行安裝。
在安裝好了 nodejs 之后,我們在終端中輸入以下兩個命令:
node -v
npm -v
能夠得到版本號信息,則說明你的 nodejs 環境已經安裝完成了。
二、安裝 vue-cli VUE的腳手架工具
我們首先安裝全局vue-cil腳手架,用於幫助搭建所需的模板框架,命令如下:
npm install -g vue-cli
# 回車,等待安裝...
vue
# 回車,若出現vue信息說明表示成功
這個命令只需要運行一次就可以了。安裝上之后,以后就不用安裝了。
下面,我們來用vue-cil構建一個項目。
首先,我們在終端中把當前目錄定位到你准備存放項目的地方。如我是准備放在~/Sites/MyWork/這個目錄下面,那么我的命令如下:
cd ~/Sites/MyWork
(也可以直接在目錄內打開終端)進入到目錄之后,我們按照下面的代碼逐條輸入,新建一個自己的vue項目,vuedemo是自己起的項目名稱
vue init webpack vuedemo
# 這里需要進行一些配置,默認回車即可
? Project name vuedemo
? Project description A Vue.js project
? Author
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (reco
mmended) npm
vue-cli · Generated "vuedemo".
# Project initialization finished!
# ========================
To get started:
cd vuedemo
npm install
npm run dev
Documentation can be found at https://vuejs-templates.github.io/webpack如上圖所示,就說明我們的項目構建成功了。並且,給出了下面要執行的命令,我們進入項目,安裝並運行:
#在cmd輸入項目名
cd vuedemo
#回車,進入到具體項目文件夾
npm install
#回車,等待一小會兒
#回到項目文件夾,會發現項目結構里,多了一個node_modules文件夾(該文件里的內容就是之前安裝的依賴)
最后,執行下面一句,把項目跑起來
npm run dev
DONE Compiled successfully in 4388ms
> Listening at http://localhost:8080
成功執行以上命令后訪問 http://localhost:8080/,輸出結果
進入項目文件夾后
vue init webpack vuedemo新建一個項目進入項目
cd vuedemo創建項目
npm install運行項目
npm run dev
當然,這個名字你是可以隨便起的,根據你的實際項目來定。
通過上面的這些命令,我們就實現了新建一個vue+webpack的項目。在運行了npm run dev之后,會自動打開一個瀏覽器窗口,就可以看到實際的效果了。
JavaScript Standard
在創建項目時我們有開啟了一個standard語法效驗,JavaScript 代碼規范,自帶 linter & 代碼自動修正。
官網:https://standardjs.com/readme-zhcn.html
主要的語法規則有:
- 使用兩個空格 – 進行縮進
- 字符串使用單引號 – 需要轉義的地方除外
- 不再有冗余的變量 – 這是導致 大量 bug 的源頭!
- 無分號 – 這沒什么不好。不騙你!
- 行首不要以
(,[, or`開頭- 這是省略分號時唯一會造成問題的地方 – 工具里已加了自動檢測!
- 詳情
- 關鍵字后加空格
if (condition) { ... } - 函數名后加空格
function name (arg) { ... } - 堅持使用全等
===摒棄==一但在需要檢查null || undefined時可以使用obj == null。 - 一定要處理 Node.js 中錯誤回調傳遞進來的
err參數。 - 文件末尾留一空行
- 使用瀏覽器全局變量時加上
window前綴 –document和navigator除外- 避免無意中使用到了這些命名看上去很普通的全局變量,
open,length,event還有name。
- 避免無意中使用到了這些命名看上去很普通的全局變量,
- 查看更多 – 為何不試試
standard規范呢!
三、
1 ├── README.md // 項目說明文檔 2 ├── node_modules // 項目依賴包文件夾 3 ├── build // 編譯配置文件,一般不用管 4 │ ├── build.js 5 │ ├── check-versions.js 6 │ ├── dev-client.js 7 │ ├── dev-server.js 8 │ ├── utils.js 9 │ ├── vue-loader.conf.js 10 │ ├── webpack.base.conf.js 11 │ ├── webpack.dev.conf.js 12 │ └── webpack.prod.conf.js 13 ├── config // 項目基本設置文件夾 14 │ ├── dev.env.js // 開發配置文件 15 │ ├── index.js // 配置主文件 16 │ └── prod.env.js // 編譯配置文件 17 ├── index.html // 項目入口文件 18 ├── package-lock.json // npm5 新增文件,優化性能 19 ├── package.json // 項目依賴包配置文件 20 ├── src // 我們的項目的源碼編寫文件 21 │ ├── App.vue // APP入口文件 22 │ ├── assets // 初始項目資源目錄,回頭刪掉 23 │ │ └── logo.png 24 │ ├── components // 組件目錄 25 │ │ └── Hello.vue // 測試組件,回頭刪除 26 │ ├── main.js // 主配置文件 27 │ └── router // 路由配置文件夾 28 │ └── index.js // 路由配置文件 29 └── static // 資源放置目錄
如上圖所示,我們的目錄結構就是這樣的了。
我們絕大多數的操作,就是在 src 這個目錄下面。默認的 src 結構比較簡單,我們需要重新整理。
另外 static 資源目錄,我們也需要根據放置不同的資源,在這邊構建不同的子文件夾。
我們來配置 src 目錄
先不要管這些文件的內容,我們先建立這些空的文件在這邊。然后我們后面去完善它。
我們的這個項目是要做兩個頁面,一個是 cnodejs 的列表頁面,一個是詳情頁面。
所以,我把項目文件夾整理成如下的結構
1 ├── App.vue // APP入口文件 2 ├── api // 接口調用工具文件夾 3 │ └── index.js // 接口調用工具 4 ├── components // 組件文件夾,目前為空 5 ├── config // 項目配置文件夾 6 │ └── index.js // 項目配置文件 7 ├── frame // 子路由文件夾 8 │ └── frame.vue // 默認子路由文件 9 ├── main.js // 項目配置文件 10 ├── page // 我們的頁面組件文件夾 11 │ ├── content.vue // 准備些 cnodejs 的內容頁面 12 │ └── index.vue // 准備些 cnodejs 的列表頁面 13 ├── router // 路由配置文件夾 14 │ └── index.js // 路由配置文件 15 ├── style // scss 樣式存放目錄 16 │ ├── base // 基礎樣式存放目錄 17 │ │ ├── _base.scss // 基礎樣式文件 18 │ │ ├── _color.scss // 項目顏色配置變量文件 19 │ │ ├── _mixin.scss // scss 混入文件 20 │ │ └── _reset.scss // 瀏覽器初始化文件 21 │ ├── scss // 頁面樣式文件夾 22 │ │ ├── _content.scss // 內容頁面樣式文件 23 │ │ └── _index.scss // 列表樣式文件 24 │ └── style.scss // 主樣式文件 25 └── utils // 常用工具文件夾 26 └── index.js // 常用工具文件
因為我們刪除了一些默認的文件,所以這個時候項目一定是報錯的,先不管他,我們根據我們的需求,新建如上的項目結構。這些都是在 src 目錄里面的結構。
四、調整app.vue和設置路由
1.調整 App.vue 文件
我們先把默認項目里面沒用的東西先刪除掉,把代碼調整為下面的樣子。
1 <template> 2 <div id="app"> 3 <router-view></router-view> 4 </div> 5 </template> 6 7 <script> 8 export default { 9 name: 'app' 10 } 11 </script> 12 13 <style lang="css"> 14 @import "./style/index.css"; 15 </style>
入口,只有一個空的路由視窗,我們的項目的所有內容,都基於這個視窗來展現。因為scss文件容易出現語法錯誤,這里先用css文件替代。后期熟悉之后再更改
我們的樣式,都將從 src/style/index.css這個文件中引用,如果沒有請按路徑新建!
2.調整 index.vue 和 content.vue 文件
昨天,我們在 page 文件夾下面建立了兩個空文本文件 index.vue 和 content.vue 文件,是我們准備用來放列表和內容的。
這里,我們先去填寫一點基礎內容在里面。
index.vue
1 <template> 2 <div>index page</div> 3 </template>
content.vue
1 <template> 2 <div>content page</div> 3 </template>
好,寫上如上的代碼就行,我們后面再豐富這些內容。
3.調整 router 路由文件
現在,這個項目還跑不起來呢,如果你運行 npm run dev 還是會出錯的。因為我們還沒有配置路由。
1 import Vue from 'vue' 2 import Router from 'vue-router' 3 import Hello from '@/components/Hello' 4 5 Vue.use(Router) 6 7 export default new Router({ 8 routes: [ 9 { 10 path: '/', 11 name: 'Hello', 12 component: Hello 13 } 14 ] 15 })
以上,是默認的路由文件,引用了一個 Hello 的組件,這個組件被我們昨天的博文中整理文件結構的時候刪除掉了。所以,這里就報錯啦。
我們根據我們的需要,來調整這個路由文件,如下:
1 import Vue from 'vue' 2 import Router from 'vue-router' 3 import Index from '@/page/index' 4 import Content from '@/page/content' 5 6 Vue.use(Router) 7 8 export default new Router({ 9 routes: [ 10 { 11 path: '/', 12 component: Index 13 }, { 14 path: '/content/:id', 15 component: Content 16 } 17 ] 18 })
默認,我們的首頁,就是我們的 index.vue 組件,這里,你可能要問 :id 是什么意思?
因為我們的內容頁面是要展示N條內容的,我們如何來區分這些內容呢,就是根據ID來進行區分。所以,這里使用了動態路由匹配。
更多內容,可以參考官方文檔《動態路由匹配》
好,我們現在,項目應該是沒有任何錯誤,可以跑起來了。忘記跑起來的命令了?如下:
npm run dev
五。配置axios api接口調用文件
我們通過配置基本的信息,已經讓我們的項目能夠正常的跑起來了。但是,這里還沒有涉及到 AJAX 請求接口的內容。
vue 本身是不支持 ajax 接口請求的,所以我們需要安裝一個接口請求的 npm 包,來使我們的項目擁有這個功能。
這其實是一個重要的
unix思想,就是一個工具只做好一件事情,你需要額外的功能的時候,則需要安裝對應的軟件來執行。如果你以前是一個jquery重度用戶,那么可能理解這個思想一定要深入的理解。
支持 ajax 的工具有很多。一開始,我使用的是 superagent 這個工具。但是我發現近一年來,絕大多數的教程都是使用的 axios 這個接口請求工具。其實,這本來是沒有什么差別的。但是為了防止你們在看了我的博文和其他的文章之后,產生理念上的沖突。因此,我也就改用 axios 這個工具了
本身, axios 這個工具已經做了很好的優化和封裝。但是,在使用的時候,還是略顯繁瑣,因此,我重新封裝了一下。當然,更重要的是,封裝 axios 這個工具是為了和我以前寫的代碼的兼容。不過我封裝得很好,也推薦大家使用。
1.封裝 axios 工具,編輯 src/api/index.js 文件
首先,我們要使用 axios 工具,就必須先安裝 axios 工具。執行下面的命令進行安裝
npm install axios -D
這樣,我們就安裝好了 axios 工具了。
還記得我們在第三篇博文中整理的系統結構嗎?我們新建了一個 src/api/index.js 這個空文本文件,就那么放在那里了。這里,我們給它填寫上內容。
// 配置API接口地址 var root = 'https://cnodejs.org/api/v1' // 引用axios var axios = require('axios') // 自定義判斷元素類型JS function toType (obj) { return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase() } // 參數過濾函數 function filterNull (o) { for (var key in o) { if (o[key] === null) { delete o[key] } if (toType(o[key]) === 'string') { o[key] = o[key].trim() } else if (toType(o[key]) === 'object') { o[key] = filterNull(o[key]) } else if (toType(o[key]) === 'array') { o[key] = filterNull(o[key]) } } return o } /* 接口處理函數 這個函數每個項目都是不一樣的,我現在調整的是適用於 https://cnodejs.org/api/v1 的接口,如果是其他接口 需要根據接口的參數進行調整。參考說明文檔地址: https://cnodejs.org/topic/5378720ed6e2d16149fa16bd 主要是,不同的接口的成功標識和失敗提示是不一致的。 另外,不同的項目的處理方法也是不一致的,這里出錯就是簡單的alert */ function apiAxios (method, url, params, success, failure) { if (params) { params = filterNull(params) } axios({ method: method, url: url, data: method === 'POST' || method === 'PUT' ? params : null, params: method === 'GET' || method === 'DELETE' ? params : null, baseURL: root, withCredentials: false }) .then(function (res) { if (res.data.success === true) { if (success) { success(res.data) } } else { if (failure) { failure(res.data) } else { window.alert('error: ' + JSON.stringify(res.data)) } } }) .catch(function (err) { let res = err.response if (err) { window.alert('api error, HTTP CODE: ' + res.status) } }) } // 返回在vue模板中的調用接口 export default { get: function (url, params, success, failure) { return apiAxios('GET', url, params, success, failure) }, post: function (url, params, success, failure) { return apiAxios('POST', url, params, success, failure) }, put: function (url, params, success, failure) { return apiAxios('PUT', url, params, success, failure) }, delete: function (url, params, success, failure) { return apiAxios('DELETE', url, params, success, failure) } }
有關 axios 的更多內容,請參考官方 github: https://github.com/mzabriskie/axios ,中文資料自行百度。
但就是這樣,我們還不能再 vue 模板文件中使用這個工具,還需要調整一下 main.js 文件。
2.調整 main.js 綁定 api/index.js 文件
這次呢,我們沒有上來就調整 main.js 文件,因為原始文件就配置得比較好,我就沒有刻意的想要調整它。
原始文件如下:
1 import Vue from 'vue' 2 import App from './App' 3 import router from './router' 4 5 Vue.config.productionTip = false 6 7 /* eslint-disable no-new */ 8 new Vue({ 9 el: '#app', 10 router, 11 template: '<App/>', 12 components: { App } 13 })
我們插入以下代碼:
1 // 引用API文件 2 import api from './api/index.js' 3 // 將API方法綁定到全局 4 Vue.prototype.$api = api
也就是講代碼調整為:
import Vue from 'vue' import App from './App' import router from './router' // 引用API文件 import api from './api/index.js' // 將API方法綁定到全局 Vue.prototype.$api = api Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, template: '<App/>', components: { App } })
好了,這樣,我們就可以在項目中使用我們封裝的 api 接口調用文件了。
3.測試一下看看能不能調通
我們來修改一下 src/page/index.vue 文件,將代碼調整為以下代碼:
1 <template> 2 <div>index page</div> 3 </template> 4 <script> 5 export default { 6 created () { 7 this.$api.get('topics', null, r => { 8 console.log(r) 9 }) 10 } 11 } 12 </script>
好,這里是調用 cnodejs.org 的 topics 列表接口,並且將結果打印出來。
我們在瀏覽器中打開控制台,看看 console 下面有沒有輸出入下圖一樣的內容。如果有的話,就說明我們的接口配置已經成功了。

好,如果你操作正確,代碼沒有格式錯誤的話,那么現在應該得到的結果是和我一樣的。如果出錯或者怎么樣,請仔細的檢查代碼,看看有沒有什么問題。
六、將接口用webpack代理到本地
我們已經非常順利的調用到了 cnodejs.org 的接口了。但是,我們可以注意到我們的 src/api/index.js 的第一句,就是:
1 // 配置API接口地址 2 var root = 'https://cnodejs.org/api/v1'
這里,我們將接口地址寫死了。
當然,這並不是最重要的事情,而是 cnodejs.org 幫我們把接口處理得很好,解決了跨域的問題。而我們的實際項目中,很多接口都是不允許我們這樣跨域請求的。
而我們的開發環境下,不可能跑到服務器上直接開發,或者在本地直接搞一個服務器環境,這樣就違背了我們前后端分離開發的初衷了。
如何解決這個問題呢?其實非常好辦,要知道 跨域不是接口的限制 而是瀏覽器為了保障數據安全做的限制。因此,一種方法可以解決,那就是打開瀏覽器的限制,讓我們可以順利的進行開發。但是無奈的是,最新的 chrome 瀏覽器好像已經關閉了這個選項,那么我們只能采用另外一種方法了——將接口代理到本地。
1.配置 webpack 將接口代理到本地
好在,vue-cli 腳手架工具,已經充分的考慮了這個問題,我們只要進行簡單的設置,就可以實現我們的目的。
我們打開 config/index.js 文件,找到以下代碼:
1 dev: { 2 env: require('./dev.env'), 3 port: 8080, 4 autoOpenBrowser: true, 5 assetsSubDirectory: 'static', 6 assetsPublicPath: '/', 7 proxyTable: {}, 8 cssSourceMap: false 9 }
其中,proxyTable: {}, 這一行,就是給我們配置代理的。
根據 cnodejs.org 的接口,我們把這里調整為:
1 proxyTable: { 2 '/api/v1/**': { 3 target: 'https://cnodejs.org', // 你接口的域名 4 secure: false, 5 changeOrigin: false, 6 } 7 }
OK,我們這樣配置好后,就可以將接口代理到本地了。
更多接口參數配置,請參考 https://github.com/chimurai/http-proxy-middleware#options
webpack 接口配置文檔 https://webpack.js.org/configuration/dev-server/#devserver-proxy
2.重新配置 src/api/index.js 文件
好,上面已經代理成功了,但是我們的 src/api/index.js 文件,還是直接調用的人家的地址呢,我們要調整為我們的地址,調整如下:
1 // 配置API接口地址 2 var root = '/api/v1'
之前我有一篇博文,說過如何配置開發接口地址和生產接口地址,當時是利用了 webpack 的不同的配置文件來進行配置的。如果我們采用這種代理模式呢,那么就沒有必要那么做了。因為我們的系統放到生產環境的時候,一般是沒有這個跨域問題的。這個問題一般僅僅是存在於我們的開發環境下面。
值得注意的事情是,配置完成后,是不會立即生效的,我們需要重啟我們的項目。
我們按 ctrl + c 停止掉之前的服務,然后重新輸入命令 npm run dev 重啟項目,就可以了。
如上圖所示,我們可以清晰的看到,我們跑的服務,先開啟了一個代理。
重新跑起來之后,我們看下我們的項目在瀏覽器中的表現:

我們打開瀏覽器控制台,切換到 network 選項卡中,選中我們調用的接口 topics 接口,我們可以清晰的看到,我們讀取的接口地址是我們的本地代理過來的地址。
狀態碼為 304 代表這個數據沒有發生變化,直接讀取本地緩存了。關於 http 狀態碼 的部分,請參考 百度百科 http 狀態碼,這里不做過多的說明。
我們再看一下數據是不是正常的過來了。切換到 Previdw 選項卡查看:

沒有問題,數據過來了。
好,到這里,我們已經順利的將接口代理到本地,並且數據讀取出來了。我們開始准備下面的工作吧!
7.初識*.vue文件,即VUE組件
*.vue 文件,是一個自定義的文件類型,用類 HTML 語法描述一個 Vue 組件。每個 .vue文件包含三種類型的頂級語言塊 <template>, <script> 和 <style>。這三個部分分別代表了 html,js,css。
其中 <template> 和 <style> 是支持用預編譯語言來寫的。比如,在我們的項目中,我們可以用 scss 預編譯,我們可以這樣寫的:
1 <style lang="scss">
html 也有自己的預編譯語言, vue 也是支持的,不過一般來說,我們前端人員還是比較中意 html 原生語言,所以,我就不過多闡述了。
另外,我在
App.vue文件中,已經用一句@import "./style/style";將我們的樣式給寫到指定的地方去了。所以,在后面所有的我的文章中,是不會出現這個部分的內容的。所有樣式,都會在src/style/文件夾中對應的位置去寫。
我這樣做的好處是,不需要重復的引入各種scss基礎文件,並且做到了項目的樣式代碼的可管控
1.一個常見的 *.vue 文件代碼解析
首先,我們來簡單看一下:
1 <template> 2 <div> 3 <Header></Header> 4 <div class="article_list"> 5 <ul> 6 <li></li> 7 </ul> 8 </div> 9 <Footer></Footer> 10 </div> 11 </template> 12 <script> 13 import Header from '../components/header.vue' 14 import Footer from '../components/footer.vue' 15 export default { 16 components: { Header, Footer }, 17 data () { 18 return { 19 list: [] 20 } 21 }, 22 created () { 23 this.getData() 24 }, 25 methods: { 26 getData () { 27 this.$api.get('topics', null, r => { 28 console.log(r) 29 }) 30 } 31 } 32 } 33 </script> 34 <style> 35 .article_list {margin: auto;} 36 </style>
好,以上就是一個簡單的 *.vue 文件的基本結構。我們一部分一部分的來解釋。
以下,我不再稱呼它為 *.vue 文件了。改成為 vue 組件。
2.template 部分
首先,一個 vue 組件,他的 template 則代表它的 html 結構,相信大家可以理解了。但是需要注意的是,我們不是說把代碼包裹在 <template></template> 中就可以了,而是必須在里面放置一個 html 標簽來包裹所有的代碼。 本例子中,我們采用了 <div></div> 標簽。vue2.x 開始,就必須這樣去寫。這是規定
大家看到 <Header></Header> 這個代碼的時候肯定很奇怪,這是個什么玩意兒。其實,這是一個自定義組件。我們在其他地方寫好了一個組件,然后就可以用這種方式引入進來。
同樣 <Footer></Footer> 也是一個組件。不再累述。
其他的內容,我們就想這么寫就怎么寫了。和寫我們的 html 代碼沒有什么太大的區別。
3.script 部分
首先,我們需要兩個自定義組件,我們先引用進來。如下格式,比較好理解吧。
1 import Header from '../components/header.vue' 2 import Footer from '../components/footer.vue'
其次,除了引用的文件,我們將所有的代碼包裹於如下的代碼中間:
1 export default { 2 // 這里寫你的代碼,外面要包起來。 3 }
4.style 部分
這里比較簡單,就是針對我們的 template 里內容出現的 html 元素寫一些樣式。如上,我的代碼:
1 <style> 2 .article_list {margin: auto;} 3 </style>
就是給我們的 .article_list 元素隨便加了一個樣式。
參考資料:《vue 模板語法》
八、渲染列表
1.制作 header.vue 和 footer.vue 組件文件。
不是本篇文章的重點,但是還是有比較講一下。在第三篇博文中,我們規划了我們的項目文件結構,當時保留了一個 components 的空文件夾。這里,就是准備放我們的自定義組件的。
首先,我們去創建兩個空文本文件,分別是 header.vue 文件和 footer.vue 文件。
然后,往里面輸入下面的內容:
header.vue
1 <template> 2 <header class="header"> 3 <h1 class="logo">Vue Demo by DEMO</h1> 4 </header> 5 </template>
footer.vue
1 <template> 2 <footer class="copy"> 3 Copy © DEMO
4 </footer> 5 </template>
非常簡單的兩個文件,表示我們的組件已經弄好了
2.編寫 src/page/index.vue 文件
1 <template> 2 <div> 3 <Header></Header> 4 <div class="article_list"> 5 <ul> 6 <li v-for="i in list" :key="i.id"> 7 <time v-text="i.create_at"></time> 8 <router-link :to="'/content/' + i.id"> 9 {{ i.title }} 10 </router-link> 11 </li> 12 </ul> 13 </div> 14 <Footer></Footer> 15 </div> 16 </template> 17 <script> 18 import Header from '../components/header.vue' 19 import Footer from '../components/footer.vue' 20 export default { 21 components: { Header, Footer }, 22 data () { 23 return { 24 list: [] 25 } 26 }, 27 created () { 28 this.getData() 29 }, 30 methods: { 31 getData () { 32 this.$api.get('topics', null, r => { 33 this.list = r.data 34 }) 35 } 36 } 37 } 38 </script>
如上,代碼,我就把頁面渲染出來了。我們看下實際的效果:

如上,我先用了一個 v-for 的循環,來循環數據。這里可以參考:https://cn.vuejs.org/v2/api/#v-for 文檔。
在 time 標簽中,我使用了 v-text="i.create_at" 來渲染時間數據。參考文檔: https://cn.vuejs.org/v2/api/#v-text
router-link 是 VueRouter2 “聲明式導航”的寫法,在實際轉換為 html 標簽的時候,會轉化為 <a></a>,里面的 to 屬性,就相當於 a 的 href 屬性。參考文檔:https://router.vuejs.org/zh-cn/essentials/getting-started.html
3.編寫 src/utils/index.js 文件
如上面的圖片所示,由於拿到的數據是一個標准的時間格式,直接渲染在頁面上,這個效果不是很理想。因此,我們可以把時間給處理一下,然后再渲染出來。
這里,我們可以直接在 getData () {...} 后面再寫一個方法即可。但是,在一個項目中,如果所有的地方都是這樣的時間格式,我們在每一個組件中都來寫這樣的處理方法,很顯然就顯得我們比較愚蠢了。
因此,我們可以獨立出來寫一個方法,然后在所有的地方都可以使用,這樣就比較方便了。
還記得我們在第三篇博文中,我們建立了一個 src/utils/index.js 的空文本文件嗎?這里,我們要用上了。
直接給代碼如下:
1 export default { 2 goodTime (str) { 3 let now = new Date().getTime() 4 let oldTime = new Date(str).getTime() 5 let difference = now - oldTime 6 let result = '' 7 let minute = 1000 * 60 8 let hour = minute * 60 9 let day = hour * 24 10 let month = day * 30 11 let year = month * 12 12 let _year = difference / year 13 let _month = difference / month 14 let _week = difference / (7 * day) 15 let _day = difference / day 16 let _hour = difference / hour 17 let _min = difference / minute 18 19 if (_year >= 1) { 20 result = '發表於 ' + ~~(_year) + ' 年前' 21 } else if (_month >= 1) { 22 result = '發表於 ' + ~~(_month) + ' 個月前' 23 } else if (_week >= 1) { 24 result = '發表於 ' + ~~(_week) + ' 周前' 25 } else if (_day >= 1) { 26 result = '發表於 ' + ~~(_day) + ' 天前' 27 } else if (_hour >= 1) { 28 result = '發表於 ' + ~~(_hour) + ' 個小時前' 29 } else if (_min >= 1) { 30 result = '發表於 ' + ~~(_min) + ' 分鍾前' 31 } else { 32 result = '剛剛' 33 } 34 return result 35 } 36 }
好,代碼惡心了點,我拿我以前寫的代碼改的,沒有深入優化,大家就隨便看看,大概就是這么個東西。
寫好代碼之后,我們保存文件。但是此時,我們還不能使用我們的這個方法函數。我們必須在 main.js 中將我們的方法函數給綁定上。如下代碼:
1 // 引用工具文件 2 import utils from './utils/index.js' 3 // 將工具方法綁定到全局 4 Vue.prototype.$utils = utils
好了,這樣,我們寫的這個函數,就可以隨便被我們調用了。我們再來修改一下我們上面的 index.vue 中的代碼,將 time 調整為:
1 <time v-text="$utils.goodTime(i.create_at)"></time>
然后,我們再來看一下實際的效果:

好,我們已經看到,時間已經搞的挺好的了。
九、把內容頁面渲染出來
前面我們從文章中留下的引子 <router-link :to="'/content/' + i.id"> 應該知道,我們還是要做內容頁面的。
1.編寫內容頁面
1 <template> 2 <div> 3 <myHeader></myHeader> 4 <h2 v-text="dat.title"></h2> 5 <p v-if="dat.author">作者:{{dat.author.loginname}}發表於:{{$utils.goodTime(dat.create_at)}}</p> 6 <hr> 7 <article v-html="dat.content"></article> 8 <h3>網友回復:</h3> 9 <ul> 10 <li v-for="i in dat.replies"> 11 <p>評論者:{{i.author.loginname}}評論於:{{$utils.goodTime(i.create_at)}}</p> 12 <article v-html="i.content"></article> 13 </li> 14 </ul> 15 <myFooter></myFooter> 16 </div> 17 </template> 18 <script> 19 import myHeader from "../components/header.vue"; 20 import myFooter from "../components/footer.vue"; 21 export default { 22 components: { myHeader, myFooter }, 23 data() { 24 return { 25 id: this.$route.params.id, 26 dat: {} 27 }; 28 }, 29 created() { 30 this.getData(); 31 }, 32 methods: { 33 getData() { 34 this.$api.get("topic/" + this.id, null, r => { 35 this.dat = r.data; 36 }); 37 } 38 } 39 }; 40 </script>
好,我們這邊把代碼寫進 src/page/content.vue 文件。然后保存,我在我們先前的列表頁面隨便點開一篇文章,然后我們看下結果:

十、打包項目並發布到子目錄
我們已經完成了我們設想的項目的開發。但是,我們做好的這套東西,是基於 nodejs 開發的。而我們最終希望,我們開發的項目,生成好一堆文件,然后隨便通過任何一個 http 服務就能跑起來,也就是,還原成我們熟悉的 html+css+js 的模式。
1.打包項目
們進入到終端,並且進入到我們的開發目錄:
cd ~/Site/MyWork/vue-demo-cnodejs
然后運行如下代碼,進行打包:
pm run build
運行結果如下:

好,我們已經打包好了。文件打包位置於項目目錄里面的 dist 文件夾內。
但是,我們從上圖可以看到,我們生成了一些 .map 的文件。這就有點惡心了。當我們的項目變得比較大的時候,這些文件,第一個是,非常大,第二個,編譯時間非常長。所以,我們要把這個文件給去掉。
我們編輯 /config/index.js 文件,找到其中的
1 productionSourceMap: true,
修改為:
1 productionSourceMap: false,
然后我們重新運行打包命令:
npm run build

沒用的 map 文件已經沒有了。
好,我們可以從上圖中看出,有一個 tip 。它告訴我們,打包出來的文件,必須在 http 服務中運行,否則,不會工作。
2.安裝 http-server 啟動 http 服務
我們進入 dist 文件夾,然后啟動一個 http 服務,來看看可以不可以訪問。
你可能不知道如何啟動這樣一個 http 服務,或者,你現在已經到 apache 里面去進行配置去了。不用那么麻煩,我們有 nodejs 環境,只要全局安裝一個 http-server 服務就好了呀。
npm install http-server -g

好,通過這個命令,我們就安裝好了。安裝好了之后正常我們就能夠使用 http-server 命令來跑服務了。但是,這個世界不正常的時候是很多的嘛!
在終端里面輸入,
http-server
看能否正常啟動,還是爆 -bash: http-server: command not found 錯誤,這里,是說沒有找到這個命令,沒有關系,這是表示,我們的 nodejs 的程序執行路徑,沒有添加到環境變量中去。
首先,如上圖所示,我們的 http-server 安裝到了 /usr/local/Cellar/node/7.6.0/bin/ 這個目錄下面,我們只要把這個目錄,添加到環境變量即可。
請注意,你的安裝路徑可能和我的是不一致的,請注意調整。
我們在終端內執行下面兩個命令,就可以了。
echo 'export PATH="$PATH:/usr/local/Cellar/node/7.6.0/bin/"' >> ~/.bash_profile
. ~/.bash_profile
第一條命令是追加環境變量,第二個命令是,使我們的追加立即生效。
當然,你也可以直接編輯 ~/.bash_profile 文件,把上面命令中的單引號里面的內容插入到文件最后面,然后執行第二個命令生效。隨便。
好,一個插曲結束。我們要把我們打包出來的東西跑起來呀!
cd dist
http-server -p 3000
如果你是嚴格按照我的教程來的,那么現在已經可以順利的跑起來了。我們在瀏覽器中輸入 http://127.0.0.1:3000 就應該可以訪問了。
當然,會報錯,說是接口找不到,404錯誤。因為我們把接口給通過代理的方式開啟到了本地,但是這里我們並沒有開啟代理,所以就跑不起來了。很正常的。
這是因為示例的接口的問題。實際開發你還要按照我的這個做。只不過,最終代碼放到真實的服務器環境去和后端接口在一個 http 服務下面的話,就不存在這個問題了。
好,我們就跑起來了。
3.將項目打包到子目錄
剛剛,我們是將文件,打包為根目錄訪問的。也就是說,必須在 dist 文件夾下面啟動一個服務,才能把項目跑起來。
但是我們開發的大多數項目,可能是必須跑在二級目錄,甚至更深層次的目錄的。怎么做呢?
我們編輯 config/index.js 文件,找到:
assetsPublicPath: '/',
把 '/' 修改為你要放的子目錄的路徑就行了。這里,我要放到 /dist/ 目錄下面。於是,我就把這里修改為
1 assetsPublicPath: '/dist/',
然后,重新運行
npm run build
進行打包。
很快,就打包好了。
還記得,我們在項目文件夾中用 npm run dev 就可以開啟一個 http 服務嗎?並且那里,我們還代理了接口的。
好,我們就這么做。
然后我們訪問二級目錄 /dist/ 我們就可以看到效果了。

注意,我訪問的不是根目錄,而是 /dist/ 這個子目錄哦,這里是訪問的我們打包的文件的。
1 ├── index.html 2 └── static 3 ├── css 4 │ └── app.d41d8cd98f00b204e9800998ecf8427e.css 5 └── js 6 ├── app.8ffccad49e36e43a4e9b.js 7 ├── manifest.7a471601ff5a8b26ee49.js 8 └── vendor.057dd4249604e1e9c3b5.js
好,到這里,我們的打包工作,就講完了。
實際開發中,你只需要把 dist 文件夾中打包好的文件,給運維他們,讓他們去部署到真實的服務器環境就好了。
十一、打包項目圖片等資源的處理
1.在 vue 文件中,引用圖片
例如,我們將一張圖片放到資源目錄 /static/image/lyf.jpg 我們在 vue 文件中用下面的代碼來使用這張圖片。
1 <img src="static/image/lyf.jpg" alt="劉亦菲">
注意,最前面不要加 / 如果是這樣操作的話,會變成相對根目錄調用圖片。如果你的項目要打包到子目錄的話,這樣做就會出現問題。
2.在 css 文件中,引用圖片的處理
還是上面那張圖片,我們需要在 css 中來引用,如何來寫呢?
.love { background-image: url('../static/image/lyf.jpg'); }
好,這里為什么要加上 ../ 呢?
如果是最終打包到根目錄的話,可以使用 / 這種路徑。這個是完全可以理解的。
但,如果是打包到子目錄,我們必須看下生成的最終路徑:
├── index.html
└── static
├── css
│ └── app.a7a745952a8ca7f8c9413d53b431b8c8.css
├── image
│ └── lyf.jpg
├── img
│ └── lyf.9125a01.jpg
└── js
├── app.39ccc604caeb34166b49.js
├── manifest.b1ad113c36e077a9b54d.js
└── vendor.0b8d67613e49db91b787.js
如上,我們可以看到這個 css 相對 圖片 的路徑的地址。
你要疑問了,這樣的相對路徑,我們可以使用 ../image/lyf.jpg 來進行調用呀。嗯,看上去可以,但是,如果是這樣的話,在開發模式中又會出錯了。
所以,還是要用 '../static/image/lyf.jpg' 這樣的路徑方式來調用圖片。
字體圖標,js 文件等,都是這樣的路數。不在贅述。
