vue開發項目完全指南

這篇文章總結了vue項目的所遇到的問題,包括跨域、用戶認證、接口統一管理、路由配置、兼容性處理,性能優化等內容。

項目github地址 :

image-20190318090248419

一、環境依賴安裝

1. node環境

1.1 node和npm環境的安裝

根據以下教程安裝,然後設置好環境變量

http://www.runoob.com/nodejs/...

視頻教程 http://101.110.118.22/github....

centos如果裝不上看這裏:https://www.rosehosting.com/b...

1.2 爲npm更改源

npm默認使用的源的服務器在國外下載速度慢,所以需要更換源

以下兩種方法任選一種

1.2.1使用cnpm代替npm

參考鏈接:https://npm.taobao.org/
# 安裝
npm install -g cnpm --registry=https://registry.npm.taobao.org

#安裝完cnpm,之後再按照依賴就要使用cnpm
cnpm install [包名]

1.2.2爲npm更換源

參考鏈接 https://segmentfault.com/a/11...

修改源爲淘寶的源

npm config set registry http://registry.npm.taobao.org/

我們在發佈自己包的時候需要將官方的源改回來

npm config set registry https://registry.npmjs.org/

1.3 管理(更新)nodejs的版本

切換nodejs版本有兩種方式,分別是nvmn,n更簡單推薦使用

使用n管理nodejs版本

參考鏈接 https://www.jianshu.com/p/c64...

官網 https://github.com/tj/n

#安裝
npm install -g n

#使用n下載所需node版本
n 版本號
#下載最新版本
n latest
# 切換版本
輸入 n,
然後選中所需版本
#以指定的版本來執行版本
n use 7.4.0 index.js

linux使用n安裝新版本nodejs之後,如果node -v還是原來的版本,那麼就需要改變一下環境變量

vim .bash_profile

export NODE_HOME=/usr/local     #NODE_HOME改成新版本nodejs安裝的目錄,如果找不到,find / -name node
export PATH=$NODE_HOME/bin:$PATH
export NODE_PATH=$NODE_HOME/lib/node_modules:$PATH

修改環境變量參考:https://blog.csdn.net/yi412/a...

1.4 package.json文件詳解

參考文檔 http://javascript.ruanyifeng....

2. vue腳手架

vue-cli目前已經更新到3版本,vue-cli3把webpack相關的配置隱藏起來了,所有的配置都在vue.config.js文件夾中,所以使用vue-cli3需要的webpack水平較高,建議使用vue-cli2

3.1 vue-cli2.x安裝

參考鏈接:https://github.com/vuejs/vue-...

安裝:

npm install -g vue-cli

用法:

$ vue init < template-name >  < project-name >

例:

$ vue init webpack my-project

目前可用的模塊包括:

  • webpack - 一個功能齊全的Webpack + vue-loader設置,具有熱重載,linting,測試和css提取功能。
  • webpack-simple - 一個簡單的Webpack + vue-loader設置,用於快速原型設計。
  • browserify -全功能Browserify + vueify設置用熱重裝載,linting&單元測試。
  • browserify -simple - 一個簡單的Browserify + vueify設置,用於快速原型設計。
  • pwa - 基於webpack模板的vue-cli的PWA模板
  • simple - 單個HTML文件中最簡單的Vue設置

3.2 vue-cli3.x安裝及配置(僅供參考)

vue-cli3x的官方文檔:https://cli.vuejs.org/

Vue-cli3 中vue.config.js文件配置參考文檔:https://cli.vuejs.org/zh/conf...

Vue CLI 的包名稱由 vue-cli 改成了 @vue/cli。 如果你已經全局安裝了舊版本的 vue-cli(1.x 或 2.x),你需要先通過 npm uninstall vue-cli -gyarn global remove vue-cli 卸載它。

安裝

npm install -g @vue/cli

安裝了vue-cli3如果還想使用vue-cli2的init功能,需要安裝一個橋接功能

npm install -g @vue/cli-init
// vue.config.js 配置說明
//官方vue.config.js 參考文檔 https://cli.vuejs.org/zh/config/#css-loaderoptions
// 這裏只列一部分,具體配置參考文檔
module.exports = {
  // 部署生產環境和開發環境下的URL。
  // 默認情況下,Vue CLI 會假設你的應用是被部署在一個域名的根路徑上
  //例如 https://www.my-app.com/。如果應用被部署在一個子路徑上,你就需要用這個選項指定這個子路徑。例如,如果你的應用被部署在 https://www.my-app.com/my-app/,則設置 baseUrl 爲 /my-app/。
  baseUrl: process.env.NODE_ENV === "production" ? "./" : "/",
 
  // outputDir: 在npm run build 或 yarn build 時 ,生成文件的目錄名稱(要和baseUrl的生產環境路徑一致)
  outputDir: "dist",
  //用於放置生成的靜態資源 (js、css、img、fonts) 的;(項目打包之後,靜態資源會放在這個文件夾下)
  assetsDir: "assets",
  //指定生成的 index.html 的輸出路徑  (打包之後,改變系統默認的index.html的文件名)
  // indexPath: "myIndex.html",
  //默認情況下,生成的靜態資源在它們的文件名中包含了 hash 以便更好的控制緩存。你可以通過將這個選項設爲 false 來關閉文件名哈希。(false的時候就是讓原來的文件名不改變)
  filenameHashing: false,
 
  //   lintOnSave:{ type:Boolean default:true } 問你是否使用eslint
  `lintOnSave`: true,
  //如果你想要在生產構建時禁用 eslint-loader,你可以用如下配置
  // lintOnSave: process.env.NODE_ENV !== 'production',
 
  //是否使用包含運行時編譯器的 Vue 構建版本。設置爲 true 後你就可以在 Vue 組件中使用 template 選項了,但是這會讓你的應用額外增加 10kb 左右。(默認false)
  // runtimeCompiler: false,
 
  /**
   * 如果你不需要生產環境的 source map,可以將其設置爲 false 以加速生產環境構建。
   *  打包之後發現map文件過大,項目文件體積很大,設置爲false就可以不輸出map文件
   *  map文件的作用在於:項目打包後,代碼都是經過壓縮加密的,如果運行時報錯,輸出的錯誤信息無法準確得知是哪裏的代碼報錯。
   *  有了map就可以像未加密的代碼一樣,準確的輸出是哪一行哪一列有錯。
   * */
  productionSourceMap: false,
 
  // 它支持webPack-dev-server的所有選項
  devServer: {
    host: "localhost",
    port: 1111, // 端口號
    https: false, // https:{type:Boolean}
    open: true, //配置自動啓動瀏覽器
    // proxy: 'http://localhost:4000' // 配置跨域處理,只有一個代理
 
    // 配置多個代理
    proxy: {
      "/api": {
        target: "<url>",
        ws: true,
        changeOrigin: true
      },
      "/foo": {
        target: "<other_url>"
      }
    }
  }
};

