@liujitcn/kratos-admin-system 0.0.28 → 0.0.31

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 (135) hide show
  1. package/README.md +19 -2
  2. package/dist/package/declarations/src/api/base/notification.d.ts +24 -0
  3. package/dist/package/declarations/src/api/system/base_audit_log.d.ts +55 -0
  4. package/dist/package/declarations/src/api/system/base_dashboard.d.ts +14 -0
  5. package/dist/package/declarations/src/api/system/base_file.d.ts +13 -0
  6. package/dist/package/declarations/src/api/system/base_i18n.d.ts +5 -5
  7. package/dist/package/declarations/src/api/system/base_login_policy.d.ts +10 -0
  8. package/dist/package/declarations/src/api/system/base_message.d.ts +24 -0
  9. package/dist/package/declarations/src/api/system/base_message_category.d.ts +21 -0
  10. package/dist/package/declarations/src/api/system/base_session.d.ts +11 -0
  11. package/dist/package/declarations/src/api/system/oauth_client.d.ts +24 -0
  12. package/dist/package/declarations/src/components/AuditLogTable.vue.d.ts +45 -0
  13. package/dist/package/declarations/src/components/DynamicI18nCell.vue.d.ts +16 -0
  14. package/dist/package/declarations/src/components/DynamicI18nEditor.vue.d.ts +20 -0
  15. package/dist/package/declarations/src/components/auditLog.d.ts +9 -0
  16. package/dist/package/declarations/src/components/dynamicI18n.d.ts +38 -0
  17. package/dist/package/declarations/src/locales/generated.d.ts +1364 -68
  18. package/dist/package/declarations/src/rpc/base/v1/login.d.ts +149 -0
  19. package/dist/package/declarations/src/rpc/base/v1/notification.d.ts +281 -0
  20. package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +25 -0
  21. package/dist/package/declarations/src/rpc/base/v1/oauth_client.d.ts +23 -0
  22. package/dist/package/declarations/src/rpc/system/admin/v1/base_audit_log.d.ts +640 -0
  23. package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +3 -3
  24. package/dist/package/declarations/src/rpc/system/admin/v1/base_dashboard.d.ts +60 -0
  25. package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +4 -4
  26. package/dist/package/declarations/src/rpc/system/admin/v1/base_file.d.ts +73 -0
  27. package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +27 -23
  28. package/dist/package/declarations/src/rpc/system/admin/v1/base_job.d.ts +5 -0
  29. package/dist/package/declarations/src/rpc/system/admin/v1/base_login_policy.d.ts +51 -0
  30. package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +2 -2
  31. package/dist/package/declarations/src/rpc/system/admin/v1/base_message.d.ts +193 -0
  32. package/dist/package/declarations/src/rpc/system/admin/v1/base_message_category.d.ts +125 -0
  33. package/dist/package/declarations/src/rpc/system/admin/v1/base_session.d.ts +33 -0
  34. package/dist/package/declarations/src/rpc/system/admin/v1/base_user.d.ts +3 -3
  35. package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +1 -1
  36. package/dist/package/declarations/src/rpc/system/admin/v1/oauth_client.d.ts +145 -0
  37. package/dist/package/declarations/src/views/base/api-log/index.vue.d.ts +3 -0
  38. package/dist/package/declarations/src/views/base/dashboard/index.vue.d.ts +3 -0
  39. package/dist/package/declarations/src/views/base/data-access-log/index.vue.d.ts +3 -0
  40. package/dist/package/declarations/src/views/base/file/index.vue.d.ts +3 -0
  41. package/dist/package/declarations/src/views/base/login-log/index.vue.d.ts +3 -0
  42. package/dist/package/declarations/src/views/base/login-policy/index.vue.d.ts +3 -0
  43. package/dist/package/declarations/src/views/base/message/index.vue.d.ts +3 -0
  44. package/dist/package/declarations/src/views/base/message-category/index.vue.d.ts +3 -0
  45. package/dist/package/declarations/src/views/base/oauth-client/index.vue.d.ts +3 -0
  46. package/dist/package/declarations/src/views/base/operation-log/index.vue.d.ts +3 -0
  47. package/dist/package/declarations/src/views/base/permission-log/index.vue.d.ts +3 -0
  48. package/dist/package/declarations/src/views/base/policy-evaluation-log/index.vue.d.ts +3 -0
  49. package/dist/package/declarations/src/views/message/inbox/index.vue.d.ts +3 -0
  50. package/dist/package/declarations/src/views/profile/components/session.vue.d.ts +3 -0
  51. package/dist/package/src/api/base/notification.ts +69 -0
  52. package/dist/package/src/api/base/sse.ts +4 -4
  53. package/dist/package/src/api/system/auth.ts +1 -0
  54. package/dist/package/src/api/system/base_audit_log.ts +168 -0
  55. package/dist/package/src/api/system/base_dashboard.ts +55 -0
  56. package/dist/package/src/api/system/base_file.ts +43 -0
  57. package/dist/package/src/api/system/base_i18n.ts +12 -12
  58. package/dist/package/src/api/system/base_login_policy.ts +32 -0
  59. package/dist/package/src/api/system/base_message.ts +70 -0
  60. package/dist/package/src/api/system/base_message_category.ts +57 -0
  61. package/dist/package/src/api/system/base_session.ts +33 -0
  62. package/dist/package/src/api/system/oauth_client.ts +102 -0
  63. package/dist/package/src/components/AuditLogTable.vue +105 -0
  64. package/dist/package/src/components/{DynamicTranslationCell.vue → DynamicI18nCell.vue} +50 -50
  65. package/dist/package/src/components/{DynamicTranslationEditor.vue → DynamicI18nEditor.vue} +45 -45
  66. package/dist/package/src/components/Notification.vue +196 -0
  67. package/dist/package/src/components/auditLog.ts +46 -0
  68. package/dist/package/src/components/{dynamicTranslation.ts → dynamicI18n.ts} +15 -15
  69. package/dist/package/src/locales/README.md +5 -3
  70. package/dist/package/src/locales/en-US.json +345 -21
  71. package/dist/package/src/locales/ja-JP.json +347 -23
  72. package/dist/package/src/locales/zh-CN.json +343 -19
  73. package/dist/package/src/locales/zh-TW.json +346 -22
  74. package/dist/package/src/module.ts +5 -2
  75. package/dist/package/src/rpc/base/v1/login.ts +172 -0
  76. package/dist/package/src/rpc/base/v1/notification.ts +315 -0
  77. package/dist/package/src/rpc/base/v1/oauth.ts +25 -0
  78. package/dist/package/src/rpc/base/v1/oauth_client.ts +33 -0
  79. package/dist/package/src/rpc/system/admin/v1/base_audit_log.ts +737 -0
  80. package/dist/package/src/rpc/system/admin/v1/base_config.ts +3 -3
  81. package/dist/package/src/rpc/system/admin/v1/base_dashboard.ts +81 -0
  82. package/dist/package/src/rpc/system/admin/v1/base_dict.ts +4 -4
  83. package/dist/package/src/rpc/system/admin/v1/base_file.ts +88 -0
  84. package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +27 -23
  85. package/dist/package/src/rpc/system/admin/v1/base_job.ts +5 -0
  86. package/dist/package/src/rpc/system/admin/v1/base_login_policy.ts +63 -0
  87. package/dist/package/src/rpc/system/admin/v1/base_menu.ts +2 -2
  88. package/dist/package/src/rpc/system/admin/v1/base_message.ts +236 -0
  89. package/dist/package/src/rpc/system/admin/v1/base_message_category.ts +149 -0
  90. package/dist/package/src/rpc/system/admin/v1/base_session.ts +44 -0
  91. package/dist/package/src/rpc/system/admin/v1/base_user.ts +3 -3
  92. package/dist/package/src/rpc/system/admin/v1/code_gen.ts +1 -1
  93. package/dist/package/src/rpc/system/admin/v1/oauth_client.ts +174 -0
  94. package/dist/package/src/views/ai/chat/components/ChatPanel.vue +2 -2
  95. package/dist/package/src/views/ai/chat/message.ts +7 -3
  96. package/dist/package/src/views/base/api-log/index.vue +83 -0
  97. package/dist/package/src/views/base/config/index.vue +41 -41
  98. package/dist/package/src/views/base/dashboard/index.vue +200 -0
  99. package/dist/package/src/views/base/data-access-log/index.vue +84 -0
  100. package/dist/package/src/views/base/dict/index.vue +23 -23
  101. package/dist/package/src/views/base/dict/item.vue +25 -25
  102. package/dist/package/src/views/base/file/index.vue +119 -0
  103. package/dist/package/src/views/base/job/index.vue +62 -7
  104. package/dist/package/src/views/base/language/index.vue +2 -1
  105. package/dist/package/src/views/base/login-log/index.vue +92 -0
  106. package/dist/package/src/views/base/login-policy/index.vue +219 -0
  107. package/dist/package/src/views/base/menu/index.vue +23 -23
  108. package/dist/package/src/views/base/message/index.vue +582 -0
  109. package/dist/package/src/views/base/message-category/index.vue +356 -0
  110. package/dist/package/src/views/base/oauth-client/index.vue +411 -0
  111. package/dist/package/src/views/base/operation-log/index.vue +82 -0
  112. package/dist/package/src/views/base/permission-log/index.vue +85 -0
  113. package/dist/package/src/views/base/policy-evaluation-log/index.vue +82 -0
  114. package/dist/package/src/views/message/inbox/index.vue +249 -0
  115. package/dist/package/src/views/profile/components/base.vue +6 -5
  116. package/dist/package/src/views/profile/components/password.vue +2 -2
  117. package/dist/package/src/views/profile/components/security.vue +475 -62
  118. package/dist/package/src/views/profile/components/session.vue +153 -0
  119. package/dist/package/src/views/profile/index.vue +35 -22
  120. package/dist/package/src/views/tool/code-gen/code-preview/index.vue +9 -9
  121. package/dist/package/src/views/tool/code-gen/columns/index.vue +1 -1
  122. package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +8 -8
  123. package/dist/package/src/views/tool/code-gen/config.ts +1 -0
  124. package/dist/package/src/views/tool/code-gen/table/index.vue +7 -7
  125. package/dist/package/src/views/tool/ops-monitoring/index.vue +25 -21
  126. package/package.json +2 -2
  127. package/dist/package/declarations/src/api/system/base_log.d.ts +0 -9
  128. package/dist/package/declarations/src/components/DynamicTranslationCell.vue.d.ts +0 -16
  129. package/dist/package/declarations/src/components/DynamicTranslationEditor.vue.d.ts +0 -20
  130. package/dist/package/declarations/src/components/dynamicTranslation.d.ts +0 -38
  131. package/dist/package/declarations/src/rpc/system/admin/v1/base_log.d.ts +0 -87
  132. package/dist/package/src/api/system/base_log.ts +0 -32
  133. package/dist/package/src/rpc/system/admin/v1/base_log.ts +0 -101
  134. package/dist/package/src/views/base/log/index.vue +0 -339
  135. /package/dist/package/declarations/src/{views/base/log/index.vue.d.ts → components/Notification.vue.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <el-link class="dynamic-translation-cell" type="primary" underline="never" @click="openDialog">
3
- <span class="dynamic-translation-cell__source">{{ displaySource || "--" }}</span>
2
+ <el-link class="dynamic-i18n-cell" type="primary" underline="never" @click="openDialog">
3
+ <span class="dynamic-i18n-cell__source">{{ displaySource || "--" }}</span>
4
4
  </el-link>
5
5
 
6
6
  <ProDialog
@@ -12,20 +12,20 @@
12
12
  :destroy-on-close="true"
13
13
  @closed="handleDialogClosed"
14
14
  >
15
- <el-table v-loading="loading" :data="translationRows" border>
15
+ <el-table v-loading="loading" :data="i18nRows" border>
16
16
  <el-table-column :label="t('common.field.language')" width="150">
17
17
  <template #default="{ row }">
18
18
  {{ getLanguageLabel(row.locale) }}
19
19
  </template>
20
20
  </el-table-column>
21
- <el-table-column :label="t('system.base.translation.field.translations')" min-width="360">
21
+ <el-table-column :label="t('system.base.i18n.field.i18ns')" min-width="360">
22
22
  <template #default="{ row }">
23
23
  <el-input
24
24
  v-if="row.editing"
25
25
  v-model="row.text"
26
- :placeholder="t('system.base.translation.placeholder.text', { language: getLanguageLabel(row.locale) })"
26
+ :placeholder="t('system.base.i18n.placeholder.text', { language: getLanguageLabel(row.locale) })"
27
27
  />
28
- <span v-else class="dynamic-translation-dialog__text">
28
+ <span v-else class="dynamic-i18n-dialog__text">
29
29
  {{ row.text || t("common.value.none") }}
30
30
  </span>
31
31
  </template>
@@ -36,7 +36,7 @@
36
36
  {{ t("common.action.edit") }}
37
37
  </el-button>
38
38
  <template v-else>
39
- <el-button link type="primary" :icon="Check" :loading="row.saving" @click="saveTranslation(row)">
39
+ <el-button link type="primary" :icon="Check" :loading="row.saving" @click="saveI18n(row)">
40
40
  {{ t("common.action.save") }}
41
41
  </el-button>
42
42
  <el-button link :icon="Close" :disabled="row.saving" @click="cancelEdit(row)">
@@ -58,48 +58,48 @@ import { t, useLocaleStore } from "@liujitcn/kratos-admin-core";
58
58
  import { loadEnabledBaseLanguages, useEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/base_language";
59
59
  import { defBaseI18nService } from "@liujitcn/kratos-admin-system/api/system/base_i18n";
60
60
  import type { BaseI18n } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
61
- import { TranslationTargetType } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
62
- import { getLanguageLabel } from "./dynamicTranslation";
61
+ import { I18nTargetType } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
62
+ import { getLanguageLabel } from "./dynamicI18n";
63
63
 
64
- /** DynamicTranslationCellProps 动态翻译列表单元格属性。 */
65
- interface DynamicTranslationCellProps {
64
+ /** DynamicI18nCellProps 动态翻译列表单元格属性。 */
65
+ interface DynamicI18nCellProps {
66
66
  /** 资源源文。 */
67
67
  source: string;
68
68
  /** 统一翻译表目标类型。 */
69
- targetType?: TranslationTargetType;
69
+ targetType?: I18nTargetType;
70
70
  /** 目标资源编号。 */
71
71
  targetId?: number;
72
72
  /** 资源列表返回的非主语言翻译。 */
73
- translations?: BaseI18n[];
73
+ i18ns?: BaseI18n[];
74
74
  }
75
75
 
76
- const props = defineProps<DynamicTranslationCellProps>();
76
+ const props = defineProps<DynamicI18nCellProps>();
77
77
 
78
78
  const { languages } = useEnabledBaseLanguages();
79
79
  const { locale } = useLocaleStore();
80
80
  const dialogVisible = ref(false);
81
81
  const loading = ref(false);
82
- const translationRows = ref<DynamicTranslationRow[]>([]);
83
- const translationOverrides = ref(new Map<string, string>());
82
+ const i18nRows = ref<DynamicI18nRow[]>([]);
83
+ const i18nOverrides = ref(new Map<string, string>());
84
84
 
85
85
  const displaySource = computed(() => {
86
- const override = translationOverrides.value.get(locale.value);
86
+ const override = i18nOverrides.value.get(locale.value);
87
87
  if (override) return override;
88
- const translation = props.translations?.find(item => item.locale === locale.value && item.name);
89
- return translation?.name || props.source;
88
+ const i18n = props.i18ns?.find(item => item.locale === locale.value && item.name);
89
+ return i18n?.name || props.source;
90
90
  });
91
91
 
92
92
  watch(
93
93
  () => [props.targetType, props.targetId],
94
94
  () => {
95
- translationRows.value = [];
96
- translationOverrides.value.clear();
95
+ i18nRows.value = [];
96
+ i18nOverrides.value.clear();
97
97
  dialogVisible.value = false;
98
98
  }
99
99
  );
100
100
 
101
- /** DynamicTranslationRow 描述弹窗内可编辑的单语言翻译行。 */
102
- interface DynamicTranslationRow {
101
+ /** DynamicI18nRow 描述弹窗内可编辑的单语言翻译行。 */
102
+ interface DynamicI18nRow {
103
103
  /** 翻译记录主键,新增翻译时为空。 */
104
104
  id: number;
105
105
  /** 语言区域。 */
@@ -117,43 +117,43 @@ interface DynamicTranslationRow {
117
117
  /** openDialog 打开翻译编辑弹窗并加载当前资源译文。 */
118
118
  async function openDialog() {
119
119
  dialogVisible.value = true;
120
- await loadTranslations();
120
+ await loadI18ns();
121
121
  }
122
122
 
123
- /** loadTranslations 查询当前资源的翻译并补齐启用语言行。 */
124
- async function loadTranslations() {
123
+ /** loadI18ns 查询当前资源的翻译并补齐启用语言行。 */
124
+ async function loadI18ns() {
125
125
  const targetType = props.targetType;
126
126
  const targetId = props.targetId;
127
127
  if (!targetType || !targetId) return;
128
128
  loading.value = true;
129
129
  try {
130
130
  await loadEnabledBaseLanguages();
131
- const rows = await queryTranslationRows();
132
- translationRows.value = rows;
131
+ const rows = await queryI18nRows();
132
+ i18nRows.value = rows;
133
133
  const missingRows = rows.filter(row => !row.text);
134
134
  if (missingRows.length === 0) return;
135
135
  if (!props.source) return;
136
- const draft = await defBaseI18nService.DraftBaseTranslation({ source: props.source });
137
- const translations = new Map(draft.translations.map(item => [item.locale, item.translation]));
136
+ const draft = await defBaseI18nService.DraftBaseI18n({ source: props.source });
137
+ const i18ns = new Map(draft.i18ns.map(item => [item.locale, item.i18n]));
138
138
  const results = await Promise.allSettled(
139
139
  missingRows.map(async row => {
140
- const translation = translations.get(row.locale);
141
- if (!translation) throw new Error("translation not found");
142
- await defBaseI18nService.UpdateBaseTranslation({
140
+ const i18n = i18ns.get(row.locale);
141
+ if (!i18n) throw new Error("i18n not found");
142
+ await defBaseI18nService.UpdateBaseI18n({
143
143
  id: row.id,
144
144
  target_type: targetType,
145
145
  target_id: targetId,
146
146
  locale: row.locale,
147
- name: translation
147
+ name: i18n
148
148
  });
149
- return { row, text: translation };
149
+ return { row, text: i18n };
150
150
  })
151
151
  );
152
152
  for (const result of results) {
153
153
  if (result.status !== "fulfilled") continue;
154
154
  result.value.row.text = result.value.text;
155
155
  result.value.row.originalText = result.value.text;
156
- translationOverrides.value.set(result.value.row.locale, result.value.text);
156
+ i18nOverrides.value.set(result.value.row.locale, result.value.text);
157
157
  }
158
158
  } catch {
159
159
  ElMessage.error(t("common.message.system_error"));
@@ -162,14 +162,14 @@ async function loadTranslations() {
162
162
  }
163
163
  }
164
164
 
165
- /** queryTranslationRows 将列表返回的翻译记录补齐为可编辑语言行。 */
166
- async function queryTranslationRows(): Promise<DynamicTranslationRow[]> {
167
- const records = new Map((props.translations ?? []).map(item => [item.locale, item]));
165
+ /** queryI18nRows 将列表返回的翻译记录补齐为可编辑语言行。 */
166
+ async function queryI18nRows(): Promise<DynamicI18nRow[]> {
167
+ const records = new Map((props.i18ns ?? []).map(item => [item.locale, item]));
168
168
  return languages.value
169
169
  .filter(item => !item.is_primary)
170
170
  .map(item => {
171
171
  const record = records.get(item.language_code);
172
- const text = translationOverrides.value.get(item.language_code) ?? record?.name ?? "";
172
+ const text = i18nOverrides.value.get(item.language_code) ?? record?.name ?? "";
173
173
  return {
174
174
  id: record?.id ?? 0,
175
175
  locale: item.language_code,
@@ -182,19 +182,19 @@ async function queryTranslationRows(): Promise<DynamicTranslationRow[]> {
182
182
  }
183
183
 
184
184
  /** startEdit 开启指定语言的编辑状态。 */
185
- function startEdit(row: DynamicTranslationRow) {
185
+ function startEdit(row: DynamicI18nRow) {
186
186
  row.originalText = row.text;
187
187
  row.editing = true;
188
188
  }
189
189
 
190
190
  /** cancelEdit 恢复编辑前的内容。 */
191
- function cancelEdit(row: DynamicTranslationRow) {
191
+ function cancelEdit(row: DynamicI18nRow) {
192
192
  row.text = row.originalText;
193
193
  row.editing = false;
194
194
  }
195
195
 
196
- /** saveTranslation 使用统一更新接口保存指定语言译文。 */
197
- async function saveTranslation(row: DynamicTranslationRow) {
196
+ /** saveI18n 使用统一更新接口保存指定语言译文。 */
197
+ async function saveI18n(row: DynamicI18nRow) {
198
198
  const targetType = props.targetType;
199
199
  const targetId = props.targetId;
200
200
  if (row.saving || !targetType || !targetId) return;
@@ -204,7 +204,7 @@ async function saveTranslation(row: DynamicTranslationRow) {
204
204
  }
205
205
  row.saving = true;
206
206
  try {
207
- await defBaseI18nService.UpdateBaseTranslation({
207
+ await defBaseI18nService.UpdateBaseI18n({
208
208
  id: row.id,
209
209
  target_type: targetType,
210
210
  target_id: targetId,
@@ -214,7 +214,7 @@ async function saveTranslation(row: DynamicTranslationRow) {
214
214
  ElMessage.success(t("common.message.operation_success"));
215
215
  row.editing = false;
216
216
  row.originalText = row.text;
217
- translationOverrides.value.set(row.locale, row.text);
217
+ i18nOverrides.value.set(row.locale, row.text);
218
218
  } catch {
219
219
  ElMessage.error(t("common.message.system_error"));
220
220
  } finally {
@@ -224,25 +224,25 @@ async function saveTranslation(row: DynamicTranslationRow) {
224
224
 
225
225
  /** handleDialogClosed 清理弹窗状态,避免复用旧资源译文。 */
226
226
  function handleDialogClosed() {
227
- translationRows.value = [];
227
+ i18nRows.value = [];
228
228
  loading.value = false;
229
229
  }
230
230
  </script>
231
231
 
232
232
  <style scoped>
233
- .dynamic-translation-cell {
233
+ .dynamic-i18n-cell {
234
234
  display: inline-flex;
235
235
  align-items: center;
236
236
  min-width: 0;
237
237
  max-width: 100%;
238
238
  }
239
- .dynamic-translation-cell__source {
239
+ .dynamic-i18n-cell__source {
240
240
  min-width: 0;
241
241
  overflow: hidden;
242
242
  text-overflow: ellipsis;
243
243
  white-space: nowrap;
244
244
  }
245
- .dynamic-translation-dialog__text {
245
+ .dynamic-i18n-dialog__text {
246
246
  min-width: 0;
247
247
  overflow-wrap: anywhere;
248
248
  }
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="translation-editor">
3
- <div class="translation-editor__toolbar">
2
+ <div class="i18n-editor">
3
+ <div class="i18n-editor__toolbar">
4
4
  <el-button
5
5
  type="primary"
6
6
  size="small"
@@ -9,14 +9,14 @@
9
9
  :disabled="!canTranslate"
10
10
  @click="handleBatchTranslate"
11
11
  >
12
- {{ t("system.base.translation.action.batch_translate") }}
12
+ {{ t("system.base.i18n.action.batch_translate") }}
13
13
  </el-button>
14
14
  </div>
15
- <div v-for="item in localValues" :key="item.locale" class="translation-editor__row">
16
- <div class="translation-editor__heading">
15
+ <div v-for="item in localValues" :key="item.locale" class="i18n-editor__row">
16
+ <div class="i18n-editor__heading">
17
17
  <span>{{ getLanguageLabel(item.locale) }}</span>
18
18
  </div>
19
- <div class="translation-editor__control" :class="{ 'translation-editor__control--multiline': multiline }">
19
+ <div class="i18n-editor__control" :class="{ 'i18n-editor__control--multiline': multiline }">
20
20
  <el-input
21
21
  :model-value="item.text"
22
22
  :maxlength="maxlength"
@@ -24,11 +24,11 @@
24
24
  :rows="multiline ? 6 : undefined"
25
25
  :disabled="translating || translatingLocale === item.locale"
26
26
  show-word-limit
27
- :placeholder="t('system.base.translation.placeholder.text', { language: getLanguageLabel(item.locale) })"
27
+ :placeholder="t('system.base.i18n.placeholder.text', { language: getLanguageLabel(item.locale) })"
28
28
  @update:model-value="value => updateText(item.locale, value)"
29
29
  />
30
30
  <el-button
31
- class="translation-editor__translate"
31
+ class="i18n-editor__translate"
32
32
  link
33
33
  type="primary"
34
34
  size="small"
@@ -37,7 +37,7 @@
37
37
  :disabled="!props.source || Boolean(item.text) || translating || Boolean(translatingLocale)"
38
38
  @click="handleTranslate(item.locale)"
39
39
  >
40
- {{ t("system.base.translation.action.translate") }}
40
+ {{ t("system.base.i18n.action.translate") }}
41
41
  </el-button>
42
42
  </div>
43
43
  </div>
@@ -50,26 +50,26 @@ import { ElMessage } from "element-plus";
50
50
  import { Promotion } from "@element-plus/icons-vue";
51
51
  import { t, useLocaleStore } from "@liujitcn/kratos-admin-core";
52
52
  import { defBaseI18nService } from "@liujitcn/kratos-admin-system/api/system/base_i18n";
53
- import { getLanguageLabel, type DynamicTranslationValue } from "./dynamicTranslation";
53
+ import { getLanguageLabel, type DynamicI18nValue } from "./dynamicI18n";
54
54
 
55
- /** DynamicTranslationEditorProps 动态翻译编辑器属性。 */
56
- interface DynamicTranslationEditorProps {
57
- modelValue: DynamicTranslationValue[];
55
+ /** DynamicI18nEditorProps 动态翻译编辑器属性。 */
56
+ interface DynamicI18nEditorProps {
57
+ modelValue: DynamicI18nValue[];
58
58
  /** 当前语言源文本。 */
59
59
  source?: string;
60
60
  maxlength?: number;
61
61
  multiline?: boolean;
62
62
  }
63
63
 
64
- const props = withDefaults(defineProps<DynamicTranslationEditorProps>(), {
64
+ const props = withDefaults(defineProps<DynamicI18nEditorProps>(), {
65
65
  source: "",
66
66
  maxlength: 100,
67
67
  multiline: false
68
68
  });
69
69
 
70
70
  const { locale: currentLocale } = useLocaleStore();
71
- const emit = defineEmits<{ "update:modelValue": [value: DynamicTranslationValue[]] }>();
72
- const localValues = ref<DynamicTranslationValue[]>([]);
71
+ const emit = defineEmits<{ "update:modelValue": [value: DynamicI18nValue[]] }>();
72
+ const localValues = ref<DynamicI18nValue[]>([]);
73
73
  const translating = ref(false);
74
74
  const translatingLocale = ref("");
75
75
  const canTranslate = computed(() => Boolean(props.source) && localValues.value.some(item => !item.text && item.locale !== currentLocale.value));
@@ -83,7 +83,7 @@ watch(
83
83
  );
84
84
 
85
85
  /** updateText 更新指定语言的人工译文。 */
86
- function updateText(locale: DynamicTranslationValue["locale"], text: string) {
86
+ function updateText(locale: DynamicI18nValue["locale"], text: string) {
87
87
  const values = localValues.value.map(item => (item.locale === locale ? { ...item, text } : item));
88
88
  localValues.value = values;
89
89
  emit("update:modelValue", values);
@@ -95,29 +95,29 @@ async function handleBatchTranslate() {
95
95
  translating.value = true;
96
96
  try {
97
97
  const pending = localValues.value.filter(item => !item.text && item.locale !== currentLocale.value);
98
- const response = await defBaseI18nService.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) {
98
+ const response = await defBaseI18nService.DraftBaseI18n({ source: props.source });
99
+ const i18ns = new Map(response.i18ns.map(item => [item.locale, item.i18n]));
100
+ const failed = pending.filter(item => !i18ns.has(item.locale)).length;
101
+ if (i18ns.size) {
102
102
  const values = localValues.value.map(item => {
103
- const translation = translations.get(item.locale);
104
- return translation === undefined ? item : { ...item, text: translation };
103
+ const i18n = i18ns.get(item.locale);
104
+ return i18n === undefined ? item : { ...item, text: i18n };
105
105
  });
106
106
  localValues.value = values;
107
107
  emit("update:modelValue", values);
108
108
  }
109
109
  if (failed) {
110
110
  ElMessage.warning(
111
- t("system.base.translation.message.batch_translate_partial", {
112
- success: translations.size,
111
+ t("system.base.i18n.message.batch_translate_partial", {
112
+ success: i18ns.size,
113
113
  failed
114
114
  })
115
115
  );
116
116
  } else {
117
- ElMessage.success(t("system.base.translation.message.batch_translate_success", { count: translations.size }));
117
+ ElMessage.success(t("system.base.i18n.message.batch_translate_success", { count: i18ns.size }));
118
118
  }
119
119
  } catch {
120
- ElMessage.error(t("system.base.translation.message.batch_translate_failed"));
120
+ ElMessage.error(t("system.base.i18n.message.batch_translate_failed"));
121
121
  } finally {
122
122
  translating.value = false;
123
123
  }
@@ -129,15 +129,15 @@ async function handleTranslate(locale: string) {
129
129
  if (!item || item.text || item.locale === currentLocale.value || !props.source || translating.value || translatingLocale.value) return;
130
130
  translatingLocale.value = locale;
131
131
  try {
132
- const response = await defBaseI18nService.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));
132
+ const response = await defBaseI18nService.DraftBaseI18n({ source: props.source, locale });
133
+ const i18n = response.i18ns.find(item => item.locale === locale)?.i18n;
134
+ if (!i18n) throw new Error("i18n not found");
135
+ const values = localValues.value.map(value => (value.locale === locale ? { ...value, text: i18n } : value));
136
136
  localValues.value = values;
137
137
  emit("update:modelValue", values);
138
- ElMessage.success(t("system.base.translation.message.translate_success", { language: getLanguageLabel(locale) }));
138
+ ElMessage.success(t("system.base.i18n.message.translate_success", { language: getLanguageLabel(locale) }));
139
139
  } catch {
140
- ElMessage.error(t("system.base.translation.message.translate_failed", { language: getLanguageLabel(locale) }));
140
+ ElMessage.error(t("system.base.i18n.message.translate_failed", { language: getLanguageLabel(locale) }));
141
141
  } finally {
142
142
  translatingLocale.value = "";
143
143
  }
@@ -146,51 +146,51 @@ async function handleTranslate(locale: string) {
146
146
  </script>
147
147
 
148
148
  <style scoped>
149
- .translation-editor {
149
+ .i18n-editor {
150
150
  display: flex;
151
151
  flex-direction: column;
152
152
  gap: 14px;
153
153
  width: 100%;
154
154
  }
155
- .translation-editor__toolbar {
155
+ .i18n-editor__toolbar {
156
156
  display: flex;
157
157
  justify-content: flex-end;
158
158
  }
159
- .translation-editor__row {
159
+ .i18n-editor__row {
160
160
  display: flex;
161
161
  flex-direction: column;
162
162
  gap: 6px;
163
163
  }
164
- .translation-editor__heading,
165
- .translation-editor__control {
164
+ .i18n-editor__heading,
165
+ .i18n-editor__control {
166
166
  display: flex;
167
167
  gap: 8px;
168
168
  align-items: center;
169
169
  }
170
- .translation-editor__heading {
170
+ .i18n-editor__heading {
171
171
  color: var(--el-text-color-regular);
172
172
  }
173
- .translation-editor__control {
173
+ .i18n-editor__control {
174
174
  position: relative;
175
175
  width: 100%;
176
176
  }
177
- .translation-editor__control :deep(.el-input) {
177
+ .i18n-editor__control :deep(.el-input) {
178
178
  flex: 1;
179
179
  }
180
- .translation-editor__control--multiline :deep(.el-textarea__inner) {
180
+ .i18n-editor__control--multiline :deep(.el-textarea__inner) {
181
181
  padding-right: 78px;
182
182
  }
183
- .translation-editor__control:not(.translation-editor__control--multiline) :deep(.el-input__suffix) {
183
+ .i18n-editor__control:not(.i18n-editor__control--multiline) :deep(.el-input__suffix) {
184
184
  padding-right: 78px;
185
185
  }
186
- .translation-editor__translate {
186
+ .i18n-editor__translate {
187
187
  position: absolute;
188
188
  z-index: 1;
189
189
  top: 50%;
190
190
  right: 8px;
191
191
  transform: translateY(-50%);
192
192
  }
193
- .translation-editor__control--multiline .translation-editor__translate {
193
+ .i18n-editor__control--multiline .i18n-editor__translate {
194
194
  top: 8px;
195
195
  transform: none;
196
196
  }
@@ -0,0 +1,196 @@
1
+ <template>
2
+ <el-popover placement="bottom-end" :width="360" trigger="click" @show="refresh">
3
+ <template #reference>
4
+ <el-badge class="notification-trigger" :value="badgeValue" :hidden="unreadTotal === 0" :max="99">
5
+ <el-icon :size="24"><Bell /></el-icon>
6
+ </el-badge>
7
+ </template>
8
+ <div class="notification-panel">
9
+ <div class="notification-header">
10
+ <strong>{{ t("system.notification.title") }}</strong>
11
+ <el-button link type="primary" @click="markAllRead">{{ t("system.notification.action.mark_all_read") }}</el-button>
12
+ </div>
13
+ <div class="notification-list">
14
+ <button
15
+ v-for="item in notifications"
16
+ :key="item.id"
17
+ class="notification-item"
18
+ :class="{ 'is-unread': !item.read_at }"
19
+ type="button"
20
+ @click="openInbox"
21
+ >
22
+ <span class="notification-category-icon" :style="{ color: item.category_color || undefined }" aria-hidden="true">
23
+ <component :is="resolveNotificationIcon(item.category_icon)" />
24
+ </span>
25
+ <span class="notification-status" :style="{ backgroundColor: item.category_color || undefined }" aria-hidden="true"></span>
26
+ <span class="notification-title">{{ item.title }}</span>
27
+ <span class="notification-time">{{ item.received_at }}</span>
28
+ </button>
29
+ <el-empty v-if="notifications.length === 0" :description="t('system.notification.empty')" :image-size="72" />
30
+ </div>
31
+ <el-button class="notification-more" text @click="openInbox">{{ t("system.notification.action.view_all") }}</el-button>
32
+ </div>
33
+ </el-popover>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue";
38
+ import { useRouter } from "vue-router";
39
+ import { t } from "@liujitcn/kratos-admin-core";
40
+ import { defNotificationService } from "@liujitcn/kratos-admin-system/api/base/notification";
41
+ import { subscribeSseEvent, type SseStop } from "@liujitcn/kratos-admin-system/api/base/sse";
42
+ import type { Notification } from "@liujitcn/kratos-admin-system/rpc/base/v1/notification";
43
+ import { NotificationView } from "@liujitcn/kratos-admin-system/rpc/base/v1/notification";
44
+
45
+ const router = useRouter();
46
+ const unreadTotal = ref(0);
47
+ const latestDeliveryID = ref(0);
48
+ const notifications = ref<Notification[]>([]);
49
+ let stopSse: SseStop | undefined;
50
+
51
+ const badgeValue = computed(() => Math.min(unreadTotal.value, 99));
52
+ const notificationIcons = {
53
+ Bell,
54
+ Calendar,
55
+ ChatDotRound,
56
+ CircleCheckFilled,
57
+ CollectionTag,
58
+ DataAnalysis,
59
+ InfoFilled,
60
+ List,
61
+ Lock,
62
+ Message,
63
+ Promotion,
64
+ Setting,
65
+ User,
66
+ WarningFilled
67
+ };
68
+
69
+ onMounted(() => {
70
+ void refresh();
71
+ stopSse = subscribeSseEvent(
72
+ { stream: "base.notification", channel_id: undefined },
73
+ "inbox.changed",
74
+ raw => {
75
+ try {
76
+ return JSON.parse(raw) as Record<string, unknown>;
77
+ } catch {
78
+ return null;
79
+ }
80
+ },
81
+ () => void refresh()
82
+ );
83
+ });
84
+
85
+ onBeforeUnmount(() => stopSse?.());
86
+
87
+ /** 刷新未读汇总和最近消息。 */
88
+ async function refresh() {
89
+ const [summary, page] = await Promise.all([
90
+ defNotificationService.GetNotificationSummary({}),
91
+ defNotificationService.PageNotification({ view: NotificationView.NOTIFICATION_VIEW_INBOX, category_id: undefined, priority: undefined, cursor_id: 0, page_num: 1, page_size: 5 })
92
+ ]);
93
+ unreadTotal.value = summary.unread_total;
94
+ latestDeliveryID.value = summary.latest_delivery_id;
95
+ notifications.value = page.notifications;
96
+ }
97
+
98
+ /** 标记全部消息为已读。 */
99
+ async function markAllRead() {
100
+ if (!latestDeliveryID.value) return;
101
+ await defNotificationService.MarkAllNotificationRead({ before_delivery_id: latestDeliveryID.value });
102
+ await refresh();
103
+ }
104
+
105
+ /** 打开完整收件箱。 */
106
+ function openInbox() {
107
+ void router.push({ name: "NotificationInbox" });
108
+ }
109
+
110
+ /** 解析收件箱分类图标,兼容历史数据中的未知图标名。 */
111
+ function resolveNotificationIcon(icon: string) {
112
+ return notificationIcons[icon as keyof typeof notificationIcons] ?? CollectionTag;
113
+ }
114
+ </script>
115
+
116
+ <style scoped lang="scss">
117
+ .notification-trigger {
118
+ display: inline-flex;
119
+ width: 24px;
120
+ height: 24px;
121
+ align-items: center;
122
+ justify-content: center;
123
+ cursor: pointer;
124
+ }
125
+
126
+ .notification-panel {
127
+ min-height: 160px;
128
+ }
129
+
130
+ .notification-list {
131
+ max-height: min(440px, calc(100vh - 240px));
132
+ overflow-y: auto;
133
+ overscroll-behavior: contain;
134
+ }
135
+
136
+ .notification-header {
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: space-between;
140
+ margin-bottom: 8px;
141
+ }
142
+
143
+ .notification-item {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 8px;
147
+ width: 100%;
148
+ padding: 10px 0;
149
+ border: 0;
150
+ border-bottom: 1px solid var(--el-border-color-lighter);
151
+ background: transparent;
152
+ color: var(--el-text-color-primary);
153
+ cursor: pointer;
154
+ text-align: left;
155
+ }
156
+
157
+ .notification-status {
158
+ width: 7px;
159
+ height: 7px;
160
+ flex: 0 0 7px;
161
+ border-radius: 50%;
162
+ background: var(--el-color-primary);
163
+ visibility: hidden;
164
+ }
165
+
166
+ .notification-category-icon {
167
+ display: inline-flex;
168
+ flex: 0 0 18px;
169
+ align-items: center;
170
+ justify-content: center;
171
+ font-size: 17px;
172
+ }
173
+
174
+ .notification-item.is-unread .notification-status {
175
+ visibility: visible;
176
+ }
177
+
178
+ .notification-title {
179
+ min-width: 0;
180
+ flex: 1;
181
+ overflow: hidden;
182
+ text-overflow: ellipsis;
183
+ white-space: nowrap;
184
+ }
185
+
186
+ .notification-time {
187
+ margin-left: 12px;
188
+ color: var(--el-text-color-secondary);
189
+ font-size: 12px;
190
+ }
191
+
192
+ .notification-more {
193
+ width: 100%;
194
+ margin-top: 8px;
195
+ }
196
+ </style>