umi实现动态获取菜单权限

news2025/2/23 21:54:12

文章目录

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

前景

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

登录组件编写

//当我们需要使用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");
          }
        }
		});
	},
 },

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Color-Light-Control-and-Four-Way-Responder based on STM32F103C8T6

Light Control and Responder 若要实现同样效果请看源码: gitee.com/apollo_666/Color-Light-Control-and-Four-Way-Responder # Abstract The design project for a decorative lighting controller enhanced our practical skills and engineering capabilities. During our…

数据库中的运算符

1.算术运算符 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行加&#xff08;&#xff09;、减&#xff08;-&#xff09;、乘&#xff08;*&#xff09;、除&#xff08;/&#xff09;和取模&#xff08;%&…

python爬虫--小白篇【爬取B站视频】

目录 一、任务分析 二、网页分析 三、任务实现 一、任务分析 将B站视频爬取并保存到本地&#xff0c;经过分析可知可以分为四个步骤&#xff0c;分别是&#xff1a; 爬取视频页的网页源代码&#xff1b;提取视频和音频的播放地址&#xff1b;下载并保存视频和音频&#x…

【计算机网络】实验18:动态主机配置协议DHCP的作用

实验18 动态主机配置协议DHCP的作用 一、实验目的 验证动态主机协议DHCP的作用 二、实验环境 Cisco Packet Tracer模拟器 三、实验过程 1.构建网络拓扑&#xff0c;不给局域网中的各主机手动配置IP地址、子网掩码、默认网关、DNS服务器等信息&#xff0c;而是开启动态主机…

MFC案例:基于对话框的简易阅读器

一、功能目标&#xff1a; 1.阅读txt文件 2.阅读时可以调整字体及字的大小 3.打开曾经阅读过的文件时&#xff0c;能够自动从上次阅读结束的位置开始显示&#xff0c;也就是能够保存和再次使用阅读信息。 4.对于利用剪贴板粘贴来的文字能够存储成txt文件保存。 5.显示…

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

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

谷粒商城—分布式基础

1. 整体介绍 1)安装vagrant 2)安装Centos7 $ vagrant init centos/7 A `Vagrantfile` has been placed in this directory. You are now ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on…

MySQL概述以及下载安装

MySQL5.7概述以及下载安装&#xff08;centOS7&#xff09; 一、MySQL简介 MySQL是一个典型的关系数据库&#xff0c;目前是Oracle公司产品之一&#xff0c;也是目前主流使用的关系型数据库之一。使用MySQL可以进行最基本的数据存储、管理、查询等操作&#xff0c;也可以方便的…

中粮凤凰里共有产权看房记

中粮凤凰里看房是希望而来&#xff0c;失望而归。主要是对如下失望&#xff0c;下述仅个人看房感受&#xff1a; 1. 户型不喜欢&#xff1a;三房的厨房和餐厅位置很奇葩 2. 样板间在25楼&#xff1a;湖景一言难尽和有工厂噪声 3. 精装修的交房质量:阳台的推拉门用料很草率 …

轮播(css+js)

目录 1.实现效果 2.基础代码演示 2.1js代码 2.1css样式 2.3实现效果 3.实现点击切换 3.1给button添加点击事件 3.2效果图如下 3.3发现问题 3.3.1不循环 3.3.2循环 1.实现效果 2.基础代码演示 2.1js代码 <div class"out-box"><div class"tes…

深度学习(2)前向传播与反向传播

这一次我们重点讲解前向传播与反向传播&#xff0c;对这里还是有点糊涂 前向传播&#xff08;Forward Propagation&#xff09;和反向传播&#xff08;Backward Propagation&#xff09;是深度学习中神经网络训练的核心过程。它们分别负责计算神经网络的输出以及更新神经网络的…

HTML5 拖拽 API 深度解析

一、HTML5 拖拽 API 深度解析 1.1 背景与发展 HTML5 的拖拽 API 是为了解决传统拖拽操作复杂而设计的。传统方法依赖鼠标事件和复杂的逻辑计算&#xff0c;而 HTML5 提供了标准化的拖拽事件和数据传递机制&#xff0c;使得开发者能够快速实现从一个元素拖拽到另一个元素的交互…

前端自己也能开启HTTPS

目录 前言 使用mkcert 安装 创建证书 利用 mkcert 创建 ca 根据 ca 创建 cert 安装证书 项目开启HTTPS 安装插件 配置 vitecofnig.js 最终效果 前言 今天我发现了一个宝藏&#xff0c;兄弟们&#xff01;就是前端开发阶段是可以使用https来开发的。对不懂前端的后端兄…

精通 Python 网络安全

与 FTP、SSH 和 SNMP 服务器交互 本章将帮助您了解允许我们与 FTP、SSH 和 SNMP 服务器交互的模块。在本章中&#xff0c;我们将探讨网络中的计算机如何相互交互。一些允许我们连接 FTP、SSH 和 SNMP 服务器的工具可以在 Python 中找到&#xff0c;其中我们可以突出显示 FTPLi…

【C++跬步积累】 —— 二叉搜索树(模拟实现+源代码)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;C跬步积累 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日一题 &#x1f7e1; Linux跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0…

Redis安装和Python练习(Windows11 + Python3.X + Pycharm社区版)

环境 Windows11 Python3.X Pycharm社区版 思路 1 github下载redis压缩包 &#xff0c;安装并启动redis服务&#xff0c;在客户端连接redis服务。 2 在pycharm中运行python程序&#xff0c;连接redis服务&#xff0c;熟悉redis的使用和巩固python语言。 3 python开发环境…

【电子通识】能用5V电源去驱动线圈电压12V的继电器吗?

最近新人在使用继电器做一些工装&#xff0c;选择的是一款汽车级的继电器JZC-32F/012-ZS3(555) 。其原因主要是因为封装小&#xff0c;通流能力也OK。 但因为产品是5V USB-TypeC接口供电的&#xff0c;所以他想用5V电源去驱动继电器&#xff0c;从而减少一个电源输入或是电源升…

先进电机拓扑及控制算法介绍(2)——开绕组电机拓扑的容错控制

1.引言 共直流母线开绕组电机拓扑通过打开绕组中性点&#xff0c;电机绕组可由单逆变器供电改为双逆变器供电&#xff0c;这可以将电机的调速范围扩大为原来的两倍&#xff08;考虑到三次谐波反电势的因素&#xff0c;最低也可以把转速扩大为原来的根号3倍&#xff09;。扩大转…

️️️ 避坑指南:如何修复国密gmssl 库填充问题并提炼优秀加密实践20241212

&#x1f6e1;️ 避坑指南&#xff1a;如何修复国密gmssl 库填充问题并提炼优秀加密实践 ✨ 引言 在当下的数据安全环境中&#xff0c;SM4作为中国国家密码算法的代表性选择&#xff0c;被广泛应用于金融、通信和政府领域。然而&#xff0c;在实际开发中&#xff0c;即便是开…

C++ webrtc开发(非原生开发,linux上使用libdatachannel库)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、libdatachannel库的下载和build二、开始使用 1.2.引入库3.开始使用 总结 前言 使用c开发webrtc在互联网上留下的资料甚少&#xff0c;经过我一段时间的探…