umi实现动态获取菜单权限

文章目录

    • 前景
    • 登录组件编写
    • 登录逻辑
    • 菜单的时机
    • 动态路由
    • 页面刷新
    • 手动修改地址

前景

不同用户拥有不同的菜单权限,现在我们实现登录动态获取权限菜单。

登录组件编写

//当我们需要使用dva的dispatch函数时,除了通过@connect函数包裹组件还可以使用这种方式来实现
import { getDvaApp } from 'umi';
const Login = ({ form ,dispatch}) => {
    const { getFieldDecorator, validateFields } = form; // 从 props 中解构出 form 方法
    /**
     * export default Form.create()(Login);
     */
    const onSubmit = () => {
        validateFields((err, values) => {
            if (!err) {
                // 处理登录逻辑
                getDvaApp()._store.dispatch({
                    type:"globalModel/login",
                    payload:{
                        username: values.username,
                        password: values.password,
                    }
                });
            } else {
                console.log('Validation Failed:', err);
            }
        });
    };
    return (
        <div className="login-page">
            <div className="login-container"  style ={{
                marginTop: '7%',
                marginRight: '13%'
            }}>
                <div className="image-container">
                    <img src={loginImage} alt="Login" />
                </div>
                <div className="form-container">
                    <h2>欢迎回来</h2>
                    <Form
                        name="login"
                        layout="vertical"
                    >
                        <Form.Item label="用户名">
                            {getFieldDecorator('username', {
                                rules: [{ required: true, message: '请输入用户名!' }],
                            })(<Input placeholder="请输入用户名" />)}
                        </Form.Item>
                        <Form.Item label="密码">
                            {getFieldDecorator('password', {
                                rules: [{ required: true, message: '请输入密码!' }],
                            })(<Input.Password placeholder="请输入密码" />)}
                        </Form.Item>
                        <Form.Item>
                            <Button type="primary" onClick={onSubmit} className="login-button">
                                登录
                            </Button>
                        </Form.Item>
                    </Form>
                </div>
            </div>
        </div>
    );
};
// 使用 Form.create 包裹 Login 组件 从而获取form中相关的函数 否则需要使用队Form组件设置ref 通过ref来实现详见loginbak.js
export default Form.create()(Login);



效果如下所示

在这里插入图片描述

登录逻辑

上面登录组件我们点击登录后,使用dispatch函数发送了一个访问请求,该请求处理逻辑处理来自dva中一个namespace叫做globalModel的model,代码如下所示:

getDvaApp()._store.dispatch({
                    type:"globalModel/login",
                    payload:{
                        username: values.username,
                        password: values.password,
                    }
                });

在globalModel这个model中,我们在effect块中定义了一个login函数,他主要做一下操作:

  • 访问后端接口
  • 存储登录的数据
  • 跳转到/index页面

menuList: 是当前用户拥有的菜单权限

   //import {routerRedux} from 'dva/router'
   //import * as requestUtil from "../utils/request";
	//globalModel文件内
  state: {
    menuList: [],//当前用户拥有的菜单权限
  },
    *login({payload:{username,password}}, { select, call, put }) {
      const {data, code, msg} = yield call(globalModelService.login,{username,password});
      if (code === 200){
        requestUtil.save(data);
        yield put({
          type: 'updateState',
          payload:{
            menuList: data.menuList,
          }
        });
        //去首页信息
        yield put(routerRedux.push('/index'))

      } else {
        message.error("登陆失败!");
      }
    },
//requestUtil内容
export function save({accessToken,refreshToken,menuList}){
    sessionStorage.setItem("accessToken",accessToken);
    sessionStorage.setItem("refreshToken",refreshToken);
    sessionStorage.setItem("menuList",JSON.stringify(menuList));
}

菜单的时机

上面我们将数据存储在了sessionStorage中,下面我们来看如何使用这些数据,首先我们看看路由信息。

//src/routes 这个是定义的全局路由,各个模块的路由信息将在这里汇总

module.exports = [
  {
    path: "/",
    exact: true,
    redirect:"/login",//跳转到登录页
  },
  {
    path: "/login",
    exact: true,
    component: "@/layouts/login/login.js",
  },
  {
    //不能加exact=true
    path: "/",
    component: "@/layouts/index.js",
    routes: [//routes将会作为 index.js 中BasicRoute组件的props信息
      {
        path: '/index',
        component: '@/pages/atscript/basic.tsx'
      },
      ...routeConsole(practice_routes),
      ...routeConsole(lesson_routes)
    ]
  }
];

