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颈将,就需要服務(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姑尺。
這樣是默認使用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)然了再做項目的時候立镶,還有很多的配置項可以進行更改壁袄,大家也要靈活多變,合理配置媚媒,覺得有用的話幫忙點個贊吧