016 Umi 项目中的菜单与权限

简介: 016 Umi 项目中的菜单与权限

image.png


上节课中我们完成了页面的大致布局的编写,今天我们主要把重点放在菜单配置中,为什么这么一个简单的菜单配置要单独写一篇文章来说明呢?


因为他在后续的“动态菜单”,权限校验等环节都有很重要的作用。


首先你要先把菜单数据上升到页面级数据,虽然它只是一个组件,但是它里面的数据需要和页面数据(主要是路由)关联上,几乎所有的导航组件,都需要有这一点的意识。


首先我们需要获取到当前项目的所有路由信息,这有两种方式,一种是配置式,自己整理出一个清单,每增加一个页面都更新这个清单,有个好处就是后续菜单交由服务端管控的时候,可以直接将这份数据给他。坏处就是页面数是固定的,后续想做动态菜单,有点困难,因为你配置的路由信息需要是一个“最大值”,否则未配置的页面没有被引用则不会被编译。


另一种就是约定式的,新建一个页面,即增加一个菜单信息,我们通过 Umi 提供的 API 获取到最新的页面路由信息,调用一些工具类,将他们转换成菜单数据,后面维护心智很低。约定式的方式,所有的页面都会被构建,只是通过菜单加权限来控制页面是否可访问,可以实现类似动态路由这样的需求。缺点就是需要独立维护一份菜单数据,主要是页面名称的“翻译文档“。比如首页 ”/home“ 在菜单中应该显示 “首页”。



获取当前页面数据

Umi@4 中要获取页面配置非常的简单,只需要使用 useAppData 即可,它返回全局的应用数据。

declare function useAppData(): {
  routes: Record<id, Route>;
  routeComponents: Record<id, Promise<React.ReactComponent>>;
  clientRoutes: ClientRoute[];
  pluginManager: any;
  rootElement: string;
  basename: string;
  clientLoaderData: { [routeKey: string]: any };
  preloadRoute: (to: string) => void;
};
复制代码


routesclientRoutes 这两个数据都是路由数据,前者是对象,以 pathnamekey,以 parentId 来标记层级和嵌套关系。后者是一个数组,以 children 来表示树形结构。


const routes = {
    'a':{
        parentId: "b"
        path: "a"
    },
    'b':{
        path: "b"
    },
} 
复制代码
const clientRoutes = [{
    path: "b",
    children:[{
        path: "a"
    }]
}]
复制代码


以上两个数据“对等”。

所以我们要取到当前的所有的路由配置信息,则


import { useAppData } from "umi";
const App = ()=>{
    const { clientRoutes } = useAppData();
    const { children } = clientRoutes[0];
}
复制代码



将路由转化成菜单数据

const clientRoutes = [{
    path: "b",
    children:[{
        path: "a"
    }]
}];
// 转化为
const menuData = [{
    key:"/b",
    icon:<PieChartOutlined />,
    label:"首页",
    children:[{
        key:"/a",
        icon:<UserOutlined />,
        label:"用户",
    }]
}];
复制代码


通过观察分析,我们发现,其实路由数据中,我们只有 pathchildren 数据有用,而菜单数据中,我们还需要 iconlabel ,这时候就需要引入我们前面提到的 翻译文档 了。


const menuHash: any = {
  "/": {
    label: "首页",
    icon: <PieChartOutlined />,
  },
  user: {
    label: "用户",
    icon: <UserOutlined />,
  },
};
复制代码


至此我们的 路由转菜单的工具类 为:

const getItem = (path: string, children?: MenuItem[]) => {
  const route = menuHash[path];
  return {
    key: path.startsWith("/") ? path : `/${path}`,
    icon: route?.icon || <></>,
    children,
    label: route?.label || path,
  } as MenuItem;
};
const routesToMenu = (routes: any[]): MenuItem[] => {
  return routes
    .map((route) => {
      const { path, children } = route;
      if (children) {
        return getItem(path, routesToMenu(children));
      }
      return getItem(path);
    });
};
复制代码


运行项目,访问 http://127.0.0.1:8888/

image.png

这是你会发现,菜单中有很多我们之前写的 demo 页面,我们并不想让他们展示出来。所以我们需要增加一个访问权限的黑名单。


const unaccessible = ["/hooks", "/useEffect", "/usemodel", "/useState"];
复制代码


