Vue CLI3從腳手架到毛坯房

Vue CLI 是一個基于 Vue.js 進行快速開發(fā)的完整系統(tǒng)帆赢,是個初始的vue腳手架茶敏,有關(guān)Vue CLI的更多信息壤靶,可以參考vue_cli官網(wǎng)

但是僅僅只有vue_cli腳手架的話還是不夠的,于是乎就需要給vue_cli添磚添瓦惊搏,大概需要添加的磚瓦大體可以考慮以下方面

  • GZIP
  • 打包自動添加版本號
  • vuex模塊化(與持久化)
  • 路由導(dǎo)航守衛(wèi)
  • rem
  • 請求的封裝與配置
  • loading

接下來我們一步一步贮乳,把毛坯房給建出來
首先是GZIP,為什么我把這個放在了首位呢恬惯,因為它真的很重要向拆,盡量減少文件的大小,提升響應(yīng)速度酪耳,強烈推薦配置GZIP

GZIP

我們需要用到的插件compression-webpack-plugin
npm install compression-webpack-plugin --save-dev
然后在你的vue.config.js中進行g(shù)zip的配置浓恳,在打正式環(huán)境包的時候開啟gzip

// vue.config.js
const IS_PROD = ['production'].includes(process.env.NODE_ENV) // 是否是生產(chǎn)環(huán)境
const CompressionWebpackPlugin = require('compression-webpack-plugin') // 引入compression-webpack-plugin
const productionGzipExtensions = ['js', 'css'] // 需要gzip的文件
module.exports = {
  configureWebpack: config => {
    if (IS_PROD) {
      config.plugins.push(new CompressionWebpackPlugin({
        algorithm: 'gzip',
        test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
        threshold: 10240,
        minRatio: 0.8
      }))
    }
  }
}

配置好了,打個包看看效果,css和js超過配置的大小就會生成一份gzip文件碗暗,大小減少了很多


gzip.png

前端配置好了GZIP颈将,就需要服務(wù)端配合了,服務(wù)器開啟GZIP,以nginx為例

在nginx.config配置文件中

gzip  on;
gzip_types text/plain application/x-javascript application/javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;

nginx -s reload重啟nginx看看效果言疗,這個時候請求的時候的就默認先讀取gzip文件

打包自動添加版本號

這個功能的話晴圾,還是有必要加上的,防止瀏覽器緩存洲守,一般防止js緩存的話很多項目都會做疑务,我接下來配置js和css的打包自動添加版本號
css和js的話,處理起來是用的不同方法梗醇,這里的版本號我取的是當(dāng)前的時間戳
npm install mini-css-extract-plugin --save-dev

// vue.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin') // css增加版本號需要的插件
const Timestamp = new Date().getTime()
module.exports = {
  configureWebpack: config => {
    // js 文件打包生產(chǎn)版本號知允,防止瀏覽器緩存
    config.output.filename = `js/[name].[hash:6].${Timestamp}.js`
    config.output.chunkFilename = `js/[name].[hash:6].${Timestamp}.js`
    config.plugins.push(new MiniCssExtractPlugin({
      filename: `css/[name].[hash:6].${Timestamp}.css`,
      chunkFilename: `css/[name].[hash:6].${Timestamp}.css`
    }))
  }
}
vuex模塊化與持久化

vuex模塊化我之前有過單獨的一篇進行介紹,可以參考vuex模塊化叙谨,持久化的功能是看情況下的温鸽,當(dāng)你的業(yè)務(wù)場景有刷新,并且不想直接使用localStorage的時候,可以配置下vuex的持久化
npm i -S vuex-persistedstate
vuex-persistedstate會同步vuex狀態(tài)到本地存儲localStorage涤垫、 sessionStorage或者cookie姑尺。

vuex持久化.png

這樣是默認使用localStorage來同步數(shù)據(jù),也可以去vuex-persistedstate
查看其他配置選項

rem

rem的話蝠猬,直接上代碼切蟋,在src目錄下新建一個util文件夾,在util下新建rem.js

//rem.js
// 設(shè)置 rem 函數(shù)
function setRem() {
  // 320 默認大小16px; 320px = 20rem ;每個元素px基礎(chǔ)上/16
  const htmlWidth = document.documentElement.clientWidth || document.body.clientWidth
  // 得到html的Dom元素
  const htmlDom = document.getElementsByTagName('html')[0]
  // 設(shè)置根元素字體大小
  htmlDom.style.fontSize = htmlWidth / 20 + 'px'
}
// 初始化
setRem()
// 改變窗口大小時重新設(shè)置 rem
window.onresize = function() {
  setRem()
}

// main.js
import './util/rem' // 引入rem

借助插件postcss-pxtorem來自動換算rem

module.exports = {
  css: {
    // 是否開啟支持 foo.module.css 樣式
    requireModuleExtension: true,
    // css預(yù)設(shè)器配置項
    loaderOptions: {
      css: {
        // options here will be passed to css-loader
      },
      postcss: {
        // options here will be passed to postcss-loader
        plugins: [
          require('postcss-pxtorem')({
            rootValue: 18.75, // 換算的基數(shù)
            propList: ['*']
          })
        ]
      }
    }
  }
}
請求的封裝與配置

