cloud-web-corejs 1.0.273 → 1.0.274
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/package.json +2 -93
- package/src/views/user/menu/voc_list.vue +686 -0
- package/src/views/user/role/edit.vue +32 -9
- package/src/views/user/role/list.vue +145 -67
- package/src/views/user/role/voc_edit.vue +293 -0
- package/src/components/baseArea/index.vue +0 -1628
- package/src/components/micro-view-host/contract.js +0 -66
- package/src/components/micro-view-host/index.vue +0 -148
- package/src/microRouter/index.js +0 -447
- package/src/public-path.js +0 -38
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 详情承载契约(纯常量 + props 构造,无副作用,可完整单测)。
|
|
3
|
-
* micro-view-host(主/渲染方)与子应用「详情承载入口」共同遵守。
|
|
4
|
-
* 完整设计见 docs/qiankun动态详情页承载方案.md 第 5 节。
|
|
5
|
-
*
|
|
6
|
-
* 关键前提:qiankun loadMicroApp 的 props 是同一 JS realm 内的普通对象,
|
|
7
|
-
* 函数可直接传入并被子应用调用——故上行回调用函数 props,不需事件总线;
|
|
8
|
-
* 真正跨不过去的是 Vue 实例(parentVue)与 $parent 链,由子应用侧用 shim 合成。
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// props 标志:子应用 mount 时据此进入「详情承载」分支(而非常规整站渲染)
|
|
12
|
-
export const DETAIL_HOST_MODE = "microDetailHost";
|
|
13
|
-
|
|
14
|
-
// 上行回调字段名(函数 props)。子应用承载入口据此把详情页的
|
|
15
|
-
// $baseReload / _handleCallback / 关闭 / outParam.sync / 任意 listeners
|
|
16
|
-
// 转调回渲染方(见方案 §5.2 / §5.3 的 parentVue shim)。
|
|
17
|
-
export const DETAIL_HOST_CALLBACKS = [
|
|
18
|
-
"onReload",
|
|
19
|
-
"onHandleCallback",
|
|
20
|
-
"onClose",
|
|
21
|
-
"onUpdateOutParam",
|
|
22
|
-
"onEmit",
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 构造下发给子应用的 props。
|
|
27
|
-
*
|
|
28
|
-
* @param {object} input
|
|
29
|
-
* @param {string} input.appCode 归属子应用 appCode
|
|
30
|
-
* @param {string} input.targetPath 剥掉 /micro/<appCode> 后的子应用内路径
|
|
31
|
-
* @param {object} input.bizParams 业务字段整包(objTypeCode/formCode/dataId/param/queryParam…)
|
|
32
|
-
* @param {object} input.viewMeta 承载元数据(visibleKey/defaultShowWfContent/viewType)
|
|
33
|
-
* @param {*} input.initialOutParam outParam 初值(替代 .sync 入向)
|
|
34
|
-
* @param {object} input.baseProps 基础 props(token/lang/userInfo/mainAdapter…)
|
|
35
|
-
* @param {object} input.callbacks 上行回调;仅函数值会被收录
|
|
36
|
-
* @returns {object} 传给 loadMicroApp 的 props
|
|
37
|
-
*/
|
|
38
|
-
export function buildDetailHostProps(input = {}) {
|
|
39
|
-
const {
|
|
40
|
-
appCode,
|
|
41
|
-
targetPath,
|
|
42
|
-
bizParams,
|
|
43
|
-
viewMeta,
|
|
44
|
-
initialOutParam,
|
|
45
|
-
baseProps,
|
|
46
|
-
callbacks,
|
|
47
|
-
} = input;
|
|
48
|
-
|
|
49
|
-
const props = Object.assign({}, baseProps, {
|
|
50
|
-
[DETAIL_HOST_MODE]: true,
|
|
51
|
-
appCode: appCode || "",
|
|
52
|
-
targetPath: targetPath || "",
|
|
53
|
-
bizParams: bizParams || {},
|
|
54
|
-
viewMeta: viewMeta || {},
|
|
55
|
-
initialOutParam: initialOutParam,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const cb = callbacks || {};
|
|
59
|
-
DETAIL_HOST_CALLBACKS.forEach((name) => {
|
|
60
|
-
if (typeof cb[name] === "function") {
|
|
61
|
-
props[name] = cb[name];
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return props;
|
|
66
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div :id="containerId" class="micro-view-host" style="height: 100%"></div>
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script>
|
|
6
|
-
/**
|
|
7
|
-
* 详情承载宿主:在弹框/容器内用 qiankun loadMicroApp 装载归属子应用,
|
|
8
|
-
* 渲染其私有详情页(组件不在当前 bundle,本地 require 拿不到)。
|
|
9
|
-
* 契约见 ./contract.js 与 docs/qiankun动态详情页承载方案.md 第 5 节。
|
|
10
|
-
*
|
|
11
|
-
* ⚠ 依赖子应用侧实现「详情承载入口」(方案 §5.5):mount 时识别 props.microDetailHost,
|
|
12
|
-
* 按 targetPath 解析自身组件、用 bizParams/viewMeta 绑 props、装 parentVue shim 渲染。
|
|
13
|
-
* 在子应用支持该入口前,本组件装载出的仍是子应用默认渲染,未必是目标详情页。
|
|
14
|
-
* 因此暂不接入各弹框站点——它们仍走 $message 短期提示兜底。
|
|
15
|
-
*
|
|
16
|
-
* 多实例:宿主实例名带唯一后缀,与路由保活实例(name=appCode)隔离;
|
|
17
|
-
* 同一 appCode 的保活实例与若干宿主实例可并存。要求子应用容忍多实例并发。
|
|
18
|
-
*/
|
|
19
|
-
import { loadMicroApp } from "qiankun";
|
|
20
|
-
import { getToken } from "@base/utils/auth";
|
|
21
|
-
import { getAppRegistry, DEBUG_ENTRY } from "@base/microRouter";
|
|
22
|
-
import { buildDetailHostProps } from "./contract";
|
|
23
|
-
|
|
24
|
-
// 注册表一次会话内不变,缓存首个成功结果,避免每次开弹框都打逻辑参数接口
|
|
25
|
-
let registryCache = null;
|
|
26
|
-
function loadRegistry() {
|
|
27
|
-
if (!registryCache) {
|
|
28
|
-
registryCache = getAppRegistry().catch((e) => {
|
|
29
|
-
registryCache = null; // 失败不缓存,允许下次重试
|
|
30
|
-
return Promise.reject(e);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
return registryCache;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let hostSeq = 0;
|
|
37
|
-
|
|
38
|
-
export default {
|
|
39
|
-
name: "MicroViewHost",
|
|
40
|
-
props: {
|
|
41
|
-
// 归属子应用 appCode(helper 的 remote 结果透出)
|
|
42
|
-
owner: { type: String, required: true },
|
|
43
|
-
// 剥掉 /micro/<appCode> 后的子应用内路径
|
|
44
|
-
targetPath: { type: String, default: "" },
|
|
45
|
-
// 业务字段整包
|
|
46
|
-
bizParams: { type: Object, default: () => ({}) },
|
|
47
|
-
// 承载元数据
|
|
48
|
-
viewMeta: { type: Object, default: () => ({}) },
|
|
49
|
-
// outParam 初值
|
|
50
|
-
initialOutParam: { default: undefined },
|
|
51
|
-
},
|
|
52
|
-
data() {
|
|
53
|
-
hostSeq += 1;
|
|
54
|
-
return {
|
|
55
|
-
containerId: "micro-viewhost-" + this.owner + "-" + hostSeq,
|
|
56
|
-
instance: null,
|
|
57
|
-
failed: false,
|
|
58
|
-
};
|
|
59
|
-
},
|
|
60
|
-
mounted() {
|
|
61
|
-
this.load();
|
|
62
|
-
},
|
|
63
|
-
beforeDestroy() {
|
|
64
|
-
this.unmount();
|
|
65
|
-
},
|
|
66
|
-
methods: {
|
|
67
|
-
load() {
|
|
68
|
-
loadRegistry()
|
|
69
|
-
.then((apps) => {
|
|
70
|
-
if (this._isDestroyed || this._isBeingDestroyed) return;
|
|
71
|
-
const app = (apps || []).find((a) => a.appCode === this.owner);
|
|
72
|
-
if (!app || !app.entry) {
|
|
73
|
-
this.fail("未在应用注册表找到子应用或缺 entry:" + this.owner);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
// 逻辑参数 micro_debug_enabled=true 时,与路由整站装载一致:改用本机约定端口,
|
|
77
|
-
// 不动注册表 entry。本机联调 detail-host 时子应用 devServer 起在 17527 即可。
|
|
78
|
-
const entry = app.debugEnabled ? DEBUG_ENTRY : app.entry;
|
|
79
|
-
if (app.debugEnabled) {
|
|
80
|
-
// eslint-disable-next-line no-console
|
|
81
|
-
console.warn(
|
|
82
|
-
"[MicroViewHost] ⚠ 调试装载:" + this.owner + " → " + entry
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
this.mountApp(entry);
|
|
86
|
-
})
|
|
87
|
-
.catch((e) => {
|
|
88
|
-
this.fail("读取应用注册表失败", e);
|
|
89
|
-
});
|
|
90
|
-
},
|
|
91
|
-
mountApp(entry) {
|
|
92
|
-
const props = buildDetailHostProps({
|
|
93
|
-
appCode: this.owner,
|
|
94
|
-
targetPath: this.targetPath,
|
|
95
|
-
bizParams: this.bizParams,
|
|
96
|
-
viewMeta: this.viewMeta,
|
|
97
|
-
initialOutParam: this.initialOutParam,
|
|
98
|
-
baseProps: {
|
|
99
|
-
token: getToken(),
|
|
100
|
-
lang: localStorage.getItem("i18n-lang") || "",
|
|
101
|
-
},
|
|
102
|
-
callbacks: {
|
|
103
|
-
onReload: () => this.$emit("reload"),
|
|
104
|
-
onHandleCallback: (flag) => this.$emit("handle-callback", flag),
|
|
105
|
-
onClose: () => this.$emit("close"),
|
|
106
|
-
onUpdateOutParam: (v) => this.$emit("update-out-param", v),
|
|
107
|
-
onEmit: (name, ...args) => this.$emit("bridge-emit", name, args),
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
try {
|
|
111
|
-
this.instance = loadMicroApp({
|
|
112
|
-
name: this.owner + "@detailhost-" + this.containerId,
|
|
113
|
-
entry,
|
|
114
|
-
container: this.$el,
|
|
115
|
-
props,
|
|
116
|
-
});
|
|
117
|
-
const p = this.instance.mountPromise;
|
|
118
|
-
if (p && typeof p.then === "function") {
|
|
119
|
-
p.then(() => this.$emit("mounted")).catch((e) => {
|
|
120
|
-
this.fail("子应用挂载失败:" + this.owner, e);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
} catch (e) {
|
|
124
|
-
this.fail("loadMicroApp 失败:" + this.owner, e);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
unmount() {
|
|
128
|
-
const inst = this.instance;
|
|
129
|
-
if (!inst) return;
|
|
130
|
-
this.instance = null;
|
|
131
|
-
const p = inst.mountPromise || Promise.resolve();
|
|
132
|
-
p.then(() => inst.unmount()).catch(() => {});
|
|
133
|
-
},
|
|
134
|
-
fail(msg, err) {
|
|
135
|
-
this.failed = true;
|
|
136
|
-
// eslint-disable-next-line no-console
|
|
137
|
-
console.error("[MicroViewHost] " + msg, err || "");
|
|
138
|
-
this.$emit("load-error", this.owner);
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
</script>
|
|
143
|
-
|
|
144
|
-
<style scoped>
|
|
145
|
-
.micro-view-host {
|
|
146
|
-
width: 100%;
|
|
147
|
-
}
|
|
148
|
-
</style>
|
package/src/microRouter/index.js
DELETED
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* qiankun 主应用侧:应用注册表驱动的子应用注册。
|
|
3
|
-
* 注册表载体为逻辑参数 micro_app_registry(JSON 数组),
|
|
4
|
-
* 协议见 docs/qiankun微前端改造-实现方案.md 3.2 / 3.4。
|
|
5
|
-
* 由 permission store 在登录后菜单生成流程中调用(settings.microMainEnabled 门控)。
|
|
6
|
-
*/
|
|
7
|
-
import { loadMicroApp } from "qiankun";
|
|
8
|
-
import { Loading } from "element-ui";
|
|
9
|
-
import { getLogicParamValue } from "@base/api/user";
|
|
10
|
-
import {
|
|
11
|
-
getAccessPath,
|
|
12
|
-
validateRegistry,
|
|
13
|
-
resolveEntry,
|
|
14
|
-
swapMenuNamespace,
|
|
15
|
-
isDebugEnabled,
|
|
16
|
-
MICRO_SEGMENT,
|
|
17
|
-
MICRO_DEBUG_SEGMENT,
|
|
18
|
-
} from "@base/utils/menuAdapter";
|
|
19
|
-
import { getToken } from "@base/utils/auth";
|
|
20
|
-
import router from "@base/router";
|
|
21
|
-
|
|
22
|
-
const REGISTRY_PARAM_CODE = "micro_app_registry";
|
|
23
|
-
// 调试开关单独一条逻辑参数:与注册表生命周期不同,运维开关调试不必改注册表 JSON
|
|
24
|
-
const DEBUG_PARAM_CODE = "micro_debug_enabled";
|
|
25
|
-
|
|
26
|
-
const REGISTRY_TIMEOUT = 8000;
|
|
27
|
-
const CONTAINER_WRAPPER_ID = "Appmicro";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* 调试命名空间(/micro_debug/<appCode>/...)的约定入口。
|
|
31
|
-
* 调试态不落任何存储、不读 URL 查询参数——路径本身就是开关,因此入口只能是约定值。
|
|
32
|
-
* 各子应用把 devServer 端口固定为 17527 即可零配置被主应用装载。
|
|
33
|
-
*/
|
|
34
|
-
export const DEBUG_ENTRY = "http://localhost:17527/";
|
|
35
|
-
|
|
36
|
-
// 保活管理:appCode → { instance, el };离开子应用路由只隐藏容器不卸载,
|
|
37
|
-
// 切回直接显示并经 update 生命周期同步路由,页面状态(页签/表格/表单)得以保留
|
|
38
|
-
let microAppConfigs = {};
|
|
39
|
-
let aliveApps = {};
|
|
40
|
-
let routeHookInstalled = false;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 读一条逻辑参数的原始值;缺失 / 请求失败 / 超时一律回落空串,不阻塞登录流程。
|
|
44
|
-
*/
|
|
45
|
-
function readLogicParam(paramCode) {
|
|
46
|
-
const fetchPromise = new Promise((resolve) => {
|
|
47
|
-
getLogicParamValue({
|
|
48
|
-
data: { paramCode },
|
|
49
|
-
failMsg: false,
|
|
50
|
-
errorMsg: false,
|
|
51
|
-
modal: false,
|
|
52
|
-
success: (res) => resolve((res && res.objx) || ""),
|
|
53
|
-
}).catch(() => resolve(""));
|
|
54
|
-
});
|
|
55
|
-
const timeoutPromise = new Promise((resolve) => {
|
|
56
|
-
setTimeout(() => resolve(""), REGISTRY_TIMEOUT);
|
|
57
|
-
});
|
|
58
|
-
return Promise.race([fetchPromise, timeoutPromise]);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 读应用注册表 + 调试开关(两条逻辑参数并行取,互不依赖)。
|
|
63
|
-
* 注册表非法条目由 validateRegistry 跳过并在控制台显式报错;
|
|
64
|
-
* 调试开关取不到即视为关闭,标注在各 app 的 debugEnabled 上供下游使用。
|
|
65
|
-
*/
|
|
66
|
-
export function getAppRegistry() {
|
|
67
|
-
return Promise.all([
|
|
68
|
-
readLogicParam(REGISTRY_PARAM_CODE),
|
|
69
|
-
readLogicParam(DEBUG_PARAM_CODE),
|
|
70
|
-
]).then(([registryRaw, debugRaw]) => {
|
|
71
|
-
if (!registryRaw) return [];
|
|
72
|
-
const { apps, errors } = validateRegistry(registryRaw);
|
|
73
|
-
if (errors.length) {
|
|
74
|
-
console.error("[microRouter] 应用注册表存在非法条目(已跳过):", errors);
|
|
75
|
-
}
|
|
76
|
-
const debugEnabled = isDebugEnabled(debugRaw);
|
|
77
|
-
return apps.map((app) => Object.assign({}, app, { debugEnabled }));
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** 装载实例键:同一个 appCode 的正式态与调试态各自保活,可同时开着对比 */
|
|
82
|
-
function instanceKey(appCode, debug) {
|
|
83
|
-
return debug ? appCode + "@" + MICRO_DEBUG_SEGMENT : appCode;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 登记子应用(保活模式):不再走 registerMicroApps/start 的 activeRule 自动装卸,
|
|
88
|
-
* 改为路由钩子驱动的 loadMicroApp 手动装载——首次进入装载,离开只隐藏,切回显示并同步路由。
|
|
89
|
-
* options.subAppMenus 为 menuAdapter.dispatchMenus 的切分结果。
|
|
90
|
-
*/
|
|
91
|
-
export function registerSubApps(apps, options = {}) {
|
|
92
|
-
const subAppMenus = options.subAppMenus || {};
|
|
93
|
-
const mainAdapter = options.mainAdapter || createDefaultMainAdapter();
|
|
94
|
-
const userInfo = options.userInfo || null;
|
|
95
|
-
microAppConfigs = {};
|
|
96
|
-
(apps || []).forEach((app) => {
|
|
97
|
-
const menus = subAppMenus[app.appCode] || [];
|
|
98
|
-
const baseProps = {
|
|
99
|
-
appCode: app.appCode,
|
|
100
|
-
token: getToken(),
|
|
101
|
-
lang: localStorage.getItem("i18n-lang") || "",
|
|
102
|
-
userInfo,
|
|
103
|
-
mainAdapter,
|
|
104
|
-
};
|
|
105
|
-
const entry = resolveEntry(app.entry);
|
|
106
|
-
if (entry) {
|
|
107
|
-
microAppConfigs[instanceKey(app.appCode, false)] = {
|
|
108
|
-
name: app.appCode,
|
|
109
|
-
entry,
|
|
110
|
-
props: Object.assign({}, baseProps, { menus, microDebug: false }),
|
|
111
|
-
};
|
|
112
|
-
} else {
|
|
113
|
-
console.error(
|
|
114
|
-
"[microRouter] 子应用缺少当前环境 entry,正式态已跳过:" + app.appCode
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
// 调试态:入口固定为约定端口,菜单路径一并换到 /micro_debug/ 命名空间——
|
|
118
|
-
// 子应用据此生成的路由才与浏览器 hash 对得上(主子路径始终一致)。
|
|
119
|
-
// 注册表未显式 debugEnabled 的应用不建调试实例,其 /micro_debug/ 地址打不开
|
|
120
|
-
if (app.debugEnabled) {
|
|
121
|
-
microAppConfigs[instanceKey(app.appCode, true)] = {
|
|
122
|
-
name: instanceKey(app.appCode, true),
|
|
123
|
-
entry: DEBUG_ENTRY,
|
|
124
|
-
props: Object.assign({}, baseProps, {
|
|
125
|
-
menus: menus.map((menu) => swapMenuNamespace(menu, true)),
|
|
126
|
-
// 调试态才允许 URL 直达未配菜单的页面(见 permission.js 调试兜底路由)
|
|
127
|
-
microDebug: true,
|
|
128
|
-
}),
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
installRouteHook();
|
|
133
|
-
return Object.keys(microAppConfigs);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// 当前应处于激活态的实例键(等待容器期间用户可能又切走,异步续体据此丢弃过期操作)
|
|
137
|
-
let currentActiveKey = null;
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* 由路由推导装载实例:正式路由 meta 直接带 microKey;
|
|
141
|
-
* 调试通配路由只带 appCode 参数,在此拼出实例键。
|
|
142
|
-
*/
|
|
143
|
-
function resolveMicroKey(to) {
|
|
144
|
-
const meta = (to && to.meta) || {};
|
|
145
|
-
if (!meta.isMicroApp) return null;
|
|
146
|
-
if (meta.microDebugCatchAll) {
|
|
147
|
-
const appCode = (to.params && to.params.appCode) || "";
|
|
148
|
-
const key = appCode ? instanceKey(appCode, true) : "";
|
|
149
|
-
if (!microAppConfigs[key]) {
|
|
150
|
-
console.error(
|
|
151
|
-
"[microRouter] 无法装载调试实例:"
|
|
152
|
-
+ (appCode || "(空 appCode)")
|
|
153
|
-
+ "。请检查该 appCode 是否在应用注册表 "
|
|
154
|
-
+ REGISTRY_PARAM_CODE
|
|
155
|
-
+ " 里,以及逻辑参数 "
|
|
156
|
-
+ DEBUG_PARAM_CODE
|
|
157
|
-
+ " 是否为 true(默认关闭)"
|
|
158
|
-
);
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
return key;
|
|
162
|
-
}
|
|
163
|
-
return microAppConfigs[meta.microKey] ? meta.microKey : null;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function installRouteHook() {
|
|
167
|
-
if (routeHookInstalled) return;
|
|
168
|
-
routeHookInstalled = true;
|
|
169
|
-
router.afterEach((to) => {
|
|
170
|
-
const key = resolveMicroKey(to);
|
|
171
|
-
currentActiveKey = key;
|
|
172
|
-
Object.keys(aliveApps).forEach((code) => {
|
|
173
|
-
if (code !== key) {
|
|
174
|
-
deactivateMicroApp(code);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
if (key) {
|
|
178
|
-
activateMicroApp(key);
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// 刷新直接落在子应用路由时,afterEach 早于布局渲染(AppMain 懒加载 + showAppMain 延迟),
|
|
184
|
-
// 容器尚未进 DOM——轮询等待,超时显式报错
|
|
185
|
-
const WRAPPER_WAIT_INTERVAL = 100;
|
|
186
|
-
const WRAPPER_WAIT_MAX_TRIES = 100;
|
|
187
|
-
|
|
188
|
-
function waitForWrapper(tryCount = 0) {
|
|
189
|
-
const wrapper = document.getElementById(CONTAINER_WRAPPER_ID);
|
|
190
|
-
if (wrapper) return Promise.resolve(wrapper);
|
|
191
|
-
if (tryCount >= WRAPPER_WAIT_MAX_TRIES) {
|
|
192
|
-
console.error(
|
|
193
|
-
"[microRouter] 等待子应用容器 #" + CONTAINER_WRAPPER_ID + " 超时"
|
|
194
|
-
);
|
|
195
|
-
return Promise.resolve(null);
|
|
196
|
-
}
|
|
197
|
-
return new Promise((resolve) => {
|
|
198
|
-
setTimeout(() => {
|
|
199
|
-
resolve(waitForWrapper(tryCount + 1));
|
|
200
|
-
}, WRAPPER_WAIT_INTERVAL);
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function getContainerEl(wrapper, key) {
|
|
205
|
-
const id = CONTAINER_WRAPPER_ID + "-" + key.replace("@", "_");
|
|
206
|
-
let el = document.getElementById(id);
|
|
207
|
-
if (!el) {
|
|
208
|
-
el = document.createElement("div");
|
|
209
|
-
el.id = id;
|
|
210
|
-
el.style.height = "100%";
|
|
211
|
-
wrapper.appendChild(el);
|
|
212
|
-
}
|
|
213
|
-
return el;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function cleanupFailedMicroApp(key, alive, error) {
|
|
217
|
-
// 失败回调可能晚于登出/重新注册,只清理由本次装载创建且仍在缓存中的记录
|
|
218
|
-
if (aliveApps[key] !== alive) return;
|
|
219
|
-
delete aliveApps[key];
|
|
220
|
-
if (alive.el && alive.el.parentNode) {
|
|
221
|
-
alive.el.parentNode.removeChild(alive.el);
|
|
222
|
-
}
|
|
223
|
-
console.error(
|
|
224
|
-
"[microRouter] 子应用挂载失败,已清理缓存,可在下次进入时重试:" + key,
|
|
225
|
-
error
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function activateMicroApp(key) {
|
|
230
|
-
const config = microAppConfigs[key];
|
|
231
|
-
if (!config) return;
|
|
232
|
-
waitForWrapper().then((wrapper) => {
|
|
233
|
-
if (!wrapper) return;
|
|
234
|
-
if (currentActiveKey !== key) return; // 等待期间已切走
|
|
235
|
-
const el = getContainerEl(wrapper, key);
|
|
236
|
-
el.style.display = "";
|
|
237
|
-
const alive = aliveApps[key];
|
|
238
|
-
if (!alive) {
|
|
239
|
-
if (config.props.microDebug) {
|
|
240
|
-
console.warn(
|
|
241
|
-
"[microRouter] ⚠ 调试命名空间装载:"
|
|
242
|
-
+ config.name
|
|
243
|
-
+ " → "
|
|
244
|
-
+ config.entry
|
|
245
|
-
+ "(把地址里的 /"
|
|
246
|
-
+ MICRO_DEBUG_SEGMENT
|
|
247
|
-
+ "/ 改回 /"
|
|
248
|
-
+ MICRO_SEGMENT
|
|
249
|
-
+ "/ 即回到正式环境)"
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
// 子应用是另一套系统,首次装载要下载其全量资源、耗时明显,期间容器空白。
|
|
253
|
-
// loading 只盖内容区,不遮页签,也不挂子应用容器 el(el 会被 qiankun 写 innerHTML
|
|
254
|
-
// 清掉,且空 #Appmicro 高度为 0):挂到有尺寸、qiankun 不碰的 .app-main,
|
|
255
|
-
// 再把遮罩顶边下移到 #Appmicro 的位置(offsetTop = 固定表头里页签的占位高度,
|
|
256
|
-
// 动态读取不写死),从而避开顶部的页签栏。挂载成功 / 失败都关闭。
|
|
257
|
-
const appMain = wrapper.closest(".app-main");
|
|
258
|
-
const loadingInstance = Loading.service({
|
|
259
|
-
target: appMain || wrapper,
|
|
260
|
-
text: "加载中…",
|
|
261
|
-
// 全局 .el-loading-spinner 的转圈伪元素是 position:fixed(按全屏遮罩写的),
|
|
262
|
-
// 局部遮罩下会跑到视口中心、与文字错位,靠这个类在样式里纠正(见 style.scss)
|
|
263
|
-
customClass: "micro-loading",
|
|
264
|
-
});
|
|
265
|
-
if (appMain && loadingInstance.$el && loadingInstance.$el.style) {
|
|
266
|
-
loadingInstance.$el.style.top = wrapper.offsetTop + "px";
|
|
267
|
-
}
|
|
268
|
-
let instance;
|
|
269
|
-
try {
|
|
270
|
-
instance = loadMicroApp({
|
|
271
|
-
name: config.name,
|
|
272
|
-
entry: config.entry,
|
|
273
|
-
container: el,
|
|
274
|
-
props: config.props,
|
|
275
|
-
});
|
|
276
|
-
} catch (error) {
|
|
277
|
-
loadingInstance.close();
|
|
278
|
-
if (el.parentNode) {
|
|
279
|
-
el.parentNode.removeChild(el);
|
|
280
|
-
}
|
|
281
|
-
console.error("[microRouter] 子应用创建失败,可在下次进入时重试:" + key, error);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
const createdAlive = { el, instance };
|
|
285
|
-
aliveApps[key] = createdAlive;
|
|
286
|
-
if (
|
|
287
|
-
instance
|
|
288
|
-
&& instance.mountPromise
|
|
289
|
-
&& typeof instance.mountPromise.then === "function"
|
|
290
|
-
) {
|
|
291
|
-
instance.mountPromise
|
|
292
|
-
.then(() => loadingInstance.close())
|
|
293
|
-
.catch((error) => {
|
|
294
|
-
loadingInstance.close();
|
|
295
|
-
cleanupFailedMicroApp(key, createdAlive, error);
|
|
296
|
-
});
|
|
297
|
-
} else {
|
|
298
|
-
// 理论不会走到(loadMicroApp 恒返回带 mountPromise 的实例),兜底避免遮罩悬挂
|
|
299
|
-
loadingInstance.close();
|
|
300
|
-
}
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
// 已保活:显示容器并通知子应用恢复路由监听、同步当前 hash(含"切到本应用另一菜单"的深导航)
|
|
304
|
-
notifyMicroApp(alive.instance, { microActive: true });
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function deactivateMicroApp(key) {
|
|
309
|
-
const alive = aliveApps[key];
|
|
310
|
-
if (!alive) return;
|
|
311
|
-
alive.el.style.display = "none";
|
|
312
|
-
// 暂停子应用路由监听,防止隐藏期间对主应用的 URL 变化做出反应(回写 URL 互相打架)
|
|
313
|
-
notifyMicroApp(alive.instance, { microActive: false });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function notifyMicroApp(instance, props) {
|
|
317
|
-
if (!instance || typeof instance.update !== "function") return;
|
|
318
|
-
const mountPromise = instance.mountPromise || Promise.resolve();
|
|
319
|
-
mountPromise
|
|
320
|
-
.then(() => instance.update(props))
|
|
321
|
-
.catch((e) => {
|
|
322
|
-
console.error("[microRouter] 子应用 update 通知失败:", e);
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* 转发"刷新当前页"给子应用。
|
|
328
|
-
* 保活模式下主应用那套 delCachedView + /redirect 重建只作用于自己的 router-view,
|
|
329
|
-
* 子应用内容由 qiankun 挂在独立容器里,走一圈只是容器隐藏再显示、实例毫发无损,
|
|
330
|
-
* 表现为点刷新没反应——必须由子应用在自身路由内重建组件。
|
|
331
|
-
* 入参是当前路由(调试通配路由的实例键要从路由参数推导,不在 meta 里)。
|
|
332
|
-
* 返回是否已转发(false 表示该应用未装载,调用方回落自身刷新逻辑)。
|
|
333
|
-
*/
|
|
334
|
-
export function refreshMicroApp(route) {
|
|
335
|
-
const alive = aliveApps[resolveMicroKey(route)];
|
|
336
|
-
if (!alive) return false;
|
|
337
|
-
// 带时间戳:qiankun 对相同 props 也会调用 update,但时间戳能让子应用区分每次点击
|
|
338
|
-
notifyMicroApp(alive.instance, { microRefresh: Date.now() });
|
|
339
|
-
return true;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/** 真正卸载全部子应用(登出等场景按需调用;常规页签切换不卸载) */
|
|
343
|
-
export function unmountAllMicroApps() {
|
|
344
|
-
Object.keys(aliveApps).forEach((code) => {
|
|
345
|
-
const alive = aliveApps[code];
|
|
346
|
-
if (alive && alive.instance) {
|
|
347
|
-
alive.instance.unmount().catch(() => {});
|
|
348
|
-
}
|
|
349
|
-
if (alive && alive.el && alive.el.parentNode) {
|
|
350
|
-
alive.el.parentNode.removeChild(alive.el);
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
aliveApps = {};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* 每个子应用先按菜单生成精确路由以携带菜单标题等元数据,再追加通配兜底。
|
|
358
|
-
* 路由均不挂组件,内容由 qiankun 挂进 AppMain 的 #Appmicro 容器。
|
|
359
|
-
*
|
|
360
|
-
* 调试命名空间不按应用展开:精确路由只为菜单元数据(标题/树路径/重复打开)而生,
|
|
361
|
-
* 装载哪个实例只取决于 appCode——做成一条参数化通配即可,
|
|
362
|
-
* 子应用再多也还是一条,代价仅是调试页签标题不显示具体菜单名。
|
|
363
|
-
*/
|
|
364
|
-
export function buildSubAppRoutes(apps, subAppMenus = {}) {
|
|
365
|
-
const routes = [];
|
|
366
|
-
(apps || []).forEach((app) => {
|
|
367
|
-
const key = instanceKey(app.appCode, false);
|
|
368
|
-
const seenPaths = {};
|
|
369
|
-
(subAppMenus[app.appCode] || []).forEach((menu, index) => {
|
|
370
|
-
let path = getAccessPath(menu).split("?")[0];
|
|
371
|
-
if (!path) return;
|
|
372
|
-
if (!path.startsWith("/")) {
|
|
373
|
-
path = "/" + path;
|
|
374
|
-
}
|
|
375
|
-
if (seenPaths[path]) return;
|
|
376
|
-
seenPaths[path] = true;
|
|
377
|
-
routes.push({
|
|
378
|
-
path,
|
|
379
|
-
name: `micro-${app.appCode}-menu-${menu.id || menu.menuCode || index}`,
|
|
380
|
-
meta: {
|
|
381
|
-
title: menu.menuName || app.appName,
|
|
382
|
-
enTitle: menu.menuEnName || null,
|
|
383
|
-
treePathName: menu.treePathName,
|
|
384
|
-
repeatOpen: menu.repeatOpen,
|
|
385
|
-
isMicroApp: true,
|
|
386
|
-
appCode: app.appCode,
|
|
387
|
-
microKey: key,
|
|
388
|
-
},
|
|
389
|
-
});
|
|
390
|
-
});
|
|
391
|
-
routes.push({
|
|
392
|
-
path: "/" + MICRO_SEGMENT + "/" + app.appCode + "/*",
|
|
393
|
-
name: "micro-" + app.appCode,
|
|
394
|
-
meta: {
|
|
395
|
-
title: app.appName,
|
|
396
|
-
isMicroApp: true,
|
|
397
|
-
appCode: app.appCode,
|
|
398
|
-
microKey: key,
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
// 没有任何应用开启调试时连路由都不注册——/micro_debug/ 直接落 404,
|
|
403
|
-
// 而不是进到一个装载不了任何东西的空白页
|
|
404
|
-
if ((apps || []).some((app) => app.debugEnabled)) {
|
|
405
|
-
// 整个调试命名空间共用这一条;appCode 由路由参数带出,装载实例在 afterEach 里推导
|
|
406
|
-
routes.push({
|
|
407
|
-
path: "/" + MICRO_DEBUG_SEGMENT + "/:appCode/*",
|
|
408
|
-
name: "micro-" + MICRO_DEBUG_SEGMENT,
|
|
409
|
-
meta: {
|
|
410
|
-
title: "微前端调试",
|
|
411
|
-
isMicroApp: true,
|
|
412
|
-
microDebugCatchAll: true,
|
|
413
|
-
},
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
return routes;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// 子应用表单脚本跳主应用路由必须走该回调,不得直接摸主应用 router
|
|
420
|
-
function createDefaultMainAdapter() {
|
|
421
|
-
return {
|
|
422
|
-
navigate(path) {
|
|
423
|
-
const vueRoot = window.$vueRoot;
|
|
424
|
-
if (vueRoot && vueRoot.$router) {
|
|
425
|
-
// vue-router 3.0.2 的 push 返回 undefined(3.1.0 才返回 Promise),
|
|
426
|
-
// 直接 .catch 会抛 "reading 'catch'",仅在返回 Promise 时才吞异常
|
|
427
|
-
const nav = vueRoot.$router.push(path);
|
|
428
|
-
if (nav && typeof nav.catch === "function") {
|
|
429
|
-
nav.catch(() => {});
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
},
|
|
433
|
-
openTab(path) {
|
|
434
|
-
this.navigate(path);
|
|
435
|
-
},
|
|
436
|
-
closeTab(view) {
|
|
437
|
-
const vueRoot = window.$vueRoot;
|
|
438
|
-
if (vueRoot && vueRoot.$store) {
|
|
439
|
-
return vueRoot.$store.dispatch(
|
|
440
|
-
"tagsView/delView",
|
|
441
|
-
view || vueRoot.$route
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
return Promise.resolve();
|
|
445
|
-
},
|
|
446
|
-
};
|
|
447
|
-
}
|
package/src/public-path.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/* eslint-disable camelcase, no-undef */
|
|
2
|
-
/**
|
|
3
|
-
* qiankun 子应用模式下修正 webpack 运行时 publicPath,
|
|
4
|
-
* 必须在入口文件的第一行 import,早于其余任何模块。
|
|
5
|
-
*/
|
|
6
|
-
if (window.__POWERED_BY_QIANKUN__) {
|
|
7
|
-
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* API base 运行时解析:解决"被外部域名的主应用嵌入时,相对 baseURL 会误打主应用域名"。
|
|
12
|
-
* 消费应用的 vue.config.js 经 DefinePlugin 把每个 process.env.VUE_APP_BASE_API 编译成
|
|
13
|
-
* window.__resolveApiBase__("<原值>") 的调用,于是 request.js 及一切读该值的模块运行时按下述规则求值:
|
|
14
|
-
* 独立运行 → 原值(相对地址命中自身域名,行为不变)
|
|
15
|
-
* 子应用挂载:
|
|
16
|
-
* 原值是域名(绝对) → 原值(含协议相对 //host)
|
|
17
|
-
* 原值不是域名 → 子应用自身 origin + 原值("" → origin;"/gapi" → origin+"/gapi")
|
|
18
|
-
* 前提:API 与子应用静态资源同源部署(origin 即后端所在域名,路径前缀由原值补齐)。
|
|
19
|
-
* 该 helper 只接受调用点传入的原值作参数、绝不引用 process.env.VUE_APP_BASE_API,
|
|
20
|
-
* 以免被 DefinePlugin 递归改写成自我调用。
|
|
21
|
-
*/
|
|
22
|
-
if (!window.__resolveApiBase__) {
|
|
23
|
-
window.__resolveApiBase__ = function (raw) {
|
|
24
|
-
raw = raw || "";
|
|
25
|
-
if (!window.__POWERED_BY_QIANKUN__) return raw;
|
|
26
|
-
// http:// https:// 以及协议相对 //host 都算"是域名",原样返回
|
|
27
|
-
if (/^(https?:)?\/\//.test(raw)) return raw;
|
|
28
|
-
try {
|
|
29
|
-
var origin = new URL(
|
|
30
|
-
window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__,
|
|
31
|
-
window.location.href
|
|
32
|
-
).origin;
|
|
33
|
-
return origin + raw;
|
|
34
|
-
} catch (e) {
|
|
35
|
-
return raw;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|