这个routes将在.umirc.ts作为系统的路由配置

登录完成后页面被重定向到/index中,通过路由可以发现首先会加载父路径/下的@/layout/index.js的资源,相关代码如下所示,在该组件中我有一个SysMenu组件,所需的参数是当前用户获取的菜单权限信息

const BasicRoute = (props) => {
  const {globalModel,dispatch} = props;
  const historyHook = useHistory();
  let {
    menuList
  } = globalModel;
  useEffect(()=>{
    const curMenuList = (!!menuList && menuList.length > 0 )? menuList :
        (!!sessionStorage.getItem("menuList")? JSON.parse(sessionStorage.getItem("menuList")) : []);
    dispatch({
      type: 'globalModel/updateState',
      payload:{
        menuList: curMenuList,
      }
    });
  },[menuList]);
  const sysMenuProps ={
    menuList: menuList
  }
  return (
    <SysMenu {...sysMenuProps}>
      {props.children}
    </SysMenu>
  );
}

// 指定订阅数据,这里关联了 model的namespace = globalModel
function mapStateToProps({globalModel }) {
  return {globalModel};
}

// 建立数据关联关系 对于关联组件名称
export default connect(mapStateToProps)(BasicRoute);

useEffect 一是为了防止页面刷新数据导致菜单信息丢失,二是可以避免menuList数据有延迟导致渲染问题(第一次默认值为[])

下面我们来到SysMenu组件,最终生成菜单信息来自MenuTree 组件。

 <Layout>
      <Sider trigger={null} collapsible collapsed={collapsed}>
        <div className={layoutModule.logo} >
          学习系统 !!!
        </div>
        <MenuTree {...menuTreeProps} />
      </Sider>
      <Layout>
        <Header style={{ background: '#fff', padding: 0 }}>
          <Icon
            className={layoutModule.trigger}
            type={collapsed ? 'menu-unfold' : 'menu-fold'}
            onClick={onCollapse}
          />
        </Header>
        <Content className={layoutModule.content}>
          <Breadcrumb  separator=">" style={{ margin: '16px 0' }}>
            <Breadcrumb.Item>User</Breadcrumb.Item>
            <Breadcrumb.Item>Bill</Breadcrumb.Item>
          </Breadcrumb>
          <div style={{ padding: 24, background: '#fff', minHeight: '90%' }}>
            {props.children}
          </div>
        </Content>
        <Footer style={{ textAlign: 'center' }}>Ant Design ©2018 Created by Ant UED</Footer>
      </Layout>
    </Layout>

动态路由

前面我们配置了路由地址,实际情况会根据用户的不同权限展示不同的菜单,下面我们将菜单抽取出一个专门的组件来动态处理。文件目录如下所示:
在这里插入图片描述
其中SysMenu文件中菜单配置变化如下:

目前我们指定菜单属性只有5个属性(后台已封装为父子结构),分别为nameurliconkeychildren,按照顺序依次表现为菜单名称,菜单路由,菜单图标,唯一key,菜单的子级节点。数据结构如下所示:

    public static List<Menu> getMenuList() {
        List<Menu> menuList = new ArrayList<>();
        menuList.addAll(Arrays.asList(
                new Menu("学习模块", "#", "build", "lesson", new ArrayList<>())
                        .addChild(new Menu("Ref学习", "/lesson/reftest", "skin", "/lesson/reftest", new ArrayList<>())),

                new Menu("练习程序", "#", "book", "practice", new ArrayList<>())
                        .addChild(new Menu("计算程序", "/practice/calculate", "snippets", "/practice/calculate", new ArrayList<>()))
                        .addChild(new Menu("中央空调", "/practice/air", "skin", "/practice/air", new ArrayList<>()))
                        .addChild(new Menu("流程管理", "/practice/activiti", "user", "/practice/activiti", new ArrayList<>())),


//                new Menu("文件操作", "#", "build", "fileManage", new ArrayList<>())
//                        .addChild(new Menu("整体上传", "/fileManage/commonUploadFile", "user", "/fileManage/commonUploadFile", new ArrayList<>()))
//                        .addChild(new Menu("分片上传", "/fileManage/filePartUploadFile", "user", "/fileManage/filePartUploadFile", new ArrayList<>()))
//                        .addChild(new Menu("文件秒传", "/fileManage/flashUploadFile", "user", "/fileManage/flashUploadFile", new ArrayList<>()))
//                        .addChild(new Menu("断点续传", "/fileManage/breakPointUploadFile", "user", "/fileManage/breakPointUploadFile", new ArrayList<>())),

                new Menu("支付对接", "#", "build", "pay", new ArrayList<>())
                        .addChild(new Menu("支付宝web支付", "/pay/AliWebPay", "user", "/pay/AliWebPay", new ArrayList<>())),

                new Menu("二维码", "/qrcode/qrcode", "build", "/qrcode/qrcode", new ArrayList<>())));

        return menuList;
    }

