@epxing/utils 0.0.0-stage → 5.3.0-beta.1

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 (230) hide show
  1. package/API.md +602 -0
  2. package/README.md +22 -2
  3. package/dist/_vendor/.pnpm/jsencrypt@3.5.4/_vendor/jsencrypt/bin/jsencrypt.min.js +1691 -0
  4. package/dist/_virtual/_commonjsHelpers.js +6 -0
  5. package/dist/_virtual/jsencrypt.min.js +31 -0
  6. package/dist/_virtual/jsencrypt.min2.js +4 -0
  7. package/dist/_virtual/preload-helper.js +88 -0
  8. package/dist/assets/ai-BBFn3QRy.png +0 -0
  9. package/dist/assets/ai_import-BxQ7Ohn6.png +0 -0
  10. package/dist/assets/app-DBNIokDP.png +0 -0
  11. package/dist/assets/approval-DGcBv-re.png +0 -0
  12. package/dist/assets/create_market_system-BH__UVbt.png +0 -0
  13. package/dist/assets/create_system-irg--ePh.png +0 -0
  14. package/dist/assets/custom-C-ujlHz1.png +0 -0
  15. package/dist/assets/default-icon-2193FMjv.png +0 -0
  16. package/dist/assets/ex_link-4fYPd9nh.png +0 -0
  17. package/dist/assets/excel-BYqRm2Y8.png +0 -0
  18. package/dist/assets/folder-CFVsNbjj.png +0 -0
  19. package/dist/assets/form-DD9HV-jN.png +0 -0
  20. package/dist/assets/import-tfKWbWVS.png +0 -0
  21. package/dist/assets/meta-Qorpi70B.png +0 -0
  22. package/dist/assets/module-DQrs7VAS.png +0 -0
  23. package/dist/assets/null-B2An29h4.png +0 -0
  24. package/dist/assets/option-CG02tIgh.svg +1 -0
  25. package/dist/assets/photo-uscRzRXF.png +0 -0
  26. package/dist/assets/process-Dcndu9m2.png +0 -0
  27. package/dist/assets/rule-DcYhYse0.png +0 -0
  28. package/dist/assets/service-CLYLpPl3.png +0 -0
  29. package/dist/assets/single_file-B3TJtJli.png +0 -0
  30. package/dist/assets/snapshot-history-BsVY9cVj.png +0 -0
  31. package/dist/assets/snapshot-manual-DBRfFHYI.png +0 -0
  32. package/dist/assets/snapshot-realtime-DjHnJI6C.png +0 -0
  33. package/dist/assets/staff-Bn_-_00l.png +0 -0
  34. package/dist/assets/sync_append-Bm8mtvZs.png +0 -0
  35. package/dist/assets/sync_append_update-DzjxqJ_U.png +0 -0
  36. package/dist/assets/sync_loading-D7LoNPFP.png +0 -0
  37. package/dist/assets/sync_replace-Cbbzz327.png +0 -0
  38. package/dist/assets/sync_update-Birao6Cr.png +0 -0
  39. package/dist/assets/userRole-D1LAdM6N.png +0 -0
  40. package/dist/assets/user_bg-BFHtaZjz.png +0 -0
  41. package/dist/src/Config.js +25 -0
  42. package/dist/src/asset-images.js +125 -0
  43. package/dist/src/assets/images/add.png.js +4 -0
  44. package/dist/src/assets/images/ai-button.png.js +4 -0
  45. package/dist/src/assets/images/ai.png.js +4 -0
  46. package/dist/src/assets/images/ai_import.png.js +4 -0
  47. package/dist/src/assets/images/app.png.js +4 -0
  48. package/dist/src/assets/images/approval.png.js +4 -0
  49. package/dist/src/assets/images/book.png.js +4 -0
  50. package/dist/src/assets/images/canvas-editor/alignment.svg.js +4 -0
  51. package/dist/src/assets/images/canvas-editor/arrow-left.svg.js +4 -0
  52. package/dist/src/assets/images/canvas-editor/arrow-right.svg.js +4 -0
  53. package/dist/src/assets/images/canvas-editor/block.svg.js +4 -0
  54. package/dist/src/assets/images/canvas-editor/bold.svg.js +4 -0
  55. package/dist/src/assets/images/canvas-editor/catalog.svg.js +4 -0
  56. package/dist/src/assets/images/canvas-editor/center.svg.js +4 -0
  57. package/dist/src/assets/images/canvas-editor/checkbox.svg.js +4 -0
  58. package/dist/src/assets/images/canvas-editor/close.svg.js +4 -0
  59. package/dist/src/assets/images/canvas-editor/codeblock.svg.js +4 -0
  60. package/dist/src/assets/images/canvas-editor/color.svg.js +4 -0
  61. package/dist/src/assets/images/canvas-editor/control.svg.js +4 -0
  62. package/dist/src/assets/images/canvas-editor/date.svg.js +4 -0
  63. package/dist/src/assets/images/canvas-editor/exit-fullscreen.svg.js +4 -0
  64. package/dist/src/assets/images/canvas-editor/format.svg.js +4 -0
  65. package/dist/src/assets/images/canvas-editor/highlight.svg.js +4 -0
  66. package/dist/src/assets/images/canvas-editor/hyperlink.svg.js +4 -0
  67. package/dist/src/assets/images/canvas-editor/image.svg.js +4 -0
  68. package/dist/src/assets/images/canvas-editor/italic.svg.js +4 -0
  69. package/dist/src/assets/images/canvas-editor/justify.svg.js +4 -0
  70. package/dist/src/assets/images/canvas-editor/latex.svg.js +4 -0
  71. package/dist/src/assets/images/canvas-editor/left.svg.js +4 -0
  72. package/dist/src/assets/images/canvas-editor/line-dash-dot-dot.svg.js +4 -0
  73. package/dist/src/assets/images/canvas-editor/line-dash-dot.svg.js +4 -0
  74. package/dist/src/assets/images/canvas-editor/line-dash-large-gap.svg.js +4 -0
  75. package/dist/src/assets/images/canvas-editor/line-dash-small-gap.svg.js +4 -0
  76. package/dist/src/assets/images/canvas-editor/line-dot.svg.js +4 -0
  77. package/dist/src/assets/images/canvas-editor/line-double.svg.js +4 -0
  78. package/dist/src/assets/images/canvas-editor/line-single.svg.js +4 -0
  79. package/dist/src/assets/images/canvas-editor/line-wavy.svg.js +4 -0
  80. package/dist/src/assets/images/canvas-editor/list.svg.js +4 -0
  81. package/dist/src/assets/images/canvas-editor/option.svg.js +4 -0
  82. package/dist/src/assets/images/canvas-editor/page-break.svg.js +4 -0
  83. package/dist/src/assets/images/canvas-editor/page-mode.svg.js +4 -0
  84. package/dist/src/assets/images/canvas-editor/page-scale-add.svg.js +4 -0
  85. package/dist/src/assets/images/canvas-editor/page-scale-minus.svg.js +4 -0
  86. package/dist/src/assets/images/canvas-editor/painter.svg.js +4 -0
  87. package/dist/src/assets/images/canvas-editor/paper-direction.svg.js +4 -0
  88. package/dist/src/assets/images/canvas-editor/paper-margin.svg.js +4 -0
  89. package/dist/src/assets/images/canvas-editor/paper-size.svg.js +4 -0
  90. package/dist/src/assets/images/canvas-editor/print.svg.js +4 -0
  91. package/dist/src/assets/images/canvas-editor/radio.svg.js +4 -0
  92. package/dist/src/assets/images/canvas-editor/redo.svg.js +4 -0
  93. package/dist/src/assets/images/canvas-editor/request-fullscreen.svg.js +4 -0
  94. package/dist/src/assets/images/canvas-editor/right.svg.js +4 -0
  95. package/dist/src/assets/images/canvas-editor/row-margin.svg.js +4 -0
  96. package/dist/src/assets/images/canvas-editor/search.svg.js +4 -0
  97. package/dist/src/assets/images/canvas-editor/separator.svg.js +4 -0
  98. package/dist/src/assets/images/canvas-editor/signature-undo.svg.js +4 -0
  99. package/dist/src/assets/images/canvas-editor/signature.svg.js +4 -0
  100. package/dist/src/assets/images/canvas-editor/size-add.svg.js +4 -0
  101. package/dist/src/assets/images/canvas-editor/size-minus.svg.js +4 -0
  102. package/dist/src/assets/images/canvas-editor/strikeout.svg.js +4 -0
  103. package/dist/src/assets/images/canvas-editor/subscript.svg.js +4 -0
  104. package/dist/src/assets/images/canvas-editor/superscript.svg.js +4 -0
  105. package/dist/src/assets/images/canvas-editor/table.svg.js +4 -0
  106. package/dist/src/assets/images/canvas-editor/title.svg.js +4 -0
  107. package/dist/src/assets/images/canvas-editor/trash.svg.js +4 -0
  108. package/dist/src/assets/images/canvas-editor/underline.svg.js +4 -0
  109. package/dist/src/assets/images/canvas-editor/undo.svg.js +4 -0
  110. package/dist/src/assets/images/canvas-editor/watermark.svg.js +4 -0
  111. package/dist/src/assets/images/canvas-editor/word-tool.svg.js +4 -0
  112. package/dist/src/assets/images/clear.png.js +4 -0
  113. package/dist/src/assets/images/create_market_system.png.js +4 -0
  114. package/dist/src/assets/images/create_system.png.js +4 -0
  115. package/dist/src/assets/images/custom.png.js +4 -0
  116. package/dist/src/assets/images/default-icon.png.js +4 -0
  117. package/dist/src/assets/images/ex_link.png.js +4 -0
  118. package/dist/src/assets/images/excel.png.js +4 -0
  119. package/dist/src/assets/images/file.png.js +4 -0
  120. package/dist/src/assets/images/folder.png.js +4 -0
  121. package/dist/src/assets/images/hall/form.png.js +4 -0
  122. package/dist/src/assets/images/hall/meta.png.js +4 -0
  123. package/dist/src/assets/images/hall/process.png.js +4 -0
  124. package/dist/src/assets/images/hall/rule.png.js +4 -0
  125. package/dist/src/assets/images/hall/service.png.js +4 -0
  126. package/dist/src/assets/images/hall/userRole.png.js +4 -0
  127. package/dist/src/assets/images/hr.png.js +4 -0
  128. package/dist/src/assets/images/import.png.js +4 -0
  129. package/dist/src/assets/images/jian.png.js +4 -0
  130. package/dist/src/assets/images/module.png.js +4 -0
  131. package/dist/src/assets/images/null.png.js +4 -0
  132. package/dist/src/assets/images/photo.png.js +4 -0
  133. package/dist/src/assets/images/qq.png.js +4 -0
  134. package/dist/src/assets/images/robot.png.js +4 -0
  135. package/dist/src/assets/images/single_file.png.js +4 -0
  136. package/dist/src/assets/images/snapshot-history.png.js +4 -0
  137. package/dist/src/assets/images/snapshot-manual.png.js +4 -0
  138. package/dist/src/assets/images/snapshot-realtime.png.js +4 -0
  139. package/dist/src/assets/images/staff.png.js +4 -0
  140. package/dist/src/assets/images/switch_app.png.js +4 -0
  141. package/dist/src/assets/images/sync_append.png.js +4 -0
  142. package/dist/src/assets/images/sync_append_update.png.js +4 -0
  143. package/dist/src/assets/images/sync_loading.png.js +4 -0
  144. package/dist/src/assets/images/sync_replace.png.js +4 -0
  145. package/dist/src/assets/images/sync_update.png.js +4 -0
  146. package/dist/src/assets/images/user_bg.png.js +4 -0
  147. package/dist/src/assets/images/weixin.png.js +4 -0
  148. package/dist/src/chart-config-update.js +17 -0
  149. package/dist/src/chart-data-adapters.js +112 -0
  150. package/dist/src/chart-option-override.js +128 -0
  151. package/dist/src/chart-option-paths.js +64 -0
  152. package/dist/src/chart-registry.js +46 -0
  153. package/dist/src/chart-render-host.js +57 -0
  154. package/dist/src/chart-request-broker.js +176 -0
  155. package/dist/src/chartTheme.js +683 -0
  156. package/dist/src/collection.js +25 -0
  157. package/dist/src/date-helper.js +60 -0
  158. package/dist/src/dom-helper.js +22 -0
  159. package/dist/src/eval-with.js +14 -0
  160. package/dist/src/fieldFactory.js +5 -0
  161. package/dist/src/formula.js +365 -0
  162. package/dist/src/hljs-lite.js +29 -0
  163. package/dist/src/index.js +24 -0
  164. package/dist/src/lazy-jsencrypt.js +13 -0
  165. package/dist/src/legacy.js +25 -0
  166. package/dist/src/locale.js +88 -0
  167. package/dist/src/menu-open.js +43 -0
  168. package/dist/src/mermaid.js +79 -0
  169. package/dist/src/meta.js +4 -0
  170. package/dist/src/mobile-chart-appearance.js +148 -0
  171. package/dist/src/moment-compat.js +558 -0
  172. package/dist/src/nav-history.js +184 -0
  173. package/dist/src/page.js +116 -0
  174. package/dist/src/pdf.viewer.js +4 -0
  175. package/dist/src/placeholder.js +64 -0
  176. package/dist/src/public.js +49 -0
  177. package/dist/src/security.js +26 -0
  178. package/dist/src/storage.js +107 -0
  179. package/dist/src/token-storage.js +108 -0
  180. package/dist/src/util.js +4 -0
  181. package/package.json +109 -3
  182. package/src/Config.js +25 -0
  183. package/src/asset-images.js +16 -0
  184. package/src/chart-config-update.js +16 -0
  185. package/src/chart-data-adapters.js +106 -0
  186. package/src/chart-option-override.js +126 -0
  187. package/src/chart-option-paths.js +63 -0
  188. package/src/chart-registry.js +41 -0
  189. package/src/chart-render-host.js +56 -0
  190. package/src/chart-request-broker.js +171 -0
  191. package/src/chartTheme.js +711 -0
  192. package/src/collection.js +28 -0
  193. package/src/date-helper.js +72 -0
  194. package/src/dom-helper.js +26 -0
  195. package/src/eval-with.js +21 -0
  196. package/src/fieldFactory.js +3 -0
  197. package/src/formula.js +362 -0
  198. package/src/hljs-lite.js +41 -0
  199. package/src/index.js +2 -0
  200. package/src/lazy-jsencrypt.js +36 -0
  201. package/src/legacy.js +29 -0
  202. package/src/locale.js +104 -0
  203. package/src/menu-open.js +72 -0
  204. package/src/mermaid.js +115 -0
  205. package/src/meta.js +2 -0
  206. package/src/mobile-chart-appearance.js +147 -0
  207. package/src/moment-compat.d.ts +91 -0
  208. package/src/moment-compat.js +744 -0
  209. package/src/nav-history.js +274 -0
  210. package/src/page.js +129 -0
  211. package/src/pdf.viewer.js +2 -0
  212. package/src/placeholder.js +107 -0
  213. package/src/public.js +11 -0
  214. package/src/security.js +29 -0
  215. package/src/storage.js +135 -0
  216. package/src/token-storage.js +120 -0
  217. package/src/util.js +2 -0
  218. package/types/Config.d.ts +4 -0
  219. package/types/collection.d.ts +40 -0
  220. package/types/date-helper.d.ts +11 -0
  221. package/types/date-time.d.ts +2 -0
  222. package/types/date.d.ts +1 -0
  223. package/types/dom-helper.d.ts +3 -0
  224. package/types/dom.d.ts +1 -0
  225. package/types/index.d.ts +1 -0
  226. package/types/moment-compat.d.ts +91 -0
  227. package/types/page.d.ts +56 -0
  228. package/types/placeholder.d.ts +42 -0
  229. package/types/public.d.ts +10 -0
  230. package/types/storage.d.ts +31 -0
