chore: optimize the code of async router; 🌟

This commit is contained in:
iczer 2020-07-28 20:48:02 +08:00
parent f5b452f82b
commit c9e4451974
19 changed files with 331 additions and 68 deletions

4
.env Normal file
View File

@ -0,0 +1,4 @@
VUE_APP_NAME=admin
VUE_APP_ROUTES_KEY=admin.routes
VUE_APP_PERMISSIONS_KEY=admin.permissions
VUE_APP_ROLES_KEY=admin.roles

4
.env.development Normal file
View File

@ -0,0 +1,4 @@
VUE_APP_NAME=admin
VUE_APP_ROUTES_KEY=admin.routes
VUE_APP_PERMISSIONS_KEY=admin.permissions
VUE_APP_ROLES_KEY=admin.roles

19
src/bootstrap.js vendored
View File

@ -1,23 +1,18 @@
// 应用启动时需要执行的操作放在这里 import {loadRoutes, loginGuard, authorityGuard} from '@/utils/routerUtil'
import {loadRoutes} from '@/utils/routerUtil'
/** /**
* 启动引导方法 * 启动引导方法
* 应用启动时需要执行的操作放在这里
* @param router 应用的路由实例 * @param router 应用的路由实例
* @param store 应用的 vuex.store 实例 * @param store 应用的 vuex.store 实例
* @param i18n 应用的 vue-i18n 实例 * @param i18n 应用的 vue-i18n 实例
*/ */
function bootstrap({router, store, i18n}) { function bootstrap({router, store, i18n}) {
// 加载本地存储的异步路由 // 加载路由
const localRoutes = localStorage.getItem('routes') loadRoutes({router, store, i18n})
if (localRoutes) { // 添加路由守卫
try { loginGuard(router)
const routesConfig = JSON.parse(localRoutes) authorityGuard(router, store)
loadRoutes(routesConfig, router, store, i18n)
} catch (e) {
console.error(e.message)
}
}
} }
export default bootstrap export default bootstrap

View File

@ -7,7 +7,7 @@
<h1>{{config[type].title}}</h1> <h1>{{config[type].title}}</h1>
<div class="desc">{{config[type].desc}}</div> <div class="desc">{{config[type].desc}}</div>
<div class="action"> <div class="action">
<a-button type="primary" >返回首页</a-button> <a-button type="primary" @click="backHome">返回首页</a-button>
</div> </div>
</div> </div>
</div> </div>
@ -18,11 +18,19 @@ import Config from './typeConfig'
export default { export default {
name: 'ExceptionPage', name: 'ExceptionPage',
props: ['type'], props: ['type', 'homeRoute'],
data () { data () {
return { return {
config: Config config: Config
} }
},
methods: {
backHome() {
if (this.homeRoute) {
this.$router.push(this.homeRoute)
}
this.$emit('backHome', this.type)
}
} }
} }
</script> </script>

View File

@ -0,0 +1,2 @@
import TabsView from './TabsView'
export default TabsView

View File

@ -22,6 +22,8 @@ Mock.mock('/login', 'post', ({body}) => {
result.data.user = user result.data.user = user
result.data.token = 'Authorization:' + Math.random() result.data.token = 'Authorization:' + Math.random()
result.data.expireAt = new Date(new Date().getTime() + 30 * 60 * 1000) result.data.expireAt = new Date(new Date().getTime() + 30 * 60 * 1000)
result.data.permissions = [{id: 'demo', extra: ['add', 'edit', 'delete']}]
result.data.roles = [{id: 'admin', extra: ['add', 'edit', 'delete']}]
} }
return result return result
}) })

View File

