對於後臺管理系統來說,要做好許可權管理離不開菜單項和頁面按鈕控制項功能的管理。由於程式沒法智能的知道有什麼菜單和控制項,哪些人擁有哪些操作許可權,所以首先要做的是菜單管理功能,將需要管理的菜單項和各個功能項添加(註冊)到菜單管理表中,方便後續許可權控制管理。 要開發一個菜單管理功能,離不開這些功能:菜單列表展 ...
對於後臺管理系統來說,要做好許可權管理離不開菜單項和頁面按鈕控制項功能的管理。由於程式沒法智能的知道有什麼菜單和控制項,哪些人擁有哪些操作許可權,所以首先要做的是菜單管理功能,將需要管理的菜單項和各個功能項添加(註冊)到菜單管理表中,方便後續許可權控制管理。
要開發一個菜單管理功能,離不開這些功能:菜單列表展示(需要菜單列表獲取介面)、新增菜單(新增介面)、編輯菜單(獲取菜單記錄以及提交修改介面)、刪除菜單(刪除介面),由於菜單是多層級的關係,所以還需要增加菜單樹列表獲取介面來綁定菜單層級,在主頁面還需要增加菜單列表項輸出介面,用來展示菜單項。
在正式編寫菜單管理功能之前,我們需要先在邏輯層(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方法,就可以獲取對應的數據返回給客戶端了,具休代碼如下:
1 @get('/system/menu_info/') 2 def callback(): 3 """ 4 獲取列表數據 5 """ 6 # 菜單列表中,當前節點id,即父節點id 7 parent_id = convert_helper.to_int0(web_helper.get_query('nodeid', '', is_check_null=False)) 8 # 頁面索引 9 page_number = convert_helper.to_int1(web_helper.get_query('page', '', is_check_null=False)) 10 # 頁面頁碼與顯示記錄數量 11 page_size = convert_helper.to_int0(web_helper.get_query('rows', '', is_check_null=False)) 12 # 接收排序參數 13 sidx = web_helper.get_query('sidx', '', is_check_null=False) 14 sord = web_helper.get_query('sord', '', is_check_null=False) 15 # 初始化排序欄位 16 order_by = 'sort asc' 17 if sidx: 18 order_by = sidx + ' ' + sord 19 20 _menu_info_logic = menu_info_logic.MenuInfoLogic() 21 # 讀取記錄 22 wheres = 'parent_id=' + str(parent_id) 23 result = _menu_info_logic.get_list('*', wheres, page_number, page_size, order_by) 24 if result: 25 return json.dumps(result) 26 else: 27 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的方式來進行賦值
1 @post('/api/system/menu_info/') 2 def callback(): 3 """ 4 新增記錄 5 """ 6 name = web_helper.get_form('name', '菜單名稱') 7 icon = web_helper.get_form('icon', '菜單小圖標', True, 10, False, is_check_special_char=False) 8 icon = icon.replace('\'', '').replace('|', '').replace('%', '') 9 page_url = web_helper.get_form('page_url', '頁面URL', is_check_null=False) 10 interface_url = web_helper.get_form('interface_url', '介面url', is_check_null=False, is_check_special_char=False) 11 # 替換編碼 12 interface_url = interface_url.replace('@', '').replace('\'', '').replace('|', '').replace('%', '') 13 parent_id = convert_helper.to_int0(web_helper.get_form('parent_id', '父id', is_check_null=False)) 14 sort = convert_helper.to_int0(web_helper.get_form('sort', '排序', is_check_null=False)) 15 is_leaf = web_helper.get_form('is_leaf', '是否最終節點', is_check_null=False) 16 is_show = web_helper.get_form('is_show', '是否顯示', is_check_null=False) 17 is_enabled = web_helper.get_form('is_enabled', '是否啟用', is_check_null=False) 18 19 _menu_info_logic = menu_info_logic.MenuInfoLogic() 20 # 計算深度級別,即當前菜單在哪一級 21 if parent_id == 0: 22 level = 0 23 else: 24 level = _menu_info_logic.get_value_for_cache(parent_id, 'level') + 1 25 # 如果沒有設置排序,則自動獲取當前級別最大的序號加1 26 if sort == 0: 27 sort = _menu_info_logic.get_max('parent_id', 'parent_id=' + str(parent_id)) + 1 28 29 # 組合更新欄位 30 fields = { 31 'name': string(name), 32 'icon': string(icon), 33 'page_url': string(page_url), 34 'interface_url': string(interface_url), 35 'parent_id': parent_id, 36 'sort': sort, 37 'level': level, 38 'is_leaf': is_leaf, 39 'is_show': is_show, 40 'is_enabled': is_enabled, 41 } 42 # 新增記錄 43 result = _menu_info_logic.add_model(fields) 44 if result: 45 return web_helper.return_msg(0, '提交成功') 46 else: 47 return web_helper.return_msg(-1, "提交失敗")
前端菜單新增頁面(menu_info_edit.html)大家下載源碼包查看
新增菜單頁面樹列表我們使用的是zTree插件,它需要我們輸出指定的數據格式才能正常顯示,所以調用介面返回:id、parent_id、name、open這幾個欄位,在菜單項中,我們有是否最終節點的欄位,所以查詢條件中我們指定查詢出所有非最終節點的項就可以了
1 @get('/api/system/menu_info/tree/') 2 def callback(): 3 """ 4 獲取列表數據(樹列表) 5 """ 6 _menu_info_logic = menu_info_logic.MenuInfoLogic() 7 # 讀取記錄 8 result = _menu_info_logic.get_list('id, parent_id, name, not is_leaf as open', 'is_leaf=false', orderby='sort asc') 9 if result: 10 return web_helper.return_msg(0, "成功", {'tree_list': result.get('rows')}) 11 else: 12 return web_helper.return_msg(-1, "查詢失敗")
完成後直接填寫參數就可以提交新增菜單記錄了。
對於編輯介面,它基本上和新增介面代碼相差不大,區別地方有下麵幾點:
1.為了減少菜單層級變更所造成的錯誤,在編輯記錄介面我們需要屏蔽對父節點id的修改
2.不需要再計算當前菜單所在層級的深度
3.將新增方法add_model()更改為edit_model()方法
1 @put('/api/system/menu_info/<id:int>/') 2 def callback(id): 3 """ 4 修改記錄 5 """ 6 name = web_helper.get_form('name', '菜單名稱') 7 icon = web_helper.get_form('icon', '菜單小圖標', True, 10, False, is_check_special_char=False) 8 icon = icon.replace('\'', '').replace('|', '').replace('%', '') 9 page_url = web_helper.get_form('page_url', '頁面URL', is_check_null=False) 10 interface_url = web_helper.get_form('interface_url', '介面url', is_check_null=False, is_check_special_char=False) 11 # 替換編碼 12 interface_url = interface_url.replace('\'', '').replace('|', '').replace('%', '') 13 parent_id = convert_helper.to_int0(web_helper.get_form('parent_id', '父id', is_check_null=False)) 14 sort = convert_helper.to_int0(web_helper.get_form('sort', '排序', is_check_null=False)) 15 is_leaf = web_helper.get_form('is_leaf', '是否最終節點', is_check_null=False) 16 is_show = web_helper.get_form('is_show', '是否顯示', is_check_null=False) 17 is_enabled = web_helper.get_form('is_enabled', '是否啟用', is_check_null=False) 18 19 _menu_info_logic = menu_info_logic.MenuInfoLogic() 20 # 如果沒有設置排序,則自動獲取當前級別最大的序號加1 21 if sort == 0: 22 sort = _menu_info_logic.get_max('parent_id', 'parent_id=' + str(parent_id)) + 1 23 24 # 組合更新欄位 25 fields = { 26 'name': string(name), 27 'icon': string(icon), 28 'page_url': string(page_url), 29 'interface_url': string(interface_url), 30 'sort': sort, 31 'is_leaf': is_leaf, 32 'is_show': is_show, 33 'is_enabled': is_enabled, 34 } 35 # 修改記錄 36 result = _menu_info_logic.edit_model(id, fields) 37 if result: 38 return web_helper.return_msg(0, '提交成功') 39 else: 40 return web_helper.return_msg(-1, "提交失敗")
大家可以比較一下新增與編輯介面代碼,可以發現代碼幾乎都是一樣的。
最後增加刪除介面
1 @delete('/api/system/menu_info/<id:int>/') 2 def callback(id): 3 """ 4 刪除指定記錄 5 """ 6 _menu_info_logic = menu_info_logic.MenuInfoLogic() 7 # 判斷要刪除的節點是否有子節點,是的話不能刪除 8 if _menu_info_logic.exists('parent_id=' + str(id)): 9 return web_helper.return_msg(-1, "當前菜單存在子菜單,不能直接刪除") 10 11 # 刪除記錄 12 result = _menu_info_logic.delete_model(id) 13 if result: 14 return web_helper.return_msg(0, '刪除成功') 15 else: 16 return web_helper.return_msg(-1, "刪除失敗")
刪除介面跟前端產品分類刪除介面一樣,在刪除前需要判斷當前菜單是否已被引用(即當前菜單下是否存在子菜單)
菜單管理項添加完成後,列表效果圖
完成這些之後,我們還需要改造一下管理主界面左欄的菜單列表,改為從菜單管理數據表中讀取方式
為了方便後續許可權的管理改造,我們在介面中組合菜單代碼來實現菜單的展示效果。
首先我們通過查看左欄菜單列表的html代碼,提取出菜單html展示代碼
然後在介面中,獲取設置為顯示並啟用狀態的菜單列表
通過迴圈判斷,拼接一級菜單和二級菜單項的html輸出代碼
最後將結果輸出到前端展示出來
1 @get('/api/main/menu_info/') 2 def callback(): 3 """ 4 主頁面獲取菜單列表數據 5 """ 6 _menu_info_logic = menu_info_logic.MenuInfoLogic() 7 # 讀取記錄 8 result = _menu_info_logic.get_list('*', 'is_show and is_enabled', orderby='sort') 9 if result: 10 # 定義最終輸出的html存儲變數 11 html = '' 12 for model in result.get('rows'): 13 # 提取出第一級菜單 14 if model.get('parent_id') == 0: 15 # 添加一級菜單 16 temp = """ 17 <dl id="menu-%(id)s"> 18 <dt><i class="Hui-iconfont">%(icon)s</i> %(name)s<i class="Hui-iconfont menu_dropdown-arrow"></i></dt> 19 <dd> 20 <ul> 21 """ % {'id': model.get('id'), 'icon': model.get('icon'), 'name': model.get('name')} 22 html = html + temp 23 24 # 從所有菜單記錄中提取當前一級菜單下的子菜單 25 for sub_model in result.get('rows'): 26 # 如果父id等於當前一級菜單id,則為當前菜單的子菜單 27 if sub_model.get('parent_id') == model.get('id'): 28 temp = """ 29 <li><a data-href="%(page_url)s" data-title="%(name)s" href="javascript:void(0)">%(name)s</a></li> 30 """ % {'page_url': sub_model.get('page_url'), 'name': sub_model.get('name')} 31 html = html + temp 32 33 # 閉合菜單html 34 temp = """ 35 </ul> 36 </dd> 37 </dl> 38 """ 39 html = html + temp 40 41 return web_helper.return_msg(0, '成功', {'menu_html': html}) 42 else: 43 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/