- ImageStrategyGroup/ImageLightbox/RegenControls/ImportTextModal/ RejectReasonBanner 新建组件 - tasks images/text/new + history + 配置/布局/SSE hooks 存量改动 - 总核销表进度更新 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
2.4 KiB
TypeScript
78 lines
2.4 KiB
TypeScript
'use client';
|
||
/**
|
||
* AuthGuard — 路由守卫
|
||
* - 未登录 → /login
|
||
* - 越权(组长访问 /config,非管理员等)→ /dashboard
|
||
* 扒 banana AuthGuard 范式,全新重建
|
||
*/
|
||
import { useEffect } from 'react';
|
||
import { useRouter, usePathname } from 'next/navigation';
|
||
import { useAuthStore } from '@/stores/authStore';
|
||
import { UserRole } from '@/types/index';
|
||
|
||
// 路由 → 最低所需角色
|
||
const ROUTE_ROLES: Record<string, UserRole[]> = {
|
||
'/config': ['admin'],
|
||
'/review': ['admin', 'supervisor'],
|
||
'/history': ['admin', 'supervisor', 'operator'],
|
||
'/tasks/new': ['admin', 'supervisor', 'operator'],
|
||
'/fission': ['admin', 'supervisor', 'operator'],
|
||
'/dashboard': ['admin', 'supervisor', 'operator'],
|
||
};
|
||
|
||
function getRequiredRoles(pathname: string): UserRole[] | null {
|
||
// 精确匹配或前缀匹配(如 /tasks/[id]/text)
|
||
if (ROUTE_ROLES[pathname]) return ROUTE_ROLES[pathname];
|
||
for (const [prefix, roles] of Object.entries(ROUTE_ROLES)) {
|
||
if (pathname.startsWith(prefix + '/')) return roles;
|
||
}
|
||
return null; // 公开路由(/login 等)
|
||
}
|
||
|
||
interface AuthGuardProps {
|
||
children: React.ReactNode;
|
||
}
|
||
|
||
export function AuthGuard({ children }: AuthGuardProps) {
|
||
const router = useRouter();
|
||
const pathname = usePathname();
|
||
const { isAuthenticated, role, isLoading, fetchMe, endLoading } = useAuthStore();
|
||
|
||
useEffect(() => {
|
||
// 首屏校验:有 token 且 store 未初始化 → 拉 me;无 token → 结束 loading 让守卫放行判断
|
||
const token = typeof window !== 'undefined' ? localStorage.getItem('clover_token') : null;
|
||
if (token && !isAuthenticated && isLoading) {
|
||
fetchMe();
|
||
} else if (!token && isLoading) {
|
||
endLoading();
|
||
}
|
||
}, [isAuthenticated, isLoading, fetchMe, endLoading]);
|
||
|
||
useEffect(() => {
|
||
if (isLoading) return;
|
||
|
||
// 公开路由直接放行
|
||
if (pathname === '/login') return;
|
||
|
||
if (!isAuthenticated) {
|
||
router.replace('/login');
|
||
return;
|
||
}
|
||
|
||
const required = getRequiredRoles(pathname);
|
||
if (required && role && !required.includes(role)) {
|
||
router.replace('/dashboard');
|
||
}
|
||
}, [isAuthenticated, role, isLoading, pathname, router]);
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="flex items-center justify-center min-h-screen bg-surface-secondary">
|
||
<div className="text-text-secondary text-sm">加载中…</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return <>{children}</>;
|
||
}
|