背景
已有项目A(vue2,element ui,webpack)需要引用B项目(vue2,element ui,webpack)和C项目(vue3、ant,ant design vue,vite)的部分页面,已经用iframe实现。但是领导看了效果后说加载太慢体验感不好,说是否能用组件嵌入。调研发现vue2工程打包成公共组件包,方案可行但是也需要拆分出来单独管理,需要管理两份代码不满足要求。vue3工程打包成公共组件包嵌入vue2工程方案不可行。和同学聊了下发现微前端可以解决单前困惑,就此踏上微前端(QianKun)坑路。
微前端选型调研
根据各个微前端的优缺点结合单前需求是改造久项目选择了qiankun。但是在改造vue3、ant,ant design vue,vite项目时,qiankun对vite配置并不友好,子项目改造工程有点大怕影响原有功能。又去了解了wujie框架,wujie框架对子项目的改造和qiankun相比几乎为0。但是wujie框架相对qiankun还偏年轻,qiankun虽然很多问题,但是都被踩过坑,有解决的办法。但是wujie有可能出现解决不了的问题。所以最终选择了qiankun。
主项目改造
项目整体配置入口配置修改
import { registerMicroApps, start } from 'qiankun'
import { microAppConfig, sandboxConfig } from './config/microAppConfig'
// 使用配置文件中的微应用配置
registerMicroApps([
{
...microAppConfig['kg-ui'],
props: { targetPath: '/knowledgeBaseSearch' }
},
{
...microAppConfig['mindMap-ui'],
props: {
// 入参
showRightSuffix: true,
isNumberPerson: false,
routeId: null
}
}
], {
beforeLoad: [
app => {
console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
},
],
beforeMount: [
app => {
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
},
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
},
],
})
console.log('已注册微前端');
start(sandboxConfig)
console.log('qiankun 已启动');
// 微前端应用配置
const isDev = process.env.NODE_ENV === 'development' || window.location.hostname === 'localhost'
// 微应用配置
export const microAppConfig = {
// 项目C
'kg-ui': {
name: 'kg-ui',
entry: isDev ? 'http://localhost:8081/' : '/kg-ui/',
container: '#knowledge-search',
activeRule: isDev ? '/knowledgeBaseSearch' : '/kg-ui'
},
// 项目B
'mindMap-ui': {
name: 'mindMap-ui',
entry: isDev ? 'http://localhost:8080/' : '/mindMap-ui/',
container: '#mindMapContainer',
activeRule: isDev ? '/mindMap-ui' : '/mindMap-ui'
}
}
// 获取微应用入口地址
export function getAppEntry(appName) {
const config = microAppConfig[appName]
if (!config) {
console.error(`未找到微应用配置: ${appName}`)
return `/${appName}/`
}
return config.entry
}
// 获取微应用完整配置
export function getAppConfig(appName, props = {}) {
const config = microAppConfig[appName]
if (!config) {
console.error(`未找到微应用配置: ${appName}`)
return null
}
return {
...config,
props: {
...props,
// 添加环境信息
isDev,
timestamp: Date.now()
}
}
}
// 沙箱配置
export const sandboxConfig = {
sandbox: {
strictStyleIsolation: false,
experimentalStyleIsolation: true,
loose: true
}
}
// 生产环境特殊配置
export const productionConfig = {
// 生产环境下的特殊配置
baseUrl: window.location.origin
// 可以添加更多生产环境特定的配置
}
export default {
microAppConfig,
getAppEntry,
getAppConfig,
sandboxConfig,
productionConfig,
isDev
}
需要加载其他项目的页面的主页面修改。
<!-- C项目前端容器 -->
<div v-if="isMindMapApp" id="mindMapContainer" class="micro-app-container">
<!-- 微前端加载状态 -->
<div v-if="!iframeLoaded" class="micro-app-loading">
<div class="loading-content">
<div class="loading-spinner"></div>
<p>正在加载 {{ title }}...</p>
</div>
</div>
</div>
<!-- B项目微前端容器 -->
<div v-else id="knowledge-search" class="micro-app-container">
<!-- 微前端加载状态 -->
<div v-if="!iframeLoaded" class="micro-app-loading">
<div class="loading-content">
<div class="loading-spinner"></div>
<p>正在加载 {{ title }}...</p>
</div>
</div>
</div>
import { loadMicroApp } from 'qiankun'
// 加载微前端应用
async loadMicroApp() {
try {
console.log('开始加载微前端应用...')
console.log('应用类型:', this.isMindMapApp ? '项目B' : '项目C')
console.log('目标路由:', this.targetRoute)
// 先确保之前的应用完全销毁
if (this.microApp) {
console.log('发现已存在的微前端应用,先销毁')
this.destroyMicroApp()
// 等待销毁完成
await new Promise(resolve => setTimeout(resolve, 300))
}
// 获取当前应用配置
const config = this.microAppConfig
// 先检查子应用是否可访问
const response = await fetch(config.entry)
if (!response.ok) {
throw new Error(`子应用无法访问: ${response.status}`)
}
// 确保容器存在
const container = document.querySelector(config.container)
if (!container) {
throw new Error(`微前端容器不存在: ${config.container}`)
}
// 彻底清空容器
container.innerHTML = ''
container.style.cssText = ''
console.log('容器已彻底清空')
console.log('容器元素:', container)
console.log('容器尺寸:', container.getBoundingClientRect())
// 确保容器有明确的尺寸约束
container.style.position = 'relative'
container.style.boxSizing = 'border-box'
// 移除 overflow: hidden,允许滚动
// 移除 contain: layout,避免影响滚动
// 使用计算属性中的配置,并添加额外的运行时信息
const microAppConfig = {
...config,
props: {
...config.props,
timestamp: Date.now(),
// 添加容器信息
containerInfo: {
width: container.clientWidth,
height: container.clientHeight,
id: container.id
},
// 添加路由导航信息(仅对知识库搜索应用)
...(this.isMindMapApp ? {} : {
route: this.targetRoute,
testMode: true,
navigation: {
shouldNavigate: true
}
})
}
}
// 根据应用类型和环境优化沙箱配置
const appSandboxConfig = this.getOptimizedSandboxConfig()
// 只在开发环境输出详细日志
if (process.env.NODE_ENV === 'development') {
console.log('微前端配置:', microAppConfig)
console.log('沙箱配置:', appSandboxConfig)
}
this.microApp = this.performanceWrapper(
() => loadMicroApp(microAppConfig, appSandboxConfig),
`加载${this.isMindMapApp ? '项目B' : '项目C'}微应用`
)
// 只对知识库搜索应用发送路由信息
if (!this.isMindMapApp) {
this.sendInitialRoute()
}
await this.microApp.mountPromise
this.iframeLoaded = true
// 只在开发环境输出详细日志
if (process.env.NODE_ENV === 'development') {
console.log(`微前端应用加载成功: ${config.name}`)
console.log('容器内容:', container.innerHTML.substring(0, 200))
}
// 只对知识库搜索应用发送路由导航消息
if (!this.isMindMapApp) {
this.navigateToRoute()
} else {
// 对思维树应用发送特殊的初始化消息
this.sendMessageToMindMap({
type: 'INIT',
data: {
title: this.title,
description: this.description,
mtPlatStaffId: this.mtPlatStaffId
}
})
}
this.$emit('iframe-loaded')
} catch (error) {
// 根据环境决定错误处理方式
if (process.env.NODE_ENV === 'development') {
console.error('微前端应用加载失败,降级到iframe模式:', error)
} else {
console.warn('微前端应用加载失败,降级到iframe模式')
}
// 降级到iframe模式
this.useMicroFrontend = false
this.$nextTick(() => {
this.iframeLoaded = true
})
}
},
// 获取优化的沙箱配置
getOptimizedSandboxConfig() {
const isDev = process.env.NODE_ENV === 'development'
if (this.isMindMapApp) {
// 项目B
return {
sandbox: {
strictStyleIsolation: false,
experimentalStyleIsolation: true,
loose: true,
skipCheckForMultipleInstance: true
}
}
} else {
// 项目C - 生产环境使用更宽松的配置
return {
sandbox: isDev ? {
strictStyleIsolation: false,
experimentalStyleIsolation: true,
loose: true
} : {
// 生产环境使用最宽松配置,减少错误
strictStyleIsolation: false,
experimentalStyleIsolation: false,
loose: true
}
}
}
},
// 销毁微前端应用
destroyMicroApp() {
if (this.microApp) {
try {
// 2. 卸载微前端应用
this.microApp.unmount()
this.microApp = null
// 3. 彻底清空容器内容
const container = document.getElementById('knowledge-search')
if (container) {
container.innerHTML = ''
// 强制重置容器样式
container.style.cssText = ''
console.log('微前端容器已彻底清空')
}
// 4. 清理可能的全局状态
if (window.__POWERED_BY_QIANKUN__) {
// 清理qiankun相关的全局状态
delete window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
// 5. 重置组件状态
this.iframeLoaded = false
console.log('微前端应用已完全销毁')
} catch (error) {
console.error('销毁微前端应用时出错:', error)
}
} else {
console.log('没有微前端应用需要销毁')
}
},
handleBack() {
// 先销毁微前端,再触发退出事件
this.destroyMicroApp()
// 延迟一点时间确保销毁完成
setTimeout(() => {
this.$emit('back')
}, 100)
}
主项目改造完成。
子项目B改造(vue2,element ui,webpack)
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
}
}
说明:umd= commonjs+AMD
qiankun需要使用umd打包的原因: qiankun下的子应用通过 webpack 的 umd 输出格式来做,让父应用在执行子应用的 js 资源时可以通过 eval,将 window 绑定到一个 Proxy 对象上,以此来防止污染全局变量,方便对脚本的 window 相关操作做劫持处理,达到子应用之间的脚本隔离架构
function render(props = {}) {
const { container } = props
// 在微前端模式下,直接渲染指定组件,不使用路由
if (window.__POWERED_BY_QIANKUN__) {
// 创建一个简单的包装组件
const MicroAppWrapper = {
name: 'MicroAppWrapper',
data() {
return {
FtaManageEntry: null
}
},
async created() {
// 动态导入组件
const module = await import('./pages/MicroFrontend/FtaManageEntry.vue')
this.FtaManageEntry = module.default
},
render(h) {
if (!this.FtaManageEntry) {
return h('div', { style: { padding: '20px', textAlign: 'center' } }, '加载中...')
}
return h('div', { attrs: { id: 'micro-app-root' } }, [
h(this.FtaManageEntry, {
props: {
showRightSuffix: props.showRightSuffix || false,
isNumberPerson: props.isNumberPerson || false,
routeId: props.routeId || null
}
})
])
}
}
instance = new Vue({
router, // 保留路由实例,避免组件中的路由调用出错
store,
i18n,
render: h => h(MicroAppWrapper)
}).$mount(container ? container.querySelector('#app') : '#app')
} else {
// 独立运行时使用正常的路由
instance = new Vue({
router,
store,
i18n,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
}
export async function mount(props) {
console.log('[vue] props from main framework', props)
initApp()
render(props)
// 在微前端模式下,通知主应用加载完成
if (window.__POWERED_BY_QIANKUN__) {
setTimeout(() => {
console.log('🎯 微前端组件已挂载')
// 通过 postMessage 通知主应用
window.parent.postMessage({
type: 'MICRO_APP_LOADED',
data: {
appName: 'mindMap-ui',
component: 'FtaManageEntry',
timestamp: Date.now()
}
}, '*')
}, 100)
}
}
<template>
<div class="fta-manage-entry">
<ftaManage
:mtPlatStaffId="routeId"
v-bind="$attrs"
v-on="$listeners"
/>
</div>
</template>
<script>
import ftaManage from '../ftaMaintenance/ftaManage/ftaManage.vue'
export default {
name: 'FtaManageEntry',
components: {
ftaManage
},
props: {
// 路由ID,用于微前端模式
routeId: {
type: String | Number,
default: null
}
},
computed: {
isQiankunMode() {
return !!window.__POWERED_BY_QIANKUN__
}
},
mounted() {
console.log('🚀 FtaManageEntry mounted with props:', this.$props)
console.log('🚀 Is qiankun mode:', this.isQiankunMode)
// 在微前端模式下不需要解析 URL 参数,直接使用 props
if (!this.isQiankunMode) {
this.parseUrlParams()
}
// 监听来自主应用的消息
window.addEventListener('message', this.handleMessage)
// 通知主应用子应用已加载完成
if (this.isQiankunMode) {
const message = {
type: 'MICRO_APP_COMPONENT_LOADED',
data: {
appName: 'mindMap-ui',
component: 'FtaManageEntry',
props: this.$props,
timestamp: Date.now()
}
}
console.log('📤 发送组件加载完成消息:', message)
// 通过多种方式发送消息,确保主应用能收到
window.parent.postMessage(message, '*')
window.postMessage(message, '*')
}
},
beforeDestroy() {
window.removeEventListener('message', this.handleMessage)
},
methods: {
// 解析 URL 参数
parseUrlParams() {
const query = this.$route.query
if (query.routeId) {
this.routeId = query.routeId
}
console.log('从 URL 解析的参数:', {
showRightSuffix: this.showRightSuffix,
isNumberPerson: this.isNumberPerson,
routeId: this.routeId
})
},
handleMessage(event) {
const { type, data } = event.data || {}
switch (type) {
case 'UPDATE_PROPS':
// 更新组件属性
Object.keys(data).forEach(key => {
if (Object.prototype.hasOwnProperty.call(this.$props, key)) {
this[key] = data[key]
}
})
break
case 'NAVIGATE':
// 处理路由导航
if (data.path) {
// 处理 hash 模式路径
const path = data.path.startsWith('#/') ? data.path.substring(1) : data.path
this.$router.push(path)
}
break
default:
break
}
}
}
}
</script>
<style scoped>
.fta-manage-entry {
width: 100%;
height: 100%;
}
</style>
子项目C改造(vue3,Ant Design Vue,vite)
这个项目将知识库管理系统改造为可以作为微前端子应用运行,同时保持独立运行的能力。使用了 qiankun 框架实现微前端架构。
一、核心改造内容
- 依赖安装
"vite-plugin-qiankun": "^1.0.15" // qiankun 的 Vite 插件
"qiankun": "^2.10.16", //qiankun 插件
- 主入口文件改造 (main.ts) 2.1 环境检测与适配
// 检测是否在微前端环境
const isMicroApp = !!(window as any).__POWERED_BY_QIANKUN__;
// 根据环境进行适配
- 微前端模式:动态导入适配、静态资源路径适配
- 独立模式:使用标准模式
2.2 生命周期函数 实现 qiankun 要求的三个生命周期钩子:
// qiankun 初始化
const initQianKun = () => {
renderWithQiankun({
async bootstrap() {
console.log('[knowledgeBaseSearch] vue app bootstraped');
// 确保 bootstrap 返回 resolved Promise
return Promise.resolve();
},
async mount(props) {
console.log('[knowledgeBaseSearch] 子应用挂载', props);
try {
// 立即清空容器并显示加载状态
if (props.container) {
props.container.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #666;">正在加载...</div>';
}
await render(props);
console.log('[knowledgeBaseSearch] 子应用挂载完成');
} catch (error) {
console.error('[knowledgeBaseSearch] 子应用挂载失败:', error);
throw error;
}
},
async unmount() {
console.log('[knowledgeBaseSearch] 子应用卸载');
try {
if (app) {
app.unmount();
app = null;
}
// 清空容器内容,避免下次挂载时显示残留内容
const containers = document.querySelectorAll('[data-qiankun]');
containers.forEach(container => {
if (container) {
container.innerHTML = '';
}
});
console.log('[knowledgeBaseSearch] ✅ 子应用卸载完成,容器已清空');
} catch (error) {
console.error('[knowledgeBaseSearch] 子应用卸载失败:', error);
throw error;
}
},
update() {
console.log('[knowledgeBaseSearch] 子应用更新');
}
});
};
- 路由改造、微前端模式使用内存路由,独立模式使用 hash 路由 3.1 路由模式切换 (router/index.ts)
// app router
// 创建一个可以被 Vue 应用程序使用的路由实例
export const router = createRouter({
// 根据 qiankun 官网推荐:微前端模式使用内存路由,独立模式使用 hash 路由
history: (window as any).__POWERED_BY_QIANKUN__
? createMemoryHistory() // 微前端模式:使用内存路由
: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH), // 独立模式:使用hash路由
// 应该添加到路由的初始路由列表。
routes: basicRoutes as unknown as RouteRecordRaw[],
// 是否应该禁止尾部斜杠。默认为假
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
});
4、消息通信 (utils/microAppRouter.ts)
// 主应用与子应用通信
export function setupMessageListener() {
window.addEventListener('message', (event) => {
// 处理主应用发送的消息
if (event.data.type === 'navigate') {
router.push(event.data.route);
}
});
}
// 子应用向主应用发送消息
export function sendMessageToMain(data: any) {
window.parent.postMessage(data, '*');
}
5、主工程和子工程传值
return getAppConfig('mindMap-ui', {
showRightSuffix: true,
isNumberPerson: false,
title: this.title,
description: this.description,
routeId: this.mtPlatStaffId
})
props: {
routeId: {
type: [String, Number],
default: ''
}
},
6、遇到的问题及解决方案
6.1子项目c用到的组件库是Element Plus,而主应用使用 Element UI。两者的 CSS 类名默认都使用 .el- 前缀,会导致样式冲突。
解决方案:通过修改 Element Plus 的命名空间(namespace)从默认的 el 改为 ep,使所有 Element Plus 组件的 CSS 类名从 .el-* 变为 .ep-*,从而避免与主应用的 Element UI 样式冲突。
代码:
// 仅需要重写 $namespace 变量即可
@forward 'element-plus/theme-chalk/src/mixins/config.scss' with (
$namespace: 'ep' !default
);
// 导入所有 Element Plus 的样式
// 这将自动应用上面的命名空间配置
@use "element-plus/theme-chalk/src/index.scss" as *;
<template>
<ConfigProvider :locale="getAntdLocale">
<AppProvider>
<div :class="{ 'micro-app-container': isMicroApp }">
<!-- 使用 Element Plus 的配置提供者,设置命名空间 -->
<el-config-provider namespace="ep">
<RouterView />
</el-config-provider>
</div>
</AppProvider>
</ConfigProvider>
</template>
<script lang="ts" setup>
import { ElConfigProvider } from 'element-plus';
// ...
</script>
6.2message、notification、Modal 等组件在微前端环境下显示图层被遮挡 1、全局组件配置
// 配置 Ant Design Vue 全局组件
// 确保 message、notification、Modal 等组件
// 在微前端环境下正确显示
message.config({
getContainer: () => document.body, // 绑定到 body
maxCount: 3,
duration: 3
});
2、 弹出层组件适配
// 获取弹出层容器
export function usePopupContainer() {
if (window.__POWERED_BY_QIANKUN__) {
// 微前端模式:查找微前端容器
const containers = [
'#subapp-container',
'#subapp-viewport',
'[data-qiankun]',
'.qiankun-container'
];
// 返回找到的第一个容器或 body
}
return () => document.body;
}
// 配置 Ant Design 全局配置
export function setupAntdGlobalConfig() {
// 设置所有弹出层组件的容器
}
// 修复弹出层样式
export function fixPopupStyles() {
// 添加 CSS 样式,确保弹出层正确显示
}
在各个 Vue 组件中使用:
// 示例:caseList.vue
const getMicroContainer = () => {
if (window.__POWERED_BY_QIANKUN__) {
// 查找微前端容器
const containers = ['#subapp-container', ...];
for (const selector of containers) {
const container = document.querySelector(selector);
if (container) return container;
}
}
return document.body;
};
// 使用在 Modal、Dropdown 等组件
<a-modal :getContainer="getMicroContainer" />
6.3静态资源如图片无法正常展示
// 判断是否在微前端环境
export const isMicroApp = (): boolean => {
return !!window.__POWERED_BY_QIANKUN__;
};
// 获取正确的资源路径
export function getAssetPath(path: string): string {
if (isMicroApp()) {
const baseUrl = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ || '/kg-ui/';
return `${baseUrl}${path}`;
}
return path;
}
6.4样式丢失,微前端模式下需要动态加载所有 CSS // 统一的 CSS 加载函数
async function loadAllCSS(): Promise<void> {
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
console.log('[KnowledgeBaseApp] 非微前端模式,CSS 会自动加载');
return;
}
console.log('[KnowledgeBaseApp] 🎨 微前端模式:开始加载所有 CSS 文件');
try {
const baseUrl = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__ || '/kg-ui/';
console.log('[KnowledgeBaseApp] 基础路径:', baseUrl);
// 检查是否已经加载过
if (document.querySelector('#kg-ui-all-styles-loaded')) {
console.log('[KnowledgeBaseApp] ✅ 样式已加载,跳过');
return;
}
// 直接从 index.html 获取所有 CSS 链接
const htmlResponse = await fetch(`${baseUrl}index.html`);
if (!htmlResponse.ok) {
throw new Error(`无法获取 index.html: ${htmlResponse.status}`);
}
const htmlText = await htmlResponse.text();
console.log('[KnowledgeBaseApp] 获取到 HTML 内容,长度:', htmlText.length);
// 提取所有 stylesheet 链接
const linkRegex = /<link[^>]*rel=["']stylesheet["'][^>]*>/g;
const linkMatches = htmlText.match(linkRegex);
if (!linkMatches || linkMatches.length === 0) {
console.warn('[KnowledgeBaseApp] ⚠️ 在 HTML 中未找到任何 stylesheet 链接');
return;
}
console.log('[KnowledgeBaseApp] 找到', linkMatches.length, '个 stylesheet 链接');
// 创建标记元素
const marker = document.createElement('div');
marker.id = 'kg-ui-all-styles-loaded';
marker.style.display = 'none';
document.head.appendChild(marker);
// 逐个加载每个 CSS 文件
let loadedCount = 0;
for (const linkTag of linkMatches) {
const hrefMatch = linkTag.match(/href=["']([^"']*)["']/);
if (!hrefMatch) {
console.warn('[KnowledgeBaseApp] 无法解析 href:', linkTag);
continue;
}
let href = hrefMatch[1];
console.log('[KnowledgeBaseApp] 原始 href:', href);
// 处理各种路径格式
if (href.startsWith('http://') || href.startsWith('https://')) {
// 绝对 URL,直接使用
} else if (href.startsWith('/kg-ui/')) {
// 已经包含基础路径,转换为相对路径
href = href.replace('/kg-ui/', '');
} else if (href.startsWith('./')) {
// 相对路径,去掉 ./
href = href.substring(2);
} else if (href.startsWith('/')) {
// 根路径,去掉开头的 /
href = href.substring(1);
}
const finalUrl = href.startsWith('http') ? href : `${baseUrl}${href}`;
console.log('[KnowledgeBaseApp] 最终 URL:', finalUrl);
try {
await loadSingleCSS(finalUrl, document.head);
loadedCount++;
console.log('[KnowledgeBaseApp] ✅ 成功加载 CSS:', finalUrl);
} catch (error) {
console.error('[KnowledgeBaseApp] ❌ CSS 加载失败:', finalUrl, error);
}
}
console.log('[KnowledgeBaseApp] ✅ CSS 加载完成,成功:', loadedCount, '个,总共:', linkMatches.length, '个');
} catch (error) {
console.error('[KnowledgeBaseApp] ❌ CSS 加载过程失败:', error);
}
}
// 简化的单个 CSS 加载函数
function loadSingleCSS(url: string, container: HTMLElement): Promise<void> {
return new Promise((resolve, reject) => {
// 检查是否已经加载过这个 URL
const existingLink = document.querySelector(`link[href="${url}"]`);
if (existingLink) {
console.log('[KnowledgeBaseApp] CSS 已存在,跳过:', url);
resolve();
return;
}
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
link.onload = () => {
resolve();
};
link.onerror = () => {
reject(new Error(`CSS 加载失败: ${url}`));
};
container.appendChild(link);
});
}
// 配置 Ant Design Vue 全局组件的函数
function configureAntdGlobalComponents(props: MicroAppProps) {
console.log('[KnowledgeBaseApp] 🔧 重新配置 Ant Design Vue 全局组件');
// 动态导入并配置全局组件
import('ant-design-vue').then(({ message, notification, Modal }) => {
// 配置消息组件 - 强制绑定到 body
if (message && message.config) {
message.config({
getContainer: () => document.body,
maxCount: 3,
duration: 3
});
console.log('[KnowledgeBaseApp] ✅ message 组件重新配置完成 - 绑定到 body');
}
// 配置通知组件 - 强制绑定到 body
if (notification && notification.config) {
notification.config({
getContainer: () => document.body,
placement: 'topRight'
});
console.log('[KnowledgeBaseApp] ✅ notification 组件重新配置完成 - 绑定到 body');
}
// 配置模态框组件 - 强制绑定到 body
if (Modal && Modal.config) {
Modal.config({
getContainer: () => document.body
});
console.log('[KnowledgeBaseApp] ✅ Modal 组件重新配置完成 - 绑定到 body');
}
}).catch(error => {
console.error('[KnowledgeBaseApp] ❌ 重新配置全局组件失败:', error);
});
}