import { useEffect, useState, useRef } from 'react' import { useNavigate } from 'react-router-dom' import { Toast } from 'antd-mobile' import { useUserStore } from '@/stores/user' import QRCodeLogin from './QRCodeLogin' import styles from './login.module.css' // 声明飞书 JSAPI 类型 declare global { interface Window { // 官方 tt API(H5 应用免登) tt?: any // 旧版 ttjsBridge(兼容性) ttjsBridge?: any // 其他可能的飞书 API h5sdk?: any bridge?: any _tt?: any } } export default function Login() { const navigate = useNavigate() const { login } = useUserStore() const [loading, setLoading] = useState(true) const [environment, setEnvironment] = useState<'feishu' | 'external' | null>(null) const [showQRCode, setShowQRCode] = useState(false) useEffect(() => { // 监听飞书登录成功事件 const handleFeishuLoginSuccess = ((e: any) => { console.log('🎉 收到飞书登录成功事件:', e.detail) const { user, token } = e.detail login(user, token) }) as EventListener window.addEventListener('feishu-login-success', handleFeishuLoginSuccess) return () => { window.removeEventListener('feishu-login-success', handleFeishuLoginSuccess) } }, []) useEffect(() => { // 打印详细的环境信息 const ua = navigator.userAgent const isInFeishu = ua.includes('Lark') || ua.includes('feishu') || ua.includes('lark') const currentUrl = window.location.href console.log('=== 环境检测 ===') console.log('当前 URL:', currentUrl) console.log('User Agent:', ua) console.log('在飞书中:', isInFeishu) console.log('window.tt:', window.tt) // 检查 URL 中是否来自飞书消息通知 const urlParams = new URLSearchParams(window.location.search) const authCode = urlParams.get('auth_code') const fromFeishu = urlParams.get('from') === 'feishu' // 关键修复:在飞书中但 tt 对象不存在时,刷新页面让飞书注入 tt 对象 // 增加多次重试机制 if (isInFeishu && !window.tt) { const reloadCount = parseInt(urlParams.get('reload') || '0') if (reloadCount < 3) { console.log(`⚠️ 在飞书中但 tt 对象不存在,第 ${reloadCount + 1} 次刷新页面`) const separator = currentUrl.indexOf('?') > 0 ? '&' : '?' // 清除旧的 reload 参数并添加新的 const newUrl = currentUrl.split('?')[0] + '?' + urlParams.toString().replace(/reload=\d+/g, '').replace(/^&+/, '') window.location.href = newUrl + (newUrl.includes('?') ? '&' : '?') + `reload=${reloadCount + 1}` return } else { console.log('❌ 多次刷新后 tt 对象仍不存在,放弃自动登录') } } if (authCode) { // 有授权码,直接处理登录 console.log('✅ Found auth_code in URL:', authCode.substring(0, 20) + '...') handleFeishuLogin(authCode) } else if (isInFeishu && window.tt) { // 飞书内且有 tt 对象:静默授权 console.log('✅ 飞书内环境,tt 对象可用,开始免登') setEnvironment('feishu') handleFeishuAutoLogin() } else if (isInFeishu) { // 飞书内但 tt 对象不可用:提示用户 console.log('⚠️ 在飞书内但 tt 对象不可用') setEnvironment('feishu') setLoading(false) Toast.show('请在飞书客户端中打开,或使用扫码登录') } else { // 飞书外:显示登录选项 console.log('📱 飞书外环境,显示登录选项') setEnvironment('external') setLoading(false) } }, []) // 飞书内静默授权 const handleFeishuAutoLogin = () => { console.log('🚀 开始飞书免登') console.log('🔍 window.tt:', window.tt) console.log('🔍 window.tt 类型:', typeof window.tt) const appId = 'cli_a8033d607cba500d' // 详细检查 tt 对象 if (window.tt) { console.log('✅ tt 对象存在') console.log('🔍 tt 的所有属性:', Object.keys(window.tt)) console.log('🔍 tt.requestAuthCode 类型:', typeof window.tt.requestAuthCode) // 检查 requestAuthCode 是否存在 if ('requestAuthCode' in window.tt) { console.log('✅ requestAuthCode 属性存在') console.log('🔍 类型:', typeof window.tt.requestAuthCode) } else { console.log('❌ requestAuthCode 属性不存在') } } else { console.log('❌ tt 对象不存在') setLoading(false) Toast.show('飞书 API 未加载,请刷新重试') return } // 等待一小段时间,确保 tt 完全初始化 setTimeout(() => { console.log('⏰ 延迟后再次检查 tt:', !!window.tt) if (!window.tt) { setLoading(false) Toast.show('飞书 API 加载失败') return } // 尝试调用 requestAuthCode try { console.log('🔧 调用 tt.requestAuthCode,appId:', appId) window.tt.requestAuthCode({ appId: appId, success: (res: any) => { console.log('✅ 成功获取授权码:', res) console.log('✅ code:', res.code?.substring(0, 30)) handleFeishuLogin(res.code) }, fail: (error: any) => { console.error('❌ requestAuthCode 失败:', error) console.error('❌ 错误详情:', JSON.stringify(error)) setLoading(false) Toast.show('免登失败:' + (error.errorMessage || error.errMsg || JSON.stringify(error))) }, }) } catch (e) { console.error('❌ 调用异常:', e) setLoading(false) Toast.show('免登失败,请使用其他方式登录') } }, 500) } // 跳转到飞书授权页面(飞书外扫码登录) const redirectToFeishu = () => { // 直接打开扫码登录弹窗 setShowQRCode(true) } // 处理飞书回调(飞书外扫码后回调) const handleFeishuCallback = async (authCode: string) => { try { await handleFeishuLogin(authCode) } catch (error) { console.error('登录失败:', error) setLoading(false) } } // 处理扫码登录回调 const handleQrcodeCallback = async (qrcodeId: string) => { try { // 飞书扫码后会带着 qrcode_id 参数跳转回来 // 这个时候前端需要显示 loading 状态 // 后端会处理 qrcodeLoginCallback,更新 session 状态 // 前端只需要轮询状态即可 setShowQRCode(true) setLoading(false) // 设置轮询 session_id // 实际上这个逻辑在 QRCodeLogin 组件中处理 } catch (error) { console.error('处理扫码回调失败:', error) setLoading(false) } } // 处理飞书登录 const handleFeishuLogin = async (authCode: string) => { try { // 从 URL 获取 app_id 参数 const urlParams = new URLSearchParams(window.location.search) const urlAppId = urlParams.get('app_id') console.log('🔍 URL 中的 app_id:', urlAppId) const response = await fetch('/api/v1/auth/feishu-login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ auth_code: authCode, app_id: urlAppId || undefined, // 如果 URL 有 app_id 就传递 }), }) const result = await response.json() if (result.code === 0) { login(result.data.user, result.data.token) Toast.show('登录成功') navigate('/dashboard') } else { Toast.show(result.message || '登录失败') setLoading(false) } } catch (error: any) { console.error('登录失败:', error) Toast.show('登录失败,请重试') setLoading(false) } } if (loading) { return (
正在登录...
{/* 调试信息 */}正在登录...
{/* 调试信息 */}请选择登录方式
💡 提示:在飞书客户端内打开可自动登录
)}