vome-core 0.0.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 (209) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/server/index.d.ts +1 -0
  4. package/dist/server/index.js +1 -0
  5. package/dist/src/auth/define-macro.d.ts +8 -0
  6. package/dist/src/auth/index.d.ts +2 -0
  7. package/dist/src/comm/daily-log.d.ts +8 -0
  8. package/dist/src/comm/env.d.ts +4 -0
  9. package/dist/src/comm/format-time.d.ts +10 -0
  10. package/dist/src/comm/merge.d.ts +2 -0
  11. package/dist/src/comm/scan.d.ts +4 -0
  12. package/dist/src/comm/startBanner.d.ts +1 -0
  13. package/dist/src/core/app.d.ts +37 -0
  14. package/dist/src/core/config.d.ts +2 -0
  15. package/dist/src/core/context/index.d.ts +22 -0
  16. package/dist/src/core/host-infra.d.ts +20 -0
  17. package/dist/src/core/index.d.ts +20 -0
  18. package/dist/src/core/plugins/async-context.d.ts +3 -0
  19. package/dist/src/core/plugins/error.d.ts +5 -0
  20. package/dist/src/core/plugins/logging.d.ts +3 -0
  21. package/dist/src/core/plugins/openapi.d.ts +3 -0
  22. package/dist/src/core/vome-config.d.ts +2 -0
  23. package/dist/src/ext/base-plugin.d.ts +13 -0
  24. package/dist/src/ext/index.d.ts +15 -0
  25. package/dist/src/ext/load-plugin.d.ts +12 -0
  26. package/dist/src/ext/micro-apps.d.ts +60 -0
  27. package/dist/src/ext/module-gateway.d.ts +10 -0
  28. package/dist/src/ext/module-registry.d.ts +45 -0
  29. package/dist/src/ext/path.d.ts +8 -0
  30. package/dist/src/index.d.ts +10 -0
  31. package/dist/src/ioc/container.d.ts +29 -0
  32. package/dist/src/ioc/decorators.d.ts +35 -0
  33. package/dist/src/ioc/host.d.ts +2 -0
  34. package/dist/src/ioc/index.d.ts +6 -0
  35. package/dist/src/ioc/metadata.d.ts +12 -0
  36. package/dist/src/ioc/scope.d.ts +9 -0
  37. package/dist/src/logger/bootstrap.d.ts +2 -0
  38. package/dist/src/logger/exception.d.ts +22 -0
  39. package/dist/src/logger/index.d.ts +5 -0
  40. package/dist/src/logger/logger.d.ts +18 -0
  41. package/dist/src/logger/pino.d.ts +8 -0
  42. package/dist/src/orm/base-columns.d.ts +11 -0
  43. package/dist/src/orm/column-comments.d.ts +25 -0
  44. package/dist/src/orm/crud-config.d.ts +7 -0
  45. package/dist/src/orm/crud-register.d.ts +5 -0
  46. package/dist/src/orm/data-scope.d.ts +24 -0
  47. package/dist/src/orm/db-store.d.ts +12 -0
  48. package/dist/src/orm/entity-schema.d.ts +7 -0
  49. package/dist/src/orm/index.d.ts +22 -0
  50. package/dist/src/orm/query-builder.d.ts +12 -0
  51. package/dist/src/orm/query-op.d.ts +27 -0
  52. package/dist/src/orm/repository.d.ts +74 -0
  53. package/dist/src/orm/service.d.ts +71 -0
  54. package/dist/src/orm/tenant.d.ts +24 -0
  55. package/dist/src/routing/base.d.ts +27 -0
  56. package/dist/src/routing/context.d.ts +14 -0
  57. package/dist/src/routing/crud-summary.d.ts +2 -0
  58. package/dist/src/routing/decorators.d.ts +48 -0
  59. package/dist/src/routing/eps.d.ts +16 -0
  60. package/dist/src/routing/index.d.ts +8 -0
  61. package/dist/src/routing/metadata.d.ts +28 -0
  62. package/dist/src/routing/openapi-tags.d.ts +23 -0
  63. package/dist/src/routing/path-utils.d.ts +5 -0
  64. package/dist/src/routing/perm.d.ts +18 -0
  65. package/dist/src/routing/register.d.ts +7 -0
  66. package/dist/src/server/index.d.ts +10 -0
  67. package/dist/src/shared/index.d.ts +8 -0
  68. package/dist/src/shared/tree.d.ts +18 -0
  69. package/dist/typings/comm/crud.d.ts +24 -0
  70. package/dist/typings/comm/page.d.ts +16 -0
  71. package/dist/typings/comm/scan.d.ts +6 -0
  72. package/dist/typings/common.d.ts +6 -0
  73. package/dist/typings/context/data.d.ts +10 -0
  74. package/dist/typings/ext/module.d.ts +45 -0
  75. package/dist/typings/ext/plugin.d.ts +40 -0
  76. package/dist/typings/ioc/metadata.d.ts +31 -0
  77. package/dist/typings/logger/config.d.ts +6 -0
  78. package/dist/typings/routing/crud.d.ts +99 -0
  79. package/dist/typings/routing/eps.d.ts +53 -0
  80. package/dist/typings/routing/meta.d.ts +56 -0
  81. package/dist/typings/routing/register.d.ts +15 -0
  82. package/dist/typings/routing/scan-context.d.ts +6 -0
  83. package/package.json +96 -0
  84. package/src/admin/api/client.ts +184 -0
  85. package/src/admin/components/vm-aside.vue +227 -0
  86. package/src/admin/components/vm-auto-perm-dialog.vue +361 -0
  87. package/src/admin/components/vm-check-tree.vue +338 -0
  88. package/src/admin/components/vm-dept-tree.vue +549 -0
  89. package/src/admin/components/vm-empty.vue +62 -0
  90. package/src/admin/components/vm-eps-perm-picker.vue +114 -0
  91. package/src/admin/components/vm-group-cascader.vue +563 -0
  92. package/src/admin/components/vm-icon-picker.vue +616 -0
  93. package/src/admin/components/vm-markdown.vue +141 -0
  94. package/src/admin/components/vm-ri-icon.vue +21 -0
  95. package/src/admin/components/vm-role-picker.vue +314 -0
  96. package/src/admin/components/vm-view-path-picker.vue +56 -0
  97. package/src/admin/config/plugin-dev.ts +31 -0
  98. package/src/admin/crud/components/vm-column-custom.vue +196 -0
  99. package/src/admin/crud/components/vm-context-menu.vue +99 -0
  100. package/src/admin/crud/components/vm-date-picker.vue +74 -0
  101. package/src/admin/crud/components/vm-date-range.vue +291 -0
  102. package/src/admin/crud/components/vm-date-text.vue +41 -0
  103. package/src/admin/crud/components/vm-dict-tag.vue +47 -0
  104. package/src/admin/crud/components/vm-file-icon.vue +54 -0
  105. package/src/admin/crud/components/vm-multi-select.vue +181 -0
  106. package/src/admin/crud/components/vm-number-range.vue +200 -0
  107. package/src/admin/crud/components/vm-radio.vue +87 -0
  108. package/src/admin/crud/components/vm-select.vue +131 -0
  109. package/src/admin/crud/components/vm-switch.vue +128 -0
  110. package/src/admin/crud/components/vm-tree-select.vue +456 -0
  111. package/src/admin/crud/components/vm-upload-item.vue +215 -0
  112. package/src/admin/crud/components/vm-upload.vue +323 -0
  113. package/src/admin/crud/components/vm-user-select.vue +234 -0
  114. package/src/admin/crud/config.ts +38 -0
  115. package/src/admin/crud/confirm.ts +135 -0
  116. package/src/admin/crud/dict.ts +49 -0
  117. package/src/admin/crud/index.ts +79 -0
  118. package/src/admin/crud/key.ts +47 -0
  119. package/src/admin/crud/mitt.ts +27 -0
  120. package/src/admin/crud/plugins.ts +298 -0
  121. package/src/admin/crud/registry.ts +45 -0
  122. package/src/admin/crud/span.ts +48 -0
  123. package/src/admin/crud/style.ts +112 -0
  124. package/src/admin/crud/useCrud.ts +81 -0
  125. package/src/admin/crud/validate.ts +109 -0
  126. package/src/admin/crud/vm-add-btn.vue +68 -0
  127. package/src/admin/crud/vm-adv-search.vue +73 -0
  128. package/src/admin/crud/vm-crud.vue +517 -0
  129. package/src/admin/crud/vm-dialog.vue +234 -0
  130. package/src/admin/crud/vm-flex1.vue +14 -0
  131. package/src/admin/crud/vm-form.vue +239 -0
  132. package/src/admin/crud/vm-multi-delete-btn.vue +48 -0
  133. package/src/admin/crud/vm-pagination.vue +125 -0
  134. package/src/admin/crud/vm-refresh-btn.vue +34 -0
  135. package/src/admin/crud/vm-row.vue +42 -0
  136. package/src/admin/crud/vm-search-key.vue +76 -0
  137. package/src/admin/crud/vm-search.vue +367 -0
  138. package/src/admin/crud/vm-table.vue +852 -0
  139. package/src/admin/crud/vm-toolbar.vue +245 -0
  140. package/src/admin/crud/vm-upsert.vue +530 -0
  141. package/src/admin/data/remixicon-all.json +1 -0
  142. package/src/admin/directives/perm.ts +18 -0
  143. package/src/admin/env.d.ts +5 -0
  144. package/src/admin/hooks/useBrowser.ts +27 -0
  145. package/src/admin/hooks/useUpload.ts +82 -0
  146. package/src/admin/hooks/useVome.ts +13 -0
  147. package/src/admin/index.ts +59 -0
  148. package/src/admin/lib/browser.ts +28 -0
  149. package/src/admin/lib/cn.ts +7 -0
  150. package/src/admin/lib/dialog-float.ts +14 -0
  151. package/src/admin/lib/eps.ts +43 -0
  152. package/src/admin/lib/menu.ts +15 -0
  153. package/src/admin/lib/module.ts +112 -0
  154. package/src/admin/lib/tree.ts +2 -0
  155. package/src/admin/lib/upload.ts +102 -0
  156. package/src/admin/router/index.ts +61 -0
  157. package/src/admin/router/menu-routes.ts +126 -0
  158. package/src/admin/service/base.ts +96 -0
  159. package/src/admin/service/index.ts +281 -0
  160. package/src/admin/static/fileicon/demo.css +539 -0
  161. package/src/admin/static/fileicon/demo_index.html +901 -0
  162. package/src/admin/static/fileicon/iconfont.css +139 -0
  163. package/src/admin/static/fileicon/iconfont.js +1 -0
  164. package/src/admin/static/fileicon/iconfont.json +226 -0
  165. package/src/admin/static/fileicon/iconfont.ttf +0 -0
  166. package/src/admin/static/fileicon/iconfont.woff +0 -0
  167. package/src/admin/static/fileicon/iconfont.woff2 +0 -0
  168. package/src/admin/static/scarce//344/270/213/350/275/275/345/244/261/350/264/245.svg +1 -0
  169. package/src/admin/static/scarce//346/224/257/344/273/230/346/210/220/345/212/237.svg +1 -0
  170. package/src/admin/static/scarce//346/232/202/346/227/240/344/274/230/346/203/240/345/210/270.svg +1 -0
  171. package/src/admin/static/scarce//346/232/202/346/227/240/345/206/205/345/256/271.svg +1 -0
  172. package/src/admin/static/scarce//346/232/202/346/227/240/345/217/221/347/245/250.svg +1 -0
  173. package/src/admin/static/scarce//346/232/202/346/227/240/345/233/276/347/211/207.svg +1 -0
  174. package/src/admin/static/scarce//346/232/202/346/227/240/345/234/260/345/235/200.svg +1 -0
  175. package/src/admin/static/scarce//346/232/202/346/227/240/345/272/227/351/223/272.svg +1 -0
  176. package/src/admin/static/scarce//346/232/202/346/227/240/346/216/250/351/200/201.svg +1 -0
  177. package/src/admin/static/scarce//346/232/202/346/227/240/346/220/234/347/264/242/347/273/223/346/236/234.svg +1 -0
  178. package/src/admin/static/scarce//346/232/202/346/227/240/346/224/266/350/227/217.svg +1 -0
  179. package/src/admin/static/scarce//346/232/202/346/227/240/346/225/260/346/215/256.svg +1 -0
  180. package/src/admin/static/scarce//346/232/202/346/227/240/346/235/203/351/231/220.svg +1 -0
  181. package/src/admin/static/scarce//346/232/202/346/227/240/346/266/210/346/201/257.svg +1 -0
  182. package/src/admin/static/scarce//346/232/202/346/227/240/347/244/274/347/211/251.svg +1 -0
  183. package/src/admin/static/scarce//346/232/202/346/227/240/347/247/257/345/210/206.svg +1 -0
  184. package/src/admin/static/scarce//346/232/202/346/227/240/347/275/221/347/273/234.svg +1 -0
  185. package/src/admin/static/scarce//346/232/202/346/227/240/350/247/206/351/242/221.svg +1 -0
  186. package/src/admin/static/scarce//346/232/202/346/227/240/350/256/242/345/215/225.svg +1 -0
  187. package/src/admin/static/scarce//346/232/202/346/227/240/350/257/204/350/256/272.svg +1 -0
  188. package/src/admin/static/scarce//346/232/202/346/227/240/351/205/215/351/200/201.svg +1 -0
  189. package/src/admin/static/scarce//346/232/202/346/227/240/351/223/266/350/241/214/345/215/241.svg +1 -0
  190. package/src/admin/static/scarce//346/232/202/346/227/240/351/237/263/344/271/220.svg +1 -0
  191. package/src/admin/static/scarce//351/241/265/351/235/242/344/270/215/345/255/230/345/234/250.svg +1 -0
  192. package/src/admin/stores/app.ts +30 -0
  193. package/src/admin/stores/tags.ts +73 -0
  194. package/src/admin/stores/user.ts +52 -0
  195. package/src/admin/styles/base.css +137 -0
  196. package/src/admin/tsconfig.json +19 -0
  197. package/typings/admin/base/auth.ts +23 -0
  198. package/typings/admin/base/permission.ts +19 -0
  199. package/typings/admin/comm/cascader.ts +7 -0
  200. package/typings/admin/comm/check-tree.ts +7 -0
  201. package/typings/admin/comm/crud.ts +294 -0
  202. package/typings/admin/comm/eps.ts +52 -0
  203. package/typings/admin/comm/request.ts +12 -0
  204. package/typings/admin/comm/service.ts +35 -0
  205. package/typings/admin/comm/upload.ts +39 -0
  206. package/typings/admin/host.d.ts +61 -0
  207. package/typings/admin/vome/browser.ts +11 -0
  208. package/typings/admin/vome/module.ts +13 -0
  209. package/typings/admin/vome/tags.ts +9 -0
