@liujitcn/kratos-admin-system 0.0.21 → 0.0.24
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/package/declarations/src/api/system/base_language.d.ts +5 -4
- package/dist/package/declarations/src/api/system/base_translation.d.ts +7 -4
- package/dist/package/declarations/src/api/system/ops_monitoring.d.ts +19 -0
- package/dist/package/declarations/src/api/system/ops_monitoring_sse.d.ts +12 -0
- package/dist/package/declarations/src/components/DynamicTranslationCell.vue.d.ts +9 -32
- package/dist/package/declarations/src/components/DynamicTranslationEditor.vue.d.ts +3 -6
- package/dist/package/declarations/src/components/dynamicTranslation.d.ts +11 -17
- package/dist/package/declarations/src/locales/generated.d.ts +6536 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_session.d.ts +12 -2
- package/dist/package/declarations/src/rpc/base/v1/ai_tool.d.ts +9 -1
- package/dist/package/declarations/src/rpc/base/v1/config.d.ts +35 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +1 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +23 -7
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +7 -77
- package/dist/package/declarations/src/rpc/system/admin/v1/base_job.d.ts +9 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/base_language.d.ts +7 -2
- package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +5 -40
- package/dist/package/declarations/src/rpc/system/admin/v1/base_migration.d.ts +6 -6
- package/dist/package/declarations/src/rpc/system/admin/v1/base_role.d.ts +13 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/base_translation.d.ts +54 -81
- package/dist/package/declarations/src/rpc/system/admin/v1/base_user.d.ts +11 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +11 -11
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_table.d.ts +11 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/common.d.ts +13 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/ops_monitoring.d.ts +222 -0
- package/dist/package/declarations/src/views/tool/ops-monitoring/index.vue.d.ts +3 -0
- package/dist/package/src/api/base/ai_message.ts +2 -2
- package/dist/package/src/api/system/base_language.ts +13 -15
- package/dist/package/src/api/system/base_translation.ts +17 -6
- package/dist/package/src/api/system/ops_monitoring.ts +88 -0
- package/dist/package/src/api/system/ops_monitoring_sse.ts +49 -0
- package/dist/package/src/components/DynamicTranslationCell.vue +199 -126
- package/dist/package/src/components/DynamicTranslationEditor.vue +109 -78
- package/dist/package/src/components/dynamicTranslation.ts +11 -31
- package/dist/package/src/locales/README.md +7 -5
- package/dist/package/src/locales/en-US.json +876 -872
- package/dist/package/src/locales/es-ES.json +900 -895
- package/dist/package/src/locales/fr-FR.json +896 -891
- package/dist/package/src/locales/generated.ts +22 -0
- package/dist/package/src/locales/ja-JP.json +876 -872
- package/dist/package/src/locales/ko-KR.json +881 -876
- package/dist/package/src/locales/zh-CN.json +876 -872
- package/dist/package/src/locales/zh-TW.json +876 -871
- package/dist/package/src/module.ts +3 -17
- package/dist/package/src/rpc/base/v1/ai_session.ts +13 -2
- package/dist/package/src/rpc/base/v1/ai_tool.ts +10 -1
- package/dist/package/src/rpc/base/v1/config.ts +47 -0
- package/dist/package/src/rpc/system/admin/v1/auth.ts +1 -1
- package/dist/package/src/rpc/system/admin/v1/base_config.ts +24 -7
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +7 -79
- package/dist/package/src/rpc/system/admin/v1/base_job.ts +10 -1
- package/dist/package/src/rpc/system/admin/v1/base_language.ts +8 -2
- package/dist/package/src/rpc/system/admin/v1/base_menu.ts +5 -41
- package/dist/package/src/rpc/system/admin/v1/base_migration.ts +8 -8
- package/dist/package/src/rpc/system/admin/v1/base_role.ts +14 -1
- package/dist/package/src/rpc/system/admin/v1/base_translation.ts +54 -81
- package/dist/package/src/rpc/system/admin/v1/base_user.ts +12 -1
- package/dist/package/src/rpc/system/admin/v1/code_gen.ts +11 -11
- package/dist/package/src/rpc/system/admin/v1/code_gen_table.ts +12 -1
- package/dist/package/src/rpc/system/admin/v1/common.ts +21 -0
- package/dist/package/src/rpc/system/admin/v1/ops_monitoring.ts +255 -0
- package/dist/package/src/views/ai/chat/components/ChatPanel.vue +36 -36
- package/dist/package/src/views/ai/chat/components/SessionPanel.vue +6 -6
- package/dist/package/src/views/ai/chat/components/XSender.vue +20 -20
- package/dist/package/src/views/ai/chat/index.vue +33 -32
- package/dist/package/src/views/ai/chat/message.ts +16 -15
- package/dist/package/src/views/base/api/index.vue +55 -56
- package/dist/package/src/views/base/area/index.vue +18 -19
- package/dist/package/src/views/base/config/index.vue +143 -133
- package/dist/package/src/views/base/dept/index.vue +42 -43
- package/dist/package/src/views/base/dict/index.vue +49 -45
- package/dist/package/src/views/base/dict/item.vue +54 -49
- package/dist/package/src/views/base/job/index.vue +51 -52
- package/dist/package/src/views/base/job/log.vue +18 -18
- package/dist/package/src/views/{tool → base}/language/index.vue +51 -47
- package/dist/package/src/views/base/log/index.vue +40 -40
- package/dist/package/src/views/base/menu/index.vue +220 -177
- package/dist/package/src/views/base/migration/index.vue +24 -24
- package/dist/package/src/views/base/post/index.vue +42 -43
- package/dist/package/src/views/base/role/index.vue +54 -55
- package/dist/package/src/views/base/tenant/index.vue +42 -43
- package/dist/package/src/views/base/user/components/dept-tree.vue +1 -1
- package/dist/package/src/views/base/user/index.vue +69 -71
- package/dist/package/src/views/profile/components/base.vue +9 -9
- package/dist/package/src/views/profile/components/password.vue +19 -20
- package/dist/package/src/views/profile/components/security.vue +20 -20
- package/dist/package/src/views/profile/index.vue +3 -3
- package/dist/package/src/views/tool/api-doc/index.vue +1 -1
- package/dist/package/src/views/tool/code-gen/code-preview/index.vue +13 -13
- package/dist/package/src/views/tool/code-gen/columns/index.vue +89 -89
- package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +5 -5
- package/dist/package/src/views/tool/code-gen/components/CodeGenProgressDialog.vue +21 -21
- package/dist/package/src/views/tool/code-gen/components/CodePreviewPane.vue +5 -5
- package/dist/package/src/views/tool/code-gen/config.ts +60 -60
- package/dist/package/src/views/tool/code-gen/preview/data.ts +2 -2
- package/dist/package/src/views/tool/code-gen/preview/index.vue +30 -31
- package/dist/package/src/views/tool/code-gen/proto/index.vue +43 -43
- package/dist/package/src/views/tool/code-gen/table/index.vue +95 -95
- package/dist/package/src/views/tool/ops-monitoring/index.vue +1175 -0
- package/dist/package/src/views/tool/project-doc/index.vue +7 -7
- package/package.json +2 -2
- package/dist/package/declarations/src/rpc/base/v1/enum.d.ts +0 -43
- package/dist/package/declarations/src/rpc/system/common/v1/enum.d.ts +0 -67
- package/dist/package/src/rpc/base/v1/enum.ts +0 -54
- package/dist/package/src/rpc/system/common/v1/enum.ts +0 -80
- /package/dist/package/declarations/src/views/{tool → base}/language/index.vue.d.ts +0 -0
|
@@ -1,129 +1,230 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
:
|
|
9
|
-
|
|
2
|
+
<el-link class="dynamic-translation-cell" type="primary" underline="never" @click="openDialog">
|
|
3
|
+
<span class="dynamic-translation-cell__source">{{ displaySource || "--" }}</span>
|
|
4
|
+
</el-link>
|
|
5
|
+
|
|
6
|
+
<ProDialog
|
|
7
|
+
v-model="dialogVisible"
|
|
8
|
+
:title="displaySource"
|
|
9
|
+
width="760px"
|
|
10
|
+
append-to-body
|
|
11
|
+
:destroy-on-close="true"
|
|
12
|
+
@closed="handleDialogClosed"
|
|
10
13
|
>
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
<el-table v-loading="loading" :data="translationRows" border>
|
|
15
|
+
<el-table-column :label="t('common.field.language')" width="150">
|
|
16
|
+
<template #default="{ row }">
|
|
17
|
+
{{ getLanguageLabel(row.locale) }}
|
|
18
|
+
</template>
|
|
19
|
+
</el-table-column>
|
|
20
|
+
<el-table-column :label="t('system.base.translation.field.translations')" min-width="360">
|
|
21
|
+
<template #default="{ row }">
|
|
22
|
+
<el-input
|
|
23
|
+
v-if="row.editing"
|
|
24
|
+
v-model="row.text"
|
|
25
|
+
:placeholder="t('system.base.translation.placeholder.text', { language: getLanguageLabel(row.locale) })"
|
|
26
|
+
/>
|
|
27
|
+
<span v-else class="dynamic-translation-dialog__text">
|
|
28
|
+
{{ row.text || t("common.value.none") }}
|
|
29
|
+
</span>
|
|
30
|
+
</template>
|
|
31
|
+
</el-table-column>
|
|
32
|
+
<el-table-column :label="t('common.field.operation')" width="150" fixed="right">
|
|
33
|
+
<template #default="{ row }">
|
|
34
|
+
<el-button v-if="!row.editing" link type="primary" :icon="EditPen" @click="startEdit(row)">
|
|
35
|
+
{{ t("common.action.edit") }}
|
|
36
|
+
</el-button>
|
|
37
|
+
<template v-else>
|
|
38
|
+
<el-button link type="primary" :icon="Check" :loading="row.saving" @click="saveTranslation(row)">
|
|
39
|
+
{{ t("common.action.save") }}
|
|
40
|
+
</el-button>
|
|
41
|
+
<el-button link :icon="Close" :disabled="row.saving" @click="cancelEdit(row)">
|
|
42
|
+
{{ t("common.action.cancel") }}
|
|
43
|
+
</el-button>
|
|
44
|
+
</template>
|
|
45
|
+
</template>
|
|
46
|
+
</el-table-column>
|
|
47
|
+
</el-table>
|
|
48
|
+
</ProDialog>
|
|
27
49
|
</template>
|
|
28
50
|
|
|
29
51
|
<script setup lang="ts">
|
|
30
|
-
import { computed } from "vue";
|
|
52
|
+
import { computed, ref, watch } from "vue";
|
|
53
|
+
import { ElMessage } from "element-plus";
|
|
54
|
+
import { Check, Close, EditPen } from "@element-plus/icons-vue";
|
|
55
|
+
import ProDialog from "@liujitcn/kratos-admin-core/components/Dialog/ProDialog.vue";
|
|
31
56
|
import { t, useLocaleStore } from "@liujitcn/kratos-admin-core";
|
|
32
|
-
import { useEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/base_language";
|
|
33
|
-
import {
|
|
57
|
+
import { loadEnabledBaseLanguages, useEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/base_language";
|
|
58
|
+
import { defBaseTranslationService } from "@liujitcn/kratos-admin-system/api/system/base_translation";
|
|
59
|
+
import type { BaseTranslation } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_translation";
|
|
60
|
+
import { TranslationTargetType } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_translation";
|
|
34
61
|
import { getLanguageLabel } from "./dynamicTranslation";
|
|
35
62
|
|
|
36
|
-
/** DynamicTranslationCellRecord 描述列表翻译预览所需的最小记录结构。 */
|
|
37
|
-
export interface DynamicTranslationCellRecord {
|
|
38
|
-
/** 语言区域。 */
|
|
39
|
-
locale: string;
|
|
40
|
-
/** 通用翻译文本,供系统配置使用。 */
|
|
41
|
-
text?: string;
|
|
42
|
-
/** 字典名称翻译。 */
|
|
43
|
-
name?: string;
|
|
44
|
-
/** 字典项标签翻译。 */
|
|
45
|
-
label?: string;
|
|
46
|
-
/** 菜单标题翻译。 */
|
|
47
|
-
title?: string;
|
|
48
|
-
/** 翻译审核状态。 */
|
|
49
|
-
translation_status?: TranslationStatus;
|
|
50
|
-
/** 中文源文是否已变化。 */
|
|
51
|
-
source_changed?: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
63
|
/** DynamicTranslationCellProps 动态翻译列表单元格属性。 */
|
|
55
64
|
interface DynamicTranslationCellProps {
|
|
56
65
|
/** 资源源文。 */
|
|
57
66
|
source: string;
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
/** 是否显示翻译状态标签。 */
|
|
65
|
-
showStatus?: boolean;
|
|
67
|
+
/** 统一翻译表目标类型。 */
|
|
68
|
+
targetType?: TranslationTargetType;
|
|
69
|
+
/** 目标资源编号。 */
|
|
70
|
+
targetId?: number;
|
|
71
|
+
/** 资源列表返回的非主语言翻译。 */
|
|
72
|
+
translations?: BaseTranslation[];
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
const props =
|
|
69
|
-
translations: () => [],
|
|
70
|
-
sourceLocale: "zh-CN",
|
|
71
|
-
textField: "text",
|
|
72
|
-
showStatus: true
|
|
73
|
-
});
|
|
75
|
+
const props = defineProps<DynamicTranslationCellProps>();
|
|
74
76
|
|
|
75
|
-
const { locale } = useLocaleStore();
|
|
76
77
|
const { languages } = useEnabledBaseLanguages();
|
|
77
|
-
const
|
|
78
|
+
const { locale } = useLocaleStore();
|
|
79
|
+
const dialogVisible = ref(false);
|
|
80
|
+
const loading = ref(false);
|
|
81
|
+
const translationRows = ref<DynamicTranslationRow[]>([]);
|
|
82
|
+
const translationOverrides = ref(new Map<string, string>());
|
|
78
83
|
|
|
79
84
|
const displaySource = computed(() => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return
|
|
85
|
+
const override = translationOverrides.value.get(locale.value);
|
|
86
|
+
if (override) return override;
|
|
87
|
+
const translation = props.translations?.find(item => item.locale === locale.value && item.name);
|
|
88
|
+
return translation?.name || props.source;
|
|
84
89
|
});
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
watch(
|
|
92
|
+
() => [props.targetType, props.targetId],
|
|
93
|
+
() => {
|
|
94
|
+
translationRows.value = [];
|
|
95
|
+
translationOverrides.value.clear();
|
|
96
|
+
dialogVisible.value = false;
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
/** DynamicTranslationRow 描述弹窗内可编辑的单语言翻译行。 */
|
|
101
|
+
interface DynamicTranslationRow {
|
|
102
|
+
/** 翻译记录主键,新增翻译时为空。 */
|
|
103
|
+
id: number;
|
|
104
|
+
/** 语言区域。 */
|
|
105
|
+
locale: string;
|
|
106
|
+
/** 翻译文本。 */
|
|
107
|
+
text: string;
|
|
108
|
+
/** 是否处于编辑状态。 */
|
|
109
|
+
editing: boolean;
|
|
110
|
+
/** 是否正在保存。 */
|
|
111
|
+
saving: boolean;
|
|
112
|
+
/** 编辑前的文本,用于取消编辑。 */
|
|
113
|
+
originalText: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** openDialog 打开翻译编辑弹窗并加载当前资源译文。 */
|
|
117
|
+
async function openDialog() {
|
|
118
|
+
dialogVisible.value = true;
|
|
119
|
+
await loadTranslations();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** loadTranslations 查询当前资源的翻译并补齐启用语言行。 */
|
|
123
|
+
async function loadTranslations() {
|
|
124
|
+
const targetType = props.targetType;
|
|
125
|
+
const targetId = props.targetId;
|
|
126
|
+
if (!targetType || !targetId) return;
|
|
127
|
+
loading.value = true;
|
|
128
|
+
try {
|
|
129
|
+
await loadEnabledBaseLanguages();
|
|
130
|
+
const rows = await queryTranslationRows();
|
|
131
|
+
translationRows.value = rows;
|
|
132
|
+
const missingRows = rows.filter(row => !row.text);
|
|
133
|
+
if (missingRows.length === 0) return;
|
|
134
|
+
if (!props.source) return;
|
|
135
|
+
const draft = await defBaseTranslationService.DraftBaseTranslation({ source: props.source });
|
|
136
|
+
const translations = new Map(draft.translations.map(item => [item.locale, item.translation]));
|
|
137
|
+
const results = await Promise.allSettled(
|
|
138
|
+
missingRows.map(async row => {
|
|
139
|
+
const translation = translations.get(row.locale);
|
|
140
|
+
if (!translation) throw new Error("translation not found");
|
|
141
|
+
await defBaseTranslationService.UpdateBaseTranslation({
|
|
142
|
+
id: row.id,
|
|
143
|
+
target_type: targetType,
|
|
144
|
+
target_id: targetId,
|
|
145
|
+
locale: row.locale,
|
|
146
|
+
name: translation
|
|
147
|
+
});
|
|
148
|
+
return { row, text: translation };
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
for (const result of results) {
|
|
152
|
+
if (result.status !== "fulfilled") continue;
|
|
153
|
+
result.value.row.text = result.value.text;
|
|
154
|
+
result.value.row.originalText = result.value.text;
|
|
155
|
+
translationOverrides.value.set(result.value.row.locale, result.value.text);
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
ElMessage.error(t("common.message.system_error"));
|
|
159
|
+
} finally {
|
|
160
|
+
loading.value = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** queryTranslationRows 将列表返回的翻译记录补齐为可编辑语言行。 */
|
|
165
|
+
async function queryTranslationRows(): Promise<DynamicTranslationRow[]> {
|
|
166
|
+
const records = new Map((props.translations ?? []).map(item => [item.locale, item]));
|
|
167
|
+
return languages.value
|
|
168
|
+
.filter(item => !item.is_primary)
|
|
89
169
|
.map(item => {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const record = props.translations.find(item => item.locale === itemLocale);
|
|
93
|
-
const text = isSource ? props.source : record ? getTranslationText(record) : "";
|
|
170
|
+
const record = records.get(item.language_code);
|
|
171
|
+
const text = translationOverrides.value.get(item.language_code) ?? record?.name ?? "";
|
|
94
172
|
return {
|
|
95
|
-
|
|
173
|
+
id: record?.id ?? 0,
|
|
174
|
+
locale: item.language_code,
|
|
96
175
|
text,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
176
|
+
editing: false,
|
|
177
|
+
saving: false,
|
|
178
|
+
originalText: text
|
|
100
179
|
};
|
|
101
|
-
})
|
|
102
|
-
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** startEdit 开启指定语言的编辑状态。 */
|
|
184
|
+
function startEdit(row: DynamicTranslationRow) {
|
|
185
|
+
row.originalText = row.text;
|
|
186
|
+
row.editing = true;
|
|
187
|
+
}
|
|
103
188
|
|
|
104
|
-
/**
|
|
105
|
-
function
|
|
106
|
-
|
|
189
|
+
/** cancelEdit 恢复编辑前的内容。 */
|
|
190
|
+
function cancelEdit(row: DynamicTranslationRow) {
|
|
191
|
+
row.text = row.originalText;
|
|
192
|
+
row.editing = false;
|
|
107
193
|
}
|
|
108
194
|
|
|
109
|
-
/**
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
195
|
+
/** saveTranslation 使用统一更新接口保存指定语言译文。 */
|
|
196
|
+
async function saveTranslation(row: DynamicTranslationRow) {
|
|
197
|
+
const targetType = props.targetType;
|
|
198
|
+
const targetId = props.targetId;
|
|
199
|
+
if (row.saving || !targetType || !targetId) return;
|
|
200
|
+
if (row.id === 0 && !row.text) {
|
|
201
|
+
row.editing = false;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
row.saving = true;
|
|
205
|
+
try {
|
|
206
|
+
await defBaseTranslationService.UpdateBaseTranslation({
|
|
207
|
+
id: row.id,
|
|
208
|
+
target_type: targetType,
|
|
209
|
+
target_id: targetId,
|
|
210
|
+
locale: row.locale,
|
|
211
|
+
name: row.text
|
|
212
|
+
});
|
|
213
|
+
ElMessage.success(t("common.message.operation_success"));
|
|
214
|
+
row.editing = false;
|
|
215
|
+
row.originalText = row.text;
|
|
216
|
+
translationOverrides.value.set(row.locale, row.text);
|
|
217
|
+
} catch {
|
|
218
|
+
ElMessage.error(t("common.message.system_error"));
|
|
219
|
+
} finally {
|
|
220
|
+
row.saving = false;
|
|
221
|
+
}
|
|
119
222
|
}
|
|
120
223
|
|
|
121
|
-
/**
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (item.translation_status === TranslationStatus.TRANSLATION_STATUS_MACHINE) return "primary";
|
|
126
|
-
return "info";
|
|
224
|
+
/** handleDialogClosed 清理弹窗状态,避免复用旧资源译文。 */
|
|
225
|
+
function handleDialogClosed() {
|
|
226
|
+
translationRows.value = [];
|
|
227
|
+
loading.value = false;
|
|
127
228
|
}
|
|
128
229
|
</script>
|
|
129
230
|
|
|
@@ -141,36 +242,8 @@ function statusTagType(item: (typeof translationRows.value)[number]) {
|
|
|
141
242
|
text-overflow: ellipsis;
|
|
142
243
|
white-space: nowrap;
|
|
143
244
|
}
|
|
144
|
-
|
|
145
|
-
</style>
|
|
146
|
-
|
|
147
|
-
<style>
|
|
148
|
-
.dynamic-translation-cell__popover .dynamic-translation-cell__rows {
|
|
149
|
-
display: flex;
|
|
150
|
-
flex-direction: column;
|
|
151
|
-
gap: 10px;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.dynamic-translation-cell__popover {
|
|
155
|
-
max-width: calc(100vw - 24px);
|
|
156
|
-
box-sizing: border-box;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.dynamic-translation-cell__popover .dynamic-translation-cell__row {
|
|
160
|
-
display: grid;
|
|
161
|
-
grid-template-columns: 76px minmax(0, 1fr) auto;
|
|
162
|
-
gap: 10px;
|
|
163
|
-
align-items: center;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.dynamic-translation-cell__popover .dynamic-translation-cell__language {
|
|
167
|
-
color: var(--el-text-color-regular);
|
|
168
|
-
font-weight: 600;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.dynamic-translation-cell__popover .dynamic-translation-cell__text {
|
|
245
|
+
.dynamic-translation-dialog__text {
|
|
172
246
|
min-width: 0;
|
|
173
|
-
color: var(--el-text-color-primary);
|
|
174
247
|
overflow-wrap: anywhere;
|
|
175
248
|
}
|
|
176
249
|
</style>
|
|
@@ -1,75 +1,78 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="translation-editor">
|
|
3
|
+
<div class="translation-editor__toolbar">
|
|
4
|
+
<el-button
|
|
5
|
+
type="primary"
|
|
6
|
+
size="small"
|
|
7
|
+
:icon="Promotion"
|
|
8
|
+
:loading="translating"
|
|
9
|
+
:disabled="!canTranslate"
|
|
10
|
+
@click="handleBatchTranslate"
|
|
11
|
+
>
|
|
12
|
+
{{ t("system.base.translation.action.batch_translate") }}
|
|
13
|
+
</el-button>
|
|
14
|
+
</div>
|
|
3
15
|
<div v-for="item in localValues" :key="item.locale" class="translation-editor__row">
|
|
4
16
|
<div class="translation-editor__heading">
|
|
5
17
|
<span>{{ getLanguageLabel(item.locale) }}</span>
|
|
6
|
-
<el-tag :type="statusTagType(item)" effect="light" size="small">{{ statusLabel(item) }}</el-tag>
|
|
7
18
|
</div>
|
|
8
|
-
<div class="translation-editor__control">
|
|
19
|
+
<div class="translation-editor__control" :class="{ 'translation-editor__control--multiline': multiline }">
|
|
9
20
|
<el-input
|
|
10
21
|
:model-value="item.text"
|
|
11
22
|
:maxlength="maxlength"
|
|
12
23
|
:type="multiline ? 'textarea' : 'text'"
|
|
13
24
|
:rows="multiline ? 6 : undefined"
|
|
25
|
+
:disabled="translating || translatingLocale === item.locale"
|
|
14
26
|
show-word-limit
|
|
15
|
-
:placeholder="t('system.translation.placeholder.text', { language: getLanguageLabel(item.locale) })"
|
|
27
|
+
:placeholder="t('system.base.translation.placeholder.text', { language: getLanguageLabel(item.locale) })"
|
|
16
28
|
@update:model-value="value => updateText(item.locale, value)"
|
|
17
29
|
/>
|
|
18
|
-
<el-
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
<el-button
|
|
31
|
+
class="translation-editor__translate"
|
|
32
|
+
link
|
|
33
|
+
type="primary"
|
|
34
|
+
size="small"
|
|
35
|
+
:icon="Promotion"
|
|
36
|
+
:loading="translatingLocale === item.locale"
|
|
37
|
+
:disabled="!props.source || Boolean(item.text) || translating || Boolean(translatingLocale)"
|
|
38
|
+
@click="handleTranslate(item.locale)"
|
|
21
39
|
>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
:loading="loadingLocale === item.locale"
|
|
25
|
-
:disabled="item.translation_status === TranslationStatus.TRANSLATION_STATUS_REVIEWED && !item.source_changed"
|
|
26
|
-
@click="generateDraft(item.locale)"
|
|
27
|
-
/>
|
|
28
|
-
</el-tooltip>
|
|
40
|
+
{{ t("system.base.translation.action.translate") }}
|
|
41
|
+
</el-button>
|
|
29
42
|
</div>
|
|
30
43
|
</div>
|
|
31
|
-
<el-alert
|
|
32
|
-
v-if="resourceId === 0 && draftEnabled"
|
|
33
|
-
:title="t('system.translation.message.saveBeforeDraft')"
|
|
34
|
-
type="info"
|
|
35
|
-
:closable="false"
|
|
36
|
-
show-icon
|
|
37
|
-
/>
|
|
38
44
|
</div>
|
|
39
45
|
</template>
|
|
40
46
|
|
|
41
47
|
<script setup lang="ts">
|
|
42
|
-
import { ref, watch } from "vue";
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
48
|
+
import { computed, ref, watch } from "vue";
|
|
49
|
+
import { ElMessage } from "element-plus";
|
|
50
|
+
import { Promotion } from "@element-plus/icons-vue";
|
|
51
|
+
import { t, useLocaleStore } from "@liujitcn/kratos-admin-core";
|
|
45
52
|
import { defBaseTranslationService } from "@liujitcn/kratos-admin-system/api/system/base_translation";
|
|
46
|
-
import {
|
|
47
|
-
BaseConfigTranslationField,
|
|
48
|
-
TranslationResourceType,
|
|
49
|
-
TranslationStatus
|
|
50
|
-
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_translation";
|
|
51
53
|
import { getLanguageLabel, type DynamicTranslationValue } from "./dynamicTranslation";
|
|
52
54
|
|
|
53
55
|
/** DynamicTranslationEditorProps 动态翻译编辑器属性。 */
|
|
54
56
|
interface DynamicTranslationEditorProps {
|
|
55
57
|
modelValue: DynamicTranslationValue[];
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
field?: BaseConfigTranslationField;
|
|
59
|
-
draftEnabled?: boolean;
|
|
58
|
+
/** 当前语言源文本。 */
|
|
59
|
+
source?: string;
|
|
60
60
|
maxlength?: number;
|
|
61
61
|
multiline?: boolean;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const props = withDefaults(defineProps<DynamicTranslationEditorProps>(), {
|
|
65
|
-
|
|
65
|
+
source: "",
|
|
66
66
|
maxlength: 100,
|
|
67
67
|
multiline: false
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
const { locale: currentLocale } = useLocaleStore();
|
|
70
71
|
const emit = defineEmits<{ "update:modelValue": [value: DynamicTranslationValue[]] }>();
|
|
71
72
|
const localValues = ref<DynamicTranslationValue[]>([]);
|
|
72
|
-
const
|
|
73
|
+
const translating = ref(false);
|
|
74
|
+
const translatingLocale = ref("");
|
|
75
|
+
const canTranslate = computed(() => Boolean(props.source) && localValues.value.some(item => !item.text && item.locale !== currentLocale.value));
|
|
73
76
|
|
|
74
77
|
watch(
|
|
75
78
|
() => props.modelValue,
|
|
@@ -86,56 +89,60 @@ function updateText(locale: DynamicTranslationValue["locale"], text: string) {
|
|
|
86
89
|
emit("update:modelValue", values);
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
/**
|
|
90
|
-
async function
|
|
91
|
-
|
|
92
|
+
/** handleBatchTranslate 批量翻译当前仍为空的语言输入框。 */
|
|
93
|
+
async function handleBatchTranslate() {
|
|
94
|
+
if (!canTranslate.value || translating.value) return;
|
|
95
|
+
translating.value = true;
|
|
92
96
|
try {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
97
|
+
const pending = localValues.value.filter(item => !item.text && item.locale !== currentLocale.value);
|
|
98
|
+
const response = await defBaseTranslationService.DraftBaseTranslation({ source: props.source });
|
|
99
|
+
const translations = new Map(response.translations.map(item => [item.locale, item.translation]));
|
|
100
|
+
const failed = pending.filter(item => !translations.has(item.locale)).length;
|
|
101
|
+
if (translations.size) {
|
|
102
|
+
const values = localValues.value.map(item => {
|
|
103
|
+
const translation = translations.get(item.locale);
|
|
104
|
+
return translation === undefined ? item : { ...item, text: translation };
|
|
105
|
+
});
|
|
106
|
+
localValues.value = values;
|
|
107
|
+
emit("update:modelValue", values);
|
|
108
|
+
}
|
|
109
|
+
if (failed) {
|
|
110
|
+
ElMessage.warning(
|
|
111
|
+
t("system.base.translation.message.batch_translate_partial", {
|
|
112
|
+
success: translations.size,
|
|
113
|
+
failed
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
} else {
|
|
117
|
+
ElMessage.success(t("system.base.translation.message.batch_translate_success", { count: translations.size }));
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
ElMessage.error(t("system.base.translation.message.batch_translate_failed"));
|
|
115
121
|
} finally {
|
|
116
|
-
|
|
122
|
+
translating.value = false;
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
/**
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
/** handleTranslate 翻译指定语言的单个输入框。 */
|
|
127
|
+
async function handleTranslate(locale: string) {
|
|
128
|
+
const item = localValues.value.find(value => value.locale === locale);
|
|
129
|
+
if (!item || item.text || item.locale === currentLocale.value || !props.source || translating.value || translatingLocale.value) return;
|
|
130
|
+
translatingLocale.value = locale;
|
|
131
|
+
try {
|
|
132
|
+
const response = await defBaseTranslationService.DraftBaseTranslation({ source: props.source, locale });
|
|
133
|
+
const translation = response.translations.find(item => item.locale === locale)?.translation;
|
|
134
|
+
if (!translation) throw new Error("translation not found");
|
|
135
|
+
const values = localValues.value.map(value => (value.locale === locale ? { ...value, text: translation } : value));
|
|
136
|
+
localValues.value = values;
|
|
137
|
+
emit("update:modelValue", values);
|
|
138
|
+
ElMessage.success(t("system.base.translation.message.translate_success", { language: getLanguageLabel(locale) }));
|
|
139
|
+
} catch {
|
|
140
|
+
ElMessage.error(t("system.base.translation.message.translate_failed", { language: getLanguageLabel(locale) }));
|
|
141
|
+
} finally {
|
|
142
|
+
translatingLocale.value = "";
|
|
143
|
+
}
|
|
130
144
|
}
|
|
131
145
|
|
|
132
|
-
/** statusTagType 返回当前翻译审核状态对应的标签样式。 */
|
|
133
|
-
function statusTagType(item: DynamicTranslationValue) {
|
|
134
|
-
if (item.source_changed) return "warning";
|
|
135
|
-
if (item.translation_status === TranslationStatus.TRANSLATION_STATUS_REVIEWED) return "success";
|
|
136
|
-
if (item.translation_status === TranslationStatus.TRANSLATION_STATUS_MACHINE) return "primary";
|
|
137
|
-
return "info";
|
|
138
|
-
}
|
|
139
146
|
</script>
|
|
140
147
|
|
|
141
148
|
<style scoped>
|
|
@@ -145,6 +152,10 @@ function statusTagType(item: DynamicTranslationValue) {
|
|
|
145
152
|
gap: 14px;
|
|
146
153
|
width: 100%;
|
|
147
154
|
}
|
|
155
|
+
.translation-editor__toolbar {
|
|
156
|
+
display: flex;
|
|
157
|
+
justify-content: flex-end;
|
|
158
|
+
}
|
|
148
159
|
.translation-editor__row {
|
|
149
160
|
display: flex;
|
|
150
161
|
flex-direction: column;
|
|
@@ -157,10 +168,30 @@ function statusTagType(item: DynamicTranslationValue) {
|
|
|
157
168
|
align-items: center;
|
|
158
169
|
}
|
|
159
170
|
.translation-editor__heading {
|
|
160
|
-
justify-content: space-between;
|
|
161
171
|
color: var(--el-text-color-regular);
|
|
162
172
|
}
|
|
173
|
+
.translation-editor__control {
|
|
174
|
+
position: relative;
|
|
175
|
+
width: 100%;
|
|
176
|
+
}
|
|
163
177
|
.translation-editor__control :deep(.el-input) {
|
|
164
178
|
flex: 1;
|
|
165
179
|
}
|
|
180
|
+
.translation-editor__control--multiline :deep(.el-textarea__inner) {
|
|
181
|
+
padding-right: 78px;
|
|
182
|
+
}
|
|
183
|
+
.translation-editor__control:not(.translation-editor__control--multiline) :deep(.el-input__suffix) {
|
|
184
|
+
padding-right: 78px;
|
|
185
|
+
}
|
|
186
|
+
.translation-editor__translate {
|
|
187
|
+
position: absolute;
|
|
188
|
+
z-index: 1;
|
|
189
|
+
top: 50%;
|
|
190
|
+
right: 8px;
|
|
191
|
+
transform: translateY(-50%);
|
|
192
|
+
}
|
|
193
|
+
.translation-editor__control--multiline .translation-editor__translate {
|
|
194
|
+
top: 8px;
|
|
195
|
+
transform: none;
|
|
196
|
+
}
|
|
166
197
|
</style>
|