多端前端后台管理
远程应用接入
后台前端最特殊的一点:它在构建期不可能知道某个租户装了哪些应用。应用前端因此不能被打进宿主 bundle,只能在运行期按后端清单 import()。
这一页讲清楚三件事:加载链路、硬约定、坏了怎么查。
加载链路
① 用户登录
↓
② 宿主启动,安装运行时:installAppMarketRuntime()
→ globalThis.__APP_MARKET_RUNTIME__ = { vue, router, pinia, elementPlus, request, modal, ... }
↓
③ 问后端:GET /app/runtime/remote-apps
→ [{ appCode: 'exam', entry: '/remote-apps/exam/index.js?v=1.2.0' }, ...]
↓
④ 动态 import(entry)
→ 远程模块导出 install(runtime) / default
↓
⑤ 远程应用返回 { manifest, views }
→ loader 注册视图键与 vue-router 路由
↓
⑥ 菜单 component = "remote:exam/settings" 命中视图键
→ 渲染远程页面对应源码:
| 阶段 | 文件 |
|---|---|
| 安装运行时 | src/plugins/appmarket/runtime.ts |
| 拉入口清单 | src/plugins/appmarket/bootstrap.ts |
| 加载 / 注册 / 卸载 | src/plugins/appmarket/loader.ts |
| 常量约定 | src/plugins/appmarket/constants.ts |

远程应用加载链路(示意图占位)
/images/docs/admin-remote-app.png可画:宿主 → 后端入口清单 → ESM 产物 → 视图/路由注册 → 菜单渲染。
三个关键约定
1. 菜单只写视图键
remote:<appCode>/<viewKey>例如 remote:exam/exam/settings:
appCode=examviewKey=exam/settings
remote: 前缀是显式声明,用来区分「宿主页面但路径写错」和「由远程应用提供」。没有这个前缀的 component 一律按宿主页面处理。
2. 入口 URL 必须带 ?v=<version>
/remote-apps/exam/index.js?v=1.2.0不带版本号时,升级后浏览器会继续吃旧缓存,表现是「升级成功了,但页面还是老样子」。
3. 裸模块必须走 import map
远程产物里不会打进 Vue / Element Plus,import { ref } from 'vue' 由 index.html 的 import map 指到 /app-runtime/vue.js,垫片再转发到宿主正在用的那一份实例。
<script type="importmap">
{
"imports": {
"vue": "/app-runtime/vue.js",
"vue-router": "/app-runtime/vue-router.js",
"pinia": "/app-runtime/pinia.js",
"element-plus": "/app-runtime/element-plus.js",
"@dromara/request": "/app-runtime/dromara-request.js",
"@dromara/components": "/app-runtime/dromara-components.js",
"@dromara/modal": "/app-runtime/dromara-modal.js"
}
}
</script>这不是可选优化:如果远程应用用了另一份 Vue,会出现「双 Vue」——响应式、组件注册、inject 全部失效。
按需加载与等待
后端菜单可能先返回、应用产物后到。loader.ts 的处理是:
- 视图未注册时,先用
entryResolver(appCode)向 bootstrap 要入口; import()后注册视图,再唤醒waitForRemoteView的等待方;- 等待超时默认
8000ms(REMOTE_VIEW_WAIT_TIMEOUT)。
所以「刷新后第一次点菜单慢半拍」是正常的;一直转圈才是问题。
排查表
| 现象 | 根因 | 处理 |
|---|---|---|
应用 xxx 未安装或入口地址未配置 | 后端清单没有该应用,或应用未启用 | 去「我的应用」确认已安装并启用 |
Failed to fetch dynamically imported module | 产物 404:没构建、目录名不对、?v= 指到旧文件 | 检查 app-market-dist/remote-apps/<appCode>/index.js |
远程应用未导出安装函数 | 产物入口不是 install() / default | 检查应用的 src/index.ts |
视图键冲突 | 两个应用用了同一个 viewKey | 改成 <appCode>/<view> 命名空间 |
页面空白,控制台报 Component is missing template | 视图加载器返回了 defineAsyncComponent 包装器 | 按 loader 约定返回 { default: Component } |
双 Vue / inject 失效 | import map 或垫片被改 | 重跑 npm run gen:runtime && npm run check:runtime |
更底层的运行时机制见 前端远程应用(宿主侧)。