@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.
- package/README.md +15 -8
- package/build/auto-imports.json +73 -0
- package/build/plugins.ts +2 -58
- package/dist/package/declarations/src/api/base/{config.d.ts → v1/config.d.ts} +1 -1
- package/dist/package/declarations/src/api/base/{file.d.ts → v1/file.d.ts} +3 -1
- package/dist/package/declarations/src/api/base/{language.d.ts → v1/language.d.ts} +1 -1
- package/dist/package/declarations/src/api/base/{login.d.ts → v1/login.d.ts} +2 -2
- package/dist/package/declarations/src/api/base/v1/mfa.d.ts +26 -0
- package/dist/package/declarations/src/api/base/{oauth.d.ts → v1/oauth.d.ts} +1 -1
- package/dist/package/declarations/src/api/system/{auth.d.ts → admin/v1/auth.d.ts} +2 -2
- package/dist/package/declarations/src/api/system/{base_dict.d.ts → admin/v1/base_dict.d.ts} +1 -1
- package/dist/package/declarations/src/api/system/{base_tenant.d.ts → admin/v1/base_tenant.d.ts} +2 -2
- 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/ProTable/index.vue.d.ts +114 -0
- package/dist/package/declarations/src/components/RichTextPreview/index.vue.d.ts +8 -0
- package/dist/package/declarations/src/components/TreeFilter/index.vue.d.ts +84 -76
- package/dist/package/declarations/src/components/Upload/File.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Upload/Files.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Upload/Img.vue.d.ts +2 -2
- package/dist/package/declarations/src/components/Upload/Imgs.vue.d.ts +2 -2
- package/dist/package/declarations/src/components/WangEditor/index.vue.d.ts +2 -0
- package/dist/package/declarations/src/documentTitle.d.ts +2 -0
- package/dist/package/declarations/src/format.d.ts +1 -1
- package/dist/package/declarations/src/index.d.ts +2 -1
- package/dist/package/declarations/src/layouts/components/LockScreen.vue.d.ts +3 -0
- package/dist/package/declarations/src/locales/generated.d.ts +208 -0
- package/dist/package/declarations/src/locales/index.d.ts +2 -0
- package/dist/package/declarations/src/modules/index.d.ts +1 -3
- package/dist/package/declarations/src/request.d.ts +1 -1
- package/dist/package/declarations/src/routers/index.d.ts +1 -1
- package/dist/package/declarations/src/rpc/base/v1/login.d.ts +37 -5
- 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/auth.d.ts +13 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +0 -101
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +10 -6
- package/dist/package/declarations/src/rpc/system/common/v1/common.d.ts +26 -0
- package/dist/package/declarations/src/security.d.ts +2 -0
- package/dist/package/declarations/src/stores/interface/index.d.ts +19 -1
- package/dist/package/declarations/src/stores/modules/lockScreen.d.ts +20 -0
- package/dist/package/declarations/src/stores/modules/user.d.ts +7 -6
- 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/passwordStrength.d.ts +1 -12
- package/dist/package/declarations/src/utils/request.d.ts +13 -2
- package/dist/package/declarations/src/utils/utils.d.ts +4 -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/{config.ts → v1/config.ts} +2 -2
- package/dist/package/src/api/base/{file.ts → v1/file.ts} +20 -24
- package/dist/package/src/api/base/{language.ts → v1/language.ts} +2 -2
- package/dist/package/src/api/base/{login.ts → v1/login.ts} +3 -3
- package/dist/package/src/api/base/v1/mfa.ts +114 -0
- package/dist/package/src/api/base/{oauth.ts → v1/oauth.ts} +2 -2
- package/dist/package/src/api/system/{auth.ts → admin/v1/auth.ts} +3 -3
- package/dist/package/src/api/system/{base_dict.ts → admin/v1/base_dict.ts} +2 -2
- package/dist/package/src/api/system/{base_tenant.ts → admin/v1/base_tenant.ts} +3 -3
- package/dist/package/src/bootstrap.ts +6 -5
- package/dist/package/src/components/CronExpression/index.vue +4 -3
- package/dist/package/src/components/Dialog/PasswordVerifyDialog.vue +176 -0
- package/dist/package/src/components/Dialog/ProDialog.vue +1 -0
- package/dist/package/src/components/ImportExcel/index.vue +4 -2
- package/dist/package/src/components/Loading/fullScreen.ts +2 -1
- package/dist/package/src/components/MarkdownPreview/index.vue +22 -2
- 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/PasswordStrength/index.vue +1 -1
- 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/components/TableColumn.vue +14 -6
- package/dist/package/src/components/ProTable/index.vue +9 -5
- package/dist/package/src/components/RichTextPreview/index.vue +56 -0
- package/dist/package/src/components/SelectIcon/index.vue +3 -2
- package/dist/package/src/components/Upload/File.vue +2 -2
- package/dist/package/src/components/Upload/Files.vue +2 -2
- package/dist/package/src/components/Upload/Img.vue +7 -5
- package/dist/package/src/components/Upload/Imgs.vue +6 -5
- package/dist/package/src/components/WangEditor/index.vue +10 -7
- package/dist/package/src/config/index.ts +1 -1
- package/dist/package/src/documentTitle.ts +8 -0
- package/dist/package/src/format.ts +1 -1
- package/dist/package/src/index.ts +2 -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 +26 -6
- package/dist/package/src/layouts/components/Header/components/InfoDialog.vue +4 -3
- package/dist/package/src/layouts/components/Header/components/PasswordDialog.vue +4 -3
- package/dist/package/src/layouts/components/Header/components/SearchMenu.vue +16 -8
- package/dist/package/src/layouts/components/LockScreen.vue +573 -0
- package/dist/package/src/layouts/components/Tabs/index.vue +12 -3
- package/dist/package/src/layouts/components/ThemeDrawer/index.scss +4 -4
- 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 +51 -3
- package/dist/package/src/locales/index.ts +16 -12
- package/dist/package/src/locales/ja-JP.json +51 -3
- package/dist/package/src/locales/zh-CN.json +51 -3
- package/dist/package/src/locales/zh-TW.json +51 -3
- package/dist/package/src/modules/index.ts +2 -4
- package/dist/package/src/request.ts +3 -0
- package/dist/package/src/routers/index.ts +15 -14
- package/dist/package/src/rpc/base/v1/login.ts +37 -4
- 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/auth.ts +13 -0
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +0 -112
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +10 -6
- package/dist/package/src/rpc/system/common/v1/common.ts +35 -0
- package/dist/package/src/security.ts +2 -0
- package/dist/package/src/stores/interface/index.ts +20 -1
- package/dist/package/src/stores/modules/auth.ts +1 -1
- package/dist/package/src/stores/modules/config.ts +2 -2
- package/dist/package/src/stores/modules/dict.ts +1 -1
- package/dist/package/src/stores/modules/lockScreen.ts +78 -0
- package/dist/package/src/stores/modules/user.ts +34 -22
- package/dist/package/src/stores/runtime.ts +1 -0
- package/dist/package/src/styles/element.scss +35 -4
- package/dist/package/src/utils/clipboard.ts +22 -0
- package/dist/package/src/utils/passwordCrypto.ts +1 -1
- package/dist/package/src/utils/passwordStrength.ts +7 -24
- package/dist/package/src/utils/request.ts +175 -40
- package/dist/package/src/utils/tenant.ts +1 -1
- package/dist/package/src/utils/utils.ts +25 -6
- package/dist/package/src/utils/webauthn.ts +90 -0
- package/dist/package/src/views/login/components/LoginForm.vue +257 -24
- package/dist/package/src/views/login/index.scss +2 -2
- 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 +4 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { defineStore } from "pinia";
|
|
2
|
+
import type { LockScreenState } from "../../stores/interface";
|
|
3
|
+
import piniaPersistConfig from "../../stores/helper/persist";
|
|
4
|
+
|
|
5
|
+
/** 将锁屏密码转换为不可逆摘要,避免在本地持久化明文密码。 */
|
|
6
|
+
async function hashPassword(password: string): Promise<string> {
|
|
7
|
+
const data = new TextEncoder().encode(password);
|
|
8
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
9
|
+
return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, "0")).join("");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 管理端锁屏状态仓库。 */
|
|
13
|
+
export const useLockScreenStore = defineStore("admin-lock-screen", {
|
|
14
|
+
state: (): LockScreenState => ({
|
|
15
|
+
isLocked: false,
|
|
16
|
+
passwordHash: "",
|
|
17
|
+
setupVisible: false,
|
|
18
|
+
unlockVisible: false,
|
|
19
|
+
unlockError: false
|
|
20
|
+
}),
|
|
21
|
+
getters: {
|
|
22
|
+
/** 当前是否已配置可用于解锁的密码。 */
|
|
23
|
+
isConfigured: state => Boolean(state.passwordHash)
|
|
24
|
+
},
|
|
25
|
+
actions: {
|
|
26
|
+
/** 打开锁屏密码设置界面。 */
|
|
27
|
+
openSetup() {
|
|
28
|
+
this.setupVisible = true;
|
|
29
|
+
this.unlockVisible = false;
|
|
30
|
+
this.unlockError = false;
|
|
31
|
+
},
|
|
32
|
+
/** 关闭锁屏密码设置界面。 */
|
|
33
|
+
closeSetup() {
|
|
34
|
+
this.setupVisible = false;
|
|
35
|
+
},
|
|
36
|
+
/** 设置锁屏密码并立即进入锁屏状态。 */
|
|
37
|
+
async lock(password: string) {
|
|
38
|
+
this.passwordHash = await hashPassword(password);
|
|
39
|
+
this.isLocked = true;
|
|
40
|
+
this.setupVisible = false;
|
|
41
|
+
this.unlockVisible = false;
|
|
42
|
+
this.unlockError = false;
|
|
43
|
+
},
|
|
44
|
+
/** 打开锁屏解锁输入界面。 */
|
|
45
|
+
openUnlock() {
|
|
46
|
+
this.unlockVisible = true;
|
|
47
|
+
this.unlockError = false;
|
|
48
|
+
},
|
|
49
|
+
/** 返回锁屏首页。 */
|
|
50
|
+
closeUnlock() {
|
|
51
|
+
this.unlockVisible = false;
|
|
52
|
+
this.unlockError = false;
|
|
53
|
+
},
|
|
54
|
+
/** 校验锁屏密码,成功后解除当前锁定。 */
|
|
55
|
+
async unlock(password: string): Promise<boolean> {
|
|
56
|
+
const passwordHash = await hashPassword(password);
|
|
57
|
+
if (!this.passwordHash || passwordHash !== this.passwordHash) {
|
|
58
|
+
this.unlockError = true;
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.isLocked = false;
|
|
63
|
+
this.passwordHash = "";
|
|
64
|
+
this.unlockVisible = false;
|
|
65
|
+
this.unlockError = false;
|
|
66
|
+
return true;
|
|
67
|
+
},
|
|
68
|
+
/** 清理锁屏状态,供退出登录时使用。 */
|
|
69
|
+
clearLock() {
|
|
70
|
+
this.isLocked = false;
|
|
71
|
+
this.passwordHash = "";
|
|
72
|
+
this.setupVisible = false;
|
|
73
|
+
this.unlockVisible = false;
|
|
74
|
+
this.unlockError = false;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
persist: piniaPersistConfig("admin-lock-screen", ["isLocked", "passwordHash"])
|
|
78
|
+
});
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { defineStore } from "pinia";
|
|
2
|
-
import { defAuthService } from "../../api/system/auth";
|
|
3
|
-
import { defLoginService } from "../../api/base/login";
|
|
4
|
-
import
|
|
2
|
+
import { defAuthService } from "../../api/system/admin/v1/auth";
|
|
3
|
+
import { defLoginService } from "../../api/base/v1/login";
|
|
4
|
+
import { defMfaService } from "../../api/base/v1/mfa";
|
|
5
|
+
import type { LoginRequest, LoginResponse } from "../../rpc/base/v1/login";
|
|
6
|
+
import type { VerifyMfaRequest } from "../../rpc/base/v1/mfa";
|
|
5
7
|
import type { UserInfoForm } from "../../rpc/system/admin/v1/auth";
|
|
6
8
|
import { UserState } from "../../stores/interface";
|
|
7
9
|
import piniaPersistConfig from "../../stores/helper/persist";
|
|
8
10
|
import { useDictStoreHook } from "../../stores/modules/dict";
|
|
9
|
-
import {
|
|
11
|
+
import { useLockScreenStore } from "../../stores/modules/lockScreen";
|
|
10
12
|
|
|
11
13
|
const defaultUserInfo: UserInfoForm = {
|
|
12
14
|
user_name: "",
|
|
13
15
|
nick_name: "",
|
|
14
16
|
phone: "",
|
|
17
|
+
email: "",
|
|
18
|
+
id_type: 0,
|
|
19
|
+
id_code: "",
|
|
15
20
|
avatar: "",
|
|
16
21
|
role_code: "",
|
|
17
22
|
role_name: "",
|
|
@@ -23,9 +28,11 @@ const defaultUserInfo: UserInfoForm = {
|
|
|
23
28
|
export const useUserStore = defineStore("admin-user", {
|
|
24
29
|
state: (): UserState => ({
|
|
25
30
|
token: "",
|
|
26
|
-
refreshToken: "",
|
|
27
31
|
tokenType: "",
|
|
28
32
|
tokenExpiresAt: 0,
|
|
33
|
+
isLoggingOut: false,
|
|
34
|
+
authVersion: 0,
|
|
35
|
+
authInvalidated: false,
|
|
29
36
|
userInfo: defaultUserInfo
|
|
30
37
|
}),
|
|
31
38
|
getters: {},
|
|
@@ -34,10 +41,6 @@ export const useUserStore = defineStore("admin-user", {
|
|
|
34
41
|
setToken(token: string) {
|
|
35
42
|
this.token = token;
|
|
36
43
|
},
|
|
37
|
-
/** 设置刷新令牌 */
|
|
38
|
-
setRefreshToken(refreshToken: string) {
|
|
39
|
-
this.refreshToken = refreshToken;
|
|
40
|
-
},
|
|
41
44
|
/** 设置令牌类型 */
|
|
42
45
|
setTokenType(tokenType: string) {
|
|
43
46
|
this.tokenType = tokenType;
|
|
@@ -51,12 +54,12 @@ export const useUserStore = defineStore("admin-user", {
|
|
|
51
54
|
this.userInfo = userInfo;
|
|
52
55
|
},
|
|
53
56
|
/** 根据接口返回统一更新令牌信息 */
|
|
54
|
-
updateTokenAuth(accessToken: string,
|
|
57
|
+
updateTokenAuth(accessToken: string, tokenType: string, expiresIn?: number) {
|
|
55
58
|
const tokenPrefix = tokenType ? `${tokenType} ` : "";
|
|
56
59
|
const expiresAt = expiresIn ? Date.now() + expiresIn * 1000 : 0;
|
|
57
60
|
|
|
61
|
+
this.authInvalidated = false;
|
|
58
62
|
this.setToken(`${tokenPrefix}${accessToken}`.trim());
|
|
59
|
-
this.setRefreshToken(refreshToken ?? "");
|
|
60
63
|
this.setTokenType(tokenType ?? "");
|
|
61
64
|
this.setTokenExpiresAt(expiresAt);
|
|
62
65
|
},
|
|
@@ -65,18 +68,23 @@ export const useUserStore = defineStore("admin-user", {
|
|
|
65
68
|
return Boolean(this.token.trim() && this.tokenExpiresAt > Date.now());
|
|
66
69
|
},
|
|
67
70
|
/** 登录 */
|
|
68
|
-
async login(loginRequest: LoginRequest) {
|
|
71
|
+
async login(loginRequest: LoginRequest): Promise<LoginResponse> {
|
|
69
72
|
const data = await defLoginService.Login(loginRequest);
|
|
70
|
-
|
|
73
|
+
if (data.status === 1 || data.status === 0 || data.status === 4) {
|
|
74
|
+
this.updateTokenAuth(data.access_token, data.token_type ?? "", data.expires_in);
|
|
75
|
+
}
|
|
76
|
+
return data;
|
|
71
77
|
},
|
|
72
|
-
/**
|
|
78
|
+
/** 校验登录阶段的多因素认证并保存正式令牌。 */
|
|
79
|
+
async verifyMfa(request: VerifyMfaRequest): Promise<LoginResponse> {
|
|
80
|
+
const data = await defMfaService.VerifyMfa(request);
|
|
81
|
+
this.updateTokenAuth(data.access_token, data.token_type ?? "", data.expires_in);
|
|
82
|
+
return data;
|
|
83
|
+
},
|
|
84
|
+
/** 通过 HttpOnly Cookie 刷新认证令牌。 */
|
|
73
85
|
async refreshAccessToken() {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const data = await defLoginService.RefreshToken({ refresh_token: this.refreshToken });
|
|
79
|
-
this.updateTokenAuth(data.access_token, data.refresh_token ?? this.refreshToken, data.token_type ?? "", data.expires_in);
|
|
86
|
+
const data = await defLoginService.RefreshToken({});
|
|
87
|
+
this.updateTokenAuth(data.access_token, data.token_type ?? "", data.expires_in);
|
|
80
88
|
return data;
|
|
81
89
|
},
|
|
82
90
|
/** 获取用户信息 */
|
|
@@ -89,22 +97,26 @@ export const useUserStore = defineStore("admin-user", {
|
|
|
89
97
|
clearAuthData() {
|
|
90
98
|
// 清理登录态时同步清空字典缓存,避免切换账号后读到旧字典。
|
|
91
99
|
useDictStoreHook().clearDictionaryCache();
|
|
100
|
+
useLockScreenStore().clearLock();
|
|
101
|
+
this.authVersion += 1;
|
|
102
|
+
this.authInvalidated = true;
|
|
92
103
|
this.setToken("");
|
|
93
|
-
this.setRefreshToken("");
|
|
94
104
|
this.setTokenType("");
|
|
95
105
|
this.setTokenExpiresAt(0);
|
|
96
106
|
this.setUserInfo({ ...defaultUserInfo });
|
|
97
107
|
},
|
|
98
108
|
/** 退出登录 */
|
|
99
109
|
async logout() {
|
|
110
|
+
this.isLoggingOut = true;
|
|
100
111
|
try {
|
|
101
112
|
await defLoginService.Logout({});
|
|
102
113
|
} catch {
|
|
103
114
|
// 退出接口返回异常时,前端仍需清理本地登录态,避免用户卡在当前会话。
|
|
104
115
|
} finally {
|
|
105
116
|
this.clearAuthData();
|
|
117
|
+
this.isLoggingOut = false;
|
|
106
118
|
}
|
|
107
119
|
}
|
|
108
120
|
},
|
|
109
|
-
persist: piniaPersistConfig("admin-user")
|
|
121
|
+
persist: piniaPersistConfig("admin-user-security-v2", ["userInfo"])
|
|
110
122
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { useAuthStore } from "./modules/auth";
|
|
2
2
|
export { useConfigStore } from "./modules/config";
|
|
3
3
|
export { useGlobalStore } from "./modules/global";
|
|
4
|
+
export { useLockScreenStore } from "./modules/lockScreen";
|
|
4
5
|
export { useTabsStore } from "./modules/tabs";
|
|
5
6
|
export { useUserStore } from "./modules/user";
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/* 统一 Element Plus 矩形控件与后台页面的圆角。 */
|
|
2
|
+
html:root {
|
|
3
|
+
--el-border-radius-base: var(--admin-page-radius);
|
|
4
|
+
--el-border-radius-small: 2px;
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
/* el-alert */
|
|
2
8
|
.el-alert {
|
|
3
9
|
border: 1px solid;
|
|
@@ -27,7 +33,18 @@
|
|
|
27
33
|
overflow-x: hidden;
|
|
28
34
|
background-color: var(--el-bg-color);
|
|
29
35
|
border: 1px solid var(--el-border-color-light);
|
|
30
|
-
border-radius:
|
|
36
|
+
border-radius: var(--admin-page-radius);
|
|
37
|
+
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* 非列表业务页沿用 ProTable 卡片外观 */
|
|
41
|
+
.el-card.admin-page-card {
|
|
42
|
+
background-color: var(--el-bg-color);
|
|
43
|
+
border: 1px solid var(--el-border-color-light);
|
|
44
|
+
border-radius: var(--admin-page-radius);
|
|
45
|
+
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
|
|
46
|
+
}
|
|
47
|
+
.el-card.admin-page-card.is-always-shadow {
|
|
31
48
|
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
|
|
32
49
|
}
|
|
33
50
|
|
|
@@ -165,10 +182,9 @@
|
|
|
165
182
|
}
|
|
166
183
|
}
|
|
167
184
|
|
|
168
|
-
// 设置 el-table
|
|
185
|
+
// 设置 el-table 表头文字单行省略,完整内容由列标题悬浮提示展示。
|
|
169
186
|
.el-table__header .el-table__cell > .cell {
|
|
170
|
-
|
|
171
|
-
white-space: wrap;
|
|
187
|
+
white-space: nowrap;
|
|
172
188
|
}
|
|
173
189
|
|
|
174
190
|
// 空表时去掉底部那条默认分隔线,和菜单管理页保持一致。
|
|
@@ -328,6 +344,21 @@
|
|
|
328
344
|
}
|
|
329
345
|
}
|
|
330
346
|
|
|
347
|
+
// ProDialog 需要去掉 Element Plus 默认的外层内边距,避免异步样式重复加载后覆盖紧凑布局。
|
|
348
|
+
.el-dialog.pro-dialog {
|
|
349
|
+
padding: 0;
|
|
350
|
+
|
|
351
|
+
// 对齐原生 MessageBox 的紧凑上下间距,避免标题和操作区留白过大。
|
|
352
|
+
.el-dialog__header,
|
|
353
|
+
.el-dialog__body {
|
|
354
|
+
padding: 12px 20px;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.el-dialog__footer {
|
|
358
|
+
padding: 0 20px 12px;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
331
362
|
@media screen and (width <= 768px) {
|
|
332
363
|
.el-dialog {
|
|
333
364
|
margin-top: 4vh;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** 将文本复制到系统剪贴板,并兼容非安全上下文的管理端开发地址。 */
|
|
2
|
+
export async function copyText(content: string): Promise<void> {
|
|
3
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && typeof window !== "undefined" && window.isSecureContext) {
|
|
4
|
+
try {
|
|
5
|
+
await navigator.clipboard.writeText(content);
|
|
6
|
+
return;
|
|
7
|
+
} catch {
|
|
8
|
+
// Clipboard API 受权限限制时继续尝试传统 DOM 方案。
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
if (typeof document === "undefined") throw new Error("copy failed");
|
|
12
|
+
const textarea = document.createElement("textarea");
|
|
13
|
+
textarea.value = content;
|
|
14
|
+
textarea.style.position = "fixed";
|
|
15
|
+
textarea.style.opacity = "0";
|
|
16
|
+
document.body.appendChild(textarea);
|
|
17
|
+
textarea.focus();
|
|
18
|
+
textarea.select();
|
|
19
|
+
const copied = document.execCommand("copy");
|
|
20
|
+
document.body.removeChild(textarea);
|
|
21
|
+
if (!copied) throw new Error("copy failed");
|
|
22
|
+
}
|
|
@@ -9,12 +9,10 @@ export interface PasswordStrengthResult {
|
|
|
9
9
|
strengthScore: number;
|
|
10
10
|
/** 强度等级。 */
|
|
11
11
|
level: PasswordStrengthLevel;
|
|
12
|
-
/** 是否达到允许提交的最高强度。 */
|
|
13
|
-
isValid: boolean;
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
15
|
+
* 计算密码强度结果,供表单展示使用;最终规则由服务端密码策略校验。
|
|
18
16
|
*
|
|
19
17
|
* @param password 当前密码
|
|
20
18
|
* @returns 密码强度分析结果
|
|
@@ -24,14 +22,13 @@ export function getPasswordStrength(password?: string): PasswordStrengthResult {
|
|
|
24
22
|
return {
|
|
25
23
|
ruleScore: 0,
|
|
26
24
|
strengthScore: 0,
|
|
27
|
-
level: "empty"
|
|
28
|
-
isValid: false
|
|
25
|
+
level: "empty"
|
|
29
26
|
};
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
let ruleScore = 0;
|
|
33
|
-
if (password
|
|
34
|
-
if (/[
|
|
30
|
+
if (/[a-z]/.test(password)) ruleScore += 1;
|
|
31
|
+
if (/[A-Z]/.test(password)) ruleScore += 1;
|
|
35
32
|
if (/\d/.test(password)) ruleScore += 1;
|
|
36
33
|
if (/[^A-Za-z0-9]/.test(password)) ruleScore += 1;
|
|
37
34
|
|
|
@@ -39,33 +36,19 @@ export function getPasswordStrength(password?: string): PasswordStrengthResult {
|
|
|
39
36
|
return {
|
|
40
37
|
ruleScore,
|
|
41
38
|
strengthScore: 3,
|
|
42
|
-
level: "high"
|
|
43
|
-
isValid: true
|
|
39
|
+
level: "high"
|
|
44
40
|
};
|
|
45
41
|
}
|
|
46
42
|
if (ruleScore === 3) {
|
|
47
43
|
return {
|
|
48
44
|
ruleScore,
|
|
49
45
|
strengthScore: 2,
|
|
50
|
-
level: "medium"
|
|
51
|
-
isValid: false
|
|
46
|
+
level: "medium"
|
|
52
47
|
};
|
|
53
48
|
}
|
|
54
49
|
return {
|
|
55
50
|
ruleScore,
|
|
56
51
|
strengthScore: 1,
|
|
57
|
-
level: "low"
|
|
58
|
-
isValid: false
|
|
52
|
+
level: "low"
|
|
59
53
|
};
|
|
60
54
|
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 校验密码是否达到最高强度,便于表单规则直接复用。
|
|
64
|
-
*
|
|
65
|
-
* @param password 当前密码
|
|
66
|
-
* @returns 校验结果
|
|
67
|
-
*/
|
|
68
|
-
export function validatePasswordStrengthValue(password?: string) {
|
|
69
|
-
const result = getPasswordStrength(password);
|
|
70
|
-
return { valid: result.isValid };
|
|
71
|
-
}
|