From 64eb1c2ebfc25f11f5757a0eef04de230fa8fa15 Mon Sep 17 00:00:00 2001
From: zhengyiming <540361168@qq.com>
Date: 星期四, 04 十二月 2025 17:52:54 +0800
Subject: [PATCH] fix: 账号密码双因子登录
---
src/services/api/Account.ts | 67 ++++++++++-
src/views/Login/Login.vue | 57 +++++++-
src/services/api/typings.d.ts | 70 +++++++++++
src/store/modules/user.ts | 31 +++-
src/style/element/element-plus.scss | 7 +
src/views/Login/components/SendVerificationCodeView.vue | 97 ++++++++++++++++
6 files changed, 303 insertions(+), 26 deletions(-)
diff --git a/src/services/api/Account.ts b/src/services/api/Account.ts
index 3dc39aa..67f2e96 100644
--- a/src/services/api/Account.ts
+++ b/src/services/api/Account.ts
@@ -219,9 +219,17 @@
});
}
+/** 鏌ヨ绯荤粺淇℃伅 GET /api/Account/GetSystemInfo */
+export async function getSystemInfo(options?: API.RequestConfig) {
+ return request<API.GetSystemInfoOutput>('/api/Account/GetSystemInfo', {
+ method: 'GET',
+ ...(options || {}),
+ });
+}
+
/** 鐢靛瓙绛剧櫥褰� POST /api/Account/GetTokenForUserSign */
export async function getTokenForUserSign(body: API.AccessRequestDto, options?: API.RequestConfig) {
- return request<API.IdentityModelTokenCacheItem>('/api/Account/GetTokenForUserSign', {
+ return request<API.IdentityModelToken>('/api/Account/GetTokenForUserSign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -233,7 +241,7 @@
/** 姝ゅ鍚庣娌℃湁鎻愪緵娉ㄩ噴 POST /api/Account/GetTokenForWeb */
export async function getTokenForWeb(body: API.AccessRequestDto, options?: API.RequestConfig) {
- return request<API.IdentityModelTokenCacheItem>('/api/Account/GetTokenForWeb', {
+ return request<API.IdentityModelToken>('/api/Account/GetTokenForWeb', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -313,7 +321,7 @@
/** 瀵嗙爜鐧诲綍 POST /api/Account/PasswordLogin */
export async function passwordLogin(body: API.PasswordLoginInput, options?: API.RequestConfig) {
- return request<API.IdentityModelTokenCacheItem>('/api/Account/PasswordLogin', {
+ return request<API.IdentityModelToken>('/api/Account/PasswordLogin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -328,7 +336,7 @@
body: API.PhoneMesssageCodeLoginInput,
options?: API.RequestConfig
) {
- return request<API.IdentityModelTokenCacheItem>('/api/Account/PhoneMesssageCodeLogin', {
+ return request<API.IdentityModelToken>('/api/Account/PhoneMesssageCodeLogin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -395,6 +403,51 @@
});
}
+/** 鍙屽洜瀛愮櫥褰�-绗竴姝ヨ处鍙峰瘑鐮佺櫥褰� POST /api/Account/TwoFactorLoginPassword */
+export async function twoFactorLoginPassword(
+ body: API.TwoFactorLoginPasswordInput,
+ options?: API.RequestConfig
+) {
+ return request<API.TwoFactorLoginPasswordOutput>('/api/Account/TwoFactorLoginPassword', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** 鍙屽洜瀛愮浜屾-鍙戦�侀獙璇佺爜 POST /api/Account/TwoFactorLoginSendVerificationCode */
+export async function twoFactorLoginSendVerificationCode(
+ body: API.TwoFactorLoginSendVerificationCodeInput,
+ options?: API.RequestConfig
+) {
+ return request<number>('/api/Account/TwoFactorLoginSendVerificationCode', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
+/** 鍙屽洜瀛愮涓夋-楠岃瘉鐮佺櫥褰� POST /api/Account/TwoFactorLoginSms */
+export async function twoFactorLoginSms(
+ body: API.TwoFactorLoginSmsInput,
+ options?: API.RequestConfig
+) {
+ return request<API.IdentityModelToken>('/api/Account/TwoFactorLoginSms', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ data: body,
+ ...(options || {}),
+ });
+}
+
/** 瑙g粦鐢ㄦ埛閭 POST /api/Account/UnbindingUserEmail */
export async function unbindingUserEmail(
body: API.UnbindingUserEmailInput,
@@ -445,7 +498,7 @@
body: API.WxMiniAppPhoneLoginInput,
options?: API.RequestConfig
) {
- return request<API.IdentityModelTokenCacheItem>('/api/Account/WxMiniAppPhoneAuthLogin', {
+ return request<API.IdentityModelToken>('/api/Account/WxMiniAppPhoneAuthLogin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -460,7 +513,7 @@
body: API.WxMiniAppPhoneAuthLoginFromScanInput,
options?: API.RequestConfig
) {
- return request<API.IdentityModelTokenCacheItem>('/api/Account/WxMiniAppPhoneAuthLoginFromScan', {
+ return request<API.IdentityModelToken>('/api/Account/WxMiniAppPhoneAuthLoginFromScan', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -490,7 +543,7 @@
body: API.WxMiniAppUserLoginFromScanInput,
options?: API.RequestConfig
) {
- return request<API.IdentityModelTokenCacheItem>('/api/Account/WxMiniAppUserLoginFromScan', {
+ return request<API.IdentityModelToken>('/api/Account/WxMiniAppUserLoginFromScan', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
diff --git a/src/services/api/typings.d.ts b/src/services/api/typings.d.ts
index e6dfa4b..573ce44 100644
--- a/src/services/api/typings.d.ts
+++ b/src/services/api/typings.d.ts
@@ -6488,6 +6488,8 @@
type EnumWalletSignStatus = 1 | 10 | 100 | 999;
+ type EnumWalletTransactionStatus = 1 | 10 | 20 | 30 | 40 | 50;
+
interface ExportBountyApplyData {
/** 浼佷笟鍚嶇О */
enterpriseName: string;
@@ -8004,6 +8006,13 @@
amount?: number;
remainAmount?: number;
invoiceUrl?: string;
+ transactionStatus?: EnumWalletTransactionStatus;
+ /** 璁㈠崟鏀粯鏃堕棿 */
+ transactionDate?: string;
+ /** 鏌ヨ鍒扮殑璁㈠崟鐘舵�佷负FAIL澶辫触鎴朢EFUND閫�绁ㄦ椂锛岃繑鍥為敊璇唬鐮� */
+ transactionErrorCode?: string;
+ /** 鏌ヨ鍒扮殑璁㈠崟鐘舵�佷负FAIL澶辫触鎴朢EFUND閫�绁ㄦ椂锛岃繑鍥炲叿浣撶殑鍘熷洜銆� */
+ transactionFailReason?: string;
checkStatus?: EnterpriseRechargeStatusEnum;
checkTime?: string;
checkUserId?: string;
@@ -8034,7 +8043,15 @@
parkType?: string;
amount?: number;
remainAmount?: number;
+ transactionStatus?: EnumWalletTransactionStatus;
+ /** 璁㈠崟鏀粯鏃堕棿 */
+ transactionDate?: string;
+ /** 鏌ヨ鍒扮殑璁㈠崟鐘舵�佷负FAIL澶辫触鎴朢EFUND閫�绁ㄦ椂锛岃繑鍥為敊璇唬鐮� */
+ transactionErrorCode?: string;
+ /** 鏌ヨ鍒扮殑璁㈠崟鐘舵�佷负FAIL澶辫触鎴朢EFUND閫�绁ㄦ椂锛岃繑鍥炲叿浣撶殑鍘熷洜銆� */
+ transactionFailReason?: string;
checkStatus?: EnterpriseRechargeStatusEnum;
+ status?: GetEnterpriseDrawWithListOutputStatus;
checkTime?: string;
checkRemark?: string;
checkFileUrl?: string;
@@ -8047,6 +8064,8 @@
objectData?: any;
data?: GetEnterpriseDrawWithListOutput[];
}
+
+ type GetEnterpriseDrawWithListOutputStatus = 10 | 20 | 21 | 22 | 30;
interface GetEnterpriseMonthApplyFileOutput {
id?: string;
@@ -9681,6 +9700,13 @@
auditRemark?: string;
/** 瀹℃牳鏃堕棿 */
auditTime?: string;
+ transactionStatus?: EnumWalletTransactionStatus;
+ /** 璁㈠崟鏀粯鏃堕棿 */
+ transactionDate?: string;
+ /** 鏌ヨ鍒扮殑璁㈠崟鐘舵�佷负FAIL澶辫触鎴朢EFUND閫�绁ㄦ椂锛岃繑鍥為敊璇唬鐮� */
+ transactionErrorCode?: string;
+ /** 鏌ヨ鍒扮殑璁㈠崟鐘舵�佷负FAIL澶辫触鎴朢EFUND閫�绁ㄦ椂锛岃繑鍥炲叿浣撶殑鍘熷洜銆� */
+ transactionFailReason?: string;
financeAuditStatus?: EnumParkBountyTradeDetailAuditStatus;
/** 璐㈠姟瀹℃牳澶囨敞 */
financeAuditRemark?: string;
@@ -10617,6 +10643,10 @@
signName?: string;
}
+ interface GetSystemInfoOutput {
+ openTwoFactorLogin?: boolean;
+ }
+
interface GetTagsInput {
/** 绫诲瀷锛�0浜у搧鏍囩锛�1璧勮鏍囩锛�3蹇嵎璇勮鏍囩 */
type?: number;
@@ -11549,6 +11579,15 @@
interface IanaTimeZone {
timeZoneName?: string;
+ }
+
+ interface IdentityModelToken {
+ accessToken?: string;
+ expiresIn?: number;
+ creationTime?: string;
+ refreshToken?: string;
+ /** 鐢ㄦ埛Id */
+ userId?: string;
}
interface IdentityModelTokenCacheItem {
@@ -22785,6 +22824,33 @@
type TransferToStatusEnum = 1 | 2;
+ interface TwoFactorLoginPasswordInput {
+ clientId?: string;
+ /** 鐧诲綍鍚� */
+ loginName: string;
+ /** 鐧诲綍瀵嗙爜 */
+ password: string;
+ }
+
+ interface TwoFactorLoginPasswordOutput {
+ /** 鐧诲綍瀵嗛挜 */
+ loginKey?: string;
+ /** 鎵嬫満鍙� */
+ phoneNumber?: string;
+ }
+
+ interface TwoFactorLoginSendVerificationCodeInput {
+ /** 鐧诲綍瀵嗛挜 */
+ loginKey?: string;
+ }
+
+ interface TwoFactorLoginSmsInput {
+ /** 鐧诲綍瀵嗛挜 */
+ loginKey?: string;
+ /** 楠岃瘉鐮� */
+ code?: string;
+ }
+
interface TypeApiDescriptionModel {
baseType?: string;
isEnum?: boolean;
@@ -25929,6 +25995,8 @@
iv: string;
/** 鑾峰彇浼氳瘽瀵嗛挜 */
sessionKey: string;
+ /** 閴存潈 */
+ sign?: string;
/** 灏忕▼搴廜penId */
openId: string;
wxMiniApp?: WxMiniAppEnum;
@@ -25941,6 +26009,8 @@
iv: string;
/** 鑾峰彇浼氳瘽瀵嗛挜 */
sessionKey: string;
+ /** 閴存潈 */
+ sign?: string;
/** 灏忕▼搴廜penId */
openId: string;
wxMiniApp?: WxMiniAppEnum;
diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts
index 49bd21d..c226b2e 100644
--- a/src/store/modules/user.ts
+++ b/src/store/modules/user.ts
@@ -68,15 +68,7 @@
.then((res) => {
if (res) {
console.log('res: ', res);
- this.setToken(res.accessToken);
-
- const accountInfo = getAccountInfoFromAccessToken(res.accessToken);
-
- this.setName(accountInfo.name);
- this.setAccountInfo(accountInfo);
-
- // 鑾峰彇鐢ㄦ埛淇℃伅
- this.setUserInfo(res);
+ this.loginSuccess(res);
resolve();
}
@@ -87,6 +79,27 @@
});
},
+ async twoFactorLoginSms(params: API.TwoFactorLoginSmsInput) {
+ try {
+ let res = await accountServices.twoFactorLoginSms(params, { showLoading: false });
+ if (res) {
+ this.loginSuccess(res);
+ }
+ } catch (error) {}
+ },
+
+ loginSuccess(res: API.IdentityModelToken) {
+ this.setToken(res.accessToken);
+
+ const accountInfo = getAccountInfoFromAccessToken(res.accessToken);
+
+ this.setName(accountInfo.name);
+ this.setAccountInfo(accountInfo);
+
+ // 鑾峰彇鐢ㄦ埛淇℃伅
+ this.setUserInfo(res);
+ },
+
// 鐧诲嚭 娓呯┖缂撳瓨
logout(redirectPath = '/') {
return new Promise(async (resolve) => {
diff --git a/src/style/element/element-plus.scss b/src/style/element/element-plus.scss
index ab5713f..4262378 100644
--- a/src/style/element/element-plus.scss
+++ b/src/style/element/element-plus.scss
@@ -129,3 +129,10 @@
}
}
}
+
+.send-code-message-box {
+ .el-message-box__container,
+ .el-message-box__message {
+ width: 100%;
+ }
+}
diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue
index 774987f..3d476ee 100644
--- a/src/views/Login/Login.vue
+++ b/src/views/Login/Login.vue
@@ -137,6 +137,8 @@
import closeEye from '@/assets/svgIcons/close_eye.svg?component';
import openEye from '@/assets/svgIcons/close_eye.svg?component';
+import * as accountServices from '@/services/api/Account';
+import SendVerificationCodeView from './components/SendVerificationCodeView.vue';
// import { useSettingStoreHook } from '@/store/modules/settings';
import Config from '@config/config';
@@ -175,16 +177,44 @@
return;
}
loading.value = true;
- await userStore.loginByUsername({
- userName: unref(user),
- userPassword: unref(pwd),
- clientId: 'goverend-admin-app-client',
- });
- loading.value = false;
- router.push({
- path: redirect.value || '/',
- query: otherQuery.value,
- });
+ let systemInfo = await getSystemInfo();
+ if (systemInfo.openTwoFactorLogin) {
+ let twoFactorLoginPasswordRes = await accountServices.twoFactorLoginPassword({
+ loginName: unref(user),
+ password: unref(pwd),
+ clientId: 'goverend-admin-app-client',
+ });
+ loading.value = false;
+ ElMessageBox({
+ title: `鍙戦�侀獙璇佺爜鍒�${twoFactorLoginPasswordRes.phoneNumber}`,
+ customClass: 'send-code-message-box',
+ //@ts-ignore
+ modalClass: 'send-code-message-box-model',
+ showConfirmButton: false,
+ message: h(SendVerificationCodeView, {
+ phoneNumber: twoFactorLoginPasswordRes.phoneNumber,
+ loginKey: twoFactorLoginPasswordRes.loginKey,
+ onSuccess: () => {
+ router.push({
+ path: redirect.value || '/',
+ query: otherQuery.value,
+ });
+ document.querySelector('.send-code-message-box-model').remove();
+ },
+ }),
+ });
+ } else {
+ await userStore.loginByUsername({
+ userName: unref(user),
+ userPassword: unref(pwd),
+ clientId: 'goverend-admin-app-client',
+ });
+ loading.value = false;
+ router.push({
+ path: redirect.value || '/',
+ query: otherQuery.value,
+ });
+ }
} catch (error) {
console.log(error);
// ElMessage({
@@ -195,6 +225,13 @@
};
const beforeLog = useDebounceFn(onLogin, 1000);
+async function getSystemInfo() {
+ let res = await accountServices.getSystemInfo({
+ showLoading: false,
+ });
+ return res;
+}
+
function onUserFocus() {
addClass(document.querySelector('.user'), 'focus');
}
diff --git a/src/views/Login/components/SendVerificationCodeView.vue b/src/views/Login/components/SendVerificationCodeView.vue
new file mode 100644
index 0000000..6af7c12
--- /dev/null
+++ b/src/views/Login/components/SendVerificationCodeView.vue
@@ -0,0 +1,97 @@
+<template>
+ <ProForm :model="form" ref="formRef" class="el-message-box__input">
+ <ProFormItemV2
+ prop="code"
+ :check-rules="[{ message: '璇疯緭鍏ラ獙璇佺爜' }]"
+ label-width="0px"
+ class="pro-form-item-label-hidden"
+ >
+ <ProFormText v-model.trim="form.code" placeholder="璇疯緭鍏ラ獙璇佺爜">
+ <template #suffix>
+ <ProFormCaptcha
+ :onGetCaptcha="onGetCaptcha"
+ phonePropName="loginKey"
+ class="code-btn"
+ link
+ ></ProFormCaptcha>
+ </template>
+ </ProFormText>
+ </ProFormItemV2>
+ <div class="el-message-box__btns" style="padding-top: 0">
+ <el-button type="primary" @click="handleConfirm" :loading="loading">纭畾</el-button>
+ </div>
+ </ProForm>
+</template>
+
+<script setup lang="ts">
+import { ProForm, ProFormText, ProFormCaptcha, ProFormItemV2 } from '@bole-core/components';
+import { FormInstance } from 'element-plus';
+import * as accountServices from '@/services/api/Account';
+import { useUserStore } from '@/store/modules/user';
+
+defineOptions({
+ name: 'SendVerificationCodeView',
+});
+
+type Props = {
+ /** 鐧诲綍瀵嗛挜 */
+ loginKey?: string;
+ /** 鎵嬫満鍙� */
+ phoneNumber?: string;
+};
+
+const props = withDefaults(defineProps<Props>(), {});
+
+const emit = defineEmits<{
+ (e: 'success'): void;
+}>();
+
+const form = reactive({
+ loginKey: props.loginKey,
+ code: '',
+});
+
+const loading = ref(false);
+
+async function onGetCaptcha() {
+ await accountServices.twoFactorLoginSendVerificationCode(
+ {
+ loginKey: props.loginKey,
+ },
+ { showLoading: false }
+ );
+}
+
+const formRef = ref<FormInstance>();
+
+const handleConfirm = () => {
+ if (!formRef.value) return;
+ formRef.value.validate((valid) => {
+ if (valid) {
+ twoFactorLoginSms();
+ } else {
+ return;
+ }
+ });
+};
+
+const userStore = useUserStore();
+
+async function twoFactorLoginSms() {
+ try {
+ loading.value = true;
+ await userStore.twoFactorLoginSms({
+ loginKey: props.loginKey,
+ code: form.code,
+ });
+ emit('success');
+ } catch (error) {
+ } finally {
+ loading.value = false;
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+@use '@/style/common.scss' as *;
+</style>
--
Gitblit v1.9.1