cloud-web-corejs 1.0.54-dev.781 → 1.0.54-dev.783
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 +1 -1
- package/src/components/VabUpload/index.vue +242 -242
- package/src/components/VabUpload/mixins.js +1971 -1971
- package/src/components/VabUpload/privateProfileDialog.vue +2 -2
- package/src/components/hiprint/view/design/index.vue +7 -2
- package/src/components/obsUpload/index.vue +234 -234
- package/src/components/obsUpload/mixins.js +1542 -1542
- package/src/components/vb-tabs/x-tabs.vue +7 -3
- package/src/components/xform/docs//346/240/221/350/241/250rowField/346/224/266/346/225/233-/350/220/275/345/234/260/346/226/207/346/241/243.md +1427 -0
- package/src/components/xform/form-designer/form-widget/dialog/formDialog.vue +2 -2
- package/src/components/xform/form-designer/form-widget/dialog/formDrawer.vue +2 -2
- package/src/components/xform/form-designer/form-widget/dialog/searchFormDialog.vue +2 -2
- package/src/components/xform/form-designer/form-widget/field-widget/copy_button-widget.vue +11 -2
- package/src/components/xform/form-designer/form-widget/field-widget/vabUpload-widget.vue +369 -369
- package/src/components/xform/form-designer/setting-panel/property-editor/fileTypesSelect.vue +69 -23
- package/src/components/xform/form-render/container-item/data-table-item.vue +1 -1
- package/src/components/xform/form-render/container-item/data-table-mixin.js +1469 -428
- package/src/components/xform/form-render/indexMixin.js +379 -263
- package/src/components/xform/mixins/defaultHandle.js +234 -28
- package/src/components/xform/mixins/scriptHttp.js +4 -4
- package/src/components/xform/utils/treeTableUtil.js +1265 -0
- package/src/components/xform/utils/util.js +29 -14
- package/src/microRouter/index.js +3 -0
- package/src/mixins/tableTree/index.js +77 -14
- package/src/store/config/index.js +28 -28
- package/src/utils/menuAdapter.js +277 -277
- package/src/utils/resolveViewComponent.js +203 -203
- package/src/utils/vab.js +6 -3
- package/src/views/bd/setting/formVersion/ftHistoryDialog.vue +9 -2
- package/src/views/user/common_attribute/list.vue +1 -1
- package/src/views/user/form/vform/render.vue +17 -2
- package/src/views/user/form/view/list.vue +11 -10
- package/src/views/user/wf/wfReport/index.vue +625 -625
package/src/utils/menuAdapter.js
CHANGED
|
@@ -1,277 +1,277 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 微前端菜单适配层(零依赖纯函数)。
|
|
3
|
-
* 供引包应用与未引包主应用(deep-import 本文件)共用,
|
|
4
|
-
* 禁止在此引入 @base 的 store / router / request / 组件。
|
|
5
|
-
* 协议说明见 docs/qiankun微前端改造-实现方案.md 第三章。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// 表单菜单(动态表单)类型值,菜单归属识别的唯一权威标识
|
|
9
|
-
export const FORM_MENU_TYPE = 5;
|
|
10
|
-
|
|
11
|
-
// url 命名空间保留首段:/base/xxx 表示组件在 npm 包内(@base/views/xxx)
|
|
12
|
-
export const BASE_SEGMENT = "base";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 微前端固定前缀:访问路径形如 /micro/<appCode>/<来源>/<组件路径>。
|
|
16
|
-
* 用固定前缀而非"首段即 appCode",是为了让归属命名空间与主应用自身的
|
|
17
|
-
* 一级视图目录(/user、/pmd、/yx…)结构性隔离——否则注册一个与视图目录
|
|
18
|
-
* 同名的 appCode 会劫持主应用整棵子树,且新增视图目录还会反向撞掉已注册的应用。
|
|
19
|
-
*/
|
|
20
|
-
export const MICRO_SEGMENT = "micro";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 本机调试命名空间:/micro_debug/<appCode>/... 与 /micro/<appCode>/... 归属相同、
|
|
24
|
-
* 菜单相同、组件解析相同,**只有 entry 不同**(正式取注册表,调试取约定端口)。
|
|
25
|
-
* 把"调试还是正式"编码进路径而非存储,是为了让调试态无状态——不会残留在
|
|
26
|
-
* localStorage 里跨天生效,刷新/分享/多标签页的行为都由 URL 自身决定。
|
|
27
|
-
*/
|
|
28
|
-
export const MICRO_DEBUG_SEGMENT = "micro_debug";
|
|
29
|
-
|
|
30
|
-
const MICRO_SEGMENTS = [MICRO_SEGMENT, MICRO_DEBUG_SEGMENT];
|
|
31
|
-
const BASE_VIEW_PREFIX = "@base/views";
|
|
32
|
-
const APP_CODE_REG = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
33
|
-
|
|
34
|
-
export function isFormMenu(menu) {
|
|
35
|
-
return !!menu && menu.type === FORM_MENU_TYPE;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 访问路径 = route || url(与 createRoute3 现有取值逻辑一致)
|
|
40
|
-
*/
|
|
41
|
-
export function getAccessPath(menu) {
|
|
42
|
-
if (!menu) return "";
|
|
43
|
-
return String(menu.route || menu.url || "");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 取路径首段(不含查询串);@ 开头的存量组件路径无首段语义,返回 ""
|
|
48
|
-
*/
|
|
49
|
-
function getFirstSegment(path) {
|
|
50
|
-
return getSegments(path)[0] || "";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** 切分路径为段数组(不含查询串);@ 开头的存量组件路径无首段语义,返回 [] */
|
|
54
|
-
function getSegments(path) {
|
|
55
|
-
if (!path || typeof path !== "string" || path.startsWith("@")) {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
return path.split("?")[0].split("/").filter(Boolean);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 取访问路径里的归属 appCode:仅 /micro/<appCode>/... 或
|
|
63
|
-
* /micro_debug/<appCode>/... 形态才有归属,其余(主应用自身路由、
|
|
64
|
-
* /base 命名空间、裸路径)一律返回 ""。
|
|
65
|
-
*/
|
|
66
|
-
export function getOwnerSegment(path) {
|
|
67
|
-
const segs = getSegments(path);
|
|
68
|
-
if (segs.length < 2 || MICRO_SEGMENTS.indexOf(segs[0]) < 0) return "";
|
|
69
|
-
return segs[1];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* 切换访问路径的微前端命名空间(/micro/ ⇄ /micro_debug/),非微前端路径原样返回。
|
|
74
|
-
* 用途:主应用在调试命名空间下装载子应用时,把下发的菜单路径一并换过去——
|
|
75
|
-
* 子应用据此生成的路由才与浏览器 hash 对得上(主子路径始终保持一致)。
|
|
76
|
-
*/
|
|
77
|
-
export function swapMicroNamespace(path, debug) {
|
|
78
|
-
const segs = getSegments(path);
|
|
79
|
-
if (segs.length < 2 || MICRO_SEGMENTS.indexOf(segs[0]) < 0) return path;
|
|
80
|
-
const target = debug ? MICRO_DEBUG_SEGMENT : MICRO_SEGMENT;
|
|
81
|
-
if (segs[0] === target) return path;
|
|
82
|
-
return "/" + target + stripFirstSegment(path);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* 按调试与否重写整条菜单的访问路径命名空间(浅拷贝,不改原对象)。
|
|
87
|
-
* route 有值改 route,否则改 url——与 getAccessPath 的取值口径一致;
|
|
88
|
-
* 单字段写法里 url 同时是组件路径,但 resolveComponentPath 对两个命名空间
|
|
89
|
-
* 都会剥前缀,所以换过去不影响组件解析。
|
|
90
|
-
*/
|
|
91
|
-
export function swapMenuNamespace(menu, debug) {
|
|
92
|
-
if (!menu) return menu;
|
|
93
|
-
const field = menu.route ? "route" : "url";
|
|
94
|
-
const swapped = swapMicroNamespace(String(menu[field] || ""), debug);
|
|
95
|
-
if (swapped === menu[field]) return menu;
|
|
96
|
-
return Object.assign({}, menu, { [field]: swapped });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 解析菜单归属:访问路径的 /micro/<appCode>(或 /micro_debug/<appCode>)
|
|
101
|
-
* 命中注册表则归属该子应用,否则(无前缀 / base / 未命中)归主应用,返回 ""。
|
|
102
|
-
*/
|
|
103
|
-
export function resolveOwner(menu, registry) {
|
|
104
|
-
const seg = getOwnerSegment(getAccessPath(menu));
|
|
105
|
-
if (!seg || seg === BASE_SEGMENT) return "";
|
|
106
|
-
const apps = normalizeRegistry(registry);
|
|
107
|
-
return apps.some((app) => app.appCode === seg) ? seg : "";
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 解析组件路径(type 0 普通菜单):
|
|
112
|
-
* - "@..." 存量旧写法:url 即组件路径,原样返回;
|
|
113
|
-
* - /micro/<appCode>/… 或 /micro_debug/<同>:剥掉归属两段后本地解析——条件是
|
|
114
|
-
* 归属自己,或来源为 base(/micro/<any>/base/… 是包内组件,任何引包应用本地都有,
|
|
115
|
-
* 不看归属直接本地渲染,省去为一个共享组件装载整个子应用);
|
|
116
|
-
* - 首段 base:转 @base/views 命名空间;
|
|
117
|
-
* - 其余原样(无前缀走渲染方 @/views;归属别家的私有页原样返回交子应用渲染)。
|
|
118
|
-
*/
|
|
119
|
-
export function resolveComponentPath(menu, rendererAppCode) {
|
|
120
|
-
const url = menu && menu.url != null ? String(menu.url) : "";
|
|
121
|
-
if (!url) return "";
|
|
122
|
-
if (url.startsWith("@")) return url;
|
|
123
|
-
const seg = getFirstSegment(url);
|
|
124
|
-
if (!seg) return url;
|
|
125
|
-
if (MICRO_SEGMENTS.indexOf(seg) >= 0) {
|
|
126
|
-
const owner = getOwnerSegment(url);
|
|
127
|
-
// 剥掉归属两段 /micro/<owner>;剩余首段为 base 即"包内组件来源"
|
|
128
|
-
const rest = stripFirstSegment(stripFirstSegment(url));
|
|
129
|
-
const isBaseSource = getFirstSegment(rest) === BASE_SEGMENT;
|
|
130
|
-
// 归属自己、或来源 base(包内组件人人有)→ 本地解析;否则(归属别家的私有页)原样返回。
|
|
131
|
-
// 递归一次把 rest 里的 base 命名空间转 @base/views;appCode 传空避免连剥两轮。
|
|
132
|
-
if (isBaseSource || (owner && rendererAppCode && owner === rendererAppCode)) {
|
|
133
|
-
return rest ? resolveComponentPath({ url: rest }, "") : "";
|
|
134
|
-
}
|
|
135
|
-
return url;
|
|
136
|
-
}
|
|
137
|
-
if (seg === BASE_SEGMENT) {
|
|
138
|
-
const rest = stripFirstSegment(url);
|
|
139
|
-
return rest ? BASE_VIEW_PREFIX + rest : "";
|
|
140
|
-
}
|
|
141
|
-
return url;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function stripFirstSegment(url) {
|
|
145
|
-
const clean = url.startsWith("/") ? url : "/" + url;
|
|
146
|
-
const idx = clean.indexOf("/", 1);
|
|
147
|
-
return idx > 0 ? clean.slice(idx) : "";
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* 全量菜单按归属切分,返回 { appCode: [菜单行] }。
|
|
152
|
-
* 主应用侧不需要切分结果——它始终用原始整树渲染侧边栏,直接用入参即可;
|
|
153
|
-
* 这里只产出各子应用归属自己的扁平列表(用于 props 下发生成子应用内部路由)。
|
|
154
|
-
*/
|
|
155
|
-
export function dispatchMenus(menus, registry) {
|
|
156
|
-
const apps = normalizeRegistry(registry);
|
|
157
|
-
const subAppMenus = {};
|
|
158
|
-
apps.forEach((app) => {
|
|
159
|
-
subAppMenus[app.appCode] = [];
|
|
160
|
-
});
|
|
161
|
-
const walk = (rows) => {
|
|
162
|
-
(rows || []).forEach((row) => {
|
|
163
|
-
const owner = resolveOwner(row, apps);
|
|
164
|
-
if (owner) {
|
|
165
|
-
subAppMenus[owner].push(row);
|
|
166
|
-
}
|
|
167
|
-
if (row && row.children && row.children.length) {
|
|
168
|
-
walk(row.children);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
walk(menus || []);
|
|
173
|
-
return subAppMenus;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* 校验应用注册表(逻辑参数 micro_app_registry 的 paramValue)。
|
|
178
|
-
* 非法条目跳过并记入 errors,绝不静默;enabled === false 的合法条目不收录也不报错。
|
|
179
|
-
* appCode 处在 /micro/ 命名空间内,与主应用一级视图目录天然隔离,无需再防目录撞名;
|
|
180
|
-
* 仅保留 base(组件来源命名空间)与 micro / micro_debug(前缀自身)三个保留字。
|
|
181
|
-
* options.reservedSegments 可追加应用自定义保留字。
|
|
182
|
-
*/
|
|
183
|
-
export function validateRegistry(raw, options = {}) {
|
|
184
|
-
const errors = [];
|
|
185
|
-
let list = raw;
|
|
186
|
-
if (typeof raw === "string") {
|
|
187
|
-
try {
|
|
188
|
-
list = JSON.parse(raw);
|
|
189
|
-
} catch (e) {
|
|
190
|
-
return { apps: [], errors: ["注册表 JSON 解析失败: " + e.message] };
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (!Array.isArray(list)) {
|
|
194
|
-
return { apps: [], errors: ["注册表必须是 JSON 数组"] };
|
|
195
|
-
}
|
|
196
|
-
const reserved = [BASE_SEGMENT]
|
|
197
|
-
.concat(MICRO_SEGMENTS)
|
|
198
|
-
.concat(options.reservedSegments || []);
|
|
199
|
-
const apps = [];
|
|
200
|
-
const seen = {};
|
|
201
|
-
list.forEach((item, index) => {
|
|
202
|
-
const tag = "注册表第" + (index + 1) + "项";
|
|
203
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
204
|
-
errors.push(tag + "不是对象");
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const appCode = item.appCode;
|
|
208
|
-
if (!appCode || typeof appCode !== "string" || !APP_CODE_REG.test(appCode)) {
|
|
209
|
-
errors.push(tag + " appCode 缺失或格式非法: " + JSON.stringify(appCode));
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (reserved.indexOf(appCode) >= 0) {
|
|
213
|
-
errors.push(tag + " appCode 与保留字/视图目录撞名: " + appCode);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (seen[appCode]) {
|
|
217
|
-
errors.push(tag + " appCode 重复: " + appCode);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (typeof item.entry !== "string" || !item.entry.trim()) {
|
|
221
|
-
errors.push(
|
|
222
|
-
tag
|
|
223
|
-
+ "("
|
|
224
|
-
+ appCode
|
|
225
|
-
+ ")entry 必须是非空字符串(分环境对象写法已废弃,"
|
|
226
|
-
+ "注册表本身按环境分库;本机联调用 /"
|
|
227
|
-
+ MICRO_DEBUG_SEGMENT
|
|
228
|
-
+ "/ 命名空间)"
|
|
229
|
-
);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
seen[appCode] = true;
|
|
233
|
-
if (item.enabled === false) return;
|
|
234
|
-
apps.push({
|
|
235
|
-
appCode: appCode,
|
|
236
|
-
appName: item.appName || appCode,
|
|
237
|
-
entry: item.entry.trim(),
|
|
238
|
-
enabled: true,
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
return { apps, errors };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* 解析调试开关(逻辑参数 micro_debug_enabled 的值)→ 布尔。
|
|
246
|
-
* 与注册表分开承载:注册表描述"有哪些应用",本开关是环境级运维动作,
|
|
247
|
-
* 生命周期不同——运维开关调试不必改注册表 JSON,少一类把注册表改坏的风险。
|
|
248
|
-
*
|
|
249
|
-
* "true"(忽略大小写与首尾空格)→ 开启
|
|
250
|
-
* 其余一切(不存在 / 空 / "false" / 认不出的值)→ 关闭
|
|
251
|
-
*
|
|
252
|
-
* 只认一个字面量、其余一律关闭,是有意的安全默认:认不出就不放行,
|
|
253
|
-
* 不会因为写了个形近值而把某个环境悄悄放开。
|
|
254
|
-
*/
|
|
255
|
-
export function isDebugEnabled(raw) {
|
|
256
|
-
return (raw == null ? "" : String(raw)).trim().toLowerCase() === "true";
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* 取 entry。不再支持分环境对象写法:注册表存在后端逻辑参数里,本身已按环境分库,
|
|
261
|
-
* 主应用连哪个后端就拿到哪份注册表——再按前端 NODE_ENV 分一次是重复机制。
|
|
262
|
-
* 本机联调走 /micro_debug/ 命名空间(显式、按 URL 生效),不依赖构建环境。
|
|
263
|
-
*/
|
|
264
|
-
export function resolveEntry(entry) {
|
|
265
|
-
return typeof entry === "string" ? entry.trim() : "";
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function normalizeRegistry(registry) {
|
|
269
|
-
if (!registry) return [];
|
|
270
|
-
const list = Array.isArray(registry) ? registry : registry.apps || [];
|
|
271
|
-
return list.filter((app) => {
|
|
272
|
-
return app
|
|
273
|
-
&& typeof app.appCode === "string"
|
|
274
|
-
&& app.appCode
|
|
275
|
-
&& app.enabled !== false;
|
|
276
|
-
});
|
|
277
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 微前端菜单适配层(零依赖纯函数)。
|
|
3
|
+
* 供引包应用与未引包主应用(deep-import 本文件)共用,
|
|
4
|
+
* 禁止在此引入 @base 的 store / router / request / 组件。
|
|
5
|
+
* 协议说明见 docs/qiankun微前端改造-实现方案.md 第三章。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// 表单菜单(动态表单)类型值,菜单归属识别的唯一权威标识
|
|
9
|
+
export const FORM_MENU_TYPE = 5;
|
|
10
|
+
|
|
11
|
+
// url 命名空间保留首段:/base/xxx 表示组件在 npm 包内(@base/views/xxx)
|
|
12
|
+
export const BASE_SEGMENT = "base";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 微前端固定前缀:访问路径形如 /micro/<appCode>/<来源>/<组件路径>。
|
|
16
|
+
* 用固定前缀而非"首段即 appCode",是为了让归属命名空间与主应用自身的
|
|
17
|
+
* 一级视图目录(/user、/pmd、/yx…)结构性隔离——否则注册一个与视图目录
|
|
18
|
+
* 同名的 appCode 会劫持主应用整棵子树,且新增视图目录还会反向撞掉已注册的应用。
|
|
19
|
+
*/
|
|
20
|
+
export const MICRO_SEGMENT = "micro";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 本机调试命名空间:/micro_debug/<appCode>/... 与 /micro/<appCode>/... 归属相同、
|
|
24
|
+
* 菜单相同、组件解析相同,**只有 entry 不同**(正式取注册表,调试取约定端口)。
|
|
25
|
+
* 把"调试还是正式"编码进路径而非存储,是为了让调试态无状态——不会残留在
|
|
26
|
+
* localStorage 里跨天生效,刷新/分享/多标签页的行为都由 URL 自身决定。
|
|
27
|
+
*/
|
|
28
|
+
export const MICRO_DEBUG_SEGMENT = "micro_debug";
|
|
29
|
+
|
|
30
|
+
const MICRO_SEGMENTS = [MICRO_SEGMENT, MICRO_DEBUG_SEGMENT];
|
|
31
|
+
const BASE_VIEW_PREFIX = "@base/views";
|
|
32
|
+
const APP_CODE_REG = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
33
|
+
|
|
34
|
+
export function isFormMenu(menu) {
|
|
35
|
+
return !!menu && menu.type === FORM_MENU_TYPE;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 访问路径 = route || url(与 createRoute3 现有取值逻辑一致)
|
|
40
|
+
*/
|
|
41
|
+
export function getAccessPath(menu) {
|
|
42
|
+
if (!menu) return "";
|
|
43
|
+
return String(menu.route || menu.url || "");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 取路径首段(不含查询串);@ 开头的存量组件路径无首段语义,返回 ""
|
|
48
|
+
*/
|
|
49
|
+
function getFirstSegment(path) {
|
|
50
|
+
return getSegments(path)[0] || "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 切分路径为段数组(不含查询串);@ 开头的存量组件路径无首段语义,返回 [] */
|
|
54
|
+
function getSegments(path) {
|
|
55
|
+
if (!path || typeof path !== "string" || path.startsWith("@")) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
return path.split("?")[0].split("/").filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 取访问路径里的归属 appCode:仅 /micro/<appCode>/... 或
|
|
63
|
+
* /micro_debug/<appCode>/... 形态才有归属,其余(主应用自身路由、
|
|
64
|
+
* /base 命名空间、裸路径)一律返回 ""。
|
|
65
|
+
*/
|
|
66
|
+
export function getOwnerSegment(path) {
|
|
67
|
+
const segs = getSegments(path);
|
|
68
|
+
if (segs.length < 2 || MICRO_SEGMENTS.indexOf(segs[0]) < 0) return "";
|
|
69
|
+
return segs[1];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 切换访问路径的微前端命名空间(/micro/ ⇄ /micro_debug/),非微前端路径原样返回。
|
|
74
|
+
* 用途:主应用在调试命名空间下装载子应用时,把下发的菜单路径一并换过去——
|
|
75
|
+
* 子应用据此生成的路由才与浏览器 hash 对得上(主子路径始终保持一致)。
|
|
76
|
+
*/
|
|
77
|
+
export function swapMicroNamespace(path, debug) {
|
|
78
|
+
const segs = getSegments(path);
|
|
79
|
+
if (segs.length < 2 || MICRO_SEGMENTS.indexOf(segs[0]) < 0) return path;
|
|
80
|
+
const target = debug ? MICRO_DEBUG_SEGMENT : MICRO_SEGMENT;
|
|
81
|
+
if (segs[0] === target) return path;
|
|
82
|
+
return "/" + target + stripFirstSegment(path);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 按调试与否重写整条菜单的访问路径命名空间(浅拷贝,不改原对象)。
|
|
87
|
+
* route 有值改 route,否则改 url——与 getAccessPath 的取值口径一致;
|
|
88
|
+
* 单字段写法里 url 同时是组件路径,但 resolveComponentPath 对两个命名空间
|
|
89
|
+
* 都会剥前缀,所以换过去不影响组件解析。
|
|
90
|
+
*/
|
|
91
|
+
export function swapMenuNamespace(menu, debug) {
|
|
92
|
+
if (!menu) return menu;
|
|
93
|
+
const field = menu.route ? "route" : "url";
|
|
94
|
+
const swapped = swapMicroNamespace(String(menu[field] || ""), debug);
|
|
95
|
+
if (swapped === menu[field]) return menu;
|
|
96
|
+
return Object.assign({}, menu, { [field]: swapped });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 解析菜单归属:访问路径的 /micro/<appCode>(或 /micro_debug/<appCode>)
|
|
101
|
+
* 命中注册表则归属该子应用,否则(无前缀 / base / 未命中)归主应用,返回 ""。
|
|
102
|
+
*/
|
|
103
|
+
export function resolveOwner(menu, registry) {
|
|
104
|
+
const seg = getOwnerSegment(getAccessPath(menu));
|
|
105
|
+
if (!seg || seg === BASE_SEGMENT) return "";
|
|
106
|
+
const apps = normalizeRegistry(registry);
|
|
107
|
+
return apps.some((app) => app.appCode === seg) ? seg : "";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 解析组件路径(type 0 普通菜单):
|
|
112
|
+
* - "@..." 存量旧写法:url 即组件路径,原样返回;
|
|
113
|
+
* - /micro/<appCode>/… 或 /micro_debug/<同>:剥掉归属两段后本地解析——条件是
|
|
114
|
+
* 归属自己,或来源为 base(/micro/<any>/base/… 是包内组件,任何引包应用本地都有,
|
|
115
|
+
* 不看归属直接本地渲染,省去为一个共享组件装载整个子应用);
|
|
116
|
+
* - 首段 base:转 @base/views 命名空间;
|
|
117
|
+
* - 其余原样(无前缀走渲染方 @/views;归属别家的私有页原样返回交子应用渲染)。
|
|
118
|
+
*/
|
|
119
|
+
export function resolveComponentPath(menu, rendererAppCode) {
|
|
120
|
+
const url = menu && menu.url != null ? String(menu.url) : "";
|
|
121
|
+
if (!url) return "";
|
|
122
|
+
if (url.startsWith("@")) return url;
|
|
123
|
+
const seg = getFirstSegment(url);
|
|
124
|
+
if (!seg) return url;
|
|
125
|
+
if (MICRO_SEGMENTS.indexOf(seg) >= 0) {
|
|
126
|
+
const owner = getOwnerSegment(url);
|
|
127
|
+
// 剥掉归属两段 /micro/<owner>;剩余首段为 base 即"包内组件来源"
|
|
128
|
+
const rest = stripFirstSegment(stripFirstSegment(url));
|
|
129
|
+
const isBaseSource = getFirstSegment(rest) === BASE_SEGMENT;
|
|
130
|
+
// 归属自己、或来源 base(包内组件人人有)→ 本地解析;否则(归属别家的私有页)原样返回。
|
|
131
|
+
// 递归一次把 rest 里的 base 命名空间转 @base/views;appCode 传空避免连剥两轮。
|
|
132
|
+
if (isBaseSource || (owner && rendererAppCode && owner === rendererAppCode)) {
|
|
133
|
+
return rest ? resolveComponentPath({ url: rest }, "") : "";
|
|
134
|
+
}
|
|
135
|
+
return url;
|
|
136
|
+
}
|
|
137
|
+
if (seg === BASE_SEGMENT) {
|
|
138
|
+
const rest = stripFirstSegment(url);
|
|
139
|
+
return rest ? BASE_VIEW_PREFIX + rest : "";
|
|
140
|
+
}
|
|
141
|
+
return url;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function stripFirstSegment(url) {
|
|
145
|
+
const clean = url.startsWith("/") ? url : "/" + url;
|
|
146
|
+
const idx = clean.indexOf("/", 1);
|
|
147
|
+
return idx > 0 ? clean.slice(idx) : "";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 全量菜单按归属切分,返回 { appCode: [菜单行] }。
|
|
152
|
+
* 主应用侧不需要切分结果——它始终用原始整树渲染侧边栏,直接用入参即可;
|
|
153
|
+
* 这里只产出各子应用归属自己的扁平列表(用于 props 下发生成子应用内部路由)。
|
|
154
|
+
*/
|
|
155
|
+
export function dispatchMenus(menus, registry) {
|
|
156
|
+
const apps = normalizeRegistry(registry);
|
|
157
|
+
const subAppMenus = {};
|
|
158
|
+
apps.forEach((app) => {
|
|
159
|
+
subAppMenus[app.appCode] = [];
|
|
160
|
+
});
|
|
161
|
+
const walk = (rows) => {
|
|
162
|
+
(rows || []).forEach((row) => {
|
|
163
|
+
const owner = resolveOwner(row, apps);
|
|
164
|
+
if (owner) {
|
|
165
|
+
subAppMenus[owner].push(row);
|
|
166
|
+
}
|
|
167
|
+
if (row && row.children && row.children.length) {
|
|
168
|
+
walk(row.children);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
walk(menus || []);
|
|
173
|
+
return subAppMenus;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 校验应用注册表(逻辑参数 micro_app_registry 的 paramValue)。
|
|
178
|
+
* 非法条目跳过并记入 errors,绝不静默;enabled === false 的合法条目不收录也不报错。
|
|
179
|
+
* appCode 处在 /micro/ 命名空间内,与主应用一级视图目录天然隔离,无需再防目录撞名;
|
|
180
|
+
* 仅保留 base(组件来源命名空间)与 micro / micro_debug(前缀自身)三个保留字。
|
|
181
|
+
* options.reservedSegments 可追加应用自定义保留字。
|
|
182
|
+
*/
|
|
183
|
+
export function validateRegistry(raw, options = {}) {
|
|
184
|
+
const errors = [];
|
|
185
|
+
let list = raw;
|
|
186
|
+
if (typeof raw === "string") {
|
|
187
|
+
try {
|
|
188
|
+
list = JSON.parse(raw);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return { apps: [], errors: ["注册表 JSON 解析失败: " + e.message] };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!Array.isArray(list)) {
|
|
194
|
+
return { apps: [], errors: ["注册表必须是 JSON 数组"] };
|
|
195
|
+
}
|
|
196
|
+
const reserved = [BASE_SEGMENT]
|
|
197
|
+
.concat(MICRO_SEGMENTS)
|
|
198
|
+
.concat(options.reservedSegments || []);
|
|
199
|
+
const apps = [];
|
|
200
|
+
const seen = {};
|
|
201
|
+
list.forEach((item, index) => {
|
|
202
|
+
const tag = "注册表第" + (index + 1) + "项";
|
|
203
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
204
|
+
errors.push(tag + "不是对象");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const appCode = item.appCode;
|
|
208
|
+
if (!appCode || typeof appCode !== "string" || !APP_CODE_REG.test(appCode)) {
|
|
209
|
+
errors.push(tag + " appCode 缺失或格式非法: " + JSON.stringify(appCode));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (reserved.indexOf(appCode) >= 0) {
|
|
213
|
+
errors.push(tag + " appCode 与保留字/视图目录撞名: " + appCode);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (seen[appCode]) {
|
|
217
|
+
errors.push(tag + " appCode 重复: " + appCode);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (typeof item.entry !== "string" || !item.entry.trim()) {
|
|
221
|
+
errors.push(
|
|
222
|
+
tag
|
|
223
|
+
+ "("
|
|
224
|
+
+ appCode
|
|
225
|
+
+ ")entry 必须是非空字符串(分环境对象写法已废弃,"
|
|
226
|
+
+ "注册表本身按环境分库;本机联调用 /"
|
|
227
|
+
+ MICRO_DEBUG_SEGMENT
|
|
228
|
+
+ "/ 命名空间)"
|
|
229
|
+
);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
seen[appCode] = true;
|
|
233
|
+
if (item.enabled === false) return;
|
|
234
|
+
apps.push({
|
|
235
|
+
appCode: appCode,
|
|
236
|
+
appName: item.appName || appCode,
|
|
237
|
+
entry: item.entry.trim(),
|
|
238
|
+
enabled: true,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
return { apps, errors };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 解析调试开关(逻辑参数 micro_debug_enabled 的值)→ 布尔。
|
|
246
|
+
* 与注册表分开承载:注册表描述"有哪些应用",本开关是环境级运维动作,
|
|
247
|
+
* 生命周期不同——运维开关调试不必改注册表 JSON,少一类把注册表改坏的风险。
|
|
248
|
+
*
|
|
249
|
+
* "true"(忽略大小写与首尾空格)→ 开启
|
|
250
|
+
* 其余一切(不存在 / 空 / "false" / 认不出的值)→ 关闭
|
|
251
|
+
*
|
|
252
|
+
* 只认一个字面量、其余一律关闭,是有意的安全默认:认不出就不放行,
|
|
253
|
+
* 不会因为写了个形近值而把某个环境悄悄放开。
|
|
254
|
+
*/
|
|
255
|
+
export function isDebugEnabled(raw) {
|
|
256
|
+
return (raw == null ? "" : String(raw)).trim().toLowerCase() === "true";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 取 entry。不再支持分环境对象写法:注册表存在后端逻辑参数里,本身已按环境分库,
|
|
261
|
+
* 主应用连哪个后端就拿到哪份注册表——再按前端 NODE_ENV 分一次是重复机制。
|
|
262
|
+
* 本机联调走 /micro_debug/ 命名空间(显式、按 URL 生效),不依赖构建环境。
|
|
263
|
+
*/
|
|
264
|
+
export function resolveEntry(entry) {
|
|
265
|
+
return typeof entry === "string" ? entry.trim() : "";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function normalizeRegistry(registry) {
|
|
269
|
+
if (!registry) return [];
|
|
270
|
+
const list = Array.isArray(registry) ? registry : registry.apps || [];
|
|
271
|
+
return list.filter((app) => {
|
|
272
|
+
return app
|
|
273
|
+
&& typeof app.appCode === "string"
|
|
274
|
+
&& app.appCode
|
|
275
|
+
&& app.enabled !== false;
|
|
276
|
+
});
|
|
277
|
+
}
|