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.
- package/dist/index.js +236 -1
- package/dist/server/index.js +3261 -1
- package/package.json +10 -2
- package/src/admin/components/vm-auto-perm-dialog.vue +3 -0
- package/src/admin/components/vm-check-tree.vue +1 -0
- package/src/admin/components/vm-dept-tree.vue +3 -0
- package/src/admin/components/vm-empty.vue +1 -0
- package/src/admin/components/vm-eps-perm-picker.vue +2 -0
- package/src/admin/components/vm-group-cascader.vue +1 -0
- package/src/admin/components/vm-icon-picker.vue +1 -0
- package/src/admin/components/vm-markdown.vue +1 -0
- package/src/admin/components/vm-ri-icon.vue +2 -0
- package/src/admin/components/vm-role-picker.vue +2 -0
- package/src/admin/components/vm-view-path-picker.vue +1 -0
- package/src/admin/crud/components/vm-column-custom.vue +1 -0
- package/src/admin/crud/components/vm-context-menu.vue +1 -0
- package/src/admin/crud/components/vm-date-picker.vue +1 -0
- package/src/admin/crud/components/vm-date-range.vue +1 -0
- package/src/admin/crud/components/vm-date-text.vue +1 -0
- package/src/admin/crud/components/vm-dict-tag.vue +1 -0
- package/src/admin/crud/components/vm-file-icon.vue +1 -0
- package/src/admin/crud/components/vm-multi-select.vue +1 -0
- package/src/admin/crud/components/vm-select.vue +2 -0
- package/src/admin/crud/components/vm-switch.vue +1 -0
- package/src/admin/crud/components/vm-tree-select.vue +1 -0
- package/src/admin/crud/components/vm-upload-item.vue +1 -0
- package/src/admin/crud/components/vm-upload.vue +2 -0
- package/src/admin/crud/components/vm-user-select.vue +1 -0
- package/src/admin/crud/vm-add-btn.vue +1 -0
- package/src/admin/crud/vm-adv-search.vue +1 -0
- package/src/admin/crud/vm-crud.vue +2 -0
- package/src/admin/crud/vm-dialog.vue +1 -0
- package/src/admin/crud/vm-form.vue +1 -0
- package/src/admin/crud/vm-pagination.vue +1 -0
- package/src/admin/crud/vm-search-key.vue +1 -0
- package/src/admin/crud/vm-search.vue +1 -0
- package/src/admin/crud/vm-table.vue +1 -0
- package/src/admin/crud/vm-toolbar.vue +1 -0
- package/src/admin/crud/vm-upsert.vue +2 -0
- package/src/admin/lib/eps.ts +3 -2
- 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
|
-
/**
|
|
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
|
|
15
|
-
'/admin/
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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] = (
|
|
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 (
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
167
|
-
for (let i = 0; i <
|
|
168
|
-
const key =
|
|
272
|
+
let missing = false
|
|
273
|
+
for (let i = 0; i < parts.length; i++) {
|
|
274
|
+
const key = parts[i]!
|
|
169
275
|
if (!node[key]) {
|
|
170
|
-
|
|
276
|
+
missing = true
|
|
171
277
|
break
|
|
172
278
|
}
|
|
173
|
-
|
|
279
|
+
if (i < parts.length - 1) {
|
|
280
|
+
node = node[key] as Record<string, unknown>
|
|
281
|
+
}
|
|
174
282
|
}
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
|
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 */
|