umi@3 实现动态添加路由渲染菜单栏 超详细超详细!!

前言

前段时间学习了react,然后顺便研究了以下umijs框架,自己拿着搭了一下,首先碰到的就是路由权限和动态菜单栏的问题,然后查阅网上的资料:方法分为两种:一种是通过wrappers+access,还有一种就是通过patchRoutes 生命周期 路由加载前发起请求从后端获取路由表然后动态添加.然后都感觉不符合我自己的想法我就自己琢磨了两天实现动态路由无非两种模式 一种后端给路由表(我自己写的后端所以没用这种),还有一种就是返回权限的名称根据名称去过滤路由然后动态添加(我选择了后者)

解决思路,及遇到问题

  1. render生命周期中判断自己本地是否有token,有token那就去获取个人用户信息没有就直接跳到login页面去
  2. patchRoutes生命周期中在路由加载前处理路由并且添加到路由表里
  3. getInitialState初始化全局状态管理用于在页面中获取路由表及个人用户信息

确定了选择通过后端给的权限名称的方式及思路,会遇到一个的问题:在app.ts文件中中render,patchRoutes,getInitialState三个生命周期都只会在初始化应用程序的时候执行一次后续不会执行就等于说你在登录页面这三个生命周期就走一遍了当你通过history.push("/")的时候其实是不会再执行了,本来想通过wrappers包装器去解决的没有发现如何去在包装器中动态添加路由我就抛弃了现在使用的思路是用location.reload()刷新的方式去重新初始化应用程序从而达到路由表的渲染

实现代码

config.js 配置文件 及routes

config.js文件:

ts 复制代码
/*
        config.js文件
*/


import { defineConfig } from 'umi';
import routes from './routes';
import alias from './alias.config.ts';
import proxy from './proxy';
export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes,
  alias,
  proxy,
  fastRefresh: {},
  mfsu: false,
  favicon: "/xwya.svg",
  define: {
    'process.env.XWYA_BLOG_PREFIX': process.env.XWYA_BLOG_PREFIX,
  },
});

router.js文件

ts 复制代码
/* 
  router.js文件 这里是config.js文件中routes的配置下和底下routes文件不一样!!别搞混!!
*/

export default [

  {
    path: "/login",
    component: "@/pages/login/index",
  },
  
  {
    path: "/",
    component: "@/layouts/index",
    // wrappers: ['@/wrappers/guard'], // 相当于路由守卫 
    routes: [],
  },
]

app.ts文件

ts 复制代码
import { getUserInfo } from "@/api/user" // 获取个人信息接口
import { getToken,delToken } from "./utils/auth"; // 获取及删除token方法
import { history } from "umi"
import { normalRoutes, asyncRoutes, filteredRoutes } from "@/routes"; // 引入src/routes/index文件存储路由信息和处理路由的方法
let userInfo:any = {} // 用于存储个人信息
const token :string= getToken() // 定义token 
let allRouter:any=[] // 用于存储过滤后的路由

/*
    执行顺序
    render > patchRoutes > getInitialState
    从地下的render函数往上看
*/

// 初始化全局状态 
export async function  getInitialState(): InitialState {

    /*
        在上面已经定义好了如果底下两条线都是成功的就是有数据的没有也有个初始数据 所以可以直接进行返回仓库
    */
  const initialState = {
    userInfo,
    allRouter,
  }

  return initialState
}


// 路由渲染前执行路由处理
export const patchRoutes = async ({ routes }: any) => {  
  /*
  只有在有token的情况下我采取执行过滤路由和合并路由并添加到路由表中
  */
  if (token) { 
    allRouter=[...normalRoutes,...filteredRoutes(asyncRoutes,userInfo.authority) ]
    routes[1].routes=allRouter
  }  
}

// 页面渲染前和初始化的操作
export const render = async (oldRender: any) => {  
   /*
       开启监听刷新的页面用于存储你要跳转的页面路由如果不用你也可以去掉底下直接写死'/index'
       sessionStorage.setItem 用于临时存储地址
   */
  window.addEventListener('beforeunload', function (event) {
    console.log(history.location.pathname, "监听刷新");
    if (history.location.pathname != "/login") { 
      
      sessionStorage.setItem("redirect", history.location.pathname);
    }
  });
   /*
   判断是否有token  有token代表已经登录了发起获取个人信息的请求如果code等于200将信息存储到userInfo函数然后在进行跳转 history.replace(path?path:"/index") 是防止用户能回退到登录页面,如果code不等于200我封装的 fetch 会处理直接删除掉token然后进行页面刷新在进到声明周期的时候就会直接跳转到/login页面
   
   如果没有token直接就是去登录页面
   
   */

  if (token) {
    const res = await getUserInfo()
    if (res.code == 200) {
      userInfo = res.data
      const path = sessionStorage.getItem("redirect")
      history.replace(path?path:"/index")
    }
  } else { 
    history.push(`/login`)
  }
  oldRender()
}

