qiankun微前端

基本介绍

  • 主应用管理用户,角色,日志以及菜单以及子应用是否展示
  • 主应用菜单有两种种类型 type: 1 应用菜单 type:2 按钮权限
  • 子应用不展示菜单列表和头部信息
  • 子应用通过localstore获取用户信息,菜单列表以及权限信息
  • 子应用路由通过菜单列表动态生成

项目结构

  • main 主基座 vue3 + vite
  • tlq 子应用 vue3 + vite
  • tlcn 子应用 Vue3 + webpack4
  • test 子应用 Vue3 + webpack5

主应用

  1. 安装qiankun npm install qiankun
  2. 创建子应用显示组件

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>
  1. 创建微应用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

  1. 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()
  1. 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',
        }
      }
    },    
  1. 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打包配置的不需要任何插件

  1. vue.conig.js
js 复制代码
configureWebpack: {
  output: {
      // 把子应用打包成 umd 库格式
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
    },
}
  1. 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();
}
  1. 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...

相关推荐
高桥留1 小时前
可编辑的span
前端·javascript·css
三小河1 小时前
js Class中 静态属性和私有属性使用场景得的区别
前端·javascript
名字越长技术越强1 小时前
CSS之选择器|弹性盒子模型
前端·css
用户93816912553601 小时前
VUE3项目--路由切换时展示进度条
前端
小王码农记1 小时前
vue2中table插槽新语法 v-slot
前端·vue.js
前端婴幼儿2 小时前
前端直接下载到本地(实时显示下载进度)
前端
三小河2 小时前
前端 Class 语法从 0 开始学起
前端
hjt_未来可期2 小时前
js实现复制、粘贴文字
前端·javascript·html
米诺zuo2 小时前
Next.js 路由与中间件
前端