vome-core 0.0.6 → 0.0.9

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 (41) hide show
  1. package/dist/index.js +236 -1
  2. package/dist/server/index.js +3261 -1
  3. package/package.json +10 -2
  4. package/src/admin/components/vm-auto-perm-dialog.vue +3 -0
  5. package/src/admin/components/vm-check-tree.vue +1 -0
  6. package/src/admin/components/vm-dept-tree.vue +3 -0
  7. package/src/admin/components/vm-empty.vue +1 -0
  8. package/src/admin/components/vm-eps-perm-picker.vue +2 -0
  9. package/src/admin/components/vm-group-cascader.vue +1 -0
  10. package/src/admin/components/vm-icon-picker.vue +1 -0
  11. package/src/admin/components/vm-markdown.vue +1 -0
  12. package/src/admin/components/vm-ri-icon.vue +2 -0
  13. package/src/admin/components/vm-role-picker.vue +2 -0
  14. package/src/admin/components/vm-view-path-picker.vue +1 -0
  15. package/src/admin/crud/components/vm-column-custom.vue +1 -0
  16. package/src/admin/crud/components/vm-context-menu.vue +1 -0
  17. package/src/admin/crud/components/vm-date-picker.vue +1 -0
  18. package/src/admin/crud/components/vm-date-range.vue +1 -0
  19. package/src/admin/crud/components/vm-date-text.vue +1 -0
  20. package/src/admin/crud/components/vm-dict-tag.vue +1 -0
  21. package/src/admin/crud/components/vm-file-icon.vue +1 -0
  22. package/src/admin/crud/components/vm-multi-select.vue +1 -0
  23. package/src/admin/crud/components/vm-select.vue +2 -0
  24. package/src/admin/crud/components/vm-switch.vue +1 -0
  25. package/src/admin/crud/components/vm-tree-select.vue +1 -0
  26. package/src/admin/crud/components/vm-upload-item.vue +1 -0
  27. package/src/admin/crud/components/vm-upload.vue +2 -0
  28. package/src/admin/crud/components/vm-user-select.vue +1 -0
  29. package/src/admin/crud/vm-add-btn.vue +1 -0
  30. package/src/admin/crud/vm-adv-search.vue +1 -0
  31. package/src/admin/crud/vm-crud.vue +2 -0
  32. package/src/admin/crud/vm-dialog.vue +1 -0
  33. package/src/admin/crud/vm-form.vue +1 -0
  34. package/src/admin/crud/vm-pagination.vue +1 -0
  35. package/src/admin/crud/vm-search-key.vue +1 -0
  36. package/src/admin/crud/vm-search.vue +1 -0
  37. package/src/admin/crud/vm-table.vue +1 -0
  38. package/src/admin/crud/vm-toolbar.vue +1 -0
  39. package/src/admin/crud/vm-upsert.vue +2 -0
  40. package/src/admin/lib/eps.ts +3 -2
  41. package/src/admin/service/index.ts +199 -67
@@ -1,8 +1,63 @@
1
1
  import { loadEps } from '../lib/eps'
2
2
  import { BaseService } from './base'
3
+
3
4
  const SIDE_ID = 'admin'
4
5
 
