From 474c53266649efd52828c97ed45391059aac2b4c Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Feb 2026 11:13:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8=E9=A3=9E=E4=B9=A6?= =?UTF-8?q?=E5=AE=98=E6=96=B9=20QR=20SDK=EF=BC=8C=E6=89=AB=E7=A0=81?= =?UTF-8?q?=E5=90=8E=E4=B8=8D=E6=89=93=E5=BC=80=E6=B5=8F=E8=A7=88=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用飞书官方 LarkSSOSDKWebQRCode-1.0.3.js 生成二维码, 飞书APP会识别为登录二维码并在APP内直接处理, 不会打开任何浏览器窗口。 Co-Authored-By: Claude Opus 4.6 --- frontend/src/pages/login/QRCodeLogin.tsx | 137 ++++++++++++++--------- 1 file changed, 82 insertions(+), 55 deletions(-) diff --git a/frontend/src/pages/login/QRCodeLogin.tsx b/frontend/src/pages/login/QRCodeLogin.tsx index ae33146..3e6da7f 100644 --- a/frontend/src/pages/login/QRCodeLogin.tsx +++ b/frontend/src/pages/login/QRCodeLogin.tsx @@ -1,37 +1,89 @@ 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' +// 声明飞书 QR SDK 类型 +declare global { + interface Window { + QR?: (config: { + id: string + goto: string + width?: string + height?: string + style?: string + }) => { + matchOrigin: (origin: string) => boolean + matchData: (data: any) => boolean + } + } +} + interface QRCodeLoginProps { visible: boolean onClose: () => void } +// 动态加载飞书 QR SDK +const loadFeishuQRSDK = (): Promise => { + return new Promise((resolve, reject) => { + if (window.QR) { + resolve() + return + } + + // 检查是否已经加载过 + const existingScript = document.getElementById('feishu-qr-sdk') + if (existingScript) { + existingScript.addEventListener('load', () => resolve()) + existingScript.addEventListener('error', () => reject(new Error('Failed to load Feishu QR SDK'))) + return + } + + const script = document.createElement('script') + script.id = 'feishu-qr-sdk' + script.src = 'https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js' + script.onload = () => resolve() + script.onerror = () => reject(new Error('Failed to load Feishu QR SDK')) + document.head.appendChild(script) + }) +} + 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 [status, setStatus] = useState<'loading' | 'pending' | 'scanning' | 'success'>('loading') + const qrContainerRef = useRef(null) + const qrObjRef = useRef(null) const pollTimerRef = useRef(null) + const sessionIdRef = useRef('') useEffect(() => { if (visible) { - initQrcodeLogin() + initQRCode() } return () => { + // 清理 + if (qrContainerRef.current) { + qrContainerRef.current.innerHTML = '' + } if (pollTimerRef.current) { clearTimeout(pollTimerRef.current) } } }, [visible]) - const initQrcodeLogin = async () => { + const initQRCode = async () => { setStatus('loading') try { - // 生成登录二维码 + // 加载飞书 QR SDK + await loadFeishuQRSDK() + + if (!window.QR) { + throw new Error('QR SDK not loaded') + } + + // 生成登录会话 const response = await fetch('/api/v1/auth/qrcode-login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -43,19 +95,31 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) { const result = await response.json() if (result.code === 0) { - setQrcodeUrl(result.data.auth_url) - setSessionId(result.data.session_id) + const authUrl = result.data.auth_url + sessionIdRef.current = result.data.session_id + + // 清空容器 + if (qrContainerRef.current) { + qrContainerRef.current.innerHTML = '' + } + + // 使用飞书官方 QR SDK 创建二维码 + qrObjRef.current = window.QR({ + id: 'feishu-qrcode-container', + goto: authUrl, + width: '200', + height: '200', + }) + setStatus('pending') // 开始轮询登录状态 startPolling(result.data.session_id) } else { Toast.show(result.message || '生成二维码失败') - setStatus('expired') } } catch (error) { console.error('生成二维码失败:', error) Toast.show('生成二维码失败') - setStatus('expired') } } @@ -80,9 +144,6 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) { onClose() navigate('/dashboard') }, 500) - } else if (result.data.status === 'expired') { - setStatus('expired') - Toast.show('二维码已过期,请刷新重试') } else if (result.data.status === 'scanning') { setStatus('scanning') // 继续轮询 @@ -91,8 +152,6 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) { // pending,继续轮询 pollTimerRef.current = setTimeout(poll, 2000) } - } else if (result.message?.includes('过期')) { - setStatus('expired') } else { // 继续轮询 pollTimerRef.current = setTimeout(poll, 2000) @@ -117,8 +176,6 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) { return '已扫码,请在手机上确认' case 'success': return '登录成功!' - case 'expired': - return '二维码已过期' default: return '' } @@ -132,55 +189,25 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) { 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()} +
-
- {getStatusText()} -
- - {(status === 'expired') && ( -
- -
- )} - -

- 💡 打开飞书 APP,扫一扫登录 -

- - )} +

+ 💡 打开飞书 APP,扫一扫登录 +

} onClose={onClose}