ForcePilot/web/src/views/OIDCCallbackView.vue
Your Name d6a452434e chore: 合并上游 main 分支最新代码,解决路由冲突
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 17:35:58 +08:00

189 lines
4.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="oidc-callback-view">
<div class="callback-container">
<div v-if="loading" class="loading-section">
<a-spin size="large" />
<p class="loading-text">正在处理登录...</p>
</div>
<div v-else-if="error" class="error-section">
<a-result status="error" :title="errorTitle" :sub-title="errorMessage">
<template #extra>
<a-button type="primary" @click="goToLogin"> 返回登录页 </a-button>
</template>
</a-result>
</div>
<div v-else class="success-section">
<a-result status="success" title="登录成功" sub-title="正在跳转...">
<template #icon>
<a-spin />
</template>
</a-result>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { useAgentStore } from '@/stores/agent'
import { authApi } from '@/apis/auth_api'
import { message } from 'ant-design-vue'
import { clearAutoStartAttempt } from '@/utils/oidcAutoStart'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const agentStore = useAgentStore()
// 状态
const loading = ref(true)
const error = ref(false)
const errorTitle = ref('登录失败')
const errorMessage = ref('处理登录请求时发生错误')
// 返回登录页
const goToLogin = () => {
router.push('/login')
}
// 处理 OIDC 回调 - 从 URL 参数中获取一次性 code
const handleCallback = async () => {
try {
const code = route.query.code
// 检查必要的参数
if (!code || typeof code !== 'string') {
loading.value = false
error.value = true
errorTitle.value = '参数错误'
errorMessage.value = '缺少有效的登录 code请重新登录'
return
}
const tokenData = await authApi.exchangeOIDCCode(code)
await router.replace({ path: route.path, query: {} })
// 更新用户状态
userStore.token = tokenData.access_token
userStore.userId = tokenData.uid
userStore.username = tokenData.username
userStore.uid = tokenData.uid || ''
userStore.phoneNumber = tokenData.phone_number || ''
userStore.avatar = tokenData.avatar || ''
userStore.userRole = tokenData.role || 'user'
userStore.departmentId = tokenData.department_id || null
userStore.departmentName = tokenData.department_name || ''
// 保存 token 到 localStorage
localStorage.setItem('user_token', tokenData.access_token)
// 显示成功消息
message.success('登录成功')
// 获取重定向路径并清理 OIDC 相关标记
const redirectPath = sessionStorage.getItem('oidc_redirect') || '/'
sessionStorage.removeItem('oidc_redirect')
clearAutoStartAttempt()
loading.value = false
// 延迟跳转,让用户看到成功提示
setTimeout(async () => {
// 跳转
if (redirectPath === '/') {
try {
await agentStore.initialize()
router.push('/agent')
} catch (err) {
console.error('获取智能体信息失败:', err)
router.push('/agent')
}
} else {
router.push(redirectPath)
}
}, 500)
} catch (err) {
console.error('OIDC 回调处理失败:', err)
loading.value = false
error.value = true
errorTitle.value = '登录失败'
errorMessage.value = err?.message || '处理登录请求时发生错误,请重试'
}
}
// 组件挂载时处理回调
onMounted(async () => {
// 如果已登录,跳转到首页
if (userStore.isLoggedIn) {
router.push('/')
return
}
await handleCallback()
})
</script>
<style lang="less" scoped>
.oidc-callback-view {
min-height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--gray-10);
background-image: radial-gradient(var(--gray-200) 1px, transparent 1px);
background-size: 24px 24px;
}
.callback-container {
width: 100%;
max-width: 500px;
padding: 40px;
background: var(--gray-0);
border-radius: 16px;
box-shadow: 0 4px 20px var(--shadow-1);
}
.loading-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
.loading-text {
font-size: 16px;
color: var(--gray-600);
margin: 0;
}
}
.error-section,
.success-section {
:deep(.ant-result) {
padding: 0;
.ant-result-title {
font-size: 20px;
color: var(--gray-800);
}
.ant-result-subtitle {
font-size: 14px;
color: var(--gray-500);
}
}
}
@media (max-width: 576px) {
.callback-container {
margin: 20px;
padding: 30px 20px;
}
}
</style>