@epxing/core 5.3.0-beta.1 → 5.3.0-beta.3

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.
Files changed (149) hide show
  1. package/API.md +30 -27
  2. package/README.md +5 -4
  3. package/dist/.cache/route-chart-release/frontend/frame/public/js/ecStat.min.js +4 -0
  4. package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts-wordcloud.min.js +4 -0
  5. package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts.min.js +4 -0
  6. package/dist/_virtual/_plugin-vue_export-helper.js +10 -0
  7. package/dist/_virtual/jsencrypt.min.js +1 -1
  8. package/dist/assets/ecStat.min-BrTYPOXX.js +1 -0
  9. package/dist/assets/echarts-wordcloud.min-nWR4Hzny.js +3 -0
  10. package/dist/assets/echarts.min-DROS9E5X.js +45 -0
  11. package/dist/assets/role_select-DTqRlFNz.webp +0 -0
  12. package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/jsencrypt@3.5.4/_vendor/jsencrypt/bin/jsencrypt.min.js +1 -1
  13. package/dist/src/Container.js +1 -0
  14. package/dist/src/RootApp.js +54 -0
  15. package/dist/src/api/config.js +1 -0
  16. package/dist/src/api/createHttp.js +7 -0
  17. package/dist/src/api/index.js +1 -0
  18. package/dist/src/app-layout.js +1 -0
  19. package/dist/src/app-ui-settings.js +49 -0
  20. package/dist/src/app.js +1 -0
  21. package/dist/src/axios.js +11 -15
  22. package/dist/src/capture-ref.js +1 -0
  23. package/dist/src/chart-runtime-assets.js +9 -0
  24. package/dist/src/components/draggable/index.js +1 -0
  25. package/dist/src/create-app.js +4 -6
  26. package/dist/src/directives/clickoutside.js +1 -0
  27. package/dist/src/directives/global.js +1 -0
  28. package/dist/src/index.js +3 -0
  29. package/dist/src/lazy-library.js +1 -0
  30. package/dist/src/loadComponent.js +3 -2
  31. package/dist/src/loadLibrary.js +1 -0
  32. package/dist/src/menu-route.js +12 -0
  33. package/dist/src/mixins/component.js +4 -0
  34. package/dist/src/mixins/custom-option.js +1 -0
  35. package/dist/src/mixins/el-locale.js +1 -0
  36. package/dist/src/mixins/global.js +1 -0
  37. package/dist/src/mixins/index.js +1 -0
  38. package/dist/src/mixins/methods-to-prototype.js +1 -0
  39. package/dist/src/mixins/optimized-global.js +1 -0
  40. package/dist/src/mixins/popper.js +1 -0
  41. package/dist/src/navigation-context.js +47 -0
  42. package/dist/src/navigation.js +44 -12
  43. package/dist/src/package-install.js +4 -2
  44. package/dist/src/plugins/element-plus.js +9 -8
  45. package/dist/src/plugins/i18n.js +1 -0
  46. package/dist/src/plugins/library-locale.js +1 -0
  47. package/dist/src/progress-bar.js +1 -0
  48. package/dist/src/public-api.js +5 -2
  49. package/dist/src/register-components.js +1 -0
  50. package/dist/src/remote-deps.js +1 -0
  51. package/dist/src/route-components.js +9 -0
  52. package/dist/src/router/config.js +8 -8
  53. package/dist/src/router/index.js +1 -0
  54. package/dist/src/router/public.js +1 -0
  55. package/dist/src/runtime-services.js +2 -1
  56. package/dist/src/runtime-utils/fieldFactory.js +1 -0
  57. package/dist/src/runtime-utils/meta.js +4 -0
  58. package/dist/src/runtime-utils/pdf.viewer.js +32 -15
  59. package/dist/src/runtime-utils/util.js +30 -5
  60. package/dist/src/shallow-same.js +1 -0
  61. package/dist/src/shared-packages.js +2 -3
  62. package/dist/src/store/actions.js +1 -0
  63. package/dist/src/store/create-store.js +1 -0
  64. package/dist/src/store/index.js +1 -0
  65. package/dist/src/store/lru.js +1 -0
  66. package/dist/src/store/modules/config.js +1 -0
  67. package/dist/src/store/modules/file.js +1 -0
  68. package/dist/src/store/modules/index.js +1 -0
  69. package/dist/src/store/modules/list.js +26 -3
  70. package/dist/src/store/modules/locale.js +1 -0
  71. package/dist/src/store/modules/meta.js +50 -7
  72. package/dist/src/store/modules/page.js +1 -0
  73. package/dist/src/store/modules/router.js +1 -0
  74. package/dist/src/store/modules/tabs.js +1 -0
  75. package/dist/src/store/modules/user.js +1 -0
  76. package/dist/src/style-presets.js +5 -4
  77. package/dist/src/ui-settings.js +91 -18
  78. package/dist/src/viewport.js +1 -0
  79. package/dist/src/views/App.js +48 -0
  80. package/dist/src/views/AppRoleSelect.js +233 -0
  81. package/dist/src/views/Error.js +210 -0
  82. package/dist/src/views/Module.js +173 -0
  83. package/dist/src/vue.bus.js +1 -0
  84. package/dist/src/vue2-cancel-token-compat.js +1 -0
  85. package/dist/src/vue2-remote-shim.js +1 -0
  86. package/dist/src/vue2-render-compat.js +1 -0
  87. package/dist/src/vue2-select-style-compat.js +1 -0
  88. package/dist/src/watermark.js +1 -0
  89. package/dist/src/xss.js +1 -0
  90. package/dist/src/z-index.js +1 -0
  91. package/dist/style.css +251 -0
  92. package/package.json +6 -5
  93. package/src/RootApp.vue +75 -0
  94. package/src/api/createHttp.js +7 -0
  95. package/src/api/index.js +47 -47
  96. package/src/app-ui-settings.js +31 -0
  97. package/src/axios.js +311 -318
  98. package/src/chart-runtime-assets.js +7 -0
  99. package/src/create-app.js +3 -7
  100. package/src/index.js +28 -28
  101. package/src/install.js +157 -157
  102. package/src/loadComponent.js +187 -187
  103. package/src/loadLibrary.js +99 -99
  104. package/src/menu-route.js +12 -1
  105. package/src/mixins/component.js +1464 -1461
  106. package/src/navigation-context.js +49 -0
  107. package/src/navigation.js +208 -176
  108. package/src/package-install.js +2 -1
  109. package/src/plugins/element-plus.js +302 -302
  110. package/src/plugins/frame.js +178 -178
  111. package/src/public-api.js +1 -0
  112. package/src/route-components.js +7 -0
  113. package/src/router/config.js +861 -864
  114. package/src/router/public.js +1 -1
  115. package/src/runtime-services.js +1 -1
  116. package/src/runtime-utils/fieldFactory.js +179 -179
  117. package/src/runtime-utils/meta.js +399 -378
  118. package/src/runtime-utils/pdf.viewer.js +672 -644
  119. package/src/runtime-utils/util.js +1357 -1330
  120. package/src/shared-packages.js +1 -3
  121. package/src/store/modules/config.js +351 -351
  122. package/src/store/modules/list.js +30 -3
  123. package/src/store/modules/locale.js +236 -236
  124. package/src/store/modules/meta.js +314 -265
  125. package/src/style-presets.js +4 -4
  126. package/src/ui-settings.js +84 -23
  127. package/src/views/App.vue +53 -0
  128. package/src/views/AppRoleSelect.vue +334 -0
  129. package/src/views/Error.vue +63 -0
  130. package/src/views/Module.vue +210 -0
  131. package/src/xss.js +26 -26
  132. package/types/app.d.ts +10 -10
  133. package/types/axios.d.ts +1 -1
  134. package/types/component-mixin.d.ts +1 -1
  135. package/types/field-factory.d.ts +1 -1
  136. package/types/global-mixin.d.ts +1 -1
  137. package/types/http.d.ts +1 -1
  138. package/types/index.d.ts +3 -0
  139. package/types/meta.d.ts +1 -1
  140. package/types/navigation.d.ts +21 -21
  141. package/types/pdf-viewer.d.ts +1 -1
  142. package/types/runtime.d.ts +13 -13
  143. package/types/store.d.ts +1 -1
  144. package/types/util.d.ts +2 -2
  145. /package/dist/{build → .cache/route-chart-release/frontend/frame/build}/stubs/empty-components.js +0 -0
  146. /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-icons.names.js +0 -0
  147. /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-plus.lazy.js +0 -0
  148. /package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/element-plus@2.14.3_vue@3.5.39_typescript@5.9.3_/_vendor/element-plus/dist/locale/en.js +0 -0
  149. /package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/element-plus@2.14.3_vue@3.5.39_typescript@5.9.3_/_vendor/element-plus/dist/locale/zh-cn.js +0 -0
