@liujitcn/kratos-admin-system 0.0.20 → 0.0.23

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 (118) hide show
  1. package/README.md +51 -50
  2. package/dist/package/declarations/src/api/system/base_language.d.ts +52 -0
  3. package/dist/package/declarations/src/api/system/base_translation.d.ts +10 -0
  4. package/dist/package/declarations/src/api/system/code_gen_i18n.d.ts +8 -0
  5. package/dist/package/declarations/src/api/system/ops_monitoring.d.ts +19 -0
  6. package/dist/package/declarations/src/api/system/ops_monitoring_sse.d.ts +12 -0
  7. package/dist/package/declarations/src/components/DynamicTranslationCell.vue.d.ts +16 -0
  8. package/dist/package/declarations/src/components/DynamicTranslationEditor.vue.d.ts +20 -0
  9. package/dist/package/declarations/src/components/dynamicTranslation.d.ts +38 -0
  10. package/dist/package/declarations/src/locales/generated.d.ts +6536 -0
  11. package/dist/package/declarations/src/rpc/base/v1/ai_session.d.ts +12 -2
  12. package/dist/package/declarations/src/rpc/base/v1/ai_tool.d.ts +9 -1
  13. package/dist/package/declarations/src/rpc/base/v1/config.d.ts +35 -0
  14. package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +1 -1
  15. package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +23 -2
  16. package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +9 -0
  17. package/dist/package/declarations/src/rpc/system/admin/v1/base_job.d.ts +9 -1
  18. package/dist/package/declarations/src/rpc/system/admin/v1/base_language.d.ts +119 -0
  19. package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +6 -1
  20. package/dist/package/declarations/src/rpc/system/admin/v1/base_migration.d.ts +6 -6
  21. package/dist/package/declarations/src/rpc/system/admin/v1/base_role.d.ts +13 -1
  22. package/dist/package/declarations/src/rpc/system/admin/v1/base_translation.d.ts +75 -0
  23. package/dist/package/declarations/src/rpc/system/admin/v1/base_user.d.ts +11 -1
  24. package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +13 -11
  25. package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_column.d.ts +7 -0
  26. package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_table.d.ts +24 -1
  27. package/dist/package/declarations/src/rpc/system/admin/v1/common.d.ts +13 -0
  28. package/dist/package/declarations/src/rpc/system/admin/v1/ops_monitoring.d.ts +222 -0
  29. package/dist/package/declarations/src/views/base/language/index.vue.d.ts +3 -0
  30. package/dist/package/declarations/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue.d.ts +23 -0
  31. package/dist/package/declarations/src/views/tool/code-gen/config.d.ts +7 -7
  32. package/dist/package/declarations/src/views/tool/ops-monitoring/index.vue.d.ts +3 -0
  33. package/dist/package/src/api/base/ai_message.ts +7 -12
  34. package/dist/package/src/api/base/sse.ts +3 -1
  35. package/dist/package/src/api/system/base_language.ts +135 -0
  36. package/dist/package/src/api/system/base_translation.ts +33 -0
  37. package/dist/package/src/api/system/code_gen_column.ts +17 -3
  38. package/dist/package/src/api/system/code_gen_i18n.ts +18 -0
  39. package/dist/package/src/api/system/code_gen_table.ts +12 -4
  40. package/dist/package/src/api/system/ops_monitoring.ts +88 -0
  41. package/dist/package/src/api/system/ops_monitoring_sse.ts +49 -0
  42. package/dist/package/src/components/DynamicTranslationCell.vue +249 -0
  43. package/dist/package/src/components/DynamicTranslationEditor.vue +197 -0
  44. package/dist/package/src/components/dynamicTranslation.ts +66 -0
  45. package/dist/package/src/locales/README.md +30 -0
  46. package/dist/package/src/locales/en-US.json +933 -0
  47. package/dist/package/src/locales/es-ES.json +933 -0
  48. package/dist/package/src/locales/fr-FR.json +933 -0
  49. package/dist/package/src/locales/generated.ts +22 -0
  50. package/dist/package/src/locales/ja-JP.json +933 -0
  51. package/dist/package/src/locales/ko-KR.json +933 -0
  52. package/dist/package/src/locales/zh-CN.json +933 -0
  53. package/dist/package/src/locales/zh-TW.json +933 -0
  54. package/dist/package/src/module.ts +6 -2
  55. package/dist/package/src/rpc/base/v1/ai_session.ts +13 -2
  56. package/dist/package/src/rpc/base/v1/ai_tool.ts +10 -1
  57. package/dist/package/src/rpc/base/v1/config.ts +47 -0
  58. package/dist/package/src/rpc/system/admin/v1/auth.ts +1 -1
  59. package/dist/package/src/rpc/system/admin/v1/base_config.ts +24 -2
  60. package/dist/package/src/rpc/system/admin/v1/base_dict.ts +9 -0
  61. package/dist/package/src/rpc/system/admin/v1/base_job.ts +10 -1
  62. package/dist/package/src/rpc/system/admin/v1/base_language.ts +141 -0
  63. package/dist/package/src/rpc/system/admin/v1/base_menu.ts +6 -1
  64. package/dist/package/src/rpc/system/admin/v1/base_migration.ts +8 -8
  65. package/dist/package/src/rpc/system/admin/v1/base_role.ts +14 -1
  66. package/dist/package/src/rpc/system/admin/v1/base_translation.ts +90 -0
  67. package/dist/package/src/rpc/system/admin/v1/base_user.ts +12 -1
  68. package/dist/package/src/rpc/system/admin/v1/code_gen.ts +16 -12
  69. package/dist/package/src/rpc/system/admin/v1/code_gen_column.ts +8 -0
  70. package/dist/package/src/rpc/system/admin/v1/code_gen_table.ts +27 -1
  71. package/dist/package/src/rpc/system/admin/v1/common.ts +21 -0
  72. package/dist/package/src/rpc/system/admin/v1/ops_monitoring.ts +255 -0
  73. package/dist/package/src/views/ai/chat/components/AiMarkdown.vue +1 -6
  74. package/dist/package/src/views/ai/chat/components/ChatPanel.vue +75 -58
  75. package/dist/package/src/views/ai/chat/components/SessionPanel.vue +20 -9
  76. package/dist/package/src/views/ai/chat/components/XSender.vue +36 -23
  77. package/dist/package/src/views/ai/chat/index.vue +58 -41
  78. package/dist/package/src/views/ai/chat/message.ts +29 -27
  79. package/dist/package/src/views/base/api/index.vue +121 -84
  80. package/dist/package/src/views/base/area/index.vue +81 -32
  81. package/dist/package/src/views/base/config/index.vue +300 -96
  82. package/dist/package/src/views/base/dept/index.vue +110 -62
  83. package/dist/package/src/views/base/dict/index.vue +145 -62
  84. package/dist/package/src/views/base/dict/item.vue +155 -64
  85. package/dist/package/src/views/base/job/index.vue +134 -82
  86. package/dist/package/src/views/base/job/log.vue +24 -25
  87. package/dist/package/src/views/base/language/index.vue +260 -0
  88. package/dist/package/src/views/base/log/index.vue +68 -46
  89. package/dist/package/src/views/base/menu/index.vue +344 -200
  90. package/dist/package/src/views/base/migration/index.vue +49 -29
  91. package/dist/package/src/views/base/post/index.vue +128 -60
  92. package/dist/package/src/views/base/role/index.vue +145 -79
  93. package/dist/package/src/views/base/tenant/index.vue +132 -64
  94. package/dist/package/src/views/base/user/components/dept-tree.vue +2 -1
  95. package/dist/package/src/views/base/user/index.vue +194 -116
  96. package/dist/package/src/views/profile/components/base.vue +45 -28
  97. package/dist/package/src/views/profile/components/password.vue +45 -30
  98. package/dist/package/src/views/profile/components/security.vue +90 -54
  99. package/dist/package/src/views/profile/index.vue +10 -9
  100. package/dist/package/src/views/tool/api-doc/index.vue +6 -2
  101. package/dist/package/src/views/tool/code-gen/code-preview/index.vue +32 -16
  102. package/dist/package/src/views/tool/code-gen/columns/index.vue +382 -273
  103. package/dist/package/src/views/tool/code-gen/columns/option-copy.ts +1 -6
  104. package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +118 -0
  105. package/dist/package/src/views/tool/code-gen/components/CodeGenProgressDialog.vue +65 -36
  106. package/dist/package/src/views/tool/code-gen/components/CodePreviewPane.vue +8 -5
  107. package/dist/package/src/views/tool/code-gen/config.ts +116 -79
  108. package/dist/package/src/views/tool/code-gen/preview/data.ts +19 -17
  109. package/dist/package/src/views/tool/code-gen/preview/index.vue +96 -59
  110. package/dist/package/src/views/tool/code-gen/proto/index.vue +110 -62
  111. package/dist/package/src/views/tool/code-gen/table/index.vue +201 -121
  112. package/dist/package/src/views/tool/ops-monitoring/index.vue +1175 -0
  113. package/dist/package/src/views/tool/project-doc/index.vue +17 -15
  114. package/package.json +6 -2
  115. package/dist/package/declarations/src/rpc/base/v1/enum.d.ts +0 -43
  116. package/dist/package/declarations/src/rpc/system/common/v1/enum.d.ts +0 -67
  117. package/dist/package/src/rpc/base/v1/enum.ts +0 -54
  118. package/dist/package/src/rpc/system/common/v1/enum.ts +0 -80