二、開發

以下內容依賴環境爲 : vue-cli 版本2.9.x

項目github地址 :

安裝完以上依賴後,就可以開始一個項目了,我們先看下後端api的定義

前後端交互報文定義以及數據api接口

前後端交互報文定義

請求

http request header{ //除登錄註冊以外的請求,發起請求時要在請求頭中加入token
    authorization:jwt
}
http request body{
    
}

返回

http response header{
    
}
http response body{
    code:業務處理狀態碼
    msg:業務處理描述
    token:jwt token
    data:業務數據
}

項目中使用的後臺api定義如下

注:服務器端的host爲118.24.85.97,端口爲22222

1.測試api是否可用

  1. uri: http://118.24.85.97:22222/api
  2. 描述:測試接口是否能用,能用的話返回 'API WORDS'字符串
  3. 請求類型 GET
  4. 請求參數 無
  5. 返回值 {'Api Works'}

2.註冊

  1. uri: http://118.24.85.97:22222/api/users/reg
  2. 描述:註冊
  3. 請求類型 POST
  4. 請求參數
序號 參數名 是否必填 描述
1 name y 用戶名
2 pass y 密碼
  1. 返回參數 不重要

3.登錄

  1. uri: http://118.24.85.97:22222/api/users/login
  2. 描述:登錄
  3. 請求類型 POST
  4. 請求參數
序號 參數名 是否必填 描述
1 name y 用戶名
2 pass y 密碼
  1. 返回參數
序號 參數名 描述
1 msg ok
2 token 用於驗證用戶身份的token

4.獲取當前用戶信息

  1. uri: http://118.24.85.97:22222/api/users/current
  2. 描述:獲取用戶信息
  3. 請求類型 GET
  4. 請求參數 無
  5. 返回參數
序號 參數名 描述
1 id 用戶id
2 token 用於驗證用戶身份的token

0.初始化項目

在終端中輸入

vue init webpack vue2_template

然後會有一些選項讓你選,按照項目需求選擇,例如我不需要eslint,unit test,就可以選No,現在選no將來如果需要的話也可以自己安裝

image-20190301151747514

安裝完成之後,按照提示切換到相應目錄,執行相應指令,然後在瀏覽器打開網址,這樣一個簡單的vue項目就啓動起來了

image-20190301152115255

1. 項目文件介紹

整個文件介紹:

image-20190301153205422

注意:

  1. 開發主要使用src文件夾
  2. webpack的配置文件配置文件詳解看這裏:https://segmentfault.com/a/11...
  3. package.json配置詳解 http://javascript.ruanyifeng....

src目錄介紹

首先在src目錄下新建一個文件夾views,用來放我們的主要頁面,然後在assets文件夾中建立fonts styles imgs,用來存放相應的資源,建完之後,文件夾如下

image-20190301155249665

2. 跨域、axios配置與api管理

在這個項目中,我們使用axios進行數據請求

axios中文文檔: https://www.kancloud.cn/yunye...
# 安裝axios
npm/cnpm i axios -S      # -S 指安裝到package.json中的dependencies中

安裝完成後,我們要在main.js中引入,然後測試一下是否成功引入

//main.js文件
import axios from 'axios'

axios.get('https://api.github.com/users?since=10')   //使用github接口做一下測試
  .then(res=>console.log(res))
  .catch(err=>console.log(err))

瀏覽器顯示以下信息,說明引入成功image-20190301160510216

github提供的接口配置了cors,所以我們能夠能夠在瀏覽器正常訪問到,但cors兼容性最低到ie10,而且後臺不一定會配置cors,所以在開發時我們需要配置一下跨域

參考鏈接:

  1. cors詳解 http://www.ruanyifeng.com/blo...

2.1配置跨域

參考文檔:https://segmentfault.com/a/11...

先找個沒有設置cors的api使用axios訪問一下

axios.get('http://118.24.85.97:22222/api')
.then(res=>console.log(res))
.catch(err=>console.log(err))

瀏覽器會因爲同源策略報錯

image-20190307094529285

下面進行跨域的配置

配置目錄 config/index.js 13行
proxyTable: {
  '/apis':{
    target:'http://118.24.85.97:22222',//後臺地址 proxyTable  把/apis映射成target 即 /apis=http://118.24.85.97:22222
    changeOrigin:true,//是否跨域
    pathRewrite:{
      '^/apis':''
    }
  }

}

