feat: add support for async router; 🌟

新增:添加异步路由支持;
This commit is contained in:
iczer 2020-07-26 22:08:31 +08:00
parent 657c061b89
commit f5b452f82b
17 changed files with 249 additions and 130 deletions

View File

@ -17,10 +17,9 @@ export default {
}
},
created () {
let _this = this
this.setLanguage(this.lang)
enquireScreen(isMobile => {
_this.$store.commit('setting/setDevice', isMobile)
this.$store.commit('setting/setDevice', isMobile)
})
},
mounted() {

23
src/bootstrap.js vendored Normal file
View File

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

View File

@ -35,8 +35,6 @@ import {mapState, mapMutations} from 'vuex'
const minHeight = window.innerHeight - 64 - 24 - 122
let menuData = []
export default {
name: 'AdminLayout',
components: {Setting, SideMenu, Drawer, PageFooter, AdminHeader},
@ -44,12 +42,12 @@ export default {
return {
minHeight: minHeight,
collapsed: false,
menuData: menuData,
showSetting: false
}
},
computed: {
...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar', 'hideSetting', 'pageMinHeight']),
...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar',
'hideSetting', 'menuData']),
sideMenuWidth() {
return this.collapsed ? '80px' : '256px'
},
@ -74,9 +72,6 @@ export default {
},
beforeDestroy() {
this.correctPageMinHeight(-minHeight + 1)
},
beforeCreate () {
menuData = this.$router.options.routes.find((item) => item.path === '/').children
}
}
</script>

View File

@ -9,14 +9,16 @@ import store from './store'
import 'animate.css/source/animate.css'
import Plugins from '@/plugins'
import {initI18n} from '@/utils/i18n'
import bootstrap from '@/bootstrap'
const i18n = initI18n(router, 'CN', 'US')
bootstrap({router, store, i18n})
Vue.config.productionTip = false
Vue.use(Viser)
Vue.use(Antd)
Vue.use(Plugins)
const i18n = initI18n(router, 'CN', 'US')
new Vue({
router,
store,

View File

@ -1,5 +1,6 @@
import Mock from 'mockjs'
import '@/mock/user/login'
import '@/mock/user/routes'
// 设置全局延时
Mock.setup({

24
src/mock/user/routes.js Normal file
View File

@ -0,0 +1,24 @@
import Mock from 'mockjs'
Mock.mock('/routes', 'get', () => {
let result = {}
result.code = 0
result.data = [{
router: 'root',
children: ['demo',
{
router: 'parent1',
children: ['demo'],
},
{
router: 'parent2',
children: ['demo'],
},
{
router: 'exception',
children: ['exp404', 'exp403', 'exp500'],
}
]
}]
return result
})

View File

@ -75,8 +75,9 @@
<script>
import CommonLayout from '@/layouts/CommonLayout'
import {login} from '@/services'
import {login, getRoutesConfig} from '@/services'
import {setAuthorization} from '@/utils/request'
import {loadRoutes} from '@/utils/routerUtil'
export default {
name: 'Login',
@ -107,15 +108,21 @@ export default {
},
afterLogin(res) {
this.logging = false
const result = res.data
if (result.code >= 0) {
const user = result.data.user
setAuthorization({token: result.data.token, expireAt: new Date(result.data.expireAt)})
this.$router.push('/parent1/demo1')
this.$store.commit('account/setUser', user)
this.$message.success(result.message, 3)
const loginRes = res.data
if (loginRes.code >= 0) {
const user = loginRes.data.user
setAuthorization({token: loginRes.data.token, expireAt: new Date(loginRes.data.expireAt)})
//
getRoutesConfig().then(result => {
const routesConfig = result.data.data
localStorage.setItem('routes', JSON.stringify(routesConfig))
loadRoutes(routesConfig, this.$router, this.$store, this.$i18n)
this.$router.push('/parent1/demo')
this.$store.commit('account/setUser', user)
this.$message.success(loginRes.message, 3)
})
} else {
this.error = result.message
this.error = loginRes.message
}
}
}

View File

@ -1,92 +1,19 @@
import Login from '@/pages/login/Login'
import TabsView from '@/layouts/tabs/TabsView'
import BlankView from '@/layouts/BlankView'
import PageView from '@/layouts/PageView'
import routerMap from './router.map'
import {parseRoutes} from '@/utils/routerUtil'
// 路由配置
const routesConfig = [
'login',
'root',
{
router: 'exp404',
path: '*',
name: '404'
}
]
const options = {
routes: [
{
path: '/login',
name: '登录页',
component: Login
},
{
path: '*',
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')
}
]
},
]
}
]
routes: parseRoutes(routesConfig, routerMap)
}
// 不需要登录拦截的路由配置
@ -103,4 +30,4 @@ const loginIgnore = {
}
}
export {options, loginIgnore}
export {options, loginIgnore}

