@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.
- package/README.md +19 -2
- package/dist/package/declarations/src/api/base/notification.d.ts +24 -0
- package/dist/package/declarations/src/api/system/base_audit_log.d.ts +55 -0
- package/dist/package/declarations/src/api/system/base_dashboard.d.ts +14 -0
- package/dist/package/declarations/src/api/system/base_file.d.ts +13 -0
- package/dist/package/declarations/src/api/system/base_i18n.d.ts +5 -5
- package/dist/package/declarations/src/api/system/base_login_policy.d.ts +10 -0
- package/dist/package/declarations/src/api/system/base_message.d.ts +24 -0
- package/dist/package/declarations/src/api/system/base_message_category.d.ts +21 -0
- package/dist/package/declarations/src/api/system/base_session.d.ts +11 -0
- package/dist/package/declarations/src/api/system/oauth_client.d.ts +24 -0
- package/dist/package/declarations/src/components/AuditLogTable.vue.d.ts +45 -0
- package/dist/package/declarations/src/components/DynamicI18nCell.vue.d.ts +16 -0
- package/dist/package/declarations/src/components/DynamicI18nEditor.vue.d.ts +20 -0
- package/dist/package/declarations/src/components/auditLog.d.ts +9 -0
- package/dist/package/declarations/src/components/dynamicI18n.d.ts +38 -0
- package/dist/package/declarations/src/locales/generated.d.ts +1364 -68
- package/dist/package/declarations/src/rpc/base/v1/login.d.ts +149 -0
- package/dist/package/declarations/src/rpc/base/v1/notification.d.ts +281 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +25 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth_client.d.ts +23 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_audit_log.d.ts +640 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +3 -3
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dashboard.d.ts +60 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +4 -4
- package/dist/package/declarations/src/rpc/system/admin/v1/base_file.d.ts +73 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +27 -23
- package/dist/package/declarations/src/rpc/system/admin/v1/base_job.d.ts +5 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_login_policy.d.ts +51 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +2 -2
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message.d.ts +193 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message_category.d.ts +125 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_session.d.ts +33 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_user.d.ts +3 -3
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +1 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/oauth_client.d.ts +145 -0
- package/dist/package/declarations/src/views/base/api-log/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/dashboard/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/data-access-log/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/file/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/login-log/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/login-policy/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/message/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/message-category/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/oauth-client/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/operation-log/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/permission-log/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/policy-evaluation-log/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/message/inbox/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/profile/components/session.vue.d.ts +3 -0
- package/dist/package/src/api/base/notification.ts +69 -0
- package/dist/package/src/api/base/sse.ts +4 -4
- package/dist/package/src/api/system/auth.ts +1 -0
- package/dist/package/src/api/system/base_audit_log.ts +168 -0
- package/dist/package/src/api/system/base_dashboard.ts +55 -0
- package/dist/package/src/api/system/base_file.ts +43 -0
- package/dist/package/src/api/system/base_i18n.ts +12 -12
- package/dist/package/src/api/system/base_login_policy.ts +32 -0
- package/dist/package/src/api/system/base_message.ts +70 -0
- package/dist/package/src/api/system/base_message_category.ts +57 -0
- package/dist/package/src/api/system/base_session.ts +33 -0
- package/dist/package/src/api/system/oauth_client.ts +102 -0
- package/dist/package/src/components/AuditLogTable.vue +105 -0
- package/dist/package/src/components/{DynamicTranslationCell.vue → DynamicI18nCell.vue} +50 -50
- package/dist/package/src/components/{DynamicTranslationEditor.vue → DynamicI18nEditor.vue} +45 -45
- package/dist/package/src/components/Notification.vue +196 -0
- package/dist/package/src/components/auditLog.ts +46 -0
- package/dist/package/src/components/{dynamicTranslation.ts → dynamicI18n.ts} +15 -15
- package/dist/package/src/locales/README.md +5 -3
- package/dist/package/src/locales/en-US.json +345 -21
- package/dist/package/src/locales/ja-JP.json +347 -23
- package/dist/package/src/locales/zh-CN.json +343 -19
- package/dist/package/src/locales/zh-TW.json +346 -22
- package/dist/package/src/module.ts +5 -2
- package/dist/package/src/rpc/base/v1/login.ts +172 -0
- package/dist/package/src/rpc/base/v1/notification.ts +315 -0
- package/dist/package/src/rpc/base/v1/oauth.ts +25 -0
- package/dist/package/src/rpc/base/v1/oauth_client.ts +33 -0
- package/dist/package/src/rpc/system/admin/v1/base_audit_log.ts +737 -0
- package/dist/package/src/rpc/system/admin/v1/base_config.ts +3 -3
- package/dist/package/src/rpc/system/admin/v1/base_dashboard.ts +81 -0
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +4 -4
- package/dist/package/src/rpc/system/admin/v1/base_file.ts +88 -0
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +27 -23
- package/dist/package/src/rpc/system/admin/v1/base_job.ts +5 -0
- package/dist/package/src/rpc/system/admin/v1/base_login_policy.ts +63 -0
- package/dist/package/src/rpc/system/admin/v1/base_menu.ts +2 -2
- package/dist/package/src/rpc/system/admin/v1/base_message.ts +236 -0
- package/dist/package/src/rpc/system/admin/v1/base_message_category.ts +149 -0
- package/dist/package/src/rpc/system/admin/v1/base_session.ts +44 -0
- package/dist/package/src/rpc/system/admin/v1/base_user.ts +3 -3
- package/dist/package/src/rpc/system/admin/v1/code_gen.ts +1 -1
- package/dist/package/src/rpc/system/admin/v1/oauth_client.ts +174 -0
- package/dist/package/src/views/ai/chat/components/ChatPanel.vue +2 -2
- package/dist/package/src/views/ai/chat/message.ts +7 -3
- package/dist/package/src/views/base/api-log/index.vue +83 -0
- package/dist/package/src/views/base/config/index.vue +41 -41
- package/dist/package/src/views/base/dashboard/index.vue +200 -0
- package/dist/package/src/views/base/data-access-log/index.vue +84 -0
- package/dist/package/src/views/base/dict/index.vue +23 -23
- package/dist/package/src/views/base/dict/item.vue +25 -25
- package/dist/package/src/views/base/file/index.vue +119 -0
- package/dist/package/src/views/base/job/index.vue +62 -7
- package/dist/package/src/views/base/language/index.vue +2 -1
- package/dist/package/src/views/base/login-log/index.vue +92 -0
- package/dist/package/src/views/base/login-policy/index.vue +219 -0
- package/dist/package/src/views/base/menu/index.vue +23 -23
- package/dist/package/src/views/base/message/index.vue +582 -0
- package/dist/package/src/views/base/message-category/index.vue +356 -0
- package/dist/package/src/views/base/oauth-client/index.vue +411 -0
- package/dist/package/src/views/base/operation-log/index.vue +82 -0
- package/dist/package/src/views/base/permission-log/index.vue +85 -0
- package/dist/package/src/views/base/policy-evaluation-log/index.vue +82 -0
- package/dist/package/src/views/message/inbox/index.vue +249 -0
- package/dist/package/src/views/profile/components/base.vue +6 -5
- package/dist/package/src/views/profile/components/password.vue +2 -2
- package/dist/package/src/views/profile/components/security.vue +475 -62
- package/dist/package/src/views/profile/components/session.vue +153 -0
- package/dist/package/src/views/profile/index.vue +35 -22
- package/dist/package/src/views/tool/code-gen/code-preview/index.vue +9 -9
- package/dist/package/src/views/tool/code-gen/columns/index.vue +1 -1
- package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +8 -8
- package/dist/package/src/views/tool/code-gen/config.ts +1 -0
- package/dist/package/src/views/tool/code-gen/table/index.vue +7 -7
- package/dist/package/src/views/tool/ops-monitoring/index.vue +25 -21
- package/package.json +2 -2
- package/dist/package/declarations/src/api/system/base_log.d.ts +0 -9
- package/dist/package/declarations/src/components/DynamicTranslationCell.vue.d.ts +0 -16
- package/dist/package/declarations/src/components/DynamicTranslationEditor.vue.d.ts +0 -20
- package/dist/package/declarations/src/components/dynamicTranslation.d.ts +0 -38
- package/dist/package/declarations/src/rpc/system/admin/v1/base_log.d.ts +0 -87
- package/dist/package/src/api/system/base_log.ts +0 -32
- package/dist/package/src/rpc/system/admin/v1/base_log.ts +0 -101
- package/dist/package/src/views/base/log/index.vue +0 -339
- /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-
|
|
3
|
-
<span class="dynamic-
|
|
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="
|
|
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.
|
|
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.
|
|
26
|
+
:placeholder="t('system.base.i18n.placeholder.text', { language: getLanguageLabel(row.locale) })"
|
|
27
27
|
/>
|
|
28
|
-
<span v-else class="dynamic-
|
|
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="
|
|
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 {
|
|
62
|
-
import { getLanguageLabel } from "./
|
|
61
|
+
import { I18nTargetType } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
|
|
62
|
+
import { getLanguageLabel } from "./dynamicI18n";
|
|
63
63
|
|
|
64
|
-
/**
|
|
65
|
-
interface
|
|
64
|
+
/** DynamicI18nCellProps 动态翻译列表单元格属性。 */
|
|
65
|
+
interface DynamicI18nCellProps {
|
|
66
66
|
/** 资源源文。 */
|
|
67
67
|
source: string;
|
|
68
68
|
/** 统一翻译表目标类型。 */
|
|
69
|
-
targetType?:
|
|
69
|
+
targetType?: I18nTargetType;
|
|
70
70
|
/** 目标资源编号。 */
|
|
71
71
|
targetId?: number;
|
|
72
72
|
/** 资源列表返回的非主语言翻译。 */
|
|
73
|
-
|
|
73
|
+
i18ns?: BaseI18n[];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
const props = defineProps<
|
|
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
|
|
83
|
-
const
|
|
82
|
+
const i18nRows = ref<DynamicI18nRow[]>([]);
|
|
83
|
+
const i18nOverrides = ref(new Map<string, string>());
|
|
84
84
|
|
|
85
85
|
const displaySource = computed(() => {
|
|
86
|
-
const override =
|
|
86
|
+
const override = i18nOverrides.value.get(locale.value);
|
|
87
87
|
if (override) return override;
|
|
88
|
-
const
|
|
89
|
-
return
|
|
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
|
-
|
|
96
|
-
|
|
95
|
+
i18nRows.value = [];
|
|
96
|
+
i18nOverrides.value.clear();
|
|
97
97
|
dialogVisible.value = false;
|
|
98
98
|
}
|
|
99
99
|
);
|
|
100
100
|
|
|
101
|
-
/**
|
|
102
|
-
interface
|
|
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
|
|
120
|
+
await loadI18ns();
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
/**
|
|
124
|
-
async function
|
|
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
|
|
132
|
-
|
|
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.
|
|
137
|
-
const
|
|
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
|
|
141
|
-
if (!
|
|
142
|
-
await defBaseI18nService.
|
|
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:
|
|
147
|
+
name: i18n
|
|
148
148
|
});
|
|
149
|
-
return { row, text:
|
|
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
|
-
|
|
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
|
-
/**
|
|
166
|
-
async function
|
|
167
|
-
const records = new Map((props.
|
|
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 =
|
|
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:
|
|
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:
|
|
191
|
+
function cancelEdit(row: DynamicI18nRow) {
|
|
192
192
|
row.text = row.originalText;
|
|
193
193
|
row.editing = false;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
/**
|
|
197
|
-
async function
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
227
|
+
i18nRows.value = [];
|
|
228
228
|
loading.value = false;
|
|
229
229
|
}
|
|
230
230
|
</script>
|
|
231
231
|
|
|
232
232
|
<style scoped>
|
|
233
|
-
.dynamic-
|
|
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-
|
|
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-
|
|
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="
|
|
3
|
-
<div class="
|
|
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.
|
|
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="
|
|
16
|
-
<div class="
|
|
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="
|
|
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.
|
|
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="
|
|
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.
|
|
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
|
|
53
|
+
import { getLanguageLabel, type DynamicI18nValue } from "./dynamicI18n";
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
interface
|
|
57
|
-
modelValue:
|
|
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<
|
|
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:
|
|
72
|
-
const localValues = ref<
|
|
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:
|
|
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.
|
|
99
|
-
const
|
|
100
|
-
const failed = pending.filter(item => !
|
|
101
|
-
if (
|
|
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
|
|
104
|
-
return
|
|
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.
|
|
112
|
-
success:
|
|
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.
|
|
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.
|
|
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.
|
|
133
|
-
const
|
|
134
|
-
if (!
|
|
135
|
-
const values = localValues.value.map(value => (value.locale === locale ? { ...value, text:
|
|
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.
|
|
138
|
+
ElMessage.success(t("system.base.i18n.message.translate_success", { language: getLanguageLabel(locale) }));
|
|
139
139
|
} catch {
|
|
140
|
-
ElMessage.error(t("system.base.
|
|
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
|
-
.
|
|
149
|
+
.i18n-editor {
|
|
150
150
|
display: flex;
|
|
151
151
|
flex-direction: column;
|
|
152
152
|
gap: 14px;
|
|
153
153
|
width: 100%;
|
|
154
154
|
}
|
|
155
|
-
.
|
|
155
|
+
.i18n-editor__toolbar {
|
|
156
156
|
display: flex;
|
|
157
157
|
justify-content: flex-end;
|
|
158
158
|
}
|
|
159
|
-
.
|
|
159
|
+
.i18n-editor__row {
|
|
160
160
|
display: flex;
|
|
161
161
|
flex-direction: column;
|
|
162
162
|
gap: 6px;
|
|
163
163
|
}
|
|
164
|
-
.
|
|
165
|
-
.
|
|
164
|
+
.i18n-editor__heading,
|
|
165
|
+
.i18n-editor__control {
|
|
166
166
|
display: flex;
|
|
167
167
|
gap: 8px;
|
|
168
168
|
align-items: center;
|
|
169
169
|
}
|
|
170
|
-
.
|
|
170
|
+
.i18n-editor__heading {
|
|
171
171
|
color: var(--el-text-color-regular);
|
|
172
172
|
}
|
|
173
|
-
.
|
|
173
|
+
.i18n-editor__control {
|
|
174
174
|
position: relative;
|
|
175
175
|
width: 100%;
|
|
176
176
|
}
|
|
177
|
-
.
|
|
177
|
+
.i18n-editor__control :deep(.el-input) {
|
|
178
178
|
flex: 1;
|
|
179
179
|
}
|
|
180
|
-
.
|
|
180
|
+
.i18n-editor__control--multiline :deep(.el-textarea__inner) {
|
|
181
181
|
padding-right: 78px;
|
|
182
182
|
}
|
|
183
|
-
.
|
|
183
|
+
.i18n-editor__control:not(.i18n-editor__control--multiline) :deep(.el-input__suffix) {
|
|
184
184
|
padding-right: 78px;
|
|
185
185
|
}
|
|
186
|
-
.
|
|
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
|
-
.
|
|
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>
|