5
- /** EPS 不可用时仍保证业务页链式可调 */
6
+ /**
7
+ * 每次发版递增。globalThis 单例若跨 HMR/旧包残留,靠此戳强制换新根对象并重建。
8
+ * 根因:旧 singleton 只有 CRUD、无 tree/roleMap,且 plugin-info 错名导致 base.plugin 缺失。
9
+ */
10
+ const SERVICE_BUILD = 9
11
+
12
+ /** 标准 CRUD 之外的常用接口(不依赖 EPS 是否已返回) */
13
+ const FALLBACK_EXTRA_APIS: Record<
14
+ string,
15
+ Array<{ method: string; path: string; perms?: string[] }>
16
+ > = {
17
+ '/admin/base/department': [{ method: 'get', path: '/tree' }],
18
+ '/admin/base/menu': [{ method: 'get', path: '/tree' }],
19
+ '/admin/base/role': [
20
+ { method: 'get', path: '/menus' },
21
+ { method: 'post', path: '/setMenus' },
22
+ { method: 'get', path: '/departments' },
23
+ { method: 'post', path: '/setDepartments' },
24
+ ],
25
+ '/admin/base/user': [
26
+ { method: 'get', path: '/roles' },
27
+ { method: 'get', path: '/roleMap' },
28
+ { method: 'post', path: '/setRoles' },
29
+ { method: 'post', path: '/transferSuper' },
30
+ { method: 'post', path: '/moveDepartment' },
31
+ ],
32
+ '/admin/base/task': [
33
+ { method: 'post', path: '/start' },
34
+ { method: 'post', path: '/stop' },
35
+ { method: 'post', path: '/once' },
36
+ { method: 'get', path: '/log' },
37
+ ],
38
+ '/admin/base/log': [
39
+ { method: 'post', path: '/clear' },
40
+ { method: 'post', path: '/setKeep' },
41
+ { method: 'post', path: '/getKeep' },
42
+ ],
43
+ '/admin/base/dict/info': [
44
+ { method: 'get', path: '/types' },
45
+ { method: 'post', path: '/data' },
46
+ { method: 'post', path: '/get' },
47
+ { method: 'post', path: '/find' },
48
+ { method: 'post', path: '/getValues' },
49
+ { method: 'post', path: '/pathValue' },
50
+ { method: 'post', path: '/childValue' },
51
+ ],
52
+ '/admin/base/module': [{ method: 'post', path: '/install' }],
53
+ '/admin/base/comm': [{ method: 'post', path: '/upload' }],
54
+ '/admin/user/info': [
55
+ { method: 'get', path: '/roles' },
56
+ { method: 'get', path: '/roleMap' },
57
+ { method: 'post', path: '/setRoles' },
58
+ ],
59
+ }
60
+
6
61
  const FALLBACK_PREFIXES = [
7
62
  '/admin/base/menu',
8
63
  '/admin/base/user',
@@ -11,8 +66,13 @@ const FALLBACK_PREFIXES = [
11
66
  '/admin/base/tenant',
12
67
  '/admin/base/task',
13
68
  '/admin/base/log',
14
- '/admin/base/plugin-info',
15
- '/admin/user/user-role',
69
+ '/admin/base/plugin',
70
+ '/admin/base/dict/info',
71
+ '/admin/base/dict/type',
72
+ '/admin/base/module',
73
+ '/admin/base/comm',
74
+ '/admin/user/info',
75
+ '/admin/user/role',
16
76
  ]
17
77
 
18
78
  function toCamel(str: string) {
@@ -27,10 +87,8 @@ function isValidName(name: string) {
27
87
  return Boolean(name) && !['{', '}', ':'].some((c) => name.includes(c))
28
88
  }
29
89
 
30
- /** 无 EPS perms 时生成权限码 */
31
90
  function resolvePermCode(prefix: string, action: string) {
32
91
  const parts = prefix.replace(/^\//, '').split('/').filter(Boolean)
33
- // admin / module / resource...
34
92
  const module = parts[1] || parts[0] || 'base'
35
93
  let resource = parts.slice(2).join(':')
36
94
  if (resource === module) {
@@ -45,20 +103,16 @@ function resolvePermCode(prefix: string, action: string) {
45
103
  return `${module}:${resource}:${action}`
46
104
  }
47
105
 
48
- function getMethodKeys(leaf: Record<string, unknown>) {
49
- return Object.keys(leaf).filter(
50
- (k) => !['namespace', 'permission', '_permission', 'request', 'search'].includes(k),
51
- )
106
+ function prefixToParts(prefix: string) {
107
+ const raw = prefix.replace(/^\//, '')
108
+ return raw
109
+ .replace(new RegExp(`^${SIDE_ID}/?`), '')
110
+ .split('/')
111
+ .filter(Boolean)
112
+ .map(toCamel)
52
113
  }
53
114
 
54
- function createLeaf(namespace: string, apis: EpsApiItem[] = []): ServiceLeaf {
55
- const base = new BaseService(namespace)
56
- const leaf = base as unknown as ServiceLeaf
57
- leaf.namespace = namespace.replace(/^\//, '')
58
- leaf.permission = {}
59
- leaf._permission = {}
60
-
61
- // 挂到实例自身,避免业务侧 `...leaf` 丢失原型方法
115
+ function bindCrud(leaf: ServiceLeaf, base: BaseService) {
62
116
  const proto = BaseService.prototype as unknown as Record<string, unknown>
63
117
  for (const name of [
64
118
  'request',
@@ -72,15 +126,23 @@ function createLeaf(namespace: string, apis: EpsApiItem[] = []): ServiceLeaf {
72
126
  ]) {
73
127
  const fn = proto[name]
74
128
  if (typeof fn === 'function') {
75
- ;(leaf as Record<string, unknown>)[name] = (fn as (...a: unknown[]) => unknown).bind(base)
129
+ ;(leaf as Record<string, unknown>)[name] = (
130
+ fn as (...a: unknown[]) => unknown
131
+ ).bind(base)
76
132
  }
77
133
  }
134
+ }
78
135
 
136
+ function bindApis(leaf: ServiceLeaf, base: BaseService, apis: EpsApiItem[]) {
137
+ leaf.permission ||= {}
79
138
  for (const api of apis) {
80
139
  const name = methodName(api.path)
81
140
  if (!isValidName(name) || /[-:]/g.test(name)) continue
82
141
  const method = (api.method || 'get').toLowerCase()
83
- leaf[name] = function (this: BaseService, data?: unknown) {
142
+ ;(leaf as Record<string, unknown>)[name] = function (
143
+ this: BaseService,
144
+ data?: unknown,
145
+ ) {
84
146
  return this.request({
85
147
  url: api.path.startsWith('/') ? api.path : `/${api.path}`,
86
148
  method,
@@ -90,25 +152,57 @@ function createLeaf(namespace: string, apis: EpsApiItem[] = []): ServiceLeaf {
90
152
  leaf.permission[name] =
91
153
  api.perms?.[0] || resolvePermCode(`/${leaf.namespace}`, name)
92
154
  }
155
+ }
93
156
 
94
- // 标准 CRUD 权限兜底(EPS 未列时仍有码)
95
- for (const name of getMethodKeys(leaf as unknown as Record<string, unknown>)) {
96
- if (!leaf.permission[name]) {
97
- leaf.permission[name] = resolvePermCode(`/${leaf.namespace}`, name)
157
+ /** 原地重挂方法,保持叶子对象引用不变(避免页面 const log = service.base.log 失效) */
158
+ function remountLeaf(leaf: ServiceLeaf, namespace: string, apis: EpsApiItem[]) {
159
+ const ns = namespace.replace(/^\//, '')
160
+ const base = new BaseService(ns)
161
+ leaf.namespace = ns
162
+ leaf.permission = {}
163
+ leaf._permission = leaf._permission || {}
164
+
165
+ // 清掉旧方法再绑,避免残留错误实现
166
+ for (const key of Object.keys(leaf as object)) {
167
+ if (
168
+ key === 'namespace' ||
169
+ key === 'permission' ||
170
+ key === '_permission' ||
171
+ key === 'search'
172
+ ) {
173
+ continue
98
174
  }
175
+ delete (leaf as Record<string, unknown>)[key]
99
176
  }
100
177
 
178
+ bindCrud(leaf, base)
179
+ bindApis(leaf, base, apis)
180
+
181
+ for (const name of Object.keys(leaf.permission)) {
182
+ if (leaf._permission[name] == null) leaf._permission[name] = false
183
+ }
101
184
  return leaf
102
185
  }
103
186
 
104
- function attachLeaf(tree: ServiceTree | Eps.Service, prefix: string, apis: EpsApiItem[] = []) {
105
- const raw = prefix.replace(/^\//, '')
106
- const arr = raw
107
- .replace(new RegExp(`^${SIDE_ID}/?`), '')
108
- .split('/')
109
- .filter(Boolean)
110
- .map(toCamel)
187
+ function createLeaf(namespace: string, apis: EpsApiItem[] = []): ServiceLeaf {
188
+ const ns = namespace.replace(/^\//, '')
189
+ const base = new BaseService(ns)
190
+ const leaf = base as unknown as ServiceLeaf
191
+ leaf.namespace = ns
192
+ leaf.permission = {}
193
+ leaf._permission = {}
194
+ bindCrud(leaf, base)
195
+ bindApis(leaf, base, apis)
196
+ return leaf
197
+ }
111
198
 
199
+ function attachLeaf(
200
+ tree: ServiceTree | Eps.Service,
201
+ prefix: string,
202
+ apis: EpsApiItem[] = [],
203
+ ) {
204
+ const raw = prefix.replace(/^\//, '')
205
+ const arr = prefixToParts(prefix)
112
206
  if (!arr.length) return undefined
113
207
 
114
208
  let node: Record<string, unknown> = tree as unknown as Record<string, unknown>
@@ -116,16 +210,18 @@ function attachLeaf(tree: ServiceTree | Eps.Service, prefix: string, apis: EpsAp
116
210
  const key = arr[i]!
117
211
  const isLast = i === arr.length - 1
118
212
  if (!isLast) {
119
- if (!node[key] || typeof node[key] !== 'object') node[key] = {}
213
+ const cur = node[key] as ServiceLeaf | Record<string, unknown> | undefined
214
+ // 中间节点若被误建成叶子,换成容器(否则 dict/info 挂不上)
215
+ if (!cur || typeof cur !== 'object' || 'namespace' in cur) {
216
+ node[key] = {}
217
+ }
120
218
  node = node[key] as Record<string, unknown>
121
219
  continue
122
220
  }
221
+
123
222
  const existing = node[key] as ServiceLeaf | undefined
124
- if (existing?.namespace) {
125
- // 已有叶子:补方法
126
- const next = createLeaf(raw, apis)
127
- Object.assign(existing, next)
128
- return existing
223
+ if (existing && typeof existing === 'object' && existing.namespace) {
224
+ return remountLeaf(existing, raw, apis)
129
225
  }
130
226
  const leaf = createLeaf(raw, apis)
131
227
  node[key] = leaf
@@ -154,41 +250,87 @@ function buildFromEps(map: EpsModuleMap) {
154
250
  }
155
251
  }
156
252
 
253
+ function extraApisOf(prefix: string): EpsApiItem[] {
254
+ return (FALLBACK_EXTRA_APIS[prefix] || []).map(
255
+ (a) =>
256
+ ({
257
+ method: a.method,
258
+ path: a.path,
259
+ perms: a.perms,
260
+ }) as EpsApiItem,
261
+ )
262
+ }
263
+
264
+ /**
265
+ * 用 fallback 补全:已有叶子则合并补方法(不整棵删,避免打断引用)。
266
+ * 对 fallback 前缀:若 EPS 已挂过,只补缺失的 extra;attachLeaf/remount 会重绑整叶。
267
+ */
157
268
  function buildFallback() {
158
269
  for (const prefix of FALLBACK_PREFIXES) {
159
- const path = prefix.replace(/^\//, '')
160
- const arr = path
161
- .replace(new RegExp(`^${SIDE_ID}/?`), '')
162
- .split('/')
163
- .filter(Boolean)
164
- .map(toCamel)
270
+ const parts = prefixToParts(prefix)
165
271
  let node: Record<string, unknown> = service as unknown as Record<string, unknown>
166
- let exists = true
167
- for (let i = 0; i < arr.length; i++) {
168
- const key = arr[i]!
272
+ let missing = false
273
+ for (let i = 0; i < parts.length; i++) {
274
+ const key = parts[i]!
169
275
  if (!node[key]) {
170
- exists = false
276
+ missing = true
171
277
  break
172
278
  }
173
- node = node[key] as Record<string, unknown>
279
+ if (i < parts.length - 1) {
280
+ node = node[key] as Record<string, unknown>
281
+ }
174
282
  }
175
- if (!exists || !(node as ServiceLeaf).namespace) {
176
- attachLeaf(service, prefix)
283
+ const leaf = missing
284
+ ? undefined
285
+ : (node[parts[parts.length - 1]!] as ServiceLeaf | undefined)
286
+
287
+ if (leaf?.namespace) {
288
+ // 只补 extra 里还没有的方法,保留 EPS 已绑方法
289
+ const extras = extraApisOf(prefix).filter((api) => {
290
+ const name = methodName(api.path)
291
+ return typeof (leaf as Record<string, unknown>)[name] !== 'function'
292
+ })
293
+ if (extras.length) {
294
+ const base = new BaseService(leaf.namespace)
295
+ bindApis(leaf, base, extras)
296
+ }
297
+ continue
177
298
  }
299
+ attachLeaf(service, prefix, extraApisOf(prefix))
178
300
  }
179
301
  }
180
302
 
181
- const root = new BaseService()
303
+ type ServiceRoot = Eps.Service & { request: BaseService['request'] }
304
+
305
+ const g = globalThis as typeof globalThis & {
306
+ __vome_admin_service__?: ServiceRoot
307
+ __vome_admin_service_build__?: number
308
+ }
309
+
310
+ function createServiceRoot(): ServiceRoot {
311
+ const root = new BaseService()
312
+ return {
313
+ request: root.request.bind(root),
314
+ } as ServiceRoot
315
+ }
316
+
317
+ function getOrCreateRoot(): ServiceRoot {
318
+ if (
319
+ !g.__vome_admin_service__ ||
320
+ g.__vome_admin_service_build__ !== SERVICE_BUILD
321
+ ) {
322
+ g.__vome_admin_service__ = createServiceRoot()
323
+ g.__vome_admin_service_build__ = SERVICE_BUILD
324
+ }
325
+ return g.__vome_admin_service__
326
+ }
182
327
 
183
328
  /** 全局链式 service(service.base.user.page()) */
184
- export const service = {
185
- request: root.request.bind(root),
186
- } as unknown as Eps.Service
329
+ export const service: ServiceRoot = getOrCreateRoot()
187
330
 
188
- // 同步兜底,保证页面 import 时即可 service.base.xxx
189
331
  buildFallback()
190
332
 
191
- /** 从 EPS 构建/刷新 service 树;失败时用兜底 prefix */
333
+ /** 从 EPS 构建/刷新;再 fallback 补自定义方法 */
192
334
  export async function createEps(force = false) {
193
335
  try {
194
336
  const map = (await loadEps(force)) || {}
@@ -200,7 +342,6 @@ export async function createEps(force = false) {
200
342
  return service
201
343
  }
202
344
 
203
- // Vite 保存源码 / 刷新时:插件重拉 EPS 后通知客户端热更新 service 树
204
345
  if (import.meta.hot) {
205
346
  import.meta.hot.on('eps-update', () => {
206
347
  void createEps(true).then(() => {
@@ -209,7 +350,6 @@ if (import.meta.hot) {
209
350
  })
210
351
  }
211
352
 
212
- /** 登录后写入 _permission,供 CRUD / 按钮显隐 */
213
353
  export function setServicePerms(perms: string[], isSuper = false) {
214
354
  const list = perms || []
215
355
 
@@ -243,7 +383,6 @@ export function setServicePerms(perms: string[], isSuper = false) {
243
383
  deep(service)
244
384
  }
245
385
 
246
- /** 解析 service.base.user 或 'base.user' */
247
386
  export function resolveService(path: string): ServiceLeaf | undefined {
248
387
  const parts = path.replace(/^\//, '').split('.').filter(Boolean)
249
388
  let cur: unknown = service
@@ -257,19 +396,12 @@ export function resolveService(path: string): ServiceLeaf | undefined {
257
396
  return undefined
258
397
  }
259
398
 
260
- /** 按 prefix 取或创建 service 叶子 */
261
399
  export function serviceOf(prefix: string): ServiceLeaf {
262
400
  const raw = prefix.replace(/\/$/, '')
263
- const path = raw
264
- .replace(/^\//, '')
265
- .replace(new RegExp(`^${SIDE_ID}/?`), '')
266
- .split('/')
267
- .filter(Boolean)
268
- .map(toCamel)
269
- .join('.')
401
+ const path = prefixToParts(raw).join('.')
270
402
  const hit = resolveService(path)
271
403
  if (hit) return hit
272
- return attachLeaf(service, raw) as ServiceLeaf
404
+ return attachLeaf(service, raw, extraApisOf(raw.startsWith('/') ? raw : `/${raw}`)) as ServiceLeaf
273
405
  }
274
406
 
275
407
  /** @deprecated 请用 service.base.xxx */