View File

@ -9,15 +9,15 @@ module.exports = {
HK: {
home: {name: '首頁'},
demo: {
name: '演示頁0'
name: '演示頁'
},
parent1: {
name: '父級路由1',
demo1: {name: '演示頁面1'},
demo: {name: '演示頁面1'},
},
parent2: {
name: '父級路由2',
demo2: {name: '演示頁面2'},
demo: {name: '演示頁面2'},
},
exception: {
name: '異常頁',

62
src/router/router.map.js Normal file
View File

@ -0,0 +1,62 @@
// 视图组件
const view = {
tabs: () => import('@/layouts/tabs'),
blank: () => import('@/layouts/BlankView'),
page: () => import('@/layouts/PageView')
}
// 路由组件注册
const routerMap = {
login: {
authority: '*',
path: '/login',
component: () => import('@/pages/login')
},
demo: {
authority: 'admin',
name: '演示页',
renderMenu: false,
component: () => import('@/pages/demo')
},
exp403: {
authority: 'admin',
name: 'exp403',
path: '403',
component: () => import('@/pages/exception/403')
},
exp404: {
authority: '*',
name: 'exp404',
path: '404',
component: () => import('@/pages/exception/404')
},
exp500: {
authority: 'admin',
name: 'exp500',
path: '500',
component: () => import('@/pages/exception/500')
},
root: {
path: '/',
name: '首页',
redirect: '/login',
component: view.tabs
},
parent1: {
name: '父级路由1',
icon: 'dashboard',
component: view.blank
},
parent2: {
name: '父级路由2',
icon: 'form',
component: view.page
},
exception: {
name: '异常页',
icon: 'warning',
component: view.blank
}
}
export default routerMap

View File

@ -1,5 +1,6 @@
// const BASE_URL = 'http://localhost:8080' your service base url
const BASE_URL = '' // mock base url
module.exports = {
LOGIN: `${BASE_URL}/login`
LOGIN: `${BASE_URL}/login`,
ROUTES: `${BASE_URL}/routes`
}

View File

@ -1,6 +1,7 @@
import {login, logout} from './user'
import {login, logout, getRoutesConfig} from './user'
export {
login,
logout
logout,
getRoutesConfig
}

View File

@ -1,4 +1,4 @@
import {LOGIN} from '@/services/api'
import {LOGIN, ROUTES} from '@/services/api'
import {request, METHOD, removeAuthorization} from '@/utils/request'
/**
@ -14,11 +14,16 @@ async function login(name, password) {
})
}
async function getRoutesConfig() {
return request(ROUTES, METHOD.GET)
}
/**
* 退出登录
*/
function logout() {
localStorage.removeItem('routes')
removeAuthorization()
}
export {login, logout}
export {login, logout, getRoutesConfig}

View File

@ -12,5 +12,4 @@ const store = new Vuex.Store({modules})
db.get('currUser')
.then(doc => store.commit('account/setUser', doc.user))
.catch(() => {})
export default store

View File

@ -8,6 +8,7 @@ export default {
palettes: ADMIN.palettes,
dustbins: [],
pageMinHeight: 0,
menuData: [],
...config,
},
mutations: {
@ -46,6 +47,9 @@ export default {
},
correctPageMinHeight(state, minHeight) {
state.pageMinHeight += minHeight
},
setMenuData(state, menuData) {
state.menuData = menuData
}
}
}

View File

@ -12,20 +12,19 @@ import './Objects'
*/
function initI18n(router, locale, fallback) {
Vue.use(VueI18n)
const options = router.options.routes.find(item => item.path === '/').children
formatOptions(options, '')
const CN = generateI18n(new Object(), options, 'name')
const US = generateI18n(new Object(), options, 'path')
const i18n = new VueI18n({
const rootRoute = router.options.routes.find(item => item.path === '/')
const menuRoutes = rootRoute && rootRoute.children
let i18nOptions = {
locale,
fallbackLocale: fallback,
silentFallbackWarn: true,
messages: {CN, US}
})
const messages = routesI18n.messages
Object.keys(messages).forEach(key => {
i18n.mergeLocaleMessage(key, messages[key])
})
messages: routesI18n.messages
}
const i18n = new VueI18n(i18nOptions)
if (menuRoutes) {
mergeI18nFromRoutes(i18n, menuRoutes)
}
return i18n
}
@ -62,6 +61,15 @@ function formatOptions(options, parentPath) {
})
}
function mergeI18nFromRoutes(i18n, routes) {
formatOptions(routes, '')
const CN = generateI18n(new Object(), routes, 'name')
const US = generateI18n(new Object(), routes, 'path')
i18n.mergeLocaleMessage('CN', CN)
i18n.mergeLocaleMessage('US', US)
}
export {
initI18n
}
initI18n,
mergeI18nFromRoutes
}

