diff --git a/apps/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts index 8b88c834e..588545278 100644 --- a/apps/web-antd/src/router/guard.ts +++ b/apps/web-antd/src/router/guard.ts @@ -49,12 +49,40 @@ function setupCommonGuard(router: Router) { * @param 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 || ''); + sessionStorage.removeItem('socialBindAction'); + // 清理 URL 中的 OAuth 参数 + url.searchParams.delete('code'); + url.searchParams.delete('state'); + url.searchParams.delete('appid'); + window.history.replaceState({}, '', url.toString()); + } + } + router.beforeEach(async (to, from) => { const accessStore = useAccessStore(); const userStore = useUserStore(); const authStore = useAuthStore(); const dictStore = useDictStore(); + // 社交绑定回调:检测到待处理的绑定参数,重定向到个人中心处理 + if ( + sessionStorage.getItem('socialBindCode') && + accessStore.accessToken && + to.path !== '/profile' + ) { + return '/profile'; + } + // 基本路由,这些路由不需要进入权限拦截 if (coreRouteNames.includes(to.name as string)) { if (to.path === LOGIN_PATH && 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 6cf0be002..e23292e61 100644 --- a/apps/web-antd/src/views/_core/authentication/login.vue +++ b/apps/web-antd/src/views/_core/authentication/login.vue @@ -241,22 +241,22 @@ const formSchema = computed((): VbenFormSchema[] => { - - + --> + 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 e4927bf99..9f51a7c5f 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,13 +2,11 @@ import type { SystemSocialUserApi } from '#/api/system/social/user'; import { computed, onMounted, ref } from 'vue'; -import { useRoute } from 'vue-router'; - import { confirm } from '@vben/common-ui'; import { DICT_TYPE, SystemUserSocialTypeEnum } from '@vben/constants'; import { getDictLabel } from '@vben/hooks'; import { $t } from '@vben/locales'; -import { formatDateTime, getUrlValue } from '@vben/utils'; +import { formatDateTime } from '@vben/utils'; import { Avatar, Button, Image, message, Tag, Tooltip } from 'ant-design-vue'; @@ -23,16 +21,11 @@ const emit = defineEmits<{ (e: 'update:activeName', v: string): void; }>(); -const route = useRoute(); - /** 已经绑定的平台 */ const bindList = ref([]); -/** 暂不支持钉钉和企业微信,后续开放时移除此过滤 */ -const HIDDEN_SOCIAL_TYPES = new Set([ - SystemUserSocialTypeEnum.DINGTALK.type, - SystemUserSocialTypeEnum.WECHAT_ENTERPRISE.type, -]); +/** 暂不支持钉钉,后续开放时移除此过滤 */ +const HIDDEN_SOCIAL_TYPES = new Set([SystemUserSocialTypeEnum.DINGTALK.type]); interface SocialBindItem { title: string; @@ -47,7 +40,7 @@ const allBindList = computed(() => { .filter((social) => !HIDDEN_SOCIAL_TYPES.has(social.type)) .map((social) => { const socialUser = bindList.value.find( - (item) => item.type === social.type, + (item) => Number(item.type) === social.type, ); return { ...social, @@ -80,32 +73,43 @@ async function onBind(bind: SocialBindItem) { return; } try { - const redirectUri = `${location.origin}/profile?${encodeURIComponent(`type=${type}`)}`; + // 标记绑定操作,redirect_uri 只用 origin(hash 路由模式下不能带 # 路径) + sessionStorage.setItem('socialBindType', String(type)); + sessionStorage.setItem('socialBindAction', 'bind'); + const redirectUri = location.origin; window.location.href = await socialAuthRedirect(type, redirectUri); } catch (error) { console.error('社交绑定处理失败:', error); } } -/** 监听路由变化,处理社交绑定回调 */ -async function bindSocial() { - const type = Number(getUrlValue('type')); - const code = route.query.code as string; - const state = route.query.state as string; - if (!code) { +/** 处理社交绑定回调(参数由路由守卫从 URL query 转存到 sessionStorage) */ +async function processPendingBind() { + const code = sessionStorage.getItem('socialBindCode'); + const state = sessionStorage.getItem('socialBindState'); + const type = Number(sessionStorage.getItem('socialBindType')); + if (!code || !type) { return; } - await socialBind({ type, code, state }); - message.success('绑定成功'); - emit('update:activeName', 'userSocial'); - await loadBindList(); - window.history.replaceState({}, '', location.pathname); + // 立即清理,防止重复处理 + sessionStorage.removeItem('socialBindCode'); + sessionStorage.removeItem('socialBindState'); + sessionStorage.removeItem('socialBindType'); + try { + await socialBind({ type, code, state: state || '' }); + message.success('绑定成功'); + emit('update:activeName', 'userSocial'); + await loadBindList(); + } catch (error) { + console.error('社交绑定失败:', error); + message.error('绑定失败,请重试'); + } } /** 初始化 */ onMounted(async () => { await loadBindList(); - await bindSocial(); + await processPendingBind(); });