vome-core 0.1.86 → 0.1.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/components/ui/avatar/index.js +1 -1
- package/dist/admin/components/ui/checkbox/index.js +1 -1
- package/dist/admin/components/ui/dialog/index.js +1 -1
- package/dist/admin/components/ui/dropdown-menu/index.js +1 -1
- package/dist/admin/components/ui/input/index.js +1 -1
- package/dist/admin/components/ui/label/index.js +1 -1
- package/dist/admin/components/ui/scroll-area/index.js +1 -1
- package/dist/admin/components/ui/select/index.js +1 -1
- package/dist/admin/components/ui/separator/index.js +1 -1
- package/dist/admin/components/ui/sheet/index.js +1 -1
- package/dist/admin/components/ui/sonner/index.js +1 -1
- package/dist/admin/components/ui/table/index.js +1 -1
- package/dist/admin/components/ui/tabs/index.js +1 -1
- package/dist/admin/components/ui/tooltip/index.js +1 -1
- package/dist/admin/crud/components/vm-richtext-extensions.js +1 -1
- package/dist/admin/crud/config.js +1 -1
- package/dist/admin/crud/confirm.js +1 -1
- package/dist/admin/crud/crud-page-request.js +1 -1
- package/dist/admin/crud/data-i18n.d.ts +39 -0
- package/dist/admin/crud/data-i18n.js +1 -0
- package/dist/admin/crud/dict.js +1 -1
- package/dist/admin/crud/export-fetch.js +1 -1
- package/dist/admin/crud/key.js +1 -1
- package/dist/admin/crud/plugins.js +1 -1
- package/dist/admin/crud/span.js +1 -1
- package/dist/admin/crud/style.js +1 -1
- package/dist/admin/crud/validate.js +1 -1
- package/dist/admin/crud/vm-crud.vue +66 -0
- package/dist/admin/crud/vm-data-i18n-btn.vue +104 -42
- package/dist/admin/crud/vm-data-i18n-lang.vue +176 -0
- package/dist/admin/crud/vm-table.vue +188 -5
- package/dist/admin/crud/vm-toolbar.vue +80 -47
- package/dist/admin/directives/perm.js +1 -1
- package/dist/admin/hooks/useUpload.js +1 -1
- package/dist/admin/lib/browser.js +1 -1
- package/dist/admin/lib/cn.js +1 -1
- package/dist/admin/lib/dialog-float.js +1 -1
- package/dist/admin/lib/export-excel.js +1 -1
- package/dist/admin/lib/import-excel.js +1 -1
- package/dist/admin/lib/json.js +1 -1
- package/dist/admin/lib/menu.js +1 -1
- package/dist/admin/lib/micro-locale.js +1 -1
- package/dist/admin/lib/micro-theme.js +1 -1
- package/dist/admin/lib/setup-crud-defaults.js +1 -1
- package/dist/admin/lib/shell.js +1 -1
- package/dist/admin/lib/theme-color-options.js +1 -1
- package/dist/admin/lib/tree.js +1 -1
- package/dist/admin/lib/upload.js +1 -1
- package/dist/admin/lib/video-frame.js +1 -1
- package/dist/admin/router/create.js +1 -1
- package/dist/admin/router/guards.js +1 -1
- package/dist/admin/themes/dark.js +1 -1
- package/dist/admin/themes/index.js +1 -1
- package/dist/admin/themes/light.js +1 -1
- package/dist/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/shared/datetime-picker.js +1 -1
- package/dist/shared/excel.js +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/locale-json.js +1 -1
- package/dist/shared/tree.js +1 -1
- package/package.json +1 -1
- package/typings/admin/comm/crud.ts +8 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DropdownMenu v-if="langs.length" :modal="false">
|
|
3
|
+
<DropdownMenuTrigger as-child>
|
|
4
|
+
<button
|
|
5
|
+
type="button"
|
|
6
|
+
class="vm-crud-toolbar__btn is-ghost vm-data-i18n-lang__btn"
|
|
7
|
+
:disabled="switching"
|
|
8
|
+
:title="current?.name || langCode"
|
|
9
|
+
>
|
|
10
|
+
<span class="vm-data-i18n-lang__flag" aria-hidden="true">
|
|
11
|
+
{{ current?.flag || '🏳️' }}
|
|
12
|
+
</span>
|
|
13
|
+
切换
|
|
14
|
+
</button>
|
|
15
|
+
</DropdownMenuTrigger>
|
|
16
|
+
<DropdownMenuContent align="start" class="vm-data-i18n-lang__menu">
|
|
17
|
+
<DropdownMenuItem
|
|
18
|
+
v-for="lang in langs"
|
|
19
|
+
:key="lang.value"
|
|
20
|
+
class="vm-data-i18n-lang__item"
|
|
21
|
+
:class="{ 'is-active': langCode === lang.value }"
|
|
22
|
+
:disabled="switching"
|
|
23
|
+
@click="switchLang(lang.value)"
|
|
24
|
+
>
|
|
25
|
+
<span class="vm-data-i18n-lang__flag" aria-hidden="true">
|
|
26
|
+
{{ lang.flag || '🏳️' }}
|
|
27
|
+
</span>
|
|
28
|
+
<span class="vm-data-i18n-lang__label">{{ lang.name || lang.label }}</span>
|
|
29
|
+
</DropdownMenuItem>
|
|
30
|
+
</DropdownMenuContent>
|
|
31
|
+
</DropdownMenu>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
36
|
+
import { useVome } from '../hooks/useVome'
|
|
37
|
+
import { useCrud } from './useCrud'
|
|
38
|
+
import {
|
|
39
|
+
DropdownMenu,
|
|
40
|
+
DropdownMenuContent,
|
|
41
|
+
DropdownMenuItem,
|
|
42
|
+
DropdownMenuTrigger,
|
|
43
|
+
} from '../components/ui/dropdown-menu'
|
|
44
|
+
import type { DataI18nLangOption } from './data-i18n'
|
|
45
|
+
|
|
46
|
+
defineOptions({ name: 'vm-data-i18n-lang' })
|
|
47
|
+
|
|
48
|
+
const crud = useCrud()
|
|
49
|
+
const { service } = useVome()
|
|
50
|
+
const langs = ref<DataI18nLangOption[]>([])
|
|
51
|
+
const switching = ref(false)
|
|
52
|
+
|
|
53
|
+
const langCode = computed({
|
|
54
|
+
get: () => crud.dataI18nLangCode?.value || '',
|
|
55
|
+
set: (v: string) => {
|
|
56
|
+
if (crud.dataI18nLangCode) crud.dataI18nLangCode.value = v
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const current = computed(
|
|
61
|
+
() => langs.value.find((l) => l.value === langCode.value) || langs.value[0],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
async function loadLangs() {
|
|
65
|
+
const langSvc = (service as { i18n?: { lang?: { list?: (q: unknown) => Promise<unknown> } } })
|
|
66
|
+
.i18n?.lang
|
|
67
|
+
if (!langSvc?.list) {
|
|
68
|
+
langs.value = [{ label: 'en-US', value: 'en-US', name: 'en-US', flag: '🇺🇸' }]
|
|
69
|
+
ensureLang()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const rows = (await langSvc.list({ status: 1 })) as Array<{
|
|
74
|
+
code?: string
|
|
75
|
+
name?: string
|
|
76
|
+
flag?: string
|
|
77
|
+
}>
|
|
78
|
+
langs.value = (rows || [])
|
|
79
|
+
.filter((r) => r.code && r.code !== 'zh-CN')
|
|
80
|
+
.map((r) => ({
|
|
81
|
+
label: `${r.name || r.code} (${r.code})`,
|
|
82
|
+
value: String(r.code),
|
|
83
|
+
name: String(r.name || r.code),
|
|
84
|
+
flag: r.flag || '🏳️',
|
|
85
|
+
}))
|
|
86
|
+
} catch {
|
|
87
|
+
langs.value = [{ label: 'en-US', value: 'en-US', name: 'en-US', flag: '🇺🇸' }]
|
|
88
|
+
}
|
|
89
|
+
ensureLang()
|
|
90
|
+
if (crud.trashMode.value === 'dataI18n') {
|
|
91
|
+
await crud.refresh()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ensureLang() {
|
|
96
|
+
if (!langs.value.length) return
|
|
97
|
+
if (!langCode.value || !langs.value.some((l) => l.value === langCode.value)) {
|
|
98
|
+
langCode.value = langs.value[0]!.value
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function switchLang(code: string) {
|
|
103
|
+
if (switching.value || !code || code === langCode.value) return
|
|
104
|
+
switching.value = true
|
|
105
|
+
try {
|
|
106
|
+
langCode.value = code
|
|
107
|
+
crud.pagination.value = { ...crud.pagination.value, page: 1 }
|
|
108
|
+
await crud.refresh()
|
|
109
|
+
} finally {
|
|
110
|
+
switching.value = false
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
watch(
|
|
115
|
+
() => crud.trashMode.value,
|
|
116
|
+
(mode) => {
|
|
117
|
+
if (mode === 'dataI18n') void loadLangs()
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
onMounted(() => {
|
|
122
|
+
if (crud.trashMode.value === 'dataI18n') void loadLangs()
|
|
123
|
+
})
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<style lang="scss" scoped>
|
|
127
|
+
.vm-crud-toolbar__btn {
|
|
128
|
+
display: inline-flex;
|
|
129
|
+
height: 36px;
|
|
130
|
+
align-items: center;
|
|
131
|
+
gap: 4px;
|
|
132
|
+
padding: 0 14px;
|
|
133
|
+
border: none;
|
|
134
|
+
border-radius: 12px;
|
|
135
|
+
font-size: 13px;
|
|
136
|
+
font-weight: 600;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
background: var(--muted);
|
|
139
|
+
color: var(--foreground);
|
|
140
|
+
transition: background 0.15s ease, opacity 0.15s ease;
|
|
141
|
+
|
|
142
|
+
&:disabled {
|
|
143
|
+
cursor: not-allowed;
|
|
144
|
+
opacity: 0.45;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
&:hover:not(:disabled) {
|
|
148
|
+
background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.vm-data-i18n-lang__flag {
|
|
153
|
+
font-size: 16px;
|
|
154
|
+
line-height: 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.vm-data-i18n-lang__menu {
|
|
158
|
+
min-width: 140px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.vm-data-i18n-lang__item {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
gap: 8px;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
|
|
167
|
+
&.is-active {
|
|
168
|
+
background: color-mix(in srgb, var(--brand) 12%, transparent);
|
|
169
|
+
color: var(--brand);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.vm-data-i18n-lang__label {
|
|
174
|
+
font-size: 13px;
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
@@ -109,6 +109,27 @@
|
|
|
109
109
|
{{ crudLabel('forceDelete', crud.dict.label) }}
|
|
110
110
|
</Button>
|
|
111
111
|
</div>
|
|
112
|
+
<div
|
|
113
|
+
v-else-if="crud.trashMode.value === 'dataI18n'"
|
|
114
|
+
class="vm-crud-table__ops"
|
|
115
|
+
>
|
|
116
|
+
<Button
|
|
117
|
+
type="button"
|
|
118
|
+
size="xs"
|
|
119
|
+
variant="softPrimary"
|
|
120
|
+
@click.stop="openDataI18nEdit(row)"
|
|
121
|
+
>
|
|
122
|
+
{{ crudLabel('update', crud.dict.label) }}
|
|
123
|
+
</Button>
|
|
124
|
+
<Button
|
|
125
|
+
type="button"
|
|
126
|
+
size="xs"
|
|
127
|
+
variant="softDestructive"
|
|
128
|
+
@click.stop="deleteDataI18nEntry(row)"
|
|
129
|
+
>
|
|
130
|
+
{{ crudLabel('delete', crud.dict.label) }}
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
112
133
|
<div v-else class="vm-crud-table__ops">
|
|
113
134
|
<template v-for="(btn, bi) in resolveButtons(col, row)" :key="bi">
|
|
114
135
|
<Button
|
|
@@ -245,17 +266,60 @@
|
|
|
245
266
|
</Table>
|
|
246
267
|
</div>
|
|
247
268
|
<vm-context-menu ref="ctxRef" :items="ctxItems" @select="onCtxSelect" />
|
|
269
|
+
|
|
270
|
+
<Dialog :open="dataI18nEditOpen" @update:open="onDataI18nEditOpenChange">
|
|
271
|
+
<DialogContent shell="crud" class="vm-data-i18n-edit__dialog">
|
|
272
|
+
<DialogHeader class="vm-crud-shell__head">
|
|
273
|
+
<DialogTitle class="vm-crud-shell__title">编辑译文</DialogTitle>
|
|
274
|
+
</DialogHeader>
|
|
275
|
+
<div class="vm-data-i18n-edit__body">
|
|
276
|
+
<p class="vm-data-i18n-edit__meta">
|
|
277
|
+
<span>ID:{{ dataI18nEditForm.id }}</span>
|
|
278
|
+
<span>key:{{ dataI18nEditForm.key }}</span>
|
|
279
|
+
</p>
|
|
280
|
+
<label class="vm-data-i18n-edit__field">
|
|
281
|
+
<span>value</span>
|
|
282
|
+
<Input v-model="dataI18nEditForm.value" />
|
|
283
|
+
</label>
|
|
284
|
+
</div>
|
|
285
|
+
<DialogFooter class="vm-crud-shell__foot">
|
|
286
|
+
<button
|
|
287
|
+
type="button"
|
|
288
|
+
class="vm-crud-shell__btn is-ghost"
|
|
289
|
+
:disabled="dataI18nEditSubmitting"
|
|
290
|
+
@click="dataI18nEditOpen = false"
|
|
291
|
+
>
|
|
292
|
+
取消
|
|
293
|
+
</button>
|
|
294
|
+
<button
|
|
295
|
+
type="button"
|
|
296
|
+
class="vm-crud-shell__btn is-primary"
|
|
297
|
+
:disabled="dataI18nEditSubmitting"
|
|
298
|
+
@click="saveDataI18nEdit"
|
|
299
|
+
>
|
|
300
|
+
{{ dataI18nEditSubmitting ? '保存中…' : '保存' }}
|
|
301
|
+
</button>
|
|
302
|
+
</DialogFooter>
|
|
303
|
+
</DialogContent>
|
|
304
|
+
</Dialog>
|
|
248
305
|
</div>
|
|
249
306
|
</template>
|
|
250
307
|
|
|
251
308
|
<script setup lang="ts">
|
|
252
309
|
import { ref, computed, watch, onMounted, onBeforeUnmount, provide, useSlots, toValue } from 'vue'
|
|
310
|
+
import { toast } from 'vue-sonner'
|
|
253
311
|
import { useCrud } from './useCrud'
|
|
254
312
|
import { injectTableOptions, TABLE_API_KEY } from './key'
|
|
255
313
|
import { flattenTree } from '../lib/tree'
|
|
256
314
|
import { resolveCrudComponent } from './registry'
|
|
257
315
|
import { getCrudStyle, crudLabel, crudT, crudLocaleRev } from './style'
|
|
258
316
|
import { findEpsEntity } from '../lib/eps'
|
|
317
|
+
import { useVome } from '../hooks/useVome'
|
|
318
|
+
import { vmConfirm } from './confirm'
|
|
319
|
+
import {
|
|
320
|
+
canUseDataI18n,
|
|
321
|
+
dataPackFromService,
|
|
322
|
+
} from './data-i18n'
|
|
259
323
|
import type {
|
|
260
324
|
CrudColumn,
|
|
261
325
|
CrudOpButton,
|
|
@@ -298,7 +362,17 @@ let emptyRo: ResizeObserver | null = null
|
|
|
298
362
|
|
|
299
363
|
const overrideColumns = ref<CrudColumn[] | null>(null)
|
|
300
364
|
|
|
365
|
+
const DATA_I18N_COLUMNS: CrudColumn[] = [
|
|
366
|
+
{ prop: 'id', label: 'ID', minWidth: 120 },
|
|
367
|
+
{ prop: 'key', label: 'key', minWidth: 140 },
|
|
368
|
+
{ prop: 'value', label: 'value', minWidth: 220 },
|
|
369
|
+
{ type: 'op', buttons: ['edit', 'delete'], width: 140 },
|
|
370
|
+
]
|
|
371
|
+
|
|
372
|
+
const isDataI18nView = computed(() => crud.trashMode.value === 'dataI18n')
|
|
373
|
+
|
|
301
374
|
const columns = computed(() => {
|
|
375
|
+
if (isDataI18nView.value) return DATA_I18N_COLUMNS
|
|
302
376
|
const raw =
|
|
303
377
|
overrideColumns.value ||
|
|
304
378
|
(props.columns?.length ? props.columns : tableOpts?.columns || [])
|
|
@@ -307,13 +381,16 @@ const columns = computed(() => {
|
|
|
307
381
|
|
|
308
382
|
const hasSelectionCol = computed(() => columns.value.some((c) => c.type === 'selection'))
|
|
309
383
|
const hasOpCol = computed(() => columns.value.some((c) => c.type === 'op'))
|
|
310
|
-
const showSelection = computed(() =>
|
|
311
|
-
|
|
312
|
-
|
|
384
|
+
const showSelection = computed(() => {
|
|
385
|
+
if (isDataI18nView.value) return false
|
|
386
|
+
return props.selection !== undefined ? props.selection : hasSelectionCol.value
|
|
387
|
+
})
|
|
313
388
|
const dataColumns = computed(() =>
|
|
314
389
|
columns.value.filter((c) => c.type !== 'selection'),
|
|
315
390
|
)
|
|
316
|
-
const showSlotOp = computed(
|
|
391
|
+
const showSlotOp = computed(
|
|
392
|
+
() => Boolean(slots.op) && !hasOpCol.value && !isDataI18nView.value,
|
|
393
|
+
)
|
|
317
394
|
|
|
318
395
|
/** EPS 列 → field.{table}.{prop} */
|
|
319
396
|
function epsFieldKey(prop: string): string | undefined {
|
|
@@ -357,7 +434,9 @@ const colSpan = computed(
|
|
|
357
434
|
(showSlotOp.value ? 1 : 0),
|
|
358
435
|
)
|
|
359
436
|
|
|
360
|
-
const rowKey = computed(() =>
|
|
437
|
+
const rowKey = computed(() =>
|
|
438
|
+
isDataI18nView.value ? '__rowKey' : tableOpts?.rowKey || props.rowKey,
|
|
439
|
+
)
|
|
361
440
|
const isTree = computed(() => Boolean(tableOpts?.tree))
|
|
362
441
|
const border = computed(() => tableOpts?.border ?? style.border)
|
|
363
442
|
const autoHeight = computed(() => tableOpts?.autoHeight ?? style.autoHeight)
|
|
@@ -498,6 +577,7 @@ function colStyle(col: CrudColumn, index?: number) {
|
|
|
498
577
|
return base
|
|
499
578
|
}
|
|
500
579
|
const ctxEnabled = computed(() => {
|
|
580
|
+
if (isDataI18nView.value) return false
|
|
501
581
|
const c = tableOpts?.contextMenu ?? style.contextMenu
|
|
502
582
|
return c !== false && !(Array.isArray(c) && c.length === 0)
|
|
503
583
|
})
|
|
@@ -604,6 +684,81 @@ function deleteRowTargets(row: Record<string, unknown>) {
|
|
|
604
684
|
return isTree.value ? collectSubtree(row) : [row]
|
|
605
685
|
}
|
|
606
686
|
|
|
687
|
+
const { service: hostService } = useVome()
|
|
688
|
+
const dataI18nEditOpen = ref(false)
|
|
689
|
+
const dataI18nEditSubmitting = ref(false)
|
|
690
|
+
const dataI18nEditForm = ref({ id: '', key: '', value: '' })
|
|
691
|
+
|
|
692
|
+
function onDataI18nEditOpenChange(v: boolean) {
|
|
693
|
+
dataI18nEditOpen.value = v
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function openDataI18nEdit(row: Record<string, unknown>) {
|
|
697
|
+
dataI18nEditForm.value = {
|
|
698
|
+
id: String(row.id ?? ''),
|
|
699
|
+
key: String(row.key ?? ''),
|
|
700
|
+
value: String(row.value ?? ''),
|
|
701
|
+
}
|
|
702
|
+
dataI18nEditOpen.value = true
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function saveDataI18nEdit() {
|
|
706
|
+
if (dataI18nEditSubmitting.value) return
|
|
707
|
+
const meta = canUseDataI18n(crud.service, hostService)
|
|
708
|
+
const dataPack = dataPackFromService(hostService)
|
|
709
|
+
const lang = crud.dataI18nLangCode?.value
|
|
710
|
+
if (!meta.ok || !dataPack?.updateEntry || !lang) {
|
|
711
|
+
toast.error('未配置业务翻译服务')
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
dataI18nEditSubmitting.value = true
|
|
715
|
+
try {
|
|
716
|
+
await dataPack.updateEntry({
|
|
717
|
+
tableName: meta.tableName,
|
|
718
|
+
langCode: lang,
|
|
719
|
+
id: dataI18nEditForm.value.id,
|
|
720
|
+
key: dataI18nEditForm.value.key,
|
|
721
|
+
value: dataI18nEditForm.value.value,
|
|
722
|
+
})
|
|
723
|
+
toast.success(crudLabel('saveSuccess', crud.dict.label))
|
|
724
|
+
dataI18nEditOpen.value = false
|
|
725
|
+
await crud.refresh()
|
|
726
|
+
} catch (e) {
|
|
727
|
+
toast.error(e instanceof Error ? e.message : '保存失败')
|
|
728
|
+
} finally {
|
|
729
|
+
dataI18nEditSubmitting.value = false
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
async function deleteDataI18nEntry(row: Record<string, unknown>) {
|
|
734
|
+
const meta = canUseDataI18n(crud.service, hostService)
|
|
735
|
+
const dataPack = dataPackFromService(hostService)
|
|
736
|
+
const lang = crud.dataI18nLangCode?.value
|
|
737
|
+
if (!meta.ok || !dataPack?.deleteEntry || !lang) {
|
|
738
|
+
toast.error('未配置业务翻译服务')
|
|
739
|
+
return
|
|
740
|
+
}
|
|
741
|
+
const ok = await vmConfirm({
|
|
742
|
+
title: crudLabel('tips', crud.dict.label),
|
|
743
|
+
message: crudLabel('deleteConfirm', crud.dict.label),
|
|
744
|
+
confirmText: crudLabel('confirm', crud.dict.label),
|
|
745
|
+
cancelText: crudLabel('close', crud.dict.label),
|
|
746
|
+
})
|
|
747
|
+
if (!ok) return
|
|
748
|
+
try {
|
|
749
|
+
await dataPack.deleteEntry({
|
|
750
|
+
tableName: meta.tableName,
|
|
751
|
+
langCode: lang,
|
|
752
|
+
id: String(row.id ?? ''),
|
|
753
|
+
key: String(row.key ?? ''),
|
|
754
|
+
})
|
|
755
|
+
toast.success(crudLabel('deleteSuccess', crud.dict.label))
|
|
756
|
+
await crud.refresh()
|
|
757
|
+
} catch (e) {
|
|
758
|
+
toast.error(e instanceof Error ? e.message : '删除失败')
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
607
762
|
function toggleAll(checked: boolean | 'indeterminate') {
|
|
608
763
|
if (checked === true) {
|
|
609
764
|
// 树表:含折叠未展示的子孙
|
|
@@ -1040,4 +1195,32 @@ defineExpose({
|
|
|
1040
1195
|
color: var(--foreground);
|
|
1041
1196
|
}
|
|
1042
1197
|
}
|
|
1198
|
+
|
|
1199
|
+
.vm-data-i18n-edit__dialog {
|
|
1200
|
+
width: min(480px, calc(100vw - 32px));
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.vm-data-i18n-edit__body {
|
|
1204
|
+
display: flex;
|
|
1205
|
+
flex-direction: column;
|
|
1206
|
+
gap: 14px;
|
|
1207
|
+
padding: 8px 22px 16px;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
.vm-data-i18n-edit__meta {
|
|
1211
|
+
display: flex;
|
|
1212
|
+
flex-wrap: wrap;
|
|
1213
|
+
gap: 12px;
|
|
1214
|
+
margin: 0;
|
|
1215
|
+
font-size: 13px;
|
|
1216
|
+
color: var(--muted-foreground);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
.vm-data-i18n-edit__field {
|
|
1220
|
+
display: flex;
|
|
1221
|
+
flex-direction: column;
|
|
1222
|
+
gap: 6px;
|
|
1223
|
+
font-size: 12px;
|
|
1224
|
+
color: var(--muted-foreground);
|
|
1225
|
+
}
|
|
1043
1226
|
</style>
|
|
@@ -1,42 +1,47 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="vm-crud-toolbar">
|
|
3
3
|
<div class="vm-crud-toolbar__left">
|
|
4
|
-
<
|
|
5
|
-
v-if="
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
4
|
+
<template v-if="isDataI18nView">
|
|
5
|
+
<vm-data-i18n-btn v-if="showDataI18n" :text="dataI18nText" />
|
|
6
|
+
<vm-data-i18n-lang />
|
|
7
|
+
</template>
|
|
8
|
+
<template v-else>
|
|
9
|
+
<button
|
|
10
|
+
v-if="showAddBtn"
|
|
11
|
+
type="button"
|
|
12
|
+
class="vm-crud-toolbar__btn is-primary"
|
|
13
|
+
@click="onAdd"
|
|
14
|
+
>
|
|
15
|
+
<i class="ri-add-line" />
|
|
16
|
+
{{ addText ?? crudLabel('add', crud.dict.label) }}
|
|
17
|
+
</button>
|
|
18
|
+
<button
|
|
19
|
+
v-if="showDeleteBtn"
|
|
20
|
+
type="button"
|
|
21
|
+
class="vm-crud-toolbar__btn is-danger"
|
|
22
|
+
:disabled="!crud.selection.value.length"
|
|
23
|
+
@click="crud.rowDelete()"
|
|
24
|
+
>
|
|
25
|
+
<i class="ri-delete-bin-line" />
|
|
26
|
+
{{ deleteText ?? crudLabel('multiDelete', crud.dict.label) }}
|
|
27
|
+
</button>
|
|
28
|
+
<vm-export-btn
|
|
29
|
+
v-if="showExport"
|
|
30
|
+
:text="exportText || crudLabel('export', crud.dict.label)"
|
|
31
|
+
:filename="exportFilename"
|
|
32
|
+
:max-export-limit="maxExportLimit"
|
|
33
|
+
:columns="exportColumns"
|
|
34
|
+
:data="exportData"
|
|
35
|
+
/>
|
|
36
|
+
<vm-import-btn
|
|
37
|
+
v-if="showImport"
|
|
38
|
+
:text="importText || crudLabel('import', crud.dict.label)"
|
|
39
|
+
:tip="importTip"
|
|
40
|
+
:template-filename="importTemplateFilename"
|
|
41
|
+
:max-size-mb="importMaxSizeMb"
|
|
42
|
+
/>
|
|
43
|
+
<slot />
|
|
44
|
+
</template>
|
|
40
45
|
</div>
|
|
41
46
|
<div class="vm-crud-toolbar__right">
|
|
42
47
|
<slot name="right" />
|
|
@@ -49,10 +54,10 @@
|
|
|
49
54
|
</button>
|
|
50
55
|
</div>
|
|
51
56
|
<vm-tabs
|
|
52
|
-
v-if="
|
|
57
|
+
v-if="showViewTabs"
|
|
53
58
|
:model-value="crud.trashMode.value"
|
|
54
|
-
:items="
|
|
55
|
-
@update:model-value="
|
|
59
|
+
:items="viewTabs"
|
|
60
|
+
@update:model-value="onViewTab"
|
|
56
61
|
/>
|
|
57
62
|
</div>
|
|
58
63
|
</div>
|
|
@@ -60,11 +65,14 @@
|
|
|
60
65
|
|
|
61
66
|
<script setup lang="ts">
|
|
62
67
|
import { computed } from 'vue'
|
|
68
|
+
import { useVome } from '../hooks/useVome'
|
|
63
69
|
import { useCrud } from './useCrud'
|
|
64
70
|
import VmExportBtn from './vm-export-btn.vue'
|
|
65
71
|
import VmImportBtn from './vm-import-btn.vue'
|
|
66
72
|
import VmDataI18nBtn from './vm-data-i18n-btn.vue'
|
|
73
|
+
import VmDataI18nLang from './vm-data-i18n-lang.vue'
|
|
67
74
|
import VmTabs from './vm-tabs.vue'
|
|
75
|
+
import { canUseDataI18n } from './data-i18n'
|
|
68
76
|
import { crudLabel } from './style'
|
|
69
77
|
import type {
|
|
70
78
|
CrudColumn,
|
|
@@ -80,7 +88,7 @@ const props = withDefaults(
|
|
|
80
88
|
showExport?: boolean
|
|
81
89
|
/** 显示导入(默认开;需 add 权限) */
|
|
82
90
|
showImport?: boolean
|
|
83
|
-
/**
|
|
91
|
+
/** 显示业务数据翻译(默认开;EPS dataI18n 且在语言 Tab) */
|
|
84
92
|
showDataI18n?: boolean
|
|
85
93
|
dataI18nText?: string
|
|
86
94
|
/**
|
|
@@ -118,14 +126,32 @@ const props = withDefaults(
|
|
|
118
126
|
)
|
|
119
127
|
|
|
120
128
|
const crud = useCrud()
|
|
129
|
+
const { service } = useVome()
|
|
130
|
+
|
|
131
|
+
const dataI18nEnabled = computed(() => canUseDataI18n(crud.service, service).ok)
|
|
121
132
|
|
|
122
133
|
/** 有 restore 权限(Controller api + 权限码)才显示列表/回收站 */
|
|
123
134
|
const showTrash = computed(() => Boolean(crud.getPermission('restore')))
|
|
124
135
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
136
|
+
const showViewTabs = computed(() => showTrash.value || dataI18nEnabled.value)
|
|
137
|
+
|
|
138
|
+
const viewTabs = computed(() => {
|
|
139
|
+
const tabs: Array<{ label: string; value: CrudTrashMode }> = [
|
|
140
|
+
{ label: crudLabel('list', crud.dict.label), value: 'default' },
|
|
141
|
+
]
|
|
142
|
+
if (showTrash.value) {
|
|
143
|
+
tabs.push({
|
|
144
|
+
label: crudLabel('trash', crud.dict.label),
|
|
145
|
+
value: 'onlyTrashed',
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
if (dataI18nEnabled.value) {
|
|
149
|
+
tabs.push({ label: '语言', value: 'dataI18n' })
|
|
150
|
+
}
|
|
151
|
+
return tabs
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const isDataI18nView = computed(() => crud.trashMode.value === 'dataI18n')
|
|
129
155
|
|
|
130
156
|
/** 有 add 权限才显示;回收站内可再藏 */
|
|
131
157
|
const showAddBtn = computed(() => {
|
|
@@ -138,7 +164,10 @@ const showAddBtn = computed(() => {
|
|
|
138
164
|
const showDeleteBtn = computed(() => Boolean(crud.getPermission('delete')))
|
|
139
165
|
|
|
140
166
|
const showSearchActions = computed(
|
|
141
|
-
() =>
|
|
167
|
+
() =>
|
|
168
|
+
props.search &&
|
|
169
|
+
crud.hasSearchFilters.value &&
|
|
170
|
+
crud.trashMode.value !== 'dataI18n',
|
|
142
171
|
)
|
|
143
172
|
|
|
144
173
|
async function onAdd() {
|
|
@@ -161,11 +190,15 @@ function onReset() {
|
|
|
161
190
|
crud.searchActions.value?.reset()
|
|
162
191
|
}
|
|
163
192
|
|
|
164
|
-
function
|
|
193
|
+
function onViewTab(mode: string | number) {
|
|
165
194
|
const next = String(mode) as CrudTrashMode
|
|
166
|
-
if (next !== 'default' && next !== 'onlyTrashed') return
|
|
195
|
+
if (next !== 'default' && next !== 'onlyTrashed' && next !== 'dataI18n') return
|
|
196
|
+
if (next === 'onlyTrashed' && !showTrash.value) return
|
|
197
|
+
if (next === 'dataI18n' && !dataI18nEnabled.value) return
|
|
167
198
|
crud.trashMode.value = next
|
|
168
199
|
crud.pagination.value = { ...crud.pagination.value, page: 1 }
|
|
200
|
+
// 语言 Tab:等 vm-data-i18n-lang 拉完语种再 refresh,避免默认 en-US 抢跑
|
|
201
|
+
if (next === 'dataI18n') return
|
|
169
202
|
void crud.refresh()
|
|
170
203
|
}
|
|
171
204
|
</script>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
function _0x12cb(){const _0x2ac14d=['mtqZndC0uLvlBe5Q','DMfSDwu','ofD4r3Pcza','mZzxtMPRDu0','mtqWodu0AfftChbI','mtiXnJC4oeTWuhHcwq','nJiWotryyxzswfO','ndqYDvfZzhny','oxDyzeL5sW','mJaWnZaZwgfXAvfo','ntvevLPjBxa','zgLZCgXHEq','BM9Uzq','mZi1nJaYouTqtLjlvW','mteWzKPOs2np','AgfZugvYBq','ndG5nZq1mhffvwjPtq','mNbcvhDkza'];_0x12cb=function(){return _0x2ac14d;};return _0x12cb();}(function(_0x540b81,_0x22f599){const _0x54e094=_0x5541,_0x4a5856=_0x540b81();while(!![]){try{const _0x4ce157=-parseInt(_0x54e094(0x180))/0x1*(-parseInt(_0x54e094(0x16f))/0x2)+-parseInt(_0x54e094(0x178))/0x3*(-parseInt(_0x54e094(0x172))/0x4)+parseInt(_0x54e094(0x17d))/0x5*(-parseInt(_0x54e094(0x175))/0x6)+-parseInt(_0x54e094(0x17c))/0x7*(parseInt(_0x54e094(0x171))/0x8)+parseInt(_0x54e094(0x177))/0x9*(parseInt(_0x54e094(0x17f))/0xa)+-parseInt(_0x54e094(0x179))/0xb*(parseInt(_0x54e094(0x174))/0xc)+-parseInt(_0x54e094(0x176))/0xd*(-parseInt(_0x54e094(0x173))/0xe);if(_0x4ce157===_0x22f599)break;else _0x4a5856['push'](_0x4a5856['shift']());}catch(_0x3cb12e){_0x4a5856['push'](_0x4a5856['shift']());}}}(_0x12cb,0x5c2ee));import{useUserStore}from'../stores/user';export const vPerm={'mounted'(_0x126587,_0xb8063){const _0x4a00ef=_0x5541;apply(_0x126587,_0xb8063[_0x4a00ef(0x170)]);},'updated'(_0xd93563,_0x55edb8){const _0xaa8461=_0x5541;apply(_0xd93563,_0x55edb8[_0xaa8461(0x170)]);}};function _0x5541(_0x418c3f,_0x329ff6){_0x418c3f=_0x418c3f-0x16f;const _0x12cbcc=_0x12cb();let _0x5541c0=_0x12cbcc[_0x418c3f];if(_0x5541['kPPLdK']===undefined){var _0x260002=function(_0x4055f4){const _0x32cdc3='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x126587='',_0xb8063='';for(let _0xd93563=0x0,_0x55edb8,_0x1a8856,_0xe3c6e0=0x0;_0x1a8856=_0x4055f4['charAt'](_0xe3c6e0++);~_0x1a8856&&(_0x55edb8=_0xd93563%0x4?_0x55edb8*0x40+_0x1a8856:_0x1a8856,_0xd93563++%0x4)?_0x126587+=String['fromCharCode'](0xff&_0x55edb8>>(-0x2*_0xd93563&0x6)):0x0){_0x1a8856=_0x32cdc3['indexOf'](_0x1a8856);}for(let _0x9d9b33=0x0,_0x6479c2=_0x126587['length'];_0x9d9b33<_0x6479c2;_0x9d9b33++){_0xb8063+='%'+('00'+_0x126587['charCodeAt'](_0x9d9b33)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xb8063);};_0x5541['OoemNK']=_0x260002,_0x5541['qtlEUF']={},_0x5541['kPPLdK']=!![];}const _0xdbc4bc=_0x12cbcc[0x0],_0x2f69de=_0x418c3f+_0xdbc4bc,_0x2754ff=_0x5541['qtlEUF'][_0x2f69de];return!_0x2754ff?(_0x5541c0=_0x5541['OoemNK'](_0x5541c0),_0x5541['qtlEUF'][_0x2f69de]=_0x5541c0):_0x5541c0=_0x2754ff,_0x5541c0;}function apply(_0x1a8856,_0xe3c6e0){const _0x4889a6=_0x5541,_0x9d9b33=useUserStore(),_0x6479c2=_0x9d9b33[_0x4889a6(0x17e)](_0xe3c6e0);_0x1a8856['style'][_0x4889a6(0x17a)]=_0x6479c2?'':_0x4889a6(0x17b);}
|