版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: gudepeng.github.io/note/2020/0…
上一篇贴帖子已经讲了什么是微前端,不是太了解的同学们可以查看 微前端架构设计和实践:由来
本文案例会持续更新成为一个开源管理项目,欢迎 star,如果需要支持和需要新特性可以联系我
项目地址: github.com/gudepeng/vu…
qiankug 官方链接: github.com/umijs/qiank…
本文使用的是 qiankun2.0 之后的版本和方法
因为主项目和子项目都是用的 vue 开发,所以使用 vue-cli4 创建项目,创建 2 个项目,一个主项目,一个子项目。
npm install -g @vue/cli @vue/cli-service-global vue create vue-giant-master vue create vue-giant-module 复制代码
下面正常开始编写项目,我之前也在网上看了好多 demo 和实际项目,大家主项目都是直接把乾坤的代码放在 main 里面。然后在 app.vue 中用 v-if 去控制是路由显示还是加载的子项目显示。因为是后台管理项目,在我实际的项目开发中,主项目的路由页面页比较多,所以感觉这种方式不是太方便。所以我使用了正常 vue 项目启动,然后在登录后的主页面加载 qiankun 的代码。下面是具体实现。
<template> <div class="panel" @click="doOnWeb" @keydown="doOnWeb"> <top-header class="panel-heder"></top-header> <div class="panel-main"> <!-- 根据当前路由地址判断是子项目页面,还是主项目页面进行选择 --> <router-view v-if="showView" /> <div v-else id="root-view"></div> </div> <main-menu ref="mainMenu" class="main-menu" v-show="showMenu"></main-menu> <main-login ref="mainLogin"></main-login> </div> </template> <script> import TopHeader from '@/layout/components/Header' import MainMenu from '@/layout/components/Menu' import MainLogin from '@/layout/components/MainLogin' // 导入乾坤函数 import { registerMicroApps, start } from 'qiankun' import axios from '@/utils/request' export default { name: 'Layout', components: { TopHeader, MainMenu, MainLogin }, data() { return { showMenu: false } }, computed: { showView: function() { return this.$route.path === '/home' } }, mounted() { // 定义传入子应用的数据,方法和组件 const msg = { data: this.$store.getters, fns: [], prototype: [{ name: '$axios', value: axios }] } // 注册子应用,可以根据登录后的权限加载对应的子项目 registerMicroApps( [ { name: 'module-app1', entry: '//localhost:8081', container: '#root-view', activeRule: '/app1', props: msg }, { name: 'module-app2', entry: '//localhost:8082', container: '#root-view', activeRule: '/app2', props: msg } ], { beforeLoad: [ app => { console.log('before load', app) } ], beforeMount: [ app => { console.log('before mount', app) } ], afterUnmount: [ app => { console.log('after unload', app) } ] } ) // 启动微服务 start({ prefetch: true }) }, methods: { toggleMenu() { this.showMenu = !this.showMenu this.$refs['mainMenu'].showTwoMenu = false }, doOnWeb() { this.$refs.mainLogin.doOnWeb() } } } </script> 复制代码
这么做的话,就可以很好的区分子项目和主项目的加载了。而且也可以有共同的部署显示(例如 menu 和 title)
1.路由要使用 history 模式,并且主项目所有页面的跳转要使用 window.history.pushState()方法,否则会出现子项目没注销掉,然而页面上子项目的容器#root-view 被 v-if 删除掉的情况,这样 qiankun 就会报错,之后在跳转到子项目页面就会不进行加载。
2.在往子项目传递方法或者组件的时候,不要直接使用[function]数组内直接放方法的方式,因为在实际部署打包的时候这样的方法名会被 webpack 打包压缩,子项目中接受到的方法名就会不对,子项目调用主项目的方法时就会找不到这个方法。
3.在传递数据 data 的时候,本文使用传递 store 的 getter,然后子项目接受到值得时候初始到子项目的 store 中,当主项目有值变化的时候后使用 qiankun 提供的 initGlobalState 传递(在 qiankun 1.x 的版本的使用 qiankun 不支持加载子项目后的通信,我使用的是绑定 window 的 event 事件的方式传值),在子项目中在更新到 store 中。
我也尝试过直接把 store 传递到子项目中,实际情况也会传递到,这样主项目和子项目就相当于公用 store 了,但是子项目中的页面不会监听 store 的变化而变化,需要自己手动使用$forceUpdate(),我大概看了下 vuex 的源码,好像是因为他只创建了一个 dom 变化的监听的原因,具体还没有深入查看,之后会做详细调查,如果有直接的小伙伴们,可以留言告诉我小。
import router from '../router' import store from '../store' import { Message } from 'element-ui' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { getUserToken } from '@/utils/user' import Layout from '@/layout/index.vue' NProgress.configure({ showSpinner: false }) router.beforeEach(async (to, from, next) => { NProgress.start() const hasToken = getUserToken() if (hasToken) { if (to.path === '/login') { next({ path: '/home' }) NProgress.done() } else { const isLogin = store.getters.userInfo if (isLogin) { next() } else { try { // 获取到实际有什么子项目后再加入到router权限中 await store.dispatch('user/getUserInfo') router.addRoutes([ { path: '/', name: 'Layout', component: Layout, children: [ { path: 'app1*' } ] } ]) next({ ...to, replace: true }) } catch (error) { Message.error(error || 'Has Error') next(`/login?redirect=${to.path}`) NProgress.done() } } } } else { if (to.path === '/login') { next() NProgress.done() } else { next({ path: '/login' }) } } }) router.afterEach(() => { NProgress.done() }) 复制代码
当用户没有登录的时候都拦截到登录页面,当登录过的时候判断获取过权限没,如果没有获取过权限,去获取权限,然后添加对应子项目的路由到 router 中。
因为我的子项目页面是在布局页面下面,所以没有一个子项目的权限就要添加下对应权限
router.addRoutes([ { path: '/', name: 'Layout', component: Layout, children: [ { path: 'app1*' } ] } ]) 复制代码
子项目其实就是个普通的 vue 项目编写,main.js 中接收到方法和组件挂载到 vue.prototype 上,接收到的 data 初始化到 store 中。
import './public-path' import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import './plugins/element.js' import '@/utils/permission.js' Vue.config.productionTip = false let instance = null export async function bootstrap({ prototype }) { prototype.map(p => { Vue.prototype[p.name] = p.value }) } function initStore(props) { props.onGlobalStateChange && props.onGlobalStateChange((value, prev) => {}) props.setGlobalState && props.setGlobalState({ ignore: props.name, user: { name: props.name } }) } function render(props = {}) { instance = new Vue({ router, store, render: h => h(App) }).$mount('#app') } export async function mount(props) { initStore(props) if (props.data.userInfo.roles) { store.commit('permission/SET_ROLES', props.data.userInfo.roles) } render() } export async function unmount() { instance.$destroy() instance = null } if (!window.__POWERED_BY_QIANKUN__) { render() } 复制代码
需要在 main.js 同级目录创建 public-path.js
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ } 复制代码
因为主项目已经把权限传递到子项目中。在初始化路由的时候只需要权限加载对象的路由即可
import router from '@/router' import store from '@/store' import NProgress from 'nprogress' import 'nprogress/nprogress.css' NProgress.configure({ showSpinner: false }) router.beforeEach(async (to, from, next) => { NProgress.start() const hasRoles = store.getters.routes && store.getters.routes.length > 0 if (hasRoles) { next() } else { // get user info const roles = await store.state.permission.roles const accessRoutes = await store.dispatch( 'permission/generateRoutes', roles ) router.addRoutes(accessRoutes) next({ ...to, replace: true }) } }) router.afterEach(() => { NProgress.done() }) 复制代码
需要在其中添加文件的输出
output: { library: `${name}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${name}` } 复制代码
子项目需要支持跨域,所以需要添加
headers: { 'Access-Control-Allow-Origin': '*' } 复制代码
以上就是整个项目的核心代码,其他部分为具体 vue 项目的开发方法,本文就不详细说明了。 例如:用户隔一段时间不操作就会弹出 login 的 dialog,菜单等功能