我的第一个python web开发框架(36)——后台菜单管理功能
2018-08-30 20:27
821 查看
对于后台管理系统来说,要做好权限管理离不开菜单项和页面按钮控件功能的管理。由于程序没法智能的知道有什么菜单和控件,哪些人拥有哪些操作权限,所以首先要做的是菜单管理功能,将需要管理的菜单项和各个功能项添加(注册)到菜单管理表中,方便后续权限控制管理。
要开发一个菜单管理功能,离不开这些功能:菜单列表展示(需要菜单列表获取接口)、新增菜单(新增接口)、编辑菜单(获取菜单记录以及提交修改接口)、删除菜单(删除接口),由于菜单是多层级的关系,所以还需要增加菜单树列表获取接口来绑定菜单层级,在主页面还需要增加菜单列表项输出接口,用来展示菜单项。
在正式编写菜单管理功能之前,我们需要先在逻辑层(logic文件夹)中添加菜单逻辑类:menu_info_logic.py,继承前面我们开发的ORM基类,让当前的菜单管理逻辑类拥有ORM的所有方法。
为了方便管理,我们在api文件中创建system文件夹,用来存放所有后台权限管理功能的代码,并创建menu_info.py文件,来存放菜单管理接口
接下来我们先实现菜单列表获取接口,由第一部分的后端管理功能可以知道,我们前端使用的是jqGrid插件,这一块我们在前面已经实现过了,而ORM中也封装好对应的方法,所以直接调用就可以了。(这个接口在实现时,我们要了解清楚的是,前端插件jqGrid它会传递什么参数和需要返回什么格式的数据回去)
jqGrid会通过接口,将当前页面索引值page、页面显示记录行数rows、排序字段sidx和排序方式sord(顺序或倒序)提交到服务器端接口,如果我们使用树列表,它还会提交当前节点id参数nodeid
所以我们在服务器端接口需要做好这几个参数的接收与使用操作,然后我们通过调用前面实现的ORM的get_list方法,就可以获取对应的数据返回给客户端了,具休代码如下:
7到18行,是接收参数。
20行初始化菜单逻辑类
22行是设置查询条件,默认菜单列表我们只显示第一级菜单,也就是父id为0的菜单。在列表第一次加载时,列表提交上来的nodeid为空(即父节点为默认为0),所以设置查询条件时父节点会赋值为parent_id=0。当我们点击树菜单展开时,才加载下一级菜单出来,这时jqGrid控件会再次访问接口,提交当前要展开发节点id给接口,接口接收到参数以后返回对应的子节点列表给客户端。
get_list是前端ORM中封装好的参数,它会返回jqGrid所需要的数据格式,所以第25行直接将符合jqGrid要求的数据返回给列表展示出来。
我们在后台main.html中添加菜单,方便登录后台查看效果
前端菜单管理的hmtl页面大家自行下载源码包查看,下面是完成后展示效果
由于当前还没有数据,所以暂时列表是空的,下面我们创建添加和修改功能
先看看新增页面效果(页面内容项一般我们是根据数据字典和原型来设计的,大家可以参照一下上一章菜单管理的数据结构)
我们需要接收页面提交上来的这些参数,然后向数据库中添加一条记录
上级菜单选项,这里我们点击选择时,需要显示菜单树列表,让我们选择当前新增菜单项所属菜单层级,方便菜单层级的管理,如果为顶级菜单,则不需要进行选择
为了让后台菜单好看一些,我们可以增加菜单小图标,H-ui框架中,提供了字体图标,这里的查看增加链接到官网中,可以直接查询字体图标编码复制过来使用
排序可以输入任意的数字,通过从 小到大顺序排列菜单项,为了方便排序项可以自行累加,代码中可以获取当前菜单层级最大值加1的方式来进行赋值
前端菜单新增页面(menu_info_edit.html)大家下载源码包查看
新增菜单页面树列表我们使用的是zTree插件,它需要我们输出指定的数据格式才能正常显示,所以调用接口返回:id、parent_id、name、open这几个字段,在菜单项中,我们有是否最终节点的字段,所以查询条件中我们指定查询出所有非最终节点的项就可以了
完成后直接填写参数就可以提交新增菜单记录了。
对于编辑接口,它基本上和新增接口代码相差不大,区别地方有下面几点:
1.为了减少菜单层级变更所造成的错误,在编辑记录接口我们需要屏蔽对父节点id的修改
2.不需要再计算当前菜单所在层级的深度
3.将新增方法add_model()更改为edit_model()方法
大家可以比较一下新增与编辑接口代码,可以发现代码几乎都是一样的。
最后增加删除接口
删除接口跟前端产品分类删除接口一样,在删除前需要判断当前菜单是否已被引用(即当前菜单下是否存在子菜单)
菜单管理项添加完成后,列表效果图
完成这些之后,我们还需要改造一下管理主界面左栏的菜单列表,改为从菜单管理数据表中读取方式
为了方便后续权限的管理改造,我们在接口中组合菜单代码来实现菜单的展示效果。
首先我们通过查看左栏菜单列表的html代码,提取出菜单html展示代码
然后在接口中,获取设置为显示并启用状态的菜单列表
通过循环判断,拼接一级菜单和二级菜单项的html输出代码
最后将结果输出到前端展示出来
执行后会输出下面结果:
前端通过AJAX获取菜单列表hmtl代码,然后添加到后台左栏菜单列表中就实现我们想要的效果了
页面展示效果
对于菜单管理的改造,完成上面这些项就算完厉了。对于菜单权限的控制,后续完成整个改造后会专门讲解。
权限系统中的部门管理(角色权限组管理),它的基本功能和菜单功能相似,所以就不开新章节进行讲解,大家可以根据数据结构尝试编写,也可以参考本节提供的源码进行研究。
PS:部门管理中,部门编码生成是一个比较特殊的方法,需要多debug理解。
本文对应的源码下载 (内附本章源码对应数据库表单和记录创建sql代码,上传的图片如果显示不了,可以nginx.conf配置的location项中添加upload,第一部分章节的nginx那里忘记添加了)
版权声明:本文原创发表于 博客园,作者为 AllEmpty 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
python开发QQ群:669058475 作者博客:http://www.cnblogs.com/EmptyFS/
要开发一个菜单管理功能,离不开这些功能:菜单列表展示(需要菜单列表获取接口)、新增菜单(新增接口)、编辑菜单(获取菜单记录以及提交修改接口)、删除菜单(删除接口),由于菜单是多层级的关系,所以还需要增加菜单树列表获取接口来绑定菜单层级,在主页面还需要增加菜单列表项输出接口,用来展示菜单项。
在正式编写菜单管理功能之前,我们需要先在逻辑层(logic文件夹)中添加菜单逻辑类:menu_info_logic.py,继承前面我们开发的ORM基类,让当前的菜单管理逻辑类拥有ORM的所有方法。
#!/usr/bin/env python # coding=utf-8 from logic import _logic_base from config import db_config class MenuInfoLogic(_logic_base.LogicBase): """菜单管理表逻辑类""" def __init__(self): # 表名称 __table_name = 'menu_info' # 初始化 _logic_base.LogicBase.__init__(self, db_config.DB, db_config.IS_OUTPUT_SQL, __table_name)
为了方便管理,我们在api文件中创建system文件夹,用来存放所有后台权限管理功能的代码,并创建menu_info.py文件,来存放菜单管理接口
接下来我们先实现菜单列表获取接口,由第一部分的后端管理功能可以知道,我们前端使用的是jqGrid插件,这一块我们在前面已经实现过了,而ORM中也封装好对应的方法,所以直接调用就可以了。(这个接口在实现时,我们要了解清楚的是,前端插件jqGrid它会传递什么参数和需要返回什么格式的数据回去)
jqGrid会通过接口,将当前页面索引值page、页面显示记录行数rows、排序字段sidx和排序方式sord(顺序或倒序)提交到服务器端接口,如果我们使用树列表,它还会提交当前节点id参数nodeid
所以我们在服务器端接口需要做好这几个参数的接收与使用操作,然后我们通过调用前面实现的ORM的get_list方法,就可以获取对应的数据返回给客户端了,具休代码如下:
@get('/system/menu_info/') def callback(): """ 获取列表数据 """ # 菜单列表中,当前节点id,即父节点id parent_id = convert_helper.to_int0(web_helper.get_query('nodeid', '', is_check_null=False)) # 页面索引 page_number = convert_helper.to_int1(web_helper.get_query('page', '', is_check_null=False)) # 页面页码与显示记录数量 page_size = convert_helper.to_int0(web_helper.get_query('rows', '', is_check_null=False)) # 接收排序参数 sidx = web_helper.get_query('sidx', '', is_check_null=False) sord = web_helper.get_query('sord', '', is_check_null=False) # 初始化排序字段 order_by = 'sort asc' if sidx: order_by = sidx + ' ' + sord _menu_info_logic = menu_info_logic.MenuInfoLogic() # 读取记录 wheres = 'parent_id=' + str(parent_id) result = _menu_info_logic.get_list('*', wheres, page_number, page_size, order_by) if result: return json.dumps(result) else: return web_helper.return_msg(-1, "查询失败")
7到18行,是接收参数。
20行初始化菜单逻辑类
22行是设置查询条件,默认菜单列表我们只显示第一级菜单,也就是父id为0的菜单。在列表第一次加载时,列表提交上来的nodeid为空(即父节点为默认为0),所以设置查询条件时父节点会赋值为parent_id=0。当我们点击树菜单展开时,才加载下一级菜单出来,这时jqGrid控件会再次访问接口,提交当前要展开发节点id给接口,接口接收到参数以后返回对应的子节点列表给客户端。
get_list是前端ORM中封装好的参数,它会返回jqGrid所需要的数据格式,所以第25行直接将符合jqGrid要求的数据返回给列表展示出来。
我们在后台main.html中添加菜单,方便登录后台查看效果
前端菜单管理的hmtl页面大家自行下载源码包查看,下面是完成后展示效果
由于当前还没有数据,所以暂时列表是空的,下面我们创建添加和修改功能
先看看新增页面效果(页面内容项一般我们是根据数据字典和原型来设计的,大家可以参照一下上一章菜单管理的数据结构)
我们需要接收页面提交上来的这些参数,然后向数据库中添加一条记录
上级菜单选项,这里我们点击选择时,需要显示菜单树列表,让我们选择当前新增菜单项所属菜单层级,方便菜单层级的管理,如果为顶级菜单,则不需要进行选择
为了让后台菜单好看一些,我们可以增加菜单小图标,H-ui框架中,提供了字体图标,这里的查看增加链接到官网中,可以直接查询字体图标编码复制过来使用
排序可以输入任意的数字,通过从 小到大顺序排列菜单项,为了方便排序项可以自行累加,代码中可以获取当前菜单层级最大值加1的方式来进行赋值
@post('/api/system/menu_info/') def callback(): """ 新增记录 """ name = web_helper.get_form('name', '菜单名称') icon = web_helper.get_form('icon', '菜单小图标', True, 10, False, is_check_special_char=False) icon = icon.replace('\'', '').replace('|', '').replace('%', '') page_url = web_helper.get_form('page_url', '页面URL', is_check_null=False) interface_url = web_helper.get_form('interface_url', '接口url', is_check_null=False, is_check_special_char=False) # 替换编码 interface_url = interface_url.replace('@', '').replace('\'', '').replace('|', '').replace('%', '') parent_id = convert_helper.to_int0(web_helper.get_form('parent_id', '父id', is_check_null=False)) sort = convert_helper.to_int0(web_helper.get_form('sort', '排序', is_check_null=False)) is_leaf = web_helper.get_form('is_leaf', '是否最终节点', is_check_null=False) is_show = web_helper.get_form('is_show', '是否显示', is_check_null=False) is_enabled = web_helper.get_form('is_enabled', '是否启用', is_check_null=False) _menu_info_logic = menu_info_logic.MenuInfoLogic() # 计算深度级别,即当前菜单在哪一级 if parent_id == 0: level = 0 else: level = _menu_info_logic.get_value_for_cache(parent_id, 'level') + 1 # 如果没有设置排序,则自动获取当前级别最大的序号加1 if sort == 0: sort = _menu_info_logic.get_max('parent_id', 'parent_id=' + str(parent_id)) + 1 # 组合更新字段 fields = { 'name': string(name), 'icon': string(icon), 'page_url': string(page_url), 'interface_url': string(interface_url), 'parent_id': parent_id, 'sort': sort, 'level': level, 'is_leaf': is_leaf, 'is_show': is_show, 'is_enabled': is_enabled, } # 新增记录 result = _menu_info_logic.add_model(fields) if result: return web_helper.return_msg(0, '提交成功') else: return web_helper.return_msg(-1, "提交失败")
前端菜单新增页面(menu_info_edit.html)大家下载源码包查看
新增菜单页面树列表我们使用的是zTree插件,它需要我们输出指定的数据格式才能正常显示,所以调用接口返回:id、parent_id、name、open这几个字段,在菜单项中,我们有是否最终节点的字段,所以查询条件中我们指定查询出所有非最终节点的项就可以了
@get('/api/system/menu_info/tree/') def callback(): """ 获取列表数据(树列表) """ _menu_info_logic = menu_info_logic.MenuInfoLogic() # 读取记录 result = _menu_info_logic.get_list('id, parent_id, name, not is_leaf as open', 'is_leaf=false', orderby='sort asc') if result: return web_helper.return_msg(0, "成功", {'tree_list': result.get('rows')}) else: return web_helper.return_msg(-1, "查询失败")
完成后直接填写参数就可以提交新增菜单记录了。
对于编辑接口,它基本上和新增接口代码相差不大,区别地方有下面几点:
1.为了减少菜单层级变更所造成的错误,在编辑记录接口我们需要屏蔽对父节点id的修改
2.不需要再计算当前菜单所在层级的深度
3.将新增方法add_model()更改为edit_model()方法
@put('/api/system/menu_info/<id:int>/') def callback(id): """ 修改记录 """ name = web_helper.get_form('name', '菜单名称') icon = web_helper.get_form('icon', '菜单小图标', True, 10, False, is_check_special_char=False) icon = icon.replace('\'', '').replace('|', '').replace('%', '') page_url = web_helper.get_form('page_url', '页面URL', is_check_null=False) interface_url = web_helper.get_form('interface_url', '接口url', is_check_null=False, is_check_special_char=False) # 替换编码 interface_url = interface_url.replace('\'', '').replace('|', '').replace('%', '') parent_id = convert_helper.to_int0(web_helper.get_form('parent_id', '父id', is_check_null=False)) sort = convert_helper.to_int0(web_helper.get_form('sort', '排序', is_check_null=False)) is_leaf = web_helper.get_form('is_leaf', '是否最终节点', is_check_null=False) is_show = web_helper.get_form('is_show', '是否显示', is_check_null=False) is_enabled = web_helper.get_form('is_enabled', '是否启用', is_check_null=False) _menu_info_logic = menu_info_logic.MenuInfoLogic() # 如果没有设置排序,则自动获取当前级别最大的序号加1 if sort == 0: sort = _menu_info_logic.get_max('parent_id', 'parent_id=' + str(parent_id)) + 1 # 组合更新字段 fields = { 'name': string(name), 'icon': string(icon), 'page_url': string(page_url), 'interface_url': string(interface_url), 'sort': sort, 'is_leaf': is_leaf, 'is_show': is_show, 'is_enabled': is_enabled, } # 修改记录 result = _menu_info_logic.edit_model(id, fields) if result: return web_helper.return_msg(0, '提交成功') else: return web_helper.return_msg(-1, "提交失败")
大家可以比较一下新增与编辑接口代码,可以发现代码几乎都是一样的。
最后增加删除接口
@delete('/api/system/menu_info/<id:int>/') def callback(id): """ 删除指定记录 """ _menu_info_logic = menu_info_logic.MenuInfoLogic() # 判断要删除的节点是否有子节点,是的话不能删除 if _menu_info_logic.exists('parent_id=' + str(id)): return web_helper.return_msg(-1, "当前菜单存在子菜单,不能直接删除") # 删除记录 result = _menu_info_logic.delete_model(id) if result: return web_helper.return_msg(0, '删除成功') else: return web_helper.return_msg(-1, "删除失败")
删除接口跟前端产品分类删除接口一样,在删除前需要判断当前菜单是否已被引用(即当前菜单下是否存在子菜单)
菜单管理项添加完成后,列表效果图
完成这些之后,我们还需要改造一下管理主界面左栏的菜单列表,改为从菜单管理数据表中读取方式
为了方便后续权限的管理改造,我们在接口中组合菜单代码来实现菜单的展示效果。
首先我们通过查看左栏菜单列表的html代码,提取出菜单html展示代码
然后在接口中,获取设置为显示并启用状态的菜单列表
通过循环判断,拼接一级菜单和二级菜单项的html输出代码
最后将结果输出到前端展示出来
@get('/api/main/menu_info/') def callback(): """ 主页面获取菜单列表数据 """ _menu_info_logic = menu_info_logic.MenuInfoLogic() # 读取记录 result = _menu_info_logic.get_list('*', 'is_show and is_enabled', orderby='sort') if result: # 定义最终输出的html存储变量 html = '' for model in result.get('rows'): # 提取出第一级菜单 if model.get('parent_id') == 0: # 添加一级菜单 temp = """ <dl id="menu-%(id)s"> <dt><i class="Hui-iconfont">%(icon)s</i> %(name)s<i class="Hui-iconfont menu_dropdown-arrow"></i></dt> <dd> <ul> """ % {'id': model.get('id'), 'icon': model.get('icon'), 'name': model.get('name')} html = html + temp # 从所有菜单记录中提取当前一级菜单下的子菜单 for sub_model in result.get('rows'): # 如果父id等于当前一级菜单id,则为当前菜单的子菜单 if sub_model.get('parent_id') == model.get('id'): temp = """ <li><a data-href="%(page_url)s" data-title="%(name)s" href="javascript:void(0)">%(name)s</a></li> """ % {'page_url': sub_model.get('page_url'), 'name': sub_model.get('name')} html = html + temp # 闭合菜单html temp = """ </ul> </dd> </dl> """ html = html + temp return web_helper.return_msg(0, '成功', {'menu_html': html}) else: return web_helper.return_msg(-1, "查询失败")
执行后会输出下面结果:
{ "data": { "menu_html": "\n <dl id=\"menu-1\">\n <dt><i class=\"Hui-iconfont\"></i> 系统管理<i class=\"Hui-iconfont menu_dropdown-arrow\"></i></dt>\n <dd>\n <ul>\n \n <li><a data-href=\"menu_info.html\" data-title=\"菜单管理\" href=\"javascript:void(0)\">菜单管理</a></li>\n \n </ul>\n </dd>\n </dl>\n " }, "msg": "成功", "state": 0 }
前端通过AJAX获取菜单列表hmtl代码,然后添加到后台左栏菜单列表中就实现我们想要的效果了
<aside class="Hui-aside"> <div class="menu_dropdown bk_2" id="menu"> </div> </aside> <script type="text/javascript"> $(function () { $.ajax({ url: "/api/main/menu_info/?" + 100 * Math.random(), type: "GET", dataType:'json', success: function (data) { if (checkLogin(data, true)) { $("#menu").html(data.data.menu_html); $.Huifold(".menu_dropdown dl dt",".menu_dropdown dl dd","fast",1,"click"); } } }); }); </script>
页面展示效果
对于菜单管理的改造,完成上面这些项就算完厉了。对于菜单权限的控制,后续完成整个改造后会专门讲解。
权限系统中的部门管理(角色权限组管理),它的基本功能和菜单功能相似,所以就不开新章节进行讲解,大家可以根据数据结构尝试编写,也可以参考本节提供的源码进行研究。
PS:部门管理中,部门编码生成是一个比较特殊的方法,需要多debug理解。
本文对应的源码下载 (内附本章源码对应数据库表单和记录创建sql代码,上传的图片如果显示不了,可以nginx.conf配置的location项中添加upload,第一部分章节的nginx那里忘记添加了)
版权声明:本文原创发表于 博客园,作者为 AllEmpty 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
python开发QQ群:669058475 作者博客:http://www.cnblogs.com/EmptyFS/
相关文章推荐
- 我的第一个python web开发框架(38)——管理员管理功能
- 我的第一个python web开发框架(39)——后台接口权限访问控制处理
- 我的第一个python web开发框架(15)——公司介绍编辑功能
- 我的第一个python web开发框架(16)——产品分类管理
- 我的第一个python web开发框架(17)——产品管理
- 我的第一个python web开发框架(14)——后台管理系统登录功能
- 我的第一个python web开发框架(10)——工具函数包说明(一)
- 我的第一个python web开发框架(1)——前言
- .NET快速信息化系统开发框架 V3.2->Web版本新增“文件管理中心”集上传、下载、文件共享等一身,非常实用的功能
- 我的第一个python web开发框架(3)——怎么开始?
- 我的第一个python web开发框架(7)——本地部署前端访问服务器
- 我的第一个python web开发框架(13)——工具函数包说明(四)
- 我的第一个python web开发框架(9)——目录与配置说明
- 我的第一个python web开发框架(11)——工具函数包说明(二)
- 我的第一个python web开发框架(8)——项目结构与RESTful接口风格说明
- 基于java极速WEB+ORM 框架:jfinal2.0开发的通用后台管理系统及源码
- 我的第一个python web开发框架(4)——数据库结构设计与创建
- 我的第一个python web开发框架本地部署前端访问服务器
- 我的第一个python web开发框架(2)——一个简单的小外包