SmartAdmin(后台系统)+MateChat (一个前端智能化场景解决方案UI库)的微前端架构
1、 技术选型
在AI快速发展的时代下,很多AI产品伴随而来,同时对后台系统+AI系统的需求也随之出现。
最近做的一个新的项目,是基于SmartAdmin(后台系统)+MateChat (一个前端智能化场景解决方案UI库)
经过与AI的探讨分析,决定采用微前端方式把这两个项目集成在一起
想要把两个不同项目集成在一起,最开始我有三种想法;
| 方案 | 复杂度 | 适用场景 | 优缺点 |
|---|---|---|---|
| 方案一:iframe | ⭐ 最简单 | 快速集成,两个项目独立维护 | ✅ 零耦合,互不干扰 ❌ 跨域通信稍麻烦 |
| 方案二:源码合并 | ⭐⭐⭐ 复杂 | 深度定制,需要修改 MateChat | ✅ 完全融合,无跨域问题 ❌ 维护成本高 |
| 方案三:微前端 | ⭐⭐⭐⭐ 最复杂 | 大型项目,需要完全隔离 | ✅ 技术先进,独立部署 ❌ 配置复杂 |
经过上面三种方案的对比分析,采用微前端的方式作为技术选型;
-
整体架构设计
┌─────────────────────────────────────────┐
│ SmartAdmin(主应用) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 系统管理 │ │ 数据报表 │ │ AI聊天 │ │ ← 菜单导航统一
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ┌─────────────────────────────────────┐│
│ │ ││
│ │ 【MateChat 子应用】 ││ ← 全屏区域
│ │ 独立运行,独立技术栈 ││
│ │ 左侧会话 + 右侧聊天 ││
│ │ ││
│ └─────────────────────────────────────┘│
└─────────────────────────────────────────┘ -
微前端的思想
1、把多个独立的前端应用组合成一个完整的应用,每个子应用可以独立开发、独立部署、独立运行,但用户感知上是一个整体;
2、可以把它看作是后端“微服务”思想在前端的延伸,将一个庞大复杂的“巨石”前端应用,拆分成多个独立、小巧的“微应用”。这些微应用可以由不同团队独立开发、独立部署,最后像搭积木一样组装成一个完整的产品; -
为什么要使用微前端
1、技术栈解耦:不再受限于单一框架。老旧模块用 Vue2,新模块用 React 或 Vue3,两者可以完美共存;
2、独立开发部署:各团队负责各自的模块,代码互不干扰。更新某个模块时只需单独部署,无需全量发布,极大降低上线风险;
3、渐进式重构:面对庞大的老项目,不需要一次性重写。可以按业务模块,一点一点地用新技术进行替换和升级;
4、提升协作效率:解决了多人共用一个代码仓库带来的频繁代码冲突和沟通成本; -
总结
微前端的核心不在于某项具体技术,而是一种“高内聚,低耦合”的架构思想
2、微前端技术选型与对比
单页应用(SPA)与微前端的适用场景分析
主流微前端方案对比:Module Federation、Single-SPA、Qiankun
选择 Qiankun 作为集成方案的理由(沙箱隔离、通信机制等)
| 项目 | 技术栈 | 关键特征 |
|---|---|---|
| SmartAdmin | Vue3 + Vite5 + Pinia + Ant Design Vue | 中后台管理系统,有完整的路由/权限体系 |
| MateChat | Vue3 + TypeScript + DevUI Design | AI对话UI组件库,支持按需引入 |
| 微前端方案 | Qiankun + vite-plugin-qiankun | 业界主流微前端方案,支持Vite |
基于 Single-SPA 封装,提供了开箱即用的 JS 沙箱和样式隔离,是目前国内最成熟的方案之一;
3、SmartAdmin 基础框架改造
1、主应用(SmartAdmin)的 Qiankun 初始化配置
- 安装 qiankun
cd smartadmin-project
npm install qiankun
- 创建微前端配置文件
// smartadmin-project/src/micro-apps.ts
import { registerMicroApps, start } from 'qiankun'
let started = false
export function initMicroApps() {
// 防止重复初始化
if (started) {
console.log('微前端已初始化,跳过')
return
}
registerMicroApps([
{
name: 'matechat-subapp',
entry: '//localhost:7100',
container: '#matechat-container',
activeRule: (location) => {
// hash 模式用 location.hash 判断
const hash = location.hash.replace('#', '')
console.log('当前hash:', hash)
return hash === '/ai-chat'
},
props: {
token: localStorage.getItem('token'),
userInfo: JSON.parse(localStorage.getItem('userInfo') || '{}')
}
}
])
start({
sandbox: false
})
started = true
console.log('微前端初始化完成')
}
这里一定要把start({ sandbox: false })设置为false,不然会报错
产生这个错误的原因是
- qiankun 为了隔离子应用,会重写(代理)浏览器原生 API,比如 window、document、addEventListener 等,它代理了 EventTarget.prototype.addEventListenerMateChat(或 DevUI 组件库)在代码中调用了 addEventListener,并传入了 { passive: true } 选项,qiankun 的代理代码试图修改这个 options 对象上的 passive 属性
- 但现代浏览器中,passive 是只读属性(read-only getter),不能通过赋值修改于是报错:Cannot set property passive
2、路由配置以及AI智能助手页面
-
路由配置
需要在SmartAdmin后台管理系统的菜单配置中,配置好AI智能助手的菜单;
-
AI智能助手页面
1、我想要的是点击AI智能助手打开新的浏览器窗口,并且不受SmartAdmin菜单栏的限制,子应用单独占用一整个页面的布局,而不是内嵌在SmartAdmin的layout里面;
所以在side-layout.vue文件中用v-if="route.path === ‘/ai-chat’"独立渲染布局
<template>
<!-- 独立页面:直接渲染内容,没有任何布局 -->
<template v-if="route.path === '/ai-chat'">
<div class="standalone-page">
<router-view />
</div>
</template>
<a-layout v-else class="admin-layout" style="min-height: 100%">
<!-- 侧边菜单 side-menu -->
<a-layout-sider
:id="LAYOUT_ELEMENT_IDS.menu"
class="side-menu"
:width="sideMenuWidth"
v-model:collapsed="collapsed"
:theme="theme"
v-show="!fullScreenFlag"
>
<!-- 左侧菜单 -->
<SideMenu :collapsed="collapsed" />
</a-layout-sider>
..........
</a-layout>
</template>
在recursion-menu.vue文件中的turnToPage方法中添加一下代码
// 页面跳转
function turnToPage(menu) {
// useUserStore().deleteKeepAliveIncludes(menu.menuId.toString());
// router.push({ path: menu.path });
if (menu.path === '/ai-chat') {
const origin = window.location.origin
const isHash = window.location.hash || router.options.history.base
const url = isHash ? `${origin}/#${menu.path}` : `${origin}${menu.path}`
const width = screen.availWidth
const height = screen.availHeight
window.open(
url,
'_blank',
)
return
}
useUserStore().deleteKeepAliveIncludes(menu.menuId.toString())
router.push({ path: menu.path })
}
通过这种方式点击AI智能助手菜单时,就可以在浏览器打开新的同源窗口,然后不受主应用layout布局的影响,全屏展示AI聊天页面。
3、智能助手页面,因为是打开新的浏览器窗口,所以 独立页面需要手动初始化微前端
initMicroApps()
<template>
<div class="ai-chat-standalone">
<div id="matechat-container"></div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { initMicroApps } from '../../micro-app'
onMounted(() => {
// 独立页面需要手动初始化微前端
initMicroApps()
})
</script>
<style scoped>
.ai-chat-standalone {
width: 100vw;
height: 100vh;
overflow: hidden;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
#matechat-container {
width: 100%;
height: 100%;
}
</style>
4、MateChat 子应用适配
1、安装必要依赖
cd matechat-subapp
# 1. 安装 MateChat 核心库
npm install @matechat/core @devui-design/icons vue-devui
# 2. 安装微前端依赖
npm install vite-plugin-qiankun --save-dev
# 3. 安装路由(如果需要)
npm install vue-router@4
2、修改 vite.config.ts
import path from "node:path";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import AutoImport from "unplugin-auto-import/vite";
import { defineConfig } from "vite";
import qiankun from 'vite-plugin-qiankun'
// https://vite.dev/config/
export default defineConfig({
base: "/",
plugins: [
vue(),
vueJsx(),
AutoImport({
include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/],
imports: ["vue"],
dirs: ["./src"],
}),
qiankun('matechat-subapp', { useDevMode: true })
],
server: {
host: true,
open: true,
port: 7100, //固定端口
cors: true,
headers: {
'Access-Control-Allow-Origin': '*'
}
},
resolve: {
alias: {
"@view": path.resolve(__dirname, "./src/view"),
"@": path.resolve(__dirname, "./src"),
},
},
build: {
target: 'esnext',
rollupOptions: {
output: {
format: 'umd', // qiankun 需要 UMD 格式
name: 'matechat-subapp',
entryFileNames: 'static/js/[name].js',
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.')
const ext = info[info.length - 1]
return `static/[ext]/[name][extname]`
},
},
},
},
/* 关键:静默 Sass @import 警告 */
css: {
preprocessorOptions: {
scss: {
quietDeps: true, // 不打印依赖里的警告
silenceDeprecations: ["import"], // 彻底关闭 import 弃用警告
},
},
},
});
3、修改 main.ts 为微前端入口,所有 import 之前执行,必须在子应用上打补丁 ,关闭沙箱注入
// ================== 必须在所有 import 之前执行 ==================
// 子应用补丁 关闭沙箱注入
// 保存原始方法
const originalAddEventListener = EventTarget.prototype.addEventListener
// 重写 addEventListener,拦截对 passive 的修改
EventTarget.prototype.addEventListener = function(
type: string,
listener: EventListenerOrEventListenerObject | null,
options?: boolean | AddEventListenerOptions
) {
// 如果 options 是对象,创建一个新的,避免修改原始对象
if (options && typeof options === 'object') {
// 用 Object.defineProperty 创建新对象,确保 writable
const newOptions: any = {}
// 复制所有属性
Object.keys(options).forEach(key => {
const descriptor = Object.getOwnPropertyDescriptor(options, key)
if (descriptor) {
Object.defineProperty(newOptions, key, {
value: descriptor.value,
writable: true,
enumerable: true,
configurable: true
})
}
})
return originalAddEventListener.call(this, type, listener, newOptions)
}
return originalAddEventListener.call(this, type, listener, options)
}
import { createPinia } from 'pinia';
import { createApp } from 'vue';
import './style.scss';
import MateChat from '@matechat/core';
import VueDevui from 'vue-devui';
import App from './App.vue';
import i18n from './i18n';
// 微前端辅助函数
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const pinia = createPinia();
let instance: any = null
function render(props: any = {}) {
const { container } = props
// 直接找 #app,不管在不在 container 里
const mountNode = container
? container.querySelector('#app')
: document.getElementById('app')
if (!mountNode) {
console.error('找不到 #app 节点')
return
}
instance = createApp(App)
instance.use(MateChat)
instance.use(pinia);
instance.use(VueDevui);
instance.use(i18n);
// 挂载到微前端容器或独立运行
instance.mount(mountNode || '#app')
}
// 独立运行时直接渲染
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render()
} else {
// 微前端环境下暴露生命周期
renderWithQiankun({
mount(props) {
console.log('MateChat 挂载', props)
render(props)
},
bootstrap() {
console.log('MateChat 启动')
},
update(props) {
console.log('MateChat 更新', props)
},
unmount() {
console.log('MateChat 卸载')
instance?.unmount()
instance = null
}
})
}
5、部署
- 服务器目录结构
├── /var/www/
│ ├── smartadmin/ # 主应用(SmartAdmin)
│ │ ├── index.html
│ │ └── assets/
│ │
│ └── matechat/ # 子应用(MateChat)
│ ├── index.html
│ └── assets/ - Nginx 配置示例
server {
listen 80;
server_name your-domain.com;
# 主应用 - SmartAdmin
location / {
root /var/www/smartadmin;
try_files $uri $uri/ /index.html;
}
# 子应用 - MateChat(独立访问,也供 qiankun 加载)
location /matechat/ {
root /var/www;
try_files $uri $uri/ /matechat/index.html;
# 必须加 CORS,否则 qiankun 跨域加载失败
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
}
}
-
访问方式
主应用:http://your-domain.com/
子应用:http://your-domain.com/matechat/ -
总结流程图
┌─────────────────┐ ┌─────────────────┐
│ 开发阶段 │ │ 部署阶段 │
├─────────────────┤ ├─────────────────┤
│ 1. 改 MateChat │ │ 1. npm run build │
│ vite.config │ │ (两个项目) │
│ 2. 改 main.ts │────▶│ 2. 上传 dist/ │
│ 暴露生命周期 │ │ 到服务器 │
│ 3. 改 SmartAdmin│ │ 3. Nginx 配置 │
│ 注册子应用 │ │ 4. 配置 CORS │
│ 4. 创建全屏页面 │ │ 5. 访问测试 │
└─────────────────┘ └─────────────────┘
6、项目集成中可能出现的前端问题
1、子应用的图标独立打开时能展示,但是集成到主应用通过微前端方式访问时,图标展示异常;
- 这是微前端集成中非常经典的字体文件加载问题
- MateChat 子应用独立运行时,字体文件(.woff / .ttf)的加载路径是相对于 localhost:7100 的,比如:
http://localhost:7100/static/fonts/devui-icon.woff2
但通过 qiankun 嵌入后,浏览器认为页面在 SmartAdmin 的域名下,字体文件的相对路径解析错误,或者因为跨域/路径解析问题导致加载失败。
| 情况 | 说明 |
|---|---|
| 独立运行正常 | font-family: 'devui-icon' 生效,.icon-add:before { content: '\e900' } 正常 |
| 微前端下失效 | qiankun 重写 CSS 选择器,如 [data-qiankun-matechat-subapp] .icon-add,但字体文件路径或 font-family 定义可能没被正确隔离 |
- 解决办法
在 SmartAdmin 安装图标包
cd /path/to/smartadmin
npm install @devui-design/icons
在 SmartAdmin 的 main.ts 中加一行:
import '@devui-design/icons/icomoon/devui-icon.css'
2、子应用中的一些资源文件,比如图片,无法正常加载;
- 这是因为微前端环境下,子应用的
public目录资源路径解析方式变了。
| 场景 | 路径解析 |
|---|---|
| 子应用独立运行 | http://localhost:7100/logo2x.svg |
| 微前端嵌入 | 浏览器认为页面在 SmartAdmin 域名下,../../../public/logo2x.svg 解析到 SmartAdmin 的根目录,找不到文件 |
解决方法:直接把所有子应用相关的资源复制到主应用
| 优点 | 缺点 |
|---|---|
| ✅ 简单直接,不用改 Vite 配置 | ❌ 资源需要手动同步,维护麻烦 |
| ✅ 没有跨域和路径问题 | ❌ 子应用独立运行时可能找不到资源 |
| ✅ 生产环境部署简单 | ❌ 资源重复,增加主应用体积 |
| ✅ 字体文件也能统一处理 | ❌ 更新子应用时需要同步更新主应用资源 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)