MenuTree组件整体结构如下,其中extractMenus方法则是将后台返回的权限菜单进行转化为对应的配置。

const MenuTree = ()=>{  
  return(
    <Menu theme="dark" mode="inline" defaultSelectedKeys={[menuList[0].key]}>
      {extractMenus(menuList)}
    </Menu>
  );
}
export default MenuTree;
  const extractMenus = (list) =>{
    return list.map((item, index) => (
      doExtractMenus(item)
    ));
  }

  //显示菜单项
  const doExtractMenus = (item)=>{
    if(item.children.length == 0)
    {
      return (
        <Menu.Item key={item.key}>
          <Icon type={item.icon}/>
          <span>{item.name}</span>
          <Link to={item.url}/>
        </Menu.Item>
      );
    }
    else
    {
      return (
        <SubMenu key={item.key} title={
          <span>
            <Icon type={item.icon}/>
            <span>{item.name}</span>
          </span>
        }>
          {extractMenus(item.children)}
        </SubMenu>
      );
    }
  }

页面刷新

当我们刷新页面后丢失了菜单的选中信息,实际上需要还是在对应选中的菜单节点,下面我们来避免这个问题,我们需要记录以下信息:

  • 原来展开的菜单节点信息
  • 原来选中的菜单
//import { useHistory } from 'react-router-dom';
//import {useEffect, useState} from "react";
const MenuTree = (props)=>{
  const historyHook = useHistory();
  const menuList = props.menuList;
  const [state,setState] = useState ({
    visible:false,
    menuTree:[],
    defaultOpenKeys:[],
    defaultSelectedKeys:[historyHook.location.pathname]
  });

  useEffect(()=>{
    const defaultOpenKeys = [historyHook.location.pathname];
    const menuTree = extractMenus(menuList,null,defaultOpenKeys);
    console.log("defaultOpenKeys",defaultOpenKeys);
    setState({
      ...state,
      visible: true,
      defaultOpenKeys: defaultOpenKeys,
      menuTree:menuTree,
    })
  },[menuList])

  //显示菜单列表
  const extractMenus = (list, parent,curOpenKeys) =>{
    return list.map((item, index) => doExtractMenus(item, parent,curOpenKeys));
  }

  //显示菜单项
  const doExtractMenus = (item, parent,curOpenKeys)=> {
    //需要展开父节点
    if (!!parent && item.url == state.defaultSelectedKeys[0]) {
      console.log("parent", parent);
      curOpenKeys.push( parent.key);
    }
    //没有子节点
    if(item.children.length == 0)
    {
      return (
        <Menu.Item key={item.key}>
          <Icon type={item.icon}/>
          <span>{item.name}</span>
          <Link to={item.url}/>
        </Menu.Item>
      );
    }
    //当前时父节点
    else
    {
      return (
        <SubMenu key={item.key} title={
          <span>
            <Icon type={item.icon}/>
            <span>{item.name}</span>
          </span>
        }>
          {extractMenus(item.children, item,curOpenKeys)}
        </SubMenu>
      );
    }
  }
  console.log("state",state);

  /**
   * defaultOpenKeys 的使用:defaultOpenKeys 只在组件首次渲染时生效。组件执行顺序
   * render => useEffect 所以利用state.visible来控制
   */
  return(
      state.visible &&
    <Menu theme="dark" mode="inline" defaultSelectedKeys={state.defaultSelectedKeys} defaultOpenKeys={state.defaultOpenKeys}>
      {state.menuTree}
    </Menu>
  );

}