在各個環(huán)境的環(huán)境變量文件中榆芦,有

VUE_APP_BASE_API = '/api'
VUE_APP_BASE_URL = '服務(wù)器地址'

配置跨域代理process.env.環(huán)境變量名可以拿到該環(huán)境變量

// vue.config.js
module.exports = {
 devServer: {
    proxy: {
      [process.env.VUE_APP_BASE_API]: {
        target: process.env.VUE_APP_BASE_URL,
        pathRewrite: { // 重寫路徑: 去掉路徑中開頭的'/api'
          '^/api': ''
        },
        changeOrigin: true
      }
    }
  }
}

這里的VUE_APP_BASE_API = '/api'柄粹,/api是我們使用的統(tǒng)一前綴,服務(wù)端微服務(wù)可能前綴有很多匆绣,所以推薦使用一個統(tǒng)一的接口前綴
在根目錄下新建一個config.js用來存放微服務(wù)的接口前綴

module.exports = {
  partner: '/mtourists-partner' // API接口前綴
}

接下來封裝request了驻右,在util文件夾下新建request.js文件

import axios from 'axios'
import { Toast } from 'vant'
const defaultToken = '06764f6f3f9098c31979ab6e6a837267'
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000
})
service.interceptors.request.use(
  config => {
    const localToken = localStorage.getItem('Token')
    const configToken = localToken || defaultToken
    // 請求頭中增加token
    config.headers['X-Authorization'] = `Bearer ${configToken}`
    return config
  },
  error => {
    return Promise.reject(error)
  }
)
service.interceptors.response.use(
  response => {
    // response的headers中返回token,存儲下來崎淳,放在本地
    const authorization = response.headers['x-authorization']
    if (authorization) {
      const token = authorization.replace(/Bearer\s/, '')
      const locToken = localStorage.getItem('Token')
      if (token && token !== locToken) {
        localStorage.setItem('Token', token)
      }
    }
    const res = response.data
    const code = 200
    if (res.code !== 20000 && code !== 200) {
      // handle error
    } else {
      return res
    }
  },
  error => {
    console.log('err' + error)
    // handle error
    return Promise.reject(error)
  }
)
export default service

在src下新建api文件夾堪夭,用來存放我們的請求,新建一個user.js,是我們user模塊的請求

import request from '../util/request'
const apiConfig = require('../../config')
// 登錄
export function userLogin(data) {
  return request({
    url: `${apiConfig.partner}/index/login`,
    method: 'post',
    data: data
  })
}
// 登出
export function userLoginOut() {
  return request({
    url: `${apiConfig.partner}/index/logout`,
    method: 'post'
  })
}

調(diào)用的時候先import引入

// login.vue
import { userLogin } from '../api/user'
//發(fā)起請求
userLogin(loginParams).then(res => {
  if (res.state === 1) {
    // 登錄成功
    this.$store.commit('RECEIVE_USER_INFO', res.data)
    this.$store.commit('IS_LOGIN', true)
    this.$router.replace('/home')
  } else {
    Toast.fail('賬號或密碼不正確')
  }
})
loading

loading的實現(xiàn)的話可以借助插件拣凹,這里我們手寫一個loading森爽,然后掛載在vuex上
啟動loading先解決,在index.html里加上loading咐鹤,然后在App.vue的mounted中隱藏loading

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>項目</title>
    <style>
      #loading {
        position: fixed;
        text-align: center;
        padding-top: 50%;
        width: 100%;
        height: 100%;
        z-index: 1000;
        background-color: #ffffff;
      }
    </style>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but pdd-partner doesn't work properly without JavaScript
        enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="loading">
      <img src="./loading.gif" alt="loading" />
    </div>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

// App.vue
mounted() {
  document.getElementById('loading').style.display = 'none'
}

這樣項目啟動loading就完成了
接下來是全局loading的配置了
新建一個Loading.vue

<template>
  <div class="loading">
    <img src="../../public/loading.gif" alt="loading">
  </div>
</template>

<script>
export default {
  name: 'Loading'
}
</script>

<style scoped>
.loading {
  position: fixed;
  text-align: center;
  padding-top: 50%;
  width: 100%;
  height: 100%;
  z-index: 1000;
  background-color: rgba(0,0,0,0.4)
}
</style>

在App.vue中引入

<template>
  <div id="app">
    <Loading v-show="loading" />
    <keep-alive>
      <router-view />
    </keep-alive>
  </div>
</template>
<script>
import Loading from './components/Loading.vue'
export default {
  name: 'App',
  components: {
    Loading
  },
  computed: {
    loading() {
      return this.$store.state.status.loading
    }
  },
  mounted() {
    document.getElementById('loading').style.display = 'none'
  }
}
</script>
<style>
</style>

在vuex中新建一個status模塊拗秘,用于存放全局狀態(tài)的,如loading之類的

