vome-core 0.0.67 → 0.0.69

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 (85) hide show
  1. package/dist/admin/config/plugin-dev.js +1 -1
  2. package/dist/admin/crud/components/vm-json-editor.vue +32 -3
  3. package/dist/admin/crud/config.js +1 -1
  4. package/dist/admin/crud/confirm.js +1 -1
  5. package/dist/admin/crud/dict.js +1 -1
  6. package/dist/admin/crud/index.d.ts +1 -1
  7. package/dist/admin/crud/index.js +1 -1
  8. package/dist/admin/crud/key.js +1 -1
  9. package/dist/admin/crud/mitt.js +1 -1
  10. package/dist/admin/crud/plugins.js +1 -1
  11. package/dist/admin/crud/span.js +1 -1
  12. package/dist/admin/crud/style.d.ts +37 -0
  13. package/dist/admin/crud/style.js +1 -1
  14. package/dist/admin/crud/validate.js +1 -1
  15. package/dist/admin/crud/vm-add-btn.vue +2 -1
  16. package/dist/admin/crud/vm-adv-search.vue +5 -4
  17. package/dist/admin/crud/vm-crud.vue +14 -9
  18. package/dist/admin/crud/vm-export-btn.vue +2 -1
  19. package/dist/admin/crud/vm-import-btn.vue +2 -1
  20. package/dist/admin/crud/vm-multi-delete-btn.vue +2 -1
  21. package/dist/admin/crud/vm-pagination.vue +16 -7
  22. package/dist/admin/crud/vm-refresh-btn.vue +2 -1
  23. package/dist/admin/crud/vm-search-key.vue +3 -2
  24. package/dist/admin/crud/vm-search.vue +29 -20
  25. package/dist/admin/crud/vm-table.vue +55 -17
  26. package/dist/admin/crud/vm-toolbar.vue +12 -13
  27. package/dist/admin/crud/vm-upsert.vue +12 -6
  28. package/dist/admin/directives/perm.js +1 -1
  29. package/dist/admin/hooks/useUpload.js +1 -1
  30. package/dist/admin/index.d.ts +1 -1
  31. package/dist/admin/index.js +6 -1
  32. package/dist/admin/lib/browser.js +1 -1
  33. package/dist/admin/lib/cn.js +1 -1
  34. package/dist/admin/lib/dialog-float.js +1 -1
  35. package/dist/admin/lib/export-excel.js +1 -1
  36. package/dist/admin/lib/file-preview.js +1 -1
  37. package/dist/admin/lib/import-excel.js +1 -1
  38. package/dist/admin/lib/json.js +1 -1
  39. package/dist/admin/lib/menu.js +1 -1
  40. package/dist/admin/lib/tree.js +1 -1
  41. package/dist/admin/lib/upload.js +1 -1
  42. package/dist/admin/lib/video-frame.js +1 -1
  43. package/dist/admin/styles/base.css +33 -0
  44. package/dist/ai/index.js +28761 -0
  45. package/dist/ai.d.ts +1 -0
  46. package/dist/client/base.js +3 -1
  47. package/dist/client/index.d.ts +1 -1
  48. package/dist/client/index.js +3 -5
  49. package/dist/client/locale-store.d.ts +59 -0
  50. package/dist/client/locale-store.js +159 -0
  51. package/dist/client/vite-plugin-eps.js +0 -21
  52. package/dist/index.js +1 -1
  53. package/dist/server/index.js +1 -1
  54. package/dist/shared/excel.js +1 -1
  55. package/dist/shared/index.js +1 -1
  56. package/dist/shared/locale-json.js +1 -0
  57. package/dist/shared/tree.js +1 -1
  58. package/dist/src/ai/adapters/anthropic-gemini.d.ts +5 -0
  59. package/dist/src/ai/adapters/auth-headers.d.ts +4 -0
  60. package/dist/src/ai/adapters/index.d.ts +3 -0
  61. package/dist/src/ai/adapters/openai-compatible.d.ts +4 -0
  62. package/dist/src/ai/call-log-helpers.d.ts +13 -0
  63. package/dist/src/ai/index.d.ts +10 -0
  64. package/dist/src/ai/normalize.d.ts +18 -0
  65. package/dist/src/ai/sse.d.ts +13 -0
  66. package/dist/src/ai/transport.d.ts +16 -0
  67. package/dist/src/ai/types.d.ts +194 -0
  68. package/dist/src/core/index.d.ts +1 -1
  69. package/dist/src/index.d.ts +1 -0
  70. package/dist/src/orm/index.d.ts +1 -1
  71. package/dist/src/server/index.d.ts +3 -1
  72. package/dist/src/shared/index.d.ts +1 -0
  73. package/dist/src/shared/locale-json.d.ts +7 -0
  74. package/dist/typings/routing/eps.d.ts +2 -0
  75. package/package.json +7 -2
  76. package/src/shared/index.ts +6 -0
  77. package/src/shared/locale-json.ts +79 -0
  78. package/typings/admin/comm/crud.d.ts +12 -0
  79. package/typings/admin/comm/crud.ts +12 -0
  80. package/typings/admin/comm/eps.d.ts +3 -0
  81. package/typings/admin/comm/eps.ts +3 -0
  82. package/typings/admin/comm/request.d.ts +1 -1
  83. package/typings/admin/comm/request.ts +1 -1
  84. package/dist/client/ai-model.d.ts +0 -38
  85. package/dist/client/ai-model.js +0 -39
