第三方应用集成
第三方应用集成主要分为两种,一种是基于自身框架,能够把控源码的,可以采用qiankun框架进行微前端的集成,还有一种是采用IFrame方式集成。
1、微前端集成
在前端应用进行多模板拆分后,最终集成在一个框架中运行时,这里采用qiankun进行集成,建议>=waf-parent:2.2.0版本,之前版本只是做为集成,没有具体项目进行实践。
qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
1.1、菜单配置
基于qiankun集成的子应用,菜单配置需要注意组件的地址:layouts/QiankunLayout,路由地址需要根据qiankun注册时activeRule规则来配置,这里的路由地址一般就采用子应用的路由 base+path的方式配置。
微应用系统: 从数据字典MicroApp(微应用)中获取,微应用的编码也在该字典项配置
微应用组件: 组件的路径地址(vue文件在子应用的路径)
数据字典微应用添加需要集成的微应用编码,如: waf-micro-web (微应用示例)编码需要与应用是setting.js中microAppCode值保持一致

菜单配置:
访问地址:
/waf-micro-web/microDemo(这里需要添加微应用的上下文/waf-micro-web,后面自定义不重复就是)微应用系统:选择数据字典配置的
微应用微应用组件:
admin/SystemLog(微应用中views目录下的文件路径)