@@ -0,0 +1,112 @@
1
+ import type { ViewLoader, ViewsTreeNode } from '../../../typings/admin/vome/module'
2
+
3
+ /** 视图重扫事件:保存 views 触发 HMR 后派发,选择器监听刷新 */
4
+ export const VIEWS_RESCAN_EVENT = 'vome:views-rescan'
5
+
6
+ const viewMap = new Map<string, ViewLoader>()
7
+
8
+
9
+ function rebuildViewMap(files: Record<string, ViewLoader>) {
10
+ viewMap.clear()
11
+ for (const [file, loader] of Object.entries(files)) {
12
+ if (file.includes('/components/')) continue
13
+ const m = file.match(/modules\/.+\.vue$/)
14
+ if (!m) continue
15
+ const rel = m[0].replace(/\.vue$/, '').replace(/\/index$/, '')
16
+ viewMap.set(rel, loader)
17
+ }
18
+ }
19
+
20
+ /** 宿主注册业务视图 glob(包内无法扫描宿主 modules) */
21
+ export function registerViews(
22
+ files: Record<string, () => Promise<{ default: import('vue').Component }>>,
23
+ ) {
24
+ rebuildViewMap(files)
25
+ if (typeof window !== 'undefined') {
26
+ window.dispatchEvent(new CustomEvent(VIEWS_RESCAN_EVENT))
27
+ }
28
+ }
29
+
30
+ /** 解析菜单 viewPath,如 modules/base/views/menu */
31
+ export function resolveView(viewPath: string | null | undefined): ViewLoader | undefined {
32
+ if (!viewPath) return undefined
33
+ const normalized = viewPath
34
+ .replace(/^\/+/, '')
35
+ .replace(/\.vue$/, '')
36
+ .replace(/\/index$/, '')
37
+ return (
38
+ viewMap.get(normalized) ||
39
+ viewMap.get(`modules/${normalized.replace(/^modules\//, '')}`)
40
+ )
41
+ }
42
+
43
+ /**
44
+ * viewPath → 页面组件 name
45
+ * modules/base/views/role → base-role
46
+ * modules/user/views/info → user-info
47
+ */
48
+ export function viewPathToName(viewPath: string | null | undefined): string | undefined {
49
+ if (!viewPath) return undefined
50
+ const normalized = viewPath
51
+ .replace(/^\/+/, '')
52
+ .replace(/\.vue$/, '')
53
+ .replace(/\/index$/, '')
54
+ const m = normalized.match(/^(?:modules\/)?([^/]+)\/views\/(.+)$/)
55
+ if (m) return `${m[1]}-${m[2]!.replace(/\//g, '-')}`
56
+ return undefined
57
+ }
58
+
59
+ export function listViews() {
60
+ return [...viewMap.keys()].sort()
61
+ }
62
+
63
+ /**
64
+ * 将扁平 viewPath 建成多列树(展示从 modules/ 后分段)
65
+ * modules/base/views/menu → base > views > menu
66
+ */
67
+ export function listViewsTree(): ViewsTreeNode[] {
68
+ const root: ViewsTreeNode[] = []
69
+
70
+ for (const full of listViews()) {
71
+ const parts = full.replace(/^modules\//, '').split('/').filter(Boolean)
72
+ if (!parts.length) continue
73
+
74
+ let level = root
75
+ let acc = 'modules'
76
+ for (let i = 0; i < parts.length; i++) {
77
+ const label = parts[i]!
78
+ acc += `/${label}`
79
+ const isLeaf = i === parts.length - 1
80
+ let node = level.find((n) => n.label === label)
81
+ if (!node) {
82
+ node = {
83
+ label,
84
+ value: isLeaf ? full : acc,
85
+ isLeaf,
86
+ children: isLeaf ? undefined : [],
87
+ }
88
+ level.push(node)
89
+ } else if (isLeaf) {
90
+ node.isLeaf = true
91
+ node.value = full
92
+ node.children = undefined
93
+ }
94
+ if (!isLeaf) {
95
+ if (!node.children) node.children = []
96
+ level = node.children
97
+ }
98
+ }
99
+ }
100
+
101
+ const sortTree = (nodes: ViewsTreeNode[]) => {
102
+ nodes.sort((a, b) => {
103
+ if (!!a.isLeaf !== !!b.isLeaf) return a.isLeaf ? 1 : -1
104
+ return a.label.localeCompare(b.label)
105
+ })
106
+ for (const n of nodes) {
107
+ if (n.children?.length) sortTree(n.children)
108
+ }
109
+ }
110
+ sortTree(root)
111
+ return root
112
+ }
@@ -0,0 +1,2 @@
1
+ /** 树工具:同构实现见 @vome-core */
2
+ export { deepTree, flattenTree } from '../../shared/tree'
@@ -0,0 +1,102 @@
1
+ import type { UploadFileType, UploadRule } from '../../../typings/admin/comm/upload'
2
+
3
+ /** 默认上传规则 */
4
+ export const DEFAULT_UPLOAD_RULES: UploadRule[] = [
5
+ {
6
+ name: '图片',
7
+ type: 'image',
8
+ color: '#67C23A',
9
+ exts: ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'gif', 'svg', 'webp'],
10
+ },
11
+ {
12
+ name: '视频',
13
+ type: 'video',
14
+ color: '#826aec',
15
+ exts: ['avi', 'wmv', 'mpg', 'mpeg', 'mov', 'rm', 'ram', 'swf', 'flv', 'mp4', '3gp'],
16
+ },
17
+ {
18
+ name: '音频',
19
+ type: 'audio',
20
+ color: '#826aec',
21
+ exts: ['mp3', 'wav', 'wma', 'mp2', 'flac', 'midi', 'ra', 'ape', 'aac', 'cda'],
22
+ },
23
+ {
24
+ name: '文档',
25
+ type: 'word',
26
+ color: '#53B7F4',
27
+ exts: ['doc', 'docx', 'docm', 'dot', 'dotx', 'dotm'],
28
+ },
29
+ {
30
+ name: '表格',
31
+ type: 'excel',
32
+ color: '#53D39C',
33
+ exts: ['xls', 'xlsx', 'xlsm', 'xlt', 'xltx', 'xltm'],
34
+ },
35
+ {
36
+ name: '演示',
37
+ type: 'ppt',
38
+ color: '#F56C6C',
39
+ exts: ['ppt', 'pptx', 'pptm', 'ppsx', 'ppsm', 'pps', 'potx', 'potm', 'dpt', 'dps'],
40
+ },
41
+ {
42
+ name: 'PDF',
43
+ type: 'pdf',
44
+ color: '#8f3500',
45
+ exts: ['pdf'],
46
+ },
47
+ {
48
+ name: '压缩包',
49
+ type: 'rar',
50
+ color: '#FFC757',
51
+ exts: ['rar', 'zip', '7z', 'tar', 'gz'],
52
+ },
53
+ {
54
+ name: '文件',
55
+ type: 'file',
56
+ color: '#909399',
57
+ exts: [],
58
+ },
59
+ ]
60
+
61
+ export function extname(path = '') {
62
+ const m = String(path).split('?')[0]?.match(/\.([^.]+)$/)
63
+ return m ? m[1].toLowerCase() : ''
64
+ }
65
+
66
+ export function filename(path = '') {
67
+ const base = String(path).split('?')[0]?.split('/').pop() || ''
68
+ const ext = extname(base)
69
+ return ext ? base.slice(0, -(ext.length + 1)) : base
70
+ }
71
+
72
+ export function fileRule(path?: string, rules = DEFAULT_UPLOAD_RULES) {
73
+ if (path?.includes('data:image/')) {
74
+ return rules.find((r) => r.type === 'image')!
75
+ }
76
+ const ext = extname(path || '')
77
+ const hit = rules.find((r) => r.exts.includes(ext))
78
+ return hit || rules.find((r) => r.type === 'file')!
79
+ }
80
+
81
+ export function getFileType(path: string, rules = DEFAULT_UPLOAD_RULES): UploadFileType {
82
+ return fileRule(path, rules).type
83
+ }
84
+
85
+ export function getUploadRule(type: string, rules = DEFAULT_UPLOAD_RULES) {
86
+ return rules.find((r) => r.type === type) || rules[rules.length - 1]!
87
+ }
88
+
89
+ export function getUrls(list: Array<{ url?: string }>) {
90
+ return list.map((e) => String(e.url || '').replace(/,/g, encodeURIComponent(',')))
91
+ }
92
+
93
+ export function pathJoin(...parts: string[]) {
94
+ return parts
95
+ .map((p) => p.replace(/(^\/+|\/+$)/g, ''))
96
+ .filter(Boolean)
97
+ .join('/')
98
+ }
99
+
100
+ export function uploadUid() {
101
+ return `${Date.now()}_${Math.random().toString(36).slice(2, 9)}`
102
+ }
@@ -0,0 +1,61 @@
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+ import { api, clearTokens, getAccessToken, getRefreshToken } from '../api/client'
3
+ import { clearMenuRoutesFlag, ensureMenuRoutes } from './menu-routes'
4
+ import Home from '#vome-host/pages/home/index.vue'
5
+ import Layout from '#vome-host/pages/layout/index.vue'
6
+ import Login from '#vome-host/pages/login/index.vue'
7
+
8
+ export const router = createRouter({
9
+ history: createWebHistory(),
10
+ routes: [
11
+ {
12
+ path: '/login',
13
+ name: 'login',
14
+ component: Login,
15
+ meta: { public: true },
16
+ },
17
+ {
18
+ path: '/',
19
+ name: 'layout',
20
+ component: Layout,
21
+ children: [
22
+ {
23
+ path: '',
24
+ name: 'home',
25
+ component: Home,
26
+ meta: { title: '首页' },
27
+ },
28
+ ],
29
+ },
30
+ ],
31
+ })
32
+
33
+ let menusReady = false
34
+
35
+ router.beforeEach(async (to) => {
36
+ if (to.meta.public) return true
37
+ // 无 access 时若仍有 refresh,交给 perms 请求内自动续期
38
+ if (!getAccessToken() && !getRefreshToken()) {
39
+ return { name: 'login', query: { redirect: to.fullPath } }
40
+ }
41
+ if (!menusReady) {
42
+ try {
43
+ const authz = await api.perms({ toast: false })
44
+ await ensureMenuRoutes(router, authz.menus)
45
+ menusReady = true
46
+ if (to.matched.length <= 1 && to.path !== '/') {
47
+ return { ...to, replace: true }
48
+ }
49
+ } catch {
50
+ menusReady = false
51
+ clearTokens()
52
+ return { name: 'login', query: { redirect: to.fullPath } }
53
+ }
54
+ }
55
+ return true
56
+ })
57
+
58
+ export function resetMenuRoutesFlag() {
59
+ menusReady = false
60
+ clearMenuRoutesFlag()
61
+ }
@@ -0,0 +1,126 @@
1
+ import type { Router, RouteRecordRaw } from 'vue-router'
2
+ import { defineComponent, h } from 'vue'
3
+ import { resolveView, viewPathToName } from '../lib/module'
4
+ import MissingView from '#vome-host/pages/missing/index.vue'
5
+ import MicroAppView from '#vome-host/pages/micro/index.vue'
6
+
7
+ /** 已挂载的动态菜单路由名 menu-${id} */
8
+ const addedNames = new Set<string>()
9
+
10
+ function isMicroMenu(m: MenuTreeNode) {
11
+ return Boolean(m.appKey && m.router)
12
+ }
13
+
14
+ function walk(nodes: MenuTreeNode[], out: MenuTreeNode[]) {
15
+ for (const n of nodes) {
16
+ if (n.type === 1 && n.router) out.push(n)
17
+ if (n.children?.length) walk(n.children, out)
18
+ }
19
+ }
20
+
21
+ function missingView(viewPath: string) {
22
+ return defineComponent({
23
+ name: 'MissingViewPage',
24
+ setup() {
25
+ return () => h(MissingView, { path: viewPath })
26
+ },
27
+ })
28
+ }
29
+
30
+ function routeNameOf(id: number | string) {
31
+ return `menu-${id}`
32
+ }
33
+
34
+ function buildRoute(item: MenuTreeNode): RouteRecordRaw {
35
+ const full = item.router!
36
+ const childPath = full.replace(/^\//, '')
37
+ let component: RouteRecordRaw['component']
38
+ const componentName = viewPathToName(item.viewPath)
39
+ const meta: Record<string, unknown> = {
40
+ title: item.name,
41
+ keepAlive: item.keepAlive !== false,
42
+ viewPath: item.viewPath,
43
+ perms: item.perms,
44
+ componentName,
45
+ }
46
+
47
+ if (isMicroMenu(item)) {
48
+ component = MicroAppView
49
+ meta.appKey = item.appKey
50
+ meta.componentName = `micro-${item.appKey}`
51
+ meta.keepAlive = false
52
+ } else if (item.viewPath) {
53
+ const loader = resolveView(item.viewPath)
54
+ component = loader || missingView(item.viewPath)
55
+ } else {
56
+ component = missingView(full)
57
+ }
58
+
59
+ return {
60
+ path: childPath,
61
+ name: routeNameOf(item.id),
62
+ component,
63
+ meta,
64
+ }
65
+ }
66
+
67
+ /** 根据权限菜单动态挂载:viewPath 本地模块 / appKey 微应用(仅补新增) */
68
+ export async function ensureMenuRoutes(router: Router, menus: MenuTreeNode[]) {
69
+ const items: MenuTreeNode[] = []
70
+ walk(menus, items)
71
+
72
+ for (const item of items) {
73
+ const name = routeNameOf(item.id)
74
+ if (addedNames.has(name)) continue
75
+ addedNames.add(name)
76
+ router.addRoute('layout', buildRoute(item))
77
+ }
78
+ }
79
+
80
+ /** 全量同步:删旧补新,菜单改名/改路由/显隐后侧栏与路由一致 */
81
+ export async function syncMenuRoutes(router: Router, menus: MenuTreeNode[]) {
82
+ const items: MenuTreeNode[] = []
83
+ walk(menus, items)
84
+ const nextNames = new Set(items.map((i) => routeNameOf(i.id)))
85
+
86
+ for (const name of [...addedNames]) {
87
+ if (!nextNames.has(name)) {
88
+ if (router.hasRoute(name)) router.removeRoute(name)
89
+ addedNames.delete(name)
90
+ }
91
+ }
92
+
93
+ for (const item of items) {
94
+ const name = routeNameOf(item.id)
95
+ const childPath = item.router!.replace(/^\//, '')
96
+ if (addedNames.has(name) && router.hasRoute(name)) {
97
+ const existing = router.getRoutes().find((r) => r.name === name)
98
+ if (existing && existing.path.replace(/^\//, '') !== childPath) {
99
+ router.removeRoute(name)
100
+ addedNames.delete(name)
101
+ } else if (existing?.meta) {
102
+ existing.meta.title = item.name
103
+ existing.meta.keepAlive = item.keepAlive !== false
104
+ existing.meta.viewPath = item.viewPath
105
+ existing.meta.perms = item.perms
106
+ if (item.appKey) existing.meta.appKey = item.appKey
107
+ continue
108
+ }
109
+ }
110
+ if (!addedNames.has(name)) {
111
+ addedNames.add(name)
112
+ router.addRoute('layout', buildRoute(item))
113
+ }
114
+ }
115
+ }
116
+
117
+ export function clearMenuRoutesFlag() {
118
+ addedNames.clear()
119
+ }
120
+
121
+ export function removeAllMenuRoutes(router: Router) {
122
+ for (const name of [...addedNames]) {
123
+ if (router.hasRoute(name)) router.removeRoute(name)
124
+ }
125
+ addedNames.clear()
126
+ }
@@ -0,0 +1,96 @@
1
+ import { request } from '../api/client'
2
+ /** 系统时间由后端维护;所有方法请求体都不传 */
3
+ function stripTimeMeta(data: unknown): unknown {
4
+ if (Array.isArray(data)) return data.map((item) => stripTimeMeta(item))
5
+ if (data && typeof data === 'object') {
6
+ const out = { ...(data as Record<string, unknown>) }
7
+ delete out.createTime
8
+ delete out.updateTime
9
+ return out
10
+ }
11
+ return data
12
+ }
13
+
14
+ /** BaseService:namespace + request + 标准 CRUD */
15
+ export class BaseService {
16
+ namespace?: string
17
+
18
+ constructor(namespace?: string) {
19
+ if (namespace) this.namespace = namespace.replace(/^\//, '')
20
+ }
21
+
22
+ async request(options: ServiceRequestOptions = {}) {
23
+ let url = options.url || ''
24
+
25
+ if (url && !/^https?:\/\//.test(url)) {
26
+ if (this.namespace) {
27
+ const ns = this.namespace.startsWith('/')
28
+ ? this.namespace
29
+ : `/${this.namespace}`
30
+ url = url.startsWith('/') ? `${ns}${url}` : `${ns}/${url}`
31
+ }
32
+ if (!url.startsWith('/')) url = `/${url}`
33
+ }
34
+
35
+ const method = (options.method || 'GET').toUpperCase()
36
+ if (method === 'GET') {
37
+ const params = options.params || {}
38
+ const q = new URLSearchParams()
39
+ for (const [k, v] of Object.entries(params)) {
40
+ if (v == null) continue
41
+ q.set(k, String(v))
42
+ }
43
+ const qs = q.toString()
44
+ return request(qs ? `${url}?${qs}` : url)
45
+ }
46
+
47
+ return request(url, {
48
+ method,
49
+ body: JSON.stringify(stripTimeMeta(options.data ?? {})),
50
+ })
51
+ }
52
+
53
+ page(data: Record<string, unknown> = {}) {
54
+ return this.request({ url: '/page', method: 'POST', data }) as Promise<{
55
+ list: Record<string, unknown>[]
56
+ pagination: { page: number; size: number; total: number }
57
+ }>
58
+ }
59
+
60
+ list(data: Record<string, unknown> = {}) {
61
+ return this.request({
62
+ url: '/list',
63
+ method: 'POST',
64
+ data,
65
+ }) as Promise<Record<string, unknown>[]>
66
+ }
67
+
68
+ info(params: { id: number | string }) {
69
+ return this.request({
70
+ url: '/info',
71
+ method: 'GET',
72
+ params: params as Record<string, unknown>,
73
+ }) as Promise<Record<string, unknown>>
74
+ }
75
+
76
+ add(data: Record<string, unknown> | Record<string, unknown>[]) {
77
+ return this.request({ url: '/add', method: 'POST', data })
78
+ }
79
+
80
+ update(data: Record<string, unknown> | Record<string, unknown>[]) {
81
+ return this.request({ url: '/update', method: 'POST', data })
82
+ }
83
+
84
+ delete(
85
+ data:
86
+ | { ids: Array<number | string>; force?: boolean }
87
+ | Array<number | string>
88
+ | Array<Record<string, unknown>>,
89
+ ) {
90
+ return this.request({ url: '/delete', method: 'POST', data })
91
+ }
92
+
93
+ restore(data: { ids: Array<number | string> }) {
94
+ return this.request({ url: '/restore', method: 'POST', data })
95
+ }
96
+ }