feat(rbac): F3 前端 RBAC 按钮级控制
后端: - 新增 GET /user/permissions 端点返回当前用户权限码 - Login 响应附带 permissions 数组 - collectUserPermissionCodes 用 raw SQL JOIN 避免 GORM many2many 表名问题 - admin 用户返回 ['*'] 通配符 前端: - usePermission hook (hasPermission/hasAnyPermission/hasAllPermissions) - <HasPermission> 组件 (permission/anyOf/allOf + fallback) - zustand store 新增 permissions 字段 - Sidebar 菜单递归过滤 (42 菜单项全部标注 permission) - ServerManager: 添加/导入/批量删除按钮按权限显隐 - ServerTableRow: 终端/编辑/文件/删除按权限显隐 - SystemUserTab: 新建/导入/编辑/删除按钮按权限显隐 - MainLayout: 页面刷新自动拉取权限 - Login: 登录时存储 permissions 验证: - admin: permissions=['*'] 通配符 - viewer: 7 个只读权限 (dashboard:view/server:view/...) - viewer 无 server:create/delete - 10/10 回归测试通过 - npm run build 零错误
This commit is contained in:
1 parent
77c234b060
commit
be2a377f01
30 files changed
+2328
-462
No files matched your search
@@ -1,9 +1,11 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useThemeStore } from '@/store';
|
||||
import { useThemeStore, useUserStore } from '@/store';
|
||||
import { Sidebar } from './Sidebar';
|
||||
import { Header } from './Header';
|
||||
import { Breadcrumb } from './Breadcrumb';
|
||||
import { userApi } from '@/services/api';
|
||||
|
||||
/**
|
||||
* MainLayout - 优化后的主布局组件
|
||||
@@ -14,9 +16,32 @@ import { Breadcrumb } from './Breadcrumb';
|
||||
* 3. 添加 Breadcrumb 面包屑导航
|
||||
* 4. 布局过渡动画平滑
|
||||
* 5. 支持响应式布局
|
||||
* 6. F3: 页面刷新后自动拉取当前用户权限(RBAC 按钮级控制)
|
||||
*/
|
||||
export function MainLayout() {
|
||||
const { sidebarCollapsed } = useThemeStore();
|
||||
const token = useUserStore((s) => s.token);
|
||||
const permissions = useUserStore((s) => s.permissions);
|
||||
const setPermissions = useUserStore((s) => s.setPermissions);
|
||||
|
||||
// 页面刷新后 zustand persist 会恢复 token/user,但 permissions 可能丢失或过期
|
||||
// 在 MainLayout 挂载时如果 permissions 为 null 则拉取一次
|
||||
useEffect(() => {
|
||||
if (!token || permissions !== null) return;
|
||||
let cancelled = false;
|
||||
userApi.getMyPermissions()
|
||||
.then((data: any) => {
|
||||
if (cancelled) return;
|
||||
const perms: string[] = data?.permissions ?? [];
|
||||
setPermissions(Array.isArray(perms) ? perms : []);
|
||||
})
|
||||
.catch(() => {
|
||||
// 拉取失败不阻塞页面,权限保持 null(usePermission 默认放行)
|
||||
if (!cancelled) setPermissions([]);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [token]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in new issue
Block a user