@@ -0,0 +1,88 @@
1
+ import service from "@liujitcn/kratos-admin-core/request";
2
+ import type {
3
+ GetOpsAlertsRequest,
4
+ GetOpsEndpointsRequest,
5
+ GetOpsNodesRequest,
6
+ GetOpsRuntimeRequest,
7
+ GetOpsServicesRequest,
8
+ GetOpsStorageRequest,
9
+ GetOpsTrafficRequest,
10
+ OpsAlertsResponse,
11
+ OpsEndpointsResponse,
12
+ OpsNodesResponse,
13
+ OpsRuntime,
14
+ OpsServicesResponse,
15
+ OpsStorageResponse,
16
+ OpsTrafficResponse,
17
+ OpsMonitoringService
18
+ } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/ops_monitoring";
19
+
20
+ const OPS_MONITORING_URL = "/v1/admin/ops-monitoring";
21
+
22
+ /** Admin运维监控服务。 */
23
+ export class OpsMonitoringServiceImpl implements OpsMonitoringService {
24
+ /** 查询当前进程运行信息。 */
25
+ GetOpsRuntime(request: GetOpsRuntimeRequest): Promise<OpsRuntime> {
26
+ return service<GetOpsRuntimeRequest, OpsRuntime>({
27
+ url: `${OPS_MONITORING_URL}/runtime`,
28
+ method: "get",
29
+ params: request
30
+ });
31
+ }
32
+
33
+ /** 查询请求流量与延迟趋势。 */
34
+ GetOpsTraffic(request: GetOpsTrafficRequest): Promise<OpsTrafficResponse> {
35
+ return service<GetOpsTrafficRequest, OpsTrafficResponse>({
36
+ url: `${OPS_MONITORING_URL}/traffic`,
37
+ method: "get",
38
+ params: request
39
+ });
40
+ }
41
+
42
+ /** 查询服务和外部依赖状态。 */
43
+ GetOpsServices(request: GetOpsServicesRequest): Promise<OpsServicesResponse> {
44
+ return service<GetOpsServicesRequest, OpsServicesResponse>({
45
+ url: `${OPS_MONITORING_URL}/services`,
46
+ method: "get",
47
+ params: request
48
+ });
49
+ }
50
+
51
+ /** 查询数据库和缓存状态。 */
52
+ GetOpsStorage(request: GetOpsStorageRequest): Promise<OpsStorageResponse> {
53
+ return service<GetOpsStorageRequest, OpsStorageResponse>({
54
+ url: `${OPS_MONITORING_URL}/storage`,
55
+ method: "get",
56
+ params: request
57
+ });
58
+ }
59
+
60
+ /** 查询接口请求摘要。 */
61
+ GetOpsEndpoints(request: GetOpsEndpointsRequest): Promise<OpsEndpointsResponse> {
62
+ return service<GetOpsEndpointsRequest, OpsEndpointsResponse>({
63
+ url: `${OPS_MONITORING_URL}/endpoints`,
64
+ method: "get",
65
+ params: request
66
+ });
67
+ }
68
+
69
+ /** 查询实例资源状态。 */
70
+ GetOpsNodes(request: GetOpsNodesRequest): Promise<OpsNodesResponse> {
71
+ return service<GetOpsNodesRequest, OpsNodesResponse>({
72
+ url: `${OPS_MONITORING_URL}/nodes`,
73
+ method: "get",
74
+ params: request
75
+ });
76
+ }
77
+
78
+ /** 查询窗口内告警事件。 */
79
+ GetOpsAlerts(request: GetOpsAlertsRequest): Promise<OpsAlertsResponse> {
80
+ return service<GetOpsAlertsRequest, OpsAlertsResponse>({
81
+ url: `${OPS_MONITORING_URL}/alerts`,
82
+ method: "get",
83
+ params: request
84
+ });
85
+ }
86
+ }
87
+
88
+ export const defOpsMonitoringService = new OpsMonitoringServiceImpl();
@@ -0,0 +1,49 @@
1
+ import { subscribeSseEvent, type SseStop } from "../base/sse";
2
+ import type {
3
+ OpsNodesResponse,
4
+ OpsServicesResponse,
5
+ OpsStorageResponse,
6
+ OpsTrafficResponse
7
+ } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/ops_monitoring";
8
+
9
+ const OPS_MONITORING_SSE_STREAM = "system.admin.ops-monitoring";
10
+ const OPS_MONITORING_SSE_TRAFFIC = "ops.traffic";
11
+ const OPS_MONITORING_SSE_SERVICES = "ops.services";
12
+ const OPS_MONITORING_SSE_STORAGE = "ops.storage";
13
+ const OPS_MONITORING_SSE_NODES = "ops.nodes";
14
+
15
+ /** 运维监控 SSE 取消订阅函数。 */
16
+ export type OpsMonitoringSseStop = SseStop;
17
+
18
+ /** 订阅运维监控流量实时事件。 */
19
+ export function subscribeOpsMonitoringTraffic(handler: (payload: OpsTrafficResponse) => void): SseStop {
20
+ return subscribeOpsMonitoringEvent(OPS_MONITORING_SSE_TRAFFIC, parsePayload<OpsTrafficResponse>, handler);
21
+ }
22
+
23
+ /** 订阅运维监控服务实时事件。 */
24
+ export function subscribeOpsMonitoringServices(handler: (payload: OpsServicesResponse) => void): SseStop {
25
+ return subscribeOpsMonitoringEvent(OPS_MONITORING_SSE_SERVICES, parsePayload<OpsServicesResponse>, handler);
26
+ }
27
+
28
+ /** 订阅运维监控存储实时事件。 */
29
+ export function subscribeOpsMonitoringStorage(handler: (payload: OpsStorageResponse) => void): SseStop {
30
+ return subscribeOpsMonitoringEvent(OPS_MONITORING_SSE_STORAGE, parsePayload<OpsStorageResponse>, handler);
31
+ }
32
+
33
+ /** 订阅运维监控实例实时事件。 */
34
+ export function subscribeOpsMonitoringNodes(handler: (payload: OpsNodesResponse) => void): SseStop {
35
+ return subscribeOpsMonitoringEvent(OPS_MONITORING_SSE_NODES, parsePayload<OpsNodesResponse>, handler);
36
+ }
37
+
38
+ function subscribeOpsMonitoringEvent<T>(event: string, parser: (raw: string) => T | null, handler: (payload: T) => void): SseStop {
39
+ return subscribeSseEvent({ stream: OPS_MONITORING_SSE_STREAM }, event, parser, handler);
40
+ }
41
+
42
+ function parsePayload<T>(raw: string): T | null {
43
+ if (!raw) return null;
44
+ try {
45
+ return JSON.parse(raw) as T;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
@@ -0,0 +1,249 @@
1
+ <template>
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"
13
+ >
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>
49
+ </template>
50
+
51
+ <script setup lang="ts">
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";
56
+ import { t, useLocaleStore } from "@liujitcn/kratos-admin-core";
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";
61
+ import { getLanguageLabel } from "./dynamicTranslation";
62
+
63
+ /** DynamicTranslationCellProps 动态翻译列表单元格属性。 */
64
+ interface DynamicTranslationCellProps {
65
+ /** 资源源文。 */
66
+ source: string;
67
+ /** 统一翻译表目标类型。 */
68
+ targetType?: TranslationTargetType;
69
+ /** 目标资源编号。 */
70
+ targetId?: number;
71
+ /** 资源列表返回的非主语言翻译。 */
72
+ translations?: BaseTranslation[];
73
+ }
74
+
75
+ const props = defineProps<DynamicTranslationCellProps>();
76
+
77
+ const { languages } = useEnabledBaseLanguages();
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>());
83
+
84
+ const displaySource = computed(() => {
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;
89
+ });
90
+
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)
169
+ .map(item => {
170
+ const record = records.get(item.language_code);
171
+ const text = translationOverrides.value.get(item.language_code) ?? record?.name ?? "";
172
+ return {
173
+ id: record?.id ?? 0,
174
+ locale: item.language_code,
175
+ text,
176
+ editing: false,
177
+ saving: false,
178
+ originalText: text
179
+ };
180
+ });
181
+ }
182
+
183
+ /** startEdit 开启指定语言的编辑状态。 */
184
+ function startEdit(row: DynamicTranslationRow) {
185
+ row.originalText = row.text;
186
+ row.editing = true;
187
+ }
188
+
189
+ /** cancelEdit 恢复编辑前的内容。 */
190
+ function cancelEdit(row: DynamicTranslationRow) {
191
+ row.text = row.originalText;
192
+ row.editing = false;
193
+ }
194
+
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
+ }
222
+ }
223
+
224
+ /** handleDialogClosed 清理弹窗状态,避免复用旧资源译文。 */
225
+ function handleDialogClosed() {
226
+ translationRows.value = [];
227
+ loading.value = false;
228
+ }
229
+ </script>
230
+
231
+ <style scoped>
232
+ .dynamic-translation-cell {
233
+ display: inline-flex;
234
+ align-items: center;
235
+ min-width: 0;
236
+ max-width: 100%;
237
+ }
238
+
239
+ .dynamic-translation-cell__source {
240
+ min-width: 0;
241
+ overflow: hidden;
242
+ text-overflow: ellipsis;
243
+ white-space: nowrap;
244
+ }
245
+ .dynamic-translation-dialog__text {
246
+ min-width: 0;
247
+ overflow-wrap: anywhere;
248
+ }
249
+ </style>
@@ -0,0 +1,197 @@
1
+ <template>
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>
15
+ <div v-for="item in localValues" :key="item.locale" class="translation-editor__row">
16
+ <div class="translation-editor__heading">
17
+ <span>{{ getLanguageLabel(item.locale) }}</span>
18
+ </div>
19
+ <div class="translation-editor__control" :class="{ 'translation-editor__control--multiline': multiline }">
20
+ <el-input
21
+ :model-value="item.text"
22
+ :maxlength="maxlength"
23
+ :type="multiline ? 'textarea' : 'text'"
24
+ :rows="multiline ? 6 : undefined"
25
+ :disabled="translating || translatingLocale === item.locale"
26
+ show-word-limit
27
+ :placeholder="t('system.base.translation.placeholder.text', { language: getLanguageLabel(item.locale) })"
28
+ @update:model-value="value => updateText(item.locale, value)"
29
+ />
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)"
39
+ >
40
+ {{ t("system.base.translation.action.translate") }}
41
+ </el-button>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </template>
46
+
47
+ <script setup lang="ts">
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";
52
+ import { defBaseTranslationService } from "@liujitcn/kratos-admin-system/api/system/base_translation";
53
+ import { getLanguageLabel, type DynamicTranslationValue } from "./dynamicTranslation";
54
+
55
+ /** DynamicTranslationEditorProps 动态翻译编辑器属性。 */
56
+ interface DynamicTranslationEditorProps {
57
+ modelValue: DynamicTranslationValue[];
58
+ /** 当前语言源文本。 */
59
+ source?: string;
60
+ maxlength?: number;
61
+ multiline?: boolean;
62
+ }
63
+
64
+ const props = withDefaults(defineProps<DynamicTranslationEditorProps>(), {
65
+ source: "",
66
+ maxlength: 100,
67
+ multiline: false
68
+ });
69
+
70
+ const { locale: currentLocale } = useLocaleStore();
71
+ const emit = defineEmits<{ "update:modelValue": [value: DynamicTranslationValue[]] }>();
72
+ const localValues = ref<DynamicTranslationValue[]>([]);
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));
76
+
77
+ watch(
78
+ () => props.modelValue,
79
+ value => {
80
+ localValues.value = value.map(item => ({ ...item }));
81
+ },
82
+ { immediate: true, deep: true }
83
+ );
84
+
85
+ /** updateText 更新指定语言的人工译文。 */
86
+ function updateText(locale: DynamicTranslationValue["locale"], text: string) {
87
+ const values = localValues.value.map(item => (item.locale === locale ? { ...item, text } : item));
88
+ localValues.value = values;
89
+ emit("update:modelValue", values);
90
+ }
91
+
92
+ /** handleBatchTranslate 批量翻译当前仍为空的语言输入框。 */
93
+ async function handleBatchTranslate() {
94
+ if (!canTranslate.value || translating.value) return;
95
+ translating.value = true;
96
+ try {
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"));
121
+ } finally {
122
+ translating.value = false;
123
+ }
124
+ }
125
+
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
+ }
144
+ }
145
+
146
+ </script>
147
+
148
+ <style scoped>
149
+ .translation-editor {
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: 14px;
153
+ width: 100%;
154
+ }
155
+ .translation-editor__toolbar {
156
+ display: flex;
157
+ justify-content: flex-end;
158
+ }
159
+ .translation-editor__row {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 6px;
163
+ }
164
+ .translation-editor__heading,
165
+ .translation-editor__control {
166
+ display: flex;
167
+ gap: 8px;
168
+ align-items: center;
169
+ }
170
+ .translation-editor__heading {
171
+ color: var(--el-text-color-regular);
172
+ }
173
+ .translation-editor__control {
174
+ position: relative;
175
+ width: 100%;
176
+ }
177
+ .translation-editor__control :deep(.el-input) {
178
+ flex: 1;
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
+ }
197
+ </style>
@@ -0,0 +1,66 @@
1
+ import { getEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/base_language";
2
+ import type { TranslationTargetType } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_translation";
3
+
4
+ /** DynamicTranslationValue 动态资源单语言翻译编辑状态。 */
5
+ export interface DynamicTranslationValue {
6
+ /** 语言代码。 */
7
+ locale: string;
8
+ text: string;
9
+ id: number;
10
+ }
11
+
12
+ /** DynamicTranslationRecord 描述三类动态资源翻译共有字段。 */
13
+ export interface DynamicTranslationRecord extends Omit<DynamicTranslationValue, "text"> {
14
+ target_type?: TranslationTargetType;
15
+ target_id?: number;
16
+ name?: string;
17
+ /** 兼容旧接口缓存中的字段名。 */
18
+ title?: string;
19
+ label?: string;
20
+ }
21
+
22
+ /** DynamicLanguageOption 动态翻译编辑器显示的语言选项。 */
23
+ export interface DynamicLanguageOption {
24
+ /** 语言代码。 */
25
+ value: string;
26
+ /** 语言显示名称。 */
27
+ label: string;
28
+ }
29
+
30
+ /** 返回语言管理中启用且非主语言的翻译语言选项。 */
31
+ export function getEditableLanguageOptions(): DynamicLanguageOption[] {
32
+ return getEnabledBaseLanguages()
33
+ .filter(item => !item.is_primary)
34
+ .map(item => ({ value: item.language_code, label: item.native_name || item.language_name || item.language_code }));
35
+ }
36
+
37
+ /** 返回语言管理中的语言显示名称。 */
38
+ export function getLanguageLabel(locale: string): string {
39
+ const language = getEnabledBaseLanguages().find(item => item.language_code === locale);
40
+ return language?.native_name || language?.language_name || locale;
41
+ }
42
+
43
+ /** normalizeDynamicTranslations 将后端翻译记录补齐为全部非主语言编辑状态。 */
44
+ export function normalizeDynamicTranslations(
45
+ records: DynamicTranslationRecord[] | undefined,
46
+ _textField: "title" | "name" | "label",
47
+ locales: string[] = getEditableLanguageOptions().map(item => item.value)
48
+ ): DynamicTranslationValue[] {
49
+ return locales.map(locale => {
50
+ const record = records?.find(item => item.locale === locale);
51
+ return {
52
+ id: record?.id ?? 0,
53
+ locale,
54
+ text: String(record?.name ?? record?.[_textField] ?? "")
55
+ };
56
+ });
57
+ }
58
+
59
+ /** serializeDynamicTranslations 将编辑状态转换回后端资源翻译结构。 */
60
+ export function serializeDynamicTranslations(
61
+ values: DynamicTranslationValue[],
62
+ targetType: TranslationTargetType,
63
+ targetId: number
64
+ ): Array<{ id: number; target_type: TranslationTargetType; target_id: number; locale: string; name: string }> {
65
+ return values.map(({ id, locale, text }) => ({ id, target_type: targetType, target_id: targetId, locale, name: text }));
66
+ }
@@ -0,0 +1,30 @@
1
+ # 管理端 System 语言包
2
+
3
+ 本目录提供 System 管理业务模块的页面、AI、个人中心、基础资料和代码生成等业务文案。语言包由同级 `../module.ts` 导入,随 `systemAdminModule` 注册;管理端启动时由 core 的 `registerLocaleMessages` 合并,System 页面和组件通过 `t()` 或 `useLocaleStore()` 使用 `system.*` 键。
4
+
5
+ | 文件 | 用途 | 使用位置 |
6
+ | --- | --- | --- |
7
+ | `zh-CN.json` | System 模块简体中文固定 UI 默认语言包,也是所有语言键集合和占位符校验基准。 | 管理端固定文案默认语言及缺失翻译回退。 |
8
+ | `zh-TW.json` | System 模块繁体中文语言包。 | 管理端切换为繁体中文时由 Vue I18n 使用。 |
9
+ | `en-US.json` | System 模块英文语言包。 | 管理端切换为英文时由 Vue I18n 使用。 |
10
+ | `ja-JP.json` | System 模块日文语言包。 | 管理端切换为日文时由 Vue I18n 使用。 |
11
+ | `ko-KR.json` | System 模块韩语语言包。 | 管理端切换为韩语时由 Vue I18n 使用。 |
12
+ | `fr-FR.json` | System 模块法语语言包。 | 管理端切换为法语时由 Vue I18n 使用。 |
13
+ | `es-ES.json` | System 模块西班牙语语言包。 | 管理端切换为西班牙语时由 Vue I18n 使用。 |
14
+
15
+ ## 命名空间与使用位置
16
+
17
+ | 命名空间 | 文案范围 | 主要使用位置 |
18
+ | --- | --- | --- |
19
+ | `common.*` | 跨页面复用的操作、字段、弹窗、状态和校验文案。 | `src/views/**` 下的列表页、表单和确认弹窗。 |
20
+ | `system.ai.*` | AI 会话、消息流、重试和错误提示。 | `src/views/ai/**`、`src/api/base/ai_message.ts`。 |
21
+ | `system.profile.*` | 个人资料、安全设置和用户中心导航。 | `src/views/profile/**`、`src/module.ts` 的用户菜单。 |
22
+ | `system.base.*` | 区域、配置、部门、字典、岗位、角色、租户、用户等基础管理页面。 | `src/views/base/**`。 |
23
+ | `system.code.gen.*` | 代码生成配置、预览、列配置和多语言编辑。 | `src/views/tool/code-gen/**`。 |
24
+ | `system.translation.*` | 动态翻译编辑、草稿生成和语言包状态。 | `src/components/DynamicTranslationEditor.vue`、代码生成语言编辑组件。 |
25
+
26
+ 语言包由生成的 `generated.ts` 传入 `systemAdminModule.messages`,由 core 的 `registerLocaleMessages` 校验语言键集合、命名空间和占位符后注册到 Vue I18n;业务代码统一使用 `t('system....')` 或 `useLocaleStore()` 读取。
27
+
28
+ 语言键按文案归属使用 `common.` 或 `system.` 命名空间;语言包保持标准 JSON,不在文件内写注释。
29
+
30
+ 新增语言的完整文件清单和迁移流程见[国际化语言扩展指南](../../../../../../docs/国际化语言扩展指南.md)。