@ -8,11 +8,21 @@ Mock.mock('/routes', 'get', () => {
children: ['demo', children: ['demo',
{ {
router: 'parent1', router: 'parent1',
children: ['demo'], children: [{
router: 'demo',
name: 'demo1',
authority: {
permission: 'demo',
role: 'admin'
}
}],
}, },
{ {
router: 'parent2', router: 'parent2',
children: ['demo'], children: [{
router: 'demo',
name: 'demo2'
}],
}, },
{ {
router: 'exception', router: 'exception',

View File

@ -1,5 +1,5 @@
<template> <template>
<exception-page :style="`min-height: ${pageMinHeight}px`" type="403" /> <exception-page home-route="/demo" :style="`min-height: ${minHeight}`" type="403" />
</template> </template>
<script> <script>
@ -9,7 +9,10 @@ export default {
name: 'Exp403', name: 'Exp403',
components: {ExceptionPage}, components: {ExceptionPage},
computed: { computed: {
...mapState('setting', ['pageMinHeight']) ...mapState('setting', ['pageMinHeight']),
minHeight() {
return this.pageMinHeight ? this.pageMinHeight + 'px' : '100vh'
}
} }
} }
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<exception-page :style="`min-height: ${minHeight}`" type="404" /> <exception-page home-route="/demo" :style="`min-height: ${minHeight}`" type="404" />
</template> </template>
<script> <script>

View File

@ -1,5 +1,5 @@
<template> <template>
<exception-page :style="`min-height: ${pageMinHeight}px`" type="500" /> <exception-page home-route="/demo" :style="`min-height: ${minHeight}`" type="500" />
</template> </template>
<script> <script>
@ -9,7 +9,10 @@ export default {
name: 'Exp500', name: 'Exp500',
components: {ExceptionPage}, components: {ExceptionPage},
computed: { computed: {
...mapState('setting', ['pageMinHeight']) ...mapState('setting', ['pageMinHeight']),
minHeight() {
return this.pageMinHeight ? this.pageMinHeight + 'px' : '100vh'
}
} }
} }
</script> </script>

View File

@ -78,6 +78,7 @@ import CommonLayout from '@/layouts/CommonLayout'
import {login, getRoutesConfig} from '@/services' import {login, getRoutesConfig} from '@/services'
import {setAuthorization} from '@/utils/request' import {setAuthorization} from '@/utils/request'
import {loadRoutes} from '@/utils/routerUtil' import {loadRoutes} from '@/utils/routerUtil'
import {mapMutations} from 'vuex'
export default { export default {
name: 'Login', name: 'Login',
@ -95,6 +96,7 @@ export default {
} }
}, },
methods: { methods: {
...mapMutations('account', ['setUser', 'setPermissions', 'setRoles']),
onSubmit (e) { onSubmit (e) {
e.preventDefault() e.preventDefault()
this.form.validateFields((err) => { this.form.validateFields((err) => {
@ -111,14 +113,17 @@ export default {
const loginRes = res.data const loginRes = res.data
if (loginRes.code >= 0) { if (loginRes.code >= 0) {
const user = loginRes.data.user const user = loginRes.data.user
const permissions = loginRes.data.permissions
const roles = loginRes.data.roles
this.setUser(user)
this.setPermissions(permissions)
this.setRoles(roles)
setAuthorization({token: loginRes.data.token, expireAt: new Date(loginRes.data.expireAt)}) setAuthorization({token: loginRes.data.token, expireAt: new Date(loginRes.data.expireAt)})
// //
getRoutesConfig().then(result => { getRoutesConfig().then(result => {
const routesConfig = result.data.data const routesConfig = result.data.data
localStorage.setItem('routes', JSON.stringify(routesConfig)) loadRoutes({router: this.$router, store: this.$store, i18n: this.$i18n}, routesConfig)
loadRoutes(routesConfig, this.$router, this.$store, this.$i18n) this.$router.push('/demo')
this.$router.push('/parent1/demo')
this.$store.commit('account/setUser', user)
this.$message.success(loginRes.message, 3) this.$message.success(loginRes.message, 3)
}) })
} else { } else {

2
src/pages/login/index.js Normal file
View File

@ -0,0 +1,2 @@
import Login from './Login'
export default Login

View File

@ -0,0 +1,38 @@
import routerMap from './router.map'
import {parseRoutes} from '@/utils/routerUtil'
// 异步路由配置
const routesConfig = [
'login',
'root',
{
router: 'exp404',
path: '*',
name: '404'
},
{
router: 'exp403',
path: '/403',
name: '403'
}
]
const options = {
routes: parseRoutes(routesConfig, routerMap)
}
// 不需要登录拦截的路由配置
const loginIgnore = {
names: ['404'], //根据路由名称匹配
paths: ['/login'], //根据路由fullPath匹配
/**
* 判断路由是否包含在该配置中
* @param route vue-router route 对象
* @returns {boolean}
*/
includes(route) {
return this.names.includes(route.name) || this.paths.includes(route.path)
}
}
export {options, loginIgnore}

View File

@ -1,19 +1,93 @@
import routerMap from './router.map' import Login from '@/pages/login/Login'
import {parseRoutes} from '@/utils/routerUtil' import TabsView from '@/layouts/tabs/TabsView'
import BlankView from '@/layouts/BlankView'
import PageView from '@/layouts/PageView'
// 路由配置 // 路由配置
const routesConfig = [ const options = {
'login', routes: [
'root', {
path: '/login',
name: '登录页',
component: Login
},
{ {
router: 'exp404',
path: '*', path: '*',
name: '404' name: '404',
component: () => import('@/pages/exception/404'),
},
{
path: '/',
name: '首页',
component: TabsView,
redirect: '/login',
children: [
{
path: 'demo',
name: '演示页0',
meta: {
icon: 'file-ppt'
},
component: () => import('@/pages/demo')
},
{
path: 'parent1',
name: '父级路由1',
meta: {
icon: 'dashboard'
},
component: BlankView,
children: [
{
path: 'demo1',
name: '演示页面1',
component: () => import('@/pages/demo'),
}
]
},
{
path: 'parent2',
name: '父级路由2',
meta: {
icon: 'form'
},
component: PageView,
children: [
{
path: 'demo2',
name: '演示页面2',
component: () => import('@/pages/demo'),
}
]
},
{
path: 'exception',
name: '异常页',
meta: {
icon: 'warning',
},
component: BlankView,
children: [
{
path: '404',
name: 'Exp404',
component: () => import('@/pages/exception/404')
},
{
path: '403',
name: 'Exp403',
component: () => import('@/pages/exception/403')
},
{
path: '500',
name: 'Exp500',
component: () => import('@/pages/exception/500')
}
]
},
]
} }
] ]
const options = {
routes: parseRoutes(routesConfig, routerMap)
} }
// 不需要登录拦截的路由配置 // 不需要登录拦截的路由配置

View File

@ -1,19 +1,10 @@
import Vue from 'vue' import Vue from 'vue'
import Router from 'vue-router' import Router from 'vue-router'
import {checkAuthorization} from '@/utils/request' // import {options, loginIgnore} from './config' //本地路由配置
import {options, loginIgnore} from './config' import {options, loginIgnore} from './config.async' //异步路由配置
Vue.use(Router) Vue.use(Router)
const router = new Router({...options})
const router = new Router(options) export {loginIgnore}
// 登录拦截
router.beforeEach((to, from, next) => {
if (!loginIgnore.includes(to) && !checkAuthorization()) {
next({path: '/login'})
} else {
next()
}
})
export default router export default router

View File

@ -13,25 +13,22 @@ const routerMap = {
component: () => import('@/pages/login') component: () => import('@/pages/login')
}, },
demo: { demo: {
authority: 'admin',
name: '演示页', name: '演示页',
renderMenu: false, renderMenu: false,
component: () => import('@/pages/demo') component: () => import('@/pages/demo')
}, },
exp403: { exp403: {
authority: 'admin', authority: '*',
name: 'exp403', name: 'exp403',
path: '403', path: '403',
component: () => import('@/pages/exception/403') component: () => import('@/pages/exception/403')
}, },
exp404: { exp404: {
authority: '*',
name: 'exp404', name: 'exp404',
path: '404', path: '404',
component: () => import('@/pages/exception/404') component: () => import('@/pages/exception/404')
}, },
exp500: { exp500: {
authority: 'admin',
name: 'exp500', name: 'exp500',
path: '500', path: '500',
component: () => import('@/pages/exception/500') component: () => import('@/pages/exception/500')

View File

@ -22,7 +22,9 @@ async function getRoutesConfig() {
* 退出登录 * 退出登录
*/ */
function logout() { function logout() {
localStorage.removeItem('routes') localStorage.removeItem(process.env.VUE_APP_ROUTES_KEY)
localStorage.removeItem(process.env.VUE_APP_PERMISSIONS_KEY)
localStorage.removeItem(process.env.VUE_APP_ROLES_KEY)
removeAuthorization() removeAuthorization()
} }

View File

@ -10,6 +10,44 @@ export default {
avatar: '', avatar: '',
position: '', position: '',
address: '' address: ''
},
permissions: [],
roles: [],
routesConfig: []
},
getters: {
permissions: state => {
if (!state.permissions || state.permissions.length === 0) {
try {
const permissions = localStorage.getItem(process.env.VUE_APP_PERMISSIONS_KEY)
state.permissions = eval(permissions) ? JSON.parse(permissions) : state.permissions
} catch (e) {
console.error(e.message)
}
}
return state.permissions
},
roles: state => {
if (!state.roles || state.roles.length === 0) {
try {
const roles = localStorage.getItem(process.env.VUE_APP_ROLES_KEY)
state.roles = eval(roles) ? JSON.parse(roles) : state.roles
} catch (e) {
console.error(e.message)
}
}
return state.roles
},
routesConfig: state => {
if (!state.routesConfig || state.routesConfig.length === 0) {
try {
const routesConfig = localStorage.getItem(process.env.VUE_APP_ROUTES_KEY)
state.routesConfig = eval(routesConfig) ? JSON.parse(routesConfig) : state.routesConfig
} catch (e) {
console.error(e.message)
}
}
return state.routesConfig
} }
}, },
mutations: { mutations: {
@ -31,6 +69,18 @@ export default {
throw e throw e
} }
}) })
},
setPermissions(state, permissions) {
state.permissions = permissions
localStorage.setItem(process.env.VUE_APP_PERMISSIONS_KEY, JSON.stringify(permissions))
},
setRoles(state, roles) {
state.roles = roles
localStorage.setItem(process.env.VUE_APP_ROLES_KEY, JSON.stringify(roles))
},
setRoutesConfig(state, routesConfig) {
state.routesConfig = routesConfig
localStorage.setItem(process.env.VUE_APP_ROUTES_KEY, JSON.stringify(routesConfig))
} }
} }
} }

View File

@ -1,6 +1,8 @@
import routerMap from '@/router/router.map' import routerMap from '@/router/router.map'
import {mergeI18nFromRoutes} from '@/utils/i18n' import {mergeI18nFromRoutes} from '@/utils/i18n'
import Router from 'vue-router' import Router from 'vue-router'
import {loginIgnore} from '@/router'
import {checkAuthorization} from '@/utils/request'
/** /**
* 根据 路由配置 路由组件注册 解析路由 * 根据 路由配置 路由组件注册 解析路由
@ -10,7 +12,7 @@ import Router from 'vue-router'
function parseRoutes(routesConfig, routerMap) { function parseRoutes(routesConfig, routerMap) {
let routes = [] let routes = []
routesConfig.forEach(item => { routesConfig.forEach(item => {
// 读取 router初始化 routeCfg // 获取注册在 routerMap 中的 router初始化 routeCfg
let router = undefined, routeCfg = {} let router = undefined, routeCfg = {}
if (typeof item === 'string') { if (typeof item === 'string') {
router = routerMap[item] router = routerMap[item]
@ -19,7 +21,7 @@ function parseRoutes(routesConfig, routerMap) {
router = routerMap[item.router] router = routerMap[item.router]
routeCfg = item routeCfg = item
} }
// 从 register 和 routeCfg 解析路由 // 从 router 和 routeCfg 解析路由
if (!router) { if (!router) {
console.warn(`can't find register for router ${routeCfg.router}, please register it in advance.`) console.warn(`can't find register for router ${routeCfg.router}, please register it in advance.`)
} else { } else {
@ -45,11 +47,19 @@ function parseRoutes(routesConfig, routerMap) {
/** /**
* 加载路由 * 加载路由
* @param router 应用路由实例
* @param store 应用的 vuex.store 实例
* @param i18n 应用的 vue-i18n 实例
* @param routesConfig * @param routesConfig
* @param router
* @param store
*/ */
function loadRoutes(routesConfig, router, store, i18n) { function loadRoutes({router, store, i18n}, routesConfig) {
// 如果 routesConfig 有值则更新到本地localStorage否则从本地localStorage获取
if (routesConfig) {
store.commit('account/setRoutesConfig', routesConfig)
} else {
routesConfig = store.getters['account/routesConfig']
}
if (routesConfig && routesConfig.length > 0) {
const routes = parseRoutes(routesConfig, routerMap) const routes = parseRoutes(routesConfig, routerMap)
router.matcher = new Router(router.options).matcher router.matcher = new Router(router.options).matcher
router.addRoutes(routes) router.addRoutes(routes)
@ -57,5 +67,68 @@ function loadRoutes(routesConfig, router, store, i18n) {
mergeI18nFromRoutes(i18n, menuRoutes) mergeI18nFromRoutes(i18n, menuRoutes)
store.commit('setting/setMenuData', menuRoutes) store.commit('setting/setMenuData', menuRoutes)
} }
}
export {parseRoutes, loadRoutes} /**
* 登录守卫
* @param router 应用路由实例
*/
function loginGuard(router) {
router.beforeEach((to, from, next) => {
if (!loginIgnore.includes(to) && !checkAuthorization()) {
next({path: '/login'})
} else {
next()
}
})
}
/**
* 权限守卫
* @param router 应用路由实例
* @param store 应用的 vuex.store 实例
*/
function authorityGuard(router, store) {
router.beforeEach((to, form, next) => {
const permissions = store.getters['account/permissions']
const roles = store.getters['account/roles']
if (!hasPermission(to, permissions) && !hasRole(to, roles)) {
next({path: '/403'})
} else {
next()
}
})
}
/**
* 判断是否有路由的权限
* @param route 路由
* @param permissions 用户权限集合
* @returns {boolean|*}
*/
function hasPermission(route, permissions) {
const authority = route.meta.authority || '*'
let required = '*'
if (typeof authority === 'string') {
required = authority
} else if (typeof authority === 'object') {
required = authority.permission
}
return required === '*' || (permissions && permissions.findIndex(item => item === required || item.id === required) !== -1)
}
/**
* 判断是否有路由需要的角色
* @param route 路由
* @param roles 用户角色集合
*/
function hasRole(route, roles) {
const authority = route.meta.authority || '*'
let required = undefined
if (typeof authority === 'object') {
required = authority.role
}
return authority === '*' || (required && roles.findIndex(item => item === required || item.id === required) !== -1)
}
export {parseRoutes, loadRoutes, loginGuard, authorityGuard}