@liujitcn/kratos-admin-core 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 +7 -6
- package/build/auto-imports.json +73 -0
- package/build/plugins.ts +2 -58
- package/dist/package/declarations/src/api/base/mfa.d.ts +26 -0
- package/dist/package/declarations/src/components/Dialog/FormDialog.vue.d.ts +3 -3
- package/dist/package/declarations/src/components/Dialog/PasswordVerifyDialog.vue.d.ts +65 -0
- package/dist/package/declarations/src/components/Dialog/ProDialog.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Grid/components/GridItem.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Mfa/MfaRecoveryCodesDialog.vue.d.ts +19 -0
- package/dist/package/declarations/src/components/Mfa/MfaSetupPanel.vue.d.ts +8 -0
- package/dist/package/declarations/src/components/ProForm/components/ProFormItem.vue.d.ts +4 -4
- package/dist/package/declarations/src/components/ProForm/index.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/ProForm/interface/index.d.ts +4 -1
- package/dist/package/declarations/src/components/RichTextPreview/index.vue.d.ts +8 -0
- package/dist/package/declarations/src/components/Upload/Img.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Upload/Imgs.vue.d.ts +1 -1
- package/dist/package/declarations/src/layouts/components/LockScreen.vue.d.ts +3 -0
- package/dist/package/declarations/src/locales/generated.d.ts +168 -0
- package/dist/package/declarations/src/locales/index.d.ts +2 -0
- package/dist/package/declarations/src/rpc/base/v1/login.d.ts +33 -3
- package/dist/package/declarations/src/rpc/base/v1/mfa.d.ts +152 -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_dict.d.ts +4 -4
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +27 -23
- package/dist/package/declarations/src/security.d.ts +2 -0
- package/dist/package/declarations/src/stores/interface/index.d.ts +14 -1
- package/dist/package/declarations/src/stores/modules/lockScreen.d.ts +20 -0
- package/dist/package/declarations/src/stores/modules/user.d.ts +5 -2
- package/dist/package/declarations/src/stores/runtime.d.ts +1 -0
- package/dist/package/declarations/src/utils/clipboard.d.ts +2 -0
- package/dist/package/declarations/src/utils/webauthn.d.ts +4 -0
- package/dist/package/declarations/vite.config.d.ts +2 -0
- package/dist/package/src/api/base/mfa.ts +114 -0
- package/dist/package/src/components/Dialog/PasswordVerifyDialog.vue +176 -0
- package/dist/package/src/components/Loading/fullScreen.ts +2 -1
- package/dist/package/src/components/MarkdownPreview/index.vue +21 -1
- package/dist/package/src/components/Mfa/MfaRecoveryCodesDialog.vue +91 -0
- package/dist/package/src/components/Mfa/MfaSetupPanel.vue +81 -0
- package/dist/package/src/components/ProForm/components/ProFormItem.vue +16 -1
- package/dist/package/src/components/ProForm/interface/index.ts +4 -0
- package/dist/package/src/components/ProTable/components/ColSetting.vue +1 -1
- package/dist/package/src/components/ProTable/index.vue +6 -2
- package/dist/package/src/components/RichTextPreview/index.vue +45 -0
- package/dist/package/src/config/index.ts +1 -1
- package/dist/package/src/layouts/LayoutClassic/index.vue +3 -1
- package/dist/package/src/layouts/LayoutColumns/index.vue +3 -1
- package/dist/package/src/layouts/LayoutTransverse/index.vue +3 -1
- package/dist/package/src/layouts/LayoutVertical/index.vue +9 -1
- package/dist/package/src/layouts/components/Header/components/Avatar.vue +23 -4
- package/dist/package/src/layouts/components/Header/components/InfoDialog.vue +1 -1
- package/dist/package/src/layouts/components/Header/components/PasswordDialog.vue +1 -1
- package/dist/package/src/layouts/components/LockScreen.vue +450 -0
- package/dist/package/src/layouts/index.vue +2 -0
- package/dist/package/src/layouts/indexAsync.vue +2 -0
- package/dist/package/src/locales/README.md +2 -1
- package/dist/package/src/locales/en-US.json +43 -1
- package/dist/package/src/locales/index.ts +16 -12
- package/dist/package/src/locales/ja-JP.json +43 -1
- package/dist/package/src/locales/zh-CN.json +43 -1
- package/dist/package/src/locales/zh-TW.json +43 -1
- package/dist/package/src/rpc/base/v1/login.ts +33 -2
- package/dist/package/src/rpc/base/v1/mfa.ts +180 -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_dict.ts +4 -4
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +27 -23
- package/dist/package/src/security.ts +2 -0
- package/dist/package/src/stores/interface/index.ts +15 -1
- package/dist/package/src/stores/modules/config.ts +4 -4
- package/dist/package/src/stores/modules/lockScreen.ts +78 -0
- package/dist/package/src/stores/modules/user.ts +16 -3
- package/dist/package/src/stores/runtime.ts +1 -0
- package/dist/package/src/utils/clipboard.ts +22 -0
- package/dist/package/src/utils/request.ts +26 -10
- package/dist/package/src/utils/webauthn.ts +90 -0
- package/dist/package/src/views/login/components/LoginForm.vue +237 -16
- package/dist/package/src/views/login/index.vue +3 -1
- package/dist/package/types/generated/auto-imports.d.ts +132 -129
- package/dist/package/types/generated/components.d.ts +4 -72
- package/package.json +22 -1
- package/types/generated/auto-imports.d.ts +132 -129
- package/types/generated/components.d.ts +4 -72
- package/vite.config.ts +3 -1
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="lockScreenStore.setupVisible" class="lock-setup-mask" role="presentation">
|
|
3
|
+
<section class="lock-setup-dialog" role="dialog" aria-modal="true" :aria-label="t('core.layout.lock_screen')">
|
|
4
|
+
<header class="lock-dialog-header">
|
|
5
|
+
<h2>{{ t("core.layout.lock_screen") }}</h2>
|
|
6
|
+
<button class="lock-icon-button" type="button" :aria-label="t('common.action.close')" @click="closeSetup">
|
|
7
|
+
<el-icon><Close /></el-icon>
|
|
8
|
+
</button>
|
|
9
|
+
</header>
|
|
10
|
+
<div class="lock-dialog-body">
|
|
11
|
+
<div class="lock-avatar lock-avatar--setup">
|
|
12
|
+
<img :src="avatarSrc" :alt="t('core.layout.avatar')" @error="handleAvatarError" />
|
|
13
|
+
</div>
|
|
14
|
+
<div class="lock-user-name">{{ displayName }}</div>
|
|
15
|
+
<form class="lock-form" @submit.prevent="handleLock">
|
|
16
|
+
<el-input
|
|
17
|
+
v-model="setupPassword"
|
|
18
|
+
class="lock-input"
|
|
19
|
+
type="password"
|
|
20
|
+
size="large"
|
|
21
|
+
show-password
|
|
22
|
+
autocomplete="new-password"
|
|
23
|
+
:placeholder="t('core.layout.lock_screen_password_placeholder')"
|
|
24
|
+
:aria-label="t('core.layout.lock_screen_password_placeholder')"
|
|
25
|
+
/>
|
|
26
|
+
<p v-if="setupError" class="lock-form-error">{{ setupError }}</p>
|
|
27
|
+
<el-button class="lock-submit" type="primary" size="large" native-type="submit" :loading="settingLock">
|
|
28
|
+
{{ t("core.layout.lock_screen_action") }}
|
|
29
|
+
</el-button>
|
|
30
|
+
</form>
|
|
31
|
+
</div>
|
|
32
|
+
</section>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div v-if="lockScreenStore.isLocked" class="lock-screen" role="dialog" aria-modal="true">
|
|
36
|
+
<div class="lock-screen__trigger-wrap">
|
|
37
|
+
<button
|
|
38
|
+
v-if="!lockScreenStore.unlockVisible"
|
|
39
|
+
class="lock-screen__trigger"
|
|
40
|
+
type="button"
|
|
41
|
+
@click="openUnlock"
|
|
42
|
+
>
|
|
43
|
+
<el-icon :size="26"><Lock /></el-icon>
|
|
44
|
+
<span>{{ t("core.layout.lock_screen_unlock_hint") }}</span>
|
|
45
|
+
</button>
|
|
46
|
+
<section v-else class="unlock-panel" :aria-label="t('core.layout.lock_screen')">
|
|
47
|
+
<div class="lock-avatar lock-avatar--unlock">
|
|
48
|
+
<img :src="avatarSrc" :alt="t('core.layout.avatar')" @error="handleAvatarError" />
|
|
49
|
+
</div>
|
|
50
|
+
<div class="lock-user-name">{{ displayName }}</div>
|
|
51
|
+
<form class="lock-form" @submit.prevent="handleUnlock">
|
|
52
|
+
<el-input
|
|
53
|
+
ref="unlockInputRef"
|
|
54
|
+
v-model="unlockPassword"
|
|
55
|
+
class="lock-input"
|
|
56
|
+
type="password"
|
|
57
|
+
size="large"
|
|
58
|
+
show-password
|
|
59
|
+
autofocus
|
|
60
|
+
autocomplete="current-password"
|
|
61
|
+
:placeholder="t('core.layout.lock_screen_password_placeholder')"
|
|
62
|
+
:aria-label="t('core.layout.lock_screen_password_placeholder')"
|
|
63
|
+
/>
|
|
64
|
+
<p v-if="lockScreenStore.unlockError" class="lock-form-error">{{ t("core.layout.lock_screen_password_invalid") }}</p>
|
|
65
|
+
<el-button class="lock-submit" type="primary" size="large" native-type="submit" :loading="unlocking">
|
|
66
|
+
{{ t("core.layout.lock_screen_enter") }}
|
|
67
|
+
</el-button>
|
|
68
|
+
</form>
|
|
69
|
+
<button class="lock-text-button" type="button" :disabled="loggingOut" @click="returnToLogin">
|
|
70
|
+
<el-icon><SwitchButton /></el-icon>
|
|
71
|
+
<span>{{ t("core.layout.lock_screen_return_login") }}</span>
|
|
72
|
+
</button>
|
|
73
|
+
<button class="lock-text-button" type="button" @click="closeUnlock">
|
|
74
|
+
<el-icon><ArrowLeft /></el-icon>
|
|
75
|
+
<span>{{ t("common.action.back") }}</span>
|
|
76
|
+
</button>
|
|
77
|
+
</section>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="lock-screen__clock" aria-live="polite">
|
|
80
|
+
<time class="lock-screen__time">{{ timeText }}</time>
|
|
81
|
+
<time class="lock-screen__date">{{ dateText }}</time>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
85
|
+
|
|
86
|
+
<script setup lang="ts" name="LockScreen">
|
|
87
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
88
|
+
import { useRouter } from "vue-router";
|
|
89
|
+
import { LOGIN_URL } from "../../config";
|
|
90
|
+
import defaultAvatar from "../../assets/images/avatar.png";
|
|
91
|
+
import { useLocaleStore } from "../../locales";
|
|
92
|
+
import { useLockScreenStore } from "../../stores/modules/lockScreen";
|
|
93
|
+
import { useUserStore } from "../../stores/modules/user";
|
|
94
|
+
|
|
95
|
+
const router = useRouter();
|
|
96
|
+
const userStore = useUserStore();
|
|
97
|
+
const lockScreenStore = useLockScreenStore();
|
|
98
|
+
const { locale, t } = useLocaleStore();
|
|
99
|
+
const avatarSrc = ref(defaultAvatar);
|
|
100
|
+
const setupPassword = ref("");
|
|
101
|
+
const unlockPassword = ref("");
|
|
102
|
+
const setupError = ref("");
|
|
103
|
+
const settingLock = ref(false);
|
|
104
|
+
const unlocking = ref(false);
|
|
105
|
+
const loggingOut = ref(false);
|
|
106
|
+
const now = ref(new Date());
|
|
107
|
+
const unlockInputRef = ref<{ focus?: () => void }>();
|
|
108
|
+
let clockTimer: number | undefined;
|
|
109
|
+
|
|
110
|
+
const displayName = computed(() => userStore.userInfo.nick_name || userStore.userInfo.user_name || t("core.layout.not_set"));
|
|
111
|
+
const timeText = computed(() => {
|
|
112
|
+
return new Intl.DateTimeFormat(locale.value, {
|
|
113
|
+
hour: "2-digit",
|
|
114
|
+
minute: "2-digit",
|
|
115
|
+
hour12: false
|
|
116
|
+
}).format(now.value);
|
|
117
|
+
});
|
|
118
|
+
const dateText = computed(() => {
|
|
119
|
+
return new Intl.DateTimeFormat(locale.value, {
|
|
120
|
+
year: "numeric",
|
|
121
|
+
month: "2-digit",
|
|
122
|
+
day: "2-digit",
|
|
123
|
+
weekday: "long"
|
|
124
|
+
}).format(now.value);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
watch(
|
|
128
|
+
() => userStore.userInfo.avatar,
|
|
129
|
+
avatar => {
|
|
130
|
+
avatarSrc.value = avatar || defaultAvatar;
|
|
131
|
+
},
|
|
132
|
+
{ immediate: true }
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
watch(
|
|
136
|
+
() => lockScreenStore.setupVisible,
|
|
137
|
+
visible => {
|
|
138
|
+
if (!visible) return;
|
|
139
|
+
setupPassword.value = "";
|
|
140
|
+
setupError.value = "";
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
watch(
|
|
145
|
+
() => lockScreenStore.isLocked,
|
|
146
|
+
locked => {
|
|
147
|
+
if (locked) {
|
|
148
|
+
unlockPassword.value = "";
|
|
149
|
+
nextTick(() => unlockInputRef.value?.focus?.());
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
onMounted(() => {
|
|
155
|
+
clockTimer = window.setInterval(() => {
|
|
156
|
+
now.value = new Date();
|
|
157
|
+
}, 1000);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
onBeforeUnmount(() => {
|
|
161
|
+
if (clockTimer) window.clearInterval(clockTimer);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/** 关闭设置锁屏密码界面。 */
|
|
165
|
+
const closeSetup = () => {
|
|
166
|
+
lockScreenStore.closeSetup();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** 设置锁屏密码并进入锁屏界面。 */
|
|
170
|
+
const handleLock = async () => {
|
|
171
|
+
if (!setupPassword.value) {
|
|
172
|
+
setupError.value = t("core.layout.lock_screen_password_required");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
settingLock.value = true;
|
|
177
|
+
setupError.value = "";
|
|
178
|
+
try {
|
|
179
|
+
await lockScreenStore.lock(setupPassword.value);
|
|
180
|
+
} finally {
|
|
181
|
+
settingLock.value = false;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** 显示锁屏密码输入界面。 */
|
|
186
|
+
const openUnlock = () => {
|
|
187
|
+
lockScreenStore.openUnlock();
|
|
188
|
+
nextTick(() => unlockInputRef.value?.focus?.());
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/** 校验锁屏密码并返回当前管理页面。 */
|
|
192
|
+
const handleUnlock = async () => {
|
|
193
|
+
if (!unlockPassword.value) {
|
|
194
|
+
lockScreenStore.unlockError = true;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
unlocking.value = true;
|
|
199
|
+
try {
|
|
200
|
+
await lockScreenStore.unlock(unlockPassword.value);
|
|
201
|
+
if (!lockScreenStore.isLocked) unlockPassword.value = "";
|
|
202
|
+
} finally {
|
|
203
|
+
unlocking.value = false;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/** 返回登录页并清理当前登录态。 */
|
|
208
|
+
const returnToLogin = async () => {
|
|
209
|
+
if (loggingOut.value) return;
|
|
210
|
+
|
|
211
|
+
loggingOut.value = true;
|
|
212
|
+
try {
|
|
213
|
+
await userStore.logout();
|
|
214
|
+
await router.replace(LOGIN_URL);
|
|
215
|
+
} finally {
|
|
216
|
+
loggingOut.value = false;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/** 锁屏首页不再显示密码输入。 */
|
|
221
|
+
const closeUnlock = () => {
|
|
222
|
+
lockScreenStore.closeUnlock();
|
|
223
|
+
unlockPassword.value = "";
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/** 头像加载失败时回退到本地默认头像。 */
|
|
227
|
+
const handleAvatarError = () => {
|
|
228
|
+
avatarSrc.value = defaultAvatar;
|
|
229
|
+
};
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<style scoped lang="scss">
|
|
233
|
+
.lock-setup-mask {
|
|
234
|
+
position: fixed;
|
|
235
|
+
inset: 0;
|
|
236
|
+
z-index: 4100;
|
|
237
|
+
display: grid;
|
|
238
|
+
place-items: center;
|
|
239
|
+
padding: 20px;
|
|
240
|
+
background: rgb(0 0 0 / 60%);
|
|
241
|
+
}
|
|
242
|
+
.lock-setup-dialog {
|
|
243
|
+
width: min(720px, 100%);
|
|
244
|
+
overflow: hidden;
|
|
245
|
+
color: var(--el-text-color-primary);
|
|
246
|
+
background: var(--el-bg-color-overlay);
|
|
247
|
+
border: 1px solid var(--el-border-color);
|
|
248
|
+
border-radius: 16px;
|
|
249
|
+
box-shadow: 0 24px 80px rgb(0 0 0 / 28%);
|
|
250
|
+
}
|
|
251
|
+
.lock-dialog-header {
|
|
252
|
+
display: flex;
|
|
253
|
+
align-items: center;
|
|
254
|
+
justify-content: space-between;
|
|
255
|
+
padding: 26px 32px;
|
|
256
|
+
border-bottom: 1px solid var(--el-border-color-light);
|
|
257
|
+
h2 {
|
|
258
|
+
margin: 0;
|
|
259
|
+
font-size: 28px;
|
|
260
|
+
line-height: 1.25;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
.lock-icon-button {
|
|
264
|
+
display: inline-grid;
|
|
265
|
+
place-items: center;
|
|
266
|
+
width: 40px;
|
|
267
|
+
height: 40px;
|
|
268
|
+
padding: 0;
|
|
269
|
+
color: var(--el-text-color-secondary);
|
|
270
|
+
cursor: pointer;
|
|
271
|
+
background: transparent;
|
|
272
|
+
border: 0;
|
|
273
|
+
border-radius: 50%;
|
|
274
|
+
&:hover {
|
|
275
|
+
color: var(--el-text-color-primary);
|
|
276
|
+
background: var(--el-fill-color-light);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
.lock-dialog-body {
|
|
280
|
+
display: flex;
|
|
281
|
+
flex-direction: column;
|
|
282
|
+
align-items: center;
|
|
283
|
+
padding: 36px 32px 44px;
|
|
284
|
+
}
|
|
285
|
+
.lock-avatar {
|
|
286
|
+
overflow: hidden;
|
|
287
|
+
background: var(--el-fill-color-light);
|
|
288
|
+
border: 1px solid var(--el-border-color-light);
|
|
289
|
+
border-radius: 50%;
|
|
290
|
+
img {
|
|
291
|
+
display: block;
|
|
292
|
+
width: 100%;
|
|
293
|
+
height: 100%;
|
|
294
|
+
object-fit: cover;
|
|
295
|
+
}
|
|
296
|
+
&--setup {
|
|
297
|
+
width: 168px;
|
|
298
|
+
height: 168px;
|
|
299
|
+
}
|
|
300
|
+
&--unlock {
|
|
301
|
+
width: 132px;
|
|
302
|
+
height: 132px;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
.lock-user-name {
|
|
306
|
+
margin-top: 20px;
|
|
307
|
+
font-size: 24px;
|
|
308
|
+
font-weight: 600;
|
|
309
|
+
line-height: 1.35;
|
|
310
|
+
color: var(--el-text-color-primary);
|
|
311
|
+
}
|
|
312
|
+
.lock-form {
|
|
313
|
+
width: min(100%, 560px);
|
|
314
|
+
margin-top: 34px;
|
|
315
|
+
}
|
|
316
|
+
.lock-input {
|
|
317
|
+
:deep(.el-input__wrapper) {
|
|
318
|
+
min-height: 58px;
|
|
319
|
+
padding: 0 18px;
|
|
320
|
+
background: transparent;
|
|
321
|
+
border: 1px solid var(--el-border-color);
|
|
322
|
+
border-radius: 12px;
|
|
323
|
+
box-shadow: none;
|
|
324
|
+
}
|
|
325
|
+
:deep(.el-input__inner) {
|
|
326
|
+
font-size: 18px;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
.lock-form-error {
|
|
330
|
+
margin: 10px 2px 0;
|
|
331
|
+
font-size: 13px;
|
|
332
|
+
line-height: 1.4;
|
|
333
|
+
color: var(--el-color-danger);
|
|
334
|
+
}
|
|
335
|
+
.lock-submit {
|
|
336
|
+
width: 100%;
|
|
337
|
+
min-height: 56px;
|
|
338
|
+
margin-top: 28px;
|
|
339
|
+
border-radius: 10px;
|
|
340
|
+
}
|
|
341
|
+
.lock-screen {
|
|
342
|
+
position: fixed;
|
|
343
|
+
inset: 0;
|
|
344
|
+
z-index: 4000;
|
|
345
|
+
display: flex;
|
|
346
|
+
justify-content: center;
|
|
347
|
+
color: var(--el-text-color-primary);
|
|
348
|
+
background: var(--el-bg-color-page);
|
|
349
|
+
}
|
|
350
|
+
.lock-screen__trigger-wrap {
|
|
351
|
+
display: flex;
|
|
352
|
+
align-items: flex-start;
|
|
353
|
+
justify-content: center;
|
|
354
|
+
width: 100%;
|
|
355
|
+
padding-top: clamp(28px, 7vh, 72px);
|
|
356
|
+
}
|
|
357
|
+
.lock-screen__trigger {
|
|
358
|
+
display: inline-flex;
|
|
359
|
+
flex-direction: column;
|
|
360
|
+
gap: 6px;
|
|
361
|
+
align-items: center;
|
|
362
|
+
padding: 10px 18px;
|
|
363
|
+
font-size: 16px;
|
|
364
|
+
font-weight: 600;
|
|
365
|
+
color: var(--el-text-color-secondary);
|
|
366
|
+
cursor: pointer;
|
|
367
|
+
background: transparent;
|
|
368
|
+
border: 0;
|
|
369
|
+
border-radius: 10px;
|
|
370
|
+
&:hover,
|
|
371
|
+
&:focus-visible {
|
|
372
|
+
color: var(--el-text-color-primary);
|
|
373
|
+
outline: none;
|
|
374
|
+
background: var(--el-fill-color-light);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
.unlock-panel {
|
|
378
|
+
display: flex;
|
|
379
|
+
flex-direction: column;
|
|
380
|
+
align-items: center;
|
|
381
|
+
width: min(520px, calc(100vw - 32px));
|
|
382
|
+
padding: 24px;
|
|
383
|
+
}
|
|
384
|
+
.unlock-panel .lock-form {
|
|
385
|
+
margin-top: 28px;
|
|
386
|
+
}
|
|
387
|
+
.lock-text-button {
|
|
388
|
+
display: inline-flex;
|
|
389
|
+
gap: 7px;
|
|
390
|
+
align-items: center;
|
|
391
|
+
padding: 4px 8px;
|
|
392
|
+
margin-top: 24px;
|
|
393
|
+
font-size: 15px;
|
|
394
|
+
color: var(--el-text-color-secondary);
|
|
395
|
+
cursor: pointer;
|
|
396
|
+
background: transparent;
|
|
397
|
+
border: 0;
|
|
398
|
+
border-radius: 6px;
|
|
399
|
+
&:hover,
|
|
400
|
+
&:focus-visible {
|
|
401
|
+
color: var(--el-text-color-primary);
|
|
402
|
+
outline: none;
|
|
403
|
+
background: var(--el-fill-color-light);
|
|
404
|
+
}
|
|
405
|
+
&:disabled {
|
|
406
|
+
cursor: wait;
|
|
407
|
+
opacity: 0.6;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
.lock-screen__clock {
|
|
411
|
+
position: absolute;
|
|
412
|
+
right: 24px;
|
|
413
|
+
bottom: clamp(22px, 6vh, 64px);
|
|
414
|
+
left: 24px;
|
|
415
|
+
display: flex;
|
|
416
|
+
flex-direction: column;
|
|
417
|
+
align-items: center;
|
|
418
|
+
pointer-events: none;
|
|
419
|
+
}
|
|
420
|
+
.lock-screen__time {
|
|
421
|
+
font-size: clamp(44px, 8vw, 88px);
|
|
422
|
+
font-weight: 300;
|
|
423
|
+
line-height: 1;
|
|
424
|
+
}
|
|
425
|
+
.lock-screen__date {
|
|
426
|
+
margin-top: 14px;
|
|
427
|
+
font-size: clamp(17px, 2.4vw, 26px);
|
|
428
|
+
line-height: 1.35;
|
|
429
|
+
color: var(--el-text-color-secondary);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
@media (width <= 600px) {
|
|
433
|
+
.lock-dialog-header {
|
|
434
|
+
padding: 20px 22px;
|
|
435
|
+
h2 {
|
|
436
|
+
font-size: 24px;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
.lock-dialog-body {
|
|
440
|
+
padding: 28px 20px 32px;
|
|
441
|
+
}
|
|
442
|
+
.lock-avatar--setup {
|
|
443
|
+
width: 132px;
|
|
444
|
+
height: 132px;
|
|
445
|
+
}
|
|
446
|
+
.lock-screen__clock {
|
|
447
|
+
bottom: 28px;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
</style>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<el-watermark id="watermark" :font="font" :content="watermarkContent">
|
|
4
4
|
<component :is="LayoutComponents[layout]" />
|
|
5
5
|
<ThemeDrawer />
|
|
6
|
+
<LockScreen />
|
|
6
7
|
</el-watermark>
|
|
7
8
|
</template>
|
|
8
9
|
|
|
@@ -12,6 +13,7 @@ import { LayoutType } from "../stores/interface";
|
|
|
12
13
|
import { useGlobalStore } from "../stores/modules/global";
|
|
13
14
|
import { useConfigStore } from "../stores/modules/config";
|
|
14
15
|
import ThemeDrawer from "./components/ThemeDrawer/index.vue";
|
|
16
|
+
import LockScreen from "./components/LockScreen.vue";
|
|
15
17
|
import LayoutVertical from "./LayoutVertical/index.vue";
|
|
16
18
|
import LayoutClassic from "./LayoutClassic/index.vue";
|
|
17
19
|
import LayoutTransverse from "./LayoutTransverse/index.vue";
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
</template>
|
|
11
11
|
</suspense>
|
|
12
12
|
<ThemeDrawer />
|
|
13
|
+
<LockScreen />
|
|
13
14
|
</el-watermark>
|
|
14
15
|
</template>
|
|
15
16
|
|
|
@@ -20,6 +21,7 @@ import { useGlobalStore } from "../stores/modules/global";
|
|
|
20
21
|
import { useConfigStore } from "../stores/modules/config";
|
|
21
22
|
import Loading from "../components/Loading/index.vue";
|
|
22
23
|
import ThemeDrawer from "./components/ThemeDrawer/index.vue";
|
|
24
|
+
import LockScreen from "./components/LockScreen.vue";
|
|
23
25
|
|
|
24
26
|
const LayoutComponents: Record<LayoutType, Component> = {
|
|
25
27
|
vertical: defineAsyncComponent(() => import("./LayoutVertical/index.vue")),
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# 管理端 Core 语言包
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本目录提供管理端底座和公共组件使用的稳定文案,包括通用操作、字段、状态、登录认证、MFA/WebAuthn、上传、校验和错误提示。语言包由 `../modules/kratosAdmin.ts` 导入,作为 `kratos-admin` 模块注册到 `packages/core/src/locales/index.ts`,最终由 `bootstrap.ts` 合并到 Vue I18n;core 内组件、hooks、请求错误处理和登录页通过 `t()` 或 `useLocaleStore()` 使用。
|
|
4
4
|
|
|
5
5
|
| 文件 | 用途 | 使用位置 |
|
|
6
6
|
| --- | --- | --- |
|
|
7
7
|
| `zh-CN.json` | 简体中文固定 UI 默认语言包,也是所有语言键集合和占位符校验基准。 | 管理端固定文案默认语言及缺失翻译回退。 |
|
|
8
8
|
| `zh-TW.json` | 繁体中文公共语言包。 | 管理端切换为繁体中文时由 Vue I18n 使用。 |
|
|
9
9
|
| `en-US.json` | 英文公共语言包。 | 管理端切换为英文时由 Vue I18n 使用。 |
|
|
10
|
+
| `ja-JP.json` | 日文公共语言包。 | 管理端切换为日文时由 Vue I18n 使用。 |
|
|
10
11
|
|
|
11
12
|
语言包是标准 JSON,不能在文件内直接写注释;新增语言或修改键后执行仓库根目录的 `make i18n-sync`,脚本会校验所有语言并生成注册产物。语言切换选项固定显示后端 `base_language.native_name`,接口不可用时使用代码内置的原生名称;`common.language.*` 仅用于普通文案和语言迁移初始数据。新增语言的完整文件清单见[国际化语言扩展指南](../../../../../../docs/国际化语言扩展指南.md)。
|
|
@@ -47,6 +47,15 @@
|
|
|
47
47
|
"core.auth.no_menu_permission": "This account has no menu permissions. Contact an administrator.",
|
|
48
48
|
"core.auth.no_permission": "Access denied",
|
|
49
49
|
"core.clipboard.success": "Copied",
|
|
50
|
+
"core.markdown.action.copy_code": "Copy code",
|
|
51
|
+
"core.markdown.message.copy_success": "Copied!",
|
|
52
|
+
"core.markdown.message.copy_failed": "Copy failed!",
|
|
53
|
+
"core.loading.text": "Loading",
|
|
54
|
+
"core.layout.avatar": "User avatar",
|
|
55
|
+
"core.layout.logo_alt": "System logo",
|
|
56
|
+
"core.login.background_alt": "Login background",
|
|
57
|
+
"core.layout.info_content": "View the current user's information",
|
|
58
|
+
"core.layout.password_content": "Change the current user's password",
|
|
50
59
|
"core.cron.cycle_unit.day": "days",
|
|
51
60
|
"core.cron.cycle_unit.hour": "hours",
|
|
52
61
|
"core.cron.cycle_unit.minute": "minutes",
|
|
@@ -135,6 +144,14 @@
|
|
|
135
144
|
"core.layout.info": "Personal information",
|
|
136
145
|
"core.layout.password_change": "Change password",
|
|
137
146
|
"core.layout.unnamed_menu": "Unnamed menu",
|
|
147
|
+
"core.layout.lock_screen": "Lock screen",
|
|
148
|
+
"core.layout.lock_screen_action": "Lock",
|
|
149
|
+
"core.layout.lock_screen_password_placeholder": "Enter a lock screen password",
|
|
150
|
+
"core.layout.lock_screen_password_required": "Enter a lock screen password",
|
|
151
|
+
"core.layout.lock_screen_password_invalid": "Incorrect lock screen password",
|
|
152
|
+
"core.layout.lock_screen_unlock_hint": "Click to unlock",
|
|
153
|
+
"core.layout.lock_screen_enter": "Enter system",
|
|
154
|
+
"core.layout.lock_screen_return_login": "Return to login",
|
|
138
155
|
"core.layout.logout_confirm": "Are you sure you want to log out?",
|
|
139
156
|
"core.layout.logout_success": "Logged out",
|
|
140
157
|
"core.layout.not_set": "Not set",
|
|
@@ -155,6 +172,25 @@
|
|
|
155
172
|
"core.login.tenant_code": "Enter tenant code",
|
|
156
173
|
"core.login.user_name": "Enter username",
|
|
157
174
|
"core.login.welcome": "Welcome to the admin console",
|
|
175
|
+
"core.login.mfa_title": "Multi-factor authentication",
|
|
176
|
+
"core.login.mfa_code": "Enter the authenticator code",
|
|
177
|
+
"core.login.mfa_recovery_code": "Recovery code (optional)",
|
|
178
|
+
"core.login.mfa_setup_title": "Bind multi-factor authentication",
|
|
179
|
+
"core.login.mfa_setup_qr_alt": "Multi-factor authentication binding QR code",
|
|
180
|
+
"core.login.mfa_uri_copied": "Binding address copied",
|
|
181
|
+
"core.login.mfa_show_uri": "Show backup binding address",
|
|
182
|
+
"core.login.mfa_copy_uri": "Copy binding address",
|
|
183
|
+
"core.login.mfa_recovery_codes_title": "Save MFA recovery codes",
|
|
184
|
+
"core.login.mfa_recovery_codes_warning": "Each code works once and will not be shown again after this dialog closes.",
|
|
185
|
+
"core.login.mfa_recovery_codes_copied": "Recovery codes copied",
|
|
186
|
+
"core.login.mfa_copy_recovery_codes": "Copy recovery codes",
|
|
187
|
+
"core.login.mfa_recovery_codes_confirm": "I saved them",
|
|
188
|
+
"core.login.mfa_setup_success": "Multi-factor authentication enabled. Please sign in again.",
|
|
189
|
+
"core.login.mfa_webauthn_action": "Use Passkey",
|
|
190
|
+
"core.login.mfa_webauthn_setup_unsupported": "WebAuthn is not supported on this platform. Use TOTP or a recovery code.",
|
|
191
|
+
"core.login.mfa_webauthn_setup_invalid": "WebAuthn registration did not return a valid credential.",
|
|
192
|
+
"core.login.mfa_webauthn_login_unsupported": "WebAuthn is not supported on this platform. Use a recovery code.",
|
|
193
|
+
"core.login.mfa_webauthn_login_invalid": "WebAuthn authentication did not return a valid credential.",
|
|
158
194
|
"core.oauth.dingtalk": "DingTalk",
|
|
159
195
|
"core.oauth.feishu": "Feishu",
|
|
160
196
|
"core.oauth.wechat": "WeChat",
|
|
@@ -164,6 +200,8 @@
|
|
|
164
200
|
"core.password.error": "Password must contain uppercase and lowercase letters, a number, and a special character, and be at least 8 characters long",
|
|
165
201
|
"core.password.crypto_unsupported": "Password encryption is not supported in this environment",
|
|
166
202
|
"core.password.required": "Password is required",
|
|
203
|
+
"core.password.verify_title": "Verify password",
|
|
204
|
+
"core.password.verify_label": "Current password",
|
|
167
205
|
"core.password.strength": "Password strength",
|
|
168
206
|
"core.password.strength.empty": "Not entered",
|
|
169
207
|
"core.password.strength.high": "High",
|
|
@@ -276,5 +314,9 @@
|
|
|
276
314
|
"common.value.yes": "Yes",
|
|
277
315
|
"common.value.no": "No",
|
|
278
316
|
"common.value.unknown": "Unknown",
|
|
279
|
-
"common.value.none": "None"
|
|
317
|
+
"common.value.none": "None",
|
|
318
|
+
"common.value.top_level": "Top level",
|
|
319
|
+
"common.resource.default": "{resource}",
|
|
320
|
+
"common.action.delete_resource": "Delete {resource}",
|
|
321
|
+
"common.action.set_field_status": "Set {field}"
|
|
280
322
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
DAYJS_LOCALE_MAP,
|
|
12
12
|
DEFAULT_LOCALE as GENERATED_DEFAULT_LOCALE,
|
|
13
13
|
SUPPORTED_LOCALES as GENERATED_SUPPORTED_LOCALES,
|
|
14
|
-
type GeneratedLocale
|
|
14
|
+
type GeneratedLocale
|
|
15
15
|
} from "./generated";
|
|
16
16
|
|
|
17
17
|
/** 管理端已打包的语言区域;运行时可切换列表由 base_language 接口决定。 */
|
|
@@ -55,7 +55,9 @@ export function normalizeLocale(value?: string): SupportedLocale {
|
|
|
55
55
|
|
|
56
56
|
/** 将接口或系统语言代码解析为已打包的语言区域。 */
|
|
57
57
|
function parseSupportedLocale(value?: string): SupportedLocale | undefined {
|
|
58
|
-
const normalized = String(value ?? "")
|
|
58
|
+
const normalized = String(value ?? "")
|
|
59
|
+
.replace("_", "-")
|
|
60
|
+
.toLowerCase();
|
|
59
61
|
if (!normalized) return undefined;
|
|
60
62
|
const alias = normalized.startsWith("zh-hk") || normalized.startsWith("zh-mo") ? "zh-tw" : normalized;
|
|
61
63
|
const exact = SUPPORTED_LOCALES.find(locale => locale.toLowerCase() === alias);
|
|
@@ -106,10 +108,10 @@ export function initializeLocale(): SupportedLocale {
|
|
|
106
108
|
export function applyLanguageConfig(response: OptionLanguageResponse): void {
|
|
107
109
|
const options = response.languages.reduce<LocaleOption[]>((items, item) => {
|
|
108
110
|
const languageCode = parseSupportedLocale(item.language_code);
|
|
109
|
-
if (!languageCode || items.some(
|
|
111
|
+
if (!languageCode || items.some(option => option.language_code === languageCode)) return items;
|
|
110
112
|
items.push({
|
|
111
113
|
language_code: languageCode,
|
|
112
|
-
native_name: item.native_name || languageCode
|
|
114
|
+
native_name: item.native_name || languageCode
|
|
113
115
|
});
|
|
114
116
|
return items;
|
|
115
117
|
}, []);
|
|
@@ -127,7 +129,7 @@ export function getLanguageOptions(): LocaleOption[] {
|
|
|
127
129
|
|
|
128
130
|
/** 获取当前可切换的语言区域。 */
|
|
129
131
|
export function getSupportedLocales(): SupportedLocale[] {
|
|
130
|
-
return getLanguageOptions().map(
|
|
132
|
+
return getLanguageOptions().map(item => item.language_code);
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
/** 获取当前规范语言区域。 */
|
|
@@ -161,6 +163,13 @@ export function t(key: string, params: LocaleParams = {}): string {
|
|
|
161
163
|
return String(adminI18n.global.t(key, params));
|
|
162
164
|
}
|
|
163
165
|
|
|
166
|
+
/** 从指定语言包读取固定文案,供第三方组件配置和跨语言兼容判断使用。 */
|
|
167
|
+
export function getLocaleText(locale: SupportedLocale, key: string): string {
|
|
168
|
+
const messages = adminI18n.global.getLocaleMessage(locale) as LocaleMessages;
|
|
169
|
+
const fallbackMessages = adminI18n.global.getLocaleMessage(DEFAULT_LOCALE) as LocaleMessages;
|
|
170
|
+
return messages[key] ?? fallbackMessages[key] ?? "";
|
|
171
|
+
}
|
|
172
|
+
|
|
164
173
|
/** 管理端响应式语言状态。 */
|
|
165
174
|
export function useLocaleStore() {
|
|
166
175
|
return {
|
|
@@ -177,18 +186,13 @@ function getFallbackLanguageOptions(): LocaleOption[] {
|
|
|
177
186
|
return SUPPORTED_LOCALES.map(languageCode => {
|
|
178
187
|
return {
|
|
179
188
|
language_code: languageCode,
|
|
180
|
-
native_name: fallbackNativeLanguageName(languageCode)
|
|
189
|
+
native_name: fallbackNativeLanguageName(languageCode)
|
|
181
190
|
};
|
|
182
191
|
});
|
|
183
192
|
}
|
|
184
193
|
|
|
185
194
|
function fallbackNativeLanguageName(languageCode: SupportedLocale): string {
|
|
186
|
-
return {
|
|
187
|
-
"zh-CN": "简体中文",
|
|
188
|
-
"zh-TW": "繁體中文",
|
|
189
|
-
"en-US": "English",
|
|
190
|
-
"ja-JP": "日本語",
|
|
191
|
-
}[languageCode];
|
|
195
|
+
return getLocaleText(languageCode, `common.language.${languageCode}`) || languageCode;
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
function applyLocale(locale: SupportedLocale): void {
|