版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:gudepeng.github.io/note/2020/0…css
上一篇貼帖子已經講了什麼是微前端,不是太瞭解的同窗們能夠查看微前端架構設計和實踐:由來
本文案例會持續更新成爲一個開源管理項目,歡迎 star,若是須要支持和須要新特性能夠聯繫我
項目地址:github.com/gudepeng/vu…
qiankug 官方連接:github.com/umijs/qiank…
本文使用的是 qiankun2.0 以後的版本和方法 前端
由於主項目和子項目都是用的 vue 開發,因此使用 vue-cli4 建立項目,建立 2 個項目,一個主項目,一個子項目。vue
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 的代碼。下面是具體實現。webpack
<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)ios
1.路由要使用 history 模式,而且主項目全部頁面的跳轉要使用 window.history.pushState()方法,不然會出現子項目沒註銷掉,然而頁面上子項目的容器#root-view 被 v-if 刪除掉的狀況,這樣 qiankun 就會報錯,以後在跳轉到子項目頁面就會不進行加載。git
2.在往子項目傳遞方法或者組件的時候,不要直接使用[function]數組內直接放方法的方式,由於在實際部署打包的時候這樣的方法名會被 webpack 打包壓縮,子項目中接受到的方法名就會不對,子項目調用主項目的方法時就會找不到這個方法。github
3.在傳遞數據 data 的時候,本文使用傳遞 store 的 getter,而後子項目接受到值得時候初始到子項目的 store 中,當主項目有值變化的時候後使用 qiankun 提供的 initGlobalState 傳遞(在 qiankun 1.x 的版本的使用 qiankun 不支持加載子項目後的通訊,我使用的是綁定 window 的 event 事件的方式傳值),在子項目中在更新到 store 中。web
我也嘗試過直接把 store 傳遞到子項目中,實際狀況也會傳遞到,這樣主項目和子項目就至關於公用 store 了,可是子項目中的頁面不會監聽 store 的變化而變化,須要本身手動使用$forceUpdate(),我大概看了下 vuex 的源碼,好像是由於他只建立了一個 dom 變化的監聽的緣由,具體尚未深刻查看,以後會作詳細調查,若是有直接的小夥伴們,能夠留言告訴我小。vuex
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 中。
由於個人子項目頁面是在佈局頁面下面,因此沒有一個子項目的權限就要添加下對應權限vue-cli
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,菜單等功能