61
src/utils/routerUtil.js Normal file
View File

@ -0,0 +1,61 @@
import routerMap from '@/router/router.map'
import {mergeI18nFromRoutes} from '@/utils/i18n'
import Router from 'vue-router'
/**
* 根据 路由配置 路由组件注册 解析路由
* @param routesConfig 路由配置
* @param routerMap 本地路由组件注册配置
*/
function parseRoutes(routesConfig, routerMap) {
let routes = []
routesConfig.forEach(item => {
// 读取 router初始化 routeCfg
let router = undefined, routeCfg = {}
if (typeof item === 'string') {
router = routerMap[item]
routeCfg = {path: router.path || item, router: item}
} else if (typeof item === 'object') {
router = routerMap[item.router]
routeCfg = item
}
// 从 register 和 routeCfg 解析路由
if (!router) {
console.warn(`can't find register for router ${routeCfg.router}, please register it in advance.`)
} else {
const route = {
path: routeCfg.path || router.path || routeCfg.router,
name: routeCfg.name || router.name,
component: router.component,
redirect: routeCfg.redirect || router.redirect,
meta: {
authority: routeCfg.authority || router.authority || '*',
icon: routeCfg.icon || router.icon,
page: routeCfg.page || router.page
}
}
if (routeCfg.children && routeCfg.children.length > 0) {
route.children = parseRoutes(routeCfg.children, routerMap)
}
routes.push(route)
}
})
return routes
}
/**
* 加载路由
* @param routesConfig
* @param router
* @param store
*/
function loadRoutes(routesConfig, router, store, i18n) {
const routes = parseRoutes(routesConfig, routerMap)
router.matcher = new Router(router.options).matcher
router.addRoutes(routes)
const menuRoutes = routes.find(item => item.path === '/').children
mergeI18nFromRoutes(i18n, menuRoutes)
store.commit('setting/setMenuData', menuRoutes)
}
export {parseRoutes, loadRoutes}