前言
前段时间学习了react
,然后顺便研究了以下umijs
框架,自己拿着搭了一下,首先碰到的就是路由权限和动态菜单栏的问题,然后查阅网上的资料:方法分为两种:一种是通过wrappers+access,还有一种就是通过patchRoutes 生命周期 路由加载前发起请求从后端获取路由表然后动态添加.然后都感觉不符合我自己的想法我就自己琢磨了两天实现动态路由无非两种模式 一种后端给路由表(我自己写的后端所以没用这种),还有一种就是返回权限的名称根据名称去过滤路由然后动态添加(我选择了后者)
解决思路,及遇到问题
render
生命周期中判断自己本地是否有token,有token那就去获取个人用户信息没有就直接跳到login页面去patchRoutes
生命周期中在路由加载前处理路由并且添加到路由表里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()刷新页面 也算是磕出来了!!