再進行訪問數據時就要在接口前面加上/apis(/apis就相當於http://118.24.85.97:22222)

axios.get('/apis/api')
.then(res=>console.log(res))
.catch(err=>console.log(err))

然後就發現瀏覽器訪問成功了

image-20190307095002857

proxyTable原理:跨域是瀏覽器禁止的,服務端並不禁止跨域 ,所以瀏覽器可以發給自己的服務端然後,由自己的服務端再轉發給要跨域的服務端,做一層代理。proxyTable使用的是http-proxy-middleware中間件,內部用的是http-proxy

以上配置的跨域是開發環境下的,在生產環境就自動失效了,而且這樣配置我們開發時訪問接口時,都要寫成/apis/xxx/xxx格式,在部署到服務器中時,我們要把/apis拿掉,才能訪問到正確的url。有兩種方法,一種是在開發環境中設置(通過axios的baseURL),另一種是在服務器上修改nginx的配置設置。

2.2生產環境去除/apis前綴

在這裏詳細說下第一種方式,原理是這樣的:

通過檢測是開發環境和生產環境,設置不同的baseURL,使生產環境和開發環境都能正確訪問url

在src目錄下新建一個apis目錄,然後在apis目錄下新建一個api.config.js文件

//判斷是否是生產環境
//webpack在開發環境和生產環境分別執行不同的js文件,process.env.NODE_ENV設置了不同的值,process.env.NODE_ENV在生產環境中值爲'production'(這個值是在build/build.js中第4行設置的)
var isPro = process.env.NODE_ENV=== 'production'
// 如果是生產環境 我們就使用服務器的uri,如果是開發環境,我們就添加/apis前綴
module.exports = {
    baseUrl: isPro ? 'http://118.24.85.97:22222' : '/apis'
}

在main.js中引入這個文件,然後設置axios的baseURL

//引入api.config.js文件,然後設置axios的baseURL
import apiConfig from './apis/api.config'
axios.defaults.baseURL=apiConfig.baseUrl

再來測試一下不加/apis的接口

axios.get('/api')
.then(res=>console.log(res))
.catch(err=>console.log(err))

瀏覽器顯示是ok的。這樣我們以後使用axios訪問接口就可以不加/apis了,打包後訪問也不用手動去除/apis

2.3 api統一管理

在vue項目開發過程中,會涉及到很多接口的處理,當項目足夠大時,就需要統一管理接口。

具體方法應該挺多的,這裏只介紹一種:使用axios+async/await進行接口的統一管理

一般來說,後臺的接口是分模塊的,例如我們後臺的測試接口

  • 身份認證 /api/login /api/reg
  • 用戶信息 /v1/api/user

我們首先在src目錄下新建一個apis文件夾,後臺提供的所有接口都在這裏定義

第二步,按照後臺提供的模塊新建js文件,我們新建user.js auth.js

第三步,引入axios,做相應的配置

在apis目錄下新建一個http.js,在裏面做axios相應的配置

  1. 我們上文中是在main.js文件引入的axios,設置的baseURL,以上代碼可以去除,改爲在http.js中引入
  2. 我們做的主要是:引入axios,創建一個axios的實例(實例的功能和axios一樣)
import axios from 'axios'
import apiConfig from './api.config'
//創建axios的一個實例
var instance = axios.create({
    baseURL:apiConfig.baseUrl,
    timeout: 6000
})


//------------------- 一、請求攔截器 後面介紹
instance.interceptors.request.use(function (config) {

    return config;
}, function (error) {
    // 對請求錯誤做些什麼
    
    return Promise.reject(error);
});

//----------------- 二、響應攔截器 後面介紹
instance.interceptors.response.use(function (response) {
    
    return response.data;
}, function (error) {
    // 對響應錯誤做點什麼
    return Promise.reject(error);
});

/**
 * 使用es6的export default導出了一個函數,導出的函數代替axios去幫我們請求數據,
 * 函數的參數及返回值如下:
 * @param {String} method  請求的方法:get、post、delete、put
 * @param {String} url     請求的url:
 * @param {Object} data    請求的參數
 * @returns {Promise}     返回一個promise對象,其實就相當於axios請求數據的返回值
 */
export default function (method, url, data = null) {
    method = method.toLowerCase();
    if (method == 'post') {
        return instance.post(url, data)
    } else if (method == 'get') {
        return instance.get(url, { params: data })
    } else if (method == 'delete') {
        return instance.delete(url, { params: data })
    }else if(method == 'put'){
        return instance.put(url,data)
    }else{
        console.error('未知的method'+method)
        return false
    }
}

第四步,在apis/xxx.js文件中引入http.js導出的函數,拿其中一個文件auth.js說明

//auth.js 用於定義用戶的登錄、註冊、註銷等

import req from './http.js'

//定義接口

//在這裏定義了一個登陸的接口,把登陸的接口暴露出去給組件使用
export const LOGIN =params=>req('post','/api/users/login',params)
//這裏使用了箭頭函數,轉換一下寫法:
// export const LOGIN=function(params){
//   return req('post','/api/login',params)
// }

//定義註冊接口
export const REG =params=>req('post','/api/users/reg',params)


最後一步,在需要用的該api的組件中引入並調用,我們在App.vue文件中測試下

<template>
  <div>
    <h2>登錄</h2>
    用戶名<input type="text" v-model="user">
    密碼<input type="password" v-model="pass">
    <input type="button" @click="reg" value="註冊">
    <input type="button" @click="login" value="登錄">
  </div>
</template>
<script>
import {LOGIN,REG} from '../../apis/auth.js'
export default {
  data() {
    return {
      user:'',
      pass:'',
      err:[]
    }
  },
  methods: {
    async reg(){
      try {
        const data = await REG({ name: this.user,pass: this.pass })
        console.log(data)
        alert(JSON.stringify(data))
        this.cleanForm()


      } catch (error) {
        console.log(error)
      }

    },
    async login(){
      try {
        const data = await LOGIN({ name: this.user,pass: this.pass })
        alert(JSON.stringify(data))
        this.cleanForm()
      } catch (error) {
        console.log(error)
      }
    },
    cleanForm(){
      this.user=''
      this.pass=''
    }
  },

}
</script>

注:如果要打開Login.vue,需要配置對應的路由

上面的代碼引入了auth.js定義的api,並在對應的方法中使用。代碼中用到了async/await,其實很簡單,可以假設async是個標識,說明這個函數中有異步請求,await翻譯爲'等',後面接一個異步請求,等後面的異步請求執行完成之後,會把結果賦給=左邊的值

參考鏈接 http://www.runoob.com/w3cnote...

總結一下,像上面那樣定義接口雖然麻煩點,但有兩個好處:

  1. 代碼看起來規範,所有的接口都在一個文件夾定義,不用分散的各個組件,維護起來簡單,例如後臺的一些url變了,改起來也方便
  2. 可以做到接口一次定義,到處使用

3. 路由配置

Vue Router官方文檔 https://router.vuejs.org/zh/

前端路由原理:https://segmentfault.com/a/11...

3.1 最簡配置

路由的配置文件在router/index.js文件中

先引入文件,再進行配置

首先在views目錄中新建以下頁面,主頁(Home/Home.vue),登錄頁(Login/Login.vue),測試頁(Test/Test.vue)

然後配置下路由

import Vue from 'vue'
import Router from 'vue-router'
//@表示 src目錄 webpack的配置在webpack.base.conf.js第29行 alias{'@':resolve('src')}
import Home from '@/views/Home/Home.vue'
import Login from '@/views/Login/Login.vue'
import Test from '@/views/Test/Test.vue'

Vue.use(Router)

export default new Router({
  routes: [//路由規則
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path:'/login',
      name:'Login',
      component:Login
    },
    {
      path:'/test',
      name:'Test',
      component:Test
    }
  ]
})

