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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/src/auth/define-macro.d.ts +8 -0
- package/dist/src/auth/index.d.ts +2 -0
- package/dist/src/comm/daily-log.d.ts +8 -0
- package/dist/src/comm/env.d.ts +4 -0
- package/dist/src/comm/format-time.d.ts +10 -0
- package/dist/src/comm/merge.d.ts +2 -0
- package/dist/src/comm/scan.d.ts +4 -0
- package/dist/src/comm/startBanner.d.ts +1 -0
- package/dist/src/core/app.d.ts +37 -0
- package/dist/src/core/config.d.ts +2 -0
- package/dist/src/core/context/index.d.ts +22 -0
- package/dist/src/core/host-infra.d.ts +20 -0
- package/dist/src/core/index.d.ts +20 -0
- package/dist/src/core/plugins/async-context.d.ts +3 -0
- package/dist/src/core/plugins/error.d.ts +5 -0
- package/dist/src/core/plugins/logging.d.ts +3 -0
- package/dist/src/core/plugins/openapi.d.ts +3 -0
- package/dist/src/core/vome-config.d.ts +2 -0
- package/dist/src/ext/base-plugin.d.ts +13 -0
- package/dist/src/ext/index.d.ts +15 -0
- package/dist/src/ext/load-plugin.d.ts +12 -0
- package/dist/src/ext/micro-apps.d.ts +60 -0
- package/dist/src/ext/module-gateway.d.ts +10 -0
- package/dist/src/ext/module-registry.d.ts +45 -0
- package/dist/src/ext/path.d.ts +8 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/ioc/container.d.ts +29 -0
- package/dist/src/ioc/decorators.d.ts +35 -0
- package/dist/src/ioc/host.d.ts +2 -0
- package/dist/src/ioc/index.d.ts +6 -0
- package/dist/src/ioc/metadata.d.ts +12 -0
- package/dist/src/ioc/scope.d.ts +9 -0
- package/dist/src/logger/bootstrap.d.ts +2 -0
- package/dist/src/logger/exception.d.ts +22 -0
- package/dist/src/logger/index.d.ts +5 -0
- package/dist/src/logger/logger.d.ts +18 -0
- package/dist/src/logger/pino.d.ts +8 -0
- package/dist/src/orm/base-columns.d.ts +11 -0
- package/dist/src/orm/column-comments.d.ts +25 -0
- package/dist/src/orm/crud-config.d.ts +7 -0
- package/dist/src/orm/crud-register.d.ts +5 -0
- package/dist/src/orm/data-scope.d.ts +24 -0
- package/dist/src/orm/db-store.d.ts +12 -0
- package/dist/src/orm/entity-schema.d.ts +7 -0
- package/dist/src/orm/index.d.ts +22 -0
- package/dist/src/orm/query-builder.d.ts +12 -0
- package/dist/src/orm/query-op.d.ts +27 -0
- package/dist/src/orm/repository.d.ts +74 -0
- package/dist/src/orm/service.d.ts +71 -0
- package/dist/src/orm/tenant.d.ts +24 -0
- package/dist/src/routing/base.d.ts +27 -0
- package/dist/src/routing/context.d.ts +14 -0
- package/dist/src/routing/crud-summary.d.ts +2 -0
- package/dist/src/routing/decorators.d.ts +48 -0
- package/dist/src/routing/eps.d.ts +16 -0
- package/dist/src/routing/index.d.ts +8 -0
- package/dist/src/routing/metadata.d.ts +28 -0
- package/dist/src/routing/openapi-tags.d.ts +23 -0
- package/dist/src/routing/path-utils.d.ts +5 -0
- package/dist/src/routing/perm.d.ts +18 -0
- package/dist/src/routing/register.d.ts +7 -0
- package/dist/src/server/index.d.ts +10 -0
- package/dist/src/shared/index.d.ts +8 -0
- package/dist/src/shared/tree.d.ts +18 -0
- package/dist/typings/comm/crud.d.ts +24 -0
- package/dist/typings/comm/page.d.ts +16 -0
- package/dist/typings/comm/scan.d.ts +6 -0
- package/dist/typings/common.d.ts +6 -0
- package/dist/typings/context/data.d.ts +10 -0
- package/dist/typings/ext/module.d.ts +45 -0
- package/dist/typings/ext/plugin.d.ts +40 -0
- package/dist/typings/ioc/metadata.d.ts +31 -0
- package/dist/typings/logger/config.d.ts +6 -0
- package/dist/typings/routing/crud.d.ts +99 -0
- package/dist/typings/routing/eps.d.ts +53 -0
- package/dist/typings/routing/meta.d.ts +56 -0
- package/dist/typings/routing/register.d.ts +15 -0
- package/dist/typings/routing/scan-context.d.ts +6 -0
- package/package.json +96 -0
- package/src/admin/api/client.ts +184 -0
- package/src/admin/components/vm-aside.vue +227 -0
- package/src/admin/components/vm-auto-perm-dialog.vue +361 -0
- package/src/admin/components/vm-check-tree.vue +338 -0
- package/src/admin/components/vm-dept-tree.vue +549 -0
- package/src/admin/components/vm-empty.vue +62 -0
- package/src/admin/components/vm-eps-perm-picker.vue +114 -0
- package/src/admin/components/vm-group-cascader.vue +563 -0
- package/src/admin/components/vm-icon-picker.vue +616 -0
- package/src/admin/components/vm-markdown.vue +141 -0
- package/src/admin/components/vm-ri-icon.vue +21 -0
- package/src/admin/components/vm-role-picker.vue +314 -0
- package/src/admin/components/vm-view-path-picker.vue +56 -0
- package/src/admin/config/plugin-dev.ts +31 -0
- package/src/admin/crud/components/vm-column-custom.vue +196 -0
- package/src/admin/crud/components/vm-context-menu.vue +99 -0
- package/src/admin/crud/components/vm-date-picker.vue +74 -0
- package/src/admin/crud/components/vm-date-range.vue +291 -0
- package/src/admin/crud/components/vm-date-text.vue +41 -0
- package/src/admin/crud/components/vm-dict-tag.vue +47 -0
- package/src/admin/crud/components/vm-file-icon.vue +54 -0
- package/src/admin/crud/components/vm-multi-select.vue +181 -0
- package/src/admin/crud/components/vm-number-range.vue +200 -0
- package/src/admin/crud/components/vm-radio.vue +87 -0
- package/src/admin/crud/components/vm-select.vue +131 -0
- package/src/admin/crud/components/vm-switch.vue +128 -0
- package/src/admin/crud/components/vm-tree-select.vue +456 -0
- package/src/admin/crud/components/vm-upload-item.vue +215 -0
- package/src/admin/crud/components/vm-upload.vue +323 -0
- package/src/admin/crud/components/vm-user-select.vue +234 -0
- package/src/admin/crud/config.ts +38 -0
- package/src/admin/crud/confirm.ts +135 -0
- package/src/admin/crud/dict.ts +49 -0
- package/src/admin/crud/index.ts +79 -0
- package/src/admin/crud/key.ts +47 -0
- package/src/admin/crud/mitt.ts +27 -0
- package/src/admin/crud/plugins.ts +298 -0
- package/src/admin/crud/registry.ts +45 -0
- package/src/admin/crud/span.ts +48 -0
- package/src/admin/crud/style.ts +112 -0
- package/src/admin/crud/useCrud.ts +81 -0
- package/src/admin/crud/validate.ts +109 -0
- package/src/admin/crud/vm-add-btn.vue +68 -0
- package/src/admin/crud/vm-adv-search.vue +73 -0
- package/src/admin/crud/vm-crud.vue +517 -0
- package/src/admin/crud/vm-dialog.vue +234 -0
- package/src/admin/crud/vm-flex1.vue +14 -0
- package/src/admin/crud/vm-form.vue +239 -0
- package/src/admin/crud/vm-multi-delete-btn.vue +48 -0
- package/src/admin/crud/vm-pagination.vue +125 -0
- package/src/admin/crud/vm-refresh-btn.vue +34 -0
- package/src/admin/crud/vm-row.vue +42 -0
- package/src/admin/crud/vm-search-key.vue +76 -0
- package/src/admin/crud/vm-search.vue +367 -0
- package/src/admin/crud/vm-table.vue +852 -0
- package/src/admin/crud/vm-toolbar.vue +245 -0
- package/src/admin/crud/vm-upsert.vue +530 -0
- package/src/admin/data/remixicon-all.json +1 -0
- package/src/admin/directives/perm.ts +18 -0
- package/src/admin/env.d.ts +5 -0
- package/src/admin/hooks/useBrowser.ts +27 -0
- package/src/admin/hooks/useUpload.ts +82 -0
- package/src/admin/hooks/useVome.ts +13 -0
- package/src/admin/index.ts +59 -0
- package/src/admin/lib/browser.ts +28 -0
- package/src/admin/lib/cn.ts +7 -0
- package/src/admin/lib/dialog-float.ts +14 -0
- package/src/admin/lib/eps.ts +43 -0
- package/src/admin/lib/menu.ts +15 -0
- package/src/admin/lib/module.ts +112 -0
- package/src/admin/lib/tree.ts +2 -0
- package/src/admin/lib/upload.ts +102 -0
- package/src/admin/router/index.ts +61 -0
- package/src/admin/router/menu-routes.ts +126 -0
- package/src/admin/service/base.ts +96 -0
- package/src/admin/service/index.ts +281 -0
- package/src/admin/static/fileicon/demo.css +539 -0
- package/src/admin/static/fileicon/demo_index.html +901 -0
- package/src/admin/static/fileicon/iconfont.css +139 -0
- package/src/admin/static/fileicon/iconfont.js +1 -0
- package/src/admin/static/fileicon/iconfont.json +226 -0
- package/src/admin/static/fileicon/iconfont.ttf +0 -0
- package/src/admin/static/fileicon/iconfont.woff +0 -0
- package/src/admin/static/fileicon/iconfont.woff2 +0 -0
- package/src/admin/static/scarce//344/270/213/350/275/275/345/244/261/350/264/245.svg +1 -0
- package/src/admin/static/scarce//346/224/257/344/273/230/346/210/220/345/212/237.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/344/274/230/346/203/240/345/210/270.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/345/206/205/345/256/271.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/345/217/221/347/245/250.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/345/233/276/347/211/207.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/345/234/260/345/235/200.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/345/272/227/351/223/272.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/346/216/250/351/200/201.svg +1 -0
- 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
- package/src/admin/static/scarce//346/232/202/346/227/240/346/224/266/350/227/217.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/346/225/260/346/215/256.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/346/235/203/351/231/220.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/346/266/210/346/201/257.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/347/244/274/347/211/251.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/347/247/257/345/210/206.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/347/275/221/347/273/234.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/350/247/206/351/242/221.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/350/256/242/345/215/225.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/350/257/204/350/256/272.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/351/205/215/351/200/201.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/351/223/266/350/241/214/345/215/241.svg +1 -0
- package/src/admin/static/scarce//346/232/202/346/227/240/351/237/263/344/271/220.svg +1 -0
- package/src/admin/static/scarce//351/241/265/351/235/242/344/270/215/345/255/230/345/234/250.svg +1 -0
- package/src/admin/stores/app.ts +30 -0
- package/src/admin/stores/tags.ts +73 -0
- package/src/admin/stores/user.ts +52 -0
- package/src/admin/styles/base.css +137 -0
- package/src/admin/tsconfig.json +19 -0
- package/typings/admin/base/auth.ts +23 -0
- package/typings/admin/base/permission.ts +19 -0
- package/typings/admin/comm/cascader.ts +7 -0
- package/typings/admin/comm/check-tree.ts +7 -0
- package/typings/admin/comm/crud.ts +294 -0
- package/typings/admin/comm/eps.ts +52 -0
- package/typings/admin/comm/request.ts +12 -0
- package/typings/admin/comm/service.ts +35 -0
- package/typings/admin/comm/upload.ts +39 -0
- package/typings/admin/host.d.ts +61 -0
- package/typings/admin/vome/browser.ts +11 -0
- package/typings/admin/vome/module.ts +13 -0
- package/typings/admin/vome/tags.ts +9 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { findEpsEntity } from '../lib/eps'
|
|
2
|
+
import { getCrudStyle } from './style'
|
|
3
|
+
import type {
|
|
4
|
+
CrudSearchItem,
|
|
5
|
+
CrudTableOptions,
|
|
6
|
+
TablePlugin,
|
|
7
|
+
} from '../../../typings/admin/comm/crud'
|
|
8
|
+
|
|
9
|
+
export type { TablePlugin }
|
|
10
|
+
|
|
11
|
+
/** 表格树形展示 */
|
|
12
|
+
export function toTree(options: { lazy?: boolean } = {}): TablePlugin {
|
|
13
|
+
return {
|
|
14
|
+
__plugin: 'toTree',
|
|
15
|
+
tree: true,
|
|
16
|
+
lazy: options.lazy ?? false,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 表单自动聚焦 */
|
|
21
|
+
export function setFocus(prop?: string) {
|
|
22
|
+
return {
|
|
23
|
+
__plugin: 'setFocus',
|
|
24
|
+
prop: prop ?? '',
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** required → rules */
|
|
29
|
+
export function setRules() {
|
|
30
|
+
return {
|
|
31
|
+
__plugin: 'setRules',
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 搜索项自动生成 */
|
|
36
|
+
export function setAuto(
|
|
37
|
+
options: {
|
|
38
|
+
hideLabel?: boolean
|
|
39
|
+
ignoreFields?: string[]
|
|
40
|
+
} = { hideLabel: true },
|
|
41
|
+
) {
|
|
42
|
+
return {
|
|
43
|
+
__plugin: 'setAuto',
|
|
44
|
+
...options,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const Plugins = {
|
|
49
|
+
Table: { toTree },
|
|
50
|
+
Form: { setFocus, setRules },
|
|
51
|
+
Search: { setAuto },
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 把 plugins 合并进 table options + 全局 style */
|
|
55
|
+
export function applyTablePlugins(
|
|
56
|
+
options?: CrudTableOptions & { plugins?: TablePlugin[] },
|
|
57
|
+
): CrudTableOptions {
|
|
58
|
+
const style = getCrudStyle().table
|
|
59
|
+
const base: CrudTableOptions = {
|
|
60
|
+
border: style.border,
|
|
61
|
+
autoHeight: style.autoHeight,
|
|
62
|
+
contextMenu: style.contextMenu,
|
|
63
|
+
...options,
|
|
64
|
+
}
|
|
65
|
+
const plugins = [
|
|
66
|
+
...(style.plugins as TablePlugin[]),
|
|
67
|
+
...((options?.plugins as TablePlugin[]) || []),
|
|
68
|
+
]
|
|
69
|
+
const next: CrudTableOptions = { ...base }
|
|
70
|
+
delete (next as { plugins?: unknown }).plugins
|
|
71
|
+
for (const p of plugins) {
|
|
72
|
+
if (!p || typeof p !== 'object') continue
|
|
73
|
+
if (p.__plugin === 'toTree') {
|
|
74
|
+
next.tree = true
|
|
75
|
+
next.treeLazy = Boolean(p.lazy)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return next
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type ColMeta = {
|
|
82
|
+
propertyName?: string
|
|
83
|
+
comment?: string
|
|
84
|
+
dict?: string | string[]
|
|
85
|
+
type?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type FieldRefObj = {
|
|
89
|
+
column: string
|
|
90
|
+
requestParam?: string
|
|
91
|
+
label?: string
|
|
92
|
+
dict?: string
|
|
93
|
+
multiple?: boolean
|
|
94
|
+
none?: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type FieldRangeOp = {
|
|
98
|
+
column: string
|
|
99
|
+
min: string
|
|
100
|
+
max: string
|
|
101
|
+
type: NonNullable<CrudSearchItem['range']>['rangeType']
|
|
102
|
+
none?: boolean
|
|
103
|
+
label?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function stripAlias(name: string) {
|
|
107
|
+
return name.includes('.') ? name.split('.').pop()! : name
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseRef(ref: unknown): {
|
|
111
|
+
column: string
|
|
112
|
+
param: string
|
|
113
|
+
label?: string
|
|
114
|
+
dict?: string
|
|
115
|
+
multiple: boolean
|
|
116
|
+
none: boolean
|
|
117
|
+
} | null {
|
|
118
|
+
if (typeof ref === 'string') {
|
|
119
|
+
const column = stripAlias(ref)
|
|
120
|
+
return { column, param: column, multiple: true, none: false }
|
|
121
|
+
}
|
|
122
|
+
if (!ref || typeof ref !== 'object') return null
|
|
123
|
+
const o = ref as FieldRefObj
|
|
124
|
+
if (!o.column) return null
|
|
125
|
+
const column = stripAlias(o.column)
|
|
126
|
+
return {
|
|
127
|
+
column,
|
|
128
|
+
param: o.requestParam || column,
|
|
129
|
+
label: o.label,
|
|
130
|
+
dict: o.dict,
|
|
131
|
+
multiple: o.multiple !== false,
|
|
132
|
+
none: Boolean(o.none),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function displayName(
|
|
137
|
+
ref: { column: string; label?: string },
|
|
138
|
+
meta?: ColMeta,
|
|
139
|
+
) {
|
|
140
|
+
return ref.label || meta?.comment || ref.column
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 从 EPS / service.search 生成搜索项(跳过 none:true) */
|
|
144
|
+
export function buildAutoSearchItems(
|
|
145
|
+
service: { namespace?: string; search?: Record<string, unknown> },
|
|
146
|
+
options: { hideLabel?: boolean; ignoreFields?: string[] } = {},
|
|
147
|
+
): CrudSearchItem[] {
|
|
148
|
+
const ignore = new Set(options.ignoreFields || [])
|
|
149
|
+
const search = (service.search || {}) as {
|
|
150
|
+
fieldEq?: unknown[]
|
|
151
|
+
fieldLike?: unknown[]
|
|
152
|
+
fieldArray?: unknown[]
|
|
153
|
+
fieldRange?: FieldRangeOp[]
|
|
154
|
+
keyWordLikeFields?: unknown[]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let fieldEq = search.fieldEq || []
|
|
158
|
+
let fieldLike = search.fieldLike || []
|
|
159
|
+
let fieldArray = search.fieldArray || []
|
|
160
|
+
let fieldRange = search.fieldRange || []
|
|
161
|
+
let keyWordLikeFields = search.keyWordLikeFields || []
|
|
162
|
+
let cols: ColMeta[] = []
|
|
163
|
+
|
|
164
|
+
const entity = service.namespace
|
|
165
|
+
? findEpsEntity(`/${service.namespace}`)
|
|
166
|
+
: undefined
|
|
167
|
+
if (entity) {
|
|
168
|
+
cols = [
|
|
169
|
+
...((entity as { columns?: ColMeta[] }).columns || []),
|
|
170
|
+
...((entity as { pageColumns?: ColMeta[] }).pageColumns || []),
|
|
171
|
+
]
|
|
172
|
+
// service.search 未挂载时回退读 EPS.pageQueryOp
|
|
173
|
+
if (!service.search) {
|
|
174
|
+
const op = (entity as { pageQueryOp?: Record<string, unknown> }).pageQueryOp
|
|
175
|
+
if (op) {
|
|
176
|
+
fieldEq = (op.fieldEq as unknown[]) || []
|
|
177
|
+
fieldLike = (op.fieldLike as unknown[]) || []
|
|
178
|
+
fieldArray = (op.fieldArray as unknown[]) || []
|
|
179
|
+
fieldRange = (op.fieldRange as FieldRangeOp[]) || []
|
|
180
|
+
keyWordLikeFields = (op.keyWordLikeFields as unknown[]) || []
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const colOf = (name: string) => cols.find((c) => c.propertyName === name)
|
|
186
|
+
|
|
187
|
+
const items: CrudSearchItem[] = []
|
|
188
|
+
const usedProps = new Set<string>()
|
|
189
|
+
|
|
190
|
+
const pushItem = (item: CrudSearchItem) => {
|
|
191
|
+
if (!item.prop || usedProps.has(item.prop)) return
|
|
192
|
+
usedProps.add(item.prop)
|
|
193
|
+
items.push(item)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const raw of fieldLike) {
|
|
197
|
+
const ref = parseRef(raw)
|
|
198
|
+
if (!ref || ref.none || ignore.has(ref.param)) continue
|
|
199
|
+
const meta = colOf(ref.column)
|
|
200
|
+
const name = displayName(ref, meta)
|
|
201
|
+
pushItem({
|
|
202
|
+
prop: ref.param,
|
|
203
|
+
label: options.hideLabel ? '' : name,
|
|
204
|
+
placeholder: `搜索${name}`,
|
|
205
|
+
type: 'input',
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const raw of fieldArray) {
|
|
210
|
+
const ref = parseRef(raw)
|
|
211
|
+
if (!ref || ref.none || ignore.has(ref.param)) continue
|
|
212
|
+
const meta = colOf(ref.column)
|
|
213
|
+
const name = displayName(ref, meta)
|
|
214
|
+
pushItem({
|
|
215
|
+
prop: ref.param,
|
|
216
|
+
label: options.hideLabel ? '' : name,
|
|
217
|
+
placeholder: `搜索${name}`,
|
|
218
|
+
type: 'input',
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const raw of fieldEq) {
|
|
223
|
+
const ref = parseRef(raw)
|
|
224
|
+
if (!ref || ref.none || ignore.has(ref.param)) continue
|
|
225
|
+
const meta = colOf(ref.column)
|
|
226
|
+
const dictKey =
|
|
227
|
+
ref.dict ||
|
|
228
|
+
(typeof meta?.dict === 'string'
|
|
229
|
+
? meta.dict
|
|
230
|
+
: Array.isArray(meta?.dict)
|
|
231
|
+
? meta.dict[0]
|
|
232
|
+
: undefined)
|
|
233
|
+
const name = displayName(ref, meta)
|
|
234
|
+
const label = options.hideLabel ? '' : name
|
|
235
|
+
if (dictKey) {
|
|
236
|
+
pushItem({
|
|
237
|
+
prop: ref.param,
|
|
238
|
+
label,
|
|
239
|
+
placeholder: `选择${name}`,
|
|
240
|
+
type: 'select',
|
|
241
|
+
dict: dictKey,
|
|
242
|
+
multiple: ref.multiple,
|
|
243
|
+
})
|
|
244
|
+
} else {
|
|
245
|
+
pushItem({
|
|
246
|
+
prop: ref.param,
|
|
247
|
+
label,
|
|
248
|
+
placeholder: `搜索${name}`,
|
|
249
|
+
type: 'input',
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const r of fieldRange) {
|
|
255
|
+
if (!r?.column || r.none) continue
|
|
256
|
+
if (ignore.has(r.min) || ignore.has(r.max)) continue
|
|
257
|
+
const col = stripAlias(r.column)
|
|
258
|
+
const meta = colOf(col)
|
|
259
|
+
const name = r.label || meta?.comment || col
|
|
260
|
+
const label = options.hideLabel ? '' : name
|
|
261
|
+
const isNum = r.type === 'int' || r.type === 'float'
|
|
262
|
+
pushItem({
|
|
263
|
+
prop: `__range_${r.min}_${r.max}`,
|
|
264
|
+
label,
|
|
265
|
+
placeholder: name || '区间',
|
|
266
|
+
type: isNum ? 'number-range' : 'daterange',
|
|
267
|
+
range: { min: r.min, max: r.max, rangeType: r.type },
|
|
268
|
+
component: {
|
|
269
|
+
name: isNum ? 'vm-number-range' : 'vm-date-range',
|
|
270
|
+
props: isNum
|
|
271
|
+
? { mode: r.type, startPlaceholder: '最小值', endPlaceholder: '最大值' }
|
|
272
|
+
: {
|
|
273
|
+
precision: r.type,
|
|
274
|
+
startPlaceholder: '开始时间',
|
|
275
|
+
endPlaceholder: '结束时间',
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (keyWordLikeFields.length) {
|
|
282
|
+
const names = keyWordLikeFields
|
|
283
|
+
.map((raw) => {
|
|
284
|
+
const ref = parseRef(raw)
|
|
285
|
+
if (!ref) return typeof raw === 'string' ? stripAlias(raw) : ''
|
|
286
|
+
return displayName(ref, colOf(ref.column))
|
|
287
|
+
})
|
|
288
|
+
.filter(Boolean)
|
|
289
|
+
pushItem({
|
|
290
|
+
prop: 'keyWord',
|
|
291
|
+
label: options.hideLabel ? '' : '关键字',
|
|
292
|
+
placeholder: `搜索${names.join('、') || '关键字'}`,
|
|
293
|
+
type: 'input',
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return items
|
|
298
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Component } from 'vue'
|
|
2
|
+
|
|
3
|
+
type Mod = { default: Component }
|
|
4
|
+
|
|
5
|
+
/** 文件名即注册名:vm-switch.vue → vm-switch(新建放入目录即生效) */
|
|
6
|
+
function registerGlob(
|
|
7
|
+
modules: Record<string, Mod>,
|
|
8
|
+
into: Record<string, Component>,
|
|
9
|
+
) {
|
|
10
|
+
for (const [file, mod] of Object.entries(modules)) {
|
|
11
|
+
const base = file.split('/').pop()?.replace(/\.vue$/, '')
|
|
12
|
+
if (!base?.startsWith('vm-') || !mod?.default) continue
|
|
13
|
+
into[base] = mod.default
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const map: Record<string, Component> = {}
|
|
18
|
+
|
|
19
|
+
registerGlob(
|
|
20
|
+
import.meta.glob<Mod>('./components/vm-*.vue', { eager: true }),
|
|
21
|
+
map,
|
|
22
|
+
)
|
|
23
|
+
registerGlob(
|
|
24
|
+
import.meta.glob<Mod>('../components/vm-*.vue', { eager: true }),
|
|
25
|
+
map,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
/** 组件别名 */
|
|
29
|
+
if (map['vm-view-path-picker']) {
|
|
30
|
+
map['vm-view-path'] = map['vm-view-path-picker']
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function registerCrudComponent(name: string, comp: Component) {
|
|
34
|
+
map[name] = comp
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveCrudComponent(
|
|
38
|
+
name?: string | Component,
|
|
39
|
+
): Component | undefined {
|
|
40
|
+
if (!name) return undefined
|
|
41
|
+
if (typeof name !== 'string') return name
|
|
42
|
+
return map[name]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { map as crudComponentMap }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { onMounted, onUnmounted, ref, type Ref } from 'vue'
|
|
2
|
+
import { getCrudStyle } from './style'
|
|
3
|
+
|
|
4
|
+
/** 桌面表单栅格列数(全宽 span = 24) */
|
|
5
|
+
export const FORM_COLS_DESKTOP = 24
|
|
6
|
+
/** 移动端表单栅格列数(全宽 span = 12) */
|
|
7
|
+
export const FORM_COLS_MOBILE = 12
|
|
8
|
+
/** 配置侧全宽默认值(一律按 24 栅格书写) */
|
|
9
|
+
export const FORM_SPAN_FULL = 24
|
|
10
|
+
|
|
11
|
+
export const FORM_MQ_MOBILE = '(max-width: 768px)'
|
|
12
|
+
|
|
13
|
+
/** 当前断点下列数:桌面 24 / 移动端 12 */
|
|
14
|
+
export function getFormCols(): number {
|
|
15
|
+
if (typeof window !== 'undefined' && window.matchMedia(FORM_MQ_MOBILE).matches) {
|
|
16
|
+
return FORM_COLS_MOBILE
|
|
17
|
+
}
|
|
18
|
+
return FORM_COLS_DESKTOP
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 将 items.span(按 24 栅格配置)换算为当前网格列数下的 span。
|
|
23
|
+
* 移动端:24 → 12(全宽),12 → 6(半宽)。
|
|
24
|
+
*/
|
|
25
|
+
export function resolveFormSpan(span?: number, cols = getFormCols()): number {
|
|
26
|
+
const raw = span ?? getCrudStyle().form.span ?? FORM_SPAN_FULL
|
|
27
|
+
const normalized = Math.min(FORM_SPAN_FULL, Math.max(1, raw))
|
|
28
|
+
if (cols === FORM_COLS_DESKTOP) return normalized
|
|
29
|
+
return Math.max(1, Math.round((normalized / FORM_COLS_DESKTOP) * FORM_COLS_MOBILE))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 响应式列数,断点变化时触发重渲染 */
|
|
33
|
+
export function useFormCols(): Ref<number> {
|
|
34
|
+
const cols = ref(getFormCols())
|
|
35
|
+
let mql: MediaQueryList | null = null
|
|
36
|
+
const sync = () => {
|
|
37
|
+
cols.value = getFormCols()
|
|
38
|
+
}
|
|
39
|
+
onMounted(() => {
|
|
40
|
+
mql = window.matchMedia(FORM_MQ_MOBILE)
|
|
41
|
+
mql.addEventListener('change', sync)
|
|
42
|
+
sync()
|
|
43
|
+
})
|
|
44
|
+
onUnmounted(() => {
|
|
45
|
+
mql?.removeEventListener('change', sync)
|
|
46
|
+
})
|
|
47
|
+
return cols
|
|
48
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { CRUD_LABELS, DEFAULT_CRUD_DICT } from './dict'
|
|
2
|
+
|
|
3
|
+
/** CRUD 默认样式配置 */
|
|
4
|
+
export type CrudStyleConfig = {
|
|
5
|
+
form: {
|
|
6
|
+
labelPosition: 'top' | 'left' | 'right'
|
|
7
|
+
labelWidth: string
|
|
8
|
+
span: number
|
|
9
|
+
plugins: unknown[]
|
|
10
|
+
}
|
|
11
|
+
table: {
|
|
12
|
+
border: boolean
|
|
13
|
+
highlightCurrentRow: boolean
|
|
14
|
+
autoHeight: boolean
|
|
15
|
+
contextMenu: boolean | string[]
|
|
16
|
+
column: {
|
|
17
|
+
align: 'left' | 'center' | 'right'
|
|
18
|
+
opWidth: number
|
|
19
|
+
}
|
|
20
|
+
plugins: unknown[]
|
|
21
|
+
}
|
|
22
|
+
search: {
|
|
23
|
+
plugins: unknown[]
|
|
24
|
+
}
|
|
25
|
+
colors: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type CrudGlobalConfig = {
|
|
29
|
+
dict: CrudDict
|
|
30
|
+
style: CrudStyleConfig
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const DEFAULT_CRUD_STYLE: CrudStyleConfig = {
|
|
34
|
+
form: {
|
|
35
|
+
labelPosition: 'top',
|
|
36
|
+
labelWidth: '100px',
|
|
37
|
+
span: 24,
|
|
38
|
+
plugins: [],
|
|
39
|
+
},
|
|
40
|
+
table: {
|
|
41
|
+
border: false,
|
|
42
|
+
highlightCurrentRow: true,
|
|
43
|
+
autoHeight: true,
|
|
44
|
+
contextMenu: ['refresh', 'check', 'edit', 'delete', 'order-asc', 'order-desc'],
|
|
45
|
+
column: {
|
|
46
|
+
align: 'left',
|
|
47
|
+
opWidth: 180,
|
|
48
|
+
},
|
|
49
|
+
plugins: [],
|
|
50
|
+
},
|
|
51
|
+
search: {
|
|
52
|
+
plugins: [],
|
|
53
|
+
},
|
|
54
|
+
colors: [
|
|
55
|
+
'#4E5DFF',
|
|
56
|
+
'#06b31c',
|
|
57
|
+
'#e93f4d',
|
|
58
|
+
'#d57121',
|
|
59
|
+
'#6d17c3',
|
|
60
|
+
'#04c273',
|
|
61
|
+
'#aa7a24',
|
|
62
|
+
'#1c109d',
|
|
63
|
+
],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let globalConfig: CrudGlobalConfig = {
|
|
67
|
+
dict: { ...DEFAULT_CRUD_DICT, label: { ...CRUD_LABELS } },
|
|
68
|
+
style: structuredClone(DEFAULT_CRUD_STYLE),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 安装/覆盖全局 CRUD 配置 */
|
|
72
|
+
export function setCrudConfig(partial?: {
|
|
73
|
+
dict?: Partial<CrudDict>
|
|
74
|
+
style?: Partial<CrudStyleConfig>
|
|
75
|
+
}) {
|
|
76
|
+
if (!partial) return globalConfig
|
|
77
|
+
globalConfig = {
|
|
78
|
+
dict: {
|
|
79
|
+
...globalConfig.dict,
|
|
80
|
+
...partial.dict,
|
|
81
|
+
api: { ...globalConfig.dict.api, ...partial.dict?.api },
|
|
82
|
+
pagination: { ...globalConfig.dict.pagination, ...partial.dict?.pagination },
|
|
83
|
+
search: { ...globalConfig.dict.search, ...partial.dict?.search },
|
|
84
|
+
sort: { ...globalConfig.dict.sort, ...partial.dict?.sort },
|
|
85
|
+
label: { ...globalConfig.dict.label, ...partial.dict?.label },
|
|
86
|
+
},
|
|
87
|
+
style: {
|
|
88
|
+
...globalConfig.style,
|
|
89
|
+
...partial.style,
|
|
90
|
+
form: { ...globalConfig.style.form, ...partial.style?.form },
|
|
91
|
+
table: {
|
|
92
|
+
...globalConfig.style.table,
|
|
93
|
+
...partial.style?.table,
|
|
94
|
+
column: {
|
|
95
|
+
...globalConfig.style.table.column,
|
|
96
|
+
...partial.style?.table?.column,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
search: { ...globalConfig.style.search, ...partial.style?.search },
|
|
100
|
+
colors: partial.style?.colors || globalConfig.style.colors,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
return globalConfig
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getCrudConfig() {
|
|
107
|
+
return globalConfig
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getCrudStyle() {
|
|
111
|
+
return globalConfig.style
|
|
112
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
inject,
|
|
3
|
+
provide,
|
|
4
|
+
ref,
|
|
5
|
+
watch,
|
|
6
|
+
type Ref,
|
|
7
|
+
} from 'vue'
|
|
8
|
+
import {
|
|
9
|
+
CRUD_KEY,
|
|
10
|
+
CRUD_OPTIONS_KEY,
|
|
11
|
+
SEARCH_OPTIONS_KEY,
|
|
12
|
+
TABLE_OPTIONS_KEY,
|
|
13
|
+
UPSERT_OPTIONS_KEY,
|
|
14
|
+
} from './key'
|
|
15
|
+
import { applyTablePlugins } from './plugins'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* useCrud:
|
|
19
|
+
* - useCrud(options, cb) provide 配置;模板 ref="Crud" 拿到实例后 cb
|
|
20
|
+
* - useCrud() 在子组件 inject
|
|
21
|
+
*/
|
|
22
|
+
export function useCrud(): CrudContext
|
|
23
|
+
export function useCrud(
|
|
24
|
+
options: CrudOptions,
|
|
25
|
+
cb?: (app: CrudContext) => void,
|
|
26
|
+
): Ref<CrudContext | undefined>
|
|
27
|
+
export function useCrud(
|
|
28
|
+
options?: CrudOptions,
|
|
29
|
+
cb?: (app: CrudContext) => void,
|
|
30
|
+
) {
|
|
31
|
+
if (!options) {
|
|
32
|
+
const ctx = inject(CRUD_KEY)
|
|
33
|
+
if (!ctx) throw new Error('[useCrud] 必须在 VmCrud 内使用')
|
|
34
|
+
return ctx
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
provide(CRUD_OPTIONS_KEY, options)
|
|
38
|
+
const Crud = ref<CrudContext>()
|
|
39
|
+
|
|
40
|
+
watch(
|
|
41
|
+
Crud,
|
|
42
|
+
(val) => {
|
|
43
|
+
if (val && cb) cb(val)
|
|
44
|
+
},
|
|
45
|
+
{ immediate: true },
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return Crud
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useTable(
|
|
52
|
+
options?: CrudTableOptions & { plugins?: TablePlugin[] },
|
|
53
|
+
cb?: (table: unknown) => void,
|
|
54
|
+
) {
|
|
55
|
+
const Table = ref()
|
|
56
|
+
if (options) provide(TABLE_OPTIONS_KEY, applyTablePlugins(options))
|
|
57
|
+
watch(Table, (val) => {
|
|
58
|
+
if (val && cb) cb(val)
|
|
59
|
+
})
|
|
60
|
+
return Table
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useUpsert(options?: CrudUpsertOptions) {
|
|
64
|
+
const Upsert = ref()
|
|
65
|
+
if (options) provide(UPSERT_OPTIONS_KEY, options)
|
|
66
|
+
return Upsert
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function useSearch(options?: CrudSearchOptions) {
|
|
70
|
+
const Search = ref()
|
|
71
|
+
if (options) provide(SEARCH_OPTIONS_KEY, options)
|
|
72
|
+
return Search
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function useCore() {
|
|
76
|
+
const crud = inject(CRUD_KEY)
|
|
77
|
+
if (!crud) throw new Error('[useCore] 必须在 VmCrud 内使用')
|
|
78
|
+
return { crud }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { CRUD_KEY }
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export type CrudRule = {
|
|
2
|
+
required?: boolean
|
|
3
|
+
message?: string
|
|
4
|
+
min?: number
|
|
5
|
+
max?: number
|
|
6
|
+
pattern?: RegExp
|
|
7
|
+
validator?: (
|
|
8
|
+
value: unknown,
|
|
9
|
+
form: Record<string, unknown>,
|
|
10
|
+
) => true | string | Promise<true | string>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isEmpty(v: unknown) {
|
|
14
|
+
if (v == null || v === '') return true
|
|
15
|
+
if (typeof v === 'string' && v.trim() === '') return true
|
|
16
|
+
if (Array.isArray(v) && v.length === 0) return true
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeRules(item: CrudFormItem): CrudRule[] {
|
|
21
|
+
const rules: CrudRule[] = []
|
|
22
|
+
if (item.required) {
|
|
23
|
+
rules.push({
|
|
24
|
+
required: true,
|
|
25
|
+
message: `${item.label}不能为空`,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
const raw = item.rules
|
|
29
|
+
if (!raw) return rules
|
|
30
|
+
if (Array.isArray(raw)) {
|
|
31
|
+
for (const r of raw) {
|
|
32
|
+
if (r && typeof r === 'object') rules.push(r as CrudRule)
|
|
33
|
+
}
|
|
34
|
+
} else if (typeof raw === 'object') {
|
|
35
|
+
rules.push(raw as CrudRule)
|
|
36
|
+
}
|
|
37
|
+
return rules
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isItemHidden(item: CrudFormItem, form: Record<string, unknown>) {
|
|
41
|
+
if (typeof item.hidden === 'function') return item.hidden(form)
|
|
42
|
+
return Boolean(item.hidden)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function validateItem(
|
|
46
|
+
item: CrudFormItem,
|
|
47
|
+
form: Record<string, unknown>,
|
|
48
|
+
): Promise<string | null> {
|
|
49
|
+
const value = form[item.prop]
|
|
50
|
+
for (const rule of normalizeRules(item)) {
|
|
51
|
+
if (rule.required && isEmpty(value)) {
|
|
52
|
+
return rule.message || `${item.label}不能为空`
|
|
53
|
+
}
|
|
54
|
+
if (isEmpty(value)) continue
|
|
55
|
+
const str = String(value)
|
|
56
|
+
if (rule.min != null && str.length < rule.min) {
|
|
57
|
+
return rule.message || `${item.label}至少 ${rule.min} 个字符`
|
|
58
|
+
}
|
|
59
|
+
if (rule.max != null && str.length > rule.max) {
|
|
60
|
+
return rule.message || `${item.label}最多 ${rule.max} 个字符`
|
|
61
|
+
}
|
|
62
|
+
if (rule.pattern && !rule.pattern.test(str)) {
|
|
63
|
+
return rule.message || `${item.label}格式不正确`
|
|
64
|
+
}
|
|
65
|
+
if (rule.validator) {
|
|
66
|
+
const r = await rule.validator(value, form)
|
|
67
|
+
if (r !== true) return r || `${item.label}校验失败`
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 校验表单项;失败返回错误文案,成功返回 null */
|
|
74
|
+
export async function validateFormItems(
|
|
75
|
+
items: CrudFormItem[],
|
|
76
|
+
form: Record<string, unknown>,
|
|
77
|
+
): Promise<string | null> {
|
|
78
|
+
for (const item of items) {
|
|
79
|
+
if (isItemHidden(item, form)) continue
|
|
80
|
+
const err = await validateItem(item, form)
|
|
81
|
+
if (err) return err
|
|
82
|
+
}
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 按字段返回校验错误(字段下红字) */
|
|
87
|
+
export async function validateFormFields(
|
|
88
|
+
items: CrudFormItem[],
|
|
89
|
+
form: Record<string, unknown>,
|
|
90
|
+
): Promise<Record<string, string>> {
|
|
91
|
+
const errors: Record<string, string> = {}
|
|
92
|
+
for (const item of items) {
|
|
93
|
+
if (isItemHidden(item, form)) continue
|
|
94
|
+
const err = await validateItem(item, form)
|
|
95
|
+
if (err) errors[item.prop] = err
|
|
96
|
+
}
|
|
97
|
+
return errors
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** setRules:required 自动补全 rules(若尚未配置) */
|
|
101
|
+
export function applySetRules(items: CrudFormItem[]): CrudFormItem[] {
|
|
102
|
+
return items.map((it) => {
|
|
103
|
+
if (!it.required || it.rules) return it
|
|
104
|
+
return {
|
|
105
|
+
...it,
|
|
106
|
+
rules: [{ required: true, message: `${it.label}不能为空` }],
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
}
|