diff --git a/src/auth/permissions.ts b/src/auth/permissions.ts index 6999b32..336c635 100644 --- a/src/auth/permissions.ts +++ b/src/auth/permissions.ts @@ -1,5 +1,6 @@ import { redirect } from '@tanstack/react-router' import { can, useAuthStore } from '@/auth/store' +import { getFirstAccessibleRoute } from '@/components/layout/nav' /** * Admin permission keys — mirrors the backend catalog @@ -50,6 +51,7 @@ export type Permission = (typeof PERM)[keyof typeof PERM] export function requirePermission(permission: Permission): void { const { user } = useAuthStore.getState() if (user && !can(permission)) { - throw redirect({ to: '/' }) + const target = getFirstAccessibleRoute() + throw redirect({ to: target && target !== '/' ? target : '/' }) } } diff --git a/src/auth/store.ts b/src/auth/store.ts index cfebaa7..2fc256d 100644 --- a/src/auth/store.ts +++ b/src/auth/store.ts @@ -51,5 +51,8 @@ export function useCan(permission: string): boolean { } export function isAuthenticated(): boolean { - return Boolean(useAuthStore.getState().accessToken) + const { accessToken, user, roles, permissions } = useAuthStore.getState() + if (!accessToken) return false + if (user && roles.length === 0 && permissions.length === 0) return false + return true } diff --git a/src/components/layout/nav.ts b/src/components/layout/nav.ts index 2a55508..6f0b418 100644 --- a/src/components/layout/nav.ts +++ b/src/components/layout/nav.ts @@ -1,4 +1,5 @@ import type { LucideIcon } from 'lucide-react' +import { can } from '@/auth/store' import { BookOpenText, Bot, @@ -100,3 +101,15 @@ export const NAV_GROUPS: NavGroup[] = [ ], }, ] + +export function getFirstAccessibleRoute(): string | null { + for (const group of NAV_GROUPS) { + for (const item of group.items) { + if (!item.permission || can(item.permission)) { + return item.to + } + } + } + return null +} + diff --git a/src/routes/_authed.tsx b/src/routes/_authed.tsx index 6ef0d9b..0299e89 100644 --- a/src/routes/_authed.tsx +++ b/src/routes/_authed.tsx @@ -2,6 +2,7 @@ import { Link, Outlet, createFileRoute, redirect, useNavigate } from '@tanstack/ import { useQuery } from '@tanstack/react-query' import { useEffect } from 'react' import { LogOut, Moon, Sun } from 'lucide-react' +import { toast } from 'sonner' import { fetchMe } from '@/api/modules/auth' import { can, isAuthenticated, useAuthStore } from '@/auth/store' import { NAV_GROUPS } from '@/components/layout/nav' @@ -41,11 +42,19 @@ function AuthedLayout() { useEffect(() => { if (me.data) { + const roles = me.data.meta?.roles ?? [] + const permissions = me.data.meta?.permissions ?? [] + if (roles.length === 0 && permissions.length === 0) { + useAuthStore.getState().clear() + toast.error('账号未分配管理后台权限,已退出登录') + void navigate({ to: '/login' }) + return + } useAuthStore .getState() - .setSession(me.data.data, me.data.meta?.roles ?? [], me.data.meta?.permissions ?? []) + .setSession(me.data.data, roles, permissions) } - }, [me.data]) + }, [me.data, navigate]) useEffect(() => { if (me.isError && !isAuthenticated()) { diff --git a/src/routes/_authed/index.tsx b/src/routes/_authed/index.tsx index c4e9b07..eddf529 100644 --- a/src/routes/_authed/index.tsx +++ b/src/routes/_authed/index.tsx @@ -1,7 +1,51 @@ -import { createFileRoute, redirect } from '@tanstack/react-router' +import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router' +import { ShieldAlert, LogOut } from 'lucide-react' +import { getFirstAccessibleRoute } from '@/components/layout/nav' +import { useAuthStore } from '@/auth/store' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' export const Route = createFileRoute('/_authed/')({ beforeLoad: () => { - throw redirect({ to: '/categories' }) + const target = getFirstAccessibleRoute() + if (target && target !== '/') { + throw redirect({ to: target }) + } }, + component: DashboardIndexPage, }) + +function DashboardIndexPage() { + const navigate = useNavigate() + const user = useAuthStore((s) => s.user) + + const logout = () => { + useAuthStore.getState().clear() + void navigate({ to: '/login' }) + } + + return ( +
+ + +
+ +
+ 账号未分配权限 + + 当前账号【{user?.display_name ?? user?.username ?? user?.uid}】尚未分配任何管理后台权限 + +
+ +

+ 如果您需要访问管理后台,请联系超级管理员在「用户角色」管理中为您分配相应的角色。 +

+ +
+
+
+ ) +} diff --git a/src/routes/login.tsx b/src/routes/login.tsx index b48a421..a555ef0 100644 --- a/src/routes/login.tsx +++ b/src/routes/login.tsx @@ -45,7 +45,13 @@ function LoginPage() { const me = await fetchMe() const roles = me.meta?.roles ?? [] - useAuthStore.getState().setSession(me.data, roles, me.meta?.permissions ?? []) + const permissions = me.meta?.permissions ?? [] + if (roles.length === 0 && permissions.length === 0) { + useAuthStore.getState().clear() + toast.error('该账号未分配管理后台权限,无法登录') + return + } + useAuthStore.getState().setSession(me.data, roles, permissions) await navigate({ to: '/' }) } catch (error) {