路由規則在routes中進行配置,routes是一個數組,接受一系列路由規則,每個路由規則是一個對象,包括路徑、路由名字,和路徑匹配的組件,建議給每個路由加個名字,在後面可能會用到。

打開瀏覽器,輸入相應的url查看配置的路由是否正確,不正確的話檢查下自己的配置

3.2配置路由懶加載

參考文檔:

路由懶加載官方文檔:https://router.vuejs.org/zh/g...

webpack之mainfest解讀:https://github.com/younth/blo...

當打包構建應用時,Javascript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然後當路由被訪問的時候才加載對應組件,這樣就更加高效了。所以,懶加載的含義是當路由被訪問時再去加載對應的js代碼。

首先,不做路由懶加載的情況下,我們打包一下(切換到項目目錄,執行npm run build),然後會發現項目下生產了3個js文件
image-20190304110812440
簡單介紹一下作用:

  1. vendor.js 第三方庫,一般是 node_modules裏面的依賴進行打包 體積最大
  2. app.js 入口js打包的結果,即我們編寫的所有代碼都會打包進去
  3. manifest.js 主要是一些異步加載的實現方法(通過建立script方式動態引入js),內容上包含異步js的文件名和路徑。

然後我們實現一下路由懶加載 @/router/router.js

import Vue from 'vue'
import Router from 'vue-router'
// import Home from '@/views/Home/Home.vue'
// import Login from '@/views/Login/Login.vue'
// import Test from '@/views/Test/Test.vue'
// 懶加載方式
const Home=()=>import('@/views/Home/Home.vue')
const Login=()=>import('@/views/Login/Login.vue')
const Test=()=>import('@/views/Test/Test.vue')
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path:'/login',
      name:'Login',
      component:Login
    },
    {
      path:'/test',
      name:'Test',
      component:Test
    }
  ]
})

懶加載只是改變了一下組件的引用方式,由原來的直接引入變成異步引入,當我們訪問對應的路由path時,纔會加載相應的路由組件。

配置完成後再執行一次打包,結果如下:
image-20190304112607087

我們會發現目錄中多出來3個js文件,並且app.js文件變小了。這說明配置了懶加載之後,app.js中其他組件的內容被抽離出來,分配到各自的js文件中。配置懶加載之後,剛開始打開頁面只會加載app.js文件,只有在用戶點擊相應路由時,纔會加載對應的js代碼。當我們的業務代碼非常多時,懶加載是個很好的選擇。

3.3 配置history模式

官方文檔:https://router.vuejs.org/zh/g...

配置history模式有兩個原因,一是因爲hash模式看很醜,二是因爲預加載要用到History模式,配置非常簡單,只需要配置屬性mode的值爲'history'

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

不過這種方式需要後臺的支持,當匹配不到url時,返回url/index.html頁面

nginx配置如下

location / {
  try_files $uri /index.html;
}

4. 權限管理

參考鏈接:

json web token入門教程 http://www.ruanyifeng.com/blo...

jwt官網 https://jwt.io/

4.1 token驗證

我們通過jwt進行用戶認證,jwt的原理是:服務器認證以後,生成一個json對象,發回給用戶.

{
    "id":"001",
    "姓名":"小明",
    "角色":"管理員",
    "到期時間":"2019年3月3日12時30分"
}

以後用戶與服務端通信的時候,都要發回這個json對象。服務器完全靠這個對象認定用戶身份(一般是通過這個對象的中id去數據庫請求數據)。爲了防止用戶篡改數據,服務器會在生成這個對象的時候,加上簽名。就像這種形式:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

關於JWT保存更新的業務流程如下:

  1. 保存:登錄後保存token
  2. 添加:每次發送請求之前檢查token是否存在,存在,添加到請求頭中,發送請求
  3. 更新:每次發送請求服務器返回數據之後更新token

主要邏輯包括:

  1. 登錄之後,在localStorage中保存token
  2. 每次發送請求之前,使用axios請求攔截器將token放到請求頭中
  3. 每次發送請求服務器返回數據之後在axios的響應攔截器中更新token
//1.登錄之後保存token login.vue
async login(){
    const data = await LOGIN({ name: this.user,pass: this.pass })
    //保存token
    localStorage.setItem('token',data.token)
    //查看是否保存成功
    console.log(localStorage.getItem('token'))
}
//每次發送請求之前,講token放到請求頭中 api/http.js
//---使用axios的請求攔截器,每次發送請求之前攔截一下
instance.interceptors.request.use(function (config) {
    // 給頭添加token
    if (localStorage.getItem('token')){//存在token,加入頭
        config.headers.authorization=localStorage.getItem('token')
    }
    return config;
}, function (error) {
    // 對請求錯誤做些什麼

    return Promise.reject(error);
});
//完成之後,記得發送一個請求,看看是否正確添加token

