V4.0與之前版本的差異
在vue-element-admin V4.0之前的版本,根目錄下存在2個文件夾:
- build文件夾包含構建相關的build及webpack等文件。
- config文件夾包含各種環境下的配置。
通常地,各種基礎設置都能在這2個文件夾下的配置文件中完成。
然而,V4.0版本變化較大:
- build文件夾保留,但其下只剩了一個index.js文件。
- config文件夾去掉。
- 根目錄下多了2種重要文件:.env.xxx,vue.config.js。
其中,.env.xxx爲開發/生產環境的配置文件,vue.config.js爲整個工程的全局配置文件。
修改服務端地址
要修改服務端地址,打開.env.development,這是開發環境的配置文件。其下有個配置是:
VUE_APP_BASE_API = '/dev-api'
將該處地址修改爲正確的服務端地址:
VUE_APP_BASE_API = 'http://192.168.15.98:9098/template'
即可。
服務端地址前被附加http://localhost:9527/
有時,按上述更改設置後請求,會發現在路徑前附加了http://localhost:9527/
,類似:
http://localhost:9527/192.168.15.98:9098/template/test
曾經一度排查了很久,將Mock屏蔽也不起作用。有兩種解決方法:
- 修改vue.config.js中的devServer,配置proxy指向服務端。
- 從github上重新下載最新的vue-element-admin。
然而在採用這兩種方法的同時也修改了其他的設置,且上述問題後續沒有再遇到,故而個人不太確定是否是這兩種解決方案起作用了。
同時,不知道是否與運行環境爲VS Code有關。
啓動工程測試時會打開2個頁面
每次運行npm run dev
,會啓動默認瀏覽器並打開2個頁面。
解決方法爲:
- 打開vue.config.js,其中
devServer.open
默認爲true
,更改爲false
。 - 打開package.json,其中
scripts.dev
默認爲vue-cli-service serve
,將其修改爲:vue-cli-service serve --open
。
自定義token
接收token並保存
打開 /src/store/modules/user.js,其中actions
下的login()
爲調用用戶自定義登錄接口並進行回調處理。
在這裏獲取到的response即爲服務端返回的數據。從中即可獲取token值。
在下方會調用commit()
和setToken()
來將這個token值保存起來。
將token附加到請求的header
打開 /src/utils/request.js,有這樣一段代碼:
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
其中config.headers['X-Token'] = getToken()
這行代碼的作用就是取出token值並附加到header的X-Token
屬性中。若要更改屬性名,或者在token值前附加特定的字符串,可以在這裏進行修改。
另外,若需要對特定的返回code做攔截,也是在該文件中進行。
動態路由
vue-element-admin的動態路由方案是將所有路由都在web端寫好,然後根據服務端返回的roles來決定顯示哪些。
實際情況往往需要將路由存儲在服務端,web端請求到數據後動態生成路由。
思路
vue-element-admin的動態路由流程爲:
- 用戶輸入用戶名和密碼後點擊登錄,調用
login()
接口獲取token並緩存。
其代碼位於 /store/user.js :
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
- 調用
getInfo()
接口獲取用戶信息並緩存,其中包含roles屬性。
其代碼位於 /store/user.js :
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
reject('Verification failed, please Login again.')
}
const { roles, name, avatar, introduction } = data
// roles must be a non-empty array
if (!roles || roles.length <= 0) {
reject('getInfo: roles must be a non-null array!')
}
commit('SET_ROLES', roles)
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit('SET_INTRODUCTION', introduction)
resolve(data)
}).catch(error => {
reject(error)
})
})
}
- 根據roles屬性對本地所有的路由進行過濾,只保留允許roles查看的路由,掛到
router
中。
其代碼位於 /src/permission.js :
try {
// get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
const { roles } = await store.dispatch('user/getInfo')
// generate accessible routes map based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
可以看到相關邏輯位於try
塊中。
根據此流程,可知要滿足需求,需要更改其中的2和3兩步。設登錄的賬戶都有個roleId屬性,可通過請求服務端相關接口並傳入roleId來獲取一個路由數據數組:
- 用戶輸入用戶名和密碼後點擊登錄,調用
login()
接口獲取token並緩存。 - 調用
getInfo()
接口獲取用戶信息並緩存,其中包含roleId屬性。 - 調用服務端接口並傳入roleId,來獲取路由數據數組。
- 根據路由數據數組來動態生成路由並掛到
router
上。
其中第3和第4步的邏輯應在 /src/permission.js 的try
中實現。提供一段參考代碼:
try {
// get user info
const { roleId } = await store.dispatch('user/getInfo')
// 根據roleId從服務端獲取addRoutes
const addRoutes = await store.dispatch('myRoute/generateRoutes', { roleId })
// 必須設置router.options.routes後,router.addRoutes纔會生效
router.options.routes = constantRoutes.concat(addRoutes)
// 動態添加可訪問路由表。注意已有的靜態路由不要設置,否則會提示重複
router.addRoutes(addRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || '驗證失敗,請重新登錄')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
這裏在 /src/modules/ 文件夾下自定義了myRoute.js負責路由數據的請求及獲取到數據後路由結構的生成,並將myRoute
設置到 /store/getter.js 和 /store/index.js 中。
404攔截
需要注意的一點是,需要將 /src/router/index.js 中constantRoutes
的最後兩個404路由單獨導出,加載動態路由的時候,作爲動態路由的一部分獨立掛到動態路由的最後。
// 404 頁面一定要最後加載,如果放在constantRouterMap一同聲明瞭404,後面的所以頁面都會被攔截到404
export const errorRouterMap = [
{ path: '/404', component: () => import('@/views/404'), hidden: true },
{ path: '*', redirect: '/404', hidden: true }
]
也就是說,對於動態路由而言,其構成爲:
靜態路由部分 + 動態路由部分 + 2個404路由
靜態路由(constantRouterMap)即無論哪個賬號登錄都可訪問的路由,直接存儲在前端,通常至少有2個路由:登錄(/login
)和首頁(/
)。
動態路由即從服務端獲取到路由數據後,拼裝出的路由數組。
webpackEmptyContext:動態加載失敗
路由的結構官方文檔有詳細說明,一個簡單的路由結構爲:
{
path: '/icon',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/icons/index'),
name: 'Icons',
meta: { title: 'Icons', icon: 'icon', noCache: true }
}
]
}
其中非常重要的一個屬性是component
,其設置爲() => import('@/views/icons/index')
。
靜態路由這樣寫是沒問題的。然而,若動態路由需要在import()
中使用變量,明明組件文件都在,路由地址也對,但瀏覽器就會提示:
vue-router.esm.js:1921 Error: Cannot find module ‘@/views/icons/index’ at webpackEmptyContext
這是webpack導致的,不能在import()
中使用變量。解決方法爲將import()
更改爲require()
。即:
// 定義組件變量
const newComponent = 'icons/index'
// 無法運行的寫法
component: () => import('@/views/' + newComponent)
// 正常運行的寫法
component: resolve => require([`@/views/` + newComponent], resolve)
如上,即可解決。
命令與配置文件
新版本下的配置文件直接放在了根目錄下,默認有3個:
- .env.development
- .env.prodution
- .env.staging
而package.json中主要的運行和打包命令也有3個:
- “dev”: “vue-cli-service serve”
- “build:prod”: “vue-cli-service build”
- “build:stage”: “vue-cli-service build --mode staging”
這3個命令,分別對應上面的3個配置文件。
配置文件的格式統一爲:
.env. + 名稱
引用時,在命令後加:--mode 名稱
即可。
特殊地,若運行的是serve
命令且沒有加--mode 名稱
,則調用 .env.development ;若運行的是build
命令且沒有加--mode 名稱
,則調用 .env.prodution 。
即:
vue-cli-service serve
等同於vue-cli-service serve --mode development
vue-cli-service build
等同於vue-cli-service build --mode prodution
這樣可以針對不同的環境創建多個配置文件,然後運行對應的命令即可使用對應的配置來運行/打包,而不需要每次都修改配置文件。