存储路由信息,及过滤路由方法/src/router/index.ts文件

ts 复制代码
// 常规路由
export const normalRoutes = [
  {
    name: '首页',
    icon: 'PieChartOutlined',
    hidden: true,
    path: '/index',
    exact: true,
    component: require('@/pages/index').default,
  },
  {
    exact: true,
    name: '家目录',
    icon: 'SettingOutlined',
    path: '/home',
    component: require('@/pages/home/index').default,
  },
  {
    path: '/',
    redirect: '/index', // 重定向到 /index
  },
];

// 异步路由(我的接口权限那里只返回了一个数据分析所以这里随便写的不用在意)
export const asyncRoutes = [
  {
  
    name: '数据分析',
    icon: 'UsergroupDeleteOutlined',
    path: '/userManagement',
    routes: [
      {
        exact: true,
        name: '论坛用户',
        path: '/userManagement/forumusers',
        component: require('@/pages/userManagement/forumusers/index').default,
      },
      {
        exact: true,
        name: '系统用户',
        path: '/userManagement/systemusers',
        component: require('@/pages/userManagement/systemusers/index').default,
      },
      {
        path: '/userManagement',
        redirect: '/userManagement/forumusers',
      },
    ],
  },

];

// 过滤路由方法
export const filteredRoutes = (router:any,authority: any): any => { 
  return router.filter((item: any) => {     
    if (item.routes && item.routes.length > 0) { 
      item.routes = filteredRoutes(item.routes, authority)
    }
    return   item.redirect || (item.name && authority.includes(item.name))
   })
}

登录页面相关操作

tsx 复制代码
import React, { useEffect, useState } from "react"
import { message} from "antd"
import FlowBnt from "@/components/flowBnt"
import InputBox from "@/components/inputBox"
import { login } from "@/api/user";
import { setToken } from "@/utils/auth";


const FormItemList:any = [
  { type: "text", name: "username" },
  { type: "password", name: "password" },
]

const FormMod: React.FC = (): React.FC => { 
  
  const [formData, setFormData] = useState({ username: '', password: '' });
  // const { userLogin } =useModel('userStore')
  const onInputChange = (name: string, value: string) => {
    setFormData({
      ...formData,
      [name]: value
    });
  };
  /*
      登录的时候记住不是history.push(`/`)了 要用刷新location.reload(); 然后会重新初始化程序你这里setToken了 本地就会有token 然后会去执行获取用户个人信息直接会跳转到/index页面或者你退出的页面(退出的时候也直接location.reload()去刷新页面他会自己回到登录页面)
  */
  const onLogin = async () => { 
    const res = await login(formData)
    if (res.code === 200) { 
      setToken(res.token)
      message.success(res.msg);
      location.reload();
    }
  }
  return (
    <div style={{ width: "100%", height: "100%" }}>
      <h2 style={{margin: "0 0 30px",padding: 0,color: "#fff",textAlign: "center"}}>Login</h2>
      <form>
          {
          FormItemList.map((item, index) => (<InputBox name={item.name} key={ index}  onInputChange={onInputChange} type={item.type}></InputBox>))
          }
        <FlowBnt func={onLogin} text="登录" flowColor="#99FFCC" bntColor="#66CC99" />
      </form>
    </div>
  )
} 

export default FormMod

sidebar组件动态菜单栏