//---響應攔截器,服務器響應後先到達這裏
instance.interceptors.response.use(function (response) {
    if(response.data.code=='2000'){//成功響應,更新token
      if(response.data.token){
        localStorage.setItem('token',response.data.token)
      }
    }else{
        //錯誤處理 根據不同的狀態碼,進行錯誤處理  
    }
    return response.data;
}, function (error) {
    // 對響應錯誤做點什麼
    return Promise.reject(error);
});

4.2 對頁面的訪問權限

除了對token的操作,我們還要判斷用戶有沒有權限訪問這個頁面(有些頁面是用戶必須登錄才能訪問的),具體配置要使用Vue Router的導航守衛

參考鏈接:https://router.vuejs.org/zh/g...

在全局前置守衛中進行驗證


//在router/index.js進行配置
//在每次進行路由跳轉之前進行
router.beforeEach((to,from,next)=>{//增加登錄驗證
  const isLogin=localStorage.getItem('token')?true:false;
  if(to.path=='/login'){//如果是登錄頁面,不需要token
    next();
  }else{//如果不是登錄頁面就要判斷是否登錄
    isLogin?next():next('/login');
  }

})

5. 將界面交給第三方UI庫

iview官網:https://www.iviewui.com/

爲節省開發時間,我們往往會使用一些第三方ui庫,比如iview elementui等

我們在這裏只介紹iview,其他ui庫大同小異

iview的安裝與引入

安裝

cnpm i iview --save

按需引入組件

官網說,需要下載插件才能按需引入,官網說明,但是不下好像也可以正常引入

//在main.js文件中引入項目需要的組件
import {Button,Table,Message} from 'iview'
//然後註冊組件
Vue.component('Button',Button)
Vue.component('Table',Table)
Vue.component('Message',Message)

這樣註冊的話太繁瑣,所以需要優化一下

//main.js
import {Button,Table,Message} from 'iview'
const iviewComs={Button,Table,Message}
Object.keys(iviewComs).forEach(key=>{Vue.component(key,component[key])})

代碼都寫在main.js中顯得太擁擠,我們可以把代碼拿出去,寫成一個插件

我們在components文件夾中新建一個文件iview-coms,用來放iview中引入的組件

//components/iview-coms.js  

import {Button,Table,Message} from 'iview'
const components={Button,Table,Message}
const install = function(Vue, opts = {}){
  Object.keys(components).forEach(key=>{
    Vue.component(key,components[key])
  })
}

export default install

然後在main.js中引入,use這個插件

import iviewComs from './components/iview-coms'
Vue.use(iviewComs)

ok了,接下來看自定義主題

自定義主題

官網鏈接:https://www.iviewui.com/docs/...

原理很簡單,就是把ivew的less文件引入,並且覆蓋掉,然後在main.js文件中引入自己的less文件

首先,我們需要下載解析less文件的loader ,lessless-loader,這裏有個坑,下載less的時候要下載3版本以下的,不然會報一堆錯誤

cnpm i [email protected] less-loader -D

下載完就ok了,不需要在webpack中進行配置,因爲已經配置好了

然後,在assets/styles/base.less(沒有需要自己新建)中,引入iview的樣式文件,並且覆蓋掉

默認變量列表:https://github.com/iview/ivie...

//assets/styles/base.less
//------ 引入iview樣式
@import '~iview/src/styles/index.less';
//------ 覆蓋iview的樣式
@primary-color: #E91E63;
@error-color : #FF3300;

最後在main.js引入該less文件

//main.js
import './assets/styles/base.less'

此時,引入的組件就可以在.vue文件中使用了,看一下效果:

image-20190307150805499

ok了。最後還要補充一下,在項目開發過程中,不可避免的要覆蓋iview默認的樣式,我們分爲兩種情況,一種是全局覆蓋,一種是局部覆蓋。

全局覆蓋的話我們要新建一個less文件,比如叫cover-iview.less所有覆蓋iview樣式的代碼都放在這裏,然後在base.less中引入這個文件。

局部覆蓋的話要注意不要影響到別的樣式,所以要充分利用less的作用域,例如我們只需要改home頁面下的iview按鈕樣式,我們可以這樣:

.home{
    .ivu-btn{
        
    }
}

6.開發中注意問題

6.1編寫自己的工具庫插件

參考文檔:

vue插件說明:https://cn.vuejs.org/v2/guide...

項目中往往會使用一些通用的函數,比如獲取當前時間、時間格式轉化,防抖,節流等,我們可以把這個公用的部分封裝成插件,在main.js中引入。

首先,在src目錄下新建utils文件夾,在裏面新建index.js,utils.js文件

我們在utils.js中編寫自己的工具庫,然後導出

class Utils{
    constructor(){
        this.d=new Date();//date對象
        this.instance=null;
    }
    static getInstance(){//單例模式
        if(!this.instance){
            this.instance = new Utils();
        }
        return this.instance;
    }

    pick(obj,arr){//pick({ a: 1, b: '2', 'c': 3 }, ['a', 'c'])  =>{a:1,c:3}
       return arr.reduce((acc,curr)=>{
            return (curr in obj && (acc[curr] = obj[curr]), acc)
        },{})
    }

