@liujitcn/kratos-admin-system 0.0.41 → 0.0.42
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 +2 -0
- package/dist/package/declarations/src/config/types.d.ts +4 -0
- package/dist/package/declarations/src/locales/generated.d.ts +8 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_migration.d.ts +9 -6
- package/dist/package/src/config/types.ts +4 -0
- package/dist/package/src/locales/en-US.json +2 -0
- package/dist/package/src/locales/ja-JP.json +2 -0
- package/dist/package/src/locales/zh-CN.json +2 -0
- package/dist/package/src/locales/zh-TW.json +2 -0
- package/dist/package/src/rpc/system/admin/v1/base_migration.ts +10 -6
- package/dist/package/src/views/base/config/index.vue +137 -86
- package/dist/package/src/views/base/migration/index.vue +90 -204
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -186,3 +186,5 @@ registerRuntimeConfig(notificationConfig);
|
|
|
186
186
|
|
|
187
187
|
国际化编辑弹窗使用 Element Plus 横向表单,语言标签在左侧统一占 100px,输入框及翻译操作位于右侧;单行和多行编辑均保持左右布局。
|
|
188
188
|
代码生成进度弹窗统一补齐 HTTP 和 SSE 快照中被 ProtoJSON 省略的零值计数,任务及表级进度在生成初期显示 0,避免出现 NaN。
|
|
189
|
+
|
|
190
|
+
数据库迁移记录的模块、数据源、版本和时间仅在左侧列表展示,右侧直接展示文件 Tab。详情按文件展示:说明、升级和回退文件各占一个 Tab,标签显示原始文件名,面板显示路径;Markdown 独立渲染,SQL 独立查看和复制,内容区域填满 Tab 下方的剩余高度并在内部滚动。切换记录后选中第一个文件,仅渲染当前 Tab 的正文。
|
|
@@ -26,6 +26,10 @@ export interface RuntimeConfigField {
|
|
|
26
26
|
labelKey: string;
|
|
27
27
|
/** ProForm 控件类型。 */
|
|
28
28
|
component: ProFormComponentType;
|
|
29
|
+
/** 栅格占位列数,默认双列;长路径等字段可设为 24 独占整行。 */
|
|
30
|
+
colSpan?: ProFormField["colSpan"];
|
|
31
|
+
/** 是否在当前字段前换行。 */
|
|
32
|
+
rowBreakBefore?: ProFormField["rowBreakBefore"];
|
|
29
33
|
/** 控件参数。 */
|
|
30
34
|
props?: ProFormField["props"];
|
|
31
35
|
/** 表单项参数。 */
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare const LOCALE_MESSAGES: {
|
|
2
2
|
readonly 'zh-CN': {
|
|
3
|
+
"system.base.config.tab.basic": string;
|
|
4
|
+
"system.base.config.tab.form": string;
|
|
3
5
|
"system.ai.chat.action.collapse_sessions": string;
|
|
4
6
|
"system.ai.chat.action.confirm_delete_conversation": string;
|
|
5
7
|
"system.ai.chat.action.continue": string;
|
|
@@ -1521,6 +1523,8 @@ export declare const LOCALE_MESSAGES: {
|
|
|
1521
1523
|
"system.sse.error.connection_failed": string;
|
|
1522
1524
|
};
|
|
1523
1525
|
readonly 'en-US': {
|
|
1526
|
+
"system.base.config.tab.basic": string;
|
|
1527
|
+
"system.base.config.tab.form": string;
|
|
1524
1528
|
"system.ai.chat.action.collapse_sessions": string;
|
|
1525
1529
|
"system.ai.chat.action.confirm_delete_conversation": string;
|
|
1526
1530
|
"system.ai.chat.action.continue": string;
|
|
@@ -3042,6 +3046,8 @@ export declare const LOCALE_MESSAGES: {
|
|
|
3042
3046
|
"system.sse.error.connection_failed": string;
|
|
3043
3047
|
};
|
|
3044
3048
|
readonly 'ja-JP': {
|
|
3049
|
+
"system.base.config.tab.basic": string;
|
|
3050
|
+
"system.base.config.tab.form": string;
|
|
3045
3051
|
"system.ai.chat.action.collapse_sessions": string;
|
|
3046
3052
|
"system.ai.chat.action.confirm_delete_conversation": string;
|
|
3047
3053
|
"system.ai.chat.action.continue": string;
|
|
@@ -4563,6 +4569,8 @@ export declare const LOCALE_MESSAGES: {
|
|
|
4563
4569
|
"system.sse.error.connection_failed": string;
|
|
4564
4570
|
};
|
|
4565
4571
|
readonly 'zh-TW': {
|
|
4572
|
+
"system.base.config.tab.basic": string;
|
|
4573
|
+
"system.base.config.tab.form": string;
|
|
4566
4574
|
"system.ai.chat.action.collapse_sessions": string;
|
|
4567
4575
|
"system.ai.chat.action.confirm_delete_conversation": string;
|
|
4568
4576
|
"system.ai.chat.action.continue": string;
|
|
@@ -46,15 +46,18 @@ export interface BaseMigration {
|
|
|
46
46
|
data_source: string;
|
|
47
47
|
/** 迁移版本 */
|
|
48
48
|
version: string;
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
/** 回退脚本 */
|
|
52
|
-
down_sql: string;
|
|
53
|
-
/** 升级描述 */
|
|
54
|
-
description: string;
|
|
49
|
+
/** 迁移文件列表 */
|
|
50
|
+
files: BaseMigrationFile[];
|
|
55
51
|
/** 创建时间 */
|
|
56
52
|
created_at: string;
|
|
57
53
|
}
|
|
54
|
+
/** 数据库迁移文件内容 */
|
|
55
|
+
export interface BaseMigrationFile {
|
|
56
|
+
/** 文件相对路径 */
|
|
57
|
+
path: string;
|
|
58
|
+
/** 文件内容 */
|
|
59
|
+
content: string;
|
|
60
|
+
}
|
|
58
61
|
/** Admin数据库迁移服务。 */
|
|
59
62
|
export interface BaseMigrationService {
|
|
60
63
|
/** 分页查询数据库升级历史 */
|
|
@@ -28,6 +28,10 @@ export interface RuntimeConfigField {
|
|
|
28
28
|
labelKey: string;
|
|
29
29
|
/** ProForm 控件类型。 */
|
|
30
30
|
component: ProFormComponentType;
|
|
31
|
+
/** 栅格占位列数,默认双列;长路径等字段可设为 24 独占整行。 */
|
|
32
|
+
colSpan?: ProFormField["colSpan"];
|
|
33
|
+
/** 是否在当前字段前换行。 */
|
|
34
|
+
rowBreakBefore?: ProFormField["rowBreakBefore"];
|
|
31
35
|
/** 控件参数。 */
|
|
32
36
|
props?: ProFormField["props"];
|
|
33
37
|
/** 表单项参数。 */
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
+
"system.base.config.tab.basic": "Basic information",
|
|
3
|
+
"system.base.config.tab.form": "Configuration form",
|
|
2
4
|
"system.ai.chat.action.collapse_sessions": "Collapse conversations",
|
|
3
5
|
"system.ai.chat.action.confirm_delete_conversation": "Delete",
|
|
4
6
|
"system.ai.chat.action.continue": "Continue",
|
|
@@ -62,16 +62,20 @@ export interface BaseMigration {
|
|
|
62
62
|
data_source: string;
|
|
63
63
|
/** 迁移版本 */
|
|
64
64
|
version: string;
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
/** 回退脚本 */
|
|
68
|
-
down_sql: string;
|
|
69
|
-
/** 升级描述 */
|
|
70
|
-
description: string;
|
|
65
|
+
/** 迁移文件列表 */
|
|
66
|
+
files: BaseMigrationFile[];
|
|
71
67
|
/** 创建时间 */
|
|
72
68
|
created_at: string;
|
|
73
69
|
}
|
|
74
70
|
|
|
71
|
+
/** 数据库迁移文件内容 */
|
|
72
|
+
export interface BaseMigrationFile {
|
|
73
|
+
/** 文件相对路径 */
|
|
74
|
+
path: string;
|
|
75
|
+
/** 文件内容 */
|
|
76
|
+
content: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
/** Admin数据库迁移服务。 */
|
|
76
80
|
export interface BaseMigrationService {
|
|
77
81
|
/** 分页查询数据库升级历史 */
|
|
@@ -9,95 +9,113 @@
|
|
|
9
9
|
:request-api="requestBaseConfigTable"
|
|
10
10
|
/>
|
|
11
11
|
|
|
12
|
-
<
|
|
12
|
+
<ProDialog
|
|
13
13
|
v-model="dialog.visible"
|
|
14
|
-
ref="formDialogRef"
|
|
15
14
|
class="system-config-dialog"
|
|
16
15
|
:title="t(dialog.titleKey, { resource: t('system.base.config.resource') })"
|
|
17
|
-
width="min(
|
|
16
|
+
width="min(1040px, calc(100vw - 32px))"
|
|
18
17
|
top="4vh"
|
|
19
|
-
:col-span="12"
|
|
20
|
-
:gutter="24"
|
|
21
18
|
:confirm-loading="saving"
|
|
22
|
-
:model="formData"
|
|
23
|
-
:fields="formFields"
|
|
24
|
-
:rules="rules"
|
|
25
19
|
@confirm="handleSubmit"
|
|
26
20
|
@close="handleCloseDialog"
|
|
27
21
|
>
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
</
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
22
|
+
<el-tabs
|
|
23
|
+
v-model="activeTab"
|
|
24
|
+
class="config-editor-tabs"
|
|
25
|
+
:class="{ 'config-editor-tabs--plain': formData.type !== BaseConfigType.BASE_CONFIG_TYPE_FORM }"
|
|
26
|
+
>
|
|
27
|
+
<el-tab-pane :label="t('system.base.config.tab.basic')" name="basic">
|
|
28
|
+
<ProForm
|
|
29
|
+
ref="basicFormRef"
|
|
30
|
+
:model="formData"
|
|
31
|
+
:fields="formFields"
|
|
32
|
+
:rules="rules"
|
|
33
|
+
:col-span="12"
|
|
34
|
+
:gutter="24"
|
|
35
|
+
label-position="top"
|
|
36
|
+
scroll-to-error
|
|
37
|
+
>
|
|
38
|
+
<template #textValue>
|
|
39
|
+
<el-input
|
|
40
|
+
v-model="formData.value"
|
|
41
|
+
:placeholder="t('common.validation.required_input', { field: t('system.base.config.field.value') })"
|
|
42
|
+
/>
|
|
43
|
+
</template>
|
|
44
|
+
<template #imageValue>
|
|
45
|
+
<UploadImg v-model:image-url="formData.value" upload-type="config" />
|
|
46
|
+
</template>
|
|
47
|
+
<template #richTextValue>
|
|
48
|
+
<WangEditor v-model:value="formData.value" upload-type="config" />
|
|
49
|
+
</template>
|
|
50
|
+
<template #dictValue>
|
|
51
|
+
<Dict
|
|
52
|
+
v-if="dictValueCode"
|
|
53
|
+
v-model="formData.value"
|
|
54
|
+
:code="dictValueCode"
|
|
55
|
+
code-type="string"
|
|
56
|
+
:placeholder="t('common.validation.required_select', { field: t('system.base.config.field.value') })"
|
|
57
|
+
/>
|
|
58
|
+
<el-input
|
|
59
|
+
v-else
|
|
60
|
+
v-model="formData.value"
|
|
61
|
+
:placeholder="t('common.validation.required_input', { field: t('system.base.config.field.value') })"
|
|
62
|
+
/>
|
|
63
|
+
</template>
|
|
64
|
+
<template #booleanValue>
|
|
65
|
+
<el-switch
|
|
66
|
+
v-model="formData.value"
|
|
67
|
+
active-value="true"
|
|
68
|
+
inactive-value="false"
|
|
69
|
+
active-text="true"
|
|
70
|
+
inactive-text="false"
|
|
71
|
+
inline-prompt
|
|
72
|
+
/>
|
|
73
|
+
</template>
|
|
74
|
+
<template #nameI18ns>
|
|
75
|
+
<DynamicI18nEditor v-model="nameI18nValues" :source="formData.name" :maxlength="100" />
|
|
76
|
+
</template>
|
|
77
|
+
<template #valueI18ns>
|
|
78
|
+
<DynamicI18nEditor v-model="valueI18nValues" :source="formData.value" :maxlength="10000" multiline />
|
|
79
|
+
</template>
|
|
80
|
+
</ProForm>
|
|
81
|
+
</el-tab-pane>
|
|
82
|
+
<el-tab-pane
|
|
83
|
+
v-if="formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM"
|
|
84
|
+
:label="t('system.base.config.tab.form')"
|
|
85
|
+
name="form"
|
|
86
|
+
>
|
|
87
|
+
<div class="config-form-value">
|
|
88
|
+
<template v-if="formDefinition">
|
|
89
|
+
<p>
|
|
90
|
+
<el-icon><component :is="formDefinition.icon" /></el-icon> {{ t(formDefinition.descriptionKey) }}
|
|
91
|
+
</p>
|
|
92
|
+
<ProForm
|
|
93
|
+
:key="formDefinition.key"
|
|
94
|
+
ref="valueFormRef"
|
|
95
|
+
:model="formValue"
|
|
96
|
+
:fields="localizedFormFields"
|
|
97
|
+
:col-span="12"
|
|
98
|
+
:gutter="24"
|
|
99
|
+
label-position="top"
|
|
100
|
+
scroll-to-error
|
|
101
|
+
/>
|
|
102
|
+
</template>
|
|
103
|
+
<el-alert v-else :title="t('system.base.config.message.form_unavailable')" type="warning" :closable="false" />
|
|
104
|
+
</div>
|
|
105
|
+
</el-tab-pane>
|
|
106
|
+
</el-tabs>
|
|
107
|
+
</ProDialog>
|
|
90
108
|
</div>
|
|
91
109
|
</template>
|
|
92
110
|
|
|
93
111
|
<script setup lang="ts">
|
|
94
|
-
import { computed, h, reactive, ref, watch } from "vue";
|
|
112
|
+
import { computed, h, nextTick, reactive, ref, watch } from "vue";
|
|
95
113
|
import { useDebounceFn } from "@vueuse/core";
|
|
96
114
|
import type { ColumnProps, HeaderActionProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
97
115
|
import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
|
|
98
116
|
import ProForm from "@liujitcn/kratos-admin-core/components/ProForm/index.vue";
|
|
99
117
|
import { runtimeConfigDefinitions, type RuntimeConfigModel } from "@liujitcn/kratos-admin-system/config";
|
|
100
|
-
import
|
|
118
|
+
import ProDialog from "@liujitcn/kratos-admin-core/components/Dialog/ProDialog.vue";
|
|
101
119
|
import type { ProFormField, ProFormOption, ProFormInstance } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
102
120
|
import DictLabel from "@liujitcn/kratos-admin-core/components/Dict/DictLabel.vue";
|
|
103
121
|
import RichTextPreview from "@liujitcn/kratos-admin-core/components/RichTextPreview/index.vue";
|
|
@@ -141,7 +159,8 @@ defineOptions({
|
|
|
141
159
|
|
|
142
160
|
const { BUTTONS } = useAuthButtons();
|
|
143
161
|
const proTable = ref<ProTableInstance>();
|
|
144
|
-
const
|
|
162
|
+
const basicFormRef = ref<ProFormInstance>();
|
|
163
|
+
const activeTab = ref("basic");
|
|
145
164
|
|
|
146
165
|
const saving = ref(false);
|
|
147
166
|
const valueFormRef = ref<ProFormInstance>();
|
|
@@ -155,6 +174,8 @@ const localizedFormFields = computed<ProFormField[]>(() =>
|
|
|
155
174
|
props: field.props,
|
|
156
175
|
itemProps: field.itemProps,
|
|
157
176
|
options: field.options,
|
|
177
|
+
colSpan: field.colSpan,
|
|
178
|
+
rowBreakBefore: field.rowBreakBefore,
|
|
158
179
|
visible: field.visible,
|
|
159
180
|
labelTooltip: field.labelTooltipKey ? t(field.labelTooltipKey) : undefined,
|
|
160
181
|
rules: field.rules?.map(rule => ({
|
|
@@ -200,6 +221,15 @@ const formData = reactive<BaseConfigFormState>({
|
|
|
200
221
|
value_i18ns: []
|
|
201
222
|
});
|
|
202
223
|
|
|
224
|
+
/** 配置类型切换后回到基础信息,避免停留在已移除的表单页签。 */
|
|
225
|
+
watch(
|
|
226
|
+
() => formData.type,
|
|
227
|
+
() => {
|
|
228
|
+
activeTab.value = "basic";
|
|
229
|
+
},
|
|
230
|
+
{ flush: "sync" }
|
|
231
|
+
);
|
|
232
|
+
|
|
203
233
|
/** 切换表单类型或编码时加载对应默认内容。 */
|
|
204
234
|
watch(
|
|
205
235
|
() => [formData.type, formData.key],
|
|
@@ -357,14 +387,6 @@ const formFields = computed<ProFormField[]>(() => [
|
|
|
357
387
|
component: "dict",
|
|
358
388
|
props: { code: "base_config_type", disabled: formData.id > 0 }
|
|
359
389
|
},
|
|
360
|
-
{
|
|
361
|
-
prop: "formValue",
|
|
362
|
-
label: t("system.base.config.field.value"),
|
|
363
|
-
component: "slot",
|
|
364
|
-
slotName: "formValue",
|
|
365
|
-
visible: model => model.type === BaseConfigType.BASE_CONFIG_TYPE_FORM,
|
|
366
|
-
colSpan: 24
|
|
367
|
-
},
|
|
368
390
|
{
|
|
369
391
|
prop: "value",
|
|
370
392
|
label: t("system.base.config.field.value"),
|
|
@@ -617,6 +639,7 @@ async function handleOpenDialog(configId?: number) {
|
|
|
617
639
|
loadFormValue(formData.value);
|
|
618
640
|
}
|
|
619
641
|
}
|
|
642
|
+
activeTab.value = configId && formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM ? "form" : "basic";
|
|
620
643
|
dialog.visible = true;
|
|
621
644
|
}
|
|
622
645
|
|
|
@@ -642,8 +665,9 @@ function handleCloseDialog() {
|
|
|
642
665
|
* 重置系统配置表单,避免新增时保留旧值。
|
|
643
666
|
*/
|
|
644
667
|
function resetForm() {
|
|
645
|
-
|
|
646
|
-
|
|
668
|
+
basicFormRef.value?.resetFields();
|
|
669
|
+
basicFormRef.value?.clearValidate();
|
|
670
|
+
activeTab.value = "basic";
|
|
647
671
|
formData.id = 0;
|
|
648
672
|
formData.site = undefined;
|
|
649
673
|
formData.name = "";
|
|
@@ -661,17 +685,22 @@ function resetForm() {
|
|
|
661
685
|
* 提交系统配置表单。
|
|
662
686
|
*/
|
|
663
687
|
async function handleSubmit() {
|
|
688
|
+
if (formData.type === BaseConfigType.BASE_CONFIG_TYPE_BOOLEAN) {
|
|
689
|
+
formData.value = formData.value === "true" ? "true" : "false";
|
|
690
|
+
}
|
|
691
|
+
activeTab.value = "basic";
|
|
692
|
+
await nextTick();
|
|
693
|
+
if ((await basicFormRef.value?.validate()) !== true) return;
|
|
664
694
|
if (formData.type === BaseConfigType.BASE_CONFIG_TYPE_FORM) {
|
|
695
|
+
activeTab.value = "form";
|
|
696
|
+
await nextTick();
|
|
665
697
|
if (!formDefinition.value) {
|
|
666
698
|
ElMessage.warning(t("system.base.config.message.form_unavailable"));
|
|
667
699
|
return;
|
|
668
700
|
}
|
|
669
701
|
if ((await valueFormRef.value?.validate()) !== true) return;
|
|
670
702
|
formData.value = JSON.stringify(formValue.value);
|
|
671
|
-
} else if (formData.type === BaseConfigType.BASE_CONFIG_TYPE_BOOLEAN) {
|
|
672
|
-
formData.value = formData.value === "true" ? "true" : "false";
|
|
673
703
|
}
|
|
674
|
-
if ((await formDialogRef.value?.validate()) !== true) return;
|
|
675
704
|
saving.value = true;
|
|
676
705
|
try {
|
|
677
706
|
const submitData = JSON.parse(JSON.stringify(formData)) as BaseConfigForm;
|
|
@@ -858,4 +887,26 @@ function handleDelete(selected?: number | string | Array<number | string> | Base
|
|
|
858
887
|
width: auto !important;
|
|
859
888
|
}
|
|
860
889
|
}
|
|
890
|
+
.config-editor-tabs :deep(.el-tabs__content) {
|
|
891
|
+
max-height: calc(92dvh - 180px);
|
|
892
|
+
overflow: auto;
|
|
893
|
+
padding: 8px 12px 0 0;
|
|
894
|
+
}
|
|
895
|
+
.config-editor-tabs--plain :deep(.el-tabs__header) {
|
|
896
|
+
display: none;
|
|
897
|
+
}
|
|
898
|
+
.config-editor-tabs--plain :deep(.el-tabs__content) {
|
|
899
|
+
max-height: calc(92dvh - 132px);
|
|
900
|
+
}
|
|
901
|
+
.config-editor-tabs :deep(.el-form-item) {
|
|
902
|
+
margin-bottom: 24px;
|
|
903
|
+
}
|
|
904
|
+
.config-editor-tabs :deep(.el-form-item__label) {
|
|
905
|
+
height: auto;
|
|
906
|
+
line-height: 1.6;
|
|
907
|
+
white-space: normal;
|
|
908
|
+
}
|
|
909
|
+
.config-form-value :deep(.el-input-number) {
|
|
910
|
+
width: 100%;
|
|
911
|
+
}
|
|
861
912
|
</style>
|
|
@@ -28,8 +28,12 @@
|
|
|
28
28
|
/>
|
|
29
29
|
</el-form-item>
|
|
30
30
|
<el-form-item class="migration-filters__actions">
|
|
31
|
-
<el-button type="primary"
|
|
32
|
-
|
|
31
|
+
<el-button type="primary" native-type="submit"
|
|
32
|
+
><template #icon><Search /></template>{{ t("common.action.search") }}</el-button
|
|
33
|
+
>
|
|
34
|
+
<el-button @click="handleReset"
|
|
35
|
+
><template #icon><Refresh /></template>{{ t("common.action.reset") }}</el-button
|
|
36
|
+
>
|
|
33
37
|
</el-form-item>
|
|
34
38
|
</el-form>
|
|
35
39
|
|
|
@@ -58,7 +62,9 @@
|
|
|
58
62
|
</div>
|
|
59
63
|
|
|
60
64
|
<div v-if="pageable.total > pageable.page_size" class="migration-pagination">
|
|
61
|
-
<span class="migration-pagination__total">{{
|
|
65
|
+
<span class="migration-pagination__total">{{
|
|
66
|
+
t("system.base.migration.message.total", { total: pageable.total })
|
|
67
|
+
}}</span>
|
|
62
68
|
<el-pagination
|
|
63
69
|
background
|
|
64
70
|
small
|
|
@@ -77,88 +83,56 @@
|
|
|
77
83
|
<el-skeleton :rows="8" animated />
|
|
78
84
|
</div>
|
|
79
85
|
<template v-else-if="selectedMigration">
|
|
80
|
-
<header class="detail-header">
|
|
81
|
-
<div class="detail-header__title">
|
|
82
|
-
<span class="detail-header__data-source">
|
|
83
|
-
{{ selectedMigration.module || t("system.base.migration.value.default_module") }} ·
|
|
84
|
-
{{ selectedMigration.data_source || "default" }}
|
|
85
|
-
</span>
|
|
86
|
-
<div class="detail-header__version">
|
|
87
|
-
<h2>{{ selectedMigration.version }}</h2>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
<div class="detail-header__meta">
|
|
91
|
-
<time :datetime="selectedMigration.created_at">{{ selectedMigration.created_at }}</time>
|
|
92
|
-
</div>
|
|
93
|
-
</header>
|
|
94
|
-
|
|
95
86
|
<div v-loading="detailLoading" class="detail-scroll">
|
|
96
87
|
<template v-if="!detailLoading">
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<el-tooltip :content="t('system.base.migration.action.copy_up_script')" placement="top">
|
|
127
|
-
<el-button
|
|
128
|
-
class="sql-copy"
|
|
129
|
-
text
|
|
130
|
-
circle
|
|
131
|
-
:icon="CopyDocument"
|
|
132
|
-
:aria-label="t('system.base.migration.action.copy_up_script')"
|
|
133
|
-
@click.stop="copySql(selectedMigration.up_sql)"
|
|
134
|
-
/>
|
|
135
|
-
</el-tooltip>
|
|
136
|
-
</div>
|
|
137
|
-
</el-collapse-item>
|
|
138
|
-
<el-collapse-item v-if="selectedMigration.down_sql" name="down">
|
|
139
|
-
<template #title>
|
|
140
|
-
<span class="sql-title">
|
|
141
|
-
<el-icon><DocumentRemove /></el-icon>
|
|
142
|
-
<span>{{ t("system.base.migration.field.down_script") }}</span>
|
|
143
|
-
<code>down.sql</code>
|
|
144
|
-
</span>
|
|
145
|
-
</template>
|
|
146
|
-
<div class="sql-panel">
|
|
147
|
-
<pre class="sql-code"><code>{{ selectedMigration.down_sql }}</code></pre>
|
|
148
|
-
<el-tooltip :content="t('system.base.migration.action.copy_down_script')" placement="top">
|
|
88
|
+
<el-tabs v-if="hasDetailContent" v-model="activeFilePath" class="migration-file-tabs">
|
|
89
|
+
<el-tab-pane
|
|
90
|
+
v-for="file in selectedMigration.files"
|
|
91
|
+
:key="file.path"
|
|
92
|
+
:name="file.path"
|
|
93
|
+
:label="file.path.split('/').pop()"
|
|
94
|
+
lazy
|
|
95
|
+
>
|
|
96
|
+
<template v-if="activeFilePath === file.path">
|
|
97
|
+
<div class="migration-file-path">{{ file.path }}</div>
|
|
98
|
+
<MarkdownPreview
|
|
99
|
+
v-if="file.path.endsWith('.md')"
|
|
100
|
+
class="migration-markdown"
|
|
101
|
+
:model-value="file.content"
|
|
102
|
+
:is-dark="globalStore.isDark"
|
|
103
|
+
max-code-height="360px"
|
|
104
|
+
/>
|
|
105
|
+
<div v-else class="sql-panel">
|
|
106
|
+
<pre class="sql-code"><code>{{ file.content }}</code></pre>
|
|
107
|
+
<el-tooltip
|
|
108
|
+
:content="
|
|
109
|
+
t(
|
|
110
|
+
file.path.endsWith('.down.sql')
|
|
111
|
+
? 'system.base.migration.action.copy_down_script'
|
|
112
|
+
: 'system.base.migration.action.copy_up_script'
|
|
113
|
+
)
|
|
114
|
+
"
|
|
115
|
+
placement="top"
|
|
116
|
+
>
|
|
149
117
|
<el-button
|
|
150
118
|
class="sql-copy"
|
|
151
119
|
text
|
|
152
120
|
circle
|
|
153
|
-
:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
121
|
+
:aria-label="
|
|
122
|
+
t(
|
|
123
|
+
file.path.endsWith('.down.sql')
|
|
124
|
+
? 'system.base.migration.action.copy_down_script'
|
|
125
|
+
: 'system.base.migration.action.copy_up_script'
|
|
126
|
+
)
|
|
127
|
+
"
|
|
128
|
+
@click="copySql(file.content)"
|
|
129
|
+
><template #icon><CopyDocument /></template
|
|
130
|
+
></el-button>
|
|
157
131
|
</el-tooltip>
|
|
158
132
|
</div>
|
|
159
|
-
</
|
|
160
|
-
</el-
|
|
161
|
-
</
|
|
133
|
+
</template>
|
|
134
|
+
</el-tab-pane>
|
|
135
|
+
</el-tabs>
|
|
162
136
|
|
|
163
137
|
<div v-if="!hasDetailContent" class="detail-empty">
|
|
164
138
|
<el-icon><Document /></el-icon>
|
|
@@ -176,7 +150,6 @@
|
|
|
176
150
|
|
|
177
151
|
<script setup lang="ts">
|
|
178
152
|
import { computed, onMounted, reactive, ref } from "vue";
|
|
179
|
-
import { CopyDocument, Document, DocumentAdd, DocumentRemove, Files, Refresh, Search } from "@element-plus/icons-vue";
|
|
180
153
|
import MarkdownPreview from "@liujitcn/kratos-admin-core/components/MarkdownPreview/index.vue";
|
|
181
154
|
import { useGlobalStore } from "@liujitcn/kratos-admin-core/stores/runtime";
|
|
182
155
|
import { buildPageRequest } from "@liujitcn/kratos-admin-core/table";
|
|
@@ -220,6 +193,7 @@ const histories = ref<BaseMigrationListItem[]>([]);
|
|
|
220
193
|
const selectedMigrationId = ref<number | null>(null);
|
|
221
194
|
const selectedMigration = ref<BaseMigration | null>(null);
|
|
222
195
|
const detailRequestToken = ref(0);
|
|
196
|
+
const activeFilePath = ref("");
|
|
223
197
|
const filters = reactive<MigrationFilters>({
|
|
224
198
|
version: undefined,
|
|
225
199
|
module: undefined,
|
|
@@ -230,9 +204,7 @@ const pageable = reactive<MigrationPageable>({
|
|
|
230
204
|
page_size: 10,
|
|
231
205
|
total: 0
|
|
232
206
|
});
|
|
233
|
-
const hasDetailContent = computed(() =>
|
|
234
|
-
Boolean(selectedMigration.value?.description || selectedMigration.value?.up_sql || selectedMigration.value?.down_sql)
|
|
235
|
-
);
|
|
207
|
+
const hasDetailContent = computed(() => Boolean(selectedMigration.value?.files.length));
|
|
236
208
|
|
|
237
209
|
/**
|
|
238
210
|
* 加载数据库升级历史列表。
|
|
@@ -269,6 +241,7 @@ async function loadMigrationHistory() {
|
|
|
269
241
|
async function selectMigration(id: number) {
|
|
270
242
|
selectedMigrationId.value = id;
|
|
271
243
|
selectedMigration.value = null;
|
|
244
|
+
activeFilePath.value = "";
|
|
272
245
|
detailLoading.value = true;
|
|
273
246
|
const requestToken = detailRequestToken.value + 1;
|
|
274
247
|
detailRequestToken.value = requestToken;
|
|
@@ -276,6 +249,7 @@ async function selectMigration(id: number) {
|
|
|
276
249
|
const detail = await defBaseMigrationService.GetBaseMigration({ id });
|
|
277
250
|
if (detailRequestToken.value === requestToken && selectedMigrationId.value === id) {
|
|
278
251
|
selectedMigration.value = detail;
|
|
252
|
+
activeFilePath.value = detail.files[0]?.path ?? "";
|
|
279
253
|
}
|
|
280
254
|
} finally {
|
|
281
255
|
if (detailRequestToken.value === requestToken) {
|
|
@@ -512,147 +486,69 @@ onMounted(() => {
|
|
|
512
486
|
white-space: nowrap;
|
|
513
487
|
}
|
|
514
488
|
|
|
515
|
-
.detail-header {
|
|
516
|
-
display: flex;
|
|
517
|
-
flex: 0 0 auto;
|
|
518
|
-
align-items: flex-start;
|
|
519
|
-
justify-content: space-between;
|
|
520
|
-
gap: 20px;
|
|
521
|
-
padding: 22px 24px 20px;
|
|
522
|
-
border-bottom: 1px solid var(--admin-page-divider);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
.detail-header__title {
|
|
526
|
-
min-width: 0;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
.detail-header__data-source {
|
|
530
|
-
display: block;
|
|
531
|
-
overflow: hidden;
|
|
532
|
-
font-size: 12px;
|
|
533
|
-
font-weight: 600;
|
|
534
|
-
color: var(--admin-page-text-secondary);
|
|
535
|
-
letter-spacing: 0.04em;
|
|
536
|
-
text-overflow: ellipsis;
|
|
537
|
-
white-space: nowrap;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
.detail-header__version {
|
|
541
|
-
display: flex;
|
|
542
|
-
flex-wrap: wrap;
|
|
543
|
-
gap: 10px;
|
|
544
|
-
align-items: center;
|
|
545
|
-
margin-top: 8px;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
.detail-header h2 {
|
|
549
|
-
margin: 0;
|
|
550
|
-
font-size: 24px;
|
|
551
|
-
font-weight: 650;
|
|
552
|
-
line-height: 1.35;
|
|
553
|
-
overflow-wrap: anywhere;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
.detail-header__meta {
|
|
557
|
-
display: flex;
|
|
558
|
-
flex: 0 0 auto;
|
|
559
|
-
flex-direction: column;
|
|
560
|
-
align-items: flex-end;
|
|
561
|
-
gap: 6px;
|
|
562
|
-
padding-top: 3px;
|
|
563
|
-
font-size: 12px;
|
|
564
|
-
color: var(--admin-page-text-secondary);
|
|
565
|
-
text-align: right;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
489
|
.detail-scroll {
|
|
490
|
+
display: flex;
|
|
569
491
|
flex: 1;
|
|
492
|
+
flex-direction: column;
|
|
570
493
|
min-height: 0;
|
|
571
|
-
overflow
|
|
494
|
+
overflow: hidden;
|
|
572
495
|
padding: 24px;
|
|
573
496
|
}
|
|
574
497
|
|
|
575
|
-
.detail-section + .detail-section {
|
|
576
|
-
margin-top: 26px;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
.detail-section__title {
|
|
580
|
-
display: flex;
|
|
581
|
-
gap: 7px;
|
|
582
|
-
align-items: center;
|
|
583
|
-
padding-bottom: 10px;
|
|
584
|
-
margin-bottom: 16px;
|
|
585
|
-
font-size: 14px;
|
|
586
|
-
font-weight: 600;
|
|
587
|
-
color: var(--admin-page-text-primary);
|
|
588
|
-
border-bottom: 1px solid var(--admin-page-divider);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
.detail-section__title :deep(.el-icon) {
|
|
592
|
-
color: var(--el-color-primary);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
498
|
.migration-markdown {
|
|
499
|
+
flex: 1;
|
|
500
|
+
min-height: 0;
|
|
501
|
+
overflow-y: auto;
|
|
596
502
|
width: 100%;
|
|
597
503
|
max-width: 100%;
|
|
598
504
|
overflow-wrap: anywhere;
|
|
599
505
|
}
|
|
600
506
|
|
|
601
|
-
.
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
border: 0;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
.release-sql :deep(.el-collapse-item__header) {
|
|
611
|
-
height: 46px;
|
|
612
|
-
padding: 0 14px;
|
|
613
|
-
color: var(--admin-page-text-primary);
|
|
614
|
-
background: var(--admin-page-card-bg-soft);
|
|
615
|
-
border-bottom-color: var(--admin-page-card-border-soft);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
.release-sql :deep(.el-collapse-item__wrap) {
|
|
619
|
-
background: var(--admin-page-card-bg);
|
|
620
|
-
border-bottom-color: var(--admin-page-card-border-soft);
|
|
507
|
+
.migration-file-tabs {
|
|
508
|
+
display: flex;
|
|
509
|
+
flex: 1;
|
|
510
|
+
flex-direction: column;
|
|
511
|
+
min-width: 0;
|
|
512
|
+
min-height: 0;
|
|
621
513
|
}
|
|
622
514
|
|
|
623
|
-
.
|
|
624
|
-
|
|
515
|
+
.migration-file-tabs :deep(.el-tabs__header) {
|
|
516
|
+
flex-shrink: 0;
|
|
625
517
|
}
|
|
626
518
|
|
|
627
|
-
.
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
min-width: 0;
|
|
632
|
-
font-size: 13px;
|
|
633
|
-
font-weight: 600;
|
|
519
|
+
.migration-file-tabs :deep(.el-tabs__content) {
|
|
520
|
+
flex: 1;
|
|
521
|
+
min-height: 0;
|
|
522
|
+
overflow: hidden;
|
|
634
523
|
}
|
|
635
524
|
|
|
636
|
-
.
|
|
637
|
-
|
|
525
|
+
.migration-file-tabs :deep(.el-tab-pane) {
|
|
526
|
+
display: flex;
|
|
527
|
+
flex-direction: column;
|
|
528
|
+
height: 100%;
|
|
529
|
+
min-height: 0;
|
|
638
530
|
}
|
|
639
531
|
|
|
640
|
-
.
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
font-
|
|
532
|
+
.migration-file-path {
|
|
533
|
+
flex-shrink: 0;
|
|
534
|
+
margin-bottom: 16px;
|
|
535
|
+
font-size: 12px;
|
|
644
536
|
color: var(--admin-page-text-secondary);
|
|
645
|
-
|
|
646
|
-
border-radius: var(--admin-page-radius);
|
|
537
|
+
overflow-wrap: anywhere;
|
|
647
538
|
}
|
|
648
539
|
|
|
649
540
|
.sql-panel {
|
|
650
541
|
position: relative;
|
|
542
|
+
display: flex;
|
|
543
|
+
flex: 1;
|
|
651
544
|
min-width: 0;
|
|
545
|
+
min-height: 0;
|
|
652
546
|
}
|
|
653
547
|
|
|
654
548
|
.sql-code {
|
|
655
|
-
|
|
549
|
+
flex: 1;
|
|
550
|
+
min-width: 0;
|
|
551
|
+
min-height: 0;
|
|
656
552
|
padding: 14px 48px 14px 16px;
|
|
657
553
|
margin: 0;
|
|
658
554
|
overflow: auto;
|
|
@@ -733,6 +629,7 @@ onMounted(() => {
|
|
|
733
629
|
}
|
|
734
630
|
|
|
735
631
|
.migration-detail-panel {
|
|
632
|
+
flex: 0 0 480px;
|
|
736
633
|
min-height: 480px;
|
|
737
634
|
border-left: 0;
|
|
738
635
|
}
|
|
@@ -744,20 +641,9 @@ onMounted(() => {
|
|
|
744
641
|
overflow-x: auto;
|
|
745
642
|
}
|
|
746
643
|
|
|
747
|
-
.detail-header,
|
|
748
644
|
.detail-scroll {
|
|
749
645
|
padding-right: 16px;
|
|
750
646
|
padding-left: 16px;
|
|
751
647
|
}
|
|
752
|
-
|
|
753
|
-
.detail-header {
|
|
754
|
-
flex-direction: column;
|
|
755
|
-
gap: 10px;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
.detail-header__meta {
|
|
759
|
-
align-items: flex-start;
|
|
760
|
-
text-align: left;
|
|
761
|
-
}
|
|
762
648
|
}
|
|
763
649
|
</style>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liujitcn/kratos-admin-system",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.42",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "kratos-admin system management module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"sortablejs": "^1.15.2",
|
|
17
17
|
"swagger-ui-dist": "^5.32.9",
|
|
18
18
|
"vue-element-plus-x": "^2.0.3",
|
|
19
|
-
"@liujitcn/kratos-admin-core": "0.0.
|
|
19
|
+
"@liujitcn/kratos-admin-core": "0.0.42"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@element-plus/icons-vue": "^2.3.1",
|