@@ -0,0 +1,49 @@
1
+ import { effectiveRouteName, findMenuPath, resolveRouteRole } from './menu-route'
2
+
3
+ const fields = ['moduleId', 'pageId', 'component', 'metaId', 'flag', 'showMenu']
4
+
5
+ function storageKey (route, menuId, userId) {
6
+ const name = effectiveRouteName(route.name)
7
+ if (name !== 'app1' && name !== 'hall') return ''
8
+ const { client, system, app } = route.params || {}
9
+ return 'epx_nav_context:' + JSON.stringify([name, client, system, app, resolveRouteRole(route), userId, menuId])
10
+ }
11
+
12
+ // 只保留导航所需的标识,不序列化标签里的组件实例、回调和业务数据。
13
+ export function saveMenuContext (route, menu, context, userId = '') {
14
+ const key = storageKey(route, menu.ID, userId)
15
+ if (!key) return
16
+ try {
17
+ if (!context?.moduleId) {
18
+ window.sessionStorage.removeItem(key)
19
+ return
20
+ }
21
+ const saved = {}
22
+ for (const field of fields) {
23
+ const value = context[field]
24
+ if (typeof value === 'string' || typeof value === 'boolean') saved[field] = value
25
+ }
26
+ window.sessionStorage.setItem(key, JSON.stringify(saved))
27
+ } catch (error) {
28
+ // 存储被禁用时继续使用现有导航。
29
+ }
30
+ }
31
+
32
+ export function readMenuContext (route, menu, menus, userId = '') {
33
+ const key = storageKey(route, menu.ID, userId)
34
+ if (!key) return null
35
+ try {
36
+ const saved = JSON.parse(window.sessionStorage.getItem(key) || 'null')
37
+ if (!saved) return null
38
+ const path = findMenuPath(menus, menu.ID)
39
+ const root = path.find(item => item.ID === saved.moduleId)
40
+ // 角色权限、菜单归属或绑定页面变化后,旧上下文不再适用。
41
+ if (!root || (root.MAIN_PAGE || '') !== (saved.pageId || '')) {
42
+ window.sessionStorage.removeItem(key)
43
+ return null
44
+ }
45
+ return saved
46
+ } catch (error) {
47
+ return null
48
+ }
49
+ }
package/src/navigation.js CHANGED
@@ -1,176 +1,208 @@
1
- // 平台导航服务 $nav(菜单直达 + 激活的单一实现;纯函数层在 menu-route.js,此处依赖 store/router 单例)。
2
- //
3
- // 背景:应用内导航(顶部 nav → 标签页 → 模块/菜单栈)原先只活在 Vuex,URL 停在应用级,
4
- // 刷新即回首页。现在:点击菜单/程序跳转 → activateMenu()(原 $createPage({menuId}) 分支
5
- // 迁入的单一实现)→ 同步 URL 到 /app/:client/:system/:app/m/:menuKey;刷新/直达/前进后退
6
- // → activateFromRoute() 反查菜单树走同一条激活路径。
7
- // 对外经 core/src/install.js 挂 app.config.globalProperties.$nav。
8
- import { nextTick } from 'vue'
9
- import { runtimeStore as store, runtimeRouter as router } from './runtime-services'
10
- import { getMenuKey, findMenuByKey, roleStorage, effectiveRouteName, menuLocation } from './menu-route'
11
-
12
- export * from './menu-route'
13
-
14
- function findNavId (menus = [], menuId, parent) {
15
- for (let i = 0; i < menus.length; i++) {
16
- const menu = menus[i]
17
- if (menu.ID === menuId) {
18
- return { ...menu, parentMenu: parent }
19
- } else if (menu.children) {
20
- const activeMenu = findNavId(menu.children, menuId, menu)
21
- if (activeMenu) return activeMenu
22
- }
23
- }
24
- }
25
-
26
- // 防「激活→URL push→route watcher→再激活」回环:记录最后一次由激活侧写入的 key
27
- let lastSyncedKey = null
28
-
29
- // 菜单激活的单一实现。**关键:顶级菜单和子菜单走不同的建表参数**——
30
- // 顶级菜单(直接在 appMenus 里,即顶部导航项):与 AppNav.toPage 点击**完全一致**的极简
31
- // 参数 {moduleId, pageId, title}。这类菜单可能自带 MAIN_PAGE(如人事异动既有绑定页面又有
32
- // 子菜单)——极简参数让 module-index-page 走它的绑定页面;若给了 activeMenuPageId 会被
33
- // 误判为"下钻到子页面"→ 渲染出自己的侧边菜单 + 绑定页内的侧边菜单 = 两个侧边导航。
34
- // 子菜单(在某顶级项的 children 里):需要 activeMenuId/activeMenuPageId 下钻高亮子项。
35
- export function activateMenu (menuId, extraProps = {}, { syncUrl = true, replaceUrl = false, query } = {}) {
36
- const appMenus = store.state.config.appMenus || []
37
- const topLevel = appMenus.find(m => m.ID === menuId)
38
- let menuNode = null
39
- let params = null
40
- if (topLevel) {
41
- // 顶级菜单:镜像 AppNav.toPage 的建表(不加 activeMenu*/openType,避免误触发子菜单下钻)
42
- menuNode = topLevel
43
- params = {
44
- key: 'default-' + menuId,
45
- moduleId: menuId,
46
- component: 'module-index-page',
47
- title: topLevel.NAME_ALIAS || topLevel.NAME || topLevel.text,
48
- pageId: topLevel.MAIN_PAGE,
49
- ...extraProps
50
- }
51
- } else {
52
- // 子菜单:定位所属顶部 nav,下钻高亮
53
- let navId = null
54
- let pageId = ''
55
- let activeMenuPageId = extraProps.pageId
56
- for (let i = 0; i < appMenus.length; i++) {
57
- const menu = findNavId(appMenus[i].children, menuId, appMenus[i])
58
- if (menu) {
59
- menuNode = menu
60
- navId = menu.parent
61
- if (menu.parentMenu) pageId = menu.parentMenu.MAIN_PAGE
62
- if (!activeMenuPageId) activeMenuPageId = menu.MAIN_PAGE
63
- break
64
- }
65
- }
66
- if (!menuNode) return false
67
- const moduleId = navId || menuId
68
- const props = { ...extraProps }
69
- delete props.pageId
70
- params = {
71
- key: 'default-' + moduleId,
72
- openType: '1',
73
- title: menuNode.NAME_ALIAS || menuNode.NAME || menuNode.text,
74
- moduleId,
75
- pageId,
76
- activeMenuId: menuId,
77
- activeMenuPageId,
78
- ...props,
79
- component: 'module-index-page'
80
- }
81
- }
82
- store.commit('DESTORY_TAB', params.key)
83
- nextTick(() => store.commit('CREATE_TAB', params))
84
- if (syncUrl) syncMenuUrl(menuNode, { replace: replaceUrl, query })
85
- return true
86
- }
87
-
88
- // 激活后把菜单键写进 URL(push——浏览器前进/后退可在菜单间穿梭;恢复场景用 replace)
89
- export function syncMenuUrl (menu, { replace = false, query } = {}) {
90
- // URL 一律用菜单 ID(32 位 hex——路由参数正则与 app1 的 :role 段消歧的前提);
91
- // 编号(MODULE_CODE)仍可作为 toMenu 入参,但不进 URL
92
- const key = menu && menu.ID
93
- if (!key || !/^[0-9A-Fa-f]{32}$/.test(key)) return
94
- const route = router.currentRoute.value
95
- const name = effectiveRouteName(route.name)
96
- if (name !== 'app1' && name !== 'hall') return // 独立窗口页/manage 等场景不动 URL
97
- if (route.params.menuKey === key && query === undefined) return
98
- lastSyncedKey = key
99
- const loc = menuLocation(route, key, query)
100
- ;(replace ? router.replace(loc) : router.push(loc)).catch(() => {})
101
- }
102
-
103
- // 路由跳转失败(重复导航 / 守卫中断)不是业务错误,不该弹用户提示:
104
- // 具名成常量而不是就地写空 catch,是为了把「有意忽略」写在明面上。
105
- const ignoreNavFailure = () => {}
106
-
107
- // syncMenuUrl 的反向操作:回应用首页时把 URL 上的菜单直达段(/:menuKey)抹掉——
108
- // 菜单已不再激活,URL 不该继续指向它(刷新/分享会又回到那个菜单)。
109
- // URL 上本就没有 menuKey 的场景(manage / 独立窗口页 / 本就在首页)直接早退,不动 URL。
110
- export function clearMenuUrl ({ replace = false } = {}) {
111
- const route = router.currentRoute.value
112
- if (!route.params || !route.params.menuKey) return
113
- const { client, system, app } = route.params
114
- const loc = effectiveRouteName(route.name) === 'hall'
115
- ? { name: 'hall', params: { client }, query: route.query }
116
- : { name: 'app1', params: { client, system, app }, query: route.query }
117
- ;(replace ? router.replace(loc) : router.push(loc)).catch(ignoreNavFailure)
118
- }
119
-
120
- // URL → 激活(刷新恢复 / 浏览器前进后退 / 直达链接)。AppIndex 在菜单树就绪后与
121
- // $route.params.menuKey 变化时调用。找不到(权限变化/编号改了)返回 false 落应用首页。
122
- export function activateFromRoute (route) {
123
- const key = route.params && route.params.menuKey
124
- if (!key) return false
125
- if (key === lastSyncedKey) { // 自己刚 push 的,无需再激活
126
- lastSyncedKey = null
127
- return true
128
- }
129
- const menu = findMenuByKey(store.state.config.appMenus || [], key)
130
- if (!menu) {
131
- console.warn(`[nav] 菜单直达:找不到 menuKey=${key}(权限变化或编号已改),落应用首页`)
132
- return false
133
- }
134
- return activateMenu(menu.ID, {}, { syncUrl: false })
135
- }
136
-
137
- // URL 上没有 menuKey 时的激活目标:应用首页(已开就选中,没开就用暂存的首页模板按需打开)。
138
- // activateFromRoute 的对偶——菜单直达把「有 menuKey」这一侧接住了,这一侧接的是「无 menuKey」,
139
- // 少了它,前进/后退落到无 menuKey 的那一格时地址栏变了、内容还停在上一个菜单上。
140
- export function activateHome () {
141
- const home = store.getters.getTab('default-index') || store.state.tabs.homeTab
142
- if (!home) return false
143
- if (store.getters.getTab(home.key)) store.commit('SELECT_TAB', home.key)
144
- else store.commit('CREATE_TAB', home)
145
- return true
146
- }
147
-
148
- // ── $nav 对外表面(组件内 this.$nav.xxx;组件外可直接 import { nav })──────────
149
- export const nav = {
150
- /** 跳到某菜单(key=编号或 ID)。等同用户点击菜单,URL 一并更新 */
151
- toMenu (keyOrId, { replace = false, query } = {}) {
152
- const menu = findMenuByKey(store.state.config.appMenus || [], String(keyOrId))
153
- if (!menu) {
154
- console.warn(`[nav] toMenu:找不到菜单 ${keyOrId}`)
155
- return false
156
- }
157
- return activateMenu(menu.ID, {}, { syncUrl: true, replaceUrl: replace, query })
158
- },
159
- /** 生成某菜单的可分享 URL(含 /m/:key,不含 role——打开者用自己的角色) */
160
- menuUrl (keyOrId, query) {
161
- const menu = findMenuByKey(store.state.config.appMenus || [], String(keyOrId))
162
- if (!menu) return ''
163
- const loc = menuLocation(router.currentRoute.value, getMenuKey(menu), query)
164
- return window.location.origin + window.location.pathname + router.resolve(loc).href
165
- },
166
- /** 当前 URL 上的菜单键(无则 '') */
167
- currentMenuKey () {
168
- return (router.currentRoute.value.params || {}).menuKey || ''
169
- },
170
- getMenuKey,
171
- findMenuByKey,
172
- /** 角色持久化读写(localStorage,客户+系统维度) */
173
- role: roleStorage
174
- }
175
-
176
- export default nav
1
+ // 平台导航服务 $nav(菜单直达 + 激活的单一实现;纯函数层在 menu-route.js,此处依赖 store/router 单例)。
2
+ //
3
+ // 背景:应用内导航(顶部 nav → 标签页 → 模块/菜单栈)原先只活在 Vuex,URL 停在应用级,
4
+ // 刷新即回首页。现在:点击菜单/程序跳转 → activateMenu()(原 $createPage({menuId}) 分支
5
+ // 迁入的单一实现)→ 同步 URL 到 /app/:client/:system/:app/m/:menuKey;刷新/直达/前进后退
6
+ // → activateFromRoute() 反查菜单树走同一条激活路径。
7
+ // 对外经 core/src/install.js 挂 app.config.globalProperties.$nav。
8
+ import { nextTick } from 'vue'
9
+ import { runtimeStore as store, runtimeRouter as router } from './runtime-services'
10
+ import { getMenuKey, getRouteMenuKey, findMenuByKey, roleStorage, resolveRouteRole, effectiveRouteName, menuLocation } from './menu-route'
11
+ import { saveMenuContext, readMenuContext } from './navigation-context'
12
+
13
+ export * from './menu-route'
14
+
15
+ function findNavId (menus = [], menuId, parent) {
16
+ for (let i = 0; i < menus.length; i++) {
17
+ const menu = menus[i]
18
+ if (menu.ID === menuId) {
19
+ return { ...menu, parentMenu: parent }
20
+ } else if (menu.children) {
21
+ const activeMenu = findNavId(menu.children, menuId, menu)
22
+ if (activeMenu) return activeMenu
23
+ }
24
+ }
25
+ }
26
+
27
+ // 防「激活→URL push→route watcher→再激活」回环:记录最后一次由激活侧写入的 key
28
+ let lastSyncedKey = null
29
+
30
+ // 登录用户在路由守卫中已就绪,详细资料异步加载,不用它作为会话存储的首选标识。
31
+ const navigationUserId = () => store.state.user?.user?.id || store.state.user?.userInfo?.ID || ''
32
+
33
+ // 菜单激活的单一实现。**关键:顶级菜单和子菜单走不同的建表参数**——
34
+ // 顶级菜单(直接在 appMenus 里,即顶部导航项):与 AppNav.toPage 点击**完全一致**的极简
35
+ // 参数 {moduleId, pageId, title}。这类菜单可能自带 MAIN_PAGE(如人事异动既有绑定页面又有
36
+ // 子菜单)——极简参数让 module-index-page 走它的绑定页面;若给了 activeMenuPageId 会被
37
+ // 误判为"下钻到子页面"→ 渲染出自己的侧边菜单 + 绑定页内的侧边菜单 = 两个侧边导航。
38
+ // 子菜单(在某顶级项的 children 里):需要 activeMenuId/activeMenuPageId 下钻高亮子项。
39
+ export function activateMenu (menuId, extraProps = {}, { syncUrl = true, replaceUrl = false, query } = {}) {
40
+ const appMenus = store.state.config.appMenus || []
41
+ // 配置中心由专用菜单模板负责导航和上下文,直达时保留原首页容器,不另建普通模块页。
42
+ if (effectiveRouteName(router.currentRoute.value.name) === 'system') {
43
+ const menu = findMenuByKey(appMenus, menuId)
44
+ if (!menu) return false
45
+ nextTick(activateHome)
46
+ if (syncUrl) syncMenuUrl(menu, { replace: replaceUrl, query })
47
+ return true
48
+ }
49
+ const topLevel = appMenus.find(m => m.ID === menuId)
50
+ let menuNode = null
51
+ let params = null
52
+ if (topLevel) {
53
+ // 顶级菜单:镜像 AppNav.toPage 的建表(不加 activeMenu*/openType,避免误触发子菜单下钻)
54
+ menuNode = topLevel
55
+ params = {
56
+ key: 'default-' + menuId,
57
+ moduleId: menuId,
58
+ component: 'module-index-page',
59
+ title: topLevel.NAME_ALIAS || topLevel.NAME || topLevel.text,
60
+ pageId: topLevel.MAIN_PAGE,
61
+ ...extraProps
62
+ }
63
+ } else {
64
+ // 子菜单:定位所属顶部 nav,下钻高亮
65
+ let navId = null
66
+ let pageId = ''
67
+ let activeMenuPageId = extraProps.pageId
68
+ for (let i = 0; i < appMenus.length; i++) {
69
+ const menu = findNavId(appMenus[i].children, menuId, appMenus[i])
70
+ if (menu) {
71
+ menuNode = menu
72
+ navId = menu.parent
73
+ if (menu.parentMenu) pageId = menu.parentMenu.MAIN_PAGE
74
+ if (!activeMenuPageId) activeMenuPageId = menu.MAIN_PAGE
75
+ break
76
+ }
77
+ }
78
+ if (!menuNode) return false
79
+ const moduleId = navId || menuId
80
+ const props = { ...extraProps }
81
+ delete props.pageId
82
+ params = {
83
+ key: 'default-' + moduleId,
84
+ openType: '1',
85
+ title: menuNode.NAME_ALIAS || menuNode.NAME || menuNode.text,
86
+ moduleId,
87
+ pageId,
88
+ activeMenuId: menuId,
89
+ activeMenuPageId,
90
+ ...props,
91
+ component: 'module-index-page'
92
+ }
93
+ }
94
+ store.commit('DESTORY_TAB', params.key)
95
+ nextTick(() => store.commit('CREATE_TAB', params))
96
+ if (syncUrl) syncMenuUrl(menuNode, { replace: replaceUrl, query, context: params })
97
+ return true
98
+ }
99
+
100
+ // 激活后把菜单键写进 URL(push——浏览器前进/后退可在菜单间穿梭;恢复场景用 replace)
101
+ export function syncMenuUrl (menu, { replace = false, query, context } = {}) {
102
+ // URL 一律用菜单 ID(32 位 hex——路由参数正则与 app1 的 :role 段消歧的前提);
103
+ // 编号(MODULE_CODE)仍可作为 toMenu 入参,但不进 URL
104
+ const key = menu && menu.ID
105
+ if (!key || !/^[0-9A-Fa-f]{32}$/.test(key)) return
106
+ const route = router.currentRoute.value
107
+ const name = effectiveRouteName(route.name)
108
+ if (name !== 'app1' && name !== 'hall' && name !== 'system') return // 独立窗口页/manage 等场景不动 URL
109
+ saveMenuContext(route, menu, context, navigationUserId())
110
+ if (getRouteMenuKey(route) === key && query === undefined) return
111
+ lastSyncedKey = key
112
+ const loc = menuLocation(route, key, query)
113
+ ;(replace ? router.replace(loc) : router.push(loc)).catch(() => {})
114
+ }
115
+
116
+ // 路由跳转失败(重复导航 / 守卫中断)不是业务错误,不该弹用户提示:
117
+ // 具名成常量而不是就地写空 catch,是为了把「有意忽略」写在明面上。
118
+ const ignoreNavFailure = () => {}
119
+
120
+ // syncMenuUrl 的反向操作:回首页时清除菜单路径段,并兼容配置中心旧的查询参数——
121
+ // 菜单已不再激活,URL 不该继续指向它(刷新/分享会又回到那个菜单)。
122
+ // URL 上本就没有 menuKey 的场景(manage / 独立窗口页 / 本就在首页)直接早退,不动 URL。
123
+ export function clearMenuUrl ({ replace = false } = {}) {
124
+ const route = router.currentRoute.value
125
+ if (!getRouteMenuKey(route)) return
126
+ const { client, system, app } = route.params
127
+ const name = effectiveRouteName(route.name)
128
+ const query = { ...route.query }
129
+ if (name === 'system') delete query.menuKey
130
+ const loc = name === 'system'
131
+ ? { name: 'system', params: { client, system, role: resolveRouteRole(route) }, query }
132
+ : name === 'hall'
133
+ ? { name: 'hall', params: { client }, query: route.query }
134
+ : { name: 'app1', params: { client, system, app }, query: route.query }
135
+ ;(replace ? router.replace(loc) : router.push(loc)).catch(ignoreNavFailure)
136
+ }
137
+
138
+ // URL → 激活(刷新恢复 / 浏览器前进后退 / 直达链接)。AppIndex 在菜单树就绪后与
139
+ // 当前菜单键变化时调用。找不到(权限变化/编号改了)返回 false 落应用首页。
140
+ export function activateFromRoute (route) {
141
+ const key = getRouteMenuKey(route)
142
+ if (!key) return false
143
+ if (key === lastSyncedKey) { // 自己刚 push 的,无需再激活
144
+ lastSyncedKey = null
145
+ return true
146
+ }
147
+ const menu = findMenuByKey(store.state.config.appMenus || [], key)
148
+ if (!menu) {
149
+ console.warn(`[nav] 菜单直达:找不到 menuKey=${key}(权限变化或编号已改),落应用首页`)
150
+ return false
151
+ }
152
+ const context = readMenuContext(route, menu, store.state.config.appMenus || [], navigationUserId())
153
+ if (context) {
154
+ const params = {
155
+ ...context,
156
+ key: 'default-' + context.moduleId,
157
+ component: context.component || 'module-index-page',
158
+ title: menu.NAME_ALIAS || menu.NAME || menu.text,
159
+ // 来源容器与目标相同时沿用独立入口,避免额外生成一层侧栏。
160
+ ...(context.moduleId !== menu.ID ? { activeMenuId: menu.ID, activeMenuPageId: menu.MAIN_PAGE } : {})
161
+ }
162
+ store.commit('DESTORY_TAB', params.key)
163
+ nextTick(() => store.commit('CREATE_TAB', params))
164
+ return true
165
+ }
166
+ return activateMenu(menu.ID, {}, { syncUrl: false })
167
+ }
168
+
169
+ // URL 上没有 menuKey 时的激活目标:应用首页(已开就选中,没开就用暂存的首页模板按需打开)。
170
+ // activateFromRoute 的对偶——菜单直达把「有 menuKey」这一侧接住了,这一侧接的是「无 menuKey」,
171
+ // 少了它,前进/后退落到无 menuKey 的那一格时地址栏变了、内容还停在上一个菜单上。
172
+ export function activateHome () {
173
+ const home = store.getters.getTab('default-index') || store.state.tabs.homeTab
174
+ if (!home) return false
175
+ if (store.getters.getTab(home.key)) store.commit('SELECT_TAB', home.key)
176
+ else store.commit('CREATE_TAB', home)
177
+ return true
178
+ }
179
+
180
+ // ── $nav 对外表面(组件内 this.$nav.xxx;组件外可直接 import { nav })──────────
181
+ export const nav = {
182
+ /** 跳到某菜单(key=编号或 ID)。等同用户点击菜单,URL 一并更新 */
183
+ toMenu (keyOrId, { replace = false, query } = {}) {
184
+ const menu = findMenuByKey(store.state.config.appMenus || [], String(keyOrId))
185
+ if (!menu) {
186
+ console.warn(`[nav] toMenu:找不到菜单 ${keyOrId}`)
187
+ return false
188
+ }
189
+ return activateMenu(menu.ID, {}, { syncUrl: true, replaceUrl: replace, query })
190
+ },
191
+ /** 生成包含菜单 ID 的分享地址;角色由打开者自己的登录上下文恢复。 */
192
+ menuUrl (keyOrId, query) {
193
+ const menu = findMenuByKey(store.state.config.appMenus || [], String(keyOrId))
194
+ if (!menu) return ''
195
+ const loc = menuLocation(router.currentRoute.value, menu.ID, query)
196
+ return window.location.origin + window.location.pathname + router.resolve(loc).href
197
+ },
198
+ /** 当前 URL 上的菜单键(无则 '') */
199
+ currentMenuKey () {
200
+ return getRouteMenuKey(router.currentRoute.value)
201
+ },
202
+ getMenuKey,
203
+ findMenuByKey,
204
+ /** 角色持久化读写(localStorage,客户+系统维度) */
205
+ role: roleStorage
206
+ }
207
+
208
+ export default nav
@@ -13,6 +13,7 @@ import I18n from './plugins/i18n'
13
13
  import installElementPlus from './plugins/element-plus'
14
14
  import { setApp } from './app'
15
15
  import { configureRuntime } from './runtime-services'
16
+ import routeComponents from './route-components'
16
17
  import { installGlobalMethods } from './mixins/methods-to-prototype'
17
18
  import optimizedGlobalMixin from './mixins/optimized-global'
18
19
  import loadComponentFactory from './loadComponent'
@@ -27,7 +28,7 @@ export function installCore (app, options = {}) {
27
28
  if (installed.has(app)) return installed.get(app)
28
29
  const { store, router, http = defaultHttp, plugins = [], components = {} } = options
29
30
  if (!store || !router) throw new TypeError('[epx/core] install 需要 store 和 router;可用 createFrameStore() 创建框架 store')
30
- configureRuntime({ ...options, http })
31
+ configureRuntime({ ...options, http, routeComponents: { ...routeComponents, ...options.routeComponents } })
31
32
  setApp(app)
32
33
  // 存量运行时内部仍有 window 访问,统一在安装阶段桥接,消费方无需手工挂载。
33
34
  window.store = store