cloud-web-corejs 1.0.54-dev.774 → 1.0.54-dev.776

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 CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "cloud-web-corejs",
3
3
  "private": false,
4
- "version": "1.0.54-dev.774",
4
+ "version": "1.0.54-dev.776",
5
5
  "scripts": {
6
6
  "dev": "vue-cli-service serve",
7
+ "dev:micro": "vue-cli-service serve --port 17527",
7
8
  "lint": "eslint --ext .js,.vue src",
8
9
  "build:prod": "vue-cli-service build",
9
10
  "build:stage": "vue-cli-service build --mode staging",
@@ -1,47 +1,51 @@
1
- let mixin = {};
2
- let configUtil = {
3
- baseUrl: process.env.VUE_APP_BASE_API,
4
- requireView: (url) => {
5
- return require("@/views" + url).default;
6
- },
7
- };
8
- mixin = {
9
- name: "wfModifyMixin",
10
- props: {
11
- dataId: [String, Number],
12
- code: [String, Number],
13
- visible: Boolean,
14
- paramData: Object,
15
- parentVue: Object,
16
- wfInfo: Object,
17
- },
18
- data() {
19
- return {
20
- showModifyDialog: false,
21
- showBillEdit: false,
22
- content: "",
23
- };
24
- },
25
- async mounted() {
26
- this.openWfModifyDialog(this.code);
27
- },
28
- methods: {
29
- openWfModifyDialog() {
30
- let row = this.paramData;
31
- let url = row.mobileurl + ".vue";
32
- this.showModifyDialog = true;
33
- this.content = configUtil.requireView(url);
34
- this.$openEditView("showBillEdit");
35
- },
36
- close() {
37
- this.$destroy();
38
- this.$el.parentElement.removeChild(this.$el);
39
- },
40
- refreshEdit() {
41
- this.showModifyDialog = false;
42
- this.parentVue.$baseReload()
43
- }
44
- },
45
- };
46
-
47
- export default mixin;
1
+ import {requireViewCompat} from "@base/utils/resolveViewComponent";
2
+
3
+ let mixin = {};
4
+ let configUtil = {
5
+ baseUrl: process.env.VUE_APP_BASE_API,
6
+ // 归属自己 / 包内组件本地渲染;归属别家子应用(remote)或缺文件时返回 null,
7
+ // 不再抛错崩溃。归属解析见 @base/utils/resolveViewComponent。
8
+ requireView: (url) => {
9
+ return requireViewCompat(url);
10
+ },
11
+ };
12
+ mixin = {
13
+ name: "wfModifyMixin",
14
+ props: {
15
+ dataId: [String, Number],
16
+ code: [String, Number],
17
+ visible: Boolean,
18
+ paramData: Object,
19
+ parentVue: Object,
20
+ wfInfo: Object,
21
+ },
22
+ data() {
23
+ return {
24
+ showModifyDialog: false,
25
+ showBillEdit: false,
26
+ content: "",
27
+ };
28
+ },
29
+ async mounted() {
30
+ this.openWfModifyDialog(this.code);
31
+ },
32
+ methods: {
33
+ openWfModifyDialog() {
34
+ let row = this.paramData;
35
+ let url = row.mobileurl + ".vue";
36
+ this.showModifyDialog = true;
37
+ this.content = configUtil.requireView(url);
38
+ this.$openEditView("showBillEdit");
39
+ },
40
+ close() {
41
+ this.$destroy();
42
+ this.$el.parentElement.removeChild(this.$el);
43
+ },
44
+ refreshEdit() {
45
+ this.showModifyDialog = false;
46
+ this.parentVue.$baseReload()
47
+ }
48
+ },
49
+ };
50
+
51
+ export default mixin;
@@ -2,12 +2,15 @@
2
2
  import {getToken} from "../../utils/auth";
3
3
  import settingConfig from "@/settings.js";
4
4
  import {selectDialogMixins} from "../../mixins/selectDialog";
5
+ import {requireViewCompat} from "@base/utils/resolveViewComponent";
5
6
 
6
7
  let configUtil = {
7
8
  baseUrl: process.env.VUE_APP_BASE_API,
8
9
  getToken,
10
+ // 归属自己 / 包内组件本地渲染;归属别家子应用(remote)或缺文件时返回 null,
11
+ // 不再抛错崩溃。归属解析见 @base/utils/resolveViewComponent。
9
12
  requireView: (url) => {
10
- return require("@/views" + url).default;
13
+ return requireViewCompat(url);
11
14
  },
12
15
  };
13
16
 
@@ -11,6 +11,18 @@
11
11
  :_dataId.sync="dataId" :defaultShowWfContent="defaultShowWfContent" :_isOutLink="true"
12
12
  :parent-target="_self"
13
13
  @reload="$reloadHandle" v-bind="queryParam" v-on="listeners"></component>
14
+ <micro-view-host
15
+ v-else-if="showWfContent && remoteView"
16
+ :owner="remoteView.owner"
17
+ :target-path="remoteView.targetPath"
18
+ :biz-params="remoteView.bizParams"
19
+ :view-meta="remoteView.viewMeta"
20
+ :initial-out-param="outParam"
21
+ @reload="$reloadHandle"
22
+ @update-out-param="outParam = $event"
23
+ @bridge-emit="onBridgeEmit"
24
+ @close="showWfContent = false"
25
+ ></micro-view-host>
14
26
  </div>
15
27
  <span slot="footer" class="dialog-footer" v-if="option.showFooter!==false" v-bind="option.footerConfig">
16
28
  <el-button type="primary" plain class="button-sty" @click="close">
@@ -26,6 +38,7 @@
26
38
  </template>
27
39
 
28
40
  <script>
41
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
29
42
 
30
43
  export default {
31
44
  components: {},
@@ -62,6 +75,7 @@ export default {
62
75
 
63
76
  showWfContent: true,
64
77
  wfContent: null,
78
+ remoteView: null,
65
79
  outParam: null,
66
80
  // dataId: null,
67
81
  defaultShowWfContent: null,
@@ -174,23 +188,29 @@ export default {
174
188
 
175
189
  this.showWfContent = true;
176
190
 
177
- let t = "@base/views";
178
- let urlArr = [
179
- '/user/bill_setting/h5_view.vue',
180
- '/user/form/view/list.vue',
181
- '/user/form/view/edit.vue'
182
- ]
183
-
184
- if (urlArr.includes(url)) {
185
- this.wfContent = require('@base/views' + url).default;
186
- } else if (url.startsWith(t)) {
187
- let path = url.slice(t.length);
188
- this.wfContent = require('@base/views' + path).default;
189
- } else {
190
- this.wfContent = require('@/views' + url).default;
191
- }
191
+ let res = loadViewComponent(url);
192
+ this.remoteView =
193
+ res.kind === "remote"
194
+ ? {
195
+ owner: res.owner,
196
+ targetPath: res.targetPath,
197
+ bizParams: this.queryParam,
198
+ viewMeta: {
199
+ visibleKey: "showWfContent",
200
+ defaultShowWfContent: this.defaultShowWfContent,
201
+ _isOutLink: true,
202
+ _dataId: this.dataId,
203
+ },
204
+ }
205
+ : null;
206
+ this.wfContent = res.component;
192
207
  },
193
208
 
209
+ // remote 承载态下,把子应用桥接回来的任意事件按名字派回原 option.on 监听
210
+ onBridgeEmit(name, args) {
211
+ let fn = this.listeners && this.listeners[name];
212
+ if (typeof fn === "function") fn.apply(null, args || []);
213
+ },
194
214
 
195
215
  reload(e, option) {
196
216
  let updateParam = option?.updateParam || {};
package/src/index.js CHANGED
@@ -12,6 +12,7 @@ import store from "@base/store";
12
12
  import router, { resetRouter } from "@base/router";
13
13
  import settings from "@/settings";
14
14
  import { setToken as setAuthToken } from "@base/utils/auth";
15
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
15
16
 
16
17
  import "@/icons"; // icon
17
18
  import "@base/permission"; // permission control
@@ -238,6 +239,11 @@ Object.keys(filters).forEach((key) => {
238
239
  Vue.filter(key, filters[key]);
239
240
  });
240
241
 
242
+ // 详情承载宿主:弹框 remote 分支用它 loadMicroApp 装载归属子应用渲染私有详情页
243
+ Vue.component("micro-view-host", () =>
244
+ import("@base/components/micro-view-host/index.vue")
245
+ );
246
+
241
247
  Vue.config.productionTip = false;
242
248
 
243
249
  let instance = null;
@@ -253,6 +259,82 @@ function render(props = {}) {
253
259
  window.$vueRoot = instance;
254
260
  }
255
261
 
262
+ /**
263
+ * 详情承载入口(qiankun 详情承载协议 §5.5)。
264
+ * 主应用/渲染方的 micro-view-host 用 loadMicroApp 装载本应用并置 props.microDetailHost=true,
265
+ * 要求只渲染 targetPath 指向的详情页、把详情页的刷新/关闭/回调桥接回主应用,不出整站壳。
266
+ * 契约字段见 docs/qiankun详情承载子应用对接清单.md。
267
+ *
268
+ * ⚠ parentVue shim 的委托语义需真机联调打磨:详情页 getTarget(this) 命中 shim 后会用到
269
+ * $http/$message/$baseConfirm/$attrs 等——这些经原型委托到宿主根实例(全局插件已装齐),
270
+ * 仅 $baseReload / _handleCallback 覆盖为回主应用的动作。个别详情页若有更深的 $parent 依赖,
271
+ * 按对接清单 §5.6 做最小改造。
272
+ */
273
+ function mountDetailHost(props = {}) {
274
+ if (props.appCode) settings.appCode = props.appCode;
275
+ if (props.token) {
276
+ setAuthToken(props.token);
277
+ store.commit("user/SET_TOKEN", props.token);
278
+ }
279
+ if (props.lang) i18n.locale = props.lang;
280
+
281
+ const appCode = props.appCode || settings.appCode;
282
+ const resolved = loadViewComponent(props.targetPath, { appCode });
283
+ const target = resolved.component;
284
+ const bizParams = props.bizParams || {};
285
+ const viewMeta = props.viewMeta || {};
286
+
287
+ // shim 需先于 render 存在(作为 parentVue prop 传入),其 Vue 能力在挂载后经原型委托到根实例
288
+ const shim = {
289
+ $baseReload: () => props.onReload && props.onReload(),
290
+ _handleCallback: (flag) => props.onHandleCallback && props.onHandleCallback(flag),
291
+ };
292
+
293
+ const { container } = props;
294
+ const mountEl = container ? container.querySelector("#app") : "#app";
295
+ instance = new Vue({
296
+ store,
297
+ i18n,
298
+ render(h) {
299
+ if (!target) {
300
+ return h(
301
+ "div",
302
+ { staticClass: "micro-detail-host-missing" },
303
+ "详情页组件未找到:" + (props.targetPath || "")
304
+ );
305
+ }
306
+ return h(target, {
307
+ props: Object.assign({}, bizParams, viewMeta, {
308
+ parentVue: shim,
309
+ _handleCallback: (flag) =>
310
+ props.onHandleCallback && props.onHandleCallback(flag),
311
+ outParam: props.initialOutParam,
312
+ }),
313
+ on: {
314
+ reload: () => props.onReload && props.onReload(),
315
+ close: () => props.onClose && props.onClose(),
316
+ "update:outParam": (v) =>
317
+ props.onUpdateOutParam && props.onUpdateOutParam(v),
318
+ },
319
+ });
320
+ },
321
+ }).$mount(mountEl);
322
+ // #4 listeners 透传:详情页里父级 v-on="listeners" 那批是详情页直接 this.$emit 的,
323
+ // 拦截根子组件的 $emit,任意事件都转发给主应用的 onEmit(按名字派回原 option.on)。
324
+ const child = instance.$children && instance.$children[0];
325
+ if (child && typeof child.$emit === "function") {
326
+ const rawEmit = child.$emit.bind(child);
327
+ child.$emit = (name, ...args) => {
328
+ if (typeof props.onEmit === "function") props.onEmit(name, ...args);
329
+ return rawEmit(name, ...args);
330
+ };
331
+ }
332
+ // 挂载后把 shim 的原型指向根实例:$http/$message/$baseConfirm 等委托过去,
333
+ // 而 shim 自有的 $baseReload/_handleCallback 覆盖生效(详情页动作时才会调用)
334
+ Object.setPrototypeOf(shim, instance);
335
+ window.$vueRoot = instance;
336
+ }
337
+
256
338
  if (!window.__POWERED_BY_QIANKUN__) {
257
339
  // 独立运行 / 主应用形态:行为与拆分前一致
258
340
  render();
@@ -266,6 +348,11 @@ if (!window.__POWERED_BY_QIANKUN__) {
266
348
  export async function bootstrap() {}
267
349
 
268
350
  export async function mount(props = {}) {
351
+ if (props.microDetailHost) {
352
+ // 详情承载模式(micro-view-host 装载):不走整站壳/路由/菜单,
353
+ // 只在容器里渲染 props.targetPath 指向的本应用组件。见对接清单文档。
354
+ return mountDetailHost(props);
355
+ }
269
356
  if (props.appCode) {
270
357
  // 主应用可经 props 覆盖,子应用也可在自身 settings.js 里声明
271
358
  settings.appCode = props.appCode;
@@ -31,7 +31,7 @@ const CONTAINER_WRAPPER_ID = "Appmicro";
31
31
  * 调试态不落任何存储、不读 URL 查询参数——路径本身就是开关,因此入口只能是约定值。
32
32
  * 各子应用把 devServer 端口固定为 17527 即可零配置被主应用装载。
33
33
  */
34
- const DEBUG_ENTRY = "http://localhost:17527/";
34
+ export const DEBUG_ENTRY = "http://localhost:17527/";
35
35
 
36
36
  // 保活管理:appCode → { instance, el };离开子应用路由只隐藏容器不卸载,
37
37
  // 切回直接显示并经 update 生命周期同步路由,页面状态(页签/表格/表单)得以保留
@@ -1,21 +1,21 @@
1
- import { constantRoutes } from "@base/router";
2
- import { asyncRoutes, sidebarRoutes } from "@base/router/modules/system";
3
- import Layout from "@/layout";
4
- import { getRouters } from "../../api/menu";
1
+ import { constantRoutes } from "@base/router";
2
+ import { asyncRoutes, sidebarRoutes } from "@base/router/modules/system";
3
+ import Layout from "@/layout";
4
+ import { getRouters } from "../../api/menu";
5
5
  import router from "@base/router";
6
6
  import storeConfig from "../config/index";
7
7
  import settings from "@/settings";
8
8
  import {
9
9
  resolveComponentPath,
10
- resolveOwner,
11
- dispatchMenus,
12
- } from "@base/utils/menuAdapter";
13
- import {
14
- getAppRegistry,
15
- registerSubApps,
16
- buildSubAppRoutes,
17
- } from "@base/microRouter";
18
- import MenuFallback from "@base/views/user/menuFallback/index.vue";
10
+ resolveOwner,
11
+ dispatchMenus,
12
+ } from "@base/utils/menuAdapter";
13
+ import {
14
+ getAppRegistry,
15
+ registerSubApps,
16
+ buildSubAppRoutes,
17
+ } from "@base/microRouter";
18
+ import MenuFallback from "@base/views/user/menuFallback/index.vue";
19
19
 
20
20
  // qiankun 宿主态(仅 settings.microMainEnabled 的主应用登录后填充)
21
21
  let hostRegistryApps = [];
@@ -230,34 +230,37 @@ const actions = {
230
230
  },
231
231
  // qiankun 宿主编排:拉注册表 → 按归属切分 → 注册子应用,再走常规路由生成。
232
232
  // 非宿主(未开 microMainEnabled / 自身被 qiankun 挂载)直接透传。
233
- setupHostAndRoutes({ dispatch, commit, rootState }, rows = []) {
233
+ setupHostAndRoutes({ dispatch, commit, rootState }, rows = []) {
234
234
  let isHost =
235
235
  settings.microMainEnabled === true && !window.__POWERED_BY_QIANKUN__;
236
- if (!isHost) {
237
- return dispatch("setSidebarRoute", rows);
238
- }
239
- return getAppRegistry().then((apps) => {
240
- // 自排除:自身也在注册表时(子应用开宿主形态互访别家应用)不装载自己,
241
- // 否则会自嵌套且自身菜单被误判为"派发给子应用"而跳过本地路由
242
- let ownAppCode = settings.appCode || "";
243
- hostRegistryApps = (apps || []).filter(
244
- (app) => app.appCode !== ownAppCode
245
- );
246
- commit(
247
- "SET_REGISTRY_APP_CODES",
248
- hostRegistryApps.map((app) => app.appCode)
249
- );
250
- let subAppMenus = dispatchMenus(rows, hostRegistryApps);
251
- hostMicroRoutes = buildSubAppRoutes(hostRegistryApps, subAppMenus);
252
- if (hostRegistryApps.length) {
253
- registerSubApps(hostRegistryApps, {
254
- subAppMenus,
255
- userInfo: buildUserInfoSnapshot(rootState.user),
256
- });
257
- }
258
- return dispatch("setSidebarRoute", rows);
259
- });
260
- },
236
+ if (!isHost) {
237
+ return dispatch("setSidebarRoute", rows);
238
+ }
239
+ return getAppRegistry().then((apps) => {
240
+ // 自排除:自身也在注册表时(子应用开宿主形态互访别家应用)不装载自己,
241
+ // 否则会自嵌套且自身菜单被误判为"派发给子应用"而跳过本地路由。
242
+ // 主应用 appCode 恒为空(WEB_PREFIX===MAIN_WEB_PREFIX 不派生),但它在注册表里的
243
+ // 身份是 WEB_PREFIX 去斜杠(/sys → sys)——故回落到 WEB_PREFIX 派生,
244
+ // 否则注册表误配了主应用自身条目时排不掉、导致主应用自嵌套。
245
+ let ownAppCode = settings.appCode || (window.WEB_PREFIX || "").replace(/^\/+/, "");
246
+ hostRegistryApps = (apps || []).filter(
247
+ (app) => app.appCode !== ownAppCode
248
+ );
249
+ commit(
250
+ "SET_REGISTRY_APP_CODES",
251
+ hostRegistryApps.map((app) => app.appCode)
252
+ );
253
+ let subAppMenus = dispatchMenus(rows, hostRegistryApps);
254
+ hostMicroRoutes = buildSubAppRoutes(hostRegistryApps, subAppMenus);
255
+ if (hostRegistryApps.length) {
256
+ registerSubApps(hostRegistryApps, {
257
+ subAppMenus,
258
+ userInfo: buildUserInfoSnapshot(rootState.user),
259
+ });
260
+ }
261
+ return dispatch("setSidebarRoute", rows);
262
+ });
263
+ },
261
264
  // qiankun 子应用模式:主应用经 props 下发菜单,替代 getRouters 自拉
262
265
  generateRoutesFromProps({ dispatch, rootState }, menus = []) {
263
266
  // 调试覆盖装载时允许 URL 直达未配菜单的页面(handleMenuRoutes 追加兜底路由)
@@ -0,0 +1,191 @@
1
+ /**
2
+ * 动态详情页组件解析(qiankun 微前端兼容)。
3
+ *
4
+ * 全库有一批「用运行时字符串 require 视图组件、就地渲染详情页」的站点
5
+ * (wf 单据详情、消息通知、外链单页、表单文件引用…),历史上各自散着
6
+ * 4 套前缀判断(白名单 / @base·@/ 前缀嗅探 / form 硬分叉 / 裸拼)。本文件
7
+ * 把解析逻辑收敛到一处,并让它在微前端下正确区分组件归属。
8
+ *
9
+ * 归属三类(对齐 docs/qiankun接入协议.md 的命名空间语义):
10
+ * ① base —— 组件在 cloud-web-corejs 包内(@base/views),任何应用都有
11
+ * ② localPrivate —— 渲染方自己的私有视图(@/views)
12
+ * ③ remote —— 归属别的子应用的私有视图;组件不在当前 bundle,
13
+ * 不能本地 require,交调用方走 micro-view-host 装载或兜底
14
+ * (详见 docs/qiankun动态详情页承载方案.md 第 5 节)
15
+ *
16
+ * 核心归属/剥前缀/base 命名空间的判断复用 menuAdapter.resolveComponentPath,
17
+ * 本文件只在其上加:白名单、.vue 归一、remote 判定、require 落地、缺文件兜底。
18
+ * 因此无需注册表——归属靠访问路径结构 + 本应用 appCode 比较即可;独立运行 /
19
+ * 主应用态(appCode 为空、无 /micro/ 前缀)会自动退化为与历史一致的本地解析。
20
+ *
21
+ * webpack 硬约束:@base/views 与 @/views 两条动态 require 必须保留字面前缀,
22
+ * 不可把前缀变量化,否则 webpack 无法收集 context。包被消费时 @base 指向包内
23
+ * views、@/ 指向消费应用 views——这正是 base / localPrivate 的语义来源。
24
+ */
25
+ import {
26
+ getOwnerSegment,
27
+ resolveComponentPath,
28
+ MICRO_SEGMENT,
29
+ MICRO_DEBUG_SEGMENT,
30
+ } from "@base/utils/menuAdapter";
31
+
32
+ const BASE_VIEW_PREFIX = "@base/views";
33
+ const APP_VIEW_PREFIX = "@/views";
34
+
35
+ /**
36
+ * 历史遗留的包内共享组件:以普通 /user/... 写法表达(未走 /base 命名空间),
37
+ * 必须落到 @base。微前端下的包内组件应改用 /base/... 或 @base/views 前缀,
38
+ * 由 resolveComponentPath 正确处理;本白名单只兜底存量写法。
39
+ */
40
+ export const BASE_VIEW_WHITELIST = [
41
+ "/user/bill_setting/h5_view",
42
+ "/user/form/view/list",
43
+ "/user/form/view/edit",
44
+ ];
45
+
46
+ function stripVue(p) {
47
+ return p.replace(/\.vue$/i, "");
48
+ }
49
+
50
+ function ensureVue(p) {
51
+ return /\.vue$/i.test(p) ? p : p + ".vue";
52
+ }
53
+
54
+ function ensureLeadingSlash(p) {
55
+ if (!p) return p;
56
+ return p.charAt(0) === "/" ? p : "/" + p;
57
+ }
58
+
59
+ function firstSegment(p) {
60
+ return String(p || "").replace(/^\//, "").split("?")[0].split("/")[0];
61
+ }
62
+
63
+ function isMicroPath(p) {
64
+ const seg = firstSegment(p);
65
+ return seg === MICRO_SEGMENT || seg === MICRO_DEBUG_SEGMENT;
66
+ }
67
+
68
+ /** 剥掉 /micro/<appCode> 或 /micro_debug/<appCode> 两段,得子应用内部路径 */
69
+ function stripMicroPrefix(p) {
70
+ const clean = ensureLeadingSlash(String(p || ""));
71
+ const parts = clean.split("/"); // ["", "micro", "appA", "user", ...]
72
+ return "/" + parts.slice(3).join("/");
73
+ }
74
+
75
+ /**
76
+ * 解析组件来源(纯函数,无 require、无副作用,可完整单测)。
77
+ *
78
+ * @param {string} rawUrl 组件来源路径:menu url / option.url / row.url /
79
+ * row.billUrl / row.mobileurl / defaultHomePage 等,
80
+ * 可带或不带 .vue 尾缀。
81
+ * @param {object} options
82
+ * @param {string} [options.route] 访问路径,用于归属判断(双字段写法下
83
+ * url 是组件来源、route 才带 /micro/ 归属段)
84
+ * @param {string} [options.appCode] 本应用(渲染方)appCode;主应用为空字符串
85
+ * @returns {object} 三态描述:
86
+ * { kind: 'empty' }
87
+ * { kind: 'base', base: true, rel: '/user/xxx.vue' }
88
+ * { kind: 'localPrivate', base: false, rel: '/user/xxx.vue' }
89
+ * { kind: 'remote', owner: 'appB', targetPath: '/user/xxx' }
90
+ * { kind: 'unknown', raw: '@other/xxx' } // 罕见的其它 @ 别名,调用方自行决定
91
+ */
92
+ export function resolveViewPath(rawUrl, options = {}) {
93
+ const raw = String(rawUrl == null ? "" : rawUrl);
94
+ if (!raw) return { kind: "empty" };
95
+
96
+ const route = options.route;
97
+ const appCode = options.appCode || "";
98
+
99
+ // 1. 白名单:存量普通写法的包内共享组件 → @base
100
+ if (BASE_VIEW_WHITELIST.indexOf(stripVue(raw)) >= 0) {
101
+ return { kind: "base", base: true, rel: ensureVue(raw) };
102
+ }
103
+
104
+ // 2. 委托 menuAdapter:处理 @ 前缀、/micro/ 剥离与归属、/base 命名空间
105
+ const path = resolveComponentPath({ url: raw }, appCode);
106
+ if (!path) return { kind: "empty" };
107
+
108
+ // 3. 单字段 remote:/micro/<别家>/... 时 resolveComponentPath 原样返回
109
+ if (isMicroPath(path)) {
110
+ return {
111
+ kind: "remote",
112
+ owner: getOwnerSegment(path),
113
+ targetPath: stripMicroPrefix(path),
114
+ };
115
+ }
116
+
117
+ // 4. 双字段 remote 防御:url 无 /micro/ 前缀,但 route 归属别家
118
+ const routeOwner = getOwnerSegment(String(route || ""));
119
+ if (routeOwner && routeOwner !== appCode) {
120
+ return { kind: "remote", owner: routeOwner, targetPath: ensureLeadingSlash(raw) };
121
+ }
122
+
123
+ // 5. 落位到 @base/views 或 @/views
124
+ if (path.indexOf(BASE_VIEW_PREFIX) === 0) {
125
+ return { kind: "base", base: true, rel: ensureVue(path.slice(BASE_VIEW_PREFIX.length)) };
126
+ }
127
+ if (path.indexOf(APP_VIEW_PREFIX) === 0) {
128
+ return { kind: "localPrivate", base: false, rel: ensureVue(path.slice(APP_VIEW_PREFIX.length)) };
129
+ }
130
+ if (path.charAt(0) === "@") {
131
+ // 其它 @ 别名(罕见):不猜测归属,交回调用方
132
+ return { kind: "unknown", raw: path };
133
+ }
134
+
135
+ // 原始 /xxx → 渲染方私有 @/views
136
+ return { kind: "localPrivate", base: false, rel: ensureVue(ensureLeadingSlash(path)) };
137
+ }
138
+
139
+ // ——— 以下为 require 落地层(依赖 webpack context,不进纯函数单测) ———
140
+
141
+ function requireBaseView(rel) {
142
+ return require("@base/views" + rel);
143
+ }
144
+
145
+ function requireAppView(rel) {
146
+ return require("@/views" + rel);
147
+ }
148
+
149
+ /** 惰性读取本应用 appCode,避免把整个 store 拉进 resolveViewPath 的单测 */
150
+ function getRendererAppCode() {
151
+ try {
152
+ const store = require("@base/store").default;
153
+ return (store && store.state && store.state.micro && store.state.micro.appCode) || "";
154
+ } catch (e) {
155
+ return "";
156
+ }
157
+ }
158
+
159
+ /**
160
+ * 解析并加载详情页组件。resolveViewPath 的运行时外壳。
161
+ *
162
+ * @param {string} rawUrl 同 resolveViewPath
163
+ * @param {object} [options] 同 resolveViewPath;appCode 缺省时从 store 读取
164
+ * @returns {object} resolveViewPath 的结果,附加 component 字段:
165
+ * - base / localPrivate:component 为组件对象(require 失败或缺文件时为 null,附 error)
166
+ * - remote / empty / unknown:component 为 null,由调用方决定后续(装载子应用 / 兜底提示)
167
+ */
168
+ export function loadViewComponent(rawUrl, options = {}) {
169
+ const opts = Object.assign({}, options);
170
+ if (opts.appCode == null) opts.appCode = getRendererAppCode();
171
+
172
+ const result = resolveViewPath(rawUrl, opts);
173
+ if (result.kind !== "base" && result.kind !== "localPrivate") {
174
+ return Object.assign({ component: null }, result);
175
+ }
176
+ try {
177
+ const mod = result.base ? requireBaseView(result.rel) : requireAppView(result.rel);
178
+ return Object.assign({ component: mod && mod.default }, result);
179
+ } catch (e) {
180
+ return Object.assign({ component: null, error: e }, result);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * 兼容旧签名的便捷封装:直接返回组件对象(或 null)。
186
+ * 供 components/wf/wf.js、mobile wfModifyDialogMixin.js 的 requireView 平滑替换——
187
+ * 它们历史上返回 require(...).default。remote / 缺文件时返回 null(不再抛错崩溃)。
188
+ */
189
+ export function requireViewCompat(rawUrl, options = {}) {
190
+ return loadViewComponent(rawUrl, options).component;
191
+ }
@@ -219,6 +219,15 @@
219
219
  :_viewType="1"
220
220
  @reload="$reloadHandle"
221
221
  ></component>
222
+ <micro-view-host
223
+ v-else-if="showWfContent && remoteView"
224
+ :owner="remoteView.owner"
225
+ :target-path="remoteView.targetPath"
226
+ :biz-params="remoteView.bizParams"
227
+ :view-meta="remoteView.viewMeta"
228
+ @reload="$reloadHandle"
229
+ @close="showWfContent = false"
230
+ ></micro-view-host>
222
231
  </div>
223
232
  </el-dialog>
224
233
  </div>
@@ -230,6 +239,7 @@
230
239
  import ElImageViewer from "@base/components/VabUpload/image-viewer";
231
240
  import {treeScollx} from "@base/utils/global";
232
241
  import propertiesDialog from './propertiesDialog';
242
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
233
243
 
234
244
  export default {
235
245
  name: 'file_view_ins:list',
@@ -340,6 +350,7 @@ export default {
340
350
  showWfDialog: false,
341
351
  showWfContent: true,
342
352
  wfContent: null,
353
+ remoteView: null,
343
354
  wfDataId: '',
344
355
  };
345
356
  },
@@ -860,7 +871,17 @@ export default {
860
871
  this.wfDataId = !id || typeof id == 'object' ? 0 : id;
861
872
  this.showWfDialog = true;
862
873
  this.showWfContent = true;
863
- this.wfContent = require('@/views' + url).default;
874
+ let res = loadViewComponent(url);
875
+ this.remoteView =
876
+ res.kind === "remote"
877
+ ? {
878
+ owner: res.owner,
879
+ targetPath: res.targetPath,
880
+ bizParams: { _dataId: this.wfDataId },
881
+ viewMeta: { visibleKey: "showWfContent", defaultShowWfContent: true, viewType: 1 },
882
+ }
883
+ : null;
884
+ this.wfContent = res.component;
864
885
  },
865
886
  }
866
887
  };
@@ -13,6 +13,7 @@
13
13
  <script>
14
14
  import corejsConfig from "@/corejsConfig";
15
15
  import settingConfig from "@/settings";
16
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
16
17
 
17
18
  export default {
18
19
  name: 'home',
@@ -79,9 +80,6 @@ export default {
79
80
  let userInfo = this.userInfo;
80
81
  let flag = userInfo.flag;
81
82
 
82
- let str1 = "@base/views";
83
- let str2 = "@/views";
84
-
85
83
  if (menuUrl && flag !== 1) {
86
84
  //超级管理员不使用角色首页
87
85
  url = menuUrl + '.vue';
@@ -89,13 +87,8 @@ export default {
89
87
  let defaultHomePage = this.userInfo.defaultHomePage;
90
88
  url = defaultHomePage || settingConfig.defaultHomePage || '@base/views/user/home/default.vue'
91
89
  }
92
- if (url.startsWith(str1)) {
93
- this.homeContent = require('@base/views' + url.slice(str1.length)).default;
94
- } else if (url.startsWith(str2)) {
95
- this.homeContent = require('@/views' + url.slice(str2.length)).default;
96
- } else {
97
- this.homeContent = require('@/views' + url).default;
98
- }
90
+ // 首页菜单不会归属别的子应用,无需 remote 兜底;归属解析见 @base/utils/resolveViewComponent
91
+ this.homeContent = loadViewComponent(url).component;
99
92
 
100
93
  this.showHomeContent = true;
101
94
  this.flag = 1;
@@ -25,11 +25,21 @@
25
25
  :parent-target="_self"
26
26
  @reload="$reloadHandle"
27
27
  ></component>
28
+ <micro-view-host
29
+ v-else-if="showWfContent && remoteView"
30
+ :owner="remoteView.owner"
31
+ :target-path="remoteView.targetPath"
32
+ :biz-params="remoteView.bizParams"
33
+ :view-meta="remoteView.viewMeta"
34
+ @reload="$reloadHandle"
35
+ @close="showWfContent = false"
36
+ ></micro-view-host>
28
37
  </div>
29
38
  </el-dialog>
30
39
  </template>
31
40
 
32
41
  <script>
42
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
33
43
  export default {
34
44
  props: {},
35
45
  data() {
@@ -38,6 +48,7 @@ export default {
38
48
  wfUrl2: "",
39
49
  showWfContent: true,
40
50
  wfContent: null,
51
+ remoteView: null,
41
52
  wfDataId: "",
42
53
  param: {},
43
54
  };
@@ -102,7 +113,17 @@ export default {
102
113
  this.wfDataId = !id || typeof id == "object" ? 0 : id;
103
114
  // this.showWfDialog = true;
104
115
  this.showWfContent = true;
105
- this.wfContent = require("@/views" + url).default;
116
+ let res = loadViewComponent(url);
117
+ this.remoteView =
118
+ res.kind === "remote"
119
+ ? {
120
+ owner: res.owner,
121
+ targetPath: res.targetPath,
122
+ bizParams: this.param,
123
+ viewMeta: { visibleKey: "showWfContent", _isNotifyMessage: true },
124
+ }
125
+ : null;
126
+ this.wfContent = res.component;
106
127
  }
107
128
  this.showWfDialog2 = true;
108
129
 
@@ -9,6 +9,7 @@
9
9
 
10
10
  <script>
11
11
  import indexUtil from "@base/utils/index.js"
12
+ import { loadViewComponent } from "@base/utils/resolveViewComponent"
12
13
  import {
13
14
  setReportGlobalParam
14
15
  } from "@base/components/xform/utils/util";
@@ -93,23 +94,13 @@ export default {
93
94
  let url = purl + '.vue';
94
95
  this.queryParam = queryParam;
95
96
  this.showWfContent = true;
96
- // this.wfContent = require('@/views' + url).default;
97
- let t = "@base/views";
98
- let urlArr = [
99
- '/user/bill_setting/h5_view.vue',
100
- '/user/form/view/list.vue',
101
- '/user/form/view/edit.vue'
102
- ]
103
97
  this.initUserInfo(async () => {
104
98
  await this.initI18n();
105
- if (urlArr.includes(url)) {
106
- this.wfContent = require('@base/views' + url).default;
107
- } else if (url.startsWith(t)) {
108
- let path = url.slice(t.length);
109
- this.wfContent = require('@base/views' + path).default;
110
- } else {
111
- this.wfContent = require('@/views' + url).default;
99
+ let res = loadViewComponent(url);
100
+ if (res.kind === "remote") {
101
+ this.$message({ message: "该单据归属子应用 " + res.owner + ",请从对应入口打开", type: "warning" });
112
102
  }
103
+ this.wfContent = res.component;
113
104
  })
114
105
  },
115
106
  initUserInfo(callback) {
@@ -13,6 +13,7 @@
13
13
 
14
14
  <script>
15
15
  import { getToken } from "@base/utils/auth";
16
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
16
17
 
17
18
  export default {
18
19
  name: "outLink",
@@ -27,7 +28,11 @@ export default {
27
28
  let url = this.$route.query.url;
28
29
  let route = this.$route.query.route;
29
30
  if (route) {
30
- this.content = require("@/views" + route).default;
31
+ let res = loadViewComponent(route);
32
+ if (res.kind === "remote") {
33
+ this.$message({ message: "该页面归属子应用 " + res.owner + ",请从对应入口打开", type: "warning" });
34
+ }
35
+ this.content = res.component;
31
36
  this.type = 2;
32
37
  } else {
33
38
  let params = this.getCustomReqUrlAndCleanUrl(url);
@@ -9,6 +9,7 @@
9
9
 
10
10
  <script>
11
11
  import indexUtil from "@base/utils/index.js"
12
+ import { loadViewComponent } from "@base/utils/resolveViewComponent"
12
13
 
13
14
  export default {
14
15
  data() {
@@ -85,22 +86,13 @@ export default {
85
86
  this.queryParam = queryParam;
86
87
  this.showWfContent = true;
87
88
 
88
- let t = "@base/views";
89
- let urlArr = [
90
- '/user/bill_setting/h5_view.vue',
91
- '/user/form/view/list.vue',
92
- '/user/form/view/edit.vue'
93
- ]
94
89
  this.initUserInfo(() => {
95
90
  this.initI18n();
96
- if (urlArr.includes(url)) {
97
- this.wfContent = require('@base/views' + url).default;
98
- } else if (url.startsWith(t)) {
99
- let path = url.slice(t.length);
100
- this.wfContent = require('@base/views' + path).default;
101
- } else {
102
- this.wfContent = require('@/views' + url).default;
91
+ let res = loadViewComponent(url);
92
+ if (res.kind === "remote") {
93
+ this.$message({ message: "该单据归属子应用 " + res.owner + ",请从对应入口打开", type: "warning" });
103
94
  }
95
+ this.wfContent = res.component;
104
96
  })
105
97
  },
106
98
  initUserInfo(callback) {
@@ -13,6 +13,7 @@
13
13
  </template>
14
14
 
15
15
  <script>
16
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
16
17
  export default {
17
18
  data() {
18
19
  return {
@@ -36,7 +37,11 @@ export default {
36
37
  this.wfDataId = !id || typeof id == 'object' ? 0 : id;
37
38
  // this.showWfDialog = true;
38
39
  this.showWfContent = true;
39
- this.wfContent = require('@/views' + url).default;
40
+ let res = loadViewComponent(url);
41
+ if (res.kind === "remote") {
42
+ this.$message({ message: "该单据归属子应用 " + res.owner + ",请从对应入口打开", type: "warning" });
43
+ }
44
+ this.wfContent = res.component;
40
45
 
41
46
  }
42
47
  }
@@ -12,6 +12,7 @@
12
12
  </template>
13
13
 
14
14
  <script>
15
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
15
16
  export default {
16
17
  data() {
17
18
  return {
@@ -34,7 +35,11 @@ export default {
34
35
  }
35
36
  let url = purl + '.vue';
36
37
  this.showWfContent = true;
37
- this.wfContent = require('@/views' + url).default;
38
+ let res = loadViewComponent(url);
39
+ if (res.kind === "remote") {
40
+ this.$message({ message: "该单据归属子应用 " + res.owner + ",请从对应入口打开", type: "warning" });
41
+ }
42
+ this.wfContent = res.component;
38
43
 
39
44
  },
40
45
  login() {
@@ -12,6 +12,15 @@
12
12
  :_viewType="1"
13
13
  @reload="$reloadHandle"
14
14
  ></component>
15
+ <micro-view-host
16
+ v-else-if="showWfContent && remoteView"
17
+ :owner="remoteView.owner"
18
+ :target-path="remoteView.targetPath"
19
+ :biz-params="remoteView.bizParams"
20
+ :view-meta="remoteView.viewMeta"
21
+ @reload="$reloadHandle"
22
+ @close="showWfContent = false"
23
+ ></micro-view-host>
15
24
  </el-tab-pane>
16
25
  <el-tab-pane :label="$t1('我的流程')" name="second">
17
26
  <div class="tree-box fl">
@@ -415,6 +424,7 @@
415
424
  import { treeScollx } from "@base/utils/global.js";
416
425
  import wfObjConfigDialog from "../../../../views/user/wf/wf_obj_config/dialog.vue";
417
426
  import userDialog from "@base/views/user/user/dialog";
427
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
418
428
 
419
429
  export default {
420
430
  name: "wf_manage:list",
@@ -448,6 +458,7 @@ export default {
448
458
 
449
459
  param: {},
450
460
  wfContent: null,
461
+ remoteView: null,
451
462
  showWfContent: false,
452
463
 
453
464
  showCurrentTaskUserDialog: false,
@@ -657,7 +668,17 @@ export default {
657
668
  _dataId: wfDataId,
658
669
  };
659
670
  let url = row.url + ".vue";
660
- this.wfContent = require("@/views" + url).default;
671
+ let res = loadViewComponent(url);
672
+ this.remoteView =
673
+ res.kind === "remote"
674
+ ? {
675
+ owner: res.owner,
676
+ targetPath: res.targetPath,
677
+ bizParams: this.param,
678
+ viewMeta: { visibleKey: "showWfContent", defaultShowWfContent: true, viewType: 1 },
679
+ }
680
+ : null;
681
+ this.wfContent = res.component;
661
682
  }
662
683
  this.activeName = "first";
663
684
  this.$openEditView("showWfContent");
@@ -12,6 +12,15 @@
12
12
  :_viewType="1"
13
13
  @reload="$reloadHandle"
14
14
  ></component>
15
+ <micro-view-host
16
+ v-else-if="showWfContent && remoteView"
17
+ :owner="remoteView.owner"
18
+ :target-path="remoteView.targetPath"
19
+ :biz-params="remoteView.bizParams"
20
+ :view-meta="remoteView.viewMeta"
21
+ @reload="$reloadHandle"
22
+ @close="showWfContent = false"
23
+ ></micro-view-host>
15
24
  </el-tab-pane>
16
25
  <el-tab-pane :label="$t1('我的流程2')" name="second">
17
26
  <div class="tree-box fl">
@@ -314,6 +323,7 @@
314
323
  <script>
315
324
  import { treeScollx } from "@base/utils/global.js";
316
325
  import wfObjConfigDialog from "../wf_obj_config/dialog.vue";
326
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
317
327
 
318
328
  export default {
319
329
  name: "wf_manage:list2",
@@ -347,6 +357,7 @@ export default {
347
357
 
348
358
  param: {},
349
359
  wfContent: null,
360
+ remoteView: null,
350
361
  showWfContent: false,
351
362
  };
352
363
  },
@@ -543,7 +554,17 @@ export default {
543
554
  _dataId: wfDataId,
544
555
  };
545
556
  let url = row.url + ".vue";
546
- this.wfContent = require("@/views" + url).default;
557
+ let res = loadViewComponent(url);
558
+ this.remoteView =
559
+ res.kind === "remote"
560
+ ? {
561
+ owner: res.owner,
562
+ targetPath: res.targetPath,
563
+ bizParams: this.param,
564
+ viewMeta: { visibleKey: "showWfContent", defaultShowWfContent: true, viewType: 1 },
565
+ }
566
+ : null;
567
+ this.wfContent = res.component;
547
568
  }
548
569
  this.activeName = "first";
549
570
  this.$openEditView("showWfContent");
@@ -26,11 +26,22 @@
26
26
  @reload="$reloadHandle"
27
27
  :_handleCallback="handleCallback"
28
28
  ></component>
29
+ <micro-view-host
30
+ v-else-if="showWfContent && remoteView"
31
+ :owner="remoteView.owner"
32
+ :target-path="remoteView.targetPath"
33
+ :biz-params="remoteView.bizParams"
34
+ :view-meta="remoteView.viewMeta"
35
+ @reload="$reloadHandle"
36
+ @handle-callback="handleCallback"
37
+ @close="showWfContent = false"
38
+ ></micro-view-host>
29
39
  </div>
30
40
  </el-dialog>
31
41
  </template>
32
42
 
33
43
  <script>
44
+ import { loadViewComponent } from "@base/utils/resolveViewComponent";
34
45
  export default {
35
46
  name: "wfContentDialog",
36
47
  props: {
@@ -44,6 +55,7 @@ export default {
44
55
  return {
45
56
  param: {},
46
57
  wfContent: null,
58
+ remoteView: null,
47
59
  showWfContent: false,
48
60
  showWfDialog: true,
49
61
  };
@@ -98,7 +110,17 @@ export default {
98
110
  _dataId: wfDataId,
99
111
  });
100
112
  let url = this.option.url + ".vue";
101
- this.wfContent = require("@/views" + url).default;
113
+ let res = loadViewComponent(url);
114
+ this.remoteView =
115
+ res.kind === "remote"
116
+ ? {
117
+ owner: res.owner,
118
+ targetPath: res.targetPath,
119
+ bizParams: this.param,
120
+ viewMeta: { visibleKey: "showWfContent", defaultShowWfContent: true, viewType: 1 },
121
+ }
122
+ : null;
123
+ this.wfContent = res.component;
102
124
  }
103
125
  this.showWfContent = true;
104
126
  },