@liujitcn/kratos-admin-core 0.0.29 → 0.0.32

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.
Files changed (147) hide show
  1. package/README.md +15 -8
  2. package/build/auto-imports.json +73 -0
  3. package/build/plugins.ts +2 -58
  4. package/dist/package/declarations/src/api/base/{config.d.ts → v1/config.d.ts} +1 -1
  5. package/dist/package/declarations/src/api/base/{file.d.ts → v1/file.d.ts} +3 -1
  6. package/dist/package/declarations/src/api/base/{language.d.ts → v1/language.d.ts} +1 -1
  7. package/dist/package/declarations/src/api/base/{login.d.ts → v1/login.d.ts} +2 -2
  8. package/dist/package/declarations/src/api/base/v1/mfa.d.ts +26 -0
  9. package/dist/package/declarations/src/api/base/{oauth.d.ts → v1/oauth.d.ts} +1 -1
  10. package/dist/package/declarations/src/api/system/{auth.d.ts → admin/v1/auth.d.ts} +2 -2
  11. package/dist/package/declarations/src/api/system/{base_dict.d.ts → admin/v1/base_dict.d.ts} +1 -1
  12. package/dist/package/declarations/src/api/system/{base_tenant.d.ts → admin/v1/base_tenant.d.ts} +2 -2
  13. package/dist/package/declarations/src/components/Dialog/FormDialog.vue.d.ts +3 -3
  14. package/dist/package/declarations/src/components/Dialog/PasswordVerifyDialog.vue.d.ts +65 -0
  15. package/dist/package/declarations/src/components/Dialog/ProDialog.vue.d.ts +1 -1
  16. package/dist/package/declarations/src/components/Grid/components/GridItem.vue.d.ts +1 -1
  17. package/dist/package/declarations/src/components/Mfa/MfaRecoveryCodesDialog.vue.d.ts +19 -0
  18. package/dist/package/declarations/src/components/Mfa/MfaSetupPanel.vue.d.ts +8 -0
  19. package/dist/package/declarations/src/components/ProForm/components/ProFormItem.vue.d.ts +4 -4
  20. package/dist/package/declarations/src/components/ProForm/index.vue.d.ts +1 -1
  21. package/dist/package/declarations/src/components/ProForm/interface/index.d.ts +4 -1
  22. package/dist/package/declarations/src/components/ProTable/index.vue.d.ts +114 -0
  23. package/dist/package/declarations/src/components/RichTextPreview/index.vue.d.ts +8 -0
  24. package/dist/package/declarations/src/components/TreeFilter/index.vue.d.ts +84 -76
  25. package/dist/package/declarations/src/components/Upload/File.vue.d.ts +1 -1
  26. package/dist/package/declarations/src/components/Upload/Files.vue.d.ts +1 -1
  27. package/dist/package/declarations/src/components/Upload/Img.vue.d.ts +2 -2
  28. package/dist/package/declarations/src/components/Upload/Imgs.vue.d.ts +2 -2
  29. package/dist/package/declarations/src/components/WangEditor/index.vue.d.ts +2 -0
  30. package/dist/package/declarations/src/documentTitle.d.ts +2 -0
  31. package/dist/package/declarations/src/format.d.ts +1 -1
  32. package/dist/package/declarations/src/index.d.ts +2 -1
  33. package/dist/package/declarations/src/layouts/components/LockScreen.vue.d.ts +3 -0
  34. package/dist/package/declarations/src/locales/generated.d.ts +208 -0
  35. package/dist/package/declarations/src/locales/index.d.ts +2 -0
  36. package/dist/package/declarations/src/modules/index.d.ts +1 -3
  37. package/dist/package/declarations/src/request.d.ts +1 -1
  38. package/dist/package/declarations/src/routers/index.d.ts +1 -1
  39. package/dist/package/declarations/src/rpc/base/v1/login.d.ts +37 -5
  40. package/dist/package/declarations/src/rpc/base/v1/mfa.d.ts +152 -0
  41. package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +25 -0
  42. package/dist/package/declarations/src/rpc/base/v1/oauth_client.d.ts +23 -0
  43. package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +13 -0
  44. package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +0 -101
  45. package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +10 -6
  46. package/dist/package/declarations/src/rpc/system/common/v1/common.d.ts +26 -0
  47. package/dist/package/declarations/src/security.d.ts +2 -0
  48. package/dist/package/declarations/src/stores/interface/index.d.ts +19 -1
  49. package/dist/package/declarations/src/stores/modules/lockScreen.d.ts +20 -0
  50. package/dist/package/declarations/src/stores/modules/user.d.ts +7 -6
  51. package/dist/package/declarations/src/stores/runtime.d.ts +1 -0
  52. package/dist/package/declarations/src/utils/clipboard.d.ts +2 -0
  53. package/dist/package/declarations/src/utils/passwordStrength.d.ts +1 -12
  54. package/dist/package/declarations/src/utils/request.d.ts +13 -2
  55. package/dist/package/declarations/src/utils/utils.d.ts +4 -0
  56. package/dist/package/declarations/src/utils/webauthn.d.ts +4 -0
  57. package/dist/package/declarations/vite.config.d.ts +2 -0
  58. package/dist/package/src/api/base/{config.ts → v1/config.ts} +2 -2
  59. package/dist/package/src/api/base/{file.ts → v1/file.ts} +20 -24
  60. package/dist/package/src/api/base/{language.ts → v1/language.ts} +2 -2
  61. package/dist/package/src/api/base/{login.ts → v1/login.ts} +3 -3
  62. package/dist/package/src/api/base/v1/mfa.ts +114 -0
  63. package/dist/package/src/api/base/{oauth.ts → v1/oauth.ts} +2 -2
  64. package/dist/package/src/api/system/{auth.ts → admin/v1/auth.ts} +3 -3
  65. package/dist/package/src/api/system/{base_dict.ts → admin/v1/base_dict.ts} +2 -2
  66. package/dist/package/src/api/system/{base_tenant.ts → admin/v1/base_tenant.ts} +3 -3
  67. package/dist/package/src/bootstrap.ts +6 -5
  68. package/dist/package/src/components/CronExpression/index.vue +4 -3
  69. package/dist/package/src/components/Dialog/PasswordVerifyDialog.vue +176 -0
  70. package/dist/package/src/components/Dialog/ProDialog.vue +1 -0
  71. package/dist/package/src/components/ImportExcel/index.vue +4 -2
  72. package/dist/package/src/components/Loading/fullScreen.ts +2 -1
  73. package/dist/package/src/components/MarkdownPreview/index.vue +22 -2
  74. package/dist/package/src/components/Mfa/MfaRecoveryCodesDialog.vue +91 -0
  75. package/dist/package/src/components/Mfa/MfaSetupPanel.vue +81 -0
  76. package/dist/package/src/components/PasswordStrength/index.vue +1 -1
  77. package/dist/package/src/components/ProForm/components/ProFormItem.vue +16 -1
  78. package/dist/package/src/components/ProForm/interface/index.ts +4 -0
  79. package/dist/package/src/components/ProTable/components/ColSetting.vue +1 -1
  80. package/dist/package/src/components/ProTable/components/TableColumn.vue +14 -6
  81. package/dist/package/src/components/ProTable/index.vue +9 -5
  82. package/dist/package/src/components/RichTextPreview/index.vue +56 -0
  83. package/dist/package/src/components/SelectIcon/index.vue +3 -2
  84. package/dist/package/src/components/Upload/File.vue +2 -2
  85. package/dist/package/src/components/Upload/Files.vue +2 -2
  86. package/dist/package/src/components/Upload/Img.vue +7 -5
  87. package/dist/package/src/components/Upload/Imgs.vue +6 -5
  88. package/dist/package/src/components/WangEditor/index.vue +10 -7
  89. package/dist/package/src/config/index.ts +1 -1
  90. package/dist/package/src/documentTitle.ts +8 -0
  91. package/dist/package/src/format.ts +1 -1
  92. package/dist/package/src/index.ts +2 -1
  93. package/dist/package/src/layouts/LayoutClassic/index.vue +3 -1
  94. package/dist/package/src/layouts/LayoutColumns/index.vue +3 -1
  95. package/dist/package/src/layouts/LayoutTransverse/index.vue +3 -1
  96. package/dist/package/src/layouts/LayoutVertical/index.vue +9 -1
  97. package/dist/package/src/layouts/components/Header/components/Avatar.vue +26 -6
  98. package/dist/package/src/layouts/components/Header/components/InfoDialog.vue +4 -3
  99. package/dist/package/src/layouts/components/Header/components/PasswordDialog.vue +4 -3
  100. package/dist/package/src/layouts/components/Header/components/SearchMenu.vue +16 -8
  101. package/dist/package/src/layouts/components/LockScreen.vue +573 -0
  102. package/dist/package/src/layouts/components/Tabs/index.vue +12 -3
  103. package/dist/package/src/layouts/components/ThemeDrawer/index.scss +4 -4
  104. package/dist/package/src/layouts/index.vue +2 -0
  105. package/dist/package/src/layouts/indexAsync.vue +2 -0
  106. package/dist/package/src/locales/README.md +2 -1
  107. package/dist/package/src/locales/en-US.json +51 -3
  108. package/dist/package/src/locales/index.ts +16 -12
  109. package/dist/package/src/locales/ja-JP.json +51 -3
  110. package/dist/package/src/locales/zh-CN.json +51 -3
  111. package/dist/package/src/locales/zh-TW.json +51 -3
  112. package/dist/package/src/modules/index.ts +2 -4
  113. package/dist/package/src/request.ts +3 -0
  114. package/dist/package/src/routers/index.ts +15 -14
  115. package/dist/package/src/rpc/base/v1/login.ts +37 -4
  116. package/dist/package/src/rpc/base/v1/mfa.ts +180 -0
  117. package/dist/package/src/rpc/base/v1/oauth.ts +25 -0
  118. package/dist/package/src/rpc/base/v1/oauth_client.ts +33 -0
  119. package/dist/package/src/rpc/system/admin/v1/auth.ts +13 -0
  120. package/dist/package/src/rpc/system/admin/v1/base_dict.ts +0 -112
  121. package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +10 -6
  122. package/dist/package/src/rpc/system/common/v1/common.ts +35 -0
  123. package/dist/package/src/security.ts +2 -0
  124. package/dist/package/src/stores/interface/index.ts +20 -1
  125. package/dist/package/src/stores/modules/auth.ts +1 -1
  126. package/dist/package/src/stores/modules/config.ts +2 -2
  127. package/dist/package/src/stores/modules/dict.ts +1 -1
  128. package/dist/package/src/stores/modules/lockScreen.ts +78 -0
  129. package/dist/package/src/stores/modules/user.ts +34 -22
  130. package/dist/package/src/stores/runtime.ts +1 -0
  131. package/dist/package/src/styles/element.scss +35 -4
  132. package/dist/package/src/utils/clipboard.ts +22 -0
  133. package/dist/package/src/utils/passwordCrypto.ts +1 -1
  134. package/dist/package/src/utils/passwordStrength.ts +7 -24
  135. package/dist/package/src/utils/request.ts +175 -40
  136. package/dist/package/src/utils/tenant.ts +1 -1
  137. package/dist/package/src/utils/utils.ts +25 -6
  138. package/dist/package/src/utils/webauthn.ts +90 -0
  139. package/dist/package/src/views/login/components/LoginForm.vue +257 -24
  140. package/dist/package/src/views/login/index.scss +2 -2
  141. package/dist/package/src/views/login/index.vue +3 -1
  142. package/dist/package/types/generated/auto-imports.d.ts +132 -129
  143. package/dist/package/types/generated/components.d.ts +4 -72
  144. package/package.json +22 -1
  145. package/types/generated/auto-imports.d.ts +132 -129
  146. package/types/generated/components.d.ts +4 -72
  147. package/vite.config.ts +4 -1
