19ebb85209
Login.vue: - goCasdoorLogin() 前将 redirect 参数存入 sessionStorage - 登录成功后检查 sessionStorage 跳回原页面 - wecomLogin 流程同样支持 returnUrl WecomBind.vue: - onMounted 检测未登录 → 跳转 /login?redirect=/wecom-bind?token=xxx - 登录后自动回到绑定页面,auth.user 不再为空 Co-Authored-By: Claude <noreply@anthropic.com>
154 lines
4.1 KiB
Vue
154 lines
4.1 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const auth = useAuthStore()
|
|
const loading = ref(false)
|
|
|
|
// Check for Casdoor callback
|
|
onMounted(async () => {
|
|
const code = route.query.code as string
|
|
const state = route.query.state as string
|
|
|
|
if (code) {
|
|
loading.value = true
|
|
try {
|
|
await auth.casdoorLogin(code, state)
|
|
const returnUrl = sessionStorage.getItem('login_return_url')
|
|
if (returnUrl) { sessionStorage.removeItem('login_return_url'); router.push(returnUrl); return }
|
|
ElMessage.success('登录成功')
|
|
router.push(auth.isManager ? '/m' : '/')
|
|
} catch (e: any) {
|
|
ElMessage.error('登录失败: ' + (e.response?.data?.detail || e.message))
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
return
|
|
}
|
|
|
|
// Check for wecom code
|
|
const wecomCode = route.query.wecom_code as string
|
|
if (wecomCode) {
|
|
loading.value = true
|
|
try {
|
|
const res = await auth.wecomLogin(wecomCode)
|
|
if (res.data.need_bind) {
|
|
// Redirect to Casdoor for binding
|
|
window.location.href = res.data.casdoor_url
|
|
return
|
|
}
|
|
auth.saveLogin({
|
|
access_token: res.data.access_token,
|
|
user_id: res.data.user_id,
|
|
name: res.data.name,
|
|
role: res.data.role,
|
|
})
|
|
const returnUrl = sessionStorage.getItem('login_return_url')
|
|
if (returnUrl) { sessionStorage.removeItem('login_return_url'); router.push(returnUrl); return }
|
|
ElMessage.success('登录成功')
|
|
router.push(auth.isManager ? '/m' : '/')
|
|
} catch (e: any) {
|
|
ElMessage.error('企微登录失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
})
|
|
|
|
function goCasdoorLogin() {
|
|
// Save return URL before redirecting to Casdoor
|
|
const returnUrl = (route.query.redirect as string) || ''
|
|
if (returnUrl) {
|
|
sessionStorage.setItem('login_return_url', returnUrl)
|
|
}
|
|
const endpoint = import.meta.env.VITE_CASDOOR_ENDPOINT
|
|
const clientId = import.meta.env.VITE_CASDOOR_CLIENT_ID
|
|
const redirectUri = encodeURIComponent(window.location.origin + '/login')
|
|
const casdoorUrl = `${endpoint}/login/oauth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=openid+profile&state=login`
|
|
window.location.href = casdoorUrl
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="login-page">
|
|
<div class="login-card">
|
|
<div class="brand">
|
|
<h1>企迹</h1>
|
|
<p>政企周报管理系统</p>
|
|
</div>
|
|
<el-button
|
|
type="primary"
|
|
size="large"
|
|
:loading="loading"
|
|
@click="goCasdoorLogin"
|
|
style="width: 100%"
|
|
>
|
|
登录 / 注册
|
|
</el-button>
|
|
<p class="hint">使用 Casdoor 账号登录</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.login-page {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 100vh;
|
|
background: linear-gradient(135deg, #4f6ef7 0%, #7b93fa 40%, #a5b4fc 100%);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.login-page::before {
|
|
content: ''; position: absolute; width: 600px; height: 600px;
|
|
background: rgba(255,255,255,0.05); border-radius: 50%;
|
|
top: -200px; right: -200px;
|
|
}
|
|
.login-page::after {
|
|
content: ''; position: absolute; width: 400px; height: 400px;
|
|
background: rgba(255,255,255,0.04); border-radius: 50%;
|
|
bottom: -100px; left: -100px;
|
|
}
|
|
.login-card {
|
|
background: white;
|
|
border-radius: 20px;
|
|
padding: 48px 40px;
|
|
width: 380px;
|
|
max-width: 90vw;
|
|
box-shadow: 0 25px 60px rgba(0,0,0,0.15);
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
.brand {
|
|
text-align: center;
|
|
margin-bottom: 36px;
|
|
}
|
|
.brand h1 {
|
|
margin: 0;
|
|
font-size: 36px;
|
|
font-weight: 800;
|
|
letter-spacing: 4px;
|
|
background: linear-gradient(135deg, #4f6ef7, #7b93fa);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
.brand p {
|
|
margin: 10px 0 0;
|
|
color: var(--c-text-secondary);
|
|
font-size: 14px;
|
|
letter-spacing: 1px;
|
|
}
|
|
.hint {
|
|
text-align: center;
|
|
color: var(--c-text-muted);
|
|
font-size: 12px;
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|