    dateFormat(datetime,pattern=""){
        let vWeek = ["星期天","星期一","星期二","星期三","星期四","星期五","星期六"];
        let dt=new Date(datetime);
        let y=dt.getFullYear();
        let m=(dt.getMonth()+1).toString().padStart(2,'0');
        let d=dt.getDate().toString().padStart(2,'0');
        let hh=dt.getHours().toString().padStart(2,'0');
        let mm=dt.getMinutes().toString().padStart(2,'0');
        let ss=dt.getSeconds().toString().padStart(2,'0');
        let vWeek_s = dt.getDay();//星期
        if(pattern.toLowerCase() === 'yyyy-mm-dd'){
            return `${y}-${m}-${d}`
        }else if(pattern.toLowerCase() === 'mm-dd'){
            return `${m}-${d}`
        }else if(pattern.toLowerCase() === 'yyyymmddhhmmss'){
            return `${y}${m}${d}${hh}${mm}${ss}`
        }else {
            return `${y}-${m}-${d} ${hh}:${mm}:${ss} ${vWeek[vWeek_s]}`
        }

    }

}

const UTIL = Utils.getInstance();

// console.log(UTIL.dateFormat(new Date(),'yyyymmddhhmmss')) //=>20190312110722
// console.log(UTIL.dateFormat(new Date()))//=>2019-03-12 11:07:22 星期二
// console.log(UTIL.pick({ a: 1, b: '2', 'c': 3 }, ['a', 'c']))//=>{a:1,c:3}

export default UTIL;

然後在index.js中編寫插件,導出

//utils/index.js

import UTIL from './utils.js'

const UtilPlugin={}

UtilPlugin.install=function(Vue,options){//插件必須有install方法,接受兩個參數,一個是Vue構造器,一個是參數
  Vue.prototype.$utils=UTIL//在vue prototype上添加實例方法
}
export default UtilPlugin

最後在main.js中引入並use插件

// utils
import Util from './utils/index'
Vue.use(Util)
console.log(Vue.prototype.$util)//打印下是否引入成功

之後就可以在組件中通過使用this.$utils調用方法了

7. 兼容性處理

我們的目標是兼容到ie9,對ie8及以下的瀏覽器做相應的跳轉處理(跳轉到瀏覽器下載界面)

兼容性對一個程序來說是非常重要的,兼容性測試越早越好

image-20190307151841810

7.1 對ie8及以下瀏覽器的跳轉處理

在項目根目錄下中的html中head中加入下面代碼

<!--[if lte IE 8]><script>window.location.href="https://support.dmeng.net/upgrade-your-browser.html?referrer="+encodeURIComponent(window.location.href);</script><![endif]-->

目的是檢測ie瀏覽器的版本,如果低於<=ie8,就跳轉到下面這個頁面

image-20190307153138889

7.2 兼容ie9

參考鏈接:https://juejin.im/post/5b2868...

7.2.1 ES6兼容

我們把瀏覽器調到ie9,然後看控制檯報錯信息

image-20190307154807782

報這個錯的原因是es6的新對象,新表達式,ie9不支持,爲解決這個問題,我們需要引入babel-polyfill

cnpm i babel-polyfill -D

安裝完成之後,在main.js文件中引入

import 'babel-polyfill'

在項目使用 vue-cli 生成的代碼中,根目錄有一個 .babelrc 文件,這是項目使用 babel 的配置文件。在默認生成的模板內容中,增加 "useBuiltIns": "entry" 的設置內容,這是一個指定哪些內容需要被 polyfill(兼容) 的設置

useBuiltIns 有三個設置選項

  • false - 不做任何操作
  • entry - 根據瀏覽器版本的支持,將 polyfill 需求拆分引入,僅引入有瀏覽器不支持的polyfill
  • usage - 檢測代碼中 ES6/7/8 等的使用情況,僅僅加載代碼中用到的 polyfill

7.2.2建立自己的polyfill

加入這些代碼後,工程中大部分代碼已可以兼容到ie9版本,但還是會有少部分不兼容的特性,例如requestAnimationFrameclassList等。對於這些內容,我們需要自己定義polyfill來解決,在src目錄下新建一個文件夾polyfill,然後在polyfill文件夾下面建一個polyfill.js,我們在polyfill.js中加入我們的兼容代碼

然後在main.js中引入這個文件

import './polyfill/polyfill'

解決兼容方式的正確姿勢是:拿到ie9瀏覽器下的報錯信息,去goole或者baidu搜索,得到polyfill,然後加入到自己的polyfill.js文件中

三、優化

1. webpack3.x優化打包速度

我們執行一下npm run build,結果如下:

image-20190307161705933

整個打包過程花了32s左右,現在我們的項目只是引入了相關的依賴,一些業務邏輯還沒有寫,打包速度就那麼慢了,等到我們寫完整個項目,打包速度還會繼續變長,所以我們需要優化一下。

優化打包速度,我們修改的主要是webpack.prod.conf.js文件

替換代碼壓縮工具

Webpack 默認提供的 UglifyJS 插件,由於採用單線程壓縮,速度慢 ;

webpack-parallel-uglify-plugin 插件可以並行運行 UglifyJS 插件,更加充分而合理的使用 CPU 資源,這可以大大減少的構建時間;

//安裝
cnpm i webpack-parallel-uglify-plugin -D
//配置 webpack.prod.conf.js

//首先刪除項目中的 UglifyJsPlugin插件及配置,第二次打包時提高速度,要把.cache文件加入到gitignore中
// new webpack.optimize.UglifyJsPlugin({
//   compress: {
//     warnings: false,
//     drop_console: true
//   },
//   sourceMap: true
// }),

//然後引入並使用我們剛纔裝的插件

==注意:版本控制工具提交時,要忽略.cache文件==

配置完後我們執行npm run build,發現打包速度降到了23s

image-20190307162957635

再執行一次npm run build,發現打包速度降到了12s

image-20190307164513348

時間降低那麼多是因爲文件沒有改動,直接利用了緩存中的js文件

happypack開啓多核構建項目

一般node.js是單線程執行編譯,而happypack則是啓動node的多線程進行構建,大大提高了構建速度。

首先安裝,

修改webpack.base.conf.js

