Github 地址 | 文档 | 在线预览 | 主题版在线预览
react-antd-console 是一个后台管理系统的前端解决方案,封装了后台管理系统必要功能(如登录、鉴权、菜单、面包屑、标签页等),帮助开发人员专注于业务快速开发。项目基于 React 18
、Ant design 5
、Vite
和 TypeScript
等新版本。对于使用到的各项技术,会被持续更新至最新版本。可放心用于生产环境
为了方便大家更好的掌握和使用本项目,推出系列文章:
如果你喜欢这个项目或认为对你有用,欢迎使用体验和 Star
上篇我们提到使用一个配置 routesConfig
,可以生成 reactRoutes
和 routes
,利用 reactRoutes
可生成路由,利用 routes
可生成菜单/面包屑等数据。本篇再来看看,路由、布局、菜单、权限、鉴权等相互是怎么发生化学反应的
ConsoleLayout
在 react-router
中,利用 nested-routes 特性,父组件可作为布局组件。如果在布局组件中使用 <Outlet />
,那么当页面 url 匹配到路由配置的 path
时,就会渲染对应的路由配置的 component
组件,而布局组件中的其他元素会一直保持不变,并且不会重新渲染
// src/router/config/index.tsx
{
path: '/',
component: () => import('@/layouts/ConsoleLayout'), // 使用 import() 作代码分割
flatten: true, // flatten 可以将子路由的菜单层级提升到本级,在渲染菜单时有用
children: [
{
path: 'index',
component: () => import('@/pages/home'),
name: '首页',
permission: 'homeIndex',
icon: <SvgIcon name="home" />,
}
],
}
// src/layouts/ConsoleLayout/index.tsx
import { Outlet } from 'react-router-dom';
const ConsoleLayout = () => {
return (
<div className="console-layout">
<div className="console-layout__left-side">
<SideMenu />
</div>
<div className="console-layout__right-side">
<Header />
<div className="console-layout__right-side-main">
<Outlet />
</div>
<Footer />
</div>
</div>
);
};
再稍微写点样式,就可以写好整体的布局结构了
话分两头。在登录时, token
被保存在了 localstorage
,这样刷新页面后,程序仍然可以读取到保存的 token
,因此保持了登录状态。另一方面,只要进入页面就会调用一次用户基本信息接口,其返回了代表用户路由访问权限的数据 permissions
。再把后端的数据映射为前端的数据,保存在全局数据中。为方便查询,使用 Set
数据结构
// src/models/withAuth/permissions.ts
function formatPermissions(permissions: string[]) {
const set = new Set(permissions);
return {
homeIndex: set.has('home:index'), // 每一个权限和每一个路由配置,一一对应
};
}
export default formatPermissions;
<SideMenu />
antd 有 菜单组件 Menu: Menu
组件可以根据 items
等 api 生成菜单。可以利用权限数据 permissions
和路由配置 routesConfig
,生成 items
。没有权限的路由,将不包含在 items
中
前面我们搭建好了基本的布局结构,但这还不够。还需要在每次切换路由(需要鉴权的路由)的时候,检查登录状态和是否有当前路由访问权限。具体表现为:
<Outlet />
。withAuthModel.init = () => {
// 检查 token
const { accessToken } = lsGetToken({ bellwether: true }) ?? {};
if (!accessToken) {
this.setState({
hasToken: false,
loading: false,
});
history.push(`/login`);
return;
}
this.setState({ hasToken: true });
// 检查基础数据
try {
if (!this.state.hasRequestedBaseInfo) {
await this.requestBaseInfo();
}
} catch (err) {
console.log(err);
}
this.setState({ loading: false });
// 检查当前路由的访问权限
const routePath = router.getRoutePath(window.location.pathname);
const route = router.flattenRoutes.get(routePath);
if (route?.permission && !this.state.permissions[route.permission]) {
history.push('/no-access');
}
}
上述逻辑如果在 vue
中,可以利用 vue-router
的导航守卫功能封装。在 react
可以通过 useEffect
监听 location.pathname
封装。再把这些逻辑放到一个高阶组件 withAuth
中,用 withAuth
把 ConsoleLayout
布局组件包裹
const withAuth = (Component) => {
const Auth = (props) => {
const loading = useModel(withAuthModel, 'loading');
const location = useLocation();
useEffect(() => {
(async function() {
await withAuthModel.init();
})();
}, [location.pathname]);
if (loading) {
return <Loading />;
}
return (
<Component {...props} />
);
};
return Auth;
};
const WithAuthConsoleLayout = withAuth(() => {
return <ConsoleLayout />;
});
也可以编写自定义的布局组件,然后用
withAuth
高阶组件包裹,以达到鉴权效果
通过以上一顿操作,我们不仅实现了功能,还将配置数据、布局/菜单样式和鉴权等逻辑解耦。
如果你喜欢这个项目或认为对你有用,欢迎使用体验和 Star
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.