'use client' import { useEffect, useRef, useState, Suspense } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { normalizeInternalRedirectPath } from '@/lib/safe-redirect' const DINGTALK_DIRECT_LOGIN_SOURCE = 'work_notice_direct' function parseDingtalkStateSource(value: string) { if (!value) return '' let raw = value try { raw = decodeURIComponent(raw) } catch { // URLSearchParams 通常已经解码,这里仅兼容手工拼接的 URL。 } try { const data = JSON.parse(raw) return typeof data?.source === 'string' ? data.source : '' } catch { return '' } } function DingtalkCallbackContent() { const router = useRouter() const searchParams = useSearchParams() const handledRef = useRef(false) const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading') const [message, setMessage] = useState('正在处理钉钉登录...') useEffect(() => { const handleDingtalkLogin = async () => { try { if (handledRef.current) { return } handledRef.current = true const code = searchParams?.get('code') || searchParams?.get('authCode') const state = searchParams?.get('state') || 'default' if (!code) { throw new Error('授权失败:缺少授权码') } // 解析state参数,获取原始访问地址 let originUrl = '' let redirectPath = '/' try { const stateData = JSON.parse(decodeURIComponent(state)) originUrl = stateData.origin || '' redirectPath = normalizeInternalRedirectPath(stateData.redirect) } 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.opener) { // 新窗口模式:通知打开窗口的父页面 window.opener.postMessage( { type: 'dingtalk_need_complete_profile', userInfo: data.user, }, '*' ) window.close() } else 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') // 通知父窗口登录成功(如果在 iframe 或新窗口中) if (window.opener) { // 新窗口模式:通知打开窗口的父页面 window.opener.postMessage( { type: 'dingtalk_login_success', userInfo: data.user, originUrl: originUrl, // 携带原始地址 redirectUrl: redirectPath, }, '*' ) } else if (window.parent !== window) { // iframe模式:通知父窗口(兼容旧代码) window.parent.postMessage( { type: 'dingtalk_login_success', userInfo: data.user, originUrl: originUrl, // 携带原始地址 redirectUrl: redirectPath, }, '*' ) } else { await new Promise((resolve) => setTimeout(resolve, 200)) const sessionResponse = await fetch('/api/auth/session', { cache: 'no-store', credentials: 'include', }) const sessionData = await sessionResponse.json().catch(() => null) if (!sessionData?.user) { throw new Error('登录会话写入失败,请重新使用钉钉登录') } setTimeout(() => { window.location.href = redirectPath }, 300) } } catch (error: any) { console.error('钉钉登录错误:', error) setStatus('error') setMessage(error.message || '登录失败,请稍后再试') const state = searchParams?.get('state') || '' const isDirectWorkNoticeLogin = parseDingtalkStateSource(state) === DINGTALK_DIRECT_LOGIN_SOURCE // 工作通知直达入口不再退回登录页自动重试,避免重复消费钉钉 code 后形成循环。 if (isDirectWorkNoticeLogin) { return } // 3秒后关闭或跳转 setTimeout(() => { if (window.opener) { // 新窗口模式:通知打开窗口的父页面 window.opener.postMessage({ type: 'dingtalk_login_error', message: error.message }, '*') window.close() } else if (window.parent !== window) { // iframe模式:通知父窗口(兼容旧代码) window.parent.postMessage({ type: 'dingtalk_login_error', message: error.message }, '*') } else { router.push('/login') } }, 3000) } } handleDingtalkLogin() }, [searchParams, router]) return (
{message}
正在加载...