@@ -2,7 +2,7 @@
2
2
  <div class="vm-adv-search">
3
3
  <Button size="sm" variant="outline" class="h-8" @click="open = true">
4
4
  <i class="ri-filter-3-line" />
5
- {{ crud.dict.label.advSearch }}
5
+ {{ crudLabel('advSearch', crud.dict.label) }}
6
6
  </Button>
7
7
  <Dialog :open="open" @update:open="onOpenChange">
8
8
  <DialogContent
@@ -14,7 +14,7 @@
14
14
  >
15
15
  <DialogHeader class="vm-crud-shell__head">
16
16
  <DialogTitle class="vm-crud-shell__title">
17
- {{ crud.dict.label.advSearch }}
17
+ {{ crudLabel('advSearch', crud.dict.label) }}
18
18
  </DialogTitle>
19
19
  </DialogHeader>
20
20
  <div class="vm-crud-shell__body">
@@ -26,14 +26,14 @@
26
26
  class="vm-crud-shell__btn is-ghost"
27
27
  @click="reset"
28
28
  >
29
- {{ crud.dict.label.reset }}
29
+ {{ crudLabel('reset', crud.dict.label) }}
30
30
  </button>
31
31
  <button
32
32
  type="button"
33
33
  class="vm-crud-shell__btn is-primary"
34
34
  @click="search"
35
35
  >
36
- {{ crud.dict.label.search }}
36
+ {{ crudLabel('search', crud.dict.label) }}
37
37
  </button>
38
38
  </DialogFooter>
39
39
  </DialogContent>
@@ -45,6 +45,7 @@
45
45
  import { ref } from 'vue'
46
46
  import { preventDialogOutsideClose } from '../lib/dialog-float'
47
47
  import { useCrud } from './useCrud'
48
+ import { crudLabel } from './style'
48
49
  defineOptions({ name: 'vm-adv-search' })
49
50
 
50
51
  const props = withDefaults(
@@ -12,6 +12,7 @@ import { mergeDict, mergePermission } from './config'
12
12
  import { vmConfirm } from './confirm'
13
13
  import { CRUD_KEY, CRUD_MITT_KEY, injectCrudOptions, injectTableOptions, injectUpsertOptions } from './key'
14
14
  import { createMitt } from './mitt'
15
+ import { crudLabel } from './style'
15
16
  defineOptions({ name: 'vm-crud' })
16
17
 
17
18
  const props = withDefaults(
@@ -283,7 +284,9 @@ async function doDelete(targets: Record<string, unknown>[], force = false) {
283
284
  .map((r) => r[dict.primaryId] as string | number)
284
285
  .filter((id) => id != null)
285
286
  await svc.delete({ ids, force })
286
- toast.success(force ? '已彻底删除' : dict.label.deleteSuccess || '已删除')
287
+ toast.success(
288
+ force ? crudLabel('forceDeleted', dict.label) : crudLabel('deleteSuccess', dict.label),
289
+ )
287
290
  await refresh()
288
291
  }
289
292
 
@@ -305,7 +308,9 @@ async function rowDelete(rows?: Record<string, unknown>[], force = false) {
305
308
  ...(payload as { ids: Array<string | number> }),
306
309
  force: hard,
307
310
  })
308
- toast.success(hard ? '已彻底删除' : dict.label.deleteSuccess || '已删除')
311
+ toast.success(
312
+ hard ? crudLabel('forceDeleted', dict.label) : crudLabel('deleteSuccess', dict.label),
313
+ )
309
314
  await refresh()
310
315
  return
311
316
  }