const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
...
...
// 增加plugins
 plugins: [
  new HappyPack({
    id: 'happy-babel-js',
    loaders: ['babel-loader?cacheDirectory=true'],
    threadPool: happyThreadPool,
  })
]
...
...
// 修改對應loader
{
  test: /\.js$/,
  loader: 'happypack/loader?id=happy-babel-js',
  include: [resolve('src'), resolve('test')],
}

配置完成,執行npm run build

image-20190307165549102

what??並沒有提高速度 不要用這個鬼東西了

hardSourceWebpackPlugin節省70%的時間

https://github.com/mzgoddard/...
#安裝
cnpm install --save-dev hard-source-webpack-plugin

使用,在webpack.prod.conf.js中引入並使用

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

module.exports = {
  context: // ...
  entry: // ...
  output: // ...
  plugins: [
    new HardSourceWebpackPlugin()
  ]
}

結果:

image-20190307171310562

注:要第二次打包才生效

總結下,使用了三個插件,我們的打包速度從30s降低到4s,awesome!

2. webpack3.x優化首屏加載速度

首先要說明一下,首屏加載速度優化針對的是打包後dist文件。我們如果要在本地進行測試的話,需要本地有個服務器,我們在這裏使用nginx。

2.1本地安裝nginx

下載地址: http://nginx.org/en/download....

在官網上找到自己系統適合的nginx版本,下載到本地

2.1.1window安裝

  1. 解壓文件
  2. 雙擊運行nginx.exe,在任務管理器中出現nginx的進程,則表示安裝成功

2.1.2 mac/linux安裝

#1.解壓文件
tar -xzf nginx-1.14.0.tar.gz  #mac可以使用解壓縮工具解壓,不必用命令行

#2. 配置安裝路徑   --prefix指定安裝路徑  假設我要裝到/usr/local/nginx文件夾中
./configure --prefix=/Users/best9/local/nginx

#編譯
make

##安裝
make install

安裝完成後進入到—prefix指定的文件夾中,執行ll,會發現文件夾下有以下目錄

image-20190308144717721

我們要關心就是我上面標出來的三個目錄

進到sbin目錄中,啓動nginx程序

image-20190308145219037

cd sbin
#需要使用root權限,否則會報錯  報錯信息可以在日誌中查看到,錯誤日誌目錄 /logs/error.log
sudo ./nginx     

正常的話,nginx會默認在localhost:80端口啓動,在瀏覽器訪問localhost,就會顯示默認界面

image-20190308145304691

如果電腦的80端口被佔用的話,在conf/nginx.conf文件中修改端口

2.2 nginx常用命令

nginx使用-s發送信號操作運行中的進程,常用命令如下:

注意:使用命令需要在sbin目錄下

#啓動nginx
./nginx
#立即停止服務 -s stop
./nginx -s stop
#優雅地停止服務 -s quit
./nginx -s quit
#重啓服務 -s reload
./nginx -s reload

2.3 nginx配置靜態文件服務器

我們在這裏使用nginx配置一個最簡單的靜態文件服務器,更復雜的配置稍後再講

nginx的配置文件地址:conf/nginx.conf

使用vim或者其他編輯器打開該文件,修改配置文件第43-45行:

vim conf/nginx.conf

image-20190308150902046

location / {
  alias /Users/best9/github/vue2_template/dist;  #訪問/相當於訪問alias配置的目錄    
}

配置完成後保存,然後重啓服務

sudo ./sbin/nginx -s reload 要使用root權限重啓

打開瀏覽器訪問localhost

image-20190308151058193

因爲沒有登錄,會自動跳轉到登錄界面

到這裏靜態文件服務器就配置好了,但我們刷新下頁面,會報錯404

image-20190308151213416

這是因爲我們使用了vue router的history模式,我們需要在nginx中加入以下配置

image-20190308151523068

location / {
  try_files $uri $uri/  /index.html;
}

然後重啓nginx,再刷新頁面就沒問題了

2.4 優化首屏加載速度

以上步驟就緒後,我們就可以來優化加載速度了

打開chrome的devTools面板,切換到Network,禁用瀏覽器緩存,刷新測試下加載速度,發現整個應用加載大約需要1.97s,如下圖:

image-20190308152853851

把網絡環境切換到Fast 3G,再測試一次,發現加載用了7.56s,白屏時間6.89s

image-20190308165101522

我們使用預渲染插件進行優化

2.4.1 預渲染

使用插件:prerender-spa-plugin

參考鏈接:https://juejin.im/post/59d49d...

首先,安裝 prerender-spa-plugin,安裝時件略長,因爲其依賴了 phantomjs

cnpm install prerender-spa-plugin --save-dev

我們只在生產環境中進行預渲染,修改 build/webpack.prod.conf.js,在配置插件的地方加入如下代碼。

//引入 預渲染插件
const PrerenderSpaP=require('prerender-spa-plugin')

//在plugins中配置
new PrerenderSpaP(
  // 輸出目錄的絕對路徑
  path.join(__dirname,'../dist'),
  //預渲染路由
  ['/home','/login']
)

再次執行打包,然後再進行測試:

image-20190308165347855

發現白屏時間爲4.10s,在弱網環境下,使用預渲染,大約能縮減2.5秒的白屏時間

預渲染注意事項
  • 預渲染的路由不能是動態加載的,否則會報webpackJsonp is not define的錯誤,要想解決這個錯誤,可以看這裏 https://juejin.im/entry/5911a...
  • 預渲染的路由不能是需要權限才能訪問的頁面。預渲染的機制是在本地跑一個chromium瀏覽器,然後去爬取你預渲染頁面的Html,如果你的頁面需要權限(登錄)才能進入,就爬不到,也不會報錯,最終只會渲染不需要權限的頁面

舉個例子:

插件配置如下:

new PrerenderPlugin({
    staticDir:path.join(__dirname,'../dist')
    routes:['/','/about','/login']
})

路由配置如下:

image-20190314164834830

2.4.2 配置gzip壓縮

