前言
前段时间对我们已有的工程进行了微前端改造,后来思考一下微前端的本质,查询了不少资料,从qiankun微前端示例中学到了不少。
微前端的核心,似乎应该是一个基座应用(含登录页,layout页,404和首页等),多个子应用(任意框架,提供内部页面内容),下面就对这个思路进行实现讲解:
一、主应用
1、主应用主要提供 Login、Layout、Home、404等基础页面。
2、通过提供qiankun的initGlobalState、registerMicroApps、start等完成主应用的qiankun注册、子应用的注册和数据初始化,监听。
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'
import Login from '@/views/Login'
import Home from '@/views/Home'
import Layout from '@/views/Layout'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
Vue.use(VueRouter)
// 路由
const constantRoutes = [
{
path: '/login',
name: 'login',
component: Login,
meta: {
isTabs: false, isSide: false, moduleName: 'main', title: '登录' }
},
{
path: '/',
name: 'Layout',
component: Layout,
redirect: process.env.VUE_APP_DEFAULT_APP, // 默认加载的路由
children: [
{
path: '/home',
name: 'Home',
component: Home,
meta: {
isTabs: false, isSide: false, moduleName: 'main', title: '首页' }
}
]
}
]
const createRouter = () => {
return new VueRouter({
mode: 'history',
routes: constantRoutes,
isAddAsyncMenuData: false
})
}
// 处理重复点击同一个路由报错的问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch((err) => err)
}
const router = createRouter()
/**
* 重置注册的路由导航map
* 主要是为了通过addRoutes方法动态注入新路由时,避免重复注册相同name路由
*/
const resetRouter = () => {
const newRouter = createRouter();
router && (router.matcher = newRouter.matcher);
};
router.beforeEach((to, from, next) => {
NProgress.start()
// 菜单当前选中及页面持久
if (to.path !== '/login') {
store.commit('permission/UPDATE_CURRENT_MODULE_NAME', to.meta.moduleName)
store.commit('permission/UPDATE_CURRENT_PAGE', to.path)
}
// 首页的时候组装左侧导航数据
// if (to.path === '/home') {
// store.commit('permission/UPDATE_SUB_MENU', true)
// }
if (!router.options.isAddAsyncMenuData) {
store.dispatch('permission/generateRoutes').then((accessRoutes) => {
// 根据用户权限生成可访问的路由表
for (let i = 0, length = accessRoutes.length; i < length; i += 1) {
const element = accessRoutes[i]
router.addRoute(element)
}
router.options.isAddAsyncMenuData = true
next({
...to, replace: true }) // hack方法 确保addRoutes已完成
})
} else {
next()
}
})
router.afterEach(() => {
NProgress.done()
})
export {
constantRoutes, resetRouter };
export default router
// store/modules/permission.js
import http from 'axios'
import {
constantRoutes } from '@/router'
import Layout from '@/views/Layout'
import {
homeMenuData } from '../../utils'
const permission = {
namespaced: true,
state: () => ({
routers: null,
menuList: [],
subMenu: [],
currentModuleName: 'main',
currentPage: '/home'
}),
mutations: {
// 当前模块
UPDATE_CURRENT_MODULE_NAME (state, payload) {
sessionStorage.setItem('currentApp', payload)
state.currentModuleName = payload
},
// 当前页面
UPDATE_CURRENT_PAGE (state, payload) {
sessionStorage.setItem('currentPage', payload)
state.currentPage = payload
},
// 菜单数据
UPDATE_MENU_LIST (state, payload) {
state.menuList = payload
},
// 左侧菜单数据,子应用菜单数据
UPDATE_SUB_MENU (state, payload) {
state.subMenu = []
// if (typeof payload === 'boolean') {
// state.subMenu.push(homeMenuData)
// return
// }
if (typeof payload === 'object') {
state.subMenu = payload
}
},
// 路由数据
UPDATE_ROUTERS (state, payload) {
state.routers = constantRoutes.concat(payload)
}
},
actions: {
generateRoutes ({
commit }) {
return new Promise((resolve) => {
// 向后端请求路由数据
http.get('/mock/menu.json').then((res) => {
const data = res.data
data.unshift(homeMenuData) // 添加首页菜单,默认主应用的菜单信息在这里
commit('UPDATE_MENU_LIST', data)
let routes = []
for (let i = 0; i < data.length; i++) {
const module = data[i]
const route = getMenuItem(module.menuList)
routes = [...routes, ...route]
}
routes.push({
path: '*',
name: 'notfound',
component: () => import('@/views/404.vue')
})
commit('UPDATE_ROUTERS', routes)
resolve(routes)
})
})
}
},
getters: {
}
}
// 组装路由数据
function getMenuItem (menus) {
let routers = []
for (let index = 0; index < menus.length; index++) {
const menu =</