定制app开发前端的中后台管理系统定制app开发相比于其他普通项目,定制app开发从开发设计的角度来说定制app开发有几点比较特殊:
- 定制app开发一个是权限设计,定制app开发具体实现可参考:。
- 定制app开发一个是页面布局的设计,定制app开发也是本文要说的。定制app开发一个好的页面布局设计,无论是对于页面结构的稳定性,还是功能拓展的方便性,亦或是用户体验上,都有着重要的作用。
一、市面参考
先来看看市面上的一些优秀的开源系统项目的页面布局。
1、
是 vue 框架的一个优秀的后台管理系统,目前star数75k。
vue-element-admin采用的是侧边菜单布局,侧边菜单 + 顶部导航 + 内容区域,这也是我个人最推崇的布局方式。
2、 design pro
有提供三种布局。
(1)顶部菜单布局
即:顶部导航菜单 + 内容区域。
这种方式布局简单,但缺点很明显,菜单都挤在顶部导航区域,在菜单项越来越多时就放不下了,很难处理,可扩展性不强。
(2)侧边菜单布局
侧边菜单 + 顶部导航 + 内容区域。
同vue-element-admin类似,主要区别就是antd pro的面包屑导航是另起一行单独放的,这样挤压了内容区域的空间,个人觉得还是放在顶部和右上角的快捷按钮放同一行最好。
(3)混合菜单布局
其实和侧边菜单布局大同小异,还是属于侧边菜单布局的范畴。
只不过这样布局的话,面包屑导航就不适合和顶部放一行了,只能另起一行。
二、选型
参考市面上比较优秀的两款项目模板的布局后,个人还是觉得vue-element-admin的布局方式更胜一筹。
文本就围绕这种布局结构来设计。
- 示例项目:
- 技术栈:react 17 + antd 4 + react-router-dom 6 + ts
- 路由统一管理使用 方案
效果图:
其实技术选型不那么重要,无论是react还是vue,element或是antd,思路一致,都只是实现代码的差异而已。
对于侧边栏菜单和面包屑导航,element和antd都有相应的组件可以直接使用,其他的手写实现。
三、css布局
良好的css布局代码才能保证页面布局的稳定性。
而对于整体布局来说,flex是首选,稳定性更好,不兼容ie9。
这里将整体布局封装成组件PageLayout
(1)首先,设置侧边栏右侧的盒子撑满屏幕剩余宽度。
flex布局有个特性是:只对一个子元素设置flex: 1
属性时,该子元素默认会撑满父容器的剩余空间。
.c-PageLayout-index { width: 100%; height: 100%; display: flex; .appMainWrap { height: 100%; flex: 1; // 占据屏幕剩余宽度 position: relative; padding-top: 50px; // 留出顶部导航栏区域,顶部导航栏使用悬浮置顶。 } .appMain { height: 100%; overflow: auto; padding: 15px; // 内容区域可以在这里统一设置下边距 }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- c-PageLayout-index 页面整体容器
- appMainWrap 侧边栏右侧的(顶部导航区域 + 内容区域)容器
- appMain 内容区域容器
(2)侧边菜单区域默认撑满高度,宽度可交给antd组件自适应,也可以自己设死。
.c-PageLayout-sideBar { height: 100%; overflow: auto;}
- 1
- 2
- 3
- 4
(3)顶部导航区域悬浮置顶。
.c-PageLayout-headBar { height: 50px; display: flex; justify-content: space-between; position: absolute; top: 0; right: 0; width: 100%;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
四、侧边栏菜单
侧边栏的实现方式是难点,因为这里即涉及到如何和路由数据匹配,又涉及权限的筛选。
侧边栏最好是和路由配置共用一套数据,方便扩展和维护,这里得益于 已经封装好的路由管理方案(类vue-router),所以直接读取路由配置数据,动态生成菜单组件结构。
路由配置数据:
import PageLayout from '@/components/PageLayout'import { HomeOutlined } from '@ant-design/icons'const routes: RoutesTypeNew = [ { path: '/', element: <PageLayout />, children: [ { path: 'index', component: () => import(/* webpackChunkName: "index" */ '@/views/index/index'), meta: { title: '首页', icon: <HomeOutlined />, accessId: '10000', } }, ] },]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 如有点击跳转外链的菜单场景,可以在routes里添加个自定义的配置对象,例如通过url属性指代外链地址。
动态生成菜单:
// 递归获取层级菜单function getMenuList () { const getList: any = (routeList = [], prePath = '') => { let menuList: JSX.Element[] = [] routeList.forEach((v: RoutesItemTypeNew) => { if (v.path === '/') { menuList = menuList.concat(getList(v.children, '/')) } else { const currentPath = prePath + v.path if (v.children) { menuList.push(( <SubMenu key={currentPath} icon={v.meta.icon} title={v.meta.title}> {getList(v.children, currentPath + '/')} </SubMenu> )) } else { menuList.push(( <ItemMenu key={currentPath} icon={v.meta.icon}> <Link to={currentPath}>{v.meta.title}</Link> </ItemMenu> )) } } }) return menuList } return getList(routes)}
- 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
- 如需要对权限路由做筛选,通过在路由配置数据meta里添加accessId字段作为路由权限id,然后在遍历routeList时,读取该accessId做权限判断。
五、面包屑导航
要使用面包屑导航,需要对路由路径配置有一定的约束规则,即,配置path路径时不要随意使用斜杠/
划分,只通过嵌套路径自动划分路径。
这样才能对路由完整路径通过/
分隔并匹配,来生成对应的面包屑导航数据。
首先,写个方法,遍历路由,生成路由路径和路由meta字段的映射数据:
function getRouteMetaMap () { const getMap: any = (routeList = [], prePath = '') => { let map = {} routeList.forEach((v: RoutesItemTypeNew) => { let currentPath = prePath + v.path if (v.path === '/') { currentPath = '' } else { map = { ...map, [currentPath]: v.meta || {} } } if (v.children) { map = { ...map, ...getMap(v.children, currentPath + '/') } } }) return map } return getMap(routes)}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 映射数据示例:
{ "/index": { "title": "首页", "accessId": "10000" }, "/nest": { "title": "多级菜单", }, "/nest/nest1": { "title": "二级菜单1" }, "/nest/nest1/nest11": { "title": "三级菜单11" }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
然后,获取当前路由完整路径(例如:/nest/nest1/nest11
),再通过/
分隔成多段子路由,和上述getRouteMetaMap方法取到的映射数据匹配,获取子路由的title标题组合成面包屑(多级菜单 / 二级菜单1 / 三级菜单11),展示出来。
const routeMetaMap = getRouteMetaMap()const pathSnippets = location.pathname.split('/').filter(i => i)const extraBreadcrumbItems = pathSnippets.map((_, index) => { const url = `/${pathSnippets.slice(0, index + 1).join('/')}` return ( <Breadcrumb.Item key={url}> <span>{routeMetaMap[url].title}</span> </Breadcrumb.Item> )})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
本文示例项目源码:
参考链接:
https://panjiachen.gitee.io/vue-element-admin/
https://preview.pro.ant.design/form/basic-form