vue2/vue3微前端(QianKun)改造,走过的那些坑。

54 阅读4分钟

背景

已有项目A(vue2,element ui,webpack)需要引用B项目(vue2,element ui,webpack)和C项目(vue3、ant,ant design vue,vite)的部分页面,已经用iframe实现。但是领导看了效果后说加载太慢体验感不好,说是否能用组件嵌入。调研发现vue2工程打包成公共组件包,方案可行但是也需要拆分出来单独管理,需要管理两份代码不满足要求。vue3工程打包成公共组件包嵌入vue2工程方案不可行。和同学聊了下发现微前端可以解决单前困惑,就此踏上微前端(QianKun)坑路。

微前端选型调研

image.png 根据各个微前端的优缺点结合单前需求是改造久项目选择了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 框架实现微前端架构。

一、核心改造内容

  1. 依赖安装
"vite-plugin-qiankun": "^1.0.15"  // qiankun 的 Vite 插件
 "qiankun": "^2.10.16", //qiankun 插件
  1. 主入口文件改造 (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] 子应用更新');
    }
  });
};
  1. 路由改造、微前端模式使用内存路由,独立模式使用 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);
  });
}