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. 权限同步:当权限变更时需清除缓存并刷新页面(或重新获取权限数据)

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

相关推荐
码上奶茶6 分钟前
HTML 列表、表格、表单
前端·html·表格·标签·列表·文本·表单
抹茶san26 分钟前
和 Trae 一起开发可视化拖拽编辑项目(1) :迈出第一步
前端·trae
风吹头皮凉38 分钟前
vue实现气泡词云图
前端·javascript·vue.js
南玖i39 分钟前
vue3 + ant 实现 tree默认展开,筛选对应数据打开,简单~直接cv
开发语言·前端·javascript
小钻风33661 小时前
深入浅出掌握 Axios(持续更新)
前端·javascript·axios
萌萌哒草头将军1 小时前
🚀🚀🚀尤雨溪推荐的这个库你一定要知道!轻量⚡️,优雅!
前端·vue.js·react.js
三门1 小时前
docker安装mysql8.0.20过程
前端
BillKu2 小时前
Vue3 + Vite 中使用 Lodash-es 的防抖 debounce 详解
前端·javascript·vue.js
一只小风华~2 小时前
HTML前端开发:JavaScript的条分支语句if,Switch
前端·javascript·html5
橙子家2 小时前
Select 组件实现【全选】(基于 Element)
前端