tsx 复制代码
import * as Icon from "@ant-design/icons";
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
import React, { useState,useMemo } from 'react';
import { useHistory, useLocation,useModel} from 'umi';
type MenuItem = Required<MenuProps>['items'][number];
// 处理路由返回Menu所需要的菜单格式
const itemList = (router: any): any => {  
  return router.map((item: any) => {
    if (item.redirect) {
      return null; // 过滤掉重定向路由
    }
    const newItem: any = {
      label: item.name,
      key: item.path,
    };

    if (item.icon) { 
      // 使用 React.createElement 来动态创建图标组件
      newItem.icon=React.createElement(Icon[item.icon])
    }

    if(item.routes && item.routes.length === 2) { 
      newItem.label = item.routes[0].name
      newItem.key = item.routes[0].path
    }

    if (item.routes && item.routes.length > 2) {
      newItem.children = itemList(item.routes);
    } 
    return newItem;
  }).filter(Boolean)
}




const Sidebar: React.FC = (): React.FC => {   
 // 获取全局仓库
  const { initialState,setInitialState} = useModel('@@initialState');
    const items: MenuProps['items'] = useMemo(() => itemList(initialState.allRouter), []);
    const history = useHistory();
    const location = useLocation();
    // 获取当前路由的 pathname
  const currentPathname = location.pathname;  
  const onClick = (e: any) => {     
    history.push(e.key);
  };  
  return (
    <div className="sidebar-container">
      <Menu
      theme="dark"
      onClick={onClick}
      style={{ width: 256 }}
      defaultSelectedKeys={[currentPathname=="/"?"/index":currentPathname]}
      defaultOpenKeys={[currentPathname]}
      mode="inline"
      items={items}
    />
    </div>
  )
}

export default Sidebar

封装的fetch请求

ts 复制代码
import { getToken,delToken } from "./auth";
const prefix = "/api"
import { message} from 'antd';
const queryString = (params) => {
  if (Object.keys(params).length>0) { 
    return '?'+ Object.keys(params).map(key => `${key}=${encodeURIComponent(params[key])}`).join('&')
  }
  return ' '
 }
interface Response { 
  url: string,
  method?: string,
  data?: any
  headers?: any
}

export default async ({ url, method, data, headers}:Response) => {
  try {
    let options = {
      method: method || "GET" ,
      headers: {
        Authorization: "Bearer " + getToken(),
        ...headers
      },
      body: JSON.stringify(data)  ||JSON.stringify({}) 
    };
    if (options.method.toUpperCase() === "GET") { 
      url = prefix+url + queryString(options.body)
      delete options.body
    } else {
      url = prefix+ url
    }
    /*
    可以做一些请求前的操作
    */
    const res = await fetch(url, options)
    const pageData = await res.json()
    
    if (pageData.code == 200) {
      return Promise.resolve(pageData) 
    } else { 
    /*
    做一些code 不等于200的处理
    */
      if (pageData.code == 1004 || pageData.code == 1005 || pageData.code == 1006 || pageData.code == 1007) { 
        delToken()
        location.reload()
      }
      message.error("请求错误:"+pageData.code+':'+pageData.msg);
      return new Error(pageData.code+':'+pageData.msg || "请求错误")
    }
  } catch (err) {
      /*
      统一捕获一些错误的处理
      */
    console.log("整体错误");
    
    return new Error(err)
  }
  }

总结

一开始在和wrappers包装器(相当于vue路由守卫) 死磕发现总是有绕不过去的地方,然后用生命周期又卡在不会重新触发,最后灵机一动想到了location.reload()刷新页面 也算是磕出来了!!

相关推荐
小郝同学(恩师白云)21 分钟前
SpringMVC后续4
java·服务器·前端
优联前端1 小时前
uni-app-通过vue-cli命令行快速上手
开发语言·前端·vue.js·uni-app·优联前端
点燃银河尽头的篝火(●'◡'●)2 小时前
【BurpSuite】Cross-site scripting (XSS 学徒部分:1-9)
前端·web安全·网络安全·xss
Jiaberrr2 小时前
手把手教你:微信小程序实现语音留言功能
前端·微信小程序·小程序·语音·录音
熊猫在哪2 小时前
安装nuxt3
前端·nuxt.js
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_036】5.6 Grid 网格布局中与对齐相关的属性 + 5.7本章小结
前端·css·css3·html5·网格布局·grid·css网格
啧不应该啊4 小时前
vue配置axios
前端·javascript·vue.js
__fuys__4 小时前
【HTML样式】加载动画专题 每周更新
前端·javascript·html
Want5954 小时前
HTML粉色烟花秀
前端·css·html
让开,我要吃人了4 小时前
HarmonyOS鸿蒙开发实战(5.0)自定义全局弹窗实践
前端·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统·鸿蒙开发