@liujitcn/kratos-admin-system 0.0.39 → 0.0.41

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 (39) hide show
  1. package/README.md +22 -0
  2. package/dist/package/declarations/src/api/system/admin/v1/base_config.d.ts +1 -5
  3. package/dist/package/declarations/src/components/DynamicI18nCell.vue.d.ts +2 -8
  4. package/dist/package/declarations/src/components/DynamicI18nEditor.vue.d.ts +20 -3
  5. package/dist/package/declarations/src/components/dynamicI18n.d.ts +2 -2
  6. package/dist/package/declarations/src/locales/generated.d.ts +5797 -5813
  7. package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +4 -40
  8. package/dist/package/declarations/src/utils/code-gen-progress.d.ts +3 -0
  9. package/dist/package/declarations/src/views/tool/code-gen/components/CodeGenGenerateConfirm.vue.d.ts +11 -0
  10. package/dist/package/declarations/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue.d.ts +5 -9
  11. package/dist/package/declarations/src/views/tool/code-gen/config.d.ts +3 -0
  12. package/dist/package/src/api/system/admin/v1/base_config.ts +0 -20
  13. package/dist/package/src/components/DynamicI18nCell.vue +55 -220
  14. package/dist/package/src/components/DynamicI18nEditor.vue +150 -146
  15. package/dist/package/src/components/dynamicI18n.ts +10 -6
  16. package/dist/package/src/config/index.ts +1 -3
  17. package/dist/package/src/locales/en-US.json +1430 -1434
  18. package/dist/package/src/locales/ja-JP.json +1430 -1434
  19. package/dist/package/src/locales/zh-CN.json +1429 -1433
  20. package/dist/package/src/locales/zh-TW.json +1429 -1433
  21. package/dist/package/src/module.ts +0 -1
  22. package/dist/package/src/rpc/system/admin/v1/base_config.ts +3 -43
  23. package/dist/package/src/utils/code-gen-progress.ts +15 -0
  24. package/dist/package/src/views/base/config/index.vue +183 -48
  25. package/dist/package/src/views/base/dict/index.vue +6 -9
  26. package/dist/package/src/views/base/dict/item.vue +6 -15
  27. package/dist/package/src/views/base/job/index.vue +16 -17
  28. package/dist/package/src/views/base/menu/index.vue +78 -15
  29. package/dist/package/src/views/tool/code-gen/columns/index.vue +17 -25
  30. package/dist/package/src/views/tool/code-gen/components/CodeGenGenerateConfirm.vue +115 -0
  31. package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +27 -186
  32. package/dist/package/src/views/tool/code-gen/components/CodeGenProgressDialog.vue +2 -0
  33. package/dist/package/src/views/tool/code-gen/config.ts +15 -1
  34. package/dist/package/src/views/tool/code-gen/table/index.vue +69 -40
  35. package/package.json +3 -2
  36. package/dist/package/declarations/src/config/database-backup.d.ts +0 -12
  37. package/dist/package/declarations/src/views/base/runtime-config/index.vue.d.ts +0 -3
  38. package/dist/package/src/config/database-backup.ts +0 -57
  39. package/dist/package/src/views/base/runtime-config/index.vue +0 -479
@@ -15,11 +15,7 @@
15
15
  @close="handleCloseDialog"
16
16
  >
17
17
  <template #i18ns>
18
- <DynamicI18nEditor
19
- v-model="i18nValues"
20
- :source="formData.name"
21
- :maxlength="50"
22
- />
18
+ <DynamicI18nEditor v-model="i18nValues" :source="formData.name" :maxlength="50" />
23
19
  </template>
24
20
  </FormDialog>
25
21
  </div>
@@ -215,7 +211,11 @@ function renderOperationCell(scope: RenderScope<BaseJob>) {
215
211
  );
216
212
  }
217
213
 
