vome-core 0.1.95 → 0.1.97
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/api/client.js +33 -4
- 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 +16 -6
- package/dist/admin/crud/data-i18n.js +1 -1
- 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 +45 -9
- package/dist/admin/crud/vm-data-i18n-btn.vue +45 -22
- package/dist/admin/crud/vm-data-i18n-lang.vue +147 -44
- package/dist/admin/crud/vm-table.vue +108 -27
- package/dist/admin/crud/vm-toolbar.vue +1 -1
- 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/client/base.js +7 -8
- 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/dist/typings/routing/eps.d.ts +2 -0
- package/package.json +1 -1
- package/typings/admin/comm/crud.ts +4 -0
- package/typings/admin/comm/eps.ts +2 -0
- package/typings/routing/eps.ts +2 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
@click="openDialog"
|
|
8
8
|
>
|
|
9
9
|
<i class="ri-translate-2" />
|
|
10
|
-
{{ text || '
|
|
10
|
+
{{ text || crudLabel('dataI18nTranslate', crud.dict.label) }}
|
|
11
11
|
</button>
|
|
12
12
|
|
|
13
13
|
<Dialog :open="open" @update:open="onOpenChange">
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
@interact-outside="preventDialogOutsideClose"
|
|
21
21
|
>
|
|
22
22
|
<DialogHeader class="vm-crud-shell__head">
|
|
23
|
-
<DialogTitle class="vm-crud-shell__title"
|
|
23
|
+
<DialogTitle class="vm-crud-shell__title">
|
|
24
|
+
{{ crudLabel('dataI18nTranslateTitle', crud.dict.label) }}
|
|
25
|
+
</DialogTitle>
|
|
24
26
|
<DialogClose class="vm-crud-shell__close">
|
|
25
27
|
<i class="ri-close-line" />
|
|
26
28
|
<span class="sr-only">Close</span>
|
|
@@ -33,7 +35,9 @@
|
|
|
33
35
|
class="vm-data-i18n-btn__field"
|
|
34
36
|
:style="{ gridColumn: `span ${fieldSpan}` }"
|
|
35
37
|
>
|
|
36
|
-
<Label class="vm-data-i18n-btn__label"
|
|
38
|
+
<Label class="vm-data-i18n-btn__label">
|
|
39
|
+
{{ crudLabel('dataI18nTable', crud.dict.label) }}
|
|
40
|
+
</Label>
|
|
37
41
|
<Input
|
|
38
42
|
class="vm-data-i18n-btn__control is-readonly"
|
|
39
43
|
:model-value="tableName"
|
|
@@ -46,7 +50,7 @@
|
|
|
46
50
|
:style="{ gridColumn: `span ${fieldSpan}` }"
|
|
47
51
|
>
|
|
48
52
|
<Label class="vm-data-i18n-btn__label">
|
|
49
|
-
|
|
53
|
+
{{ crudLabel('dataI18nLang', crud.dict.label) }}
|
|
50
54
|
<span class="vm-data-i18n-btn__req">*</span>
|
|
51
55
|
</Label>
|
|
52
56
|
<vm-select
|
|
@@ -55,7 +59,7 @@
|
|
|
55
59
|
:options="langOptions"
|
|
56
60
|
:refresh-on-change="false"
|
|
57
61
|
width="100%"
|
|
58
|
-
placeholder="
|
|
62
|
+
:placeholder="crudLabel('dataI18nPickLang', crud.dict.label)"
|
|
59
63
|
/>
|
|
60
64
|
</div>
|
|
61
65
|
|
|
@@ -64,7 +68,7 @@
|
|
|
64
68
|
:style="{ gridColumn: `span ${fieldSpan}` }"
|
|
65
69
|
>
|
|
66
70
|
<Label class="vm-data-i18n-btn__label">
|
|
67
|
-
|
|
71
|
+
{{ crudLabel('dataI18nModel', crud.dict.label) }}
|
|
68
72
|
<span class="vm-data-i18n-btn__req">*</span>
|
|
69
73
|
</Label>
|
|
70
74
|
<vm-select
|
|
@@ -73,7 +77,7 @@
|
|
|
73
77
|
:options="modelOptions"
|
|
74
78
|
:refresh-on-change="false"
|
|
75
79
|
width="100%"
|
|
76
|
-
placeholder="
|
|
80
|
+
:placeholder="crudLabel('dataI18nPickModel', crud.dict.label)"
|
|
77
81
|
/>
|
|
78
82
|
</div>
|
|
79
83
|
|
|
@@ -82,7 +86,7 @@
|
|
|
82
86
|
:style="{ gridColumn: `span ${fieldSpan}` }"
|
|
83
87
|
>
|
|
84
88
|
<Label class="vm-data-i18n-btn__label">
|
|
85
|
-
|
|
89
|
+
{{ crudLabel('dataI18nMode', crud.dict.label) }}
|
|
86
90
|
<span class="vm-data-i18n-btn__req">*</span>
|
|
87
91
|
</Label>
|
|
88
92
|
<vm-select
|
|
@@ -102,7 +106,7 @@
|
|
|
102
106
|
:disabled="submitting"
|
|
103
107
|
@click="open = false"
|
|
104
108
|
>
|
|
105
|
-
|
|
109
|
+
{{ crudLabel('close', crud.dict.label) }}
|
|
106
110
|
</button>
|
|
107
111
|
<button
|
|
108
112
|
type="button"
|
|
@@ -110,7 +114,11 @@
|
|
|
110
114
|
:disabled="submitting || !form.model || !form.langCode"
|
|
111
115
|
@click="runTranslate"
|
|
112
116
|
>
|
|
113
|
-
{{
|
|
117
|
+
{{
|
|
118
|
+
submitting
|
|
119
|
+
? crudLabel('dataI18nTranslating', crud.dict.label)
|
|
120
|
+
: crudLabel('dataI18nTranslate', crud.dict.label)
|
|
121
|
+
}}
|
|
114
122
|
</button>
|
|
115
123
|
</DialogFooter>
|
|
116
124
|
</DialogContent>
|
|
@@ -127,6 +135,7 @@ import { useVome } from '../hooks/useVome'
|
|
|
127
135
|
import { useCrud } from './useCrud'
|
|
128
136
|
import VmSelect from './components/vm-select.vue'
|
|
129
137
|
import { resolveFormSpan, useFormCols } from './span'
|
|
138
|
+
import { crudFormat, crudLabel } from './style'
|
|
130
139
|
import {
|
|
131
140
|
canUseDataI18n,
|
|
132
141
|
dataPackFromService,
|
|
@@ -160,10 +169,16 @@ const form = reactive({
|
|
|
160
169
|
mode: 'incremental' as 'incremental' | 'full',
|
|
161
170
|
})
|
|
162
171
|
|
|
163
|
-
const modeOptions = [
|
|
164
|
-
{
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
const modeOptions = computed(() => [
|
|
173
|
+
{
|
|
174
|
+
label: crudLabel('dataI18nModeIncremental', crud.dict.label),
|
|
175
|
+
value: 'incremental',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
label: crudLabel('dataI18nModeFull', crud.dict.label),
|
|
179
|
+
value: 'full',
|
|
180
|
+
},
|
|
181
|
+
])
|
|
167
182
|
|
|
168
183
|
const meta = computed(() =>
|
|
169
184
|
canUseDataI18n(crud.service, service, props.tableName),
|
|
@@ -245,20 +260,20 @@ async function runTranslate() {
|
|
|
245
260
|
if (submitting.value || props.disabled) return
|
|
246
261
|
const table = tableName.value
|
|
247
262
|
if (!table) {
|
|
248
|
-
toast.error('
|
|
263
|
+
toast.error(crudLabel('dataI18nNoTable', crud.dict.label))
|
|
249
264
|
return
|
|
250
265
|
}
|
|
251
266
|
if (!form.langCode || form.langCode === 'zh-CN') {
|
|
252
|
-
toast.error('
|
|
267
|
+
toast.error(crudLabel('dataI18nNeedLang', crud.dict.label))
|
|
253
268
|
return
|
|
254
269
|
}
|
|
255
270
|
if (!form.model) {
|
|
256
|
-
toast.error('
|
|
271
|
+
toast.error(crudLabel('dataI18nNeedModel', crud.dict.label))
|
|
257
272
|
return
|
|
258
273
|
}
|
|
259
274
|
const dataPack = dataPackFromService(service)
|
|
260
275
|
if (!dataPack?.translateTable) {
|
|
261
|
-
toast.error('
|
|
276
|
+
toast.error(crudLabel('dataI18nNoService', crud.dict.label))
|
|
262
277
|
return
|
|
263
278
|
}
|
|
264
279
|
submitting.value = true
|
|
@@ -279,7 +294,9 @@ async function runTranslate() {
|
|
|
279
294
|
if (isAiStreamResult(out)) {
|
|
280
295
|
for await (const chunk of out.stream) {
|
|
281
296
|
if (chunk.type === 'error') {
|
|
282
|
-
throw new Error(
|
|
297
|
+
throw new Error(
|
|
298
|
+
chunk.error?.message || crudLabel('dataI18nFail', crud.dict.label),
|
|
299
|
+
)
|
|
283
300
|
}
|
|
284
301
|
if (chunk.type === 'done') {
|
|
285
302
|
skipped = Boolean(chunk.data?.skipped)
|
|
@@ -298,9 +315,11 @@ async function runTranslate() {
|
|
|
298
315
|
rowCount = Number(res?.rowCount ?? 0)
|
|
299
316
|
}
|
|
300
317
|
if (skipped) {
|
|
301
|
-
toast.message(message || '
|
|
318
|
+
toast.message(message || crudLabel('dataI18nSkipped', crud.dict.label))
|
|
302
319
|
} else {
|
|
303
|
-
toast.success(
|
|
320
|
+
toast.success(
|
|
321
|
+
crudFormat(crudLabel('dataI18nDone', crud.dict.label), rowCount),
|
|
322
|
+
)
|
|
304
323
|
}
|
|
305
324
|
open.value = false
|
|
306
325
|
if (crud.dataI18nLangCode) {
|
|
@@ -311,7 +330,11 @@ async function runTranslate() {
|
|
|
311
330
|
}
|
|
312
331
|
await crud.refresh()
|
|
313
332
|
} catch (e) {
|
|
314
|
-
toast.error(
|
|
333
|
+
toast.error(
|
|
334
|
+
e instanceof Error
|
|
335
|
+
? e.message
|
|
336
|
+
: crudLabel('dataI18nFail', crud.dict.label),
|
|
337
|
+
)
|
|
315
338
|
} finally {
|
|
316
339
|
submitting.value = false
|
|
317
340
|
}
|
|
@@ -1,40 +1,84 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
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
|
-
|
|
2
|
+
<div v-if="targetLangs.length" class="vm-data-i18n-lang">
|
|
3
|
+
<DropdownMenu :modal="false">
|
|
4
|
+
<DropdownMenuTrigger as-child>
|
|
5
|
+
<button
|
|
6
|
+
type="button"
|
|
7
|
+
class="vm-crud-toolbar__btn is-ghost vm-data-i18n-lang__btn"
|
|
8
|
+
:disabled="switching"
|
|
9
|
+
:title="
|
|
10
|
+
crudFormat(
|
|
11
|
+
crudLabel('dataI18nTargetLang', crud.dict.label),
|
|
12
|
+
targetCurrent?.name || targetCode,
|
|
13
|
+
)
|
|
14
|
+
"
|
|
15
|
+
>
|
|
16
|
+
<span class="vm-data-i18n-lang__flag" aria-hidden="true">
|
|
17
|
+
{{ targetCurrent?.flag || '🏳️' }}
|
|
18
|
+
</span>
|
|
19
|
+
{{ crudLabel('dataI18nSwitch', crud.dict.label) }}
|
|
20
|
+
</button>
|
|
21
|
+
</DropdownMenuTrigger>
|
|
22
|
+
<DropdownMenuContent align="start" class="vm-data-i18n-lang__menu">
|
|
23
|
+
<DropdownMenuItem
|
|
24
|
+
v-for="lang in targetLangs"
|
|
25
|
+
:key="lang.value"
|
|
26
|
+
class="vm-data-i18n-lang__item"
|
|
27
|
+
:class="{ 'is-active': targetCode === lang.value }"
|
|
28
|
+
:disabled="switching"
|
|
29
|
+
@click="switchTarget(lang.value)"
|
|
30
|
+
>
|
|
31
|
+
<span class="vm-data-i18n-lang__flag" aria-hidden="true">
|
|
32
|
+
{{ lang.flag || '🏳️' }}
|
|
33
|
+
</span>
|
|
34
|
+
<span class="vm-data-i18n-lang__label">{{ lang.name || lang.label }}</span>
|
|
35
|
+
</DropdownMenuItem>
|
|
36
|
+
</DropdownMenuContent>
|
|
37
|
+
</DropdownMenu>
|
|
38
|
+
|
|
39
|
+
<DropdownMenu :modal="false">
|
|
40
|
+
<DropdownMenuTrigger as-child>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
class="vm-crud-toolbar__btn is-ghost vm-data-i18n-lang__btn"
|
|
44
|
+
:disabled="switching"
|
|
45
|
+
:title="
|
|
46
|
+
crudFormat(
|
|
47
|
+
crudLabel('dataI18nSourceLang', crud.dict.label),
|
|
48
|
+
sourceCurrent?.name || sourceCode,
|
|
49
|
+
)
|
|
50
|
+
"
|
|
51
|
+
>
|
|
52
|
+
<span class="vm-data-i18n-lang__flag" aria-hidden="true">
|
|
53
|
+
{{ sourceCurrent?.flag || '🇨🇳' }}
|
|
54
|
+
</span>
|
|
55
|
+
{{ crudLabel('dataI18nSource', crud.dict.label) }}
|
|
56
|
+
</button>
|
|
57
|
+
</DropdownMenuTrigger>
|
|
58
|
+
<DropdownMenuContent align="start" class="vm-data-i18n-lang__menu">
|
|
59
|
+
<DropdownMenuItem
|
|
60
|
+
v-for="lang in sourceLangs"
|
|
61
|
+
:key="lang.value"
|
|
62
|
+
class="vm-data-i18n-lang__item"
|
|
63
|
+
:class="{ 'is-active': sourceCode === lang.value }"
|
|
64
|
+
:disabled="switching"
|
|
65
|
+
@click="switchSource(lang.value)"
|
|
66
|
+
>
|
|
67
|
+
<span class="vm-data-i18n-lang__flag" aria-hidden="true">
|
|
68
|
+
{{ lang.flag || '🏳️' }}
|
|
69
|
+
</span>
|
|
70
|
+
<span class="vm-data-i18n-lang__label">{{ lang.name || lang.label }}</span>
|
|
71
|
+
</DropdownMenuItem>
|
|
72
|
+
</DropdownMenuContent>
|
|
73
|
+
</DropdownMenu>
|
|
74
|
+
</div>
|
|
32
75
|
</template>
|
|
33
76
|
|
|
34
77
|
<script setup lang="ts">
|
|
35
78
|
import { computed, onMounted, ref, watch } from 'vue'
|
|
36
79
|
import { useVome } from '../hooks/useVome'
|
|
37
80
|
import { useCrud } from './useCrud'
|
|
81
|
+
import { crudFormat, crudLabel } from './style'
|
|
38
82
|
import {
|
|
39
83
|
DropdownMenu,
|
|
40
84
|
DropdownMenuContent,
|
|
@@ -50,15 +94,49 @@ const { service } = useVome()
|
|
|
50
94
|
const langs = ref<DataI18nLangOption[]>([])
|
|
51
95
|
const switching = ref(false)
|
|
52
96
|
|
|
53
|
-
const
|
|
97
|
+
const targetCode = computed({
|
|
54
98
|
get: () => crud.dataI18nLangCode?.value || '',
|
|
55
99
|
set: (v: string) => {
|
|
56
100
|
if (crud.dataI18nLangCode) crud.dataI18nLangCode.value = v
|
|
57
101
|
},
|
|
58
102
|
})
|
|
59
103
|
|
|
60
|
-
const
|
|
61
|
-
|
|
104
|
+
const sourceCode = computed({
|
|
105
|
+
get: () => crud.dataI18nSourceLangCode?.value || 'zh-CN',
|
|
106
|
+
set: (v: string) => {
|
|
107
|
+
if (crud.dataI18nSourceLangCode) crud.dataI18nSourceLangCode.value = v
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const targetLangs = computed(() =>
|
|
112
|
+
langs.value.filter((l) => l.value !== 'zh-CN'),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const sourceLangs = computed(() => {
|
|
116
|
+
const zhName = crudLabel('dataI18nZh', crud.dict.label)
|
|
117
|
+
const zh: DataI18nLangOption = {
|
|
118
|
+
label: `${zhName} (zh-CN)`,
|
|
119
|
+
value: 'zh-CN',
|
|
120
|
+
name: zhName,
|
|
121
|
+
flag: '🇨🇳',
|
|
122
|
+
}
|
|
123
|
+
// 对照可选中文 + 其它语种(不含当前目标,避免无意义对照)
|
|
124
|
+
const rest = langs.value.filter(
|
|
125
|
+
(l) => l.value !== 'zh-CN' && l.value !== targetCode.value,
|
|
126
|
+
)
|
|
127
|
+
return [zh, ...rest]
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const targetCurrent = computed(
|
|
131
|
+
() =>
|
|
132
|
+
targetLangs.value.find((l) => l.value === targetCode.value) ||
|
|
133
|
+
targetLangs.value[0],
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const sourceCurrent = computed(
|
|
137
|
+
() =>
|
|
138
|
+
sourceLangs.value.find((l) => l.value === sourceCode.value) ||
|
|
139
|
+
sourceLangs.value[0],
|
|
62
140
|
)
|
|
63
141
|
|
|
64
142
|
async function loadLangs() {
|
|
@@ -66,7 +144,7 @@ async function loadLangs() {
|
|
|
66
144
|
.i18n?.lang
|
|
67
145
|
if (!langSvc?.list) {
|
|
68
146
|
langs.value = [{ label: 'en-US', value: 'en-US', name: 'en-US', flag: '🇺🇸' }]
|
|
69
|
-
|
|
147
|
+
ensureLangs()
|
|
70
148
|
return
|
|
71
149
|
}
|
|
72
150
|
try {
|
|
@@ -76,34 +154,53 @@ async function loadLangs() {
|
|
|
76
154
|
flag?: string
|
|
77
155
|
}>
|
|
78
156
|
langs.value = (rows || [])
|
|
79
|
-
.filter((r) => r.code
|
|
157
|
+
.filter((r) => r.code)
|
|
80
158
|
.map((r) => ({
|
|
81
159
|
label: `${r.name || r.code} (${r.code})`,
|
|
82
160
|
value: String(r.code),
|
|
83
161
|
name: String(r.name || r.code),
|
|
84
|
-
flag: r.flag || '🏳️',
|
|
162
|
+
flag: r.flag || (r.code === 'zh-CN' ? '🇨🇳' : '🏳️'),
|
|
85
163
|
}))
|
|
86
164
|
} catch {
|
|
87
165
|
langs.value = [{ label: 'en-US', value: 'en-US', name: 'en-US', flag: '🇺🇸' }]
|
|
88
166
|
}
|
|
89
|
-
|
|
167
|
+
ensureLangs()
|
|
90
168
|
if (crud.trashMode.value === 'dataI18n') {
|
|
91
169
|
await crud.refresh()
|
|
92
170
|
}
|
|
93
171
|
}
|
|
94
172
|
|
|
95
|
-
function
|
|
96
|
-
if (!
|
|
97
|
-
if (
|
|
98
|
-
|
|
173
|
+
function ensureLangs() {
|
|
174
|
+
if (!targetLangs.value.length) return
|
|
175
|
+
if (
|
|
176
|
+
!targetCode.value ||
|
|
177
|
+
!targetLangs.value.some((o) => o.value === targetCode.value)
|
|
178
|
+
) {
|
|
179
|
+
targetCode.value = targetLangs.value[0]!.value
|
|
180
|
+
}
|
|
181
|
+
if (!sourceCode.value) sourceCode.value = 'zh-CN'
|
|
182
|
+
if (sourceCode.value === targetCode.value) sourceCode.value = 'zh-CN'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function switchTarget(code: string) {
|
|
186
|
+
if (switching.value || !code || code === targetCode.value) return
|
|
187
|
+
switching.value = true
|
|
188
|
+
try {
|
|
189
|
+
targetCode.value = code
|
|
190
|
+
if (sourceCode.value === code) sourceCode.value = 'zh-CN'
|
|
191
|
+
crud.pagination.value = { ...crud.pagination.value, page: 1 }
|
|
192
|
+
await crud.refresh()
|
|
193
|
+
} finally {
|
|
194
|
+
switching.value = false
|
|
99
195
|
}
|
|
100
196
|
}
|
|
101
197
|
|
|
102
|
-
async function
|
|
103
|
-
if (switching.value || !code || code ===
|
|
198
|
+
async function switchSource(code: string) {
|
|
199
|
+
if (switching.value || !code || code === sourceCode.value) return
|
|
200
|
+
if (code === targetCode.value) return
|
|
104
201
|
switching.value = true
|
|
105
202
|
try {
|
|
106
|
-
|
|
203
|
+
sourceCode.value = code
|
|
107
204
|
crud.pagination.value = { ...crud.pagination.value, page: 1 }
|
|
108
205
|
await crud.refresh()
|
|
109
206
|
} finally {
|
|
@@ -124,6 +221,12 @@ onMounted(() => {
|
|
|
124
221
|
</script>
|
|
125
222
|
|
|
126
223
|
<style lang="scss" scoped>
|
|
224
|
+
.vm-data-i18n-lang {
|
|
225
|
+
display: inline-flex;
|
|
226
|
+
align-items: center;
|
|
227
|
+
gap: 8px;
|
|
228
|
+
}
|
|
229
|
+
|
|
127
230
|
.vm-crud-toolbar__btn {
|
|
128
231
|
display: inline-flex;
|
|
129
232
|
height: 36px;
|
|
@@ -277,7 +277,9 @@
|
|
|
277
277
|
@interact-outside="preventDialogOutsideClose"
|
|
278
278
|
>
|
|
279
279
|
<DialogHeader class="vm-crud-shell__head">
|
|
280
|
-
<DialogTitle class="vm-crud-shell__title"
|
|
280
|
+
<DialogTitle class="vm-crud-shell__title">
|
|
281
|
+
{{ crudLabel('dataI18nEdit', crud.dict.label) }}
|
|
282
|
+
</DialogTitle>
|
|
281
283
|
<DialogClose class="vm-crud-shell__close">
|
|
282
284
|
<i class="ri-close-line" />
|
|
283
285
|
<span class="sr-only">Close</span>
|
|
@@ -296,17 +298,27 @@
|
|
|
296
298
|
/>
|
|
297
299
|
</div>
|
|
298
300
|
<div
|
|
301
|
+
v-for="f in dataI18nEditFields"
|
|
302
|
+
:key="f.name"
|
|
299
303
|
class="vm-data-i18n-edit__field"
|
|
300
304
|
:style="{ gridColumn: `span ${dataI18nEditSpan}` }"
|
|
301
305
|
>
|
|
302
|
-
<Label class="vm-data-i18n-edit__label">
|
|
303
|
-
value
|
|
304
|
-
<span class="vm-data-i18n-edit__req">*</span>
|
|
305
|
-
</Label>
|
|
306
|
+
<Label class="vm-data-i18n-edit__label">{{ f.label || f.name }}</Label>
|
|
306
307
|
<Input
|
|
307
308
|
class="vm-data-i18n-edit__control"
|
|
308
|
-
v-model="dataI18nEditForm.
|
|
309
|
+
v-model="dataI18nEditForm.values[f.name]"
|
|
309
310
|
/>
|
|
311
|
+
<div
|
|
312
|
+
v-if="dataI18nEditForm.sources[f.name]"
|
|
313
|
+
class="vm-data-i18n-edit__source"
|
|
314
|
+
>
|
|
315
|
+
{{
|
|
316
|
+
crudFormat(
|
|
317
|
+
crudLabel('dataI18nCompare', crud.dict.label),
|
|
318
|
+
dataI18nEditForm.sources[f.name],
|
|
319
|
+
)
|
|
320
|
+
}}
|
|
321
|
+
</div>
|
|
310
322
|
</div>
|
|
311
323
|
</div>
|
|
312
324
|
<DialogFooter class="vm-crud-shell__foot">
|
|
@@ -316,7 +328,7 @@
|
|
|
316
328
|
:disabled="dataI18nEditSubmitting"
|
|
317
329
|
@click="dataI18nEditOpen = false"
|
|
318
330
|
>
|
|
319
|
-
|
|
331
|
+
{{ crudLabel('close', crud.dict.label) }}
|
|
320
332
|
</button>
|
|
321
333
|
<button
|
|
322
334
|
type="button"
|
|
@@ -324,7 +336,11 @@
|
|
|
324
336
|
:disabled="dataI18nEditSubmitting"
|
|
325
337
|
@click="saveDataI18nEdit"
|
|
326
338
|
>
|
|
327
|
-
{{
|
|
339
|
+
{{
|
|
340
|
+
dataI18nEditSubmitting
|
|
341
|
+
? crudLabel('saving', crud.dict.label)
|
|
342
|
+
: crudLabel('save', crud.dict.label)
|
|
343
|
+
}}
|
|
328
344
|
</button>
|
|
329
345
|
</DialogFooter>
|
|
330
346
|
</DialogContent>
|
|
@@ -339,7 +355,7 @@ import { useCrud } from './useCrud'
|
|
|
339
355
|
import { injectTableOptions, TABLE_API_KEY } from './key'
|
|
340
356
|
import { flattenTree } from '../lib/tree'
|
|
341
357
|
import { resolveCrudComponent } from './registry'
|
|
342
|
-
import { getCrudStyle, crudLabel, crudT, crudLocaleRev } from './style'
|
|
358
|
+
import { getCrudStyle, crudLabel, crudT, crudFormat, crudLocaleRev } from './style'
|
|
343
359
|
import { findEpsEntity } from '../lib/eps'
|
|
344
360
|
import { useVome } from '../hooks/useVome'
|
|
345
361
|
import { vmConfirm } from './confirm'
|
|
@@ -391,17 +407,56 @@ let emptyRo: ResizeObserver | null = null
|
|
|
391
407
|
|
|
392
408
|
const overrideColumns = ref<CrudColumn[] | null>(null)
|
|
393
409
|
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
410
|
+
const DATA_I18N_ID_COL: CrudColumn = {
|
|
411
|
+
prop: 'id',
|
|
412
|
+
label: 'ID',
|
|
413
|
+
minWidth: 100,
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const DATA_I18N_OP_COL: CrudColumn = {
|
|
417
|
+
type: 'op',
|
|
418
|
+
buttons: ['edit', 'delete'],
|
|
419
|
+
width: 140,
|
|
420
|
+
}
|
|
400
421
|
|
|
401
422
|
const isDataI18nView = computed(() => crud.trashMode.value === 'dataI18n')
|
|
402
423
|
|
|
403
|
-
|
|
404
|
-
|
|
424
|
+
/** 语言 Tab:按表头/当前页内容估 minWidth(限幅) */
|
|
425
|
+
function dataI18nColMinWidth(header: string, samples: string[]) {
|
|
426
|
+
let maxChars = [...header].length
|
|
427
|
+
for (const s of samples) {
|
|
428
|
+
const n = [...String(s || '')].length
|
|
429
|
+
if (n > maxChars) maxChars = n
|
|
430
|
+
}
|
|
431
|
+
return Math.min(360, Math.max(108, maxChars * 14 + 28))
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const columns = computed((): CrudColumn[] => {
|
|
435
|
+
if (isDataI18nView.value) {
|
|
436
|
+
void crudLocaleRev.value
|
|
437
|
+
const fields = crud.dataI18nFields?.value || []
|
|
438
|
+
const rows = crud.list.value || []
|
|
439
|
+
const origSuffix = crudLabel('dataI18nOrigSuffix', crud.dict.label)
|
|
440
|
+
const fieldCols: CrudColumn[] = []
|
|
441
|
+
for (const f of fields) {
|
|
442
|
+
const base = f.label || f.name
|
|
443
|
+
const srcProp = `__src_${f.name}`
|
|
444
|
+
const srcHeader = `${base}${origSuffix}`
|
|
445
|
+
const srcSamples = rows.map((r) => String(r[srcProp] ?? ''))
|
|
446
|
+
const valSamples = rows.map((r) => String(r[f.name] ?? ''))
|
|
447
|
+
fieldCols.push({
|
|
448
|
+
prop: srcProp,
|
|
449
|
+
label: srcHeader,
|
|
450
|
+
minWidth: dataI18nColMinWidth(srcHeader, srcSamples),
|
|
451
|
+
})
|
|
452
|
+
fieldCols.push({
|
|
453
|
+
prop: f.name,
|
|
454
|
+
label: base,
|
|
455
|
+
minWidth: dataI18nColMinWidth(base, valSamples),
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
return [DATA_I18N_ID_COL, ...fieldCols, DATA_I18N_OP_COL]
|
|
459
|
+
}
|
|
405
460
|
const raw =
|
|
406
461
|
overrideColumns.value ||
|
|
407
462
|
(props.columns?.length ? props.columns : tableOpts?.columns || [])
|
|
@@ -724,21 +779,36 @@ function deleteRowTargets(row: Record<string, unknown>) {
|
|
|
724
779
|
const { service: hostService } = useVome()
|
|
725
780
|
const dataI18nEditOpen = ref(false)
|
|
726
781
|
const dataI18nEditSubmitting = ref(false)
|
|
727
|
-
const dataI18nEditForm = ref
|
|
782
|
+
const dataI18nEditForm = ref<{
|
|
783
|
+
id: string
|
|
784
|
+
values: Record<string, string>
|
|
785
|
+
sources: Record<string, string>
|
|
786
|
+
}>({ id: '', values: {}, sources: {} })
|
|
728
787
|
const dataI18nEditFormCols = useFormCols()
|
|
729
788
|
const dataI18nEditSpan = computed(() =>
|
|
730
789
|
resolveFormSpan(12, dataI18nEditFormCols.value),
|
|
731
790
|
)
|
|
791
|
+
const dataI18nEditFields = computed(
|
|
792
|
+
() => crud.dataI18nFields?.value || [],
|
|
793
|
+
)
|
|
732
794
|
|
|
733
795
|
function onDataI18nEditOpenChange(v: boolean) {
|
|
734
796
|
dataI18nEditOpen.value = v
|
|
735
797
|
}
|
|
736
798
|
|
|
737
799
|
function openDataI18nEdit(row: Record<string, unknown>) {
|
|
800
|
+
const fields = crud.dataI18nFields?.value || []
|
|
801
|
+
const sources = (row.__sources as Record<string, string>) || {}
|
|
802
|
+
const values: Record<string, string> = {}
|
|
803
|
+
const src: Record<string, string> = {}
|
|
804
|
+
for (const f of fields) {
|
|
805
|
+
values[f.name] = String(row[f.name] ?? '')
|
|
806
|
+
src[f.name] = String(sources[f.name] ?? '')
|
|
807
|
+
}
|
|
738
808
|
dataI18nEditForm.value = {
|
|
739
809
|
id: String(row.id ?? ''),
|
|
740
|
-
|
|
741
|
-
|
|
810
|
+
values,
|
|
811
|
+
sources: src,
|
|
742
812
|
}
|
|
743
813
|
dataI18nEditOpen.value = true
|
|
744
814
|
}
|
|
@@ -749,7 +819,7 @@ async function saveDataI18nEdit() {
|
|
|
749
819
|
const dataPack = dataPackFromService(hostService)
|
|
750
820
|
const lang = crud.dataI18nLangCode?.value
|
|
751
821
|
if (!meta.ok || !dataPack?.updateEntry || !lang) {
|
|
752
|
-
toast.error('
|
|
822
|
+
toast.error(crudLabel('dataI18nNoService', crud.dict.label))
|
|
753
823
|
return
|
|
754
824
|
}
|
|
755
825
|
dataI18nEditSubmitting.value = true
|
|
@@ -758,8 +828,7 @@ async function saveDataI18nEdit() {
|
|
|
758
828
|
tableName: meta.tableName,
|
|
759
829
|
langCode: lang,
|
|
760
830
|
id: dataI18nEditForm.value.id,
|
|
761
|
-
|
|
762
|
-
value: dataI18nEditForm.value.value,
|
|
831
|
+
values: { ...dataI18nEditForm.value.values },
|
|
763
832
|
})
|
|
764
833
|
toast.success(crudLabel('saveSuccess', crud.dict.label))
|
|
765
834
|
dataI18nEditOpen.value = false
|
|
@@ -776,12 +845,17 @@ async function deleteDataI18nEntry(row: Record<string, unknown>) {
|
|
|
776
845
|
const dataPack = dataPackFromService(hostService)
|
|
777
846
|
const lang = crud.dataI18nLangCode?.value
|
|
778
847
|
if (!meta.ok || !dataPack?.deleteEntry || !lang) {
|
|
779
|
-
toast.error('
|
|
848
|
+
toast.error(crudLabel('dataI18nNoService', crud.dict.label))
|
|
780
849
|
return
|
|
781
850
|
}
|
|
851
|
+
const id = String(row.id ?? '')
|
|
852
|
+
if (!id) return
|
|
782
853
|
const ok = await vmConfirm({
|
|
783
854
|
title: crudLabel('tips', crud.dict.label),
|
|
784
|
-
message:
|
|
855
|
+
message: crudFormat(
|
|
856
|
+
crudLabel('dataI18nDeleteConfirm', crud.dict.label),
|
|
857
|
+
id,
|
|
858
|
+
),
|
|
785
859
|
confirmText: crudLabel('confirm', crud.dict.label),
|
|
786
860
|
cancelText: crudLabel('close', crud.dict.label),
|
|
787
861
|
})
|
|
@@ -790,8 +864,7 @@ async function deleteDataI18nEntry(row: Record<string, unknown>) {
|
|
|
790
864
|
await dataPack.deleteEntry({
|
|
791
865
|
tableName: meta.tableName,
|
|
792
866
|
langCode: lang,
|
|
793
|
-
id
|
|
794
|
-
key: String(row.key ?? ''),
|
|
867
|
+
id,
|
|
795
868
|
})
|
|
796
869
|
toast.success(crudLabel('deleteSuccess', crud.dict.label))
|
|
797
870
|
await crud.refresh()
|
|
@@ -1283,4 +1356,12 @@ defineExpose({
|
|
|
1283
1356
|
cursor: default;
|
|
1284
1357
|
}
|
|
1285
1358
|
}
|
|
1359
|
+
|
|
1360
|
+
.vm-data-i18n-edit__source {
|
|
1361
|
+
margin-top: 6px;
|
|
1362
|
+
font-size: 12px;
|
|
1363
|
+
line-height: 1.4;
|
|
1364
|
+
color: var(--muted-foreground);
|
|
1365
|
+
word-break: break-word;
|
|
1366
|
+
}
|
|
1286
1367
|
</style>
|