WuYongJie d1c6efc79d
Some checks failed
CI Check / check (push) Has been cancelled
feat: NAS 部署与 Harbor 推送流程
2025-12-03 14:39:06 +08:00

192 lines
6.7 KiB
TypeScript
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.

'use client'
import { useEffect, useState, Suspense } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { signIn } from 'next-auth/react'
function DingtalkCallbackContent() {
const router = useRouter()
const searchParams = useSearchParams()
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading')
const [message, setMessage] = useState('正在处理钉钉登录...')
useEffect(() => {
const handleDingtalkLogin = async () => {
try {
const code = searchParams.get('code')
const state = searchParams.get('state') || 'default'
if (!code) {
throw new Error('授权失败:缺少授权码')
}
// 解析state参数,获取原始访问地址
let originUrl = ''
try {
const stateData = JSON.parse(decodeURIComponent(state))
originUrl = stateData.origin || ''
} catch (e) {
// state解析失败,忽略
}
setMessage('正在验证授权信息...')
// 调用钉钉登录接口
const response = await fetch('/api/dingtalk/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code, state }),
})
const data = await response.json()
if (!response.ok || !data.success) {
throw new Error(data.message || '登录失败')
}
// 检查是否需要完善信息
if (data.needCompleteProfile) {
setMessage('首次登录,正在跳转到信息完善页面...')
setStatus('success')
// 跳转到信息完善页面
const userInfoParam = encodeURIComponent(JSON.stringify(data.user))
setTimeout(() => {
if (window.parent !== window) {
// 在 iframe 中,通知父窗口跳转
window.parent.postMessage(
{
type: 'dingtalk_need_complete_profile',
userInfo: data.user,
},
'*'
)
} else {
router.push(`/complete-profile?userInfo=${userInfoParam}`)
}
}, 1000)
return
}
setMessage('登录成功,正在跳转...')
setStatus('success')
// 检查来源:优先使用state中的origin,其次使用referrer
const isFromIntranet = originUrl && originUrl.includes('192.168.')
// 通知父窗口登录成功(如果在 iframe 中)
if (window.parent !== window) {
window.parent.postMessage(
{
type: 'dingtalk_login_success',
userInfo: data.user,
originUrl: originUrl // 携带原始地址
},
'*'
)
} else {
// 不在 iframe 中(直接访问回调页面)
if (isFromIntranet) {
// 从内网来的:不在这里创建session,跳转回内网后由内网页面创建
setMessage('登录成功,正在返回内网...')
// 将用户信息作为参数传递,让内网页面创建session
window.location.href = `${originUrl}/login?dingtalk_success=true&email=${encodeURIComponent(data.user.email)}`
} else {
// 从外网来的:直接创建session并跳转
const signInResult = await signIn('credentials', {
email: data.user.email,
password: 'dingtalk_auto_login',
redirect: false,
})
if (signInResult?.error) {
throw new Error(signInResult.error)
}
setTimeout(() => {
router.push('/')
router.refresh()
}, 1000)
}
}
} catch (error: any) {
console.error('钉钉登录错误:', error)
setStatus('error')
setMessage(error.message || '登录失败,请稍后再试')
// 3秒后关闭或跳转
setTimeout(() => {
if (window.parent !== window) {
window.parent.postMessage({ type: 'dingtalk_login_error', message: error.message }, '*')
} else {
router.push('/login')
}
}, 3000)
}
}
handleDingtalkLogin()
}, [searchParams, router])
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100">
<div className="max-w-md w-full bg-white rounded-2xl shadow-xl p-8">
<div className="text-center">
{status === 'loading' && (
<div className="mb-4">
<div className="inline-block animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"></div>
</div>
)}
{status === 'success' && (
<div className="mb-4">
<svg className="mx-auto h-12 w-12 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
)}
{status === 'error' && (
<div className="mb-4">
<svg className="mx-auto h-12 w-12 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
)}
<h2 className={`text-xl font-semibold mb-2 ${
status === 'error' ? 'text-red-600' : 'text-slate-800 dark:text-slate-200'
}`}>
{status === 'loading' && '钉钉登录'}
{status === 'success' && '登录成功'}
{status === 'error' && '登录失败'}
</h2>
<p className="text-slate-600 dark:text-slate-400">{message}</p>
</div>
</div>
</div>
)
}
export default function DingtalkCallbackPage() {
return (
<Suspense fallback={
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100">
<div className="max-w-md w-full bg-white rounded-2xl shadow-xl p-8">
<div className="text-center">
<div className="mb-4">
<div className="inline-block animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"></div>
</div>
<h2 className="text-xl font-semibold mb-2 text-slate-800 dark:text-slate-200">钉钉登录</h2>
<p className="text-slate-600 dark:text-slate-400">正在加载...</p>
</div>
</div>
</div>
}>
<DingtalkCallbackContent />
</Suspense>
)
}