这里假设两个前端应用:waf-web(主应用)、waf-micro-web(微应用)应用注册微应用配置
1.2、waf-web(主应用)改造
1.2.1、env环境变量配置
# 1、.env.development (测试环境配置全路径)
# 主数据-qiankun应用入口
VUE_APP_QIANKUN_MDMWEB_ENTRY = '//localhost:8020/waf-micro-web/'
# 2、.env.production (正式环境只配置应用上下文,host由window.location.host获取)
# 主数据-qiankun应用入口
VUE_APP_QIANKUN_MDMWEB_ENTRY = '/waf-micro-web/'
1.2.2、AppMain.vue 配置
文件路径:src/components/layouts/components/AppMain.vue
// src/components/layouts/components/AppMain.vue
import { registerMicroApps, start } from 'qiankun'
import { getMicroApps } from '@/utils'
mounted: function() {
// 注册微应用(需要集成的模块)
registerMicroApps(getMicroApps(), {
beforeLoad: (app) => {
this.loading = true
},
afterMount: (app) => {
this.loading = false
}
})
// 禁用预加载 strictStyleIsolation: false 去掉严格模式
start({ prefetch: false })
}
// utils/index.js
export function getMicroApps() {
const microApps = [
{
name: 'waf-micro-web',
entry: buildEntry(process.env.VUE_APP_QIANKUN_APIWEB_ENTRY),
container: '#subapp-container',
activeRule: '/waf-web/waf-micro-web/',
props: { store: store }
}
]
return microApps
}
function buildEntry(appUrl) {
if (process.env.NODE_ENV === 'development') {
return appUrl
} else {
return `//${window.location.host}${appUrl}`
}
}
1.2.3、vue.config.js配置
**注意:**这里只注册微应用的后端代理,前端不要进行代理,否则微应用的刷新会被proxy代理
proxy: {
'/waf-framework/': {
// 目标 API 地址
target: 'http://localhost:8081/',
// target: 'http://d.wiseda.cn/',
// 如果要代理 websockets
ws: true,
// 将主机标头的原点更改为目标URL
changeOrigin: true // 是否跨域
},
'/xxx-server/': {
// 目标 API 地址
target: 'http://localhost:8850/',
// 如果要代理 websockets
ws: true,
// 将主机标头的原点更改为目标URL
changeOrigin: true // 是否跨域
}
}
1.3、waf-micro-web(微应用)改造
微应用的配置相对于主应用要复杂一些,主要是暴露钩子函数,生成新的路由。
1.3.1、utils\public-path.js
新增文件,用于注入qiankun路径
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
1.3.2、.env
qiankun集成时,路由的base属性
# qiankun 微应用集成地址
VUE_APP_QIANKUN_PUBLICPATH = '/waf-web/waf-micro-web/'
# 超时登录地址(由主应用进行重新登录)
VUE_APP_LOGIN_URL='/waf-web/'
1.3.3、utils\request.js
子应用超时后,在生产环境,统一转向主应用处理
// 延迟2秒后跳转到登录页面
setTimeout(() => {
store.dispatch('user/resetToken').then(() => {
// 生产环境,统一由配置地址进行登录
if (process.env.NODE_ENV === 'production') {
window.location.href = process.env.VUE_APP_LOGIN_URL
} else {
router.push(`/login`).catch(err => {err})
}
1.3.4、router\index.js
修改base代码行,window.__POWERED_BY_QIANKUN__如果为true,则采用qiankun的路径,否则采用应用配置的路径(用于单独登录)
const createRouter = () => new Router({
base: window.__POWERED_BY_QIANKUN__ ? process.env.VUE_APP_QIANKUN_PUBLICPATH : process.env.VUE_APP_PUBLICPATH,
mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
1.3.5、store\modules\user.js
在actions中添加loadUserInfo,用于qiankun调用mount(props)钩子函数时,初始化用户信息
const actions = {
// qiankun微应用集成初始化用户
loadUserInfo({ commit }, storeData) {
return new Promise((resolve) => {
commit('SET_USER_INFO', storeData.userInfo)
commit('SET_TOKEN', storeData.token)
resolve()
})
},
......
}
1.3.6、permission.js
在router.beforeEach中添加if条件判断,如果是qiankun调用,直接next(),权限路由将在mount中处理
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
if (window.__POWERED_BY_QIANKUN__) {
next()
} else {
......
}
1.3.7、settings.js
在setting文件中添加microAppCode属性,指定当前微应用编码,与数据字典微应用配置的数据项保持一致
注意:
ls的前端统一改为
waf.(如果其他项目,只需要保持主应用,各子应用一致就可以了,这样token可以在同域下传递)该编码与
菜单配置中指定的微应用系统保持一致。
// ls的前端统一改为`dmm.`
storageOptions: {
namespace: 'waf.'
/**
* 微应用编码(用于qiankun集成的过滤出本系统的菜单生成路由)
*/
microAppCode: 'waf-micro-web'
1.3.8、main.js
在import中删除babel-polyfill 引用 import 'babel-polyfill',该引用在主应用中已经引用,多次引用会报错
导出bootstrap() 、mount(props) 、unmount()三个钩子函数
import '@/utils/public-path'
import router, { resetRouter } from './router'
import { microAppCode } from '@/settings'
// 删除babel-polyfill 引用
let instance = null
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
// 如果是独立运行window.__POWERED_BY_QIANKUN__=undefined
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
// 应用启动执行一次
export async function bootstrap() {
}
export async function mount(props) {
const storeData = props.store.getters
// 加载用户信息
store.dispatch('user/loadUserInfo', storeData).then(() => {
const routers = processRouter(storeData.userInfo.permissions, [])
router.addRoutes(routers)
render(props)
})
}
function processRouter(permissions, routers) {
if (!permissions) {
return
}
for (const item of permissions) {
// 未指定组件,采用BlankLayout
let component = '' // 录入的文件路径
// 菜单属性当前系统
if (item.microApp && microAppCode === item.microApp) {
component = item.microAppComponent.trim()
let menuPath = item.url || `/${item.id}`
// 将带有应用上下文的路径替换为/,上下文路由路由的base指定
menuPath = menuPath.replace(process.env.VUE_APP_PUBLICPATH, '/')
const menu = {
path: menuPath,
name: item.perms,
component: resolve => require(['@/views/' + component + '.vue'], resolve),
hidden: !item.isVisible,
meta: {
title: item.name
}
}
routers.push(menu)
}
if (item.children && item.children.length > 0) {
processRouter(item.children, routers)
}
}
return routers
}
export async function unmount() {
instance.$destroy()
instance = null
resetRouter()
}
1.3.9、vue.config.js
const { name } = require('./package')
const tmpTime = new Date().getTime()
// 子应用打包格式
configureWebpack: {
output: {
// 把子应用打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
// 输出重构 打包编译后的 文件名称 【模块名称.时间戳】
filename: `static/js/[name].${tmpTime}.js`,
chunkFilename: `static/js/[name].${tmpTime}.js`
}
},
......
// 字体打包
chainWebpack: config => {
config.module
.rule('fonts')
.test(/.(ttf|otf|eot|woff|woff2)$/)
.use('url-loader')
.loader('url-loader')
.tap(options => {
options = {
// limit: 10000,
name: '/static/fonts/[name].[ext]'
}
return options
})
.end()
......
// 添加headers,让应用可以跨域调用
devServer: {
headers: {
'Access-Control-Allow-Origin': '*'
},
1.3.10、测试验证

2,微应用主题色设置
在主应用采用自定义的主题色后,微应用集成加载后,可能会出现主题色还原到默认主题,这里通过加载微应用后,再设置一下使用的主题色。
2.1.定义主题颜色
// styles/element-variables.less
@--color-primary: #0070D2;
:export {
theme: @--color-primary;
}
2.2.设置主题色
调用一次setTheme(variables.theme)
// main.js
import { setTheme } from '@/utils/theme'
import variables from '@/styles/element-variables.less'
export async function mount(props) {
......
// 加载用户信息
store.dispatch('user/loadUserInfo', storeData).then(() => {
const routers = processRouter(storeData.userInfo.permissions, [])
if (process.env.NODE_ENV !== 'production') {
console.info('routers:', routers)
}
setTheme(variables.theme)
router.addRoutes(routers)
render(props)
})
}
3、 IFrame集成
iframe集成是最常规的做法,直接嵌入一个地址,传递相应的参数即可,在基本框架平台中,也支持IFrame的方式集成。
- 菜单配置
iframe集成菜单配置只需要注意组件的地址:layouts/IframeLayout,路由地址可以是相对地址,也可以是绝对地址,在集成的地址会默认带一个token参数,值为当前登录的用户token

- 集成建议
对于基于基础框架开发的应用,如果有多个子应用,建立采用微前端-qiankun的集成方式,目前不兼容IE,如必需在IE下运行还是采用老套路,通过IFrame来集成。
