casualroom/apps/fenghuo/web/components/providers/with-auth.tsx

196 lines
5.9 KiB
TypeScript
Executable File
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.

'use client';
import { ReactNode, useEffect } from 'react';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
import { useAuth, SystemPermission } from '@/components/providers/auth-provider';
import { Button } from '@nice/ui/components/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nice/ui/components/card';
import { AlertTriangle, Lock, Loader2, Shield } from 'lucide-react';
import { useRef } from 'react';
// 权限配置选项接口
interface WithAuthOptions {
/**
* 或权限 - 用户拥有其中任一权限即可访问
*/
orPermissions?: SystemPermission[];
/**
* 与权限 - 用户必须拥有所有权限才能访问
*/
andPermissions?: SystemPermission[];
/**
* 或角色 - 用户拥有其中任一角色即可访问
*/
orRoles?: string[];
/**
* 与角色 - 用户必须拥有所有角色才能访问
*/
andRoles?: string[];
/**
* 自定义重定向路径(默认为登录页面)
*/
redirectTo?: string;
}
// WithAuth 组件属性接口
interface WithAuthProps {
children: ReactNode;
options?: WithAuthOptions;
}
/**
* 权限拒绝页面组件
*/
function AccessDeniedPage() {
const router = useRouter();
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 px-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/20">
<Lock className="h-6 w-6 text-red-600 dark:text-red-400" />
</div>
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">访</CardTitle>
<CardDescription>访</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-2 rounded-lg bg-amber-50 dark:bg-amber-900/20 p-3">
<AlertTriangle className="h-4 w-4 text-amber-600 dark:text-amber-400" />
<p className="text-sm text-amber-800 dark:text-amber-200">628534</p>
</div>
<div className="flex gap-2">
<Button variant="outline" className="flex-1" onClick={() => router.back()}>
</Button>
<Button className="flex-1" onClick={() => router.push('/')}>
</Button>
</div>
</CardContent>
</Card>
</div>
);
}
/**
* 加载状态组件
*/
function LoadingPage() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
<div className="text-center">
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4 text-blue-600 dark:text-blue-400" />
</div>
</div>
);
}
export default function WithAuth({ children, options = {} }: WithAuthProps) {
const { isAuthenticated, user, hasAnyPermission, hasAllPermissions, hasAnyRole, hasRole, isLoading } = useAuth();
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const { orPermissions, andPermissions, orRoles, andRoles, redirectTo = '/auth/login' } = options;
// 使用 useRef 防止重复重定向
const redirectingRef = useRef(false);
// 使用 useEffect 处理路由跳转,避免在渲染期间调用 router.push
useEffect(() => {
// 如果正在加载或已经在重定向中,不执行任何操作
if (isLoading || redirectingRef.current) {
return;
}
// 如果用户未认证且不在登录页面,进行重定向
if (!isAuthenticated && !pathname.includes('/auth/login')) {
redirectingRef.current = true;
console.log('pathname', pathname);
// 获取原始重定向URL避免嵌套
const getOriginalRedirectUrl = (): string => {
const existingRedirectUrl = searchParams.get('redirectUrl');
if (existingRedirectUrl) {
try {
const decoded = decodeURIComponent(existingRedirectUrl);
// 如果已存在的redirectUrl包含登录页面提取其中的原始redirectUrl
if (decoded.includes('/auth/login')) {
const url = new URL(decoded, window.location.origin);
return url.searchParams.get('redirectUrl') || '/';
}
return decoded;
} catch {
return '/';
}
}
return pathname + (searchParams.toString() ? `?${searchParams.toString()}` : '');
};
const originalRedirectUrl = getOriginalRedirectUrl();
const loginUrl = `${redirectTo}?redirectUrl=${encodeURIComponent(originalRedirectUrl)}`;
router.push(loginUrl);
}
}, [isAuthenticated, isLoading, pathname, searchParams, redirectTo, router]);
// 如果用户未认证,显示加载页面
if (isLoading) {
return <LoadingPage />;
}
// 用户已认证但用户信息尚未加载完成,维持页面状态
if (!user) {
return <>{children}</>;
}
// 检查权限
const hasRequiredPermissions = (() => {
// 检查或权限(用户拥有其中任一权限即可)
if (orPermissions && orPermissions.length > 0) {
if (!hasAnyPermission(orPermissions)) {
return false;
}
}
// 检查与权限(用户必须拥有所有权限)
if (andPermissions && andPermissions.length > 0) {
if (!hasAllPermissions(andPermissions)) {
return false;
}
}
// 检查或角色(用户拥有其中任一角色即可)
if (orRoles && orRoles.length > 0) {
if (!hasAnyRole(orRoles)) {
return false;
}
}
// 检查与角色(用户必须拥有所有角色)
if (andRoles && andRoles.length > 0) {
const hasAllRoles = andRoles.every((role) => hasRole(role));
if (!hasAllRoles) {
return false;
}
}
return true;
})();
// 权限不足的处理
if (!hasRequiredPermissions) {
return <AccessDeniedPage />;
}
// 权限检查通过,渲染子组件
return <>{children}</>;
}
// 导出权限相关类型,方便其他组件使用
export type { WithAuthOptions, WithAuthProps };