gzip官方文檔 http://nginx.org/en/docs/http...

nginx默認是關閉gzip的,我們需要自己打開,並進行一些配置:

image-20190311103518953

gzip:on;  #打開gzip,關閉爲off
gzip_min_length 1;  #小於gzip_min_length,不進行壓縮(默認單位爲byte)
gzip_comp_level 2;  #壓縮級別
gzip_types text/plain text/css application/javascript text/javascript image/jpeg image/gif image/png;#指定類型進行gzip壓縮

配置完成後,我們再測試一下加載速度:

image-20190311103446777

發現白屏時間爲1.95s,加載文件的體積也變小了

四、部署

1. nginx配置反向代理

我們要在本地部署測試,所以後臺的地址是127.0.0.1:22222

項目開發完成後需要部署到服務器,因爲是前後端分離,所以前端的應用部署到nginx,後端的應用部署到自己對應的服務器,所以我們需要配置一下,把後端的服務器變成上游服務,nginx做反向代理服務器

反向代理:服務器根據客戶端的請求,從其關係的一組或多組後端服務器上獲取資源,然後將這些資源返回給客戶端。

由於上游服務器(後臺服務器)要處理非常複雜的邏輯,所以性能不怎麼樣,我們使用nginx作爲反向代理服務器後,可以將請求按照負載均衡算法代理給多臺上游服務器。配置如下:

image-20190311112112788

以上配置是將所有的請求轉發給上游服務器,但如果我們只想將動態請求轉發給上游服務器,靜態資源由nginx自己處理,就可以這樣做:

判斷是否是後臺api(根據location的匹配規則),如果是的話,就進行轉發

匹配規則看這裏:https://stackoverflow.com/que...

upstream local{
    server 127.0.0.1:22222;  #假設在本地部署
}
server{
    listen:80;
    server_name localhost;
    location ~ /api/ {  #以`/api/`開頭的uri就行轉發,否則不轉發 ~代表正則表達式匹配
        proxy_set_header: Host $host;
        proxy_set_header: X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://local;
    }    
    location / {
        #... alias index等配置 
          
    }
}

這裏需要注意一個問題:proxy_pass是轉發請求的模塊,當你訪問localhost:80/api/users/login時,會被轉發到local的地址,即127.0.0.1:22222/api/users/login,所以開發環境下訪問後臺接口的URI要寫你部署到nginx的URI,而不是真正的後臺地址(因爲被轉發了)

前端配置

//apis/api.config.js
//判斷是否是生產環境
var isPro = process.env.NODE_ENV=== 'production'
module.exports = {
    baseUrl: isPro ? 'http://localhost:80' : '/apis'//生產環境下的baseURl是nginx的hoost:port
}

2. 持續部署

項目做完需要發佈到服務器,但每次手動打包,然後ftp傳上去的話就太麻煩了,所以我們的需求是:git或者svn提交後,自動打包發佈到服務器。使用的工具是jenkins.

參考文檔:https://juejin.im/post/5ad198...

官網:https://jenkins.io/

jenkins安裝與啓動

jenkins一般情況下會裝在服務器,但如果是同一個局域網的話,裝在本機也可以

linux:

  1. https://blog.csdn.net/fenglai...
  2. https://www.jianshu.com/p/8a7... (centos)
  3. 配置文件地址 /etc/sysconfig/jenkins
  4. 工作空間 /var/lib/jenkins

windows下:

  1. 從Jenkins官網下載最新war文件。
  2. 運行java -jar jenkins.war即可。

mac:

  1. 從官網下載pkg文件
  2. 雙擊安裝,安裝之後自己就會啓動

jenkins初始化

  1. jenkins的默認端口是8080,啓動成功後在瀏覽器打開。
  2. 進入後會讓我們輸管理員密碼,打開網頁上提示路徑下的文件,複製密碼粘貼輸入即可。
  3. 然後會讓安裝需要的插件,此處選默認即可,等待安裝完成。
  4. 創建一個管理員賬戶。
  5. 上面都完成後會看到這個界面。

image-20190314171915326

創建任務

在主頁上點擊創建

image-20190314172049214

直接點保存,然後去安裝插件

image-20190314172224487

安裝插件

首先返回主頁,然後點擊左側菜單 系統管理->插件管理

image-20190315091230194

需要安裝的插件有:

  • Generic Webhook Trigger 實現git提交觸發更新功能
  • Publish Over SSH 實現服務器部署功能
  • nvm wrapper 引入node

安裝插件的方式:

image-20190315091837845

安裝完插件之後重啓一下jenkins(安裝完插件後,有個重啓的選項,勾選即可)

實現git鉤子功能

當我們向github/碼雲等遠程倉庫push我們的代碼時,jenkins能知道我們提交了代碼,這是自動構建自動部署的前提,鉤子的實現原理是在遠端倉庫上配置一個Jenkins服務器的接口地址,當本地向遠端倉庫發起push時,遠端倉庫會向配置的Jenkins服務器的接口地址發起一個帶參數的請求,jenkins收到後開始工作

打開創建的項目(進入工程->點擊配置)

image-20190315092840660

構建觸發器

勾選 Generic Webhook Trigger

image-20190315094851281

github倉庫配置鉤子:

進入github項目中該項目頁面,點擊setting->webhooks,添加payload URL,

URL格式爲 http://<User ID>:<API Token>@<Jenkins IP地址>:端口/generic-webhook-trigger/invoke userid和api token在jenkins的系統管理-管理用戶-選擇你的用戶點進去-左側設置

image-20190315095453871

實現自動化構建

自動化構建:jenkins實現安裝依賴,打包(npm install && npm run build),此外還可以執行一些測試行爲

點擊構建環境,勾選nvm,輸入node版本

image-20190315103055020

點擊構建,選擇執行shell,輸入執行命令,多個命令使用&&分開

npm config set registry http://registry.npm.taobao.org/ &&
npm install && 
npm run build
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章