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,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
+ }