上面我们通过监听菜单信息menuList(第一次进来为[])将原来extractMenus方法新增parent,curOpenKeys,前者是为了处理选中节点,后者是为了记录需要展开的父节点信息

当第一次进来时menuList为[],导致defaultOpenKeys一直为[] ,为了避免Menu 第一次挂在后,后续刷新defaultOpenKeys将不再生效,组件使用state.visible 来控制挂载时机

手动修改地址

登录完成后避免可以通过修改浏览器直接访问/login,也就是跳转到登录页面,我们需要配合globalModel中的监听函数subscriptions函数

//globalModel.js中
  subscriptions: {
    setup({ dispatch, history}) {
      return history.listen(location => {
      	//如果有登录信息,直接访问/login则重定向到/index页面
           if (!!sessionStorage.getItem("refreshToken") && history.location.pathname === "/login"){
              history.push("/index");
          }
        }
		});
	},
 },

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/935525.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

swagger-codegen

一、通过Swagger生成客户端代码 下载&#xff1a;https://github.com/swagger-api/swagger-codegen#编译打包 cd E:\软件空间\代码生成\swagger-codegen-3.0.64 mvn clean package#指定swagger地址生成客户端代码 cd E:\软件空间\代码生成\swagger-codegen-3.0.64\modules\swa…

Kael‘thas Sunstrider Ashes of Al‘ar

Kaelthas Sunstrider 凯尔萨斯逐日者 <血精灵之王> Kaelthas Sunstrider - NPC - 魔兽世界怀旧服TBC数据库_WOW2.43数据库_70级《燃烧的远征》数据库 Ashes of Alar 奥的灰烬 &#xff08;凤凰 310%速度&#xff09; Ashes of Alar - Item - 魔兽世界怀旧服TBC数据…

7.Vue------$refs与$el详解 ------vue知识积累

$refs 与 $el是什么&#xff1f; 作用是什么? ref&#xff0c;$refs&#xff0c;$el &#xff0c;三者之间的关系是什么&#xff1f; ref (给元素或者子组件注册引用信息) 就像你要给元素设置样式&#xff0c;就需要先给元素设定一个 class 一样&#xff0c;同理&#xff0c;…

医院门诊预约挂号管理系统设计与实现

文末获取源码和万字论文&#xff0c;制作不易&#xff0c;感谢点赞支持。 医院门诊预约挂号管理系统设计与实现 摘 要 本医院门诊预约挂号管理系统是针对目前医院门诊预约挂号管理的实际需求&#xff0c;从实际工作出发&#xff0c;对过去的医院门诊预约挂号管理系统存在的问题…

学习记录,泛型界限1

泛型界限 上限 泛型的上限&#xff0c;下限。对类型的更加具体的约束&#xff01; 如果给某个泛型设置了上界&#xff1a;这里的类型必须是上界 如果给某个泛型设置了下界&#xff1a;这里的类型必须是下界

OpenAI直播发布第4天:ChatGPT Canvas全面升级,免费开放!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…

[工具升级问题] 钉钉(linux版)升级带来的小麻烦

本文由Markdown语法编辑器编辑完成。 1. 背景: 今日钉钉又发布了新的升级版本。由于我工作时使用的是Ubuntu 20.04版本&#xff0c;收到的升级推送信息是&#xff0c;可以升级到最新的7.6.25-Release版本。根据钉钉官方给出的历次更新版说明&#xff0c;这个新的版本&#xf…

DPDK用户态协议栈-TCP Posix API 2