@@ -318,13 +323,13 @@ async function rowDelete(rows?: Record<string, unknown>[], force = false) {
318
323
  }
319
324
 
320
325
  const tip = hard
321
- ? dict.label.forceDeleteConfirm || '此操作将永久删除选中数据,是否继续?'
322
- : dict.label.deleteConfirm || '确定删除?'
326
+ ? crudLabel('forceDeleteConfirm', dict.label)
327
+ : crudLabel('deleteConfirm', dict.label)
323
328
  const ok = await vmConfirm({
324
- title: hard ? '彻底删除' : '删除确认',
329
+ title: hard ? crudLabel('forceDelete', dict.label) : crudLabel('tips', dict.label),
325
330
  message: tip,
326
- confirmText: '确定',
327
- cancelText: '取消',
331
+ confirmText: crudLabel('confirm', dict.label),
332
+ cancelText: crudLabel('close', dict.label),
328
333
  })
329
334
  if (!ok) return
330
335
  await next()
@@ -362,11 +367,11 @@ async function upsertSubmit() {
362
367
  if (upsertMode.value === 'add') {
363
368
  if (!svc.add) throw new Error('未配置 add')
364
369
  await svc.add(data)
365
- toast.success(dict.label.saveSuccess || '已新增')
370
+ toast.success(crudLabel('saveSuccess', dict.label))
366
371
  } else {
367
372
  if (!svc.update) throw new Error('未配置 update')
368
373
  await svc.update(data)
369
- toast.success(dict.label.saveSuccess || '已更新')
374
+ toast.success(crudLabel('saveSuccess', dict.label))
370
375
  }
371
376
  const after = async () => {
372
377
  upsertVisible.value = false
@@ -25,6 +25,7 @@ import {
25
25
  type TableApi,
26
26
  } from './key'
27
27
  import { useCrud } from './useCrud'
28
+ import { crudLabel } from './style'
28
29
 
29
30
  defineOptions({ name: 'vm-export-btn' })
30
31
 
@@ -59,7 +60,7 @@ const submitting = ref(false)
59
60
  const visible = computed(() => Boolean(crud.getPermission(props.permission)))
60
61
 
61
62
  const buttonText = computed(
62
- () => props.text || crud.dict.label.export || '导出',
63
+ () => props.text || crudLabel('export', crud.dict.label),
63
64
  )
64
65
 
65
66
  const exportColumns = computed(() => {
@@ -167,6 +167,7 @@ import {
167
167
  uploadImportExcel,
168
168
  } from '../lib/import-excel'
169
169
  import { useCrud } from './useCrud'
170
+ import { crudLabel } from './style'
170
171
 
171
172
  defineOptions({ name: 'vm-import-btn' })
172
173
 
@@ -215,7 +216,7 @@ const dragDepth = ref(0)
215
216
  const visible = computed(() => Boolean(crud.getPermission(props.permission)))
216
217
 
217
218
  const buttonText = computed(
218
- () => props.text || crud.dict.label.import || '导入',
219
+ () => props.text || crudLabel('import', crud.dict.label),
219
220
  )
220
221
 
221
222
  const totalPages = computed(() =>
@@ -7,12 +7,13 @@
7
7
  @click="crud.rowDelete()"
8
8
  >
9
9
  <i class="ri-delete-bin-line" />
10
- {{ crud.dict.label.multiDelete }}
10
+ {{ crudLabel('multiDelete', crud.dict.label) }}
11
11
  </button>
12
12
  </template>
13
13
 
14
14
  <script setup lang="ts">
15
15
  import { useCrud } from './useCrud'
16
+ import { crudLabel } from './style'
16
17
 
17
18
  defineOptions({ name: 'vm-multi-delete-btn' })
18
19
  const crud = useCrud()
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="vm-crud-pagination">
3
- <span class="vm-crud-pagination__total">共 {{ crud.pagination.value.total }} 条</span>
3
+ <span class="vm-crud-pagination__total">{{ totalText }}</span>
4
4
  <div class="vm-crud-pagination__actions">
5
5
  <Select
6
6
  :model-value="String(crud.pagination.value.size)"
@@ -10,10 +10,9 @@
10
10
  <SelectValue />
11
11
  </SelectTrigger>
12
12
  <SelectContent>
13
- <SelectItem value="10">10 / 页</SelectItem>
14
- <SelectItem value="20">20 / 页</SelectItem>
15
- <SelectItem value="50">50 / 页</SelectItem>
16
- <SelectItem value="100">100 / 页</SelectItem>
13
+ <SelectItem v-for="n in pageSizes" :key="n" :value="String(n)">
14
+ {{ pageSizeText(n) }}
15
+ </SelectItem>
17
16
  </SelectContent>
18
17
  </Select>
19
18
  <button
@@ -22,7 +21,7 @@
22
21
  :disabled="crud.pagination.value.page <= 1"
23
22
  @click="go(crud.pagination.value.page - 1)"
24
23
  >
25
- 上一页
24
+ {{ crudLabel('prevPage', crud.dict.label) }}
26
25
  </button>
27
26
  <span class="vm-crud-pagination__page">
28
27
  {{ crud.pagination.value.page }} / {{ totalPages }}
@@ -33,7 +32,7 @@
33
32
  :disabled="crud.pagination.value.page >= totalPages"
34
33
  @click="go(crud.pagination.value.page + 1)"
35
34
  >
36
- 下一页
35
+ {{ crudLabel('nextPage', crud.dict.label) }}
37
36
  </button>
38
37
  </div>
39
38
  </div>
@@ -42,15 +41,25 @@
42
41
  <script setup lang="ts">
43
42
  import { computed } from 'vue'
44
43
  import { useCrud } from './useCrud'
44
+ import { crudFormat, crudLabel } from './style'
45
45
 
46
46
  defineOptions({ name: 'vm-pagination' })
47
47
 
48
48
  const crud = useCrud()
49
+ const pageSizes = [10, 20, 50, 100]
49
50
 
50
51
  const totalPages = computed(() =>
51
52
  Math.max(1, Math.ceil(crud.pagination.value.total / crud.pagination.value.size) || 1),
52
53
  )
53
54
 
55
+ const totalText = computed(() =>
56
+ crudFormat(crudLabel('total', crud.dict.label), crud.pagination.value.total),
57
+ )
58
+
59
+ function pageSizeText(n: number) {
60
+ return crudFormat(crudLabel('pageSize', crud.dict.label), n)
61
+ }
62
+
54
63
  function go(page: number) {
55
64
  const next = Math.min(Math.max(1, page), totalPages.value)
56
65
  crud.pagination.value = { ...crud.pagination.value, page: next }
@@ -1,12 +1,13 @@
1
1
  <template>
2
2
  <button type="button" class="vm-crud-toolbar__btn is-ghost" @click="crud.refresh()">
3
3
  <i class="ri-refresh-line" />
4
- {{ crud.dict.label.refresh }}
4
+ {{ crudLabel('refresh', crud.dict.label) }}
5
5
  </button>
6
6
  </template>
7
7
 
8
8
  <script setup lang="ts">
9
9
  import { useCrud } from './useCrud'
10
+ import { crudLabel } from './style'
10
11
 
11
12
  defineOptions({ name: 'vm-refresh-btn' })
12
13
  const crud = useCrud()
@@ -3,12 +3,12 @@
3
3
  <Input
4
4
  class="vm-crud-search-key__input"
5
5
  :model-value="keyword"
6
- :placeholder="placeholder || crud.dict.label.searchKey"
6
+ :placeholder="placeholder || crudLabel('searchKey', crud.dict.label)"
7
7
  @update:model-value="onInput"
8
8
  @keydown.enter.prevent="search"
9
9
  />
10
10
  <button type="button" class="vm-crud-search-key__btn" @click="search">
11
- {{ crud.dict.label.search }}
11
+ {{ crudLabel('search', crud.dict.label) }}
12
12
  </button>
13
13
  </div>
14
14
  </template>
@@ -16,6 +16,7 @@
16
16
  <script setup lang="ts">
17
17
  import { ref } from 'vue'
18
18
  import { useCrud } from './useCrud'
19
+ import { crudLabel } from './style'
19
20
 
20
21
  defineOptions({ name: 'vm-search-key' })
21
22
 
@@ -17,7 +17,7 @@
17
17
  @update:model-value="(v) => (form[item.prop] = coerceSelect(item, v))"
18
18
  >
19
19
  <SelectTrigger class="vm-crud-search__control">
20
- <SelectValue :placeholder="item.placeholder || item.label" />
20
+ <SelectValue :placeholder="itemPlaceholder(item)" />
21
21
  </SelectTrigger>
22
22
  <SelectContent>
23
23
  <SelectItem
@@ -33,15 +33,19 @@
33
33
  v-else
34
34
  class="vm-crud-search__control"
35
35
  :model-value="String(form[item.prop] ?? '')"
36
- :placeholder="item.placeholder || item.label"
36
+ :placeholder="itemPlaceholder(item)"
37
37
  @update:model-value="(v) => (form[item.prop] = v)"
38
38
  @keydown.enter.prevent="search"
39
39
  />
40
40
  </template>
41
41
  </div>
42
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>
43
+ <button type="button" class="vm-crud-search__btn is-primary" @click="search">
44
+ {{ crudLabel('search', crud.dict.label) }}
45
+ </button>
46
+ <button type="button" class="vm-crud-search__btn is-ghost" @click="reset">
47
+ {{ crudLabel('reset', crud.dict.label) }}
48
+ </button>
45
49
  <slot />
46
50
  </div>
47
51
  </div>
@@ -53,7 +57,13 @@ import { onBeforeUnmount } from 'vue'
53
57
  import { useCrud } from './useCrud'
54
58
  import { injectSearchOptions } from './key'
55
59
  import { buildAutoSearchItems } from './plugins'
56
- import { getCrudStyle } from './style'
60
+ import {
61
+ getCrudStyle,
62
+ crudLocaleRev,
63
+ crudLabel,
64
+ resolveSearchPlaceholder,
65
+ resolveRangePlaceholders,
66
+ } from './style'
57
67
  import { resolveCrudComponent } from './registry'
58
68
  import { service } from '../service'
59
69
  defineOptions({ name: 'vm-search' })
@@ -85,7 +95,14 @@ function mergeSearchItems(auto: CrudSearchItem[], manual: CrudSearchItem[]) {
85
95
  return merged
86
96
  }
87
97
 
88
- const items = computed(() => mergeSearchItems(autoItems.value, manualItems.value))
98
+ const items = computed(() => {
99
+ void crudLocaleRev.value
100
+ return mergeSearchItems(autoItems.value, manualItems.value)
101
+ })
102
+
103
+ function itemPlaceholder(item: CrudSearchItem) {
104
+ return resolveSearchPlaceholder(item)
105
+ }
89
106
 
90
107
  const form = reactive<Record<string, unknown>>({})
91
108
 
@@ -194,30 +211,22 @@ function resolveField(item: CrudSearchItem) {
194
211
  }
195
212
 
196
213
  function fieldProps(item: CrudSearchItem) {
197
- const name = String(item.placeholder || item.label || '').trim()
214
+ const ph = itemPlaceholder(item)
198
215
  const rangePlaceholders =
199
- item.type === 'number-range' && name
200
- ? {
201
- startPlaceholder: `${name}最小值`,
202
- endPlaceholder: `${name}最大值`,
203
- }
204
- : item.type === 'daterange' && name
205
- ? {
206
- startPlaceholder: `${name}开始`,
207
- endPlaceholder: `${name}结束`,
208
- }
209
- : {}
216
+ item.type === 'number-range' || item.type === 'daterange' || item.phMode === 'range'
217
+ ? resolveRangePlaceholders(item)
218
+ : {}
210
219
  return {
211
220
  options: resolveOptions(item.options),
212
- placeholder: item.placeholder || item.label,
213
221
  refreshOnChange: false,
214
222
  precision: item.range?.rangeType,
215
223
  mode:
216
224
  item.range?.rangeType === 'float' || item.range?.rangeType === 'int'
217
225
  ? item.range.rangeType
218
226
  : undefined,
219
- ...rangePlaceholders,
220
227
  ...(item.component?.props || {}),
228
+ placeholder: ph,
229
+ ...rangePlaceholders,
221
230
  }
222
231
  }
223
232
 
@@ -6,7 +6,9 @@
6
6
  'is-auto-height': autoHeight,
7
7
  }"
8
8
  >
9
- <div v-if="crud.loading.value" class="vm-crud-table__loading">加载中…</div>
9
+ <div v-if="crud.loading.value" class="vm-crud-table__loading">
10
+ {{ crudLabel('loading', crud.dict.label) }}
11
+ </div>
10
12
  <div ref="scrollRef" class="vm-crud-table__scroll vm-scroll">
11
13
  <Table>
12
14
  <TableHeader>
@@ -26,7 +28,7 @@
26
28
  @click="onSortClick(col)"
27
29
  >
28
30
  <span class="vm-crud-table__th-inner">
29
- {{ col.type === 'op' ? col.label || crud.dict.label.op : col.label }}
31
+ {{ columnLabel(col) }}
30
32
  <i
31
33
  v-if="isSortable(col) && col.prop"
32
34
  class="vm-crud-table__sort"
@@ -39,7 +41,7 @@
39
41
  class="vm-crud-table__th vm-crud-table__op"
40
42
  :style="{ width: `${opWidth}px`, minWidth: `${opWidth}px` }"
41
43
  >
42
- {{ crud.dict.label.op }}
44
+ {{ crudLabel('op', crud.dict.label) }}
43
45
  </TableHead>
44
46
  </TableRow>
45
47
  </TableHeader>
@@ -85,14 +87,14 @@
85
87
  class="vm-crud-table__ops"
86
88
  >
87
89
  <Button size="xs" variant="softPrimary" @click="crud.rowRestore([row])">
88
- 恢复
90
+ {{ crudLabel('restore', crud.dict.label) }}
89
91
  </Button>
90
92
  <Button
91
93
  size="xs"
92
94
  variant="softDestructive"
93
95
  @click="crud.rowDelete([row], true)"
94
96
  >
95
- 彻底删除
97
+ {{ crudLabel('forceDelete', crud.dict.label) }}
96
98
  </Button>
97
99
  </div>
98
100
  <div v-else class="vm-crud-table__ops">
@@ -104,7 +106,7 @@
104
106
  variant="softPrimary"
105
107
  @click.stop="crud.rowEdit(row)"
106
108
  >
107
- {{ crud.dict.label.update }}
109
+ {{ crudLabel('update', crud.dict.label) }}
108
110
  </Button>
109
111
  <Button
110
112
  v-else-if="btn === 'delete' && crud.getPermission('delete')"
@@ -113,7 +115,7 @@
113
115
  variant="softDestructive"
114
116
  @click.stop="crud.rowDelete([row])"
115
117
  >
116
- {{ crud.dict.label.delete }}
118
+ {{ crudLabel('delete', crud.dict.label) }}
117
119
  </Button>
118
120
  <Button
119
121
  v-else-if="btn === 'info' && crud.getPermission('info')"
@@ -122,7 +124,7 @@
122
124
  variant="ghost"
123
125
  @click.stop="crud.rowInfo(row)"
124
126
  >
125
- {{ crud.dict.label.info }}
127
+ {{ crudLabel('info', crud.dict.label) }}
126
128
  </Button>
127
129
  <Button
128
130
  v-else-if="typeof btn === 'object' && !btn.hidden"
@@ -208,14 +210,14 @@
208
210
  class="vm-crud-table__ops"
209
211
  >
210
212
  <Button size="xs" variant="softPrimary" @click="crud.rowRestore([row])">
211
- 恢复
213
+ {{ crudLabel('restore', crud.dict.label) }}
212
214
  </Button>
213
215
  <Button
214
216
  size="xs"
215
217
  variant="softDestructive"
216
218
  @click="crud.rowDelete([row], true)"
217
219
  >
218
- 彻底删除
220
+ {{ crudLabel('forceDelete', crud.dict.label) }}
219
221
  </Button>
220
222
  </div>
221
223
  <slot v-else name="op" :row="row" />
@@ -234,7 +236,8 @@ import { useCrud } from './useCrud'
234
236
  import { injectTableOptions, TABLE_API_KEY } from './key'
235
237
  import { flattenTree } from '../lib/tree'
236
238
  import { resolveCrudComponent } from './registry'
237
- import { getCrudStyle } from './style'
239
+ import { getCrudStyle, crudLabel, crudT, crudLocaleRev } from './style'
240
+ import { findEpsEntity } from '../lib/eps'
238
241
  defineOptions({ name: 'vm-table' })
239
242
 
240
243
  const props = withDefaults(
@@ -289,6 +292,41 @@ const dataColumns = computed(() =>
289
292
  )
290
293
  const showSlotOp = computed(() => Boolean(slots.op) && !hasOpCol.value)
291
294
 
295
+ /** EPS 列 → field.{table}.{prop} */
296
+ function epsFieldKey(prop: string): string | undefined {
297
+ const ns = (crud.service as { namespace?: string } | undefined)?.namespace
298
+ if (!ns || !prop) return undefined
299
+ const entity = findEpsEntity(`/${ns}`)
300
+ if (!entity) return undefined
301
+ const cols = [
302
+ ...((entity as { columns?: Array<{ propertyName?: string; table?: string; source?: string }> }).columns || []),
303
+ ...((entity as { pageColumns?: Array<{ propertyName?: string; table?: string; source?: string }> }).pageColumns || []),
304
+ ]
305
+ const meta =
306
+ cols.find((c) => c.propertyName === prop) ||
307
+ cols.find((c) => c.source === `a.${prop}`) ||
308
+ cols.find((c) => c.source?.endsWith(`.${prop}`))
309
+ const table = meta?.table?.trim()
310
+ const name = (meta?.propertyName || prop).trim()
311
+ if (!table || !name) return undefined
312
+ return `${table}.${name}`
313
+ }
314
+
315
+ /** 表头文案:labelKey → field.* → label / op */
316
+ function columnLabel(col: CrudColumn): string {
317
+ void crudLocaleRev.value
318
+ if (col.type === 'op') {
319
+ return col.labelKey
320
+ ? crudT(col.labelKey, col.label || crudLabel('op', crud.dict.label))
321
+ : col.label || crudLabel('op', crud.dict.label)
322
+ }
323
+ const fb = col.label || col.prop || ''
324
+ if (col.labelKey) return crudT(col.labelKey, fb)
325
+ const fk = col.fieldKey || (col.prop ? epsFieldKey(col.prop) : undefined)
326
+ if (fk) return crudT(`field.${fk}`, fb)
327
+ return fb
328
+ }
329
+
292
330
  const colSpan = computed(
293
331
  () =>
294
332
  dataColumns.value.length +
@@ -408,12 +446,12 @@ const ctxItems = computed<CtxMenuItem[]>(() => {
408
446
  ? conf
409
447
  : ['refresh', 'check', 'edit', 'delete', 'order-asc', 'order-desc']
410
448
  const map: Record<string, string> = {
411
- refresh: crud.dict.label.refresh,
412
- check: crud.dict.label.select,
413
- edit: crud.dict.label.update,
414
- delete: crud.dict.label.delete,
415
- 'order-asc': crud.dict.label.asc,
416
- 'order-desc': crud.dict.label.desc,
449
+ refresh: crudLabel('refresh', crud.dict.label),
450
+ check: crudLabel('select', crud.dict.label),
451
+ edit: crudLabel('update', crud.dict.label),
452
+ delete: crudLabel('delete', crud.dict.label),
453
+ 'order-asc': crudLabel('asc', crud.dict.label),
454
+ 'order-desc': crudLabel('desc', crud.dict.label),
417
455
  }
418
456
  return keys.map((k) => ({
419
457
  key: k,
@@ -8,7 +8,7 @@
8
8
  @click="onAdd"
9
9
  >
10
10
  <i class="ri-add-line" />
11
- {{ addText || crud.dict.label.add }}
11
+ {{ addText ?? crudLabel('add', crud.dict.label) }}
12
12
  </button>
13
13
  <button
14
14
  v-if="showDelete && crud.getPermission('delete')"
@@ -18,11 +18,11 @@
18
18
  @click="crud.rowDelete()"
19
19
  >
20
20
  <i class="ri-delete-bin-line" />
21
- {{ deleteText || crud.dict.label.multiDelete }}
21
+ {{ deleteText ?? crudLabel('multiDelete', crud.dict.label) }}
22
22
  </button>
23
23
  <vm-export-btn
24
24
  v-if="showExport"
25
- :text="exportText || crud.dict.label.export"
25
+ :text="exportText || crudLabel('export', crud.dict.label)"
26
26
  :filename="exportFilename"
27
27
  :max-export-limit="maxExportLimit"
28
28
  :columns="exportColumns"
@@ -30,7 +30,7 @@
30
30
  />
31
31
  <vm-import-btn
32
32
  v-if="showImport"
33
- :text="importText || crud.dict.label.import"
33
+ :text="importText || crudLabel('import', crud.dict.label)"
34
34
  :tip="importTip"
35
35
  :template-filename="importTemplateFilename"
36
36
  :max-size-mb="importMaxSizeMb"
@@ -41,10 +41,10 @@
41
41
  <slot name="right" />
42
42
  <div v-if="showSearchActions" class="vm-crud-toolbar__search">
43
43
  <button type="button" class="vm-crud-toolbar__btn is-primary" @click="onSearch">
44
- 搜索
44
+ {{ crudLabel('search', crud.dict.label) }}
45
45
  </button>
46
46
  <button type="button" class="vm-crud-toolbar__btn is-ghost" @click="onReset">
47
- 重置
47
+ {{ crudLabel('reset', crud.dict.label) }}
48
48
  </button>
49
49
  </div>
50
50
  <vm-tabs
@@ -63,13 +63,9 @@ import { useCrud } from './useCrud'
63
63
  import VmExportBtn from './vm-export-btn.vue'
64
64
  import VmImportBtn from './vm-import-btn.vue'
65
65
  import VmTabs from './vm-tabs.vue'
66
+ import { crudLabel } from './style'
66
67
  defineOptions({ name: 'vm-toolbar' })
67
68
 
68
- const trashTabs = [
69
- { label: '列表', value: 'default' as const },
70
- { label: '回收站', value: 'onlyTrashed' as const },
71
- ]
72
-
73
69
  const props = withDefaults(
74
70
  defineProps<{
75
71
  trash?: boolean
@@ -107,8 +103,6 @@ const props = withDefaults(
107
103
  showExport: true,
108
104
  showImport: true,
109
105
  hideAddInTrash: true,
110
- addText: '新增',
111
- deleteText: '删除',
112
106
  maxExportLimit: 5000,
113
107
  importMaxSizeMb: 10,
114
108
  },
@@ -116,6 +110,11 @@ const props = withDefaults(
116
110
 
117
111
  const crud = useCrud()
118
112
 
113
+ const trashTabs = computed(() => [
114
+ { label: crudLabel('list', crud.dict.label), value: 'default' as const },
115
+ { label: crudLabel('trash', crud.dict.label), value: 'onlyTrashed' as const },
116
+ ])
117
+
119
118
  /** 是否显示新增:权限与回收站 */
120
119
  const showAddBtn = computed(() => {
121
120
  if (!props.showAdd) return false
@@ -5,6 +5,7 @@
5
5
  >
6
6
  <DialogContent
7
7
  shell="crud"
8
+ :show-close-button="false"
8
9
  class="vm-crud-upsert"
9
10
  :class="{
10
11
  'is-empty': isBodyEmpty,
@@ -17,6 +18,11 @@
17
18
  >
18
19
  <DialogHeader class="vm-crud-shell__head">
19
20
  <DialogTitle class="vm-crud-shell__title">{{ titleText }}</DialogTitle>
21
+ <slot name="title-extra" />
22
+ <DialogClose class="vm-crud-shell__close">
23
+ <i class="ri-close-line" />
24
+ <span class="sr-only">Close</span>
25
+ </DialogClose>
20
26
  </DialogHeader>
21
27
  <div
22
28
  class="vm-crud-upsert__body"
@@ -134,7 +140,7 @@ import { ref, computed, watch, useSlots } from 'vue'
134
140
  import { toast } from 'vue-sonner'
135
141
  import { useCrud } from './useCrud'
136
142
  import { injectUpsertOptions } from './key'
137
- import { getCrudStyle } from './style'
143
+ import { getCrudStyle, crudLabel } from './style'
138
144
  import { resolveCrudComponent } from './registry'
139
145
  import { applySetRules, validateFormFields } from './validate'
140
146
  import { resolveFormSpan, useFormCols } from './span'
@@ -199,9 +205,9 @@ const mergedItems = computed(() => {
199
205
  const titleText = computed(() => {
200
206
  if (props.title != null && props.title !== '') return props.title
201
207
  if (upsertOpts?.dialog?.title) return upsertOpts.dialog.title
202
- if (crud.upsertMode.value === 'add') return crud.dict.label.add
203
- if (crud.upsertMode.value === 'info') return crud.dict.label.info
204
- return crud.dict.label.update
208
+ if (crud.upsertMode.value === 'add') return crudLabel('add', crud.dict.label)
209
+ if (crud.upsertMode.value === 'info') return crudLabel('info', crud.dict.label)
210
+ return crudLabel('update', crud.dict.label)
205
211
  })
206
212
 
207
213
  const visibleItems = computed(() =>
@@ -238,13 +244,13 @@ const confirmLabel = computed(
238
244
  () =>
239
245
  props.confirmText ||
240
246
  upsertOpts?.dialog?.confirmText ||
241
- crud.dict.label.confirm,
247
+ crudLabel('confirm', crud.dict.label),
242
248
  )
243
249
  const closeLabel = computed(
244
250
  () =>
245
251
  props.closeText ||
246
252
  upsertOpts?.dialog?.closeText ||
247
- crud.dict.label.close,
253
+ crudLabel('close', crud.dict.label),
248
254
  )
249
255
  const isBodyEmpty = computed(
250
256
  () => !visibleItems.value.length && !slots.default,