video-shuoshan/web/src/components/ProtectedRoute.tsx
seaislee1209 f3f8d08b56 feat: v0.14.1 视频参考双单价 + Token刷新防抖 + CSV导出上限
- 计费双单价:含视频输入28元/百万tokens,不含视频输入46元/百万tokens
- QuotaConfig 加 base_token_price_video 字段,系统设置页两个并排输入框
- 预估费用和实际结算按参考素材类型自动选择单价
- Token 刷新加锁:同页面内并发 401 共用一次 refresh 请求
- 关闭 BLACKLIST_AFTER_ROTATION:防止快速刷新导致误登出
- ProtectedRoute 容错:请求中断时自动重试,不误跳转
- CSV 导出上限从 100 提升到 10000

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:25:58 +08:00

84 lines
2.3 KiB
TypeScript

import { useEffect, useRef } from 'react';
import { Navigate } from 'react-router-dom';
import { useAuthStore } from '../store/auth';
interface Props {
children: React.ReactNode;
requireAdmin?: boolean;
requireTeamAdmin?: boolean;
requireTeamMember?: boolean;
}
export function ProtectedRoute({ children, requireAdmin, requireTeamAdmin, requireTeamMember }: Props) {
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
const isLoading = useAuthStore((s) => s.isLoading);
const user = useAuthStore((s) => s.user);
const mustChangePassword = useAuthStore((s) => s.mustChangePassword);
const fetchUserInfo = useAuthStore((s) => s.fetchUserInfo);
const retrying = useRef(false);
// If we have a token but user info hasn't loaded, keep retrying
useEffect(() => {
if (!isAuthenticated || user || isLoading) return;
if (retrying.current) return;
retrying.current = true;
let cancelled = false;
const retry = async () => {
let delay = 500;
while (!cancelled) {
try {
await fetchUserInfo();
break; // success
} catch {
await new Promise(r => setTimeout(r, delay));
delay = Math.min(delay * 2, 3000);
}
}
retrying.current = false;
};
retry();
return () => { cancelled = true; };
}, [isAuthenticated, user, isLoading, fetchUserInfo]);
if (isLoading || (isAuthenticated && !user)) {
return (
<div style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--color-bg-page)',
color: 'var(--color-text-secondary)',
}}>
...
</div>
);
}
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
if (mustChangePassword) {
return <Navigate to="/" replace />;
}
if (requireAdmin && user?.role !== 'super_admin') {
return <Navigate to="/app" replace />;
}
if (requireTeamAdmin && user?.role !== 'team_admin') {
return <Navigate to="/app" replace />;
}
// requireTeamMember: must have a team (team_admin or member)
if (requireTeamMember && user?.role === 'super_admin') {
return <Navigate to="/admin/dashboard" replace />;
}
return <>{children}</>;
}