基本介绍
- 主应用管理用户,角色,日志以及菜单以及子应用是否展示
- 主应用菜单有两种种类型 type: 1 应用菜单 type:2 按钮权限
- 子应用不展示菜单列表和头部信息
- 子应用通过localstore获取用户信息,菜单列表以及权限信息
- 子应用路由通过菜单列表动态生成
项目结构
- main 主基座 vue3 + vite
- tlq 子应用 vue3 + vite
- tlcn 子应用 Vue3 + webpack4
- test 子应用 Vue3 + webpack5

主应用
- 安装qiankun
npm install qiankun - 创建子应用显示组件
childApp/index.vue
vue
<template>
<div id="subapp-viewport" class="subapp-container" />
</template>
<script setup>
// 子应用容器组件,qiankun 会自动将子应用挂载到此容器中
import { onMounted } from 'vue'
import { useMicroappStore } from '@/stores/microapp'
const microappStore = useMicroappStore()
onMounted(async () => {
if (!microappStore.qiankunStarted) {
await microappStore.initMicroApp()
}
})
</script>
<style lang="scss" scoped>
.subapp-container {
width: 100%;
height: 100%;
min-height: 500px;
all: initial;
}
</style>
- 创建微应用store
stores/microapp.js
js
import { defineStore } from 'pinia'
import { ElMessage } from 'element-plus'
import { useUserStore } from './user'
import { registerMicroApps, start, addGlobalUncaughtErrorHandler } from 'qiankun'
import router from '@/router'
import { getAppList } from '@/api/app'
export const useMicroappStore = defineStore('microapp', {
state: () => ({
qiankunStarted: false,
childAppList: []
}),
actions: {
// 获取子应用列表
async getChildAppList() {
const data = await getAppList()
// 处理 activeRule,如果是字符串则转换为函数以匹配 hash 路由
this.childAppList = data
},
// 子应用通知主应用
notify(app, type, message) {
console.log('子应用反馈', app)
if (type == 'message') ElMessage.info(message)
if (type == 'error') ElMessage.error(message)
if (type == 'noToken') {
ElMessage.warning(message)
useUserStore().resetUser()
router.push('/login')
}
if (type == 'backToLogin') {
useUserStore().resetUser()
router.push('/login')
}
},
// 注册子应用
registerMicroapp() {
const list = this.childAppList.map(i => {
const obj = Object.assign({}, i)
delete obj.id
obj.props.notifyMainApp = this.notify
obj.entry = import.meta.env.DEV ? obj.devEntry : obj.entry
return obj
})
try {
registerMicroApps(list)
} catch (error) {
console.error(error)
}
},
// 初始化qiankun
async initMicroApp() {
if (this.qiankunStarted) return
addGlobalUncaughtErrorHandler(event => {
console.error('qiankun error', event)
const { message: msg } = event
// 加载失败时提示
if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
ElMessage.error('微应用加载失败,请检查应用是否可运行')
}
})
this.registerMicroapp()
start({
sandbox: {
experimentalStyleIsolation: true // 可选的严格样式隔离
}
})
this.qiankunStarted = true
console.log('qiankun start')
}
},
getters: {}
})
// childApplist
// [
//{
// id: 1,
// name: 'tlq',
// entry: '//localhost:3100/',
// container: '#subapp-viewport',
// activeRule: '/app_tlq',
// props: {
// title: 'tlq管理控制台',
// desc: 'tlq相关内容展示',
// version: '1.0.1'
// }
// },
//]
主应用和子应用都是动态注册的路由(根据后端传回的菜单列表)
在主应用中,注册子应用主路由即可
例如: tlq的路由
javascript
{
router.addRoute('BasicHome', {
path: `${item.activeRule}/:subpath(.*)*`,
name: item.name,
component: () => import('@/views/childApp/index.vue'),
})
}
tlq子应用
vite打包的需要使用一个插件 vite-plugin-qiankun
- main.js
javascript
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import { qiankunWindow, renderWithQiankun } from 'vite-plugin-qiankun/dist/helper'
import { useUserStore } from './stores/user'
let app
const pinia = createPinia()
function render(props = {}) {
const { container } = props
app = createApp(App)
// 注册Element Plus
app.use(ElementPlus)
app.use(router)
app.use(pinia)
app.mount(container ? container.querySelector('#tlq-app') : '#tlq-app')
}
export async function bootstrap() {
return new Promise(resolve => {
console.log('tlq qiankun bootstrap')
resolve()
})
}
export async function mount(props) {
return new Promise((resolve, reject) => {
try {
// 保存全局状态引用
console.log('tlq qiankun mount', props)
render(props)
// 处理数据 从localstore中获取
const localUser = JSON.parse(localStorage.getItem('user') || null)
if (localUser) {
const userInfo = localUser.userInfo
const user = useUserStore()
user.userInfo = {
psw: userInfo.psw,
token: userInfo.token,
username: userInfo.username,
permissions: userInfo.permissions['tlq']
}
// 菜单处理 不从后端获取菜单
const tlq = localUser.menuList.filter(item => item.childAppName === 'tlq')
if (tlq.length > 0) {
user.menuList = tlq[0].children
}
// 通知主应用的函数
user.notifyMainApp = props.notifyMainApp
} else {
props.notifyMainApp('tlq', 'noToken', '没有用户信息, 请登录!')
}
resolve()
} catch (error) {
reject('mounted failed')
}
})
}
export async function unmount() {
return new Promise(resolve => {
console.log('tlq qiankun unmount')
app.unmount()
app = null
resolve()
})
}
const initQiankun = () => {
renderWithQiankun({
bootstrap,
mount,
unmount
})
}
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQiankun() : render()
- vite.config.js
css
import qiankun from 'vite-plugin-qiankun'
import packa from './package.json'
plugins: [
qiankun(packa.name, { useDevMode: true })
],
build: {
rollupOptions: {
output: {
format: 'umd',
}
}
},
- router
这里是动态注册路由,路由激活前缀在main主基座中已及加上了,所以子应用的路由不需要做什么
javascript
import { createRouter, createWebHistory } from 'vue-router'
import BaseHome from '@/layout/AppContainer/index.vue'
import NotFound from '@/views/404/index.vue'
/**
* 子应用没有登录功能,所有路由注册需要在此写明, 不能从后端获取后再注册
*/
export const routes = [
{
path: '/',
component: BaseHome,
name: 'BasicHome',
children: []
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
tlqcn
没有使用动态路由,自己项目的路由就这样设置
webpack打包配置的不需要任何插件
- vue.conig.js
js
configureWebpack: {
output: {
// 把子应用打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
}
- main.js
js
import './public-path';
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';
let router = null;
let instance = null;
let history = null;
function render(props = {}) {
const { container } = props;
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/app_tlqcn' : '/');
router = createRouter({
history,
routes,
});
instance = createApp(App);
instance.use(router);
instance.use(store);
instance.mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
}
export async function mount(props) {
console.log('tlqcn mount', props)
render(props);
}
export async function unmount() {
instance.unmount();
instance._container.innerHTML = '';
instance = null;
router = null;
history.destroy();
}
- src/public-path.js 新增这个文件,设置运行时publicPath
perl
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
test
vue-cli5 webpack5打包工具,只有vue.config.js中有点个tlqcn不同
js
configureWebpack: {
output: {
library: 'tlqcn',
libraryTarget: 'umd',
globalObject: 'window'
}
}
一点心得:
- 使用vite做打包工具时,不能设置运行时base, 也就是静态资源访问路径,在生产环境下,需要设置vite.config.js中的base为绝对访问路径(如果不同服务器或不同端口访问)
- 使用vite打包方式,样式不能有效隔离,只能设置独立的css前缀
- webpack可以设置运行时静态资源访问路径, 但只仅限于js。对css样式中引入的资源无效。
具体代码可访问如下仓库: gitee.com/olhong/qian...