From 7aff45ddf5057a9e0bf76a8d760c81faaad23169 Mon Sep 17 00:00:00 2001 From: lzh Date: Tue, 24 Mar 2026 09:19:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(@vben/web-antd):=20=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=89=AB=E7=A0=81=E7=99=BB=E5=BD=95=E9=80=82?= =?UTF-8?q?=E9=85=8D=20hash=20=E8=B7=AF=E7=94=B1=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hash 路由下 OAuth 回调 code/state 在 URL query 中,Vue Router 读不到。登录流程同绑定流程一样通过 sessionStorage 中转参数, 路由守卫检测后重定向到 social-login 页面完成登录。 Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/web-antd/src/router/guard.ts | 45 ++++++++++++------- .../src/views/_core/authentication/login.vue | 15 +++---- .../_core/authentication/social-login.vue | 22 ++++++--- .../_core/profile/modules/user-social.vue | 1 + 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/apps/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts index 588545278..c0749c703 100644 --- a/apps/web-antd/src/router/guard.ts +++ b/apps/web-antd/src/router/guard.ts @@ -50,22 +50,29 @@ function setupCommonGuard(router: Router) { */ function setupAccessGuard(router: Router) { // 一次性检查:hash 路由模式下,OAuth 回调的 code/state 在 URL query(?code=xxx)中, - // Vue Router 读不到,需要转存到 sessionStorage 供个人中心绑定页面使用 - const pendingBind = sessionStorage.getItem('socialBindAction'); - if (pendingBind === 'bind') { - const url = new URL(window.location.href); - const code = url.searchParams.get('code'); - const state = url.searchParams.get('state'); - if (code) { - sessionStorage.setItem('socialBindCode', code); - sessionStorage.setItem('socialBindState', state || ''); + // Vue Router 读不到,需要转存到 sessionStorage + const url = new URL(window.location.href); + const oauthCode = url.searchParams.get('code'); + if (oauthCode) { + const oauthState = url.searchParams.get('state') || ''; + const pendingBind = sessionStorage.getItem('socialBindAction'); + const pendingLogin = sessionStorage.getItem('socialLoginAction'); + if (pendingBind === 'bind') { + // 绑定回调:转存参数供个人中心使用 + sessionStorage.setItem('socialBindCode', oauthCode); + sessionStorage.setItem('socialBindState', oauthState); sessionStorage.removeItem('socialBindAction'); - // 清理 URL 中的 OAuth 参数 - url.searchParams.delete('code'); - url.searchParams.delete('state'); - url.searchParams.delete('appid'); - window.history.replaceState({}, '', url.toString()); + } else if (pendingLogin === 'login') { + // 登录回调:转存参数供 social-login 页面使用 + sessionStorage.setItem('socialLoginCode', oauthCode); + sessionStorage.setItem('socialLoginState', oauthState); + sessionStorage.removeItem('socialLoginAction'); } + // 清理 URL 中的 OAuth 参数 + url.searchParams.delete('code'); + url.searchParams.delete('state'); + url.searchParams.delete('appid'); + window.history.replaceState({}, '', url.toString()); } router.beforeEach(async (to, from) => { @@ -74,7 +81,15 @@ function setupAccessGuard(router: Router) { const authStore = useAuthStore(); const dictStore = useDictStore(); - // 社交绑定回调:检测到待处理的绑定参数,重定向到个人中心处理 + // 社交登录回调:重定向到 social-login 页面处理 + if ( + sessionStorage.getItem('socialLoginCode') && + to.path !== '/auth/social-login' + ) { + return '/auth/social-login'; + } + + // 社交绑定回调:重定向到个人中心处理 if ( sessionStorage.getItem('socialBindCode') && accessStore.accessToken && diff --git a/apps/web-antd/src/views/_core/authentication/login.vue b/apps/web-antd/src/views/_core/authentication/login.vue index e23292e61..d83aa3cee 100644 --- a/apps/web-antd/src/views/_core/authentication/login.vue +++ b/apps/web-antd/src/views/_core/authentication/login.vue @@ -97,15 +97,12 @@ async function handleThirdLogin(type: number) { return; } try { - // 计算 redirectUri - // tricky: type、redirect 需要先 encode 一次,否则钉钉回调会丢失。配合 social-login.vue#getUrlValue() 使用 - const redirectUri = `${ - location.origin - }/auth/social-login?${encodeURIComponent( - `type=${type}&redirect=${redirect || '/'}`, - )}`; - - // 进行跳转 + // hash 路由模式下,OAuth 回调的 code 在 URL query 中,Vue Router 读不到 + // 通过 sessionStorage 传递参数,redirect_uri 只用 origin + sessionStorage.setItem('socialLoginType', String(type)); + sessionStorage.setItem('socialLoginRedirect', (redirect as string) || '/'); + sessionStorage.setItem('socialLoginAction', 'login'); + const redirectUri = location.origin; window.location.href = await socialAuthRedirect(type, redirectUri); } catch (error) { console.error('第三方登录处理失败:', error); diff --git a/apps/web-antd/src/views/_core/authentication/social-login.vue b/apps/web-antd/src/views/_core/authentication/social-login.vue index 1052a9a18..fea7df5ea 100644 --- a/apps/web-antd/src/views/_core/authentication/social-login.vue +++ b/apps/web-antd/src/views/_core/authentication/social-login.vue @@ -70,13 +70,25 @@ async function fetchTenantList() { } /** 尝试登录:当账号已经绑定,socialLogin 会直接获得 token */ -const socialType = Number(getUrlValue('type')); -const redirect = getUrlValue('redirect'); -const socialCode = query?.code as string; -const socialState = query?.state as string; +// 优先从 sessionStorage 读取参数(hash 路由模式下 OAuth 回调参数在 URL query 中,Vue Router 读不到) +const socialType = Number( + sessionStorage.getItem('socialLoginType') || getUrlValue('type'), +); +const redirect = + sessionStorage.getItem('socialLoginRedirect') || getUrlValue('redirect'); +const socialCode = (sessionStorage.getItem('socialLoginCode') || + query?.code) as string; +const socialState = (sessionStorage.getItem('socialLoginState') || + query?.state) as string; +// 读取后清理 +sessionStorage.removeItem('socialLoginType'); +sessionStorage.removeItem('socialLoginRedirect'); +sessionStorage.removeItem('socialLoginCode'); +sessionStorage.removeItem('socialLoginState'); + async function tryLogin() { // 用于登录后,基于 redirect 的重定向 - if (redirect) { + if (redirect && redirect !== '/') { await router.replace({ query: { ...query, diff --git a/apps/web-antd/src/views/_core/profile/modules/user-social.vue b/apps/web-antd/src/views/_core/profile/modules/user-social.vue index 9f51a7c5f..ceed9bfc4 100644 --- a/apps/web-antd/src/views/_core/profile/modules/user-social.vue +++ b/apps/web-antd/src/views/_core/profile/modules/user-social.vue @@ -2,6 +2,7 @@ import type { SystemSocialUserApi } from '#/api/system/social/user'; import { computed, onMounted, ref } from 'vue'; + import { confirm } from '@vben/common-ui'; import { DICT_TYPE, SystemUserSocialTypeEnum } from '@vben/constants'; import { getDictLabel } from '@vben/hooks';