Files
x-server-mobile/frontend/src/components/FeishuAutoLogin.tsx
T
root b1ca2c58b7 feat: 飞书登录支持多租户和用户工号管理
后端改动:
- FeishuService: 从飞书用户信息获取 tenant_key 和手机号
- FeishuService: 新增 user_sn 表工号分配机制
- FeishuService: 优先使用 enterprise_email,支持手机号
- AuthController: 飞书免登支持 company 参数

前端改动:
- FeishuAutoLogin: 支持多租户 company 参数
- login/index: 支持 redirect 参数和已登录检测
- QRCodeLogin: 支持多租户参数传递
- router: 新增路由守卫和参数处理
- api.ts: 优化飞书登录 API 调用

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 17:57:49 +08:00

270 lines
8.1 KiB
TypeScript

import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useUserStore } from '@/stores/user'
import QRCodeLogin from '@/pages/login/QRCodeLogin'
// 声明飞书 JSAPI 类型
declare global {
interface Window {
tt?: any
ttjsBridge?: any
}
}
export default function FeishuAutoLogin() {
const navigate = useNavigate()
const { login, isLoggedIn } = useUserStore()
const [status, setStatus] = useState<'checking' | 'success' | 'failed' | 'show_options'>('checking')
const [showQRCode, setShowQRCode] = useState(false)
const [debugInfo, setDebugInfo] = useState<any>({})
useEffect(() => {
// 如果已登录,直接跳转
if (isLoggedIn) {
navigate('/dashboard')
return
}
const ua = navigator.userAgent
const currentUrl = window.location.href
const isInFeishu = ua.includes('Lark') || ua.includes('feishu') || ua.includes('lark')
console.log('=== 首页免登检测 ===')
console.log('URL:', currentUrl)
console.log('在飞书中:', isInFeishu)
console.log('window.tt:', !!window.tt)
setDebugInfo({
url: currentUrl,
isInFeishu,
hasTT: !!window.tt,
})
// 检查 URL 中是否有 auth_code
const urlParams = new URLSearchParams(window.location.search)
const authCode = urlParams.get('auth_code')
if (authCode) {
console.log('✅ 发现 auth_code,直接登录')
handleLogin(authCode)
return
}
if (!isInFeishu) {
console.log('不在飞书中,显示登录选项')
setStatus('show_options')
return
}
// 在飞书中,尝试免登
tryFeishuAutoLogin()
function tryFeishuAutoLogin() {
// 从 URL 获取 company 参数
const urlParams = new URLSearchParams(window.location.search)
const companyParam = urlParams.get('company')
console.log('🔍 URL 中的 company 参数:', companyParam)
// 打印 tt 对象的所有方法
if (window.tt) {
console.log('tt 对象的方法:')
for (let key in window.tt) {
if (typeof window.tt[key] === 'function') {
console.log(' -', key)
}
}
}
// 方式1: tt.requestAuthCode
if (window.tt && typeof window.tt.requestAuthCode === 'function') {
console.log('使用 tt.requestAuthCode')
// 不传 appId,让飞书自动选择(后端会根据 company 参数处理)
window.tt.requestAuthCode({
success: (res: any) => {
console.log('✅ 获取到授权码:', res.code?.substring(0, 20))
handleLogin(res.code)
},
fail: (err: any) => {
console.error('❌ 免登失败:', err)
setStatus('show_options')
},
})
return
}
// 方式2: ttjsBridge
if (window.ttjsBridge && typeof window.ttjsBridge.getAuthCode === 'function') {
console.log('使用 ttjsBridge.getAuthCode')
window.ttjsBridge.getAuthCode({
success: (code: string) => {
console.log('✅ 获取到授权码:', code?.substring(0, 20))
handleLogin(code)
},
fail: (err: any) => {
console.error('❌ 免登失败:', err)
setStatus('show_options')
},
})
return
}
// tt API 不可用,显示登录选项
console.log('⚠️ tt API 不可用')
setStatus('show_options')
}
async function handleLogin(authCode: string) {
try {
const response = await fetch('/api/v1/auth/feishu-login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
auth_code: authCode,
company: urlParams.get('company') || undefined,
}),
})
const result = await response.json()
if (result.code === 0) {
console.log('✅ 登录成功')
login(result.data.user, result.data.token)
setStatus('success')
setTimeout(() => navigate('/dashboard'), 500)
} else {
console.error('❌ 登录失败:', result.message)
setStatus('show_options')
}
} catch (error) {
console.error('❌ 登录异常:', error)
setStatus('show_options')
}
}
}, [isLoggedIn, login, navigate])
// 检测中的状态
if (status === 'checking') {
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white'
}}>
<h1>企业工具台</h1>
<p style={{ marginTop: 20 }}>正在登录...</p>
<div style={{
width: 40,
height: 40,
border: '4px solid rgba(255,255,255,0.3)',
borderTopColor: 'white',
borderRadius: '50%',
animation: 'spin 1s linear infinite',
marginTop: 20
}}></div>
{/* 调试信息 */}
<div style={{
marginTop: 30,
padding: 10,
background: 'rgba(0,0,0,0.2)',
borderRadius: 8,
fontSize: 12,
textAlign: 'left'
}}>
<div>URL: {debugInfo.url?.substring(0, 50)}...</div>
<div>在飞书中: {debugInfo.isInFeishu ? '✅' : '❌'}</div>
<div>tt 对象: {debugInfo.hasTT ? '✅' : '❌'}</div>
</div>
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
</div>
)
}
// 成功状态
if (status === 'success') {
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white'
}}>
<h1>✅ 登录成功</h1>
<p style={{ marginTop: 20 }}>正在进入应用...</p>
<div style={{
width: 40,
height: 40,
border: '4px solid rgba(255,255,255,0.3)',
borderTopColor: 'white',
borderRadius: '50%',
animation: 'spin 1s linear infinite',
marginTop: 20
}}></div>
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
</div>
)
}
// 显示登录选项
if (status === 'show_options') {
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
padding: 20
}}>
<h1 style={{ marginBottom: 10 }}>企业工具台</h1>
<p style={{ marginBottom: 40, opacity: 0.9 }}>请选择登录方式</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: '100%', maxWidth: 320 }}>
{/* 飞书扫码登录 */}
<button
onClick={() => setShowQRCode(true)}
style={{
display: 'flex',
alignItems: 'center',
gap: 16,
width: '100%',
padding: 16,
background: '#fff',
border: 'none',
borderRadius: 12,
cursor: 'pointer'
}}
>
<span style={{ fontSize: 28 }}>📱</span>
<div style={{ flex: 1, textAlign: 'left' }}>
<div style={{ fontSize: 16, fontWeight: 500, color: '#333', marginBottom: 2 }}>飞书扫码登录</div>
<div style={{ fontSize: 12, color: '#999' }}>使用飞书 APP 扫码登录</div>
</div>
</button>
</div>
{debugInfo.isInFeishu && (
<p style={{ marginTop: 30, fontSize: 12, opacity: 0.7, textAlign: 'center' }}>
💡 检测到在飞书客户端中,但免登功能不可用
</p>
)}
<QRCodeLogin
visible={showQRCode}
onClose={() => setShowQRCode(false)}
/>
</div>
)
}
return null
}