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,42 @@
1
+ <template>
2
+ <div class="vm-crud-row">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ defineOptions({ name: 'vm-row' })
9
+ </script>
10
+
11
+ <style lang="scss" scoped>
12
+ .vm-crud-row {
13
+ display: flex;
14
+ width: 100%;
15
+ min-width: 0;
16
+ flex-wrap: wrap;
17
+ align-items: center;
18
+ gap: 8px;
19
+
20
+ /* 筛选项为空时 vm-search 不渲染,避免占位行撑出顶部空白 */
21
+ &:empty {
22
+ display: none;
23
+ }
24
+
25
+ /* 包住表格的行:占满剩余高度,让表内滚动 */
26
+ &:has(.vm-crud-table) {
27
+ flex: 1;
28
+ min-height: 0;
29
+ flex-wrap: nowrap;
30
+ align-items: stretch;
31
+ overflow: hidden;
32
+ }
33
+ }
34
+
35
+ /* 中窄屏工具行:刷新与操作按钮顶对齐、统一换行 */
36
+ @media (max-width: 1280px) {
37
+ .vm-crud-row:has(.vm-crud-toolbar) {
38
+ align-items: flex-start;
39
+ gap: 8px;
40
+ }
41
+ }
42
+ </style>
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <div class="vm-crud-search-key">
3
+ <Input
4
+ class="vm-crud-search-key__input"
5
+ :model-value="keyword"
6
+ :placeholder="placeholder || crud.dict.label.searchKey"
7
+ @update:model-value="onInput"
8
+ @keydown.enter.prevent="search"
9
+ />
10
+ <button type="button" class="vm-crud-search-key__btn" @click="search">
11
+ {{ crud.dict.label.search }}
12
+ </button>
13
+ </div>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { useCrud } from './useCrud'
18
+
19
+ defineOptions({ name: 'vm-search-key' })
20
+
21
+ const props = defineProps<{
22
+ placeholder?: string
23
+ prop?: string
24
+ }>()
25
+
26
+ const crud = useCrud()
27
+ const keyword = ref('')
28
+ const field = props.prop || crud.dict.search.keyWord
29
+
30
+ function onInput(v: string | number) {
31
+ keyword.value = String(v)
32
+ }
33
+
34
+ function search() {
35
+ crud.setParams({ [field]: keyword.value || undefined }, true)
36
+ }
37
+ </script>
38
+
39
+ <style lang="scss" scoped>
40
+ .vm-crud-search-key {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 8px;
44
+ }
45
+
46
+ :deep(.vm-crud-search-key__input) {
47
+ height: var(--vm-control-height) !important;
48
+ width: 200px;
49
+ border: 1px solid transparent !important;
50
+ border-radius: 12px !important;
51
+ background: var(--muted) !important;
52
+ box-shadow: none !important;
53
+
54
+ &:focus,
55
+ &:focus-visible {
56
+ border-color: var(--focus-border) !important;
57
+ box-shadow: none !important;
58
+ }
59
+ }
60
+
61
+ .vm-crud-search-key__btn {
62
+ height: var(--vm-control-height);
63
+ padding: 0 14px;
64
+ border: none;
65
+ border-radius: 12px;
66
+ background: var(--brand);
67
+ color: #fff;
68
+ font-size: 13px;
69
+ font-weight: 600;
70
+ cursor: pointer;
71
+
72
+ &:hover {
73
+ background: var(--brand-deep);
74
+ }
75
+ }
76
+ </style>
@@ -0,0 +1,367 @@
1
+ <template>
2
+ <div v-if="items.length" class="vm-crud-search">
3
+ <div class="vm-crud-search__filters">
4
+ <template v-for="(item, idx) in items" :key="`${item.prop}-${idx}`">
5
+ <component
6
+ :is="resolveField(item)"
7
+ v-if="resolveField(item)"
8
+ class="vm-crud-search__control"
9
+ :class="{ 'is-range': isRange(item) }"
10
+ v-bind="fieldProps(item)"
11
+ :model-value="form[item.prop]"
12
+ @update:model-value="(v: unknown) => (form[item.prop] = v)"
13
+ />
14
+ <Select
15
+ v-else-if="item.type === 'select' && !item.multiple"
16
+ :model-value="String(form[item.prop] ?? '')"
17
+ @update:model-value="(v) => (form[item.prop] = coerceSelect(item, v))"
18
+ >
19
+ <SelectTrigger class="vm-crud-search__control">
20
+ <SelectValue :placeholder="item.placeholder || item.label" />
21
+ </SelectTrigger>
22
+ <SelectContent>
23
+ <SelectItem
24
+ v-for="opt in item.options || []"
25
+ :key="String(opt.value)"
26
+ :value="String(opt.value)"
27
+ >
28
+ {{ opt.label }}
29
+ </SelectItem>
30
+ </SelectContent>
31
+ </Select>
32
+ <Input
33
+ v-else
34
+ class="vm-crud-search__control"
35
+ :model-value="String(form[item.prop] ?? '')"
36
+ :placeholder="item.placeholder || item.label"
37
+ @update:model-value="(v) => (form[item.prop] = v)"
38
+ @keydown.enter.prevent="search"
39
+ />
40
+ </template>
41
+ </div>
42
+ <div v-if="inlineActions" class="vm-crud-search__actions">
43
+ <button type="button" class="vm-crud-search__btn is-primary" @click="search">搜索</button>
44
+ <button type="button" class="vm-crud-search__btn is-ghost" @click="reset">重置</button>
45
+ <slot />
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <script setup lang="ts">
51
+ import { onBeforeUnmount } from 'vue'
52
+ import { useCrud } from './useCrud'
53
+ import { injectSearchOptions } from './key'
54
+ import { buildAutoSearchItems } from './plugins'
55
+ import { getCrudStyle } from './style'
56
+ import { resolveCrudComponent } from './registry'
57
+ import { service } from '../service'
58
+ defineOptions({ name: 'vm-search' })
59
+
60
+ const props = withDefaults(
61
+ defineProps<{
62
+ items?: CrudSearchItem[]
63
+ /** 为 true 时在筛选器同行显示搜索/重置(默认由 vm-toolbar 展示) */
64
+ inlineActions?: boolean
65
+ }>(),
66
+ { inlineActions: false },
67
+ )
68
+
69
+ const crud = useCrud()
70
+ const searchOpts = injectSearchOptions()
71
+ const autoItems = ref<CrudSearchItem[]>([])
72
+
73
+ const manualItems = computed(
74
+ () => props.items?.length ? props.items : searchOpts?.items?.length ? searchOpts.items : [],
75
+ )
76
+
77
+ /** 自动项在前;手写项同 prop 覆盖自动项,避免多控件共绑 form[prop] */
78
+ function mergeSearchItems(auto: CrudSearchItem[], manual: CrudSearchItem[]) {
79
+ const manualProps = new Set(manual.map((i) => i.prop).filter(Boolean))
80
+ const merged = [
81
+ ...auto.filter((i) => i.prop && !manualProps.has(i.prop)),
82
+ ...manual.filter((i) => i.prop),
83
+ ]
84
+ return merged
85
+ }
86
+
87
+ const items = computed(() => mergeSearchItems(autoItems.value, manualItems.value))
88
+
89
+ const form = reactive<Record<string, unknown>>({})
90
+
91
+ function isRange(item: CrudSearchItem) {
92
+ return item.type === 'number-range' || item.type === 'daterange' || Boolean(item.range)
93
+ }
94
+
95
+ function emptyValue(item: CrudSearchItem) {
96
+ if (isRange(item)) return [null, null]
97
+ if (item.multiple) return []
98
+ return ''
99
+ }
100
+
101
+ function coerceSelect(item: CrudSearchItem, v: unknown) {
102
+ const s = v == null ? '' : String(v)
103
+ if (!s) return ''
104
+ const hit = item.options?.find((o) => String(o.value) === s)
105
+ return hit ? hit.value : s
106
+ }
107
+
108
+ async function loadDictOptions(list: CrudSearchItem[]) {
109
+ const keys = [
110
+ ...new Set(list.map((i) => i.dict).filter((d): d is string => Boolean(d))),
111
+ ]
112
+ if (!keys.length) return
113
+ const dictApi = (service as { base?: { dictInfo?: { data?: (b: { types: string[] }) => Promise<unknown> } } })
114
+ .base?.dictInfo?.data
115
+ if (!dictApi) return
116
+ try {
117
+ const res = (await dictApi({ types: keys })) as Record<
118
+ string,
119
+ Array<{ name?: string; value?: unknown }>
120
+ >
121
+ for (const it of list) {
122
+ if (!it.dict || !res?.[it.dict]) continue
123
+ it.options = res[it.dict]!.map((row) => {
124
+ let value: string | number | boolean = row.value as string | number | boolean
125
+ if (typeof row.value === 'string' && /^-?\d+(\.\d+)?$/.test(row.value)) {
126
+ value = Number(row.value)
127
+ }
128
+ return { label: String(row.name ?? row.value ?? ''), value }
129
+ })
130
+ }
131
+ } catch {
132
+ /* 字典拉取失败时保留空 options */
133
+ }
134
+ }
135
+
136
+ onMounted(async () => {
137
+ const plugins = getCrudStyle().search.plugins as TablePlugin[]
138
+ const auto = plugins?.find((p) => p?.__plugin === 'setAuto')
139
+ if (auto) {
140
+ const svc = crud.service as { namespace?: string; search?: Record<string, unknown> }
141
+ autoItems.value = buildAutoSearchItems(svc, {
142
+ hideLabel: auto.hideLabel !== false,
143
+ ignoreFields: auto.ignoreFields as string[] | undefined,
144
+ })
145
+ }
146
+ await loadDictOptions(items.value)
147
+ })
148
+
149
+ watch(
150
+ items,
151
+ (list) => {
152
+ for (const it of list) {
153
+ if (!(it.prop in form)) form[it.prop] = emptyValue(it)
154
+ }
155
+ const active = list.length > 0
156
+ crud.hasSearchFilters.value = active
157
+ crud.searchActions.value = active ? { search, reset } : null
158
+ },
159
+ { immediate: true, deep: true },
160
+ )
161
+
162
+ function resolveField(item: CrudSearchItem) {
163
+ const name = item.component?.name
164
+ if (typeof name === 'string') {
165
+ const comp = resolveCrudComponent(name)
166
+ if (comp) return comp
167
+ } else if (name) {
168
+ return name
169
+ }
170
+ if (item.type === 'select' && item.multiple) {
171
+ return resolveCrudComponent('vm-multi-select')
172
+ }
173
+ if (item.type === 'daterange') {
174
+ return resolveCrudComponent('vm-date-range')
175
+ }
176
+ if (item.type === 'date') {
177
+ return resolveCrudComponent('vm-date-picker')
178
+ }
179
+ if (item.type === 'number-range') {
180
+ return resolveCrudComponent('vm-number-range')
181
+ }
182
+ if (item.type === 'select') return undefined
183
+ return undefined
184
+ }
185
+
186
+ function fieldProps(item: CrudSearchItem) {
187
+ return {
188
+ options: item.options,
189
+ placeholder: item.placeholder || item.label,
190
+ refreshOnChange: false,
191
+ precision: item.range?.rangeType,
192
+ mode: item.range?.rangeType === 'float' || item.range?.rangeType === 'int'
193
+ ? item.range.rangeType
194
+ : undefined,
195
+ ...(item.component?.props || {}),
196
+ }
197
+ }
198
+
199
+ function search() {
200
+ const payload: Record<string, unknown> = {}
201
+ for (const it of items.value) {
202
+ const v = form[it.prop]
203
+ if (it.range) {
204
+ const pair = Array.isArray(v) ? v : [null, null]
205
+ if (pair[0] != null && pair[0] !== '') payload[it.range.min] = pair[0]
206
+ if (pair[1] != null && pair[1] !== '') payload[it.range.max] = pair[1]
207
+ continue
208
+ }
209
+ if (it.multiple) {
210
+ if (Array.isArray(v) && v.length) payload[it.prop] = v
211
+ continue
212
+ }
213
+ if (v !== '' && v != null) payload[it.prop] = v
214
+ }
215
+ crud.setParams(payload, true)
216
+ }
217
+
218
+ function reset() {
219
+ for (const it of items.value) {
220
+ form[it.prop] = emptyValue(it)
221
+ }
222
+ crud.resetParams()
223
+ }
224
+
225
+ onBeforeUnmount(() => {
226
+ crud.hasSearchFilters.value = false
227
+ if (crud.searchActions.value?.search === search) {
228
+ crud.searchActions.value = null
229
+ }
230
+ })
231
+
232
+ defineExpose({ form, search, reset })
233
+ </script>
234
+
235
+ <style lang="scss" scoped>
236
+ .vm-crud-search {
237
+ --vm-search-gap: 12px;
238
+ container-type: inline-size;
239
+ container-name: vm-crud-search;
240
+
241
+ display: flex;
242
+ width: 100%;
243
+ min-width: 0;
244
+ flex-wrap: wrap;
245
+ align-items: flex-start;
246
+ gap: var(--vm-search-gap);
247
+ margin-bottom: 6px;
248
+ }
249
+
250
+ .vm-crud-search__filters {
251
+ display: grid;
252
+ flex: 1 1 auto;
253
+ /* 默认 2 列;随容器变宽升到 8 列,单列始终 ≥140 */
254
+ grid-template-columns: repeat(2, minmax(0, 1fr));
255
+ gap: var(--vm-search-gap);
256
+ min-width: 0;
257
+ align-items: center;
258
+ }
259
+
260
+ /* n * 140 + (n-1) * 12 = 152n - 12 */
261
+ @container vm-crud-search (min-width: 444px) {
262
+ .vm-crud-search__filters {
263
+ grid-template-columns: repeat(3, minmax(0, 1fr));
264
+ }
265
+ }
266
+
267
+ @container vm-crud-search (min-width: 596px) {
268
+ .vm-crud-search__filters {
269
+ grid-template-columns: repeat(4, minmax(0, 1fr));
270
+ }
271
+ }
272
+
273
+ @container vm-crud-search (min-width: 748px) {
274
+ .vm-crud-search__filters {
275
+ grid-template-columns: repeat(5, minmax(0, 1fr));
276
+ }
277
+ }
278
+
279
+ @container vm-crud-search (min-width: 900px) {
280
+ .vm-crud-search__filters {
281
+ grid-template-columns: repeat(6, minmax(0, 1fr));
282
+ }
283
+ }
284
+
285
+ @container vm-crud-search (min-width: 1052px) {
286
+ .vm-crud-search__filters {
287
+ grid-template-columns: repeat(7, minmax(0, 1fr));
288
+ }
289
+ }
290
+
291
+ @container vm-crud-search (min-width: 1204px) {
292
+ .vm-crud-search__filters {
293
+ grid-template-columns: repeat(8, minmax(0, 1fr));
294
+ }
295
+ }
296
+
297
+ .vm-crud-search__actions {
298
+ display: flex;
299
+ flex: 0 0 auto;
300
+ flex-wrap: wrap;
301
+ align-items: center;
302
+ gap: var(--vm-search-gap);
303
+ }
304
+
305
+ :deep(.vm-crud-search__control) {
306
+ box-sizing: border-box;
307
+ width: 100% !important;
308
+ min-width: 0 !important;
309
+ max-width: none !important;
310
+ height: var(--vm-control-height) !important;
311
+ border-radius: 12px !important;
312
+ box-shadow: none !important;
313
+
314
+ &:not(.is-range) {
315
+ border: 1px solid transparent !important;
316
+ background: var(--muted) !important;
317
+
318
+ &:focus,
319
+ &:focus-visible {
320
+ border-color: var(--focus-border) !important;
321
+ box-shadow: none !important;
322
+ }
323
+ }
324
+
325
+ /* 区间占两列:2 * 列宽 + 12px */
326
+ &.is-range {
327
+ width: 100% !important;
328
+ min-width: 0 !important;
329
+ max-width: none !important;
330
+ height: var(--vm-control-height) !important;
331
+ grid-column: span 2;
332
+ padding: 0 10px !important;
333
+ border: 1px solid transparent !important;
334
+ border-radius: 12px !important;
335
+ background: var(--muted) !important;
336
+ }
337
+ }
338
+
339
+ .vm-crud-search__btn {
340
+ height: var(--vm-control-height);
341
+ padding: 0 14px;
342
+ border: none;
343
+ border-radius: 12px;
344
+ font-size: 13px;
345
+ font-weight: 600;
346
+ cursor: pointer;
347
+ transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;
348
+
349
+ &.is-primary {
350
+ background: var(--brand);
351
+ color: #fff;
352
+
353
+ &:hover {
354
+ background: var(--brand-deep);
355
+ }
356
+ }
357
+
358
+ &.is-ghost {
359
+ background: var(--muted);
360
+ color: var(--foreground);
361
+
362
+ &:hover {
363
+ background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
364
+ }
365
+ }
366
+ }
367
+ </style>