@@ -0,0 +1,573 @@
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 lock-submit--lock" size="large" native-type="submit" :loading="settingLock">
28
+ <el-icon><Lock /></el-icon>
29
+ {{ t("core.layout.lock_screen_action") }}
30
+ </el-button>
31
+ </form>
32
+ </div>
33
+ </section>
34
+ </div>
35
+
36
+ <div v-if="lockScreenStore.isLocked" class="lock-screen" role="dialog" aria-modal="true">
37
+ <div class="lock-screen__trigger-wrap">
38
+ <button
39
+ v-if="!lockScreenStore.unlockVisible"
40
+ class="lock-screen__trigger"
41
+ type="button"
42
+ @click="openUnlock"
43
+ >
44
+ <el-icon :size="26"><Lock /></el-icon>
45
+ <span>{{ t("core.layout.lock_screen_unlock_hint") }}</span>
46
+ </button>
47
+ <section v-else class="unlock-panel" :aria-label="t('core.layout.lock_screen')">
48
+ <div class="lock-avatar lock-avatar--unlock">
49
+ <img :src="avatarSrc" :alt="t('core.layout.avatar')" @error="handleAvatarError" />
50
+ </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" :class="{ 'lock-screen__clock--compact': lockScreenStore.unlockVisible }" aria-live="polite">
80
+ <div v-if="lockScreenStore.unlockVisible" class="lock-screen__compact-time" :aria-label="`${periodText} ${hourText}:${minuteText}`">
81
+ <time class="lock-screen__compact-time-value">{{ hourText }}:{{ minuteText }}</time>
82
+ <span class="lock-screen__compact-period">{{ periodText }}</span>
83
+ </div>
84
+ <div v-else class="lock-screen__time-grid" :aria-label="`${periodText} ${hourText}:${minuteText}`">
85
+ <div class="lock-screen__time-card">
86
+ <span class="lock-screen__period">{{ periodText }}</span>
87
+ <time class="lock-screen__time-value">{{ hourText }}</time>
88
+ </div>
89
+ <div class="lock-screen__time-card">
90
+ <time class="lock-screen__time-value">{{ minuteText }}</time>
91
+ </div>
92
+ </div>
93
+ <time class="lock-screen__date">{{ dateText }}</time>
94
+ </div>
95
+ </div>
96
+ </template>
97
+
98
+ <script setup lang="ts" name="LockScreen">
99
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
100
+ import { useRouter } from "vue-router";
101
+ import { LOGIN_URL } from "../../config";
102
+ import defaultAvatar from "../../assets/images/avatar.png";
103
+ import { useLocaleStore } from "../../locales";
104
+ import { useLockScreenStore } from "../../stores/modules/lockScreen";
105
+ import { useUserStore } from "../../stores/modules/user";
106
+
107
+ const router = useRouter();
108
+ const userStore = useUserStore();
109
+ const lockScreenStore = useLockScreenStore();
110
+ const { locale, t } = useLocaleStore();
111
+ const avatarSrc = ref(defaultAvatar);
112
+ const setupPassword = ref("");
113
+ const unlockPassword = ref("");
114
+ const setupError = ref("");
115
+ const settingLock = ref(false);
116
+ const unlocking = ref(false);
117
+ const loggingOut = ref(false);
118
+ const now = ref(new Date());
119
+ const unlockInputRef = ref<{ focus?: () => void }>();
120
+ let clockTimer: number | undefined;
121
+
122
+ const displayName = computed(() => userStore.userInfo.nick_name || userStore.userInfo.user_name || t("core.layout.not_set"));
123
+
124
+ /** 当前小时,使用 24 小时制并补齐两位。 */
125
+ const hourText = computed(() => String(now.value.getHours()).padStart(2, "0"));
126
+
127
+ /** 当前分钟,补齐两位。 */
128
+ const minuteText = computed(() => String(now.value.getMinutes()).padStart(2, "0"));
129
+
130
+ /** 当前时间所属的上午或下午时段。 */
131
+ const periodText = computed(() => (now.value.getHours() < 12 ? "AM" : "PM"));
132
+
133
+ /** 按“日期 星期”格式展示当前本地日期。 */
134
+ const dateText = computed(() => {
135
+ const year = now.value.getFullYear();
136
+ const month = String(now.value.getMonth() + 1).padStart(2, "0");
137
+ const day = String(now.value.getDate()).padStart(2, "0");
138
+ const weekday = new Intl.DateTimeFormat(locale.value, { weekday: "long" }).format(now.value);
139
+ return `${year}-${month}-${day} ${weekday}`;
140
+ });
141
+
142
+ watch(
143
+ () => userStore.userInfo.avatar,
144
+ avatar => {
145
+ avatarSrc.value = avatar || defaultAvatar;
146
+ },
147
+ { immediate: true }
148
+ );
149
+
150
+ watch(
151
+ () => lockScreenStore.setupVisible,
152
+ visible => {
153
+ if (!visible) return;
154
+ setupPassword.value = "";
155
+ setupError.value = "";
156
+ }
157
+ );
158
+
159
+ watch(
160
+ () => lockScreenStore.isLocked,
161
+ locked => {
162
+ if (locked) {
163
+ unlockPassword.value = "";
164
+ nextTick(() => unlockInputRef.value?.focus?.());
165
+ }
166
+ }
167
+ );
168
+
169
+ onMounted(() => {
170
+ clockTimer = window.setInterval(() => {
171
+ now.value = new Date();
172
+ }, 1000);
173
+ });
174
+
175
+ onBeforeUnmount(() => {
176
+ if (clockTimer) window.clearInterval(clockTimer);
177
+ });
178
+
179
+ /** 关闭设置锁屏密码界面。 */
180
+ const closeSetup = () => {
181
+ lockScreenStore.closeSetup();
182
+ };
183
+
184
+ /** 设置锁屏密码并进入锁屏界面。 */
185
+ const handleLock = async () => {
186
+ if (!setupPassword.value) {
187
+ setupError.value = t("core.layout.lock_screen_password_required");
188
+ return;
189
+ }
190
+
191
+ settingLock.value = true;
192
+ setupError.value = "";
193
+ try {
194
+ await lockScreenStore.lock(setupPassword.value);
195
+ } finally {
196
+ settingLock.value = false;
197
+ }
198
+ };
199
+
200
+ /** 显示锁屏密码输入界面。 */
201
+ const openUnlock = () => {
202
+ lockScreenStore.openUnlock();
203
+ nextTick(() => unlockInputRef.value?.focus?.());
204
+ };
205
+
206
+ /** 校验锁屏密码并返回当前管理页面。 */
207
+ const handleUnlock = async () => {
208
+ if (!unlockPassword.value) {
209
+ lockScreenStore.unlockError = true;
210
+ return;
211
+ }
212
+
213
+ unlocking.value = true;
214
+ try {
215
+ await lockScreenStore.unlock(unlockPassword.value);
216
+ if (!lockScreenStore.isLocked) unlockPassword.value = "";
217
+ } finally {
218
+ unlocking.value = false;
219
+ }
220
+ };
221
+
222
+ /** 返回登录页并清理当前登录态。 */
223
+ const returnToLogin = async () => {
224
+ if (loggingOut.value) return;
225
+
226
+ loggingOut.value = true;
227
+ try {
228
+ await userStore.logout();
229
+ await router.replace(LOGIN_URL);
230
+ } finally {
231
+ loggingOut.value = false;
232
+ }
233
+ };
234
+
235
+ /** 锁屏首页不再显示密码输入。 */
236
+ const closeUnlock = () => {
237
+ lockScreenStore.closeUnlock();
238
+ unlockPassword.value = "";
239
+ };
240
+
241
+ /** 头像加载失败时回退到本地默认头像。 */
242
+ const handleAvatarError = () => {
243
+ avatarSrc.value = defaultAvatar;
244
+ };
245
+ </script>
246
+
247
+ <style scoped lang="scss">
248
+ .lock-setup-mask {
249
+ position: fixed;
250
+ inset: 0;
251
+ z-index: 4100;
252
+ display: grid;
253
+ place-items: center;
254
+ padding: 20px;
255
+ background: rgb(0 0 0 / 60%);
256
+ }
257
+ .lock-setup-dialog {
258
+ width: min(600px, 100%);
259
+ overflow: hidden;
260
+ color: var(--el-text-color-primary);
261
+ background: var(--el-bg-color-overlay);
262
+ border: 1px solid var(--el-border-color);
263
+ border-radius: var(--admin-page-radius);
264
+ box-shadow: 0 24px 80px rgb(0 0 0 / 28%);
265
+ }
266
+ .lock-dialog-header {
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: space-between;
270
+ padding: 20px 24px;
271
+ border-bottom: 1px solid var(--el-border-color-light);
272
+ h2 {
273
+ margin: 0;
274
+ font-size: 24px;
275
+ line-height: 1.25;
276
+ }
277
+ }
278
+ .lock-icon-button {
279
+ display: inline-grid;
280
+ place-items: center;
281
+ width: 40px;
282
+ height: 40px;
283
+ padding: 0;
284
+ color: var(--el-text-color-secondary);
285
+ cursor: pointer;
286
+ background: transparent;
287
+ border: 0;
288
+ border-radius: 50%;
289
+ &:hover {
290
+ color: var(--el-text-color-primary);
291
+ background: var(--el-fill-color-light);
292
+ }
293
+ }
294
+ .lock-dialog-body {
295
+ display: flex;
296
+ flex-direction: column;
297
+ align-items: center;
298
+ padding: 30px 28px 36px;
299
+ }
300
+ .lock-avatar {
301
+ overflow: hidden;
302
+ background: var(--el-fill-color-light);
303
+ border: 1px solid var(--el-border-color-light);
304
+ border-radius: 50%;
305
+ img {
306
+ display: block;
307
+ width: 100%;
308
+ height: 100%;
309
+ object-fit: cover;
310
+ }
311
+ &--setup {
312
+ width: 136px;
313
+ height: 136px;
314
+ }
315
+ &--unlock {
316
+ width: 132px;
317
+ height: 132px;
318
+ }
319
+ }
320
+ .lock-user-name {
321
+ margin-top: 16px;
322
+ font-size: 22px;
323
+ font-weight: 600;
324
+ line-height: 1.35;
325
+ color: var(--el-text-color-primary);
326
+ }
327
+ .lock-form {
328
+ width: min(100%, 480px);
329
+ margin-top: 28px;
330
+ }
331
+ .lock-input {
332
+ :deep(.el-input__wrapper) {
333
+ box-sizing: border-box;
334
+ height: 48px;
335
+ padding: 0 14px;
336
+ background: var(--el-fill-color-blank);
337
+ border: 1px solid var(--el-border-color);
338
+ border-radius: var(--admin-page-radius);
339
+ box-shadow: none;
340
+ transition:
341
+ border-color var(--el-transition-duration),
342
+ box-shadow var(--el-transition-duration);
343
+ }
344
+ :deep(.el-input__wrapper:hover) {
345
+ border-color: var(--el-border-color-hover);
346
+ }
347
+ :deep(.el-input__wrapper.is-focus) {
348
+ border-color: var(--el-color-primary);
349
+ box-shadow: 0 0 0 2px var(--el-color-primary-light-8);
350
+ }
351
+ :deep(.el-input__inner) {
352
+ font-size: 16px;
353
+ }
354
+ }
355
+ .lock-form-error {
356
+ margin: 10px 2px 0;
357
+ font-size: 13px;
358
+ line-height: 1.4;
359
+ color: var(--el-color-danger);
360
+ }
361
+ .lock-submit {
362
+ width: 100%;
363
+ height: 48px;
364
+ margin-top: 20px;
365
+ font-weight: 600;
366
+ border-radius: var(--admin-page-radius);
367
+ }
368
+ .lock-submit--lock {
369
+ --el-button-text-color: var(--el-text-color-regular);
370
+ --el-button-bg-color: var(--el-fill-color-light);
371
+ --el-button-border-color: transparent;
372
+ --el-button-hover-text-color: var(--el-text-color-primary);
373
+ --el-button-hover-bg-color: var(--el-fill-color);
374
+ --el-button-hover-border-color: var(--el-border-color-light);
375
+ --el-button-active-text-color: var(--el-text-color-primary);
376
+ --el-button-active-bg-color: var(--el-fill-color-dark);
377
+ --el-button-active-border-color: var(--el-border-color);
378
+ --el-button-disabled-text-color: var(--el-text-color-placeholder);
379
+ --el-button-disabled-bg-color: var(--el-fill-color-light);
380
+ --el-button-disabled-border-color: transparent;
381
+ }
382
+ .lock-screen {
383
+ position: fixed;
384
+ inset: 0;
385
+ z-index: 4000;
386
+ display: flex;
387
+ justify-content: center;
388
+ color: var(--el-text-color-primary);
389
+ background: var(--el-bg-color-page);
390
+ }
391
+ .lock-screen__trigger-wrap {
392
+ position: relative;
393
+ z-index: 1;
394
+ display: flex;
395
+ align-items: flex-start;
396
+ justify-content: center;
397
+ width: 100%;
398
+ padding-top: clamp(28px, 7vh, 72px);
399
+ }
400
+ .lock-screen__trigger {
401
+ display: inline-flex;
402
+ flex-direction: column;
403
+ gap: 6px;
404
+ align-items: center;
405
+ padding: 10px 18px;
406
+ font-size: 16px;
407
+ font-weight: 600;
408
+ color: var(--el-text-color-secondary);
409
+ cursor: pointer;
410
+ background: transparent;
411
+ border: 0;
412
+ border-radius: var(--admin-page-radius);
413
+ &:hover,
414
+ &:focus-visible {
415
+ color: var(--el-text-color-primary);
416
+ outline: none;
417
+ background: var(--el-fill-color-light);
418
+ }
419
+ }
420
+ .unlock-panel {
421
+ display: flex;
422
+ flex-direction: column;
423
+ align-items: center;
424
+ width: min(520px, calc(100vw - 32px));
425
+ padding: 24px;
426
+ }
427
+ .unlock-panel .lock-form {
428
+ margin-top: 28px;
429
+ }
430
+ .lock-text-button {
431
+ display: inline-flex;
432
+ gap: 7px;
433
+ align-items: center;
434
+ padding: 4px 8px;
435
+ margin-top: 24px;
436
+ font-size: 15px;
437
+ color: var(--el-text-color-secondary);
438
+ cursor: pointer;
439
+ background: transparent;
440
+ border: 0;
441
+ border-radius: var(--admin-page-radius);
442
+ &:hover,
443
+ &:focus-visible {
444
+ color: var(--el-text-color-primary);
445
+ outline: none;
446
+ background: var(--el-fill-color-light);
447
+ }
448
+ &:disabled {
449
+ cursor: wait;
450
+ opacity: 0.6;
451
+ }
452
+ }
453
+ .lock-screen__clock {
454
+ position: absolute;
455
+ inset: clamp(108px, 15vh, 150px) clamp(20px, 8vw, 180px) clamp(18px, 3vh, 36px);
456
+ display: flex;
457
+ flex-direction: column;
458
+ gap: clamp(18px, 3vh, 30px);
459
+ align-items: center;
460
+ justify-content: center;
461
+ pointer-events: none;
462
+ }
463
+ .lock-screen__clock--compact {
464
+ inset: auto 24px clamp(18px, 4vh, 36px);
465
+ gap: 10px;
466
+ }
467
+ .lock-screen__compact-time {
468
+ display: flex;
469
+ gap: 8px;
470
+ align-items: baseline;
471
+ font-variant-numeric: tabular-nums;
472
+ color: var(--el-text-color-primary);
473
+ }
474
+ .lock-screen__compact-time-value {
475
+ font-size: 30px;
476
+ font-weight: 400;
477
+ line-height: 1;
478
+ letter-spacing: 0;
479
+ }
480
+ .lock-screen__compact-period {
481
+ font-size: 16px;
482
+ line-height: 1;
483
+ }
484
+ .lock-screen__time-grid {
485
+ display: grid;
486
+ flex: 1;
487
+ grid-template-columns: repeat(2, minmax(0, 1fr));
488
+ gap: clamp(20px, 4vw, 72px);
489
+ width: min(100%, 1536px);
490
+ min-height: 0;
491
+ }
492
+ .lock-screen__time-card {
493
+ position: relative;
494
+ display: grid;
495
+ place-items: center;
496
+ min-width: 0;
497
+ overflow: hidden;
498
+ color: var(--el-text-color-primary);
499
+ background: var(--el-fill-color-light);
500
+ border: 1px solid var(--el-border-color-lighter);
501
+ border-radius: var(--admin-page-radius);
502
+ }
503
+ .lock-screen__period {
504
+ position: absolute;
505
+ top: clamp(16px, 3vh, 28px);
506
+ left: clamp(16px, 2vw, 28px);
507
+ font-size: 18px;
508
+ font-weight: 600;
509
+ line-height: 1;
510
+ color: var(--el-text-color-regular);
511
+ }
512
+ .lock-screen__time-value {
513
+ font-size: clamp(112px, 31vh, 272px);
514
+ font-weight: 300;
515
+ font-variant-numeric: tabular-nums;
516
+ line-height: 1;
517
+ letter-spacing: 0;
518
+ }
519
+ .lock-screen__date {
520
+ flex: none;
521
+ font-size: clamp(20px, 3vh, 30px);
522
+ font-variant-numeric: tabular-nums;
523
+ line-height: 1.35;
524
+ color: var(--el-text-color-primary);
525
+ letter-spacing: 0;
526
+ }
527
+
528
+ @media (width <= 600px) {
529
+ .lock-dialog-header {
530
+ padding: 18px 20px;
531
+ h2 {
532
+ font-size: 22px;
533
+ }
534
+ }
535
+ .lock-dialog-body {
536
+ padding: 24px 20px 28px;
537
+ }
538
+ .lock-avatar--setup {
539
+ width: 112px;
540
+ height: 112px;
541
+ }
542
+ .lock-screen__clock {
543
+ inset: 108px 16px 24px;
544
+ gap: 16px;
545
+ }
546
+ .lock-screen__clock--compact {
547
+ inset: auto 16px 24px;
548
+ gap: 8px;
549
+ }
550
+ .lock-screen__time-grid {
551
+ flex: none;
552
+ gap: 12px;
553
+ height: min(42vh, 220px);
554
+ }
555
+ .lock-screen__period {
556
+ top: 12px;
557
+ left: 12px;
558
+ font-size: 14px;
559
+ }
560
+ .lock-screen__time-value {
561
+ font-size: clamp(72px, 17vh, 120px);
562
+ }
563
+ .lock-screen__date {
564
+ font-size: 18px;
565
+ }
566
+ .lock-screen__compact-time-value {
567
+ font-size: 26px;
568
+ }
569
+ .lock-screen__compact-period {
570
+ font-size: 14px;
571
+ }
572
+ }
573
+ </style>
@@ -18,7 +18,7 @@
18
18
 
