web-extend-plugin-vue2 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -28
- package/dist/index.cjs +141 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +140 -58
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +26 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# web-extend-plugin-vue2
|
|
2
2
|
|
|
3
|
-
面向 **Vue 2.6+** 与 **Vue Router 3** 的 Web 插件运行时:浏览器中拉取**插件清单**、加载入口脚本、执行 `activator(hostApi)`,并提供 **`ExtensionPoint
|
|
3
|
+
面向 **Vue 2.6+** 与 **Vue Router 3** 的 Web 插件运行时:浏览器中拉取**插件清单**、加载入口脚本、执行 `activator(hostApi)`,并提供 **`ExtensionPoint`**、动态路由注册与受控 **`getBridge()`**;侧栏菜单由宿主根据路由 **`meta`** 自行推导(`buildMenuDescriptorsFromRoutes`)。发布物为预构建的 **`dist/`**(CJS/ESM),类型见根目录 **`index.d.ts`**。
|
|
4
4
|
|
|
5
5
|
**Peer:`vue` ^2.6.14 \<3,`vue-router` ^3.5 \<4**(依赖 `router.addRoute`)。**
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ installWebExtendPluginVue2(Vue, router, {
|
|
|
33
33
|
}).catch(console.warn)
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
**Vue CLI + 已有 axios `request
|
|
36
|
+
**Vue CLI + 已有 axios `request`**(清单可走 Java,开发态可自动回退 `public/web-plugins/plugins.manifest.json`;**不再**默认注册 `/plugin` 壳,需显式 `ensurePluginHostRoute: true` 且配置 `pluginRoutesParentName` + `hostLayoutComponent`):
|
|
37
37
|
|
|
38
38
|
**更少样板(推荐)**:自动写入 `window.Vue`(IIFE 插件与 build-kit 约定)、合并 `hostContext`(`router` + 可选 `store`)、默认 `manifestListPath` 为 **`/frontend-plugins`**(与 `VUE_APP_BASE_API` 拼接;若后端为 `/api/frontend-plugins` 请在第 4 参数传 `manifestListPath: '/api/frontend-plugins'`)、`isDev` 与构建环境一致。按需关闭全局 Vue:`{ exposeGlobalVue: false }`。
|
|
39
39
|
|
|
@@ -49,15 +49,13 @@ installVueCliAxiosWebPlugins(
|
|
|
49
49
|
{
|
|
50
50
|
request,
|
|
51
51
|
hostLayoutComponent: Layout,
|
|
52
|
-
store
|
|
53
|
-
applyPluginMenuItems: ({ pluginId, items }) => {
|
|
54
|
-
/* 并入宿主侧栏 */
|
|
55
|
-
},
|
|
56
|
-
revokePluginMenuItems: (pluginId) => {
|
|
57
|
-
/* 撤销该插件菜单 */
|
|
58
|
-
}
|
|
52
|
+
store
|
|
59
53
|
},
|
|
60
|
-
{
|
|
54
|
+
{
|
|
55
|
+
manifestBase: process.env.VUE_APP_BASE_API,
|
|
56
|
+
pluginRoutesParentName: '__wepPluginHost',
|
|
57
|
+
ensurePluginHostRoute: true
|
|
58
|
+
}
|
|
61
59
|
).catch((e) => console.warn('[web-plugin] bootstrap failed', e))
|
|
62
60
|
```
|
|
63
61
|
|
|
@@ -83,11 +81,18 @@ import Layout from '@/layout'
|
|
|
83
81
|
installWebExtendPluginVue2(
|
|
84
82
|
Vue,
|
|
85
83
|
router,
|
|
86
|
-
createVueCliAxiosInstallOptions(
|
|
84
|
+
createVueCliAxiosInstallOptions(
|
|
85
|
+
{ request },
|
|
86
|
+
{
|
|
87
|
+
hostLayoutComponent: Layout,
|
|
88
|
+
pluginRoutesParentName: '__wepPluginHost',
|
|
89
|
+
ensurePluginHostRoute: true
|
|
90
|
+
}
|
|
91
|
+
)
|
|
87
92
|
).catch(console.warn)
|
|
88
93
|
```
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
若启用壳路由:`pluginRoutesParentName`(如 **`__wepPluginHost`**)+ `pluginMountPath`(默认 **`/plugin`**)+ `ensurePluginHostRoute: true`。未启用壳时,`registerRoutes` 的 `path` 为**绝对路径**或宿主在 `transformRoutes` 中自行加前缀。
|
|
91
96
|
|
|
92
97
|
**静态清单模式**(无需 Java `frontend-plugins`,将聚合 JSON 放入宿主 `public` 等静态目录):
|
|
93
98
|
|
|
@@ -144,11 +149,11 @@ bootstrapPlugins(router, (id, r, kit) => createHostApi(id, r, kit), runtime).cat
|
|
|
144
149
|
| `bridgeAllowedPathPrefixes` | `hostApi.getBridge().request(path)` 允许的 path 前缀 |
|
|
145
150
|
| `allowedScriptHosts` | 允许加载插件脚本的主机名白名单 |
|
|
146
151
|
| `isDev` / `webPluginDevOrigin` 等 | 开发态隐式 dev 入口与 SSE,见类型定义 |
|
|
147
|
-
| `hostLayoutComponent` |
|
|
148
|
-
| `pluginMountPath` / `pluginHostRouteMeta` / `ensurePluginHostRoute` | 壳路径(默认 `/plugin
|
|
149
|
-
| `pluginRoutesParentName` |
|
|
152
|
+
| `hostLayoutComponent` | 与 `ensurePluginHostRoute: true` 且配置了 `pluginRoutesParentName` 时用于自动注册壳路由的 Layout 组件 |
|
|
153
|
+
| `pluginMountPath` / `pluginHostRouteMeta` / `ensurePluginHostRoute` | 壳路径(默认 `/plugin`)、壳 meta、**仅在为 true 时**自动注册壳(默认 **false**) |
|
|
154
|
+
| `pluginRoutesParentName` | 命名父路由的 **name**;非空时 `registerRoutes` 子项通过 `addRoute(parentName, child)` 挂载 |
|
|
150
155
|
| `adaptRouteDeclarations` / `transformRoutes` / `interceptRegisterRoutes` | 路由 PRD 适配与宿主扩展流水线 |
|
|
151
|
-
| `
|
|
156
|
+
| `buildMenuDescriptorsFromRoutes`(具名导出 / `WebExtendPluginVue2.host`) | 由插件提交的 `RouteConfig` 树与 **`meta.title` / `meta.menuTitle`** 等推导菜单数据,供宿主写入 Vuex 等 |
|
|
152
157
|
| `hostContext` | **依赖注入(推荐)**:普通对象,引导时做**浅拷贝并浅冻结**后挂到 **`hostApi.hostContext`**,插件只读使用 `store` / `router` / 宿主开放 API 等,不污染 `HostApi` 顶层方法名 |
|
|
153
158
|
| `onBeforePluginActivate` / `onAfterPluginActivate` / `onPluginActivateError` | **生命周期**:激活前(可 `throw` 跳过该插件)、成功后、抛错后;便于埋点、门禁、监控,各宿主同一套签名即可接入 |
|
|
154
159
|
|
|
@@ -156,9 +161,10 @@ bootstrapPlugins(router, (id, r, kit) => createHostApi(id, r, kit), runtime).cat
|
|
|
156
161
|
|
|
157
162
|
### 宿主接入约定(跨项目统一)
|
|
158
163
|
|
|
159
|
-
1.
|
|
164
|
+
1. **菜单**:插件在 `registerRoutes` 的 **`meta`** 中写明 `title` / `order` / `icon` / `permission` / `hidden` 等;宿主在 `onAfterPluginActivate` 或统一收集阶段用 **`buildMenuDescriptorsFromRoutes(routes, pluginId)`** 生成侧栏数据(或由宿主在 `transformRoutes` 之后自行扫 `router.getRoutes()`)。**不再**使用 `registerMenuItems`。
|
|
160
165
|
2. **宿主能力**:通过 **`hostContext`** 注入(如 `{ store, router, getDict }`),插件内使用 **`hostApi.hostContext.store`**,避免 `Vue.prototype` 魔法与命名冲突。
|
|
161
|
-
3. **拦截激活**:在 **`onBeforePluginActivate`** 中做权限/租户校验,不通过则 **`throw`**;错误用 **`onPluginActivateError`** 上报,勿在 error 钩子里再抛异常。
|
|
166
|
+
3. **拦截激活**:在 **`onBeforePluginActivate`** 中做权限/租户校验,不通过则 **`throw`**;错误用 **`onPluginActivateError`** 上报,勿在 error 钩子里再抛异常。
|
|
167
|
+
4. **卸载**:`disposeWebPlugin(pluginId)` 会 **`removeRoute`** 移除该插件登记的动态路由,并清理槽位、脚本与 teardown;侧栏需按 **`pluginId`** 或重建菜单树与路由对齐。
|
|
162
168
|
|
|
163
169
|
---
|
|
164
170
|
|
|
@@ -170,19 +176,30 @@ bootstrapPlugins(router, (id, r, kit) => createHostApi(id, r, kit), runtime).cat
|
|
|
170
176
|
<ExtensionPoint point-id="app.toolbar.demo" />
|
|
171
177
|
```
|
|
172
178
|
|
|
173
|
-
|
|
179
|
+
**侧栏菜单**(`registries` 仅含扩展点 `slots`;菜单与路由同源):
|
|
174
180
|
|
|
175
181
|
```js
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
import { buildMenuDescriptorsFromRoutes } from 'web-extend-plugin-vue2'
|
|
183
|
+
|
|
184
|
+
// 插件 activator 内:
|
|
185
|
+
hostApi.registerRoutes([
|
|
186
|
+
{
|
|
187
|
+
path: 'hello',
|
|
188
|
+
component: Hello,
|
|
189
|
+
meta: { title: '插件 Hello', order: 10, pluginId: 'com.example' }
|
|
190
|
+
}
|
|
191
|
+
])
|
|
192
|
+
|
|
193
|
+
// 宿主在 onAfterPluginActivate 或自行约定的收集点:
|
|
194
|
+
const items = buildMenuDescriptorsFromRoutes(
|
|
195
|
+
[
|
|
196
|
+
/* 与插件注册前相同的 RouteConfig 快照,或由宿主在 transformRoutes 中缓存 */
|
|
197
|
+
],
|
|
198
|
+
pluginId
|
|
199
|
+
)
|
|
183
200
|
```
|
|
184
201
|
|
|
185
|
-
|
|
202
|
+
`disposeWebPlugin` 会移除对应动态路由;宿主从 state 中删除同一 `pluginId` 的菜单项即可。
|
|
186
203
|
|
|
187
204
|
**宿主上下文(插件读宿主能力)**:
|
|
188
205
|
|
|
@@ -198,7 +215,7 @@ hostContext: {
|
|
|
198
215
|
|
|
199
216
|
**路由扩展(宿主)**:在 `resolveRuntimeOptions` 里提供 `transformRoutes` 或 `adaptRouteDeclarations`,插件在 `activator` 第二个参数中取冻结的 **`pluginRecord`**(可含清单里的 `routeDeclarations`)。
|
|
200
217
|
|
|
201
|
-
|
|
218
|
+
**卸载插件**(含 `router.removeRoute` 批量撤销该插件登记的顶层路由名):
|
|
202
219
|
|
|
203
220
|
```js
|
|
204
221
|
import { disposeWebPlugin } from 'web-extend-plugin-vue2'
|
package/dist/index.cjs
CHANGED
|
@@ -344,15 +344,9 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
344
344
|
return String(DEF.devFallbackStaticManifestUrl).trim();
|
|
345
345
|
})();
|
|
346
346
|
const hostLayoutComponent = user.hostLayoutComponent;
|
|
347
|
-
const pluginRoutesParentName = (()
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
if (hostLayoutComponent != null) {
|
|
352
|
-
return String(DEF.pluginRoutesParentName).trim();
|
|
353
|
-
}
|
|
354
|
-
return '';
|
|
355
|
-
})();
|
|
347
|
+
const pluginRoutesParentName = user.pluginRoutesParentName !== undefined && String(user.pluginRoutesParentName).trim() !== ''
|
|
348
|
+
? String(user.pluginRoutesParentName).trim()
|
|
349
|
+
: '';
|
|
356
350
|
const pluginMountRaw = user.pluginMountPath !== undefined && String(user.pluginMountPath).trim() !== ''
|
|
357
351
|
? String(user.pluginMountPath).trim()
|
|
358
352
|
: String(resolveBundledEnv(EK.mountPath, '') || DEF.pluginMountPath).trim();
|
|
@@ -360,7 +354,7 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
360
354
|
const pluginHostRouteMeta = user.pluginHostRouteMeta !== undefined && user.pluginHostRouteMeta !== null
|
|
361
355
|
? user.pluginHostRouteMeta
|
|
362
356
|
: undefined;
|
|
363
|
-
const ensurePluginHostRoute = user.ensurePluginHostRoute
|
|
357
|
+
const ensurePluginHostRoute = user.ensurePluginHostRoute === true;
|
|
364
358
|
const devManifestFallback = (() => {
|
|
365
359
|
if (manifestMode === 'static') {
|
|
366
360
|
return false;
|
|
@@ -423,12 +417,6 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
423
417
|
...(typeof user.adaptRouteDeclarations === 'function'
|
|
424
418
|
? { adaptRouteDeclarations: user.adaptRouteDeclarations }
|
|
425
419
|
: {}),
|
|
426
|
-
...(typeof user.applyPluginMenuItems === 'function'
|
|
427
|
-
? { applyPluginMenuItems: user.applyPluginMenuItems }
|
|
428
|
-
: {}),
|
|
429
|
-
...(typeof user.revokePluginMenuItems === 'function'
|
|
430
|
-
? { revokePluginMenuItems: user.revokePluginMenuItems }
|
|
431
|
-
: {}),
|
|
432
420
|
...(user.hostContext !== undefined &&
|
|
433
421
|
user.hostContext !== null &&
|
|
434
422
|
typeof user.hostContext === 'object' &&
|
|
@@ -3220,21 +3208,18 @@ var semverExports = requireSemver();
|
|
|
3220
3208
|
var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
|
|
3221
3209
|
|
|
3222
3210
|
/**
|
|
3223
|
-
*
|
|
3211
|
+
* 引导时注入的 router 引用,供 disposeWebPlugin 按插件批量 removeRoute。
|
|
3224
3212
|
*/
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3213
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3214
|
+
let bootstrapRouter;
|
|
3215
|
+
/** 由 bootstrapPlugins 在浏览器环境调用 */
|
|
3216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3217
|
+
function setPluginBootstrapRouter(router) {
|
|
3218
|
+
bootstrapRouter = router;
|
|
3228
3219
|
}
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
revokePluginMenuItems(pluginId);
|
|
3233
|
-
}
|
|
3234
|
-
catch (e) {
|
|
3235
|
-
console.warn('[wep] revokePluginMenuItems failed', pluginId, e);
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3221
|
+
function getPluginBootstrapRouter() {
|
|
3222
|
+
return bootstrapRouter;
|
|
3238
3223
|
}
|
|
3239
3224
|
|
|
3240
3225
|
/**
|
|
@@ -3491,7 +3476,8 @@ async function fetchStaticManifestViaHttp(ctx) {
|
|
|
3491
3476
|
}
|
|
3492
3477
|
|
|
3493
3478
|
/**
|
|
3494
|
-
*
|
|
3479
|
+
* 当 `ensurePluginHostRoute === true` 且提供 `pluginRoutesParentName` + `hostLayoutComponent` 时,
|
|
3480
|
+
* 注册 `pluginMountPath` + Layout 的命名父路由,供 `router.addRoute(parentName, child)` 挂载插件页。
|
|
3495
3481
|
*/
|
|
3496
3482
|
function routeNameExists(router, name) {
|
|
3497
3483
|
if (!name) {
|
|
@@ -3518,7 +3504,7 @@ function walkRouteNames(routes, name) {
|
|
|
3518
3504
|
return false;
|
|
3519
3505
|
}
|
|
3520
3506
|
function ensurePluginHostRoute$1(router, opts) {
|
|
3521
|
-
if (opts.ensurePluginHostRoute
|
|
3507
|
+
if (opts.ensurePluginHostRoute !== true) {
|
|
3522
3508
|
return;
|
|
3523
3509
|
}
|
|
3524
3510
|
if (!router || typeof router.addRoute !== 'function') {
|
|
@@ -3647,7 +3633,7 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3647
3633
|
}
|
|
3648
3634
|
printRuntimeBannerOnce();
|
|
3649
3635
|
const opts = resolveRuntimeOptions$1(runtimeOptions || {});
|
|
3650
|
-
|
|
3636
|
+
setPluginBootstrapRouter(router);
|
|
3651
3637
|
ensurePluginHostRoute$1(router, opts);
|
|
3652
3638
|
const base = String(opts.manifestBase).replace(/\/$/, '');
|
|
3653
3639
|
const isStatic = opts.manifestMode === 'static';
|
|
@@ -3724,12 +3710,6 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3724
3710
|
: {}),
|
|
3725
3711
|
...(typeof opts.adaptRouteDeclarations === 'function'
|
|
3726
3712
|
? { adaptRouteDeclarations: opts.adaptRouteDeclarations }
|
|
3727
|
-
: {}),
|
|
3728
|
-
...(typeof opts.applyPluginMenuItems === 'function'
|
|
3729
|
-
? { applyPluginMenuItems: opts.applyPluginMenuItems }
|
|
3730
|
-
: {}),
|
|
3731
|
-
...(typeof opts.revokePluginMenuItems === 'function'
|
|
3732
|
-
? { revokePluginMenuItems: opts.revokePluginMenuItems }
|
|
3733
3713
|
: {})
|
|
3734
3714
|
};
|
|
3735
3715
|
if (!manifestResult.ok) {
|
|
@@ -4019,7 +3999,7 @@ var manifestComposer = /*#__PURE__*/Object.freeze({
|
|
|
4019
3999
|
|
|
4020
4000
|
/**
|
|
4021
4001
|
* 宿主侧响应式注册表:扩展点槽位(供布局与 `ExtensionPoint` 消费)。
|
|
4022
|
-
*
|
|
4002
|
+
* 菜单由宿主从路由 `meta` 推导(见 `buildMenuDescriptorsFromRoutes`),框架不维护平行菜单列表。
|
|
4023
4003
|
*/
|
|
4024
4004
|
const registries = Vue__default.default.observable({
|
|
4025
4005
|
slots: {},
|
|
@@ -4082,6 +4062,47 @@ function createRequestBridge(config = {}) {
|
|
|
4082
4062
|
};
|
|
4083
4063
|
}
|
|
4084
4064
|
|
|
4065
|
+
/**
|
|
4066
|
+
* 记录各插件通过 registerRoutes 挂到 router 上的顶层 route name,便于 dispose 时 removeRoute。
|
|
4067
|
+
*/
|
|
4068
|
+
const pluginIdToRouteNames = new Map();
|
|
4069
|
+
function recordPluginTopRouteNames(pluginId, names) {
|
|
4070
|
+
if (!pluginId || names.length === 0) {
|
|
4071
|
+
return;
|
|
4072
|
+
}
|
|
4073
|
+
const cur = pluginIdToRouteNames.get(pluginId) || [];
|
|
4074
|
+
pluginIdToRouteNames.set(pluginId, cur.concat(names));
|
|
4075
|
+
}
|
|
4076
|
+
/**
|
|
4077
|
+
* 从 matcher 移除该插件登记过的顶层路由(含其子树)。
|
|
4078
|
+
* 需 vue-router ≥3.5 的 removeRoute;否则静默跳过。
|
|
4079
|
+
*/
|
|
4080
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4081
|
+
function removeRegisteredRoutesForPlugin(router, pluginId) {
|
|
4082
|
+
const names = pluginIdToRouteNames.get(pluginId);
|
|
4083
|
+
if (!names || names.length === 0) {
|
|
4084
|
+
return;
|
|
4085
|
+
}
|
|
4086
|
+
if (!router || typeof router.removeRoute !== 'function') {
|
|
4087
|
+
console.warn('[wep] router.removeRoute 不可用,跳过动态路由卸载', pluginId);
|
|
4088
|
+
pluginIdToRouteNames.delete(pluginId);
|
|
4089
|
+
return;
|
|
4090
|
+
}
|
|
4091
|
+
for (let i = names.length - 1; i >= 0; i--) {
|
|
4092
|
+
try {
|
|
4093
|
+
router.removeRoute(names[i]);
|
|
4094
|
+
}
|
|
4095
|
+
catch (e) {
|
|
4096
|
+
console.warn('[wep] removeRoute failed', names[i], e);
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
pluginIdToRouteNames.delete(pluginId);
|
|
4100
|
+
}
|
|
4101
|
+
/** 测试或宿主高级场景:查询已登记 name(勿依赖顺序语义) */
|
|
4102
|
+
function getRegisteredTopRouteNamesForPlugin(pluginId) {
|
|
4103
|
+
return [...(pluginIdToRouteNames.get(pluginId) || [])];
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4085
4106
|
/**
|
|
4086
4107
|
* 构造插件 `activator(hostApi)` 使用的宿主 API:路由、菜单、扩展点、资源与受控请求桥。
|
|
4087
4108
|
*/
|
|
@@ -4137,6 +4158,7 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4137
4158
|
if (typeof router.addRoute !== 'function') {
|
|
4138
4159
|
throw new Error('[wep] vue-router 3.5+ 必需:请使用 router.addRoute(不再支持 addRoutes)');
|
|
4139
4160
|
}
|
|
4161
|
+
recordPluginTopRouteNames(pluginId, wrapped.map((r) => String(r.name)));
|
|
4140
4162
|
if (parentName) {
|
|
4141
4163
|
for (const r of wrapped) {
|
|
4142
4164
|
router.addRoute(parentName, r);
|
|
@@ -4216,16 +4238,6 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4216
4238
|
applyInternalRegister(configs);
|
|
4217
4239
|
}
|
|
4218
4240
|
},
|
|
4219
|
-
registerMenuItems(items) {
|
|
4220
|
-
const apply = hostKitOptions.applyPluginMenuItems;
|
|
4221
|
-
if (typeof apply !== 'function') {
|
|
4222
|
-
throw new Error('[wep] registerMenuItems 需要宿主在 resolveRuntimeOptions 中提供 applyPluginMenuItems,将菜单数据并入宿主侧栏/目录 state(框架不再维护 registries.menus)');
|
|
4223
|
-
}
|
|
4224
|
-
const list = Array.isArray(items) ? items : [];
|
|
4225
|
-
const enriched = list.map((item) => ({ ...item, pluginId }));
|
|
4226
|
-
enriched.sort((a, b) => (a.order != null ? Number(a.order) : 0) - (b.order != null ? Number(b.order) : 0));
|
|
4227
|
-
apply({ pluginId, items: enriched });
|
|
4228
|
-
},
|
|
4229
4241
|
registerSlotComponents(pointId, components) {
|
|
4230
4242
|
if (!pointId) {
|
|
4231
4243
|
return;
|
|
@@ -4269,15 +4281,15 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4269
4281
|
}
|
|
4270
4282
|
|
|
4271
4283
|
/**
|
|
4272
|
-
* 卸载单个插件:执行 teardown、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
|
|
4273
|
-
*
|
|
4284
|
+
* 卸载单个插件:执行 teardown、按登记 name 批量 removeRoute、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
|
|
4285
|
+
* 依赖 vue-router ≥3.5 的 removeRoute;引导时由 bootstrapPlugins 注入 router。
|
|
4274
4286
|
*/
|
|
4275
4287
|
function disposeWebPlugin(pluginId) {
|
|
4276
4288
|
if (!pluginId || typeof pluginId !== 'string') {
|
|
4277
4289
|
return;
|
|
4278
4290
|
}
|
|
4279
4291
|
runPluginTeardowns(pluginId);
|
|
4280
|
-
|
|
4292
|
+
removeRegisteredRoutesForPlugin(getPluginBootstrapRouter(), pluginId);
|
|
4281
4293
|
const slots = registries.slots;
|
|
4282
4294
|
for (const pointId of Object.keys(slots)) {
|
|
4283
4295
|
const list = slots[pointId];
|
|
@@ -4305,6 +4317,75 @@ function disposeWebPlugin(pluginId) {
|
|
|
4305
4317
|
}
|
|
4306
4318
|
}
|
|
4307
4319
|
|
|
4320
|
+
/**
|
|
4321
|
+
* 从路由配置(含 meta)推导侧栏/目录用菜单描述,与 registerRoutes 入参同源。
|
|
4322
|
+
*
|
|
4323
|
+
* 约定:
|
|
4324
|
+
* - `meta.menu === false`:该节点及其子树不参与菜单
|
|
4325
|
+
* - 节点出现条件:`meta.title` 或 `meta.menuTitle` 非空字符串,或存在非空的子菜单列表
|
|
4326
|
+
* - `meta.order` 数字越小越靠前;缺省为 0
|
|
4327
|
+
* - 可选:`meta.icon`、`meta.permission`、`meta.hidden`、`meta.external`
|
|
4328
|
+
*/
|
|
4329
|
+
function pickTitle(meta, route) {
|
|
4330
|
+
if (typeof meta.menuTitle === 'string' && meta.menuTitle) {
|
|
4331
|
+
return meta.menuTitle;
|
|
4332
|
+
}
|
|
4333
|
+
if (typeof meta.title === 'string' && meta.title) {
|
|
4334
|
+
return meta.title;
|
|
4335
|
+
}
|
|
4336
|
+
if (route.name != null && String(route.name)) {
|
|
4337
|
+
return String(route.name);
|
|
4338
|
+
}
|
|
4339
|
+
return String(route.path || '');
|
|
4340
|
+
}
|
|
4341
|
+
function sortDescriptors(items) {
|
|
4342
|
+
items.sort((a, b) => a.order - b.order || a.path.localeCompare(b.path));
|
|
4343
|
+
for (const x of items) {
|
|
4344
|
+
if (x.children && x.children.length) {
|
|
4345
|
+
sortDescriptors(x.children);
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
/**
|
|
4350
|
+
* 从插件侧 RouteConfig 树构建菜单描述(不含 component,便于并入宿主菜单 state)。
|
|
4351
|
+
*/
|
|
4352
|
+
function buildMenuDescriptorsFromRoutes(routes, pluginId) {
|
|
4353
|
+
const out = [];
|
|
4354
|
+
function walk(route) {
|
|
4355
|
+
const meta = (route.meta || {});
|
|
4356
|
+
if (meta.menu === false) {
|
|
4357
|
+
return null;
|
|
4358
|
+
}
|
|
4359
|
+
const chIn = Array.isArray(route.children) ? route.children : [];
|
|
4360
|
+
const childMenus = chIn.map(walk).filter(Boolean);
|
|
4361
|
+
const hasTitle = typeof meta.title === 'string' || typeof meta.menuTitle === 'string';
|
|
4362
|
+
if (!hasTitle && childMenus.length === 0) {
|
|
4363
|
+
return null;
|
|
4364
|
+
}
|
|
4365
|
+
const item = {
|
|
4366
|
+
path: String(route.path || ''),
|
|
4367
|
+
name: route.name,
|
|
4368
|
+
title: pickTitle(meta, route),
|
|
4369
|
+
order: meta.order != null ? Number(meta.order) : 0,
|
|
4370
|
+
...(pluginId ? { pluginId } : {}),
|
|
4371
|
+
...(meta.icon !== undefined ? { icon: meta.icon } : {}),
|
|
4372
|
+
...(meta.permission !== undefined ? { permission: meta.permission } : {}),
|
|
4373
|
+
...(meta.hidden === true ? { hidden: true } : {}),
|
|
4374
|
+
...(meta.external === true ? { external: true } : {}),
|
|
4375
|
+
...(childMenus.length ? { children: childMenus } : {})
|
|
4376
|
+
};
|
|
4377
|
+
return item;
|
|
4378
|
+
}
|
|
4379
|
+
for (const r of routes) {
|
|
4380
|
+
const m = walk(r);
|
|
4381
|
+
if (m) {
|
|
4382
|
+
out.push(m);
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
sortDescriptors(out);
|
|
4386
|
+
return out;
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4308
4389
|
/**
|
|
4309
4390
|
* 布局中的扩展点占位;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
|
|
4310
4391
|
* 样式由宿主全局 CSS 覆盖 `.extension-point` / `.plugin-point-error`。
|
|
@@ -4475,7 +4556,7 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4475
4556
|
function createVueCliAxiosQuickInstallOptions(
|
|
4476
4557
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4477
4558
|
router, deps, extra = {}) {
|
|
4478
|
-
const { request, hostLayoutComponent, store, hostContext: depHostContext
|
|
4559
|
+
const { request, hostLayoutComponent, store, hostContext: depHostContext } = deps;
|
|
4479
4560
|
const { hostContext: extraHostContext, isDev: extraIsDev, manifestListPath: extraListPath, ...restExtra } = extra;
|
|
4480
4561
|
const hostContext = {
|
|
4481
4562
|
router,
|
|
@@ -4489,8 +4570,6 @@ router, deps, extra = {}) {
|
|
|
4489
4570
|
return createVueCliAxiosInstallOptions({ request }, {
|
|
4490
4571
|
hostLayoutComponent,
|
|
4491
4572
|
hostContext,
|
|
4492
|
-
applyPluginMenuItems,
|
|
4493
|
-
revokePluginMenuItems,
|
|
4494
4573
|
isDev: extraIsDev !== undefined ? Boolean(extraIsDev) : resolveBundledIsDev(),
|
|
4495
4574
|
manifestListPath,
|
|
4496
4575
|
...restExtra
|
|
@@ -4511,7 +4590,8 @@ const presetVueCliAxios = Object.freeze({
|
|
|
4511
4590
|
*/
|
|
4512
4591
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4513
4592
|
function installVueCliAxiosWebPlugins(Vue, router, deps, extra) {
|
|
4514
|
-
|
|
4593
|
+
/** 避免输出 `?.`:Vue CLI 4 / Webpack 4 默认不转译 node_modules */
|
|
4594
|
+
const exposeGlobalVue = extra == null || extra.exposeGlobalVue !== false;
|
|
4515
4595
|
if (exposeGlobalVue && typeof window !== 'undefined') {
|
|
4516
4596
|
window.Vue = Vue;
|
|
4517
4597
|
}
|
|
@@ -4540,7 +4620,9 @@ const WebExtendPluginVue2 = Object.freeze({
|
|
|
4540
4620
|
createHostApi,
|
|
4541
4621
|
disposeWebPlugin,
|
|
4542
4622
|
createRequestBridge,
|
|
4543
|
-
registries
|
|
4623
|
+
registries,
|
|
4624
|
+
buildMenuDescriptorsFromRoutes,
|
|
4625
|
+
getRegisteredTopRouteNamesForPlugin
|
|
4544
4626
|
}),
|
|
4545
4627
|
config: Object.freeze({
|
|
4546
4628
|
defaultWebExtendPluginRuntime,
|
|
@@ -4568,6 +4650,7 @@ exports.HOST_PLUGIN_API_VERSION = HOST_PLUGIN_API_VERSION;
|
|
|
4568
4650
|
exports.RUNTIME_CONSOLE_LABEL = RUNTIME_CONSOLE_LABEL;
|
|
4569
4651
|
exports.WebExtendPluginVue2 = WebExtendPluginVue2;
|
|
4570
4652
|
exports.bootstrapPlugins = bootstrapPlugins;
|
|
4653
|
+
exports.buildMenuDescriptorsFromRoutes = buildMenuDescriptorsFromRoutes;
|
|
4571
4654
|
exports.composeManifestFetch = composeManifestFetch;
|
|
4572
4655
|
exports.createHostApi = createHostApi;
|
|
4573
4656
|
exports.createRequestBridge = createRequestBridge;
|
|
@@ -4580,6 +4663,7 @@ exports.defaultVueCliJavaManifestListPath = defaultVueCliJavaManifestListPath;
|
|
|
4580
4663
|
exports.defaultWebExtendPluginRuntime = defaultWebExtendPluginRuntime;
|
|
4581
4664
|
exports.disposeWebPlugin = disposeWebPlugin;
|
|
4582
4665
|
exports.ensurePluginHostRoute = ensurePluginHostRoute;
|
|
4666
|
+
exports.getRegisteredTopRouteNamesForPlugin = getRegisteredTopRouteNamesForPlugin;
|
|
4583
4667
|
exports.installVueCliAxiosWebPlugins = installVueCliAxiosWebPlugins;
|
|
4584
4668
|
exports.installWebExtendPluginVue2 = installWebExtendPluginVue2;
|
|
4585
4669
|
exports.manifestFetchCacheMiddleware = manifestFetchCacheMiddleware;
|