@liujitcn/kratos-admin-system 0.0.27 → 0.0.29
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 +1 -1
- package/dist/package/declarations/src/api/system/base_i18n.d.ts +5 -5
- package/dist/package/declarations/src/api/system/runtime_log.d.ts +13 -0
- package/dist/package/declarations/src/api/system/runtime_log_sse.d.ts +8 -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/dynamicI18n.d.ts +38 -0
- package/dist/package/declarations/src/locales/generated.d.ts +168 -52
- 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_dict.d.ts +4 -4
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +23 -23
- package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +2 -2
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +1 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/ops_monitoring.d.ts +4 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/project_document.d.ts +4 -6
- package/dist/package/declarations/src/rpc/system/admin/v1/runtime_log.d.ts +96 -0
- package/dist/package/declarations/src/views/tool/runtime-log/index.vue.d.ts +3 -0
- package/dist/package/src/api/system/base_i18n.ts +12 -12
- package/dist/package/src/api/system/runtime_log.ts +80 -0
- package/dist/package/src/api/system/runtime_log_sse.ts +40 -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/{dynamicTranslation.ts → dynamicI18n.ts} +15 -15
- package/dist/package/src/locales/README.md +1 -1
- package/dist/package/src/locales/en-US.json +46 -17
- package/dist/package/src/locales/ja-JP.json +44 -15
- package/dist/package/src/locales/zh-CN.json +44 -15
- package/dist/package/src/locales/zh-TW.json +44 -15
- package/dist/package/src/rpc/system/admin/v1/base_config.ts +3 -3
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +4 -4
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +23 -23
- package/dist/package/src/rpc/system/admin/v1/base_menu.ts +2 -2
- package/dist/package/src/rpc/system/admin/v1/code_gen.ts +1 -1
- package/dist/package/src/rpc/system/admin/v1/ops_monitoring.ts +4 -0
- package/dist/package/src/rpc/system/admin/v1/project_document.ts +4 -7
- package/dist/package/src/rpc/system/admin/v1/runtime_log.ts +113 -0
- package/dist/package/src/views/base/config/index.vue +38 -38
- 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/menu/index.vue +23 -23
- package/dist/package/src/views/tool/api-doc/index.vue +14 -6
- package/dist/package/src/views/tool/code-gen/code-preview/index.vue +23 -6
- package/dist/package/src/views/tool/code-gen/columns/index.vue +1 -1
- package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +96 -14
- package/dist/package/src/views/tool/code-gen/table/index.vue +33 -1
- package/dist/package/src/views/tool/ops-monitoring/index.vue +130 -24
- package/dist/package/src/views/tool/project-doc/index.vue +9 -4
- package/dist/package/src/views/tool/runtime-log/index.vue +883 -0
- package/package.json +2 -2
- 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
|
@@ -50,7 +50,7 @@ export interface BaseMenuForm {
|
|
|
50
50
|
/** 分配的API列表 */
|
|
51
51
|
api: string[];
|
|
52
52
|
/** 非主语言翻译 */
|
|
53
|
-
|
|
53
|
+
i18ns: BaseI18n[];
|
|
54
54
|
/** 排序 */
|
|
55
55
|
sort: number;
|
|
56
56
|
/** 状态 */
|
|
@@ -109,7 +109,7 @@ export interface BaseMenu {
|
|
|
109
109
|
/** 是否存在子节点 */
|
|
110
110
|
has_children: boolean;
|
|
111
111
|
/** 非主语言翻译 */
|
|
112
|
-
|
|
112
|
+
i18ns: BaseI18n[];
|
|
113
113
|
}
|
|
114
114
|
/** 菜单元信息 */
|
|
115
115
|
export interface BaseMenuMeta {
|
|
@@ -67,7 +67,7 @@ export interface PreviewCodeGenResponse {
|
|
|
67
67
|
/** 本次生效输出路径 */
|
|
68
68
|
output_paths: CodeGenOutputPaths | undefined;
|
|
69
69
|
/** 正式生成前必须补齐的翻译配置 */
|
|
70
|
-
|
|
70
|
+
missing_i18ns: string[];
|
|
71
71
|
}
|
|
72
72
|
/** 启动代码生成任务条件 */
|
|
73
73
|
export interface StartCodeGenTaskRequest {
|
|
@@ -17,6 +17,8 @@ export interface ProjectDocumentListItem {
|
|
|
17
17
|
id: string;
|
|
18
18
|
/** 项目内相对路径 */
|
|
19
19
|
path: string;
|
|
20
|
+
/** 当前语言下的文档显示文件名 */
|
|
21
|
+
name: string;
|
|
20
22
|
/** Markdown文件更新时间,RFC3339格式 */
|
|
21
23
|
updated_at: string;
|
|
22
24
|
}
|
|
@@ -52,17 +54,13 @@ export interface ProjectDocument {
|
|
|
52
54
|
project_name: string;
|
|
53
55
|
/** 项目内相对路径 */
|
|
54
56
|
path: string;
|
|
57
|
+
/** 当前语言下的文档显示文件名 */
|
|
58
|
+
name: string;
|
|
55
59
|
/** Markdown文档内容 */
|
|
56
60
|
content: string;
|
|
57
|
-
/** 按语言标识索引的Markdown翻译内容 */
|
|
58
|
-
locale: Map<string, string>;
|
|
59
61
|
/** Markdown文件更新时间,RFC3339格式 */
|
|
60
62
|
updated_at: string;
|
|
61
63
|
}
|
|
62
|
-
export interface ProjectDocument_LocaleEntry {
|
|
63
|
-
key: string;
|
|
64
|
-
value: string;
|
|
65
|
-
}
|
|
66
64
|
/** 项目文档服务 */
|
|
67
65
|
export interface ProjectDocumentService {
|
|
68
66
|
/** 查询项目文档树 */
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** 历史日志文件列表查询条件。 */
|
|
2
|
+
export interface ListRuntimeLogFilesRequest {
|
|
3
|
+
}
|
|
4
|
+
/** 历史日志文件列表响应。 */
|
|
5
|
+
export interface ListRuntimeLogFilesResponse {
|
|
6
|
+
/** 历史日志文件列表 */
|
|
7
|
+
files: RuntimeLogFile[];
|
|
8
|
+
}
|
|
9
|
+
/** 历史日志文件信息。 */
|
|
10
|
+
export interface RuntimeLogFile {
|
|
11
|
+
/** 服务端签发的日志文件标识 */
|
|
12
|
+
file_id: string;
|
|
13
|
+
/** 日志文件名称 */
|
|
14
|
+
name: string;
|
|
15
|
+
/** 日志文件大小,单位字节 */
|
|
16
|
+
size_bytes: number;
|
|
17
|
+
/** 最后修改时间,RFC3339格式 */
|
|
18
|
+
modified_at: string;
|
|
19
|
+
/** 是否为Gzip压缩文件 */
|
|
20
|
+
is_compressed: boolean;
|
|
21
|
+
/** 是否为当前活动日志文件 */
|
|
22
|
+
is_active: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** 历史日志文件读取条件。 */
|
|
25
|
+
export interface ReadRuntimeLogFileRequest {
|
|
26
|
+
/** 服务端签发的日志文件标识 */
|
|
27
|
+
file_id: string;
|
|
28
|
+
/** 向更早日志翻页的不透明游标,留空时从文件尾部读取 */
|
|
29
|
+
cursor: string;
|
|
30
|
+
/** 单次读取条数,0使用默认值,最大500 */
|
|
31
|
+
limit: number;
|
|
32
|
+
/** 日志内容搜索关键字 */
|
|
33
|
+
keyword: string;
|
|
34
|
+
/** 日志级别筛选列表 */
|
|
35
|
+
levels: string[];
|
|
36
|
+
}
|
|
37
|
+
/** 历史日志文件读取响应。 */
|
|
38
|
+
export interface ReadRuntimeLogFileResponse {
|
|
39
|
+
/** 按时间正序排列的日志条目 */
|
|
40
|
+
entries: RuntimeLogEntry[];
|
|
41
|
+
/** 继续读取更早日志的不透明游标 */
|
|
42
|
+
next_cursor: string;
|
|
43
|
+
/** 是否还有更早日志 */
|
|
44
|
+
has_more: boolean;
|
|
45
|
+
/** 读取期间日志文件是否已滚动或截断 */
|
|
46
|
+
file_changed: boolean;
|
|
47
|
+
}
|
|
48
|
+
/** 实时或历史日志条目。 */
|
|
49
|
+
export interface RuntimeLogEntry {
|
|
50
|
+
/** 当前进程内的日志序号,历史文件日志为空 */
|
|
51
|
+
sequence: string;
|
|
52
|
+
/** 日志时间 */
|
|
53
|
+
timestamp: string;
|
|
54
|
+
/** 日志级别 */
|
|
55
|
+
level: string;
|
|
56
|
+
/** 日志来源 */
|
|
57
|
+
source: string;
|
|
58
|
+
/** 日志消息 */
|
|
59
|
+
message: string;
|
|
60
|
+
/** 可直接展示的纯文本日志行 */
|
|
61
|
+
line: string;
|
|
62
|
+
/** 日志行是否因长度限制被截断 */
|
|
63
|
+
is_truncated: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** 实时控制台开启条件。 */
|
|
66
|
+
export interface OpenRuntimeConsoleRequest {
|
|
67
|
+
/** 首次返回的最近日志条数,0使用默认值,最大500 */
|
|
68
|
+
backlog_limit: number;
|
|
69
|
+
}
|
|
70
|
+
/** 实时控制台开启响应。 */
|
|
71
|
+
export interface OpenRuntimeConsoleResponse {
|
|
72
|
+
/** SSE业务流标识 */
|
|
73
|
+
stream: string;
|
|
74
|
+
/** 当前用户专属SSE频道标识 */
|
|
75
|
+
channel_id: string;
|
|
76
|
+
/** 频道过期时间,RFC3339格式 */
|
|
77
|
+
expires_at: string;
|
|
78
|
+
/** 当前服务实例标识 */
|
|
79
|
+
instance_id: string;
|
|
80
|
+
/** 当前进程最新日志序号 */
|
|
81
|
+
latest_sequence: string;
|
|
82
|
+
/** 当前进程最近日志 */
|
|
83
|
+
entries: RuntimeLogEntry[];
|
|
84
|
+
}
|
|
85
|
+
/** 实时控制台丢失消息提示。 */
|
|
86
|
+
export interface RuntimeLogGap {
|
|
87
|
+
/** 本次丢弃的日志条数 */
|
|
88
|
+
dropped_count: number;
|
|
89
|
+
/** 发生丢弃时的最新日志序号 */
|
|
90
|
+
latest_sequence: string;
|
|
91
|
+
}
|
|
92
|
+
/** 历史日志文件下载条件。 */
|
|
93
|
+
export interface DownloadRuntimeLogFileRequest {
|
|
94
|
+
/** 服务端签发的日志文件标识 */
|
|
95
|
+
file_id: string;
|
|
96
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -2,28 +2,28 @@ import service from "@liujitcn/kratos-admin-core/request";
|
|
|
2
2
|
import type { Empty } from "@liujitcn/kratos-admin-system/rpc/google/protobuf/empty";
|
|
3
3
|
import type {
|
|
4
4
|
BaseI18nService,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
DraftBaseI18nRequest,
|
|
6
|
+
DraftBaseI18nResponse,
|
|
7
|
+
UpdateBaseI18nRequest
|
|
8
8
|
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const BASE_I18N_URL = "/v1/admin/base/i18n";
|
|
11
11
|
|
|
12
12
|
/** BaseI18nServiceImpl Admin 单条动态翻译服务。 */
|
|
13
13
|
export class BaseI18nServiceImpl implements BaseI18nService {
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
return service<
|
|
17
|
-
url: `${
|
|
14
|
+
/** DraftBaseI18n 翻译请求中的单个文本。 */
|
|
15
|
+
DraftBaseI18n(request: DraftBaseI18nRequest): Promise<DraftBaseI18nResponse> {
|
|
16
|
+
return service<DraftBaseI18nRequest, DraftBaseI18nResponse>({
|
|
17
|
+
url: `${BASE_I18N_URL}/draft`,
|
|
18
18
|
method: "post",
|
|
19
19
|
data: request
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
return service<
|
|
26
|
-
url:
|
|
23
|
+
/** UpdateBaseI18n 修改或新增单条翻译信息。 */
|
|
24
|
+
UpdateBaseI18n(request: UpdateBaseI18nRequest): Promise<Empty> {
|
|
25
|
+
return service<UpdateBaseI18nRequest, Empty>({
|
|
26
|
+
url: BASE_I18N_URL,
|
|
27
27
|
method: "put",
|
|
28
28
|
data: request
|
|
29
29
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import service from "@liujitcn/kratos-admin-core/request";
|
|
2
|
+
import type {
|
|
3
|
+
DownloadRuntimeLogFileRequest,
|
|
4
|
+
ListRuntimeLogFilesRequest,
|
|
5
|
+
ListRuntimeLogFilesResponse,
|
|
6
|
+
OpenRuntimeConsoleRequest,
|
|
7
|
+
OpenRuntimeConsoleResponse,
|
|
8
|
+
ReadRuntimeLogFileRequest,
|
|
9
|
+
ReadRuntimeLogFileResponse
|
|
10
|
+
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/runtime_log";
|
|
11
|
+
|
|
12
|
+
const RUNTIME_LOG_URL = "/v1/admin/runtime-log";
|
|
13
|
+
|
|
14
|
+
/** Admin运行日志服务。 */
|
|
15
|
+
export class RuntimeLogServiceImpl {
|
|
16
|
+
/** 查询历史日志文件列表。 */
|
|
17
|
+
ListRuntimeLogFiles(request: ListRuntimeLogFilesRequest): Promise<ListRuntimeLogFilesResponse> {
|
|
18
|
+
return service<ListRuntimeLogFilesRequest, ListRuntimeLogFilesResponse>({
|
|
19
|
+
url: `${RUNTIME_LOG_URL}/files`,
|
|
20
|
+
method: "get",
|
|
21
|
+
params: request
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 分页读取历史日志文件内容。 */
|
|
26
|
+
ReadRuntimeLogFile(request: ReadRuntimeLogFileRequest): Promise<ReadRuntimeLogFileResponse> {
|
|
27
|
+
return service<ReadRuntimeLogFileRequest, ReadRuntimeLogFileResponse>({
|
|
28
|
+
url: `${RUNTIME_LOG_URL}/files/${encodeURIComponent(request.file_id)}`,
|
|
29
|
+
method: "get",
|
|
30
|
+
params: {
|
|
31
|
+
cursor: request.cursor,
|
|
32
|
+
limit: request.limit,
|
|
33
|
+
keyword: request.keyword,
|
|
34
|
+
levels: request.levels
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 创建当前用户的实时控制台频道。 */
|
|
40
|
+
OpenRuntimeConsole(request: OpenRuntimeConsoleRequest): Promise<OpenRuntimeConsoleResponse> {
|
|
41
|
+
return service<OpenRuntimeConsoleRequest, OpenRuntimeConsoleResponse>({
|
|
42
|
+
url: `${RUNTIME_LOG_URL}/console`,
|
|
43
|
+
method: "post",
|
|
44
|
+
data: request
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 下载历史日志原文件。 */
|
|
49
|
+
async DownloadRuntimeLogFile(request: DownloadRuntimeLogFileRequest, fallbackName: string): Promise<void> {
|
|
50
|
+
const response = await service<DownloadRuntimeLogFileRequest, any>({
|
|
51
|
+
url: `${RUNTIME_LOG_URL}/files/${encodeURIComponent(request.file_id)}/download`,
|
|
52
|
+
method: "get",
|
|
53
|
+
responseType: "blob"
|
|
54
|
+
});
|
|
55
|
+
const contentDisposition = String(response.headers?.["content-disposition"] ?? "");
|
|
56
|
+
const encodedName = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i)?.[1];
|
|
57
|
+
const plainName = contentDisposition.match(/filename="?([^";]+)"?/i)?.[1];
|
|
58
|
+
let filename = fallbackName;
|
|
59
|
+
if (encodedName) {
|
|
60
|
+
try {
|
|
61
|
+
filename = decodeURIComponent(encodedName);
|
|
62
|
+
} catch {
|
|
63
|
+
filename = fallbackName;
|
|
64
|
+
}
|
|
65
|
+
} else if (plainName) {
|
|
66
|
+
filename = plainName;
|
|
67
|
+
}
|
|
68
|
+
const blob = response.data instanceof Blob ? response.data : new Blob([response.data]);
|
|
69
|
+
const objectURL = window.URL.createObjectURL(blob);
|
|
70
|
+
const anchor = document.createElement("a");
|
|
71
|
+
anchor.href = objectURL;
|
|
72
|
+
anchor.download = filename;
|
|
73
|
+
document.body.appendChild(anchor);
|
|
74
|
+
anchor.click();
|
|
75
|
+
anchor.remove();
|
|
76
|
+
window.URL.revokeObjectURL(objectURL);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const defRuntimeLogService = new RuntimeLogServiceImpl();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { subscribeSseEvent, type SseStop } from "../base/sse";
|
|
2
|
+
import type {
|
|
3
|
+
RuntimeLogEntry,
|
|
4
|
+
RuntimeLogGap
|
|
5
|
+
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/runtime_log";
|
|
6
|
+
|
|
7
|
+
const RUNTIME_LOG_SSE_STREAM = "system.admin.runtime-console";
|
|
8
|
+
const RUNTIME_LOG_SSE_ENTRY = "runtime.log";
|
|
9
|
+
const RUNTIME_LOG_SSE_GAP = "runtime.gap";
|
|
10
|
+
|
|
11
|
+
/** 实时日志事件处理函数。 */
|
|
12
|
+
export type RuntimeLogEntryHandler = (entry: RuntimeLogEntry) => void;
|
|
13
|
+
|
|
14
|
+
/** 实时日志丢失事件处理函数。 */
|
|
15
|
+
export type RuntimeLogGapHandler = (gap: RuntimeLogGap) => void;
|
|
16
|
+
|
|
17
|
+
/** 订阅指定用户频道的实时日志和丢失提示事件。 */
|
|
18
|
+
export function subscribeRuntimeLog(
|
|
19
|
+
channelId: string,
|
|
20
|
+
entryHandler: RuntimeLogEntryHandler,
|
|
21
|
+
gapHandler: RuntimeLogGapHandler
|
|
22
|
+
): SseStop {
|
|
23
|
+
const request = { stream: RUNTIME_LOG_SSE_STREAM, channel_id: channelId };
|
|
24
|
+
const stopEntry = subscribeSseEvent(request, RUNTIME_LOG_SSE_ENTRY, parsePayload<RuntimeLogEntry>, entryHandler);
|
|
25
|
+
const stopGap = subscribeSseEvent(request, RUNTIME_LOG_SSE_GAP, parsePayload<RuntimeLogGap>, gapHandler);
|
|
26
|
+
return () => {
|
|
27
|
+
stopGap();
|
|
28
|
+
stopEntry();
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 解析实时日志 SSE JSON 负载。 */
|
|
33
|
+
function parsePayload<T>(raw: string): T | null {
|
|
34
|
+
if (!raw) return null;
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(raw) as T;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -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
|
}
|