19
19
  <script setup lang="ts">
20
20
  import Sortable from "sortablejs";
21
- import { ref, computed, watch, onMounted } from "vue";
21
+ import { ref, computed, watch, onBeforeUnmount, onMounted, nextTick } from "vue";
22
22
  import { useRoute, useRouter } from "vue-router";
23
23
  import { useGlobalStore } from "../../../stores/modules/global";
24
24
  import { useTabsStore } from "../../../stores/modules/tabs";
@@ -39,6 +39,7 @@ const { locale, t } = useLocaleStore();
39
39
  const tabsMenuValue = ref(getAdminTabPath(route));
40
40
  const tabsMenuList = computed(() => tabStore.tabsMenuList);
41
41
  const tabsIcon = computed(() => globalStore.tabsIcon);
42
+ let tabsSortable: Sortable | undefined;
42
43
 
43
44
  /** 获取当前路由标签标题,兼容静态路由的多语言标题键。 */
44
45
  const getCurrentRouteTitle = () => {
@@ -47,8 +48,13 @@ const getCurrentRouteTitle = () => {
47
48
  };
48
49
 
49
50
  onMounted(() => {
50
- tabsDrop();
51
51
  initTabs();
52
+ void nextTick(tabsDrop);
53
+ });
54
+
55
+ onBeforeUnmount(() => {
56
+ tabsSortable?.destroy();
57
+ tabsSortable = undefined;
52
58
  });
53
59
 
54
60
  // 监听路由的变化(防止浏览器后退/前进不变化 tabsMenuValue)
@@ -96,7 +102,10 @@ const initTabs = () => {
96
102
 
97
103
  // tabs 拖拽排序
98
104
  const tabsDrop = () => {
99
- Sortable.create(document.querySelector(".el-tabs__nav") as HTMLElement, {
105
+ const tabsNav = document.querySelector<HTMLElement>(".el-tabs__nav");
106
+ if (!tabsNav) return;
107
+ tabsSortable?.destroy();
108
+ tabsSortable = Sortable.create(tabsNav, {
100
109
  draggable: ".el-tabs__item",
101
110
  animation: 300,
102
111
  onEnd({ newIndex, oldIndex }) {
@@ -38,21 +38,21 @@
38
38
  height: 70px;
39
39
  padding: 6px;
40
40
  cursor: pointer;
41
- border-radius: 5px;
41
+ border-radius: var(--admin-page-radius);
42
42
  box-shadow: 0 0 5px 1px var(--el-border-color-dark);
43
43
  transition: all 0.2s;
44
44
  .layout-dark {
45
45
  background-color: var(--el-color-primary);
46
- border-radius: 3px;
46
+ border-radius: var(--admin-page-radius);
47
47
  }
48
48
  .layout-light {
49
49
  background-color: var(--el-color-primary-light-5);
50
- border-radius: 3px;
50
+ border-radius: var(--admin-page-radius);
51
51
  }
52
52
  .layout-content {
53
53
  background-color: var(--el-color-primary-light-8);
54
54
  border: 1px dashed var(--el-color-primary);
55
- border-radius: 3px;
55
+ border-radius: var(--admin-page-radius);
56
56
  }
57
57
  .el-icon {
58
58
  position: absolute;
@@ -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
- 本目录提供管理端底座和公共组件使用的稳定文案,包括通用操作、字段、状态、登录认证、上传、校验和错误提示。语言包由 `../modules/kratosAdmin.ts` 导入,作为 `kratos-admin` 模块注册到 `packages/core/src/locales/index.ts`,最终由 `bootstrap.ts` 合并到 Vue I18n;core 内组件、hooks、请求错误处理和登录页通过 `t()` 或 `useLocaleStore()` 使用。
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)。