tcp posix api send发送 ssize_t nsend(int sockfd, const void *buf, size_t len, __attribute__((unused))int flags) {ssize_t length 0;void* hostinfo get_host_fromfd(sockfd);if (hostinfo NULL) {return -1;}struct ln_tcp_stream* stream (struct ln_tcp_stream…

【网络开发-socket编程】

1 socket 简介 socket&#xff08;套接字&#xff09;是linux下的一种进程间通信机制&#xff0c;使用socket的IPC可以使得不同主机之间通信&#xff0c;也可以是同一台主机的不同程序之间。socket通常是客户端<------>服务端的通信模式&#xff0c;多个客户端可以同时连…

Python实现中国象棋

探索中国象棋 Python 代码实现&#xff1a;从规则逻辑到游戏呈现 中国象棋&#xff0c;这款源远流长的棋类游戏&#xff0c;承载着深厚的文化底蕴与策略智慧。如今&#xff0c;借助 Python 与 Pygame 库&#xff0c;我们能够在数字世界中复刻其魅力&#xff0c;深入探究代码背后…

TensorFlow深度学习实战(1)——神经网络与模型训练过程详解

TensorFlow深度学习实战&#xff08;1&#xff09;——神经网络与模型训练过程详解 0. 前言1. 神经网络基础1.1 神经网络简介1.2 神经网络的训练1.3 神经网络的应用 2. 从零开始构建前向传播2.1 计算隐藏层节点值2.2 应用激活函数2.3 计算输出层值2.4 计算损失值2.4.1 在连续变…

vue-router路由传参的两种方式(params 和 query )

一、vue-router路由传参问题 1、概念&#xff1a; A、vue 路由传参的使用场景一般应用在父路由跳转到子路由时&#xff0c;携带参数跳转。 B、传参方式可划分为 params 传参和 query 传参&#xff1b; C、而 params 传参又可分为在 url 中显示参数和不显示参数两种方式&#x…

数据库同步中间件DBSyncer安装配置及使用

1、介绍 DBSyncer&#xff08;英[dbsɪŋkɜː]&#xff0c;美[dbsɪŋkɜː 简称dbs&#xff09;是一款开源的数据同步中间件&#xff0c;提供MySQL、Oracle、SqlServer、PostgreSQL、Elasticsearch(ES)、Kafka、File、SQL等同步场景。支持上传插件自定义同步转换业务&#xf…

基于python快速部署属于你自己的页面智能助手

文章目录 前言一、实现目标二、代码解析2.1目录结构2.2 后端&#xff1a;Flask 服务器的搭建2.2.1 安装 Flask2.2.2 创建 Flask 应用 2.3 实现聊天界面与消息交互2.3.1 创建聊天界面 三、跨域问题的解决3.1 安装 flask-cors3.2 在 Flask 中启用 CORS 五 效果展示 前言 AI 聊天机…

Docker在Ubuntu和CentOS系统下的安装

目录 1. 各版本平台支持情况2. 在Ubuntu系统下安装docker3. 常见报错4. Docker的镜像源修改5. Docker目录修改6. 在CentOS系统下安装docker 1. 各版本平台支持情况 &#xff08;1&#xff09;平台支持情况如下&#xff1a; Server 版本 桌面版本 2. 在Ubuntu系统下安装docker…

深入解析强化学习中的 Generalized Advantage Estimation (GAE)

中文版 深入解析强化学习中的 Generalized Advantage Estimation (GAE) 1. 什么是 Generalized Advantage Estimation (GAE)? 在强化学习中&#xff0c;计算策略梯度的关键在于 优势函数&#xff08;Advantage Function&#xff09; 的设计。优势函数 ( A ( s , a ) A(s, a…

【开源】基于SpringBoot框架的个性化的旅游网站 (计算机毕业设计)+万字毕业论文 T025

系统合集跳转 源码获取链接 一、系统环境 运行环境: 最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以 tomcat环境&#xff1a; Tomcat 7.x,8.x,9.x版本均可 操作系统…

java web 实验五 Servlet控制层设计(设计性)

实验五 Servlet控制层设计&#xff08;设计性&#xff09; //代码放在资源包里了 实验目的 熟悉Servlet的基本语法。掌握采用HTML、JS、JDBC、JSP、Servlet和四层结构的综合应用。实验要求 本实验要求每个同学单独完成&#xff1b;调试程序要记录调试过程中出现的问题及解决…

汽车保养系统+ssm

摘 要 由于APP软件在开发以及运营上面所需成本较高&#xff0c;而用户手机需要安装各种APP软件&#xff0c;因此占用用户过多的手机存储空间&#xff0c;导致用户手机运行缓慢&#xff0c;体验度比较差&#xff0c;进而导致用户会卸载非必要的APP&#xff0c;倒逼管理者必须改…

仿iOS日历、飞书日历、Google日历的日模式

仿iOS日历、飞书日历、Google日历的日模式&#xff0c;24H内事件可自由上下拖动、自由拉伸。 以下是效果图&#xff1a; 具体实现比较简单&#xff0c;代码如下&#xff1a; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color;…