fix(web): 添加登录状态拦截和修复类型错误

- dashboard layout: 未登录时重定向到登录页,携带 redirect 参数
- sidebar: 移除静态菜单 fallback,仅使用动态权限菜单
- MenuEditDialog: 修复 type 字段类型为 MenuType
- RoleEditDialog: 修复 isSystem 属性访问使用 roleDetail

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
charilezhou
2026-01-17 16:22:22 +08:00
parent df69eedcd9
commit fad4842c07
4 changed files with 30 additions and 55 deletions

View File

@@ -6,26 +6,23 @@ import { useEffect } from 'react';
import { Header } from '@/components/layout/Header';
import { Sidebar, MobileSidebar } from '@/components/layout/Sidebar';
import { getRoutePermission, isPublicRoute } from '@/config/route-permissions';
import { getLoginUrl } from '@/lib/auth';
import { getUserMenusAndPermissions } from '@/services/permission.service';
import { useAuthStore, usePermissionStore } from '@/stores';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const router = useRouter();
const { token, isHydrated: authHydrated } = useAuthStore();
const { isLoaded, setPermissionData, hasPermission, clearPermissionData } = usePermissionStore();
// 加载用户权限数据
// 登录状态检查:未登录时重定向到登录页
useEffect(() => {
if (!authHydrated) return;
// 未登录时清空权限数据
if (!token) {
clearPermissionData();
router.replace(getLoginUrl(pathname));
return;
}
@@ -41,7 +38,7 @@ export default function DashboardLayout({
setPermissionData({ menus: [], permissions: [], isSuperAdmin: false });
});
}
}, [authHydrated, token, isLoaded, setPermissionData, clearPermissionData]);
}, [authHydrated, token, isLoaded, pathname, router, setPermissionData, clearPermissionData]);
// 路由权限检查
useEffect(() => {

View File

@@ -10,7 +10,6 @@ import { Button } from '@/components/ui/button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Sheet, SheetContent, SheetTitle } from '@/components/ui/sheet';
import { sidebarNav } from '@/config/nav';
import { siteConfig } from '@/config/site';
import { getIcon } from '@/lib/icons';
import { cn } from '@/lib/utils';
@@ -182,28 +181,12 @@ function SidebarContent({ collapsed = false, onItemClick }: SidebarContentProps)
const pathname = usePathname();
const { menus, isLoaded } = usePermissionStore();
// 如果权限数据已加载且有菜单,使用动态菜单
// 否则使用静态菜单作为 fallback
// 只使用动态菜单,未加载时返回空数组
const menuItems = useMemo(() => {
if (isLoaded && menus.length > 0) {
return menus;
if (!isLoaded) {
return [];
}
// 将静态导航转换为 MenuTreeNode 格式
return sidebarNav.flatMap((group) =>
group.items.map((item) => ({
id: item.href,
parentId: null,
code: item.href,
name: item.title,
type: 'menu' as const,
path: item.href,
icon: item.icon.displayName || null,
permission: null,
isHidden: false,
meta: null,
}))
);
return menus;
}, [isLoaded, menus]);
// 将动态菜单按分组渲染(如果有 parentId 则是子菜单)

View File

@@ -2,7 +2,7 @@
import type { MenuResponse, MenuTreeNode } from '@seclusion/shared';
import { MenuType } from '@seclusion/shared';
import { useEffect } from 'react';
import React, { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
@@ -48,7 +48,7 @@ interface MenuFormValues {
parentId: string;
code: string;
name: string;
type: string;
type: MenuType;
path: string;
icon: string;
permission: string;
@@ -67,7 +67,8 @@ function renderMenuOptions(menus: MenuTreeNode[], level = 0): React.ReactNode[]
const prefix = '\u00A0\u00A0'.repeat(level);
options.push(
<SelectItem key={menu.id} value={menu.id}>
{prefix}{menu.name}
{prefix}
{menu.name}
</SelectItem>
);
@@ -173,9 +174,7 @@ export function MenuEditDialog({ menu, open, onOpenChange }: MenuEditDialogProps
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>{isEditing ? '编辑菜单' : '新建菜单'}</DialogTitle>
<DialogDescription>
{isEditing ? '修改菜单配置。' : '创建新的菜单项。'}
</DialogDescription>
<DialogDescription>{isEditing ? '修改菜单配置。' : '创建新的菜单项。'}</DialogDescription>
</DialogHeader>
<Form {...form}>
@@ -221,11 +220,7 @@ export function MenuEditDialog({ menu, open, onOpenChange }: MenuEditDialogProps
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="如user-management"
{...field}
disabled={isEditing}
/>
<Input placeholder="如user-management" {...field} disabled={isEditing} />
</FormControl>
<FormMessage />
</FormItem>
@@ -317,7 +312,15 @@ export function MenuEditDialog({ menu, open, onOpenChange }: MenuEditDialogProps
<Input placeholder="Lucide 图标名Users" {...field} />
</FormControl>
<FormDescription>
<a href="https://lucide.dev/icons" target="_blank" rel="noreferrer" className="text-primary hover:underline">Lucide Icons</a>
{' '}
<a
href="https://lucide.dev/icons"
target="_blank"
rel="noreferrer"
className="text-primary hover:underline"
>
Lucide Icons
</a>
</FormDescription>
<FormMessage />
</FormItem>
@@ -335,9 +338,7 @@ export function MenuEditDialog({ menu, open, onOpenChange }: MenuEditDialogProps
<FormControl>
<Input placeholder="如user:read可选" {...field} />
</FormControl>
<FormDescription>
资源:操作
</FormDescription>
<FormDescription> 资源:操作</FormDescription>
<FormMessage />
</FormItem>
)}
@@ -350,12 +351,9 @@ export function MenuEditDialog({ menu, open, onOpenChange }: MenuEditDialogProps
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-2">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<FormLabel className="!mt-0"></FormLabel>
<FormLabel className="mt-0!"></FormLabel>
</FormItem>
)}
/>
@@ -366,12 +364,9 @@ export function MenuEditDialog({ menu, open, onOpenChange }: MenuEditDialogProps
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-2">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<FormLabel className="!mt-0"></FormLabel>
<FormLabel className="mt-0!"></FormLabel>
</FormItem>
)}
/>

View File

@@ -184,7 +184,7 @@ export function RoleEditDialog({ role, open, onOpenChange }: RoleEditDialogProps
<Input
placeholder="如manager"
{...field}
disabled={isEditing || role?.isSystem}
disabled={isEditing || roleDetail?.isSystem}
/>
</FormControl>
<FormDescription></FormDescription>