权限验证分为菜单权限,路由权限,按钮权限,布局模块权限。
菜单权限
以上实现主要是前端层面的设计方案, 我们都知道前端的安全措施永远是不可靠的, 所以我们为了保证系统的安全性, 一般我们会把菜单数据存到后端, 通过接口动态请求权限菜单.。
可以提前和后端做好约定, 让后端根据不同用户返回不同的权限菜单 schema 即可.
递归法
掘金文章介绍
这个参考了 umi 的路由方法,将路由包裹起来
路由包裹掘金介绍
GitHub 源码展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| import { IRoute, routes } from "@/route"; import React, { Suspense, useMemo } from "react"; import { Redirect, Route, Switch, HashRouter } from "react-router-dom"; import styles from "./index.module.less";
export default function App(): React.ReactElement { const getChildrenComponent = ( route: IRoute, key: number, pPath: string = "" ) => { const path = pPath + route.path; return route.redirect ? ( <Redirect key={key} to={route.redirect} from={route.path}></Redirect> ) : ( (route.component || route.routes?.length > 0) && ( <Route key={key} path={path} exact={route.exact}> {route.wrappers?.length > 0 ? route.wrappers.reduceRight( (element: any, wrapper: any) => React.createElement(wrapper, {}, element), React.createElement( route.component || React.Fragment, {}, <Switch> {route?.routes?.map((croute, rindex) => getChildrenComponent(croute, rindex, path) )} </Switch> ) ) : React.createElement( route.component || React.Fragment, {}, <Switch> {route?.routes?.map((croute, rindex) => getChildrenComponent(croute, rindex, path) )} </Switch> )} </Route> ) ); };
const Routes = useMemo(() => { const _Routes = routes.map((route, rindex) => getChildrenComponent(route, rindex) ); console.log(_Routes, "_Routes"); return _Routes; }, []);
return ( <Suspense fallback={<div>加载中</div>}> <HashRouter basename="/"> <Switch>{Routes}</Switch> </HashRouter> </Suspense> ); }
|
promise 封装法
GitHub 代码展示
Router v6 版本动态路由鉴权
掘金参考文章
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import { lazy } from "react"; import { Navigate } from "react-router-dom";
const lazyLoad = (moduleName: string) => { const Module = lazy(() => import(`views/${moduleName}`)); return <Module />; };
const Appraisal = ({ children }: any) => { const token = localStorage.getItem("token"); return token ? children : <Navigate to="/login" />; };
interface Router { name?: string; path: string; children?: Array<Router>; element: any; }
const routes: Array<Router> = [ { path: "/login", element: lazyLoad("login"), }, { path: "/", element: <Appraisal>{lazyLoad("sand-box")}</Appraisal>, children: [ { path: "", element: <Navigate to="home" />, }, { path: "*", element: lazyLoad("sand-box/nopermission"), }, ], }, { path: "*", element: lazyLoad("not-found"), }, ];
export default routes;
|
每次导航列表更新时,再触发路由更新 action
handelFilterRouter 就是根据导航菜单列表 和权限列表 得出路由表的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import { INITSIDEMENUS, UPDATUSERS, LOGINOUT, UPDATROUTES } from "./contant"; import { getSideMenus } from "services/home"; import { loginUser } from "services/login"; import { patchRights } from "services/right-list"; import { handleSideMenu } from "@/utils/devUtils"; import { handelFilterRouter } from "@/utils/routersFilter"; import { message } from "antd";
export const getSideMenusAction = (): any => { return (dispatch: any, state: any) => { getSideMenus().then((res: any) => { const rights = state().login.users.role.rights; const newMenus = handleSideMenu(res, rights); dispatch({ type: INITSIDEMENUS, menus: newMenus }); dispatch(updateRoutesAction()); }); }; };
export const loginOutAction = (): any => ({ type: LOGINOUT });
export const updateMenusAction = (item: any): any => { return (dispatch: any) => { patchRights(item).then((res: any) => { dispatch(getSideMenusAction()); }); }; };
export const updateRoutesAction = (): any => { return (dispatch: any, state: any) => { const rights = state().login.users.role.rights; const menus = state().login.menus; const routes = handelFilterRouter(rights, menus); dispatch({ type: UPDATROUTES, routes }); }; };
export const loginUserAction = (item: any, navigate: any): any => { return (dispatch: any) => { loginUser(item).then((res: any) => { if (res.length === 0) { message.error("用户名或密码错误"); } else { localStorage.setItem("token", res[0].username); dispatch({ type: UPDATUSERS, users: res[0] }); dispatch(getSideMenusAction()); navigate("/home"); } }); }; };
|
utils 工具函数处理
说一说我这里为什么要映射 element 成对应组件这步操作,原因是我使用了 redux-persist(redux 持久化),
不熟悉这个插件的可以看看我这篇文章:redux-persist
若是直接转换后存入本地再取出来渲染是会有问题的,所以需要先将 element 保存成映射路径,然后渲染前再进行一次路径映射出对应组件。
每个后台的数据返回格式都不一样,需要自己去转换,我这里的转换仅供参考。
ps:defaulyRoutes 和默认 router/index.ts 导出是一样的,可以做个小优化,复用起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| import { lazy } from "react"; import { Navigate } from "react-router-dom";
const lazyLoad = (moduleName: string) => { const Module = lazy(() => import(`views/${moduleName}`)); return <Module />; }; const Appraisal = ({ children }: any) => { const token = localStorage.getItem("token"); return token ? children : <Navigate to="/login" />; };
const defaulyRoutes: any = [ { path: "/login", element: lazyLoad("login"), }, { path: "/", element: <Appraisal>{lazyLoad("sand-box")}</Appraisal>, children: [ { path: "", element: <Navigate to="home" />, }, { path: "*", element: lazyLoad("sand-box/nopermission"), }, ], }, { path: "*", element: lazyLoad("not-found"), }, ];
export const handelFilterRouter = ( rights: any, menus: any, routes: any = [] ) => { for (const menu of menus) { if (menu.pagepermisson) { let index = rights.findIndex((item: any) => item === menu.key) + 1; if (!menu.children) { if (index) { const obj = { path: menu.key, element: `sand-box${menu.key}`, }; routes.push(obj); } } else { handelFilterRouter(rights, menu.children, routes); } } } return routes; };
export const handelEnd = (routes: any) => { defaulyRoutes[1].children = [...routes, ...defaulyRoutes[1].children]; return defaulyRoutes; };
export const handelFilterElement = (routes: any) => { return routes.map((route: any) => { route.element = lazyLoad(route.element); return route; }); };
|
主文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import routes from "./router"; import { useRoutes } from "react-router-dom"; import { shallowEqual, useSelector } from "react-redux"; import { useState, useEffect } from "react"; import { handelFilterElement, handelEnd } from "@/utils/routersFilter"; import { deepCopy } from "@/utils/devUtils";
function App() { console.log("first"); const [rout, setrout] = useState(routes); const { routs } = useSelector( (state: any) => ({ routs: state.login.routes }), shallowEqual );
const element = useRoutes(rout); useEffect(() => { const end = handelEnd(handelFilterElement(deepCopy(routs))); setrout(end); }, [routs]);
return <div className="height-all">{element}</div>; }
export default App;
|