@liujitcn/kratos-admin-system 0.0.20 → 0.0.21
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 +51 -50
- package/dist/package/declarations/src/api/system/base_language.d.ts +51 -0
- package/dist/package/declarations/src/api/system/base_translation.d.ts +7 -0
- package/dist/package/declarations/src/api/system/code_gen_i18n.d.ts +8 -0
- package/dist/package/declarations/src/components/DynamicTranslationCell.vue.d.ts +39 -0
- package/dist/package/declarations/src/components/DynamicTranslationEditor.vue.d.ts +23 -0
- package/dist/package/declarations/src/components/dynamicTranslation.d.ts +44 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +5 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +79 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_language.d.ts +114 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +40 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_translation.d.ts +102 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +2 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_column.d.ts +7 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen_table.d.ts +13 -0
- package/dist/package/declarations/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue.d.ts +23 -0
- package/dist/package/declarations/src/views/tool/code-gen/config.d.ts +7 -7
- package/dist/package/declarations/src/views/tool/language/index.vue.d.ts +3 -0
- package/dist/package/src/api/base/ai_message.ts +7 -12
- package/dist/package/src/api/base/sse.ts +3 -1
- package/dist/package/src/api/system/base_language.ts +137 -0
- package/dist/package/src/api/system/base_translation.ts +22 -0
- package/dist/package/src/api/system/code_gen_column.ts +17 -3
- package/dist/package/src/api/system/code_gen_i18n.ts +18 -0
- package/dist/package/src/api/system/code_gen_table.ts +12 -4
- package/dist/package/src/components/DynamicTranslationCell.vue +176 -0
- package/dist/package/src/components/DynamicTranslationEditor.vue +166 -0
- package/dist/package/src/components/dynamicTranslation.ts +86 -0
- package/dist/package/src/locales/README.md +28 -0
- package/dist/package/src/locales/en-US.json +929 -0
- package/dist/package/src/locales/es-ES.json +928 -0
- package/dist/package/src/locales/fr-FR.json +928 -0
- package/dist/package/src/locales/ja-JP.json +929 -0
- package/dist/package/src/locales/ko-KR.json +928 -0
- package/dist/package/src/locales/zh-CN.json +929 -0
- package/dist/package/src/locales/zh-TW.json +928 -0
- package/dist/package/src/module.ts +19 -1
- package/dist/package/src/rpc/system/admin/v1/base_config.ts +5 -0
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +81 -0
- package/dist/package/src/rpc/system/admin/v1/base_language.ts +135 -0
- package/dist/package/src/rpc/system/admin/v1/base_menu.ts +41 -0
- package/dist/package/src/rpc/system/admin/v1/base_translation.ts +117 -0
- package/dist/package/src/rpc/system/admin/v1/code_gen.ts +5 -1
- package/dist/package/src/rpc/system/admin/v1/code_gen_column.ts +8 -0
- package/dist/package/src/rpc/system/admin/v1/code_gen_table.ts +15 -0
- package/dist/package/src/views/ai/chat/components/AiMarkdown.vue +1 -6
- package/dist/package/src/views/ai/chat/components/ChatPanel.vue +70 -53
- package/dist/package/src/views/ai/chat/components/SessionPanel.vue +20 -9
- package/dist/package/src/views/ai/chat/components/XSender.vue +36 -23
- package/dist/package/src/views/ai/chat/index.vue +55 -39
- package/dist/package/src/views/ai/chat/message.ts +18 -17
- package/dist/package/src/views/base/api/index.vue +121 -83
- package/dist/package/src/views/base/area/index.vue +81 -31
- package/dist/package/src/views/base/config/index.vue +272 -78
- package/dist/package/src/views/base/dept/index.vue +110 -61
- package/dist/package/src/views/base/dict/index.vue +137 -58
- package/dist/package/src/views/base/dict/item.vue +147 -61
- package/dist/package/src/views/base/job/index.vue +134 -81
- package/dist/package/src/views/base/job/log.vue +21 -22
- package/dist/package/src/views/base/log/index.vue +68 -46
- package/dist/package/src/views/base/menu/index.vue +257 -156
- package/dist/package/src/views/base/migration/index.vue +49 -29
- package/dist/package/src/views/base/post/index.vue +128 -59
- package/dist/package/src/views/base/role/index.vue +145 -78
- package/dist/package/src/views/base/tenant/index.vue +132 -63
- package/dist/package/src/views/base/user/components/dept-tree.vue +2 -1
- package/dist/package/src/views/base/user/index.vue +192 -112
- package/dist/package/src/views/profile/components/base.vue +44 -27
- package/dist/package/src/views/profile/components/password.vue +43 -27
- package/dist/package/src/views/profile/components/security.vue +89 -53
- package/dist/package/src/views/profile/index.vue +10 -9
- package/dist/package/src/views/tool/api-doc/index.vue +6 -2
- package/dist/package/src/views/tool/code-gen/code-preview/index.vue +30 -14
- package/dist/package/src/views/tool/code-gen/columns/index.vue +382 -273
- package/dist/package/src/views/tool/code-gen/columns/option-copy.ts +1 -6
- package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +118 -0
- package/dist/package/src/views/tool/code-gen/components/CodeGenProgressDialog.vue +65 -36
- package/dist/package/src/views/tool/code-gen/components/CodePreviewPane.vue +8 -5
- package/dist/package/src/views/tool/code-gen/config.ts +114 -77
- package/dist/package/src/views/tool/code-gen/preview/data.ts +19 -17
- package/dist/package/src/views/tool/code-gen/preview/index.vue +96 -58
- package/dist/package/src/views/tool/code-gen/proto/index.vue +110 -62
- package/dist/package/src/views/tool/code-gen/table/index.vue +197 -117
- package/dist/package/src/views/tool/language/index.vue +256 -0
- package/dist/package/src/views/tool/project-doc/index.vue +17 -15
- package/package.json +6 -2
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
<template #header>
|
|
4
4
|
<div class="panel-header">
|
|
5
5
|
<div>
|
|
6
|
-
<h3
|
|
7
|
-
<p
|
|
6
|
+
<h3>{{ t("system.profile.account.title") }}</h3>
|
|
7
|
+
<p>{{ t("system.profile.account.description") }}</p>
|
|
8
8
|
</div>
|
|
9
|
-
<el-button type="primary" plain @click="openAccountDialog"
|
|
9
|
+
<el-button type="primary" plain @click="openAccountDialog">{{ t("system.profile.account.action.edit") }}</el-button>
|
|
10
10
|
</div>
|
|
11
11
|
</template>
|
|
12
12
|
|
|
@@ -18,49 +18,56 @@
|
|
|
18
18
|
<input ref="fileInputRef" type="file" class="hidden-input" accept="image/*" @change="handleFileChange" />
|
|
19
19
|
</div>
|
|
20
20
|
<div class="avatar-copy">
|
|
21
|
-
<strong
|
|
22
|
-
<p
|
|
21
|
+
<strong>{{ t("system.profile.account.field.avatar") }}</strong>
|
|
22
|
+
<p>{{ t("system.profile.account.message.avatarHint") }}</p>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
26
|
<div class="detail-grid">
|
|
27
27
|
<div class="detail-item">
|
|
28
|
-
<span class="detail-label"
|
|
28
|
+
<span class="detail-label">{{ t("system.profile.account.field.userName") }}</span>
|
|
29
29
|
<span class="detail-value">{{ profile.user_name || "--" }}</span>
|
|
30
30
|
</div>
|
|
31
31
|
<div class="detail-item">
|
|
32
|
-
<span class="detail-label"
|
|
32
|
+
<span class="detail-label">{{ t("system.profile.account.field.nickName") }}</span>
|
|
33
33
|
<span class="detail-value">{{ profile.nick_name || "--" }}</span>
|
|
34
34
|
</div>
|
|
35
35
|
<div class="detail-item">
|
|
36
|
-
<span class="detail-label"
|
|
36
|
+
<span class="detail-label">{{ t("system.profile.account.field.gender") }}</span>
|
|
37
37
|
<span class="detail-value">{{ genderText }}</span>
|
|
38
38
|
</div>
|
|
39
39
|
<div class="detail-item">
|
|
40
|
-
<span class="detail-label"
|
|
41
|
-
<span class="detail-value">{{ profile.phone || "
|
|
40
|
+
<span class="detail-label">{{ t("system.profile.account.field.phone") }}</span>
|
|
41
|
+
<span class="detail-value">{{ profile.phone || t("system.profile.value.unbound") }}</span>
|
|
42
42
|
</div>
|
|
43
43
|
<div class="detail-item">
|
|
44
|
-
<span class="detail-label"
|
|
44
|
+
<span class="detail-label">{{ t("system.profile.account.field.role") }}</span>
|
|
45
45
|
<span class="detail-value">{{ profile.role_name || "--" }}</span>
|
|
46
46
|
</div>
|
|
47
47
|
<div class="detail-item">
|
|
48
|
-
<span class="detail-label"
|
|
48
|
+
<span class="detail-label">{{ t("system.profile.account.field.dept") }}</span>
|
|
49
49
|
<span class="detail-value">{{ profile.dept_name || "--" }}</span>
|
|
50
50
|
</div>
|
|
51
51
|
<div class="detail-item detail-item--wide">
|
|
52
|
-
<span class="detail-label"
|
|
52
|
+
<span class="detail-label">{{ t("system.common.field.createdAt") }}</span>
|
|
53
53
|
<span class="detail-value">{{ profile.created_at || "--" }}</span>
|
|
54
54
|
</div>
|
|
55
55
|
</div>
|
|
56
56
|
</div>
|
|
57
57
|
|
|
58
|
-
<ProDialog
|
|
58
|
+
<ProDialog
|
|
59
|
+
v-model="accountDialogVisible"
|
|
60
|
+
:title="t('system.profile.account.action.edit')"
|
|
61
|
+
:width="520"
|
|
62
|
+
@closed="handleDialogClosed"
|
|
63
|
+
>
|
|
59
64
|
<ProForm ref="accountFormRef" :model="accountForm" :fields="accountFormFields" label-width="96px" />
|
|
60
65
|
<template #footer>
|
|
61
66
|
<div class="dialog-footer">
|
|
62
|
-
<el-button @click="accountDialogVisible = false"
|
|
63
|
-
<el-button type="primary" :loading="submitLoading" @click="handleSubmitAccount"
|
|
67
|
+
<el-button @click="accountDialogVisible = false">{{ t("common.action.cancel") }}</el-button>
|
|
68
|
+
<el-button type="primary" :loading="submitLoading" @click="handleSubmitAccount">
|
|
69
|
+
{{ t("common.action.save") }}
|
|
70
|
+
</el-button>
|
|
64
71
|
</div>
|
|
65
72
|
</template>
|
|
66
73
|
</ProDialog>
|
|
@@ -69,6 +76,7 @@
|
|
|
69
76
|
|
|
70
77
|
<script setup lang="ts">
|
|
71
78
|
import { computed, reactive, ref, watch } from "vue";
|
|
79
|
+
import { t } from "@liujitcn/kratos-admin-core";
|
|
72
80
|
import { defProfileAuthService } from "@liujitcn/kratos-admin-system/api/system/auth";
|
|
73
81
|
import type { UserProfileForm } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/auth";
|
|
74
82
|
import { defFileService } from "@liujitcn/kratos-admin-core/api/base/file";
|
|
@@ -76,7 +84,6 @@ import defaultAvatar from "@liujitcn/kratos-admin-core/assets/images/avatar.png"
|
|
|
76
84
|
import ProDialog from "@liujitcn/kratos-admin-core/components/Dialog/ProDialog.vue";
|
|
77
85
|
import ProForm from "@liujitcn/kratos-admin-core/components/ProForm/index.vue";
|
|
78
86
|
import type { ProFormField, ProFormInstance } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
79
|
-
import { ElMessage } from "element-plus";
|
|
80
87
|
import { Camera } from "@element-plus/icons-vue";
|
|
81
88
|
|
|
82
89
|
/** 个人中心基础资料组件属性。 */
|
|
@@ -101,16 +108,26 @@ const accountForm = reactive<Pick<UserProfileForm, "nick_name" | "gender">>({
|
|
|
101
108
|
gender: 3
|
|
102
109
|
});
|
|
103
110
|
|
|
104
|
-
const accountFormFields
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
const accountFormFields = computed<ProFormField[]>(() => [
|
|
112
|
+
{
|
|
113
|
+
prop: "nick_name",
|
|
114
|
+
label: t("system.profile.account.field.nickName"),
|
|
115
|
+
component: "input",
|
|
116
|
+
props: { placeholder: t("system.profile.account.placeholder.nickName") }
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
prop: "gender",
|
|
120
|
+
label: t("system.profile.account.field.gender"),
|
|
121
|
+
component: "dict",
|
|
122
|
+
props: { code: "base_user_gender" }
|
|
123
|
+
}
|
|
124
|
+
]);
|
|
108
125
|
|
|
109
126
|
/** 根据资料中的性别值输出展示文案。 */
|
|
110
127
|
const genderText = computed(() => {
|
|
111
|
-
if (props.profile.gender === 1) return "
|
|
112
|
-
if (props.profile.gender === 2) return "
|
|
113
|
-
return "
|
|
128
|
+
if (props.profile.gender === 1) return t("common.value.male");
|
|
129
|
+
if (props.profile.gender === 2) return t("common.value.female");
|
|
130
|
+
return t("system.profile.value.private");
|
|
114
131
|
});
|
|
115
132
|
|
|
116
133
|
/**
|
|
@@ -165,10 +182,10 @@ async function handleFileChange(event: Event) {
|
|
|
165
182
|
avatar: uploadResult.url
|
|
166
183
|
}
|
|
167
184
|
});
|
|
168
|
-
ElMessage.success("
|
|
185
|
+
ElMessage.success(t("system.profile.account.message.avatarUpdated"));
|
|
169
186
|
emit("refreshed");
|
|
170
187
|
} catch (_error) {
|
|
171
|
-
ElMessage.error("
|
|
188
|
+
ElMessage.error(t("system.profile.account.message.avatarUploadFailed"));
|
|
172
189
|
} finally {
|
|
173
190
|
target.value = "";
|
|
174
191
|
}
|
|
@@ -187,7 +204,7 @@ async function handleSubmitAccount() {
|
|
|
187
204
|
gender: accountForm.gender
|
|
188
205
|
}
|
|
189
206
|
});
|
|
190
|
-
ElMessage.success("
|
|
207
|
+
ElMessage.success(t("system.profile.account.message.updated"));
|
|
191
208
|
accountDialogVisible.value = false;
|
|
192
209
|
emit("refreshed");
|
|
193
210
|
} finally {
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<template #header>
|
|
5
5
|
<div class="panel-header">
|
|
6
6
|
<div>
|
|
7
|
-
<h3
|
|
8
|
-
<p
|
|
7
|
+
<h3>{{ t("system.profile.password.title") }}</h3>
|
|
8
|
+
<p>{{ t("system.profile.password.description") }}</p>
|
|
9
9
|
</div>
|
|
10
|
-
<el-tag type="warning" effect="plain"
|
|
10
|
+
<el-tag type="warning" effect="plain">{{ t("system.profile.password.value.strongRecommended") }}</el-tag>
|
|
11
11
|
</div>
|
|
12
12
|
</template>
|
|
13
13
|
<div class="password-layout">
|
|
@@ -21,25 +21,27 @@
|
|
|
21
21
|
/>
|
|
22
22
|
<PasswordStrength :password="passwordForm.new_pwd" class="password-form-wrap__strength" />
|
|
23
23
|
<div class="password-footer">
|
|
24
|
-
<el-button @click="resetPasswordForm"
|
|
25
|
-
<el-button type="primary" :loading="submitLoading" @click="handleSubmitPassword"
|
|
24
|
+
<el-button @click="resetPasswordForm">{{ t("common.action.reset") }}</el-button>
|
|
25
|
+
<el-button type="primary" :loading="submitLoading" @click="handleSubmitPassword">
|
|
26
|
+
{{ t("system.profile.password.action.update") }}
|
|
27
|
+
</el-button>
|
|
26
28
|
</div>
|
|
27
29
|
</div>
|
|
28
30
|
<div class="password-tips">
|
|
29
31
|
<div class="tip-card">
|
|
30
32
|
<span class="tip-badge">01</span>
|
|
31
|
-
<strong
|
|
32
|
-
<p
|
|
33
|
+
<strong>{{ t("system.profile.password.tip.uniqueTitle") }}</strong>
|
|
34
|
+
<p>{{ t("system.profile.password.tip.uniqueDescription") }}</p>
|
|
33
35
|
</div>
|
|
34
36
|
<div class="tip-card">
|
|
35
37
|
<span class="tip-badge">02</span>
|
|
36
|
-
<strong
|
|
37
|
-
<p
|
|
38
|
+
<strong>{{ t("system.profile.password.tip.strengthTitle") }}</strong>
|
|
39
|
+
<p>{{ t("system.profile.password.tip.strengthDescription") }}</p>
|
|
38
40
|
</div>
|
|
39
41
|
<div class="tip-card">
|
|
40
42
|
<span class="tip-badge">03</span>
|
|
41
|
-
<strong
|
|
42
|
-
<p
|
|
43
|
+
<strong>{{ t("system.profile.password.tip.recordTitle") }}</strong>
|
|
44
|
+
<p>{{ t("system.profile.password.tip.recordDescription") }}</p>
|
|
43
45
|
</div>
|
|
44
46
|
</div>
|
|
45
47
|
</div>
|
|
@@ -50,6 +52,7 @@
|
|
|
50
52
|
<script setup lang="ts">
|
|
51
53
|
import { computed, reactive, ref } from "vue";
|
|
52
54
|
import { useRouter } from "vue-router";
|
|
55
|
+
import { t } from "@liujitcn/kratos-admin-core";
|
|
53
56
|
import { defProfileAuthService } from "@liujitcn/kratos-admin-system/api/system/auth";
|
|
54
57
|
import type { UserPasswordForm } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/auth";
|
|
55
58
|
import PasswordStrength from "@liujitcn/kratos-admin-core/components/PasswordStrength/index.vue";
|
|
@@ -57,10 +60,8 @@ import ProForm from "@liujitcn/kratos-admin-core/components/ProForm/index.vue";
|
|
|
57
60
|
import type { ProFormField, ProFormInstance } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
58
61
|
import { LOGIN_URL } from "@liujitcn/kratos-admin-core/config";
|
|
59
62
|
import { useUserStore } from "@liujitcn/kratos-admin-core/stores/runtime";
|
|
60
|
-
import { ElMessage } from "element-plus";
|
|
61
63
|
import {
|
|
62
64
|
PASSWORD_CRYPTO_SCENE,
|
|
63
|
-
PASSWORD_STRENGTH_ERROR_MESSAGE,
|
|
64
65
|
encryptPassword,
|
|
65
66
|
getPasswordStrength,
|
|
66
67
|
validatePasswordStrengthValue
|
|
@@ -86,20 +87,35 @@ const passwordForm = reactive<UserPasswordFormState>({
|
|
|
86
87
|
confirm_pwd: ""
|
|
87
88
|
});
|
|
88
89
|
|
|
89
|
-
const passwordFormFields
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
const passwordFormFields = computed<ProFormField[]>(() => [
|
|
91
|
+
{
|
|
92
|
+
prop: "old_pwd",
|
|
93
|
+
label: t("system.profile.password.field.oldPassword"),
|
|
94
|
+
component: "password",
|
|
95
|
+
props: { placeholder: t("system.profile.password.placeholder.oldPassword") }
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
prop: "new_pwd",
|
|
99
|
+
label: t("system.profile.password.field.newPassword"),
|
|
100
|
+
component: "password",
|
|
101
|
+
props: { placeholder: t("system.profile.password.placeholder.newPassword") }
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
prop: "confirm_pwd",
|
|
105
|
+
label: t("system.profile.password.field.confirmPassword"),
|
|
106
|
+
component: "password",
|
|
107
|
+
props: { placeholder: t("system.profile.password.placeholder.confirmPassword") }
|
|
108
|
+
}
|
|
109
|
+
]);
|
|
94
110
|
|
|
95
|
-
const passwordFormRules = {
|
|
96
|
-
old_pwd: [{ required: true, message: "
|
|
111
|
+
const passwordFormRules = computed(() => ({
|
|
112
|
+
old_pwd: [{ required: true, message: t("system.profile.password.placeholder.oldPassword"), trigger: "blur" }],
|
|
97
113
|
new_pwd: [
|
|
98
|
-
{ required: true, message: "
|
|
114
|
+
{ required: true, message: t("system.profile.password.placeholder.newPassword"), trigger: "blur" },
|
|
99
115
|
{ validator: validatePasswordStrength, trigger: "blur" }
|
|
100
116
|
],
|
|
101
|
-
confirm_pwd: [{ required: true, message: "
|
|
102
|
-
};
|
|
117
|
+
confirm_pwd: [{ required: true, message: t("system.profile.password.placeholder.confirmPassword"), trigger: "blur" }]
|
|
118
|
+
}));
|
|
103
119
|
|
|
104
120
|
/** 统一计算当前新密码强度,供展示和提交校验复用。 */
|
|
105
121
|
const passwordStrength = computed(() => getPasswordStrength(passwordForm.new_pwd));
|
|
@@ -108,11 +124,11 @@ const passwordStrength = computed(() => getPasswordStrength(passwordForm.new_pwd
|
|
|
108
124
|
async function handleSubmitPassword() {
|
|
109
125
|
if (!(await passwordFormRef.value?.validate())) return;
|
|
110
126
|
if (passwordForm.new_pwd !== passwordForm.confirm_pwd) {
|
|
111
|
-
ElMessage.error("
|
|
127
|
+
ElMessage.error(t("system.profile.password.message.mismatch"));
|
|
112
128
|
return;
|
|
113
129
|
}
|
|
114
130
|
if (!passwordStrength.value.isValid) {
|
|
115
|
-
ElMessage.error("
|
|
131
|
+
ElMessage.error(t("system.profile.password.message.strengthInsufficient"));
|
|
116
132
|
return;
|
|
117
133
|
}
|
|
118
134
|
|
|
@@ -125,7 +141,7 @@ async function handleSubmitPassword() {
|
|
|
125
141
|
new_pwd: newPwd
|
|
126
142
|
};
|
|
127
143
|
await defProfileAuthService.UpdateUserPassword({ user_password: userPassword });
|
|
128
|
-
ElMessage.success("
|
|
144
|
+
ElMessage.success(t("system.profile.password.message.updated"));
|
|
129
145
|
resetPasswordForm();
|
|
130
146
|
await forceRelogin();
|
|
131
147
|
} finally {
|
|
@@ -167,7 +183,7 @@ function validatePasswordStrength(_rule: unknown, value: string, callback: (erro
|
|
|
167
183
|
}
|
|
168
184
|
const result = validatePasswordStrengthValue(value);
|
|
169
185
|
if (!result.valid) {
|
|
170
|
-
callback(new Error(
|
|
186
|
+
callback(new Error(t("core.password.error")));
|
|
171
187
|
return;
|
|
172
188
|
}
|
|
173
189
|
callback();
|
|
@@ -3,36 +3,44 @@
|
|
|
3
3
|
<el-card class="security-card" shadow="never">
|
|
4
4
|
<div class="security-intro">
|
|
5
5
|
<div>
|
|
6
|
-
<h3
|
|
7
|
-
<p
|
|
6
|
+
<h3>{{ t("system.profile.security.title") }}</h3>
|
|
7
|
+
<p>{{ t("system.profile.security.description") }}</p>
|
|
8
8
|
</div>
|
|
9
|
-
<el-tag effect="plain" type="success"
|
|
9
|
+
<el-tag effect="plain" type="success">{{ t("system.profile.security.value.accountNormal") }}</el-tag>
|
|
10
10
|
</div>
|
|
11
11
|
<div class="security-list">
|
|
12
12
|
<div class="security-item">
|
|
13
13
|
<div class="security-item__content">
|
|
14
|
-
<strong
|
|
15
|
-
<p
|
|
14
|
+
<strong>{{ t("system.profile.security.field.loginPassword") }}</strong>
|
|
15
|
+
<p>{{ t("system.profile.security.message.passwordHint") }}</p>
|
|
16
16
|
</div>
|
|
17
|
-
<el-button type="primary" plain @click="emit('switchTab', 'password')"
|
|
17
|
+
<el-button type="primary" plain @click="emit('switchTab', 'password')">
|
|
18
|
+
{{ t("system.profile.security.action.changePassword") }}
|
|
19
|
+
</el-button>
|
|
18
20
|
</div>
|
|
19
21
|
<div class="security-item">
|
|
20
22
|
<div class="security-item__content">
|
|
21
|
-
<strong
|
|
23
|
+
<strong>{{ t("system.profile.security.field.phone") }}</strong>
|
|
22
24
|
<p>{{ mobileTip }}</p>
|
|
23
25
|
</div>
|
|
24
|
-
<el-button plain @click="openPhoneDialog">
|
|
26
|
+
<el-button plain @click="openPhoneDialog">
|
|
27
|
+
{{ profile.phone ? t("system.profile.security.action.changePhone") : t("system.profile.security.action.bindNow") }}
|
|
28
|
+
</el-button>
|
|
25
29
|
</div>
|
|
26
30
|
<div v-for="item in oauthBindings" :key="item.provider" class="security-item">
|
|
27
31
|
<div class="security-item__content security-item__content--oauth">
|
|
28
|
-
<el-tooltip :content="item
|
|
29
|
-
<span class="oauth-icon" :aria-label="item
|
|
32
|
+
<el-tooltip :content="oauthName(item)" placement="top" :trigger="['hover', 'focus']">
|
|
33
|
+
<span class="oauth-icon" :aria-label="oauthName(item)" :title="oauthName(item)">
|
|
30
34
|
<component :is="getOauthProviderIcon(item)" />
|
|
31
35
|
</span>
|
|
32
36
|
</el-tooltip>
|
|
33
37
|
<div>
|
|
34
|
-
<strong>{{ item
|
|
35
|
-
<p>
|
|
38
|
+
<strong>{{ oauthName(item) }}</strong>
|
|
39
|
+
<p>
|
|
40
|
+
{{
|
|
41
|
+
item.bound ? t("system.profile.security.message.oauthBound") : t("system.profile.security.message.oauthUnbound")
|
|
42
|
+
}}
|
|
43
|
+
</p>
|
|
36
44
|
</div>
|
|
37
45
|
</div>
|
|
38
46
|
<el-button
|
|
@@ -42,11 +50,11 @@
|
|
|
42
50
|
:loading="oauthLoadingProvider === item.provider"
|
|
43
51
|
@click="handleUnbindOauth(item)"
|
|
44
52
|
>
|
|
45
|
-
|
|
53
|
+
{{ t("system.profile.security.action.unbind") }}
|
|
46
54
|
</el-button>
|
|
47
|
-
<el-button v-else plain :loading="oauthLoadingProvider === item.provider" @click="handleBindOauth(item)"
|
|
48
|
-
|
|
49
|
-
>
|
|
55
|
+
<el-button v-else plain :loading="oauthLoadingProvider === item.provider" @click="handleBindOauth(item)">{{
|
|
56
|
+
t("system.profile.security.action.bind")
|
|
57
|
+
}}</el-button>
|
|
50
58
|
</div>
|
|
51
59
|
</div>
|
|
52
60
|
</el-card>
|
|
@@ -55,30 +63,39 @@
|
|
|
55
63
|
<template #header>
|
|
56
64
|
<div class="status-header">
|
|
57
65
|
<div>
|
|
58
|
-
<h3
|
|
59
|
-
<p
|
|
66
|
+
<h3>{{ t("system.profile.security.status.title") }}</h3>
|
|
67
|
+
<p>{{ t("system.profile.security.status.description") }}</p>
|
|
60
68
|
</div>
|
|
61
69
|
</div>
|
|
62
70
|
</template>
|
|
63
71
|
<div class="status-grid">
|
|
64
72
|
<div class="status-item">
|
|
65
|
-
<span
|
|
66
|
-
<strong>{{ profile.phone ? "
|
|
73
|
+
<span>{{ t("system.profile.security.field.phoneVerification") }}</span>
|
|
74
|
+
<strong>{{ profile.phone ? t("common.status.enabled") : t("common.status.disabled") }}</strong>
|
|
67
75
|
</div>
|
|
68
76
|
<div class="status-item">
|
|
69
|
-
<span
|
|
77
|
+
<span>{{ t("system.profile.security.field.profileCompletion") }}</span>
|
|
70
78
|
<strong>{{ profileCompletion }}</strong>
|
|
71
79
|
</div>
|
|
72
80
|
</div>
|
|
73
81
|
</el-card>
|
|
74
82
|
|
|
75
|
-
<ProDialog
|
|
83
|
+
<ProDialog
|
|
84
|
+
v-model="phoneDialogVisible"
|
|
85
|
+
:title="t('system.profile.security.dialog.bindPhone')"
|
|
86
|
+
:width="520"
|
|
87
|
+
@closed="handleDialogClosed"
|
|
88
|
+
>
|
|
76
89
|
<ProForm ref="phoneFormRef" :model="phoneForm" :fields="phoneFormFields" :rules="phoneFormRules" label-width="96px">
|
|
77
90
|
<template #mobileCodeInput>
|
|
78
|
-
<el-input v-model="phoneForm.code" placeholder="
|
|
91
|
+
<el-input v-model="phoneForm.code" :placeholder="t('system.profile.security.placeholder.code')">
|
|
79
92
|
<template #append>
|
|
80
93
|
<el-button :disabled="countdown > 0" @click="handleSendCode">
|
|
81
|
-
{{
|
|
94
|
+
{{
|
|
95
|
+
countdown > 0
|
|
96
|
+
? t("system.profile.security.action.retryAfter", { seconds: countdown })
|
|
97
|
+
: t("system.profile.security.action.sendCode")
|
|
98
|
+
}}
|
|
82
99
|
</el-button>
|
|
83
100
|
</template>
|
|
84
101
|
</el-input>
|
|
@@ -86,8 +103,10 @@
|
|
|
86
103
|
</ProForm>
|
|
87
104
|
<template #footer>
|
|
88
105
|
<div class="dialog-footer">
|
|
89
|
-
<el-button @click="phoneDialogVisible = false"
|
|
90
|
-
<el-button type="primary" :loading="submitLoading" @click="handleSubmitPhone"
|
|
106
|
+
<el-button @click="phoneDialogVisible = false">{{ t("common.action.cancel") }}</el-button>
|
|
107
|
+
<el-button type="primary" :loading="submitLoading" @click="handleSubmitPhone">
|
|
108
|
+
{{ t("common.action.save") }}
|
|
109
|
+
</el-button>
|
|
91
110
|
</div>
|
|
92
111
|
</template>
|
|
93
112
|
</ProDialog>
|
|
@@ -97,6 +116,7 @@
|
|
|
97
116
|
<script setup lang="ts">
|
|
98
117
|
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
|
99
118
|
import { useRoute, useRouter } from "vue-router";
|
|
119
|
+
import { t } from "@liujitcn/kratos-admin-core";
|
|
100
120
|
import { defProfileAuthService } from "@liujitcn/kratos-admin-system/api/system/auth";
|
|
101
121
|
import { defProfileOauthService } from "@liujitcn/kratos-admin-system/api/base/oauth";
|
|
102
122
|
import type { OauthBinding } from "@liujitcn/kratos-admin-system/rpc/base/v1/oauth";
|
|
@@ -108,13 +128,8 @@ import type {
|
|
|
108
128
|
import ProDialog from "@liujitcn/kratos-admin-core/components/Dialog/ProDialog.vue";
|
|
109
129
|
import ProForm from "@liujitcn/kratos-admin-core/components/ProForm/index.vue";
|
|
110
130
|
import type { ProFormField, ProFormInstance } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
111
|
-
import {
|
|
112
|
-
getOauthProviderIcon,
|
|
113
|
-
withOauthProviderDisplay,
|
|
114
|
-
type OauthProviderDisplay
|
|
115
|
-
} from "@liujitcn/kratos-admin-core/security";
|
|
131
|
+
import { getOauthProviderIcon, withOauthProviderDisplay, type OauthProviderDisplay } from "@liujitcn/kratos-admin-core/security";
|
|
116
132
|
import { resolveFrontendRouteURL } from "@liujitcn/kratos-admin-core/navigation";
|
|
117
|
-
import { ElMessage, ElMessageBox } from "element-plus";
|
|
118
133
|
|
|
119
134
|
/** 安全中心组件属性。 */
|
|
120
135
|
interface ProfileSecurityProps {
|
|
@@ -149,22 +164,34 @@ const sendPhoneCodeForm = reactive<SendPhoneCodeRequest>({
|
|
|
149
164
|
phone: ""
|
|
150
165
|
});
|
|
151
166
|
|
|
152
|
-
const phoneFormFields
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
167
|
+
const phoneFormFields = computed<ProFormField[]>(() => [
|
|
168
|
+
{
|
|
169
|
+
prop: "phone",
|
|
170
|
+
label: t("system.profile.security.field.phoneNumber"),
|
|
171
|
+
component: "input",
|
|
172
|
+
props: { placeholder: t("system.profile.security.placeholder.phone") }
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
prop: "code",
|
|
176
|
+
label: t("system.profile.security.field.code"),
|
|
177
|
+
component: "slot",
|
|
178
|
+
slotName: "mobileCodeInput"
|
|
179
|
+
}
|
|
180
|
+
]);
|
|
156
181
|
|
|
157
|
-
const phoneFormRules = {
|
|
182
|
+
const phoneFormRules = computed(() => ({
|
|
158
183
|
phone: [
|
|
159
|
-
{ required: true, message: "
|
|
160
|
-
{ pattern: /^1[3-9]\d{9}$/, message: "
|
|
184
|
+
{ required: true, message: t("system.profile.security.placeholder.phone"), trigger: "blur" },
|
|
185
|
+
{ pattern: /^1[3-9]\d{9}$/, message: t("system.profile.security.validation.phone"), trigger: "blur" }
|
|
161
186
|
],
|
|
162
|
-
code: [{ required: true, message: "
|
|
163
|
-
};
|
|
187
|
+
code: [{ required: true, message: t("system.profile.security.placeholder.code"), trigger: "blur" }]
|
|
188
|
+
}));
|
|
164
189
|
|
|
165
190
|
/** 根据当前绑定状态输出手机号说明文案。 */
|
|
166
191
|
const mobileTip = computed(() => {
|
|
167
|
-
return props.profile.phone
|
|
192
|
+
return props.profile.phone
|
|
193
|
+
? t("system.profile.security.message.phoneBound", { phone: props.profile.phone })
|
|
194
|
+
: t("system.profile.security.message.phoneUnbound");
|
|
168
195
|
});
|
|
169
196
|
|
|
170
197
|
/** 根据关键资料估算当前资料完成度。 */
|
|
@@ -189,6 +216,11 @@ async function loadOauthBindings() {
|
|
|
189
216
|
oauthBindings.value = result.bindings.map(withOauthProviderDisplay);
|
|
190
217
|
}
|
|
191
218
|
|
|
219
|
+
/** 根据当前语言返回三方登录方式名称。 */
|
|
220
|
+
function oauthName(binding: SecurityOauthBinding) {
|
|
221
|
+
return binding.nameKey.includes(".") ? t(binding.nameKey) : binding.nameKey;
|
|
222
|
+
}
|
|
223
|
+
|
|
192
224
|
/** 处理 OAuth 绑定回跳结果。 */
|
|
193
225
|
async function consumeOauthBindingResult() {
|
|
194
226
|
const bindError = route.query.oauth_bind_error;
|
|
@@ -196,7 +228,7 @@ async function consumeOauthBindingResult() {
|
|
|
196
228
|
if (typeof bindError === "string" && bindError) {
|
|
197
229
|
ElMessage.error(bindError);
|
|
198
230
|
} else if (bindSuccess === "1") {
|
|
199
|
-
ElMessage.success("
|
|
231
|
+
ElMessage.success(t("system.profile.security.message.oauthBindSuccess"));
|
|
200
232
|
} else {
|
|
201
233
|
return;
|
|
202
234
|
}
|
|
@@ -230,15 +262,19 @@ async function handleBindOauth(binding: SecurityOauthBinding) {
|
|
|
230
262
|
|
|
231
263
|
/** 解绑三方账号并刷新绑定状态。 */
|
|
232
264
|
async function handleUnbindOauth(binding: SecurityOauthBinding) {
|
|
233
|
-
await ElMessageBox.confirm(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
265
|
+
await ElMessageBox.confirm(
|
|
266
|
+
t("system.profile.security.dialog.confirmUnbind", { provider: oauthName(binding) }),
|
|
267
|
+
t("common.title.warning"),
|
|
268
|
+
{
|
|
269
|
+
type: "warning",
|
|
270
|
+
confirmButtonText: t("system.profile.security.action.unbind"),
|
|
271
|
+
cancelButtonText: t("common.action.cancel")
|
|
272
|
+
}
|
|
273
|
+
);
|
|
238
274
|
oauthLoadingProvider.value = binding.provider;
|
|
239
275
|
try {
|
|
240
276
|
await defProfileOauthService.UnbindOauthAccount({ provider: binding.provider });
|
|
241
|
-
ElMessage.success("
|
|
277
|
+
ElMessage.success(t("system.profile.security.message.oauthUnbindSuccess"));
|
|
242
278
|
await loadOauthBindings();
|
|
243
279
|
} finally {
|
|
244
280
|
oauthLoadingProvider.value = "";
|
|
@@ -255,17 +291,17 @@ function openPhoneDialog() {
|
|
|
255
291
|
/** 发送手机验证码并启动倒计时。 */
|
|
256
292
|
async function handleSendCode() {
|
|
257
293
|
if (!phoneForm.phone) {
|
|
258
|
-
ElMessage.error("
|
|
294
|
+
ElMessage.error(t("system.profile.security.placeholder.phone"));
|
|
259
295
|
return;
|
|
260
296
|
}
|
|
261
297
|
if (!/^1[3-9]\d{9}$/.test(phoneForm.phone)) {
|
|
262
|
-
ElMessage.error("
|
|
298
|
+
ElMessage.error(t("system.profile.security.validation.phone"));
|
|
263
299
|
return;
|
|
264
300
|
}
|
|
265
301
|
|
|
266
302
|
sendPhoneCodeForm.phone = phoneForm.phone;
|
|
267
303
|
await defProfileAuthService.SendPhoneCode(sendPhoneCodeForm);
|
|
268
|
-
ElMessage.success("
|
|
304
|
+
ElMessage.success(t("system.profile.security.message.codeSent"));
|
|
269
305
|
startCountdown();
|
|
270
306
|
}
|
|
271
307
|
|
|
@@ -276,7 +312,7 @@ async function handleSubmitPhone() {
|
|
|
276
312
|
submitLoading.value = true;
|
|
277
313
|
try {
|
|
278
314
|
await defProfileAuthService.UpdateUserPhone({ user_phone: phoneForm });
|
|
279
|
-
ElMessage.success("
|
|
315
|
+
ElMessage.success(t("system.profile.security.message.phoneUpdated"));
|
|
280
316
|
phoneDialogVisible.value = false;
|
|
281
317
|
emit("refreshed");
|
|
282
318
|
} finally {
|
|
@@ -38,8 +38,9 @@ defineOptions({
|
|
|
38
38
|
inheritAttrs: false
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
import { onMounted, reactive, ref } from "vue";
|
|
41
|
+
import { computed, onMounted, reactive, ref } from "vue";
|
|
42
42
|
import { useRoute } from "vue-router";
|
|
43
|
+
import { t } from "@liujitcn/kratos-admin-core";
|
|
43
44
|
import { defProfileAuthService } from "@liujitcn/kratos-admin-system/api/system/auth";
|
|
44
45
|
import type { UserProfileForm } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/auth";
|
|
45
46
|
import { useUserStore } from "@liujitcn/kratos-admin-core/stores/runtime";
|
|
@@ -64,23 +65,23 @@ interface ProfileTabOption {
|
|
|
64
65
|
const userStore = useUserStore();
|
|
65
66
|
const route = useRoute();
|
|
66
67
|
const activeTab = ref<ProfileTab>("account");
|
|
67
|
-
const profileTabs
|
|
68
|
+
const profileTabs = computed<ProfileTabOption[]>(() => [
|
|
68
69
|
{
|
|
69
70
|
value: "account",
|
|
70
|
-
label: "
|
|
71
|
-
description: "
|
|
71
|
+
label: t("system.profile.account.title"),
|
|
72
|
+
description: t("system.profile.account.navDescription")
|
|
72
73
|
},
|
|
73
74
|
{
|
|
74
75
|
value: "security",
|
|
75
|
-
label: "
|
|
76
|
-
description: "
|
|
76
|
+
label: t("system.profile.security.title"),
|
|
77
|
+
description: t("system.profile.security.navDescription")
|
|
77
78
|
},
|
|
78
79
|
{
|
|
79
80
|
value: "password",
|
|
80
|
-
label: "
|
|
81
|
-
description: "
|
|
81
|
+
label: t("system.profile.password.title"),
|
|
82
|
+
description: t("system.profile.password.navDescription")
|
|
82
83
|
}
|
|
83
|
-
];
|
|
84
|
+
]);
|
|
84
85
|
const userProfileForm = reactive<UserProfileForm>({
|
|
85
86
|
user_name: "",
|
|
86
87
|
nick_name: "",
|