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,196 @@
1
+ <template>
2
+ <div>
3
+ <Button size="sm" variant="outline" class="h-8" @click="open = true">
4
+ 自定义列
5
+ </Button>
6
+ <vm-dialog v-model:open="open" title="自定义列" width="420px">
7
+ <div class="vm-col-custom">
8
+ <label
9
+ v-for="(item, i) in list"
10
+ :key="String(item.prop)"
11
+ class="vm-col-custom__item"
12
+ >
13
+ <input v-model="item.checked" type="checkbox" />
14
+ <span>{{ item.label }}</span>
15
+ <span class="vm-col-custom__move">
16
+ <button type="button" :disabled="i === 0" @click.prevent="move(i, -1)">↑</button>
17
+ <button
18
+ type="button"
19
+ :disabled="i === list.length - 1"
20
+ @click.prevent="move(i, 1)"
21
+ >
22
+
23
+ </button>
24
+ </span>
25
+ </label>
26
+ </div>
27
+ <template #footer>
28
+ <button type="button" class="vm-col-custom__btn is-ghost" @click="open = false">
29
+ 取消
30
+ </button>
31
+ <button type="button" class="vm-col-custom__btn is-danger" @click="reset">
32
+ 重置
33
+ </button>
34
+ <button type="button" class="vm-col-custom__btn is-primary" @click="save">
35
+ 保存
36
+ </button>
37
+ </template>
38
+ </vm-dialog>
39
+ </div>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import { injectTableOptions, TABLE_API_KEY } from '../key'
44
+ defineOptions({ name: 'vm-column-custom' })
45
+
46
+ const props = defineProps<{
47
+ name?: string
48
+ columns?: CrudColumn[]
49
+ }>()
50
+
51
+ const emit = defineEmits<{
52
+ change: [cols: CrudColumn[]]
53
+ 'update:modelValue': [cols: CrudColumn[]]
54
+ }>()
55
+
56
+ const tableApi = inject(TABLE_API_KEY, null)
57
+ const tableOpts = injectTableOptions()
58
+
59
+ type Item = { label: string; prop: string; checked: boolean }
60
+
61
+ const open = ref(false)
62
+ const list = ref<Item[]>([])
63
+ const storageKey = computedKey()
64
+
65
+ function computedKey() {
66
+ return `column-custom__${props.name || (typeof location !== 'undefined' ? location.pathname : 'default')}`
67
+ }
68
+
69
+ function sourceColumns(): CrudColumn[] {
70
+ if (props.columns?.length) return props.columns
71
+ return tableApi?.getColumns() || tableOpts?.columns || []
72
+ }
73
+
74
+ function buildList() {
75
+ const saved = load()
76
+ list.value = sourceColumns()
77
+ .filter((c) => c.prop && c.type !== 'selection' && c.type !== 'op')
78
+ .map((c) => ({
79
+ label: c.label || String(c.prop),
80
+ prop: String(c.prop),
81
+ checked: saved?.[c.prop!]?.checked ?? !c.hidden,
82
+ }))
83
+ }
84
+
85
+ function load(): Record<string, { checked: boolean; order?: number }> | null {
86
+ try {
87
+ return JSON.parse(localStorage.getItem(storageKey) || 'null')
88
+ } catch {
89
+ return null
90
+ }
91
+ }
92
+
93
+ watch(
94
+ () => [props.columns, tableOpts?.columns, open.value] as const,
95
+ () => buildList(),
96
+ { immediate: true, deep: true },
97
+ )
98
+
99
+ function move(i: number, delta: number) {
100
+ const j = i + delta
101
+ if (j < 0 || j >= list.value.length) return
102
+ const arr = [...list.value]
103
+ const t = arr[i]!
104
+ arr[i] = arr[j]!
105
+ arr[j] = t
106
+ list.value = arr
107
+ }
108
+
109
+ function reset() {
110
+ localStorage.removeItem(storageKey)
111
+ buildList()
112
+ }
113
+
114
+ function save() {
115
+ const map: Record<string, { checked: boolean; order: number }> = {}
116
+ list.value.forEach((it, order) => {
117
+ map[it.prop] = { checked: it.checked, order }
118
+ })
119
+ localStorage.setItem(storageKey, JSON.stringify(map))
120
+ const cols = sourceColumns()
121
+ const ordered = [...cols].sort((a, b) => {
122
+ const ao = a.prop != null ? map[a.prop]?.order ?? 999 : 999
123
+ const bo = b.prop != null ? map[b.prop]?.order ?? 999 : 999
124
+ return ao - bo
125
+ })
126
+ const next = ordered.map((c) => {
127
+ if (!c.prop || c.type === 'selection' || c.type === 'op') return c
128
+ return { ...c, hidden: !(map[c.prop]?.checked ?? true) }
129
+ })
130
+ tableApi?.setColumns(next)
131
+ emit('change', next)
132
+ emit('update:modelValue', next)
133
+ open.value = false
134
+ }
135
+ </script>
136
+
137
+ <style lang="scss" scoped>
138
+ .vm-col-custom {
139
+ display: grid;
140
+ gap: 8px;
141
+ }
142
+
143
+ .vm-col-custom__item {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 8px;
147
+ padding: 8px 10px;
148
+ border: 1px solid var(--border);
149
+ border-radius: 10px;
150
+ font-size: 13px;
151
+ cursor: pointer;
152
+ }
153
+
154
+ .vm-col-custom__move {
155
+ margin-left: auto;
156
+ display: flex;
157
+ gap: 4px;
158
+
159
+ button {
160
+ width: 24px;
161
+ height: 24px;
162
+ border: 1px solid var(--border);
163
+ border-radius: 6px;
164
+ background: var(--card);
165
+ cursor: pointer;
166
+
167
+ &:disabled {
168
+ opacity: 0.4;
169
+ }
170
+ }
171
+ }
172
+
173
+ .vm-col-custom__btn {
174
+ height: 36px;
175
+ padding: 0 14px;
176
+ border: none;
177
+ border-radius: 12px;
178
+ font-size: 13px;
179
+ font-weight: 600;
180
+ cursor: pointer;
181
+
182
+ &.is-ghost {
183
+ background: var(--muted);
184
+ }
185
+
186
+ &.is-danger {
187
+ background: var(--danger-soft, #fceeee);
188
+ color: var(--danger, #c45b5e);
189
+ }
190
+
191
+ &.is-primary {
192
+ background: var(--brand);
193
+ color: #fff;
194
+ }
195
+ }
196
+ </style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <div
4
+ v-if="visible"
5
+ class="vm-ctx-menu vm-dialog-float"
6
+ :style="{ left: `${x}px`, top: `${y}px` }"
7
+ @click.stop
8
+ >
9
+ <button
10
+ v-for="item in items"
11
+ :key="item.key"
12
+ type="button"
13
+ class="vm-ctx-menu__item"
14
+ :disabled="item.disabled"
15
+ @click="onPick(item)"
16
+ >
17
+ {{ item.label }}
18
+ </button>
19
+ </div>
20
+ </Teleport>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ defineOptions({ name: 'vm-context-menu' })
25
+
26
+ const props = defineProps<{
27
+ items: CtxMenuItem[]
28
+ }>()
29
+
30
+ const emit = defineEmits<{
31
+ select: [key: string]
32
+ }>()
33
+
34
+ const visible = ref(false)
35
+ const x = ref(0)
36
+ const y = ref(0)
37
+
38
+ function open(ev: MouseEvent) {
39
+ ev.preventDefault()
40
+ x.value = ev.clientX
41
+ y.value = ev.clientY
42
+ visible.value = true
43
+ }
44
+
45
+ function close() {
46
+ visible.value = false
47
+ }
48
+
49
+ function onPick(item: CtxMenuItem) {
50
+ if (item.disabled) return
51
+ emit('select', item.key)
52
+ close()
53
+ }
54
+
55
+ function onDoc() {
56
+ close()
57
+ }
58
+
59
+ onMounted(() => document.addEventListener('click', onDoc))
60
+ onUnmounted(() => document.removeEventListener('click', onDoc))
61
+
62
+ defineExpose({ open, close })
63
+ </script>
64
+
65
+ <style lang="scss" scoped>
66
+ .vm-ctx-menu {
67
+ position: fixed;
68
+ z-index: 80;
69
+ min-width: 140px;
70
+ padding: 6px;
71
+ border: 1px solid var(--border);
72
+ border-radius: 12px;
73
+ background: var(--card);
74
+ box-shadow: var(--shadow-soft, 0 8px 24px rgb(0 0 0 / 8%));
75
+ }
76
+
77
+ .vm-ctx-menu__item {
78
+ display: block;
79
+ width: 100%;
80
+ padding: 8px 10px;
81
+ border: none;
82
+ border-radius: 8px;
83
+ background: transparent;
84
+ color: var(--foreground);
85
+ font-size: 13px;
86
+ text-align: left;
87
+ cursor: pointer;
88
+
89
+ &:hover:not(:disabled) {
90
+ background: var(--brand-soft);
91
+ color: var(--brand);
92
+ }
93
+
94
+ &:disabled {
95
+ opacity: 0.4;
96
+ cursor: not-allowed;
97
+ }
98
+ }
99
+ </style>
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <input
3
+ class="vm-date-picker"
4
+ :type="inputType"
5
+ :value="inner"
6
+ :disabled="disabled"
7
+ :style="{ width }"
8
+ @change="onChange"
9
+ />
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { useCrud } from '../useCrud'
14
+
15
+ defineOptions({ name: 'vm-date-picker' })
16
+
17
+ const props = withDefaults(
18
+ defineProps<{
19
+ modelValue?: string | null
20
+ type?: 'date' | 'datetime' | 'month' | 'year'
21
+ valueFormat?: string
22
+ prop?: string
23
+ width?: string
24
+ disabled?: boolean
25
+ refreshOnChange?: boolean
26
+ }>(),
27
+ {
28
+ type: 'date',
29
+ width: '160px',
30
+ refreshOnChange: true,
31
+ },
32
+ )
33
+
34
+ const emit = defineEmits<{
35
+ 'update:modelValue': [v: string]
36
+ change: [v: string]
37
+ }>()
38
+
39
+ const crud = useCrud()
40
+
41
+ const inputType = computed(() => {
42
+ if (props.type === 'datetime') return 'datetime-local'
43
+ if (props.type === 'month') return 'month'
44
+ return 'date'
45
+ })
46
+
47
+ const inner = computed(() => {
48
+ const v = props.modelValue || ''
49
+ if (props.type === 'datetime' && v.includes(' ')) return v.replace(' ', 'T').slice(0, 16)
50
+ return v
51
+ })
52
+
53
+ function onChange(e: Event) {
54
+ let v = (e.target as HTMLInputElement).value
55
+ if (props.type === 'datetime' && v) v = v.replace('T', ' ') + ':00'
56
+ emit('update:modelValue', v)
57
+ emit('change', v)
58
+ if (props.prop && props.refreshOnChange) {
59
+ crud.refresh({ page: 1, [props.prop]: v || undefined })
60
+ }
61
+ }
62
+ </script>
63
+
64
+ <style lang="scss" scoped>
65
+ .vm-date-picker {
66
+ height: var(--vm-control-height);
67
+ padding: 0 var(--vm-control-padding-x);
68
+ border: 1px solid var(--border);
69
+ border-radius: var(--vm-control-radius);
70
+ background: var(--card);
71
+ color: var(--foreground);
72
+ font-size: 13px;
73
+ }
74
+ </style>
@@ -0,0 +1,291 @@
1
+ <template>
2
+ <div class="vm-date-range" :class="{ 'is-disabled': disabled }">
3
+ <div class="vm-date-range__side" @click="open(0)">
4
+ <div class="vm-date-range__field">
5
+ <span
6
+ v-if="!display(0) && startPlaceholder"
7
+ class="vm-date-range__ph"
8
+ >{{ startPlaceholder }}</span>
9
+ <input
10
+ ref="startEl"
11
+ class="vm-date-range__input"
12
+ :class="{ 'is-empty': !display(0) }"
13
+ :type="inputType"
14
+ :step="step"
15
+ :disabled="disabled"
16
+ :value="display(0)"
17
+ @click.stop="open(0)"
18
+ @change="(e) => set(0, e)"
19
+ />
20
+ </div>
21
+ <i class="ri-calendar-line vm-date-range__icon" aria-hidden="true" />
22
+ </div>
23
+ <span class="vm-date-range__sep">-</span>
24
+ <div class="vm-date-range__side" @click="open(1)">
25
+ <div class="vm-date-range__field">
26
+ <span
27
+ v-if="!display(1) && endPlaceholder"
28
+ class="vm-date-range__ph"
29
+ >{{ endPlaceholder }}</span>
30
+ <input
31
+ ref="endEl"
32
+ class="vm-date-range__input"
33
+ :class="{ 'is-empty': !display(1) }"
34
+ :type="inputType"
35
+ :step="step"
36
+ :disabled="disabled"
37
+ :value="display(1)"
38
+ @click.stop="open(1)"
39
+ @change="(e) => set(1, e)"
40
+ />
41
+ </div>
42
+ <i class="ri-calendar-line vm-date-range__icon" aria-hidden="true" />
43
+ </div>
44
+ </div>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ defineOptions({ name: 'vm-date-range' })
49
+
50
+ const props = withDefaults(
51
+ defineProps<{
52
+ modelValue?: Array<string | null>
53
+ /** 时间精度:year | month | day | hour | minute | second */
54
+ precision?: 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'
55
+ startPlaceholder?: string
56
+ endPlaceholder?: string
57
+ disabled?: boolean
58
+ }>(),
59
+ {
60
+ precision: 'day',
61
+ startPlaceholder: '开始时间',
62
+ endPlaceholder: '结束时间',
63
+ },
64
+ )
65
+
66
+ const emit = defineEmits<{
67
+ 'update:modelValue': [v: Array<string | null>]
68
+ change: [v: Array<string | null>]
69
+ }>()
70
+
71
+ const startEl = ref<HTMLInputElement | null>(null)
72
+ const endEl = ref<HTMLInputElement | null>(null)
73
+
74
+ const inputType = computed(() => {
75
+ const p = props.precision
76
+ if (p === 'year') return 'number'
77
+ if (p === 'month') return 'month'
78
+ if (p === 'day') return 'date'
79
+ return 'datetime-local'
80
+ })
81
+
82
+ const step = computed(() => {
83
+ const p = props.precision
84
+ if (p === 'hour') return 3600
85
+ if (p === 'minute') return 60
86
+ if (p === 'second') return 1
87
+ if (p === 'year') return 1
88
+ return undefined
89
+ })
90
+
91
+ function toApi(raw: string): string | null {
92
+ if (!raw) return null
93
+ const p = props.precision
94
+ if (p === 'year') return raw
95
+ if (p === 'month' || p === 'day') return raw
96
+ let v = raw.replace('T', ' ')
97
+ if (p === 'hour') {
98
+ v = v.length >= 13 ? `${v.slice(0, 13)}:00:00` : `${v}:00:00`
99
+ } else if (p === 'minute') {
100
+ v = v.length >= 16 ? `${v.slice(0, 16)}:00` : `${v}:00`
101
+ } else if (v.length === 16) {
102
+ v = `${v}:00`
103
+ }
104
+ return v
105
+ }
106
+
107
+ function toInput(api: string | null | undefined): string {
108
+ if (!api) return ''
109
+ const p = props.precision
110
+ if (p === 'year' || p === 'month' || p === 'day') {
111
+ return String(api).slice(0, p === 'year' ? 4 : p === 'month' ? 7 : 10)
112
+ }
113
+ const s = String(api).replace(' ', 'T')
114
+ if (p === 'hour') return s.slice(0, 13)
115
+ if (p === 'minute') return s.slice(0, 16)
116
+ return s.slice(0, 19)
117
+ }
118
+
119
+ function display(idx: 0 | 1) {
120
+ return toInput(props.modelValue?.[idx] ?? null)
121
+ }
122
+
123
+ function open(idx: 0 | 1) {
124
+ if (props.disabled) return
125
+ const el = idx === 0 ? startEl.value : endEl.value
126
+ if (!el) return
127
+ try {
128
+ el.showPicker?.()
129
+ } catch {
130
+ el.focus()
131
+ }
132
+ }
133
+
134
+ function set(idx: 0 | 1, e: Event) {
135
+ const raw = (e.target as HTMLInputElement).value
136
+ const next: Array<string | null> = [
137
+ props.modelValue?.[0] ?? null,
138
+ props.modelValue?.[1] ?? null,
139
+ ]
140
+ next[idx] = toApi(raw)
141
+ emit('update:modelValue', next)
142
+ emit('change', next)
143
+ }
144
+ </script>
145
+
146
+ <style lang="scss" scoped>
147
+ .vm-date-range {
148
+ display: flex;
149
+ box-sizing: border-box;
150
+ width: 100%;
151
+ height: var(--vm-control-height);
152
+ align-items: center;
153
+ padding: 0 10px;
154
+ border: 1px solid transparent;
155
+ border-radius: 12px;
156
+ background: var(--muted);
157
+ gap: 0;
158
+
159
+ &.is-disabled {
160
+ opacity: 0.55;
161
+ pointer-events: none;
162
+ }
163
+
164
+ &:focus-within {
165
+ border-color: var(--focus-border);
166
+ }
167
+ }
168
+
169
+ .vm-date-range__side {
170
+ display: flex;
171
+ min-width: 0;
172
+ flex: 1 1 0;
173
+ height: 100%;
174
+ align-items: center;
175
+ gap: 6px;
176
+ cursor: pointer;
177
+ }
178
+
179
+ .vm-date-range__field {
180
+ position: relative;
181
+ display: flex;
182
+ min-width: 0;
183
+ flex: 1 1 0;
184
+ height: 100%;
185
+ align-items: center;
186
+ justify-content: center;
187
+ }
188
+
189
+ .vm-date-range__ph {
190
+ position: absolute;
191
+ z-index: 0;
192
+ left: 50%;
193
+ max-width: 100%;
194
+ overflow: hidden;
195
+ color: var(--muted-foreground);
196
+ font-size: 12px;
197
+ text-align: center;
198
+ text-overflow: ellipsis;
199
+ white-space: nowrap;
200
+ pointer-events: none;
201
+ transform: translateX(-50%);
202
+ }
203
+
204
+ .vm-date-range__icon {
205
+ display: inline-flex;
206
+ flex: 0 0 auto;
207
+ width: 14px;
208
+ height: 14px;
209
+ align-items: center;
210
+ justify-content: center;
211
+ color: var(--muted-foreground);
212
+ font-size: 14px;
213
+ line-height: 1;
214
+ pointer-events: none;
215
+ }
216
+
217
+ .vm-date-range__input {
218
+ position: relative;
219
+ z-index: 1;
220
+ box-sizing: border-box;
221
+ width: 100%;
222
+ min-width: 0;
223
+ height: 100%;
224
+ padding: 0;
225
+ border: none;
226
+ background: transparent !important;
227
+ color: var(--foreground);
228
+ font-size: 12px;
229
+ text-align: center;
230
+ outline: none;
231
+ cursor: pointer;
232
+ appearance: textfield;
233
+ color-scheme: inherit;
234
+
235
+ &::-webkit-datetime-edit {
236
+ width: 100%;
237
+ padding: 0;
238
+ text-align: center;
239
+ }
240
+
241
+ &::-webkit-datetime-edit-fields-wrapper {
242
+ display: flex;
243
+ width: 100%;
244
+ justify-content: center;
245
+ padding: 0;
246
+ }
247
+
248
+ &::-webkit-date-and-time-value {
249
+ margin: 0 auto;
250
+ text-align: center;
251
+ }
252
+
253
+ &.is-empty {
254
+ color: transparent;
255
+
256
+ &::-webkit-datetime-edit,
257
+ &::-webkit-datetime-edit-fields-wrapper,
258
+ &::-webkit-datetime-edit-text,
259
+ &::-webkit-datetime-edit-year-field,
260
+ &::-webkit-datetime-edit-month-field,
261
+ &::-webkit-datetime-edit-day-field,
262
+ &::-webkit-datetime-edit-hour-field,
263
+ &::-webkit-datetime-edit-minute-field,
264
+ &::-webkit-datetime-edit-second-field,
265
+ &::-webkit-datetime-edit-ampm-field {
266
+ color: transparent;
267
+ }
268
+ }
269
+
270
+ &::-webkit-calendar-picker-indicator {
271
+ position: absolute;
272
+ inset: 0;
273
+ width: 100%;
274
+ height: 100%;
275
+ margin: 0;
276
+ padding: 0;
277
+ cursor: pointer;
278
+ opacity: 0;
279
+ }
280
+ }
281
+
282
+ .vm-date-range__sep {
283
+ flex: 0 0 auto;
284
+ padding: 0 8px;
285
+ color: var(--muted-foreground);
286
+ font-size: 13px;
287
+ font-weight: 600;
288
+ line-height: 1;
289
+ user-select: none;
290
+ }
291
+ </style>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <span class="vm-date-text">{{ text }}</span>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ defineOptions({ name: 'vm-date-text' })
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ modelValue?: string | number | Date | null
11
+ format?: string
12
+ }>(),
13
+ { format: 'YYYY-MM-DD HH:mm:ss' },
14
+ )
15
+
16
+ function pad(n: number) {
17
+ return String(n).padStart(2, '0')
18
+ }
19
+
20
+ const text = computed(() => {
21
+ if (props.modelValue == null || props.modelValue === '') return '—'
22
+ const d = new Date(props.modelValue)
23
+ if (Number.isNaN(d.getTime())) return String(props.modelValue)
24
+ const map: Record<string, string> = {
25
+ YYYY: String(d.getFullYear()),
26
+ MM: pad(d.getMonth() + 1),
27
+ DD: pad(d.getDate()),
28
+ HH: pad(d.getHours()),
29
+ mm: pad(d.getMinutes()),
30
+ ss: pad(d.getSeconds()),
31
+ }
32
+ return props.format.replace(/YYYY|MM|DD|HH|mm|ss/g, (k) => map[k] || k)
33
+ })
34
+ </script>
35
+
36
+ <style lang="scss" scoped>
37
+ .vm-date-text {
38
+ font-size: 13px;
39
+ color: var(--foreground);
40
+ }
41
+ </style>