// status.js
import * as types from '../mutation-types'
// initial state
const state = () => ({
  loading: false
})
// getters
const getters = {
  getLoading: store => store.loading
}
// mutations
const mutations = {
  [types.showLoading](store) {
    store.loading = true
  },
  [types.hideLoading](store) {
    store.loading = false
  }
}
// actions
const actions = {
  getLoading({ commit }) {}
}
export default {
  state,
  getters,
  actions,
  mutations
}

// mutation-types.js
// status狀態(tài)模塊
export const showLoading = 'showLoading'
export const hideLoading = 'hideLoading'

這樣

this.$store.commit('showLoading') // 顯示loading
this.$store.commit('hideLoading') // 隱藏loading
導(dǎo)航守衛(wèi)

為什么我把導(dǎo)航守衛(wèi)也當(dāng)做了一塊需要完善的磚瓦呢祈惶?因為我們做項目的話,很容易就遇到權(quán)限相關(guān)的需求扮匠,這個時候使用導(dǎo)航守衛(wèi)進行處理那肯定是很方便的捧请,vue-router 提供的導(dǎo)航守衛(wèi)主要用來通過跳轉(zhuǎn)或取消的方式守衛(wèi)導(dǎo)航。有多種機會植入路由導(dǎo)航過程中:全局的, 單個路由獨享的, 或者組件級的
注意點:參數(shù)或查詢的改變并不會觸發(fā)進入/離開的導(dǎo)航守衛(wèi)棒搜。你可以通過觀察 $route 對象來應(yīng)對這些變化疹蛉,或使用 beforeRouteUpdate 的組件內(nèi)守衛(wèi)。
我們來完成一個登陸權(quán)限的導(dǎo)航守衛(wèi)力麸,需求1:沒有登陸信息可款,訪問除登錄頁面的其他路由重定向登錄頁;需求2:有登錄信息訪問登錄頁的話重定向首頁

// router
const router = new VueRouter({
  mode: 'history',
  routes
})
router.beforeEach((to, from, next) => {
  if (to.name !== 'login') {
    if (
      router.app.$options.store.state.user.userInfo &&
      router.app.$options.store.state.user.islogin
    ) {
      // 有登錄狀態(tài)
      next()
    } else {
      next({ path: '/', replace: true })
    }
  } else {
    next()
  }
})
export default router

因為我使用的持久化vuex克蚂,用戶信息固化在localStorage里闺鲸,在router里面使用vuex,是router.app.$options.store
記住判斷用戶信息的話埃叭,一定要除去login路由摸恍,不然的話會導(dǎo)致棧溢出(想一想就明白了)
這樣我們就完成了需求1
我們再來看看需求2,需求2是單個路由獨享的導(dǎo)航守衛(wèi)

const routes = [
  // 登錄頁
  {
    path: '/',
    name: 'login',
    component: Login,
    beforeEnter: (to, from, next) => {
      if (router.app.$options.store.state.user.userInfo && router.app.$options.store.state.user.islogin) {
        next({ path: '/home', replace: true })
      } else {
        next()
      }
    }
  },
  // 首頁
  {
    path: '/home',
    name: 'Home',
    component: Home
  }
  { path: '*', redirect: '/' } // 所有未匹配到的路由,都跳轉(zhuǎn)登錄頁
]

到這里我們的需求1和2都算是簡單的實現(xiàn)了

當(dāng)然了再做項目的時候立镶,還有很多的配置項可以進行更改壁袄,大家也要靈活多變,合理配置媚媒,覺得有用的話幫忙點個贊吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嗜逻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缭召,更是在濱河造成了極大的恐慌栈顷,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恼琼,死亡現(xiàn)場離奇詭異妨蛹,居然都是意外死亡,警方通過查閱死者的電腦和手機晴竞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門蛙卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人噩死,你說我怎么就攤上這事颤难。” “怎么了已维?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵行嗤,是天一觀的道長。 經(jīng)常有香客問我垛耳,道長栅屏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任堂鲜,我火速辦了婚禮栈雳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缔莲。我一直安慰自己哥纫,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布痴奏。 她就那樣靜靜地躺著蛀骇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪读拆。 梳的紋絲不亂的頭發(fā)上擅憔,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音建椰,去河邊找鬼雕欺。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的屠列。 我是一名探鬼主播啦逆,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笛洛!你這毒婦竟也來了夏志?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤苛让,失蹤者是張志新(化名)和其女友劉穎沟蔑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狱杰,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡瘦材,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仿畸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片食棕。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖错沽,靈堂內(nèi)的尸體忽然破棺而出簿晓,到底是詐尸還是另有隱情,我是刑警寧澤千埃,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布憔儿,位于F島的核電站,受9級特大地震影響放可,放射性物質(zhì)發(fā)生泄漏谒臼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一耀里、第九天 我趴在偏房一處隱蔽的房頂上張望屋休。 院中可真熱鬧,春花似錦备韧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奶陈,卻和暖如春易阳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吃粒。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工潦俺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓事示,卻偏偏與公主長得像早像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肖爵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容