feat: 完善鉴权和密码功能,添加飞书通知模块
- 完善Token过期/不存在时跳转登录重新认证 - 实现正常的账号密码登录验证(邮箱/手机号+密码) - 用户初始化密码设为工号(sn) - 添加修改密码功能(API+前端页面) - 修复扫码登录后跳转问题(zustand persist格式) - 创建飞书通知服务 FeishuNotificationService - 设置页面添加修改密码入口 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,12 @@ declare global {
|
||||
goto: string
|
||||
width?: string
|
||||
height?: string
|
||||
style?: string
|
||||
}) => {
|
||||
matchOrigin: (origin: string) => boolean
|
||||
matchData: (data: any) => boolean
|
||||
}
|
||||
LarkSSOSDKWebQRCode?: any
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,17 +41,36 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) {
|
||||
initQRCode()
|
||||
}
|
||||
|
||||
// 监听来自回调页面的成功消息
|
||||
const handleCallbackMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === 'feishu-login-success') {
|
||||
console.log('✅ Received login success from callback popup')
|
||||
// 轮询应该会检测到成功状态,但这里作为额外的触发器
|
||||
// 如果轮询已经检测到并处理了,这个消息就忽略
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleCallbackMessage)
|
||||
|
||||
return () => {
|
||||
// 清理
|
||||
// 清理 message 事件监听器
|
||||
if (qrObjRef.current?._cleanup) {
|
||||
qrObjRef.current._cleanup()
|
||||
}
|
||||
// 清理回调消息监听器
|
||||
window.removeEventListener('message', handleCallbackMessage)
|
||||
// 清理容器
|
||||
if (qrContainerRef.current) {
|
||||
qrContainerRef.current.innerHTML = ''
|
||||
}
|
||||
// 清理轮询
|
||||
if (pollTimerRef.current) {
|
||||
clearTimeout(pollTimerRef.current)
|
||||
}
|
||||
// 重置状态
|
||||
setUseFallback(false)
|
||||
setQrcodeUrl('')
|
||||
qrObjRef.current = null
|
||||
}
|
||||
}, [visible])
|
||||
|
||||
@@ -89,25 +110,90 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) {
|
||||
// 检查飞书 QR SDK 是否已加载(在 index.html 中引入)
|
||||
if (window.QRLogin) {
|
||||
console.log('✅ Using Feishu QR SDK')
|
||||
// 使用飞书官方 QR SDK
|
||||
// 先清空容器内容(保留容器元素本身)
|
||||
const container = document.getElementById('feishu-qrcode-container')
|
||||
if (container) {
|
||||
container.innerHTML = ''
|
||||
}
|
||||
// 确保 DOM 已渲染后再调用 SDK
|
||||
setTimeout(() => {
|
||||
qrObjRef.current = window.QRLogin({
|
||||
id: 'feishu-qrcode-container',
|
||||
goto: authUrl,
|
||||
width: '250',
|
||||
height: '250',
|
||||
style: 'width:300px;height:270px;border:none'
|
||||
})
|
||||
}, 50)
|
||||
|
||||
setStatus('pending')
|
||||
startPolling(result.data.session_id)
|
||||
|
||||
// 确保 DOM 已渲染后再调用 SDK
|
||||
// Dialog 内容可能需要更长时间渲染,使用多次尝试
|
||||
const initQRSDK = (retryCount = 0) => {
|
||||
const container = document.getElementById('feishu-qrcode-container')
|
||||
console.log('🔍 Looking for container, attempt', retryCount + 1, 'found:', !!container)
|
||||
|
||||
if (container) {
|
||||
// 清空容器
|
||||
container.innerHTML = ''
|
||||
|
||||
try {
|
||||
console.log('🔧 Initializing QR SDK with goto:', authUrl.substring(0, 50) + '...')
|
||||
|
||||
qrObjRef.current = window.QRLogin({
|
||||
id: 'feishu-qrcode-container',
|
||||
goto: authUrl,
|
||||
width: '250',
|
||||
height: '250',
|
||||
style: 'width:300px;height:270px;border:none'
|
||||
})
|
||||
|
||||
console.log('✅ Feishu QR SDK initialized successfully')
|
||||
console.log('📦 Container children count:', container.children.length)
|
||||
|
||||
setStatus('pending')
|
||||
startPolling(result.data.session_id)
|
||||
|
||||
// 监听扫码后的 message 事件(关键步骤!)
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
console.log('📨 Received message event:', event.origin, event.data)
|
||||
|
||||
// 安全校验
|
||||
if (!qrObjRef.current?.matchOrigin(event.origin)) {
|
||||
console.warn('⚠️ Origin mismatch:', event.origin)
|
||||
return
|
||||
}
|
||||
if (!qrObjRef.current?.matchData(event.data)) {
|
||||
console.warn('⚠️ Data mismatch:', event.data)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取临时授权码
|
||||
const loginTmpCode = event.data?.tmp_code
|
||||
if (loginTmpCode) {
|
||||
console.log('✅ Got tmp_code:', loginTmpCode)
|
||||
console.log('🔀 Redirecting to Feishu auth page...')
|
||||
|
||||
// 保存当前 URL,登录成功后返回
|
||||
const currentUrl = window.location.href
|
||||
sessionStorage.setItem('feishu_login_return_url', currentUrl)
|
||||
|
||||
// 重定向到飞书授权页面(官方文档推荐方式)
|
||||
const authUrlWithCode = `${authUrl}&tmp_code=${loginTmpCode}`
|
||||
window.location.href = authUrlWithCode
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessage)
|
||||
// 保存清理函数
|
||||
qrObjRef.current._cleanup = () => {
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('❌ QR SDK init error:', e)
|
||||
// Fallback to normal QR code
|
||||
setQrcodeUrl(authUrl)
|
||||
setUseFallback(true)
|
||||
startPolling(result.data.session_id)
|
||||
}
|
||||
} else if (retryCount < 10) {
|
||||
// 容器还未渲染,继续等待
|
||||
setTimeout(() => initQRSDK(retryCount + 1), 100)
|
||||
} else {
|
||||
console.error('❌ Container not found after 10 attempts, using fallback')
|
||||
setQrcodeUrl(authUrl)
|
||||
setUseFallback(true)
|
||||
startPolling(result.data.session_id)
|
||||
}
|
||||
}
|
||||
|
||||
// 开始初始化
|
||||
setTimeout(() => initQRSDK(), 100)
|
||||
} else {
|
||||
// SDK 未加载,使用普通二维码
|
||||
console.warn('⚠️ Feishu QR SDK not loaded, using fallback')
|
||||
@@ -126,6 +212,8 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) {
|
||||
}
|
||||
|
||||
const startPolling = (id: string) => {
|
||||
console.log('🔄 Starting polling for session:', id)
|
||||
|
||||
const poll = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/auth/qrcode-login/status?session_id=${id}`)
|
||||
|
||||
Reference in New Issue
Block a user