Files
ln-bi/src/auth/AuthProvider.tsx
kkfluous 253cc2f2c0 fix(scheduling): fix vehicle type classification and algorithm candidate matching
- classifyVehicleType now parses dic_type.dic_name (e.g. "4.5吨冷链车") instead of raw model code
- Remove overly strict completionRate >= 0.8 filter for hopeless candidates
- Use vehicle's yearTarget as fallback when inventory has no assessment target
- Filter out suggestions with no candidates (not actionable)
- estimatedGain counts rescue_hopeless suggestions as potential gains

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 20:31:44 +08:00

112 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useRef, type ReactNode } from 'react';
import { AuthContext, type AuthState } from './useAuth';
import { setTokenGetter } from './api-client';
const AUTH_API = '/api/auth';
export default function AuthProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<AuthState>({
isLoading: true,
isAuthenticated: false,
user: null,
error: null,
});
const tokenRef = useRef<string | null>(null);
const authStarted = useRef(false);
useEffect(() => {
// 设置全局 token getter
setTokenGetter(() => tokenRef.current);
// 防止 StrictMode 双重调用jumpToken 一次性使用)
if (authStarted.current) return;
authStarted.current = true;
// 监听 401 事件
const onUnauthorized = () => {
tokenRef.current = null;
sessionStorage.removeItem('bi_jwt');
setState({ isLoading: false, isAuthenticated: false, user: null, error: '会话已过期' });
};
window.addEventListener('auth:unauthorized', onUnauthorized);
authenticate();
return () => window.removeEventListener('auth:unauthorized', onUnauthorized);
}, []);
async function authenticate() {
// 1. 检查 sessionStorage 中是否有 JWT
const savedToken = sessionStorage.getItem('bi_jwt');
if (savedToken) {
tokenRef.current = savedToken;
// 验证 token 是否仍然有效(尝试请求 health
try {
const res = await fetch('/api/health', {
headers: { Authorization: `Bearer ${savedToken}` },
});
if (res.ok) {
const savedUser = sessionStorage.getItem('bi_user');
setState({
isLoading: false,
isAuthenticated: true,
user: savedUser ? JSON.parse(savedUser) : null,
error: null,
});
return;
}
} catch { /* token 无效,继续流程 */ }
sessionStorage.removeItem('bi_jwt');
sessionStorage.removeItem('bi_user');
}
// 2. 从 URL 提取 jumpToken
const params = new URLSearchParams(window.location.search);
const jumpToken = params.get('jumpToken');
if (!jumpToken) {
// 临时:无 token 时直接放行
setState({ isLoading: false, isAuthenticated: true, user: null, error: null });
return;
}
try {
// 3. 一步完成jumpToken → 用户信息 + JWT
const res = await fetch(`${AUTH_API}/exchange?jumpToken=${encodeURIComponent(jumpToken)}`);
const data = await res.json();
if (!res.ok || !data.token) {
setState({ isLoading: false, isAuthenticated: false, user: null, error: data.message || '跳转令牌无效或已过期' });
return;
}
// 4. 存储 JWT
tokenRef.current = data.token;
sessionStorage.setItem('bi_jwt', data.token);
sessionStorage.setItem('bi_user', JSON.stringify(data.user));
// 6. 清除 URL 中的 jumpToken
params.delete('jumpToken');
const cleanUrl = params.toString()
? `${window.location.pathname}?${params.toString()}${window.location.hash}`
: `${window.location.pathname}${window.location.hash}`;
window.history.replaceState({}, '', cleanUrl);
setState({
isLoading: false,
isAuthenticated: true,
user: data.user,
error: null,
});
} catch (e) {
setState({ isLoading: false, isAuthenticated: false, user: null, error: '认证过程出错' });
}
}
return (
<AuthContext.Provider value={state}>
{children}
</AuthContext.Provider>
);
}