Umijs+权限菜单

1. 配置权限插件(@umijs/plugin-access)

先安装或启用权限插件,在 .umirc.ts 中配置:

typescript

复制

arduino 复制代码
// .umirc.ts
export default {
  plugins: ['@umijs/plugins/dist/access'],
  access: {},
  // 其他配置...
}

2. 创建 access.ts 定义权限规则

typescript

复制

typescript 复制代码
// src/access.ts
export default function access(initialState: { permissions?: string[] }) {
  const { permissions = [] } = initialState || {};
  return {
    // 示例:根据后端返回的权限标识控制权限
    canAdmin: permissions.includes('admin'),
    canRead: (routeName: string) => permissions.includes(routeName),
  };
}

3. 实现动态权限路由和菜单(核心代码)

app.tsx 中实现以下逻辑:

typescript

复制

typescript 复制代码
// src/app.tsx
import { history } from 'umi';

// 类型定义(根据后端接口调整)
interface BackendRoute {
  path: string;
  name: string;
  icon?: string;
  component: string;
  access: string; // 权限标识
  children?: BackendRoute[];
}

export async function getInitialState() {
  // 1. 获取用户权限数据(模拟从后端接口获取)
  const { permissions, dynamicRoutes } = await fetch('/api/auth/routes')
    .then(res => res.json());

  // 2. 返回初始状态(包含权限和菜单数据)
  return { 
    permissions, 
    menuData: generateMenuData(dynamicRoutes), // 生成菜单结构
    dynamicRoutes 
  };
}

// 动态路由转换器
const generateMenuData = (routes: BackendRoute[]) => {
  return routes.map(route => ({
    path: route.path,
    name: route.name,
    icon: route.icon,
    children: route.children ? generateMenuData(route.children) : undefined,
  }));
};

// 运行时动态插入路由
export function patchRoutes({ routes }: { routes: any[] }) {
  // 找到需要插入路由的位置(通常是在某个布局路由的 children 中)
  const layoutRoute = routes.find(route => route.path === '/');
  if (layoutRoute && layoutRoute.children) {
    layoutRoute.children.push(...initialState?.dynamicRoutes.map(convertRoute));
  }
}

// 将后端路由转换为 Umi 路由格式
const convertRoute = (route: BackendRoute) => ({
  path: route.path,
  name: route.name,
  access: route.access, // 对应 access.ts 中的权限规则
  component: require(`@/pages/${route.component}`).default,
  routes: route.children?.map(convertRoute),
});

// 动态菜单渲染
export const layout = {
  menuDataRender: () => initialState?.menuData || [],
};

// 路由守卫(可选)
export function onRouteChange({ location }: any) {
  const access = useAccess();
  const isLogin = localStorage.getItem('token');
  
  // 未登录跳转登录页
  if (!isLogin && location.pathname !== '/login') {
    history.push('/login');
  }
  
  // 检查页面权限(示例)
  if (!access.canRead(location.pathname)) {
    history.push('/403');
  }
}

4. 后端接口数据格式示例

后端应返回类似以下结构的 JSON 数据:

json

复制

json 复制代码
{
  "permissions": ["dashboard", "user_manage"],
  "dynamicRoutes": [
    {
      "path": "/dashboard",
      "name": "仪表盘",
      "component": "Dashboard/index",
      "access": "canReadDashboard"
    },
    {
      "path": "/user",
      "name": "用户管理",
      "component": "User/index",
      "access": "user_manage",
      "children": [
        {
          "path": "/user/list",
          "name": "用户列表",
          "component": "User/List"
        }
      ]
    }
  ]
}

5. 页面组件中控制按钮级权限

tsx

复制

javascript 复制代码
// src/pages/User/List.tsx
import { useAccess } from 'umi';

export default () => {
  const access = useAccess();
  
  return (
    <div>
      {access.user_manage && <button>删除用户</button>}
    </div>
  );
}

关键点说明:

  1. 权限初始化 :通过 getInitialState 获取用户权限和动态路由数据

  2. 动态路由注入 :使用 patchRoutes 将后端路由转换为 Umi 路由格式

  3. 菜单动态渲染 :通过 menuDataRender 结合后端返回的结构生成菜单

  4. 细粒度控制

    • 路由级:通过 access 属性控制路由可见性
    • 组件级:使用 useAccess 钩子
    • 按钮级:直接在组件中判断权限

常见问题处理:

  1. 热更新问题:动态路由修改后可能需要手动刷新页面
  2. 组件加载 :确保 component 路径与后端返回的组件路径匹配
  3. 权限同步:当权限变更时需清除缓存并刷新页面(或重新获取权限数据)

通过这套方案,可以实现完整的动态权限路由体系,且与后端完全解耦。

相关推荐
知了清语22 分钟前
pnpm之monorepo项目, vite版本冲突, 导致vite.config.ts ts警告处理
前端
弗锐土豆44 分钟前
一个基于若依(ruoyi-vue3)的小项目部署记录
前端·vue.js·部署·springcloud·ruoyi·若依
Hilaku1 小时前
我为什么放弃了“大厂梦”,去了一家“小公司”?
前端·javascript·面试
1undefined21 小时前
element中的table改造成虚拟列表(不定高),并封装成hooks
前端·vue.js
浅墨momo1 小时前
搭建第一个Shopify App
前端·程序员
然我1 小时前
React 事件机制:从代码到原理,彻底搞懂合成事件的核心逻辑
前端·react.js·面试
Codebee1 小时前
OneCode 组件服务通用协议栈:构建企业级低代码平台的技术基石
前端·前端框架·开源
Running_C1 小时前
常见web攻击类型
前端·http
jackyChan1 小时前
ES6 Proxy 性能问题,你真知道吗?🚨
前端·javascript