import { useState, useEffect, useRef } from 'react' import { Dialog, Toast } from 'antd-mobile' import { QRCodeSVG } from 'qrcode.react' import { useNavigate } from 'react-router-dom' import styles from './QRCodeLogin.module.css' interface QRCodeLoginProps { visible: boolean onClose: () => void } export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) { const navigate = useNavigate() const [qrcodeUrl, setQrcodeUrl] = useState('') const [sessionId, setSessionId] = useState('') const [status, setStatus] = useState<'loading' | 'pending' | 'scanning' | 'success' | 'expired'>('loading') const pollTimerRef = useRef(null) useEffect(() => { if (visible) { initQrcodeLogin() } return () => { if (pollTimerRef.current) { clearTimeout(pollTimerRef.current) } } }, [visible]) const initQrcodeLogin = async () => { setStatus('loading') try { // 生成登录二维码 const response = await fetch('/api/v1/auth/qrcode-login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tenant_key: '14e7bc0fe5985740', }), }) const result = await response.json() if (result.code === 0) { setQrcodeUrl(result.data.auth_url) setSessionId(result.data.session_id) setStatus('pending') // 开始轮询登录状态 startPolling(result.data.session_id) } else { Toast.show(result.message || '生成二维码失败') setStatus('expired') } } catch (error) { console.error('生成二维码失败:', error) Toast.show('生成二维码失败') setStatus('expired') } } const startPolling = (id: string) => { const poll = async () => { try { const response = await fetch(`/api/v1/auth/qrcode-login/status?session_id=${id}`) const result = await response.json() if (result.code === 0) { if (result.data.status === 'success') { setStatus('success') // 登录成功,保存 token const { token, user } = result.data localStorage.setItem('token', token) localStorage.setItem('i18nextLng', 'zh') // 触发登录成功事件,让 store 更新 window.dispatchEvent(new CustomEvent('feishu-login-success', { detail: { user, token })) Toast.show('登录成功') setTimeout(() => { onClose() navigate('/dashboard') }, 500) } else if (result.data.status === 'expired') { setStatus('expired') Toast.show('二维码已过期,请刷新重试') } else if (result.data.status === 'scanning') { setStatus('scanning') // 继续轮询 pollTimerRef.current = setTimeout(poll, 2000) } else { // pending,继续轮询 pollTimerRef.current = setTimeout(poll, 2000) } } else if (result.message?.includes('过期')) { setStatus('expired') } else { // 继续轮询 pollTimerRef.current = setTimeout(poll, 2000) } } catch (error) { console.error('轮询登录状态失败:', error) // 继续轮询 pollTimerRef.current = setTimeout(poll, 2000) } } pollTimerRef.current = setTimeout(poll, 2000) } const getStatusText = () => { switch (status) { case 'loading': return '正在生成二维码...' case 'pending': return '请使用飞书扫码登录' case 'scanning': return '已扫码,请在手机上确认' case 'success': return '登录成功!' case 'expired': return '二维码已过期' default: return '' } } const getStatusColor = () => { switch (status) { case 'pending': return '#1677ff' case 'scanning': return '#faad14' case 'success': return '#52c41a' case 'expired': return '#ff4d4f' default: return '#999' } } const handleRefresh = () => { if (pollTimerRef.current) { clearTimeout(pollTimerRef.current) } initQrcodeLogin() } return ( {status === 'loading' &&
正在生成二维码...
} {qrcodeUrl && status !== 'loading' && ( <>
{getStatusText()}
{(status === 'expired') && (
)} )} } onClose={onClose} closeOnMaskClick={status !== 'scanning' && status !== 'success'} /> ) }