From ec502ee4621c1d0e25ba2809428144a2eba3b1cc Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 5 Mar 2025 18:44:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=89=AB=E7=A0=81?= =?UTF-8?q?=E7=99=BB=E5=BD=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app-antd/src/locales/langs/en-US/abp.json | 3 + .../app-antd/src/locales/langs/zh-CN/abp.json | 3 + apps/vben5/apps/app-antd/src/store/auth.ts | 101 ++++++---- .../_core/authentication/qrcode-login.vue | 18 +- .../packages/@abp/account/src/api/index.ts | 1 + .../@abp/account/src/api/useQrCodeLoginApi.ts | 72 +++++++ .../account/src/components/QrCodeLogin.vue | 178 ++++++++++++++++++ .../src/components/components/LoginTitle.vue | 13 ++ .../@abp/account/src/components/index.ts | 1 + .../packages/@abp/account/src/types/qrcode.ts | 27 +++ .../packages/@abp/core/src/types/global.ts | 1 + 11 files changed, 375 insertions(+), 43 deletions(-) create mode 100644 apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts create mode 100644 apps/vben5/packages/@abp/account/src/components/QrCodeLogin.vue create mode 100644 apps/vben5/packages/@abp/account/src/components/components/LoginTitle.vue create mode 100644 apps/vben5/packages/@abp/account/src/types/qrcode.ts diff --git a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json index 7e8a3c8fa..af1be1952 100644 --- a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json +++ b/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json @@ -8,6 +8,9 @@ "phoneNumber": "Phone Number", "getCode": "Get Code", "code": "Code" + }, + "qrcodeLogin": { + "scaned": "Please confirm login on your phone." } }, "manage": { diff --git a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json index 4e424b605..1c4595dbb 100644 --- a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json +++ b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json @@ -8,6 +8,9 @@ "phoneNumber": "手机号码", "getCode": "获取验证码", "code": "验证码" + }, + "qrcodeLogin": { + "scaned": "请在手机上确认登录." } }, "manage": { diff --git a/apps/vben5/apps/app-antd/src/store/auth.ts b/apps/vben5/apps/app-antd/src/store/auth.ts index 6de88173e..d6700622b 100644 --- a/apps/vben5/apps/app-antd/src/store/auth.ts +++ b/apps/vben5/apps/app-antd/src/store/auth.ts @@ -1,3 +1,5 @@ +import type { TokenResult } from '@abp/account'; + import type { Recordable, UserInfo } from '@vben/types'; import { ref } from 'vue'; @@ -6,7 +8,7 @@ import { useRouter } from 'vue-router'; import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants'; import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; -import { useTokenApi, useUserInfoApi } from '@abp/account'; +import { useQrCodeLoginApi, useTokenApi, useUserInfoApi } from '@abp/account'; import { Events, useAbpStore, useEventBus } from '@abp/core'; import { notification } from 'ant-design-vue'; import { defineStore } from 'pinia'; @@ -17,6 +19,7 @@ import { $t } from '#/locales'; export const useAuthStore = defineStore('auth', () => { const { publish } = useEventBus(); const { loginApi } = useTokenApi(); + const { loginApi: qrcodeLoginApi } = useQrCodeLoginApi(); const { getUserInfoApi } = useUserInfoApi(); const { getConfigApi } = useAbpConfigApi(); const accessStore = useAccessStore(); @@ -26,6 +29,14 @@ export const useAuthStore = defineStore('auth', () => { const loginLoading = ref(false); + async function qrcodeLogin( + key: string, + onSuccess?: () => Promise | void, + ) { + const result = await qrcodeLoginApi(key); + return await _loginSuccess(result, onSuccess); + } + /** * 异步处理登录操作 * Asynchronously handle the login process @@ -35,46 +46,8 @@ export const useAuthStore = defineStore('auth', () => { params: Recordable, onSuccess?: () => Promise | void, ) { - // 异步处理用户登录操作并获取 accessToken - let userInfo: null | UserInfo = null; - try { - loginLoading.value = true; - const loginResult = await loginApi(params as any); - const { accessToken, tokenType, refreshToken } = loginResult; - // 如果成功获取到 accessToken - if (accessToken) { - accessStore.setAccessToken(`${tokenType} ${accessToken}`); - accessStore.setRefreshToken(refreshToken); - - userInfo = await fetchUserInfo(); - - userStore.setUserInfo(userInfo); - - publish(Events.UserLogin, userInfo); - - if (accessStore.loginExpired) { - accessStore.setLoginExpired(false); - } else { - onSuccess - ? await onSuccess?.() - : await router.push(userInfo.homePath || DEFAULT_HOME_PATH); - } - - if (userInfo?.realName) { - notification.success({ - description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, - duration: 3, - message: $t('authentication.loginSuccess'), - }); - } - } - } finally { - loginLoading.value = false; - } - - return { - userInfo, - }; + const result = await loginApi(params as any); + return await _loginSuccess(result, onSuccess); } async function logout(redirect: boolean = true) { @@ -129,6 +102,51 @@ export const useAuthStore = defineStore('auth', () => { return userInfo; } + async function _loginSuccess( + loginResult: TokenResult, + onSuccess?: () => Promise | void, + ) { + // 异步处理用户登录操作并获取 accessToken + let userInfo: null | UserInfo = null; + try { + loginLoading.value = true; + const { accessToken, tokenType, refreshToken } = loginResult; + // 如果成功获取到 accessToken + if (accessToken) { + accessStore.setAccessToken(`${tokenType} ${accessToken}`); + accessStore.setRefreshToken(refreshToken); + + userInfo = await fetchUserInfo(); + + userStore.setUserInfo(userInfo); + + publish(Events.UserLogin, userInfo); + + if (accessStore.loginExpired) { + accessStore.setLoginExpired(false); + } else { + onSuccess + ? await onSuccess?.() + : await router.push(userInfo.homePath || DEFAULT_HOME_PATH); + } + + if (userInfo?.realName) { + notification.success({ + description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, + duration: 3, + message: $t('authentication.loginSuccess'), + }); + } + } + } finally { + loginLoading.value = false; + } + + return { + userInfo, + }; + } + function $reset() { loginLoading.value = false; } @@ -136,6 +154,7 @@ export const useAuthStore = defineStore('auth', () => { return { $reset, authLogin, + qrcodeLogin, fetchUserInfo, loginLoading, logout, diff --git a/apps/vben5/apps/app-antd/src/views/_core/authentication/qrcode-login.vue b/apps/vben5/apps/app-antd/src/views/_core/authentication/qrcode-login.vue index 23f5f2dad..9de957fdc 100644 --- a/apps/vben5/apps/app-antd/src/views/_core/authentication/qrcode-login.vue +++ b/apps/vben5/apps/app-antd/src/views/_core/authentication/qrcode-login.vue @@ -1,10 +1,24 @@ diff --git a/apps/vben5/packages/@abp/account/src/api/index.ts b/apps/vben5/packages/@abp/account/src/api/index.ts index 2aa1d0ab1..88f26f955 100644 --- a/apps/vben5/packages/@abp/account/src/api/index.ts +++ b/apps/vben5/packages/@abp/account/src/api/index.ts @@ -1,5 +1,6 @@ export { useAccountApi } from './useAccountApi'; export { useMySessionApi } from './useMySessionApi'; export { useProfileApi } from './useProfileApi'; +export { useQrCodeLoginApi } from './useQrCodeLoginApi'; export { useTokenApi } from './useTokenApi'; export { useUserInfoApi } from './useUserInfoApi'; diff --git a/apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts b/apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts new file mode 100644 index 000000000..034292d22 --- /dev/null +++ b/apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts @@ -0,0 +1,72 @@ +import type { + GenerateQrCodeResult, + QrCodeUserInfoResult, +} from '../types/qrcode'; +import type { OAuthTokenResult } from '../types/token'; + +import { useAppConfig } from '@vben/hooks'; + +import { useRequest } from '@abp/request'; + +export function useQrCodeLoginApi() { + const { cancel, request } = useRequest(); + + /** + * 生成登录二维码 + * @returns 二维码信息 + */ + function generateApi(): Promise { + return request('/api/account/qrcode/generate', { + method: 'POST', + }); + } + + /** + * 检查二维码状态 + * @param key 二维码Key + * @returns 二维码信息 + */ + function checkCodeApi(key: string): Promise { + return request(`/api/account/qrcode/${key}/check`, { + method: 'GET', + }); + } + + /** + * 二维码登录 + * @param key 二维码Key + * @returns 用户token + */ + async function loginApi(key: string) { + const { audience, clientId, clientSecret } = useAppConfig( + import.meta.env, + import.meta.env.PROD, + ); + const result = await request('/connect/token', { + data: { + client_id: clientId, + client_secret: clientSecret, + grant_type: 'qr_code', + qrcode_key: key, + scope: audience, + }, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + }); + return { + accessToken: result.access_token, + expiresIn: result.expires_in, + refreshToken: result.refresh_token, + tokenType: result.token_type, + }; + } + + return { + cancel, + checkCodeApi, + generateApi, + loginApi, + }; +} diff --git a/apps/vben5/packages/@abp/account/src/components/QrCodeLogin.vue b/apps/vben5/packages/@abp/account/src/components/QrCodeLogin.vue new file mode 100644 index 000000000..315db9f9c --- /dev/null +++ b/apps/vben5/packages/@abp/account/src/components/QrCodeLogin.vue @@ -0,0 +1,178 @@ + + + diff --git a/apps/vben5/packages/@abp/account/src/components/components/LoginTitle.vue b/apps/vben5/packages/@abp/account/src/components/components/LoginTitle.vue new file mode 100644 index 000000000..3ed9a6bef --- /dev/null +++ b/apps/vben5/packages/@abp/account/src/components/components/LoginTitle.vue @@ -0,0 +1,13 @@ + diff --git a/apps/vben5/packages/@abp/account/src/components/index.ts b/apps/vben5/packages/@abp/account/src/components/index.ts index 691de32ef..807e2f91a 100644 --- a/apps/vben5/packages/@abp/account/src/components/index.ts +++ b/apps/vben5/packages/@abp/account/src/components/index.ts @@ -1,2 +1,3 @@ export { default as MyProfile } from './MyProfile.vue'; export { default as MySetting } from './MySetting.vue'; +export { default as QrCodeLogin } from './QrCodeLogin.vue'; diff --git a/apps/vben5/packages/@abp/account/src/types/qrcode.ts b/apps/vben5/packages/@abp/account/src/types/qrcode.ts new file mode 100644 index 000000000..e58c0acfa --- /dev/null +++ b/apps/vben5/packages/@abp/account/src/types/qrcode.ts @@ -0,0 +1,27 @@ +export enum QrCodeStatus { + /** 已确认 */ + Confirmed = 10, + /** 创建 */ + Created = 0, + /** 无效 */ + Invalid = -1, + /** 已扫描 */ + Scaned = 5, +} + +interface GenerateQrCodeResult { + key: string; +} + +interface QrCodeInfoResult { + key: string; + status: QrCodeStatus; +} + +interface QrCodeUserInfoResult extends QrCodeInfoResult { + picture?: string; + userId?: string; + userName?: string; +} + +export type { GenerateQrCodeResult, QrCodeUserInfoResult }; diff --git a/apps/vben5/packages/@abp/core/src/types/global.ts b/apps/vben5/packages/@abp/core/src/types/global.ts index 8da06034a..4230d7337 100644 --- a/apps/vben5/packages/@abp/core/src/types/global.ts +++ b/apps/vben5/packages/@abp/core/src/types/global.ts @@ -43,6 +43,7 @@ interface IHasExtraProperties { } /** 选择项 */ interface ISelectionStringValueItem { + [key: string]: any; /** 选择项显示文本多语言对象 */ displayText: LocalizableStringInfo; /** 选择项值 */