@@ -0,0 +1,274 @@
1
+ /**
2
+ * 导航历史记账器:让浏览器历史与内部页面栈/弹窗严格一一对应
3
+ *
4
+ * 背景:本框架的页面栈是内存栈(epx-router-view 的 routes 数组),与浏览器
5
+ * history 天然脱钩。旧实现打开页面时 pushState 哑条目、应用内返回却从不回退
6
+ * 历史,且每个页面组件各挂一个 popstate 监听——导致移动端系统返回手势要按
7
+ * 很多次才能退出应用、根页面按返回没反应等问题。
8
+ *
9
+ * 记账规则(每个「可关闭物」对应恰好一条浏览器历史):
10
+ * - 打开页面 / 新标签 / 弹窗 / 抽屉时 pushEntry() 压入一条哑历史
11
+ * - 系统返回(popstate):全局唯一监听器按文档序找到最顶层可见的
12
+ * routerView,让其栈顶页面执行一次 backPage(弹窗页会走弹窗分支)
13
+ * - 应用内主动关闭(返回按钮 / 弹窗 X / 遮罩等):consume() 对称回退等量历史
14
+ * 由此系统返回在任意时刻的语义都是「关一层」;条目耗尽后再按返回即可
15
+ * 直接回到上一个应用/页面。
16
+ */
17
+
18
+ const state = {
19
+ // 当前由本应用压入且未被消耗的哑历史条数
20
+ entryCount: 0,
21
+ // 待吞掉的、由 consume/leaveApp 自身 history.go 触发的 popstate 数
22
+ suppressCount: 0,
23
+ suppressDeadline: 0,
24
+ // 正在处理系统返回:此期间联动触发的关闭动作不再回退历史
25
+ handlingPop: false,
26
+ // 冷启动退出守卫是否生效(见 install/markAppJump)
27
+ guardMode: false
28
+ }
29
+
30
+ // 所有存活的 epx-router-view 实例(含弹窗/抽屉内的)
31
+ const routerViews = []
32
+
33
+ // 待合并回退的历史条数(微任务内一次 history.go,避免与 pushState 时序交错)
34
+ let pendingGo = 0
35
+ let flushScheduled = false
36
+
37
+ // 当前应用上下文标识(路由路径前 4 段,容忍 role 等尾段差异)
38
+ // 大厅→应用 的正向跳转是 pushState+reload 整页重启(见 AppList.vue),
39
+ // 反向回退若不重启:hall 与 app 路由共用 App 组件,vue-router 复用实例导致白屏
40
+ let bootIdentity = ''
41
+
42
+ // 冷启动退出守卫(state.guardMode):应用不是从大厅/上个应用跳转而来时
43
+ // (无 epx-nav-prev 标记),移动端启动即压一条守卫条目,返回键打到守卫上就
44
+ // 原地补一条(视觉无操作),恢复旧版「防止移动端返回键直接退出网页」的语义;
45
+ // 从大厅跳转而来的应用不设守卫,根页面一次返回即回大厅
46
+ const PREV_CONTEXT_KEY = 'epx-nav-prev'
47
+
48
+ function pushGuard () {
49
+ window.history.pushState({ epxGuard: true }, '')
50
+ }
51
+
52
+ /**
53
+ * 跨应用跳转(大厅点应用 / 返回大厅 / 打开其他应用)前调用:
54
+ * 在 sessionStorage 留下标记,目标应用启动时据此关闭退出守卫,使返回键可以回到本页
55
+ */
56
+ export function markAppJump () {
57
+ try {
58
+ sessionStorage.setItem(PREV_CONTEXT_KEY, window.location.href)
59
+ } catch (e) {
60
+ // sessionStorage 不可用(隐私模式等)时退化为守卫模式
61
+ }
62
+ }
63
+
64
+ function routeIdentity () {
65
+ const hash = window.location.hash || ''
66
+ let path = hash.charAt(0) === '#' ? hash.slice(1) : window.location.pathname
67
+ path = path.split('?')[0]
68
+ return path.split('/').filter(Boolean).slice(0, 4).join('/')
69
+ }
70
+
71
+ /**
72
+ * 真实回退落到了本应用上下文之外(返回服务大厅/上一个应用)时整页重启目标路由,
73
+ * 与正向 pushState+reload 对称;同应用内的回退不受影响
74
+ */
75
+ function reloadIfLeftContext () {
76
+ if (bootIdentity && routeIdentity() !== bootIdentity) {
77
+ window.location.reload()
78
+ return true
79
+ }
80
+ return false
81
+ }
82
+
83
+ /**
84
+ * 未拦截的回退(含 leaveApp/consume 自发的)落点处理:
85
+ * 离开应用上下文 → 整页重启目标路由;仍在应用内且已无可关闭物 → 守卫模式下补回守卫条目
86
+ */
87
+ function handleLanding () {
88
+ if (reloadIfLeftContext()) return
89
+ if (state.guardMode && state.entryCount <= 0 && pendingGo <= 0) {
90
+ pushGuard()
91
+ }
92
+ }
93
+
94
+ export function registerRouterView (rv) {
95
+ if (routerViews.indexOf(rv) === -1) routerViews.push(rv)
96
+ }
97
+
98
+ export function unregisterRouterView (rv) {
99
+ const index = routerViews.indexOf(rv)
100
+ if (index > -1) routerViews.splice(index, 1)
101
+ }
102
+
103
+ /**
104
+ * 打开一个可关闭物(页面/新标签/弹窗/抽屉)时调用,压入一条哑历史。
105
+ * state 里记录当前深度,供整页刷新后 install() 恢复计数。
106
+ */
107
+ export function pushEntry () {
108
+ // 应用上下文可能在启动后被路由守卫补全(如追加 role 段),压条目时刷新基准
109
+ bootIdentity = routeIdentity()
110
+ if (pendingGo > 0) {
111
+ // 有待回退的条目未冲销:直接复用栈顶现有哑条目,一进一出相抵
112
+ pendingGo--
113
+ state.entryCount++
114
+ return
115
+ }
116
+ state.entryCount++
117
+ window.history.pushState({ epxNav: state.entryCount }, '')
118
+ }
119
+
120
+ /**
121
+ * 应用内主动关闭 delta 个可关闭物后调用,对称回退等量浏览器历史。
122
+ * 系统返回(popstate)联动的关闭不需要也不允许再回退——由 handlingPop 拦截。
123
+ */
124
+ export function consume (delta = 1) {
125
+ if (state.handlingPop) return
126
+ const n = Math.min(delta, state.entryCount)
127
+ if (n <= 0) return
128
+ state.entryCount -= n
129
+ pendingGo += n
130
+ scheduleFlush()
131
+ }
132
+
133
+ /**
134
+ * 弹窗/抽屉关闭时的历史冲销(幂等:只对压过历史且未冲销的弹窗生效)
135
+ */
136
+ export function consumeForPopup (popupObj) {
137
+ if (!popupObj || !popupObj._pushedHistory) return
138
+ popupObj._pushedHistory = false
139
+ consume(1)
140
+ }
141
+
142
+ /**
143
+ * 吞掉下一次 popstate(自己发起 history 回退前调用)。
144
+ * 带超时:history.go 越过栈底或被 iframe 条目吃掉时 popstate 不会来,防止泄漏。
145
+ */
146
+ export function suppressNextPop () {
147
+ state.suppressCount++
148
+ state.suppressDeadline = Date.now() + 1500
149
+ }
150
+
151
+ /**
152
+ * 是否处于应用根页面:没有打开的弹窗/抽屉,且所有可见 routerView 的页面栈都只剩 1 页。
153
+ * 供 $backPage 判断「返回」语义应为离开应用(回上个应用/大厅)而非销毁页面
154
+ */
155
+ export function isAtRootPage () {
156
+ const opened = window.openedPopup
157
+ if (Array.isArray(opened) && opened.some(popup => popup && !popup.closed)) return false
158
+ for (const rv of routerViews) {
159
+ if (!rv.routes || rv.routes.length === 0) continue
160
+ if (!rv.$el || !isVisible(rv.$el)) continue
161
+ if (rv.routes.length > 1) return false
162
+ }
163
+ return true
164
+ }
165
+
166
+ /**
167
+ * 把控制权交还浏览器/宿主(原 Tabs 的 history.go(-1)):
168
+ * 越过所有残留哑条目直接离开应用。
169
+ */
170
+ export function leaveApp () {
171
+ const n = state.entryCount + pendingGo + 1
172
+ state.entryCount = 0
173
+ pendingGo = 0
174
+ suppressNextPop()
175
+ window.history.go(-n)
176
+ }
177
+
178
+ function scheduleFlush () {
179
+ if (flushScheduled) return
180
+ flushScheduled = true
181
+ Promise.resolve().then(() => {
182
+ flushScheduled = false
183
+ if (pendingGo <= 0) return
184
+ const n = pendingGo
185
+ pendingGo = 0
186
+ suppressNextPop()
187
+ window.history.go(-n)
188
+ })
189
+ }
190
+
191
+ function isVisible (el) {
192
+ return !!el && el.isConnected && el.offsetParent !== null
193
+ }
194
+
195
+ /**
196
+ * 找到当前应响应系统返回的页面组件:
197
+ * 可见且栈非空的 routerView 中取文档序最靠后的一个(嵌套内层在外层之后、
198
+ * 弹窗/抽屉挂在 body 尾部在主界面之后),其栈顶页面即当前用户看到的最顶层页。
199
+ */
200
+ function findTopPage () {
201
+ let top = null
202
+ for (const rv of routerViews) {
203
+ if (!rv.routes || rv.routes.length === 0) continue
204
+ if (!rv.$el || !isVisible(rv.$el)) continue
205
+ if (!top) {
206
+ top = rv
207
+ continue
208
+ }
209
+ const pos = top.$el.compareDocumentPosition(rv.$el)
210
+ // FOLLOWING 含「在其后」与「被其包含」两种情况,都视为更顶层
211
+ if (pos & Node.DOCUMENT_POSITION_FOLLOWING) top = rv
212
+ }
213
+ if (!top) return null
214
+ const pageWrap = top.$refs && top.$refs.page
215
+ // Vue3 无 $children(原 Vue2 实现用 pageWrap.$children[0] 取 epx-page 唯一的渲染子节点):
216
+ // epx-page(packages/components/src/page/Page.vue)内部动态渲染实际页面组件的
217
+ // `<component :is="componentName">` 标了 ref="component",直接取同一个实例,语义等价。
218
+ return (pageWrap && pageWrap.$refs && pageWrap.$refs.component) || null
219
+ }
220
+
221
+ function onPopstate (event) {
222
+ if (state.suppressCount > 0) {
223
+ if (Date.now() <= state.suppressDeadline) {
224
+ state.suppressCount--
225
+ // leaveApp 的回退会越出应用上下文,同样需要整页重启目标路由
226
+ handleLanding()
227
+ return
228
+ }
229
+ state.suppressCount = 0
230
+ }
231
+ // 推迟到微任务:让 AiChatPage 等自管理 popstate 的组件先行处理并标记
232
+ Promise.resolve().then(() => {
233
+ if (event && event._epxNavHandled) return
234
+ if (state.entryCount <= 0) {
235
+ // 条目耗尽后的真实回退:离开本应用(返回大厅/上一个应用)或撞上退出守卫
236
+ handleLanding()
237
+ return
238
+ }
239
+ state.entryCount--
240
+ const page = findTopPage()
241
+ if (!page || typeof page.backPage !== 'function') return
242
+ state.handlingPop = true
243
+ try {
244
+ page.backPage({ fromPopstate: true })
245
+ } finally {
246
+ state.handlingPop = false
247
+ }
248
+ })
249
+ }
250
+
251
+ export function install () {
252
+ if (window.__epxNavHistoryInstalled) return
253
+ window.__epxNavHistoryInstalled = true
254
+ // 调试句柄:控制台可查 entryCount/suppressCount 排查返回问题
255
+ window.__epxNavHistory = state
256
+ bootIdentity = routeIdentity()
257
+ // 冷启动(非跨应用跳转而来)的移动端:压守卫条目防止返回键直接退出网页
258
+ let cameFromApp = false
259
+ try {
260
+ cameFromApp = !!sessionStorage.getItem(PREV_CONTEXT_KEY)
261
+ } catch (e) {
262
+ // 读不到时按冷启动处理
263
+ }
264
+ if (!cameFromApp && window.innerWidth <= 992) {
265
+ state.guardMode = true
266
+ pushGuard()
267
+ }
268
+ // 整页刷新恢复:当前条目若带 epxNav 深度标记,说明其下方还有等量哑条目
269
+ const s = window.history.state
270
+ if (s && typeof s.epxNav === 'number' && s.epxNav > 0) {
271
+ state.entryCount = s.epxNav
272
+ }
273
+ window.addEventListener('popstate', onPopstate)
274
+ }
package/src/page.js ADDED
@@ -0,0 +1,129 @@
1
+ class Page {
2
+ constructor (props) {
3
+ const { label, linkEntitys, relatedEntitys, sysId, data } = props
4
+ const { CONTENT, FLAG, ID, META_ID, META_CODE, PAGE_ID, PAGE_CODE, PAGE_PATH, VISUAL_CONFIG_PATH, TITLE, HELP, SYSTEM, SYSTEM_CODE, RIGHT_CONTROL_MODE } = data
5
+ this.label = label
6
+ this.title = TITLE
7
+ this.linkEntitys = linkEntitys
8
+ this.relatedEntitys = relatedEntitys
9
+ this.sysId = sysId
10
+ // eslint-disable-next-line no-eval
11
+ this.config = eval('(' + (CONTENT || '{}') + ')')
12
+ this.flag = FLAG
13
+ this.pageId = ID
14
+ this.templateId = PAGE_ID
15
+ this.metaId = META_ID
16
+ this.metaCode = META_CODE
17
+ this.pagePath = PAGE_PATH
18
+ this.pageCode = PAGE_CODE
19
+ this.system = SYSTEM
20
+ this.systemCode = SYSTEM_CODE
21
+ this.help = HELP
22
+ this.rightControlMode = RIGHT_CONTROL_MODE
23
+ this.visualConfigPath = VISUAL_CONFIG_PATH
24
+ return this
25
+ }
26
+
27
+ getPageId () {
28
+ return this.pageId
29
+ }
30
+
31
+ getLabel () {
32
+ return this.label
33
+ }
34
+
35
+ getMetaId () {
36
+ return this.metaId
37
+ }
38
+
39
+ getFlag () {
40
+ return this.flag
41
+ }
42
+
43
+ getPagePath () {
44
+ return this.pagePath
45
+ }
46
+
47
+ getConfig () {
48
+ return this.config
49
+ }
50
+
51
+ /**
52
+ * 主键字段
53
+ * @return {}
54
+ */
55
+ getKey () {
56
+ // var me=this;
57
+ return this.keyNames[0]
58
+ }
59
+
60
+ /**
61
+ * 获取所有属性
62
+ * @return {}
63
+ */
64
+ getAttris () {
65
+ const data = []
66
+ const m = this
67
+ const attris = m.attributes
68
+ attris.forEach((name, index) => {
69
+ const a = attris[name]
70
+ data[index] = { ID: a.name, NAME: a.label }
71
+ })
72
+ return data
73
+ }
74
+
75
+ /**
76
+ * ID对应LAVEL
77
+ */
78
+ getIdLabelAttris () {
79
+ const data = []
80
+ const m = this
81
+ const attris = m.attributes
82
+ attris.forEach((name, index) => {
83
+ const a = attris[name]
84
+ data[index] = { ID: a.id, NAME: a.label }
85
+ })
86
+ return data
87
+ }
88
+
89
+ /**
90
+ * NAME对应LAVEL
91
+ */
92
+ getNameLabelAttris () {
93
+ const data = []
94
+ const m = this
95
+ const attris = m.attributes
96
+ attris.forEach((name, index) => {
97
+ const a = attris[name]
98
+ data[index] = { ID: a.name, NAME: a.label }
99
+ })
100
+ return data
101
+ }
102
+
103
+ /**
104
+ * 根据字段ID获取字段Name
105
+ * @param {} attriId
106
+ * @return {}
107
+ */
108
+ getAttriNameById (attriId) {
109
+ const me = this
110
+ if (me.attriIds) {
111
+ return me.attriIds[attriId]
112
+ }
113
+ return null
114
+ }
115
+
116
+ /**
117
+ * 根据字段key获取label
118
+ */
119
+ getLabelByField (field) {
120
+ const m = this
121
+ const attribute = m.getAttributes()[field]
122
+ if (attribute) {
123
+ return attribute.label
124
+ }
125
+ return null
126
+ }
127
+ }
128
+
129
+ export default Page
@@ -0,0 +1,2 @@
1
+ // 兼容旧路径;新代码从 @epxing/core 导入。
2
+ export { default } from '@epxing/core/src/runtime-utils/pdf.viewer'
@@ -0,0 +1,107 @@
1
+ // 统一的 `#{path}` 占位符解析。
2
+ //
3
+ // 为什么要有这个文件:同一个「按 key 从当前上下文取值」的操作,全仓被复制粘贴出
4
+ // **8 种不同语义**(105 行 / 51 个文件),归一化后是这样:
5
+ //
6
+ // 30 次 $query → $tab ← 根本不查组件自身
7
+ // 16 次 $query → $tab → this ← 组件自身排最后
8
+ // 12 次 this → $query ← 少了 $tab
9
+ // 8 次 this → $query → $tab → $route.query ← 多一个来源
10
+ // 6 次 this → $query → $tab
11
+ // 3 次 this
12
+ // …
13
+ //
14
+ // 也就是说同一个 `#{userId}` 写在不同组件里,解析结果不一样。本文件不试图替所有调用点
15
+ // 决定「该查哪些来源」——那是各组件的语境,强行统一等于拿 51 个文件的行为去赌。
16
+ // 它统一的是**取值语义**,把三个各处都错的地方一次修掉:
17
+ //
18
+ // 1. 命中判据是「这个来源**有没有**这个路径」,不是「取出来真不真」。
19
+ // 存量用 `eval(a.k) || eval(b.k) || ''` 串联,于是合法的 `0` / `''` / `false`
20
+ // 会静默穿透到下一个来源去取同名值——这是个真 bug,不是风格问题。
21
+ // 2. **全局**替换。存量写的是 `.replace(/#\{…\}/, fn)`,没有 `g`,
22
+ // 一个字符串里有多个占位符时只有第一个被替换。
23
+ // 3. 不用 eval。存量靠 `eval(\`that.${key}\`)` 做属性取值;这里就是纯属性路径遍历。
24
+ // (顺带说明:存量那个 eval 的注入风险其实很低——正则字符集里没有括号,
25
+ // 写不进函数调用。所以这次改动的理由是**语义一致性**,不是安全。)
26
+ //
27
+ // 迁移策略:新代码一律用这里;存量按「哪天碰到哪个文件就顺手迁哪个」推进,
28
+ // 不做一次性全量替换。
29
+
30
+ // 与存量 `#{}` 正则的字符集保持一致(含 `|`:`busi|NAME` 这类字段名里带竖线),
31
+ // 但补上 g —— 见上面第 2 点。
32
+ export const PLACEHOLDER_RE = /#\{([\w\-.|$#]+)\}/g
33
+
34
+ /**
35
+ * 按属性路径在**一个**来源里取值。
36
+ * 只按 `.` 和 `[...]` 分段;`|` 当普通字符(字段名可能长这样:`busi|NAME`)。
37
+ * @returns {{ found: boolean, value: any }} found 区分「没有这个路径」与「值恰好是 undefined」
38
+ */
39
+ export function lookup (path, source) {
40
+ if (source == null || typeof path !== 'string' || !path) return { found: false, value: undefined }
41
+ const keys = []
42
+ let cur = ''
43
+ let inBracket = false
44
+ for (const ch of path) {
45
+ if (ch === '[') {
46
+ inBracket = true
47
+ if (cur) { keys.push(cur); cur = '' }
48
+ } else if (ch === ']') {
49
+ inBracket = false
50
+ if (cur) { keys.push(cur.replace(/^['"](.*)['"]$/, '$1')); cur = '' }
51
+ } else if (ch === '.' && !inBracket) {
52
+ if (cur) { keys.push(cur); cur = '' }
53
+ } else {
54
+ cur += ch
55
+ }
56
+ }
57
+ if (cur) keys.push(cur)
58
+ if (!keys.length) return { found: false, value: undefined }
59
+
60
+ let obj = source
61
+ for (let i = 0; i < keys.length; i++) {
62
+ if (obj == null) return { found: false, value: undefined }
63
+ const k = keys[i]
64
+ // 用 in / hasOwnProperty 判存在,而不是看取出来的值真不真
65
+ const has = typeof obj === 'object' ? (k in obj) : Object.prototype.hasOwnProperty.call(Object(obj), k)
66
+ if (!has) return { found: false, value: undefined }
67
+ obj = obj[k]
68
+ }
69
+ return { found: true, value: obj }
70
+ }
71
+
72
+ /**
73
+ * 按给定顺序在多个来源里找第一个**拥有**该路径的。
74
+ * @param {string} path
75
+ * @param {Array<object>} sources 有序来源,如 [this, this.$query, this.$tab]
76
+ * @returns {{ found: boolean, value: any, index: number }} index 为命中的来源下标,未命中为 -1
77
+ */
78
+ export function resolveKey (path, sources = []) {
79
+ for (let i = 0; i < sources.length; i++) {
80
+ const r = lookup(path, sources[i])
81
+ if (r.found) return { found: true, value: r.value, index: i }
82
+ }
83
+ return { found: false, value: undefined, index: -1 }
84
+ }
85
+
86
+ /**
87
+ * 替换字符串里的全部 `#{path}`。
88
+ * @param {string} text
89
+ * @param {Array<object>} sources 有序来源
90
+ * @param {object} [options]
91
+ * @param {'empty'|'keep'} [options.onMissing='empty']
92
+ * 'empty' —— 取不到就换成空串(与存量 `… || ''` 的收尾一致,是默认)
93
+ * 'keep' —— 原样保留 `#{path}`。⚠️ 保留下来的是**非空字符串**,
94
+ * 也就是真值,`a || b` 形式的兜底会失效——选它之前先确认调用方判得了。
95
+ * @returns {string}
96
+ */
97
+ export function resolvePlaceholders (text, sources = [], options = {}) {
98
+ if (typeof text !== 'string' || text.indexOf('#{') === -1) return text
99
+ const onMissing = options.onMissing || 'empty'
100
+ return text.replace(PLACEHOLDER_RE, (match, path) => {
101
+ const r = resolveKey(path, sources)
102
+ if (!r.found) return onMissing === 'keep' ? match : ''
103
+ return r.value == null ? '' : String(r.value)
104
+ })
105
+ }
106
+
107
+ export default { PLACEHOLDER_RE, lookup, resolveKey, resolvePlaceholders }
package/src/public.js ADDED
@@ -0,0 +1,11 @@
1
+ export { default as Config } from './Config'
2
+ export { default as Page } from './page'
3
+ export { default as placeholder } from './placeholder'
4
+ export * from './placeholder'
5
+ export * from './date-helper'
6
+ export * from './dom-helper'
7
+ export { default as storage, getXsrfToken, setXsrfToken } from './storage'
8
+ export { default as cookie } from './storage'
9
+ export { default as moment, default as dateTime, createMoment } from './moment-compat'
10
+ export { default as lodash } from './collection'
11
+ export * from './collection'
@@ -0,0 +1,29 @@
1
+ const DANGEROUS_PATTERNS = [
2
+ /\beval\s*\(/,
3
+ /(?:^|[^$])\balert\s*\(/,
4
+ /(?:^|[^$])\bconfirm\s*\(/,
5
+ /(?:^|[^$])\bprompt\s*\(/,
6
+ /\bdocument\s*\.\s*write\s*\(/,
7
+ /\bdocument\s*\.\s*writeln\s*\(/,
8
+ /\blocation\s*\.\s*(href|replace|assign)\s*[=(]/,
9
+ /\bsetTimeout\s*\(\s*['"`]/,
10
+ /\bsetInterval\s*\(\s*['"`]/,
11
+ /\bnew\s+Function\s*\(/,
12
+ /\bimportScripts\s*\(/,
13
+ /\bfetch\s*\(/,
14
+ /\bXMLHttpRequest\b/
15
+ ]
16
+
17
+ /**
18
+ * 检查函数是否包含高危调用
19
+ * @param {Function} fn
20
+ * @returns {boolean} true 表示安全,false 表示包含高危调用
21
+ */
22
+ export function isSafeFn (fn) {
23
+ const hit = DANGEROUS_PATTERNS.find(pattern => pattern.test(fn.toString()))
24
+ if (hit) {
25
+ console.warn('[security] 函数包含高危调用,已阻止执行。', hit)
26
+ return false
27
+ }
28
+ return true
29
+ }
package/src/storage.js ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Web Storage 兼容层:提供与 js-cookie 同形的 get / set / remove API,
3
+ * 但底层只读写 sessionStorage / localStorage,完全不接触浏览器 Cookie。
4
+ *
5
+ * 背景:安全扫描要求站点不使用 Cookie。前端历史上只用 cookie 读过后端下发的
6
+ * xsrf_token;现在 `$cookie` / `Frame.cookie` 都指向本模块,老代码
7
+ * `this.$cookie.get(...)` 不用改,只是再也读写不到浏览器 Cookie。
8
+ *
9
+ * 语义约定:
10
+ * - get(key):先查 sessionStorage,再查 localStorage;已过期的值当不存在处理(顺手清掉)
11
+ * - set(key, value, options):默认写 localStorage;options.session = true 写 sessionStorage;
12
+ * options.expires 支持「天数」或 Date(对齐 js-cookie),过期时间另存在 `${key}.expires`;
13
+ * 同名键只保留一份(写入一个存储区时清掉另一个存储区的同名键)
14
+ * - remove(key):两个存储区连同过期标记一起清掉
15
+ * 所有读写都包在 try/catch 里:隐私模式 / 禁用存储时退化为 no-op,不抛错。
16
+ */
17
+ const EXPIRES_SUFFIX = '.expires'
18
+
19
+ const getArea = (session) => {
20
+ try {
21
+ return session ? window.sessionStorage : window.localStorage
22
+ } catch (e) {
23
+ return null
24
+ }
25
+ }
26
+
27
+ // 查找顺序:sessionStorage 优先于 localStorage
28
+ const getAreas = () => [getArea(true), getArea(false)].filter(Boolean)
29
+
30
+ const toExpiresAt = (expires) => {
31
+ if (expires === undefined || expires === null || expires === '') return null
32
+ if (expires instanceof Date) return expires.getTime()
33
+ const days = Number(expires)
34
+ if (Number.isNaN(days)) return null
35
+ return Date.now() + days * 864e5
36
+ }
37
+
38
+ const toText = (value) => {
39
+ if (typeof value === 'string') return value
40
+ if (value === undefined || value === null) return ''
41
+ if (typeof value === 'object') return JSON.stringify(value)
42
+ return String(value)
43
+ }
44
+
45
+ const readFrom = (area, key) => {
46
+ try {
47
+ const value = area.getItem(key)
48
+ if (value === null) return undefined
49
+ const expiresAt = Number(area.getItem(key + EXPIRES_SUFFIX))
50
+ if (expiresAt && Date.now() > expiresAt) {
51
+ area.removeItem(key)
52
+ area.removeItem(key + EXPIRES_SUFFIX)
53
+ return undefined
54
+ }
55
+ return value
56
+ } catch (e) {
57
+ return undefined
58
+ }
59
+ }
60
+
61
+ const removeFrom = (area, key) => {
62
+ try {
63
+ area.removeItem(key)
64
+ area.removeItem(key + EXPIRES_SUFFIX)
65
+ } catch (e) {}
66
+ }
67
+
68
+ const storage = {
69
+ /**
70
+ * @param {string} key
71
+ * @returns {string | undefined} 不存在或已过期返回 undefined(与 js-cookie 一致)
72
+ */
73
+ get (key) {
74
+ if (key === undefined || key === null) return undefined
75
+ key = String(key)
76
+ const areas = getAreas()
77
+ for (let i = 0; i < areas.length; i++) {
78
+ const value = readFrom(areas[i], key)
79
+ if (value !== undefined) return value
80
+ }
81
+ return undefined
82
+ },
83
+ /**
84
+ * @param {string} key
85
+ * @param {*} value 非字符串会被转成字符串(对象走 JSON.stringify)
86
+ * @param {{ expires?: number | Date, session?: boolean }} [options]
87
+ * @returns {string | undefined} 实际写入的字符串
88
+ */
89
+ set (key, value, options) {
90
+ if (key === undefined || key === null) return undefined
91
+ key = String(key)
92
+ const { session = false, expires } = options || {}
93
+ const target = getArea(session)
94
+ if (!target) return undefined
95
+ const text = toText(value)
96
+ const expiresAt = toExpiresAt(expires)
97
+ try {
98
+ target.setItem(key, text)
99
+ if (expiresAt) target.setItem(key + EXPIRES_SUFFIX, String(expiresAt))
100
+ else target.removeItem(key + EXPIRES_SUFFIX)
101
+ } catch (e) {
102
+ return undefined
103
+ }
104
+ const other = getArea(!session)
105
+ if (other) removeFrom(other, key)
106
+ return text
107
+ },
108
+ /**
109
+ * @param {string} key
110
+ */
111
+ remove (key) {
112
+ if (key === undefined || key === null) return
113
+ key = String(key)
114
+ getAreas().forEach(area => removeFrom(area, key))
115
+ }
116
+ }
117
+
118
+ /**
119
+ * 防 CSRF 令牌:由后端通过响应头 `xsrf_token`(或 `X-XSRF-TOKEN`)下发,
120
+ * axios 响应拦截器写入 sessionStorage(随标签页关闭失效),请求拦截器再回填到
121
+ * `xsrf_token` 请求头 / 下载类 URL 的 `xsrf_token` 查询参数。
122
+ */
123
+ export const XSRF_TOKEN_KEY = 'xsrf_token'
124
+
125
+ /** @returns {string} 没有令牌时返回空串,方便直接拼接到 URL / 请求头 */
126
+ export const getXsrfToken = () => storage.get(XSRF_TOKEN_KEY) || ''
127
+
128
+ export const setXsrfToken = (token) => {
129
+ if (!token) return
130
+ storage.set(XSRF_TOKEN_KEY, token, { session: true })
131
+ }
132
+
133
+ export const clearXsrfToken = () => storage.remove(XSRF_TOKEN_KEY)
134
+
135
+ export default storage