只要简单的修改一下,我们的 routesToMenu 方法即可。

const routesToMenu = (routes: any[]): MenuItem[] => {
  return routes
    .filter((i) => {
      const path = i.path.startsWith("/") ? i.path : `/${i.path}`;
      return !unaccessible.includes(path);
    })
    .map((route) => {
      const { path, children } = route;
      if (children) {
        return getItem(path, routesToMenu(children));
      }
      return getItem(path);
    });
};
复制代码

保存代码,你讲看到菜单中只有两个数据了。



增加页面权限

但是这只是将路由入口隐藏了,如果用户知道你的路由信息,比如此时我们直接当问 http://127.0.0.1:8888/usemodel,虽然菜单已经过滤了但是我们依旧可以直达页面。

image.png


其实原理也很简单,只要判断当前页面 pathname 在我们的不可访问清单就返回 403 页面即可,这个要看你们项目中的权限采用的是黑名单模式还是白名单模式了,黑名单模式匹配上拦截,白名单模式匹配上放行。


import { Result, Button } from "antd";
  if (unaccessible.includes(location.pathname)) {
    return (
      <Result
        status="403"
        title="403"
        subTitle="抱歉,你没有权限访问这个页面!"
        extra={
          <Button type="primary" onClick={() => navigate(-1)}>
            返回上一个页面
          </Button>
        }
      />
    );
  }
复制代码

image.png



菜单与路由跳转

需要实现的功能,点击菜单触发路由跳转,当前页面对应的菜单项需要高亮显示。

import { useAppData, useNavigate, useLocation } from "umi";
const App = ()=>{
  const navigate = useNavigate();
  const location = useLocation();
  const { clientRoutes } = useAppData();
  const { children } = clientRoutes[0];
  const items = routesToMenu(children);
  return <Menu
    theme="dark"
    onClick={(e) => {
      navigate(e?.key);
    }}
    defaultSelectedKeys={[location.pathname]}
    mode="inline"
    items={items}
  />;
}
复制代码

image.png


至此,我们的菜单与权限部分的所有功能都开发完毕。这里面需要引申到项目的权限管控上,将 unaccessible 和用户的登录信息关联上,就可以了。如果有面试官问你,你们项目中的权限部分是怎么做的?如果用户知道你的页面url是否可以直接访问页面,如何拦截?你应该可以回答的很明白了。当然这节课只是为了讲明白原理,在实际项目开发中我们可以用上 layout 和 access 插件的组合来更合理的完成权限和菜单。


源码归档

目录
相关文章
|
JavaScript 前端开发 数据库
【uniapp】文件授权验真系统(含代码)
【uniapp】文件授权验真系统(含代码)
|
前端开发 JavaScript API
020 Umi@4 中如何实现动态菜单
020 Umi@4 中如何实现动态菜单
1049 0
020 Umi@4 中如何实现动态菜单
|
2月前
|
JavaScript
Vue实现按钮级别权限
文章介绍了在Vue中实现按钮级别权限的两种方法:使用自定义Vue指令和使用v-if指令配合自定义方法。
25 4
Vue实现按钮级别权限
|
4月前
uniapp 新建组件
uniapp 新建组件
49 0
|
4月前
|
JavaScript 数据安全/隐私保护
vue 页面权限控制、按钮权限控制
vue 页面权限控制、按钮权限控制
31 0
|
6月前
|
JavaScript 安全 前端开发
Vue 项目中的权限管理:让页面也学会说“你无权访问!
Vue 项目中的权限管理:让页面也学会说“你无权访问!
234 3
|
6月前
|
JavaScript 前端开发
vue自定义指令_按钮权限设计(从0创建项目开始设计)
vue自定义指令_按钮权限设计(从0创建项目开始设计)
52 1
|
6月前
|
JavaScript 前端开发
ant design vue 配置菜单外部打开
ant design vue 配置菜单外部打开
47 0
|
缓存 前端开发 NoSQL
vue-element-admin实战 | 第二篇: 最小改动接入后台实现根据权限动态加载菜单
vue-element-admin实战 | 第二篇: 最小改动接入后台实现根据权限动态加载菜单
|
JavaScript 开发工具 git
vue项目中git命令常用以及一个新页面的建立流程
vue项目中git命令常用以及一个新页面的建立流程
78 0