218
- if (row.status === Status.STATUS_ENABLE && (row.entry_id === undefined || row.entry_id === 0) && BUTTONS.value["base:job:start"]) {
214
+ if (
215
+ row.status === Status.STATUS_ENABLE &&
216
+ (row.entry_id === undefined || row.entry_id === 0) &&
217
+ BUTTONS.value["base:job:start"]
218
+ ) {
219
219
  actionNodes.push(
220
220
  h(
221
221
  ElButton,
@@ -249,7 +249,11 @@ function renderOperationCell(scope: RenderScope<BaseJob>) {
249
249
  );
250
250
  }
251
251
 
252
- if (row.status === Status.STATUS_ENABLE && (row.entry_id === undefined || row.entry_id === 0) && BUTTONS.value["base:job:exec"]) {
252
+ if (
253
+ row.status === Status.STATUS_ENABLE &&
254
+ (row.entry_id === undefined || row.entry_id === 0) &&
255
+ BUTTONS.value["base:job:exec"]
256
+ ) {
253
257
  actionNodes.push(
254
258
  h(
255
259
  ElButton,
@@ -298,17 +302,11 @@ function renderOperationCell(scope: RenderScope<BaseJob>) {
298
302
  const formFields = computed<ProFormField[]>(() => [
299
303
  {
300
304
  prop: "name",
305
+ suffixSlotName: "i18ns",
301
306
  label: t("system.base.job.field.name"),
302
307
  component: "input",
303
308
  props: { placeholder: t("system.base.job.placeholder.name") }
304
309
  },
305
- {
306
- prop: "i18ns",
307
- label: t("system.base.i18n.field.i18ns"),
308
- component: "slot",
309
- slotName: "i18ns",
310
- colSpan: 24
311
- },
312
310
  {
313
311
  prop: "invoke_target",
314
312
  label: t("system.base.job.field.invoke_target"),
@@ -427,7 +425,7 @@ async function handleOpenDialog(jobId?: number) {
427
425
 
428
426
  const data = await defBaseJobService.GetBaseJob({ id: jobId });
429
427
  Object.assign(formData, data);
430
- i18nValues.value = normalizeDynamicI18ns(data.i18ns as DynamicI18nRecord[]);
428
+ i18nValues.value = normalizeDynamicI18ns(data.i18ns as DynamicI18nRecord[]);
431
429
  }
432
430
 
433
431
  /**
@@ -451,7 +449,7 @@ function resetForm() {
451
449
  formData.cron_expression = "";
452
450
  formData.status = Status.STATUS_ENABLE;
453
451
  formData.i18ns = [];
454
- i18nValues.value = normalizeDynamicI18ns(undefined);
452
+ i18nValues.value = normalizeDynamicI18ns(undefined);
455
453
  }
456
454
 
457
455
  /**
@@ -465,7 +463,8 @@ function handleSubmit() {
465
463
  submitData.i18ns = serializeDynamicI18ns(
466
464
  i18nValues.value,
467
465
  I18nTargetType.I18N_TARGET_TYPE_BASE_JOB_NAME,
468
- submitData.id
466
+ submitData.id,
467
+ submitData.name
469
468
  );
470
469
  const request = submitData.id
471
470
  ? defBaseJobService.UpdateBaseJob({ base_job: submitData })
@@ -18,7 +18,8 @@
18
18
  ref="formDialogRef"
19
19
  class="menu-form-dialog"
20
20
  :title="t(dialog.editing ? 'system.base.menu.action.edit' : 'system.base.menu.action.create')"
21
- width="1180px"
21
+ width="min(1440px, calc(100vw - 32px))"
22
+ top="4vh"
22
23
  label-width="180px"
23
24
  :model="formData"
24
25
  :fields="formFields"
@@ -32,11 +33,7 @@
32
33
  </template>
33
34
 
34
35
  <template #i18ns>
35
- <DynamicI18nEditor
36
- v-model="i18nValues"
37
- :source="formData.meta.title"
38
- :maxlength="100"
39
- />
36
+ <DynamicI18nEditor v-model="i18nValues" :source="formData.meta.title" :maxlength="100" />
40
37
  </template>
41
38
 
42
39
  <template #apiTransferItem="slotScope">
@@ -452,6 +449,7 @@ const formFields = computed<ProFormField[]>(() => [
452
449
  },
453
450
  {
454
451
  prop: "meta.title",
452
+ suffixSlotName: "i18ns",
455
453
  label: t(
456
454
  formData.type === BaseMenuType.BASE_MENU_TYPE_BUTTON ? "system.base.menu.field.button_name" : "system.base.menu.field.title"
457
455
  ),
@@ -467,13 +465,6 @@ const formFields = computed<ProFormField[]>(() => [
467
465
  )
468
466
  })
469
467
  },
470
- {
471
- prop: "i18ns",
472
- label: t("system.base.i18n.field.i18ns"),
473
- component: "slot",
474
- slotName: "i18ns",
475
- colSpan: 24
476
- },
477
468
  {
478
469
  prop: "path",
479
470
  label: getPathFieldLabel(),
@@ -666,6 +657,7 @@ const formFields = computed<ProFormField[]>(() => [
666
657
  slotName: "apiTransferItem",
667
658
  options: transferData.value,
668
659
  props: {
660
+ class: "menu-api-transfer",
669
661
  filterable: true,
670
662
  titles: [t("system.base.menu.value.available_api"), t("system.base.menu.value.selected_api")]
671
663
  },
@@ -1002,7 +994,8 @@ function buildSubmitPayload(): BaseMenuForm {
1002
994
  payload.i18ns = serializeDynamicI18ns(
1003
995
  i18nValues.value,
1004
996
  I18nTargetType.I18N_TARGET_TYPE_BASE_MENU_META_TITLE,
1005
- payload.id
997
+ payload.id,
998
+ payload.meta?.title ?? ""
1006
999
  );
1007
1000
  // 一级菜单在表单中保持空白,提交时仍按接口约定传回根节点标识。
1008
1001
  if (payload.id > 0 && payload.parent_id === undefined) payload.parent_id = 0;
@@ -1145,7 +1138,7 @@ function resetForm(data?: Partial<BaseMenuForm>) {
1145
1138
  formDialogRef.value?.resetFields();
1146
1139
  formDialogRef.value?.clearValidate();
1147
1140
  Object.assign(formData, normalizeMenuForm(data));
1148
- i18nValues.value = normalizeDynamicI18ns(data?.i18ns as DynamicI18nRecord[] | undefined);
1141
+ i18nValues.value = normalizeDynamicI18ns(data?.i18ns as DynamicI18nRecord[] | undefined);
1149
1142
  }
1150
1143
 
1151
1144
  /** 提交菜单表单,并在成功后关闭弹窗、刷新表格。 */
@@ -1249,3 +1242,73 @@ function handleDeleteMenu(selected?: number | string | Array<number | string> |
1249
1242
  align-items: flex-start;
1250
1243
  }
1251
1244
  </style>
1245
+
1246
+ <style lang="scss">
1247
+ .menu-form-dialog {
1248
+ .menu-api-transfer {
1249
+ display: grid;
1250
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
1251
+ align-items: center;
1252
+ gap: 16px;
1253
+ width: 100%;
1254
+ .el-transfer-panel {
1255
+ width: 100%;
1256
+ min-width: 0;
1257
+ }
1258
+ .el-transfer__buttons {
1259
+ display: flex;
1260
+ flex-direction: column;
1261
+ gap: 12px;
1262
+ padding: 0;
1263
+ .el-button {
1264
+ margin: 0;
1265
+ }
1266
+ }
1267
+ .el-transfer-panel__body {
1268
+ height: 360px;
1269
+ }
1270
+ .el-transfer-panel__list.is-filterable {
1271
+ height: 300px;
1272
+ }
1273
+ .el-transfer-panel__item.el-checkbox {
1274
+ align-items: flex-start;
1275
+ height: auto;
1276
+ min-height: 36px;
1277
+ padding-top: 8px;
1278
+ padding-bottom: 8px;
1279
+ .el-checkbox__input {
1280
+ top: 3px;
1281
+ }
1282
+ .el-checkbox__label {
1283
+ display: block;
1284
+ white-space: normal;
1285
+ overflow-wrap: anywhere;
1286
+ line-height: 20px;
1287
+ }
1288
+ }
1289
+ }
1290
+ }
1291
+
1292
+ @media (max-width: 767px) {
1293
+ .menu-form-dialog {
1294
+ .el-col {
1295
+ flex: 0 0 100%;
1296
+ max-width: 100%;
1297
+ }
1298
+ .el-form-item {
1299
+ flex-direction: column;
1300
+ }
1301
+ .el-form-item__label {
1302
+ justify-content: flex-start;
1303
+ width: auto !important;
1304
+ }
1305
+ .menu-api-transfer {
1306
+ grid-template-columns: minmax(0, 1fr);
1307
+ .el-transfer__buttons {
1308
+ flex-direction: row;
1309
+ justify-content: center;
1310
+ }
1311
+ }
1312
+ }
1313
+ }
1314
+ </style>
@@ -103,21 +103,12 @@
103
103
  maxlength="255"
104
104
  :placeholder="t('system.code.gen.column.placeholder.comment')"
105
105
  />
106
- <el-popover placement="right-start" :width="400" trigger="click">
107
- <template #reference>
108
- <el-button
109
- :icon="ChatLineSquare"
110
- circle
111
- :type="hasMissingColumnLocales(row as CodeGenColumnView) ? 'warning' : 'success'"
112
- :aria-label="t('system.code.gen.i18n.action.edit_column')"
113
- />
114
- </template>
115
- <CodeGenLocaleEditor
116
- :model-value="row.i18n_config"
117
- :source-comment="row.comment"
118
- @update:model-value="value => (row.i18n_config = value)"
119
- />
120
- </el-popover>
106
+ <CodeGenLocaleEditor
107
+ :model-value="row.i18n_config"
108
+ :source-comment="row.comment"
109
+ :disabled="!canEdit"
110
+ @update:model-value="value => (row.i18n_config = value)"
111
+ />
121
112
  </div>
122
113
  </template>
123
114
  </el-table-column>
@@ -536,7 +527,7 @@
536
527
  <script setup lang="ts">
537
528
  import Sortable from "sortablejs";
538
529
  import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
539
- import { ChatLineSquare, Delete, Document, List, Plus, Setting } from "@element-plus/icons-vue";
530
+ import { Delete, Document, List, Plus, Setting } from "@element-plus/icons-vue";
540
531
  import { useRoute, useRouter } from "vue-router";
541
532
  import { setAdminDocumentTitle, t } from "@liujitcn/kratos-admin-core";
542
533
  import ProDialog from "@liujitcn/kratos-admin-core/components/Dialog/ProDialog.vue";
@@ -557,7 +548,6 @@ import type {
557
548
  } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/code_gen_column";
558
549
  import type { CodeGenDatabaseTable, CodeGenTableForm } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/code_gen_table";
559
550
  import CodeGenLocaleEditor from "../components/CodeGenLocaleEditor.vue";
560
- import { getEditableLanguageOptions } from "@liujitcn/kratos-admin-system/components/dynamicI18n";
561
551
  import {
562
552
  copyCodeGenOptionToEmptyMatches,
563
553
  copyFirstMatchingCodeGenOption,
@@ -576,7 +566,8 @@ import {
576
566
  createDefaultCodeGenListConfig,
577
567
  createDefaultCodeGenOptionConfig,
578
568
  createDefaultCodeGenQueryConfig,
579
- createDefaultCodeGenTableForm
569
+ createDefaultCodeGenTableForm,
570
+ withCodeGenLocaleDefaults
580
571
  } from "../config";
581
572
 
582
573
  defineOptions({
@@ -784,6 +775,7 @@ async function handleSaveColumns(showMessage = true) {
784
775
  code_gen_columns: columns.value.map((item, index) => ({
785
776
  ...item,
786
777
  table_id: formData.id,
778
+ i18n_config: withCodeGenLocaleDefaults(item.i18n_config, item.comment),
787
779
  sort: index + 1
788
780
  }))
789
781
  });
@@ -999,7 +991,10 @@ async function loadDatabaseColumns(tableName: string) {
999
991
  if (!tableName || databaseColumns[tableName] || loadingDatabaseColumns.has(tableName)) return;
1000
992
  loadingDatabaseColumns.add(tableName);
1001
993
  try {
1002
- const data = await defCodeGenColumnService.ListCodeGenDatabaseColumn({ source_name: formData.source_name, table_name: tableName });
994
+ const data = await defCodeGenColumnService.ListCodeGenDatabaseColumn({
995
+ source_name: formData.source_name,
996
+ table_name: tableName
997
+ });
1003
998
  databaseColumns[tableName] = data.columns ?? [];
1004
999
  } finally {
1005
1000
  loadingDatabaseColumns.delete(tableName);
@@ -1197,7 +1192,9 @@ function hasOptionConfig(option: CodeGenColumnOptionConfig) {
1197
1192
  /** 返回选项入口的当前状态提示。 */
1198
1193
  function optionEntryTip(option: CodeGenColumnOptionConfig) {
1199
1194
  return t(
1200
- hasOptionConfig(option) ? "system.code.gen.column.tooltip.option_configured" : "system.code.gen.column.tooltip.configure_option"
1195
+ hasOptionConfig(option)
1196
+ ? "system.code.gen.column.tooltip.option_configured"
1197
+ : "system.code.gen.column.tooltip.configure_option"
1201
1198
  );
1202
1199
  }
1203
1200
 
@@ -1206,11 +1203,6 @@ function codeGenScopeLabel(scope: CodeGenOptionScope) {
1206
1203
  return t(`system.code.gen.column.scope.${scope}`);
1207
1204
  }
1208
1205
 
1209
- /** 判断字段是否缺少任一非主语言描述。 */
1210
- function hasMissingColumnLocales(column: CodeGenColumnView) {
1211
- return getEditableLanguageOptions().some(item => !column.i18n_config?.get(item.value)?.comment);
1212
- }
1213
-
1214
1206
  /** 判断组件是否依赖选择数据源。 */
1215
1207
  function hasOptionComponent(component: string, scope: CodeGenOptionScope) {
1216
1208
  return (
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <div class="generate-confirm">
3
+ <p class="generate-confirm__message">{{ message }}</p>
4
+ <template v-if="count">
5
+ <el-alert
6
+ :title="t('system.code.gen.table.dialog.missing_count', { count })"
7
+ :description="t('system.code.gen.table.dialog.missing_hint')"
8
+ type="warning"
9
+ show-icon
10
+ :closable="false"
11
+ />
12
+ <div class="generate-confirm__groups" tabindex="0" :aria-label="t('system.code.gen.table.dialog.missing_count', { count })">
13
+ <details v-for="group in groups" :key="group.name" :open="groups.length === 1">
14
+ <summary>
15
+ <span class="generate-confirm__name">{{ group.name }}</span>
16
+ <span class="generate-confirm__count">{{ group.items.length }}</span>
17
+ </summary>
18
+ <ul>
19
+ <li v-for="(item, index) in group.items" :key="index">{{ item }}</li>
20
+ </ul>
21
+ </details>
22
+ </div>
23
+ </template>
24
+ </div>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import { computed } from "vue";
29
+ import { t } from "@liujitcn/kratos-admin-core";
30
+
31
+ /** 生成确认内容,按业务表保留缺失翻译明细。 */
32
+ const props = defineProps<{
33
+ message: string;
34
+ groups: Array<{ name: string; items: string[] }>;
35
+ }>();
36
+
37
+ const count = computed(() => props.groups.reduce((total, group) => total + group.items.length, 0));
38
+ </script>
39
+
40
+ <style scoped lang="scss">
41
+ :global(.code-gen-generate-confirm) {
42
+ width: 600px;
43
+ max-width: calc(100vw - 32px);
44
+ }
45
+
46
+ :global(.code-gen-generate-confirm .el-message-box__message) {
47
+ width: 100%;
48
+ min-width: 0;
49
+ }
50
+
51
+ .generate-confirm {
52
+ display: grid;
53
+ gap: 16px;
54
+ min-width: 0;
55
+ &__message {
56
+ margin: 0;
57
+ color: var(--el-text-color-primary);
58
+ font-size: 15px;
59
+ line-height: 1.6;
60
+ overflow-wrap: anywhere;
61
+ }
62
+ &__groups {
63
+ max-height: min(320px, 35vh);
64
+ overflow: auto;
65
+ border: 1px solid var(--el-border-color-lighter);
66
+ border-radius: 8px;
67
+ scrollbar-gutter: stable;
68
+ }
69
+ details + details {
70
+ border-top: 1px solid var(--el-border-color-lighter);
71
+ }
72
+ summary {
73
+ padding: 12px 16px;
74
+ color: var(--el-text-color-primary);
75
+ cursor: pointer;
76
+ background: var(--el-fill-color-light);
77
+ overflow-wrap: anywhere;
78
+ &:hover {
79
+ background: var(--el-fill-color);
80
+ }
81
+ &:focus-visible {
82
+ outline: 2px solid var(--el-color-primary);
83
+ outline-offset: -2px;
84
+ }
85
+ }
86
+ &__name {
87
+ font-weight: 600;
88
+ }
89
+ &__count {
90
+ display: inline-block;
91
+ padding: 0 8px;
92
+ margin-left: 8px;
93
+ font-size: 12px;
94
+ line-height: 20px;
95
+ color: var(--el-color-warning);
96
+ background: var(--el-color-warning-light-9);
97
+ border-radius: 10px;
98
+ }
99
+ ul {
100
+ padding: 4px 16px;
101
+ margin: 0;
102
+ list-style: none;
103
+ }
104
+ li {
105
+ padding: 8px 0;
106
+ font-size: 13px;
107
+ line-height: 1.6;
108
+ color: var(--el-text-color-regular);
109
+ overflow-wrap: anywhere;
110
+ & + li {
111
+ border-top: 1px solid var(--el-border-color-extra-light);
112
+ }
113
+ }
114
+ }
115
+ </style>
@@ -1,200 +1,41 @@
1
1
  <template>
2
- <div class="code-gen-locale-editor">
3
- <el-alert
4
- v-if="missingLocales.length"
5
- :title="t('system.code.gen.i18n.message.missing', { locales: missingLocales.join(', ') })"
6
- type="warning"
7
- :closable="false"
8
- show-icon
9
- />
10
- <div v-for="locale in editableLocales" :key="locale.value" class="code-gen-locale-editor__group">
11
- <div class="code-gen-locale-editor__locale">
12
- <span>{{ locale.label }}</span>
13
- <el-tag v-if="hasComment(locale.value)" size="small" type="success" effect="plain">
14
- {{ t("system.code.gen.i18n.status.complete") }}
15
- </el-tag>
16
- <el-tag v-else size="small" type="warning" effect="plain">
17
- {{ t("system.code.gen.i18n.status.missing") }}
18
- </el-tag>
19
- </div>
20
- <div class="code-gen-locale-editor__control">
21
- <el-input
22
- :model-value="localeValue(locale.value).comment"
23
- :placeholder="t('system.code.gen.i18n.placeholder.comment', { source: sourceComment || '-' })"
24
- :disabled="isTranslating(locale.value, 'comment')"
25
- maxlength="255"
26
- @update:model-value="value => updateLocale(locale.value, 'comment', value)"
27
- />
28
- <el-button
29
- class="code-gen-locale-editor__translate"
30
- link
31
- type="primary"
32
- size="small"
33
- :icon="Promotion"
34
- :loading="isTranslating(locale.value, 'comment')"
35
- :disabled="!canTranslate(locale.value, 'comment', sourceComment)"
36
- @click="handleTranslate(locale.value, 'comment', sourceComment, locale.label)"
37
- >
38
- {{ t("system.base.i18n.action.translate") }}
39
- </el-button>
40
- </div>
41
- <div v-if="showLeftTreeComment" class="code-gen-locale-editor__control">
42
- <el-input
43
- :model-value="localeValue(locale.value).left_tree_comment"
44
- :placeholder="t('system.code.gen.i18n.placeholder.left_tree_comment', { source: sourceLeftTreeComment || '-' })"
45
- :disabled="isTranslating(locale.value, 'left_tree_comment')"
46
- maxlength="255"
47
- @update:model-value="value => updateLocale(locale.value, 'left_tree_comment', value)"
48
- />
49
- <el-button
50
- class="code-gen-locale-editor__translate"
51
- link
52
- type="primary"
53
- size="small"
54
- :icon="Promotion"
55
- :loading="isTranslating(locale.value, 'left_tree_comment')"
56
- :disabled="!canTranslate(locale.value, 'left_tree_comment', sourceLeftTreeComment)"
57
- @click="handleTranslate(locale.value, 'left_tree_comment', sourceLeftTreeComment, locale.label)"
58
- >
59
- {{ t("system.base.i18n.action.translate") }}
60
- </el-button>
61
- </div>
62
- </div>
63
- </div>
2
+ <DynamicI18nEditor
3
+ :model-value="values"
4
+ :source="sourceComment"
5
+ :maxlength="255"
6
+ :disabled="disabled"
7
+ @update:model-value="updateValues"
8
+ />
64
9
  </template>
65
10
 
66
11
  <script setup lang="ts">
67
- import { computed, ref } from "vue";
68
- import { Promotion } from "@element-plus/icons-vue";
69
- import { t } from "@liujitcn/kratos-admin-core";
70
- import { defBaseI18nService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_i18n";
71
- import { loadEnabledBaseLanguages, useEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_language";
12
+ import { computed } from "vue";
13
+ import DynamicI18nEditor from "@liujitcn/kratos-admin-system/components/DynamicI18nEditor.vue";
14
+ import { getEditableLanguageOptions, type DynamicI18nValue } from "@liujitcn/kratos-admin-system/components/dynamicI18n";
72
15
  import type { CodeGenLocaleConfig } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
73
16
 
74
- /** 代码生成多语言编辑器属性。 */
17
+ /** 代码生成描述使用统一国际化入口,按字段保存回语言配置。 */
75
18
  interface CodeGenLocaleEditorProps {
76
- /** 按语言区域索引的生成配置。 */
77
19
  modelValue: Map<string, CodeGenLocaleConfig>;
78
- /** 中文业务或字段描述。 */
79
20
  sourceComment?: string;
80
- /** 是否编辑左树标题。 */
81
- showLeftTreeComment?: boolean;
82
- /** 中文左树标题。 */
83
- sourceLeftTreeComment?: string;
84
- }
85
-
86
- const props = withDefaults(defineProps<CodeGenLocaleEditorProps>(), {
87
- sourceComment: "",
88
- showLeftTreeComment: false,
89
- sourceLeftTreeComment: ""
90
- });
91
-
92
- const emit = defineEmits<{
93
- "update:modelValue": [value: Map<string, CodeGenLocaleConfig>];
94
- }>();
95
-
96
- const { languages } = useEnabledBaseLanguages();
97
- const translatingField = ref("");
98
- void loadEnabledBaseLanguages();
99
-
100
- const editableLocales = computed(() =>
101
- languages.value
102
- .filter(item => !item.is_primary)
103
- .map(item => ({ value: item.language_code, label: item.native_name || item.language_name || item.language_code }))
104
- );
105
-
106
- const missingLocales = computed(() =>
107
- editableLocales.value.filter(locale => !hasComment(locale.value)).map(locale => locale.label)
21
+ field?: keyof CodeGenLocaleConfig;
22
+ disabled?: boolean;
23
+ }
24
+ const props = withDefaults(defineProps<CodeGenLocaleEditorProps>(), { sourceComment: "", field: "comment", disabled: false });
25
+ const emit = defineEmits<{ "update:modelValue": [value: Map<string, CodeGenLocaleConfig>] }>();
26
+ const values = computed<DynamicI18nValue[]>(() =>
27
+ getEditableLanguageOptions().map(({ value }) => ({
28
+ locale: value,
29
+ id: 0,
30
+ text: props.modelValue.get(value)?.[props.field] ?? ""
31
+ }))
108
32
  );
109
33
 
110
- /** 返回指定语言配置并补齐空字段。 */
111
- function localeValue(locale: string): CodeGenLocaleConfig {
112
- return props.modelValue?.get(locale) ?? { comment: "", left_tree_comment: "" };
113
- }
114
-
115
- /** 判断指定语言是否已填写业务或字段描述。 */
116
- function hasComment(locale: string) {
117
- const value = localeValue(locale);
118
- return Boolean(value.comment && (!props.showLeftTreeComment || value.left_tree_comment));
119
- }
120
-
121
- /** 更新单个语言字段并保持 Map 引用语义。 */
122
- function updateLocale(locale: string, field: keyof CodeGenLocaleConfig, value: string) {
123
- const next = new Map(props.modelValue ?? []);
124
- next.set(locale, { ...localeValue(locale), [field]: value });
34
+ /** 更新当前描述译文,保留其他字段和语言配置。 */
35
+ function updateValues(values: DynamicI18nValue[]) {
36
+ const next = new Map(props.modelValue);
37
+ for (const item of values)
38
+ next.set(item.locale, { comment: "", left_tree_comment: "", ...next.get(item.locale), [props.field]: item.text });
125
39
  emit("update:modelValue", next);
126
40
  }
127
-
128
- /** 判断指定语言字段是否正在翻译。 */
129
- function isTranslating(locale: string, field: keyof CodeGenLocaleConfig) {
130
- return translatingField.value === `${locale}:${field}`;
131
- }
132
-
133
- /** 判断指定语言字段当前是否允许自动翻译。 */
134
- function canTranslate(locale: string, field: keyof CodeGenLocaleConfig, source: string) {
135
- return Boolean(source) && !localeValue(locale)[field] && !translatingField.value;
136
- }
137
-
138
- /** 翻译指定语言的单个代码生成描述。 */
139
- async function handleTranslate(locale: string, field: keyof CodeGenLocaleConfig, source: string, language: string) {
140
- if (!canTranslate(locale, field, source)) return;
141
- translatingField.value = `${locale}:${field}`;
142
- try {
143
- const response = await defBaseI18nService.DraftBaseI18n({ source, locale });
144
- const i18n = response.i18ns.find(item => item.locale === locale)?.i18n;
145
- if (!i18n) throw new Error("i18n not found");
146
- updateLocale(locale, field, i18n);
147
- ElMessage.success(t("system.base.i18n.message.translate_success", { language }));
148
- } catch {
149
- ElMessage.error(t("system.base.i18n.message.translate_failed", { language }));
150
- } finally {
151
- translatingField.value = "";
152
- }
153
- }
154
41
  </script>
155
-
156
- <style scoped lang="scss">
157
- .code-gen-locale-editor {
158
- display: flex;
159
- flex-direction: column;
160
- gap: 12px;
161
- }
162
-
163
- .code-gen-locale-editor__group {
164
- display: grid;
165
- gap: 8px;
166
- }
167
-
168
- .code-gen-locale-editor__locale {
169
- display: flex;
170
- align-items: center;
171
- justify-content: space-between;
172
- min-height: 24px;
173
- font-size: 13px;
174
- font-weight: 600;
175
- color: var(--el-text-color-primary);
176
- }
177
-
178
- .code-gen-locale-editor__control {
179
- position: relative;
180
- display: flex;
181
- align-items: center;
182
- width: 100%;
183
- }
184
-
185
- .code-gen-locale-editor__control :deep(.el-input) {
186
- flex: 1;
187
- }
188
-
189
- .code-gen-locale-editor__control :deep(.el-input__suffix) {
190
- padding-right: 78px;
191
- }
192
-
193
- .code-gen-locale-editor__translate {
194
- position: absolute;
195
- z-index: 1;
196
- top: 50%;
197
- right: 8px;
198
- transform: translateY(-50%);
199
- }
200
- </style>
@@ -111,6 +111,7 @@ import { getCurrentLocale, t } from "@liujitcn/kratos-admin-core";
111
111
  import ProDialog from "@liujitcn/kratos-admin-core/components/Dialog/ProDialog.vue";
112
112
  import { defCodeGenService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/code_gen";
113
113
  import { subscribeCodeGenProgress, type SseStop } from "../../../../utils/code_gen_sse";
114
+ import { normalizeCodeGenTask } from "../../../../utils/code-gen-progress";
114
115
  import type { CodeGenTask } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/code_gen";
115
116
  import { CodeGenTaskStatus, CodeGenTaskStepStatus } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/code_gen";
116
117
 
@@ -174,6 +175,7 @@ async function loadTask(silent = false, taskId = props.taskId) {
174
175
 
175
176
  /** 应用实时或轮询返回的任务快照。 */
176
177
  function applyTask(latest: CodeGenTask) {
178
+ latest = normalizeCodeGenTask(latest);
177
179
  task.value = latest;
178
180
  const runningTable = latest.tables.find(table => table.status === CodeGenTaskStatus.CODE_GEN_TASK_STATUS_RUNNING);
179
181
  if (runningTable && !expandedTables.value.includes(runningTable.table_id)) {