本項目是一個系列項目,最終的目的是開發出一個類似京東商城的網站。本文主要介紹后台管理中的區域管理,以及前端基於easyui插件的使用。本次增刪改查因數據量少,因此采用模態對話框方式進行,關於數據量大采用跳轉方式修改,詳見博主后續博文。
后台界面展示:
地區管理包含省市縣的管理。詳見下文。
一、數據庫設計
class Province(Base): """ 省 """ __tablename__ = 'province' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) class City(Base): """ 市 """ __tablename__ = 'city' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) province_id = Column(Integer, ForeignKey('province.nid')) class County(Base): """ 縣(區) """ __tablename__ = 'county' nid = Column(Integer, primary_key=True) caption = Column(VARCHAR(16), index=True) city_id = Column(Integer, ForeignKey('city.nid'))
本次采用的是sqlAlchemy模塊創建數據庫,關於sqlAlchemy的數據庫鏈接以及數據庫創建本文不做介紹,詳細見Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy(點擊進入詳細介紹)
表關系分析:上述表關系比較簡單,市中有外鍵,代表這是市是屬於哪個省;同理縣中也有外鍵,代表這個縣是屬於哪個市。
二、目錄結構
該目錄結構在前面博文【tornado】系列項目(一)之基於領域驅動模型架構設計的京東用戶管理后台 中有詳細介紹,本博文不再贅述。
三、路由映射
(r"/ProvinceManager.html$", Region.ProvinceManagerHandler), #省份模板展示 (r"/province.html$", Region.ProvinceHandler), #省份的增刪改查 (r"/CityManager.html$", Region.CityManagerHandler), #市模板展示 (r"/City.html$", Region.CityHandler), #市的增刪改查 (r"/CountyManager.html$", Region.CountyManagerHandler), #縣的模板展示 (r"/County.html$", Region.CountyHandler),#縣的增刪改查
四、后台模板展示Handler
#以省份為例進行介紹(市縣類似):
數據獲取Handler:
class ProvinceManagerHandler(AdminRequestHandler): def get(self, *args, **kwargs): # 打開頁面,顯示所有的省 self.render('Region/ProvinceManager.html')
本Handler主要用於從模板展示,默認如果只有這一個handler,用戶看到的將是空頁面。關於數據的增刪改查,詳見下文。
五、核心增刪改查操作
再介紹增刪改查之前,我們先介紹母板文件layout的前端html和繼承該模板的ProvinceManager.html部分JavaScript代碼:
母版layout html:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" type="text/css" href="/Statics/Admin/Plugins/jquery-easyui/themes/default/easyui.css"> #導入easyui的css <link rel="stylesheet" type="text/css" href="/Statics/Admin/Plugins/jquery-easyui/themes/icon.css"> #導入easyui的圖標 <link rel="stylesheet" type="text/css" href="/Statics/Admin/Css/Common.css"> #自定義css <script type="text/javascript" src="/Statics/Admin/Plugins/jquery-easyui/jquery.min.js"></script> #導入jQuery <script type="text/javascript" src="/Statics/Admin/Plugins/jquery-easyui/jquery.easyui.min.js"></script> #導入easyui的js </head> <body class="easyui-layout"> <div data-options="region:'north'" style="height:50px"> </div> <div data-options="region:'south',split:true" style="height:30px;"></div> <div data-options="region:'west',split:true" title="后台管理" style="width:200px;"> <div id="aa" class="easyui-accordion" data-options="fit:true,border:false"> <div title="地區管理"> #easyui訂制的左側菜單 <a id="jd_menu_province" class="jd-menu" href="/ProvinceManager.html">省</a> <a id="jd_menu_city" class="jd-menu" href="/CityManager.html">市</a> <a id="jd_menu_county" class="jd-menu" href="/CountyManager.html">縣(區)</a> </div> <div title="用戶管理"> <a id="user" class="jd-menu" href="#">用戶管理</a> <a id="jd_menu_merchant" class="jd-menu" href="/MerchantManager.html">商戶管理</a> </div> <div title="JD自營"> <a id="jd_menu_product" class="jd-menu" href="/ProductManager.html">產品管理</a> </div> </div> </div> <div data-options="region:'center'" title="{% block crumbs %} {% end %}"> #內容顯示區 {% block content %} {% end %} </div> </body> </html>
省份內容展示區html:
<div> <table id="dg"></table> #easyui訂制table <div id="dlg" class="easyui-dialog" style="width:400px;height:200px;padding:10px 20px" closed="true" buttons="#dlg-buttons"> #easyui訂制模態對話框,默認關閉狀態 <form id="fm1"> <div class="input-group clearfix"> <div class="group-label" style="width: 80px;"> <span>省份:</span> </div> <div class="group-input" style="width: 300px;"> <input id="dlg_nid" style="width: 200px;display: none" name="nid"/> <input id="dlg_province" style="width: 200px" class="easyui-textbox" type="text" name="caption" data-options="required:true,missingMessage:'省份不能為空'" /> #easyui訂制form驗證+錯誤信息提示 </div> </div> </form> </div> <div id="dlg-buttons"> #easyui訂制按鈕 <span id="dlg_summary" style="color: red"></span> <a href="#" class="easyui-linkbutton" iconCls="icon-ok" onclick="Save()">保存</a> <a href="#" class="easyui-linkbutton" iconCls="icon-cancel" onclick="javascript:$('#dlg').dialog('close')">取消</a> </div> </div>
JavaScript代碼:
$(function () { // 加載表格數據 InitTable(); #初始化表格內容(即查詢) InitPagination(); #初始化分頁 InitMenu(); #初始化左側菜單 });
首先介紹兩個簡單的js:
/* 初始化左側菜單 */ function InitMenu(){ $('#aa').accordion('select',0); #easyui語法:選擇左側第0個標簽 $('#jd_menu_province').addClass('active'); #讓省份默認選中 }
/* 初始化分頁 */ function InitPagination(){ var pager = $('#dg').datagrid('getPager'); $(pager).pagination({ beforePageText: '第', afterPageText: '頁 共{pages}頁', displayMsg: '當前顯示{from}-{to}條記錄 共{total}條數據' }) }
關鍵的表格數據初始化js(查詢js):
function InitTable(){ $('#dg').datagrid({ title: '省份列表', iconCls: 'icon-save', #省份圖標 url: '/province.html', #獲取數據的url method: 'get', #獲取方式 //fitColumns: true, idField: 'nid', singleSelect: true, #默認單選 rownumbers: true, #顯示行號 striped: true, #奇數行與偶數行顏色有區別 columns:[[ #每一列標題(easyui默認根據field將后端傳來的數據按表格進行顯示) { field:'ck', checkbox:true #顯示checkbox }, { field:'nid', #從數據庫獲取的nid title:'ID', #顯示名稱為ID width:80, #寬度80px align:'center' #居中顯示 }, { field:'caption', title:'標題', width:180, align:'center'} ]], toolbar: [ #顯示的按鈕 { text: '添加', #按鈕名稱 iconCls: 'icon-add', #按鈕圖標 handler: AddRow #點擊按鈕后執行的返回函數 },{ text:'刪除', iconCls:'icon-remove', handler: RemoveRow },{ text:'修改', iconCls:'icon-edit', handler: EditRow } ], pagePosition: 'both', #上下均顯示分頁 pagination:true, #顯示分頁 pageSize:10, #默認每頁顯示的數據總數 pageNumber: 1, #默認第一頁 pageList: [10,20,50], #分頁可選每頁顯示數量 loadFilter: function(data){ #過濾函數 return data; } }); }
上述js代碼即查詢時的js代碼,接下來我們先看查詢的后端業務處理類:
def get(self, *args, **kwargs): """ 獲取 :param args: :param kwargs: :return: """ if self.get_argument('type', None) == 'all': #如果是獲取所有數據 ret = {'status': True, 'rows': "",'summary':''} #將來要返回給前端的字典,包含是否獲取成功的狀態、獲取的數據、錯誤信息 try: region_service = RegionService(RegionRepository()) #將數據庫處理類的對象傳入數據庫業務協調類 all_province_list = region_service.get_province() #獲取所有省份 ret['rows'] = all_province_list #將省份數據添加進返回前端的字典 except Exception as e: ret['status'] = False ret['summary'] = str(e) self.write(json.dumps(ret)) #返回給前端 else: #如果是獲取分頁數據 ret = {'status': True,'total': 0, 'rows': [], 'summary': ''} try: rows = int(self.get_argument('rows', 10))#每頁顯示10條 page = int(self.get_argument('page', 1)) #顯示第一頁 start = (page-1)*rows 開始條數 region_service = RegionService(RegionRepository()) row_list = region_service.get_province_by_page(start, rows) #根據分頁獲取省份數據 row_count = region_service.get_province_count() #獲取省份總數 ret['total'] = row_count ret['rows'] = row_list except Exception as e: ret['status'] = False ret['summary'] = str(e) self.write(json.dumps(ret)) #返回給前端
數據庫業務協調處理類的對應操作:
class RegionService: def __init__(self, region_repository): self.regionRepository = region_repository def get_province_count(self): count = self.regionRepository.fetch_province_count() #獲取省份總數 return count def get_province_by_page(self, start, offset): #根據分頁獲取省份 result = self.regionRepository.fetch_province_by_page(start, offset) return result def get_province(self): #獲取所有省份 return self.regionRepository.fetch_province()
數據庫操作類相關操作:
class RegionRepository(IRegionRepository): def __init__(self): self.db_conn = DbConnection() #實例化數據庫鏈接對象(只需創建一次對象,下面所有方法都不需要再創建) def fetch_province(self): #獲取所有省份 cursor = self.db_conn.connect() sql = """select nid,caption from province order by nid desc """ cursor.execute(sql) db_result = cursor.fetchall() self.db_conn.close() return db_result def fetch_province_by_page(self, start, offset): #根據分頁獲取省份 ret = None cursor = self.db_conn.connect() sql = """select nid,caption from province order by nid desc limit %s offset %s """ cursor.execute(sql, (offset, start)) db_result = cursor.fetchall() self.db_conn.close() return db_result def fetch_province_count(self): #獲取省份總數 cursor = self.db_conn.connect() sql = """select count(1) as count from province """ cursor.execute(sql) db_result = cursor.fetchone() self.db_conn.close() return db_result['count']
以上就是查詢操作的所有過程。
增加:
js:
/* 添加 */ function AddRow(){ // 顯示對話框,由於希望添加則將方法設置為POST $('#fm1').form('clear'); #清空上次form的內容 $('#dlg').dialog('open').dialog('setTitle','創建省份'); #設置模態對話框標簽是創建省份 $('#dlg_summary').empty(); #清空錯誤信息 METHOD = 'post'; #設置提交方式為post }
增加操作實際上就做了一個操作:打開模態對話框。
前端頁面展示:
當用戶輸入需要添加的省份,接下來點擊保存按鈕,數據將被寫入數據庫並在前端顯示:
保存的js代碼:
/* 保存按鈕 */ function Save(){ var isValid = $('#fm1').form('validate');前端form驗證 if(isValid){ $.ajax({ url: '/province.html', #提交的url type: METHOD, #根據之前定義的方法進行提交 data: {caption: $('#dlg_province').val(),nid: $('#dlg_nid').val()}, #提交的數據 dataType: 'json', #數據格式 success: function(data){ #如果后端成功返回數據 if(data.status){ #后端操作成功 $('#fm1').form('clear'); #清空form內容 $('#dlg').dialog('close'); #關閉模態對話框 $('#dg').datagrid('reload'); #重新加載數據 }else{ $('#dlg_summary').text(data.summary); #否則顯示錯誤信息 } } }) }else{ // 前端驗證通過 } // $('#fm').form('clear'); }
增加對應的后端業務處理方法:
def post(self, *args, **kwargs): """ 添加 :param args: :param kwargs: :return: """ ret = {'status': False, 'summary': ''} caption = self.get_argument('caption', None) if not caption: ret['summary'] = '省份不能為空' else: try: region_service = RegionService(RegionRepository()) result = region_service.create_province(caption) #創建省份,如果省份已存在,返回None if not result: ret['summary'] = '省份已經存在' else: ret['status'] = True #操作成功 except Exception as e: ret['summary'] = str(e) self.write(json.dumps(ret)) #返回給前端
數據庫協調處理類對應的方法:
def create_province(self, caption): exist = self.regionRepository.exist_province(caption)#先判斷省份是否存在,如果存在,該方法返回值為None if not exist: self.regionRepository.add_province(caption) #創建省份 return True
數據庫對應操作:
def exist_province(self, caption): #省份是否存在 cursor = self.db_conn.connect() sql = """select count(1) as count from province where caption=%s """ cursor.execute(sql, (caption,)) db_result = cursor.fetchone() self.db_conn.close() return db_result['count'] def add_province(self, caption): #創建省份 cursor = self.db_conn.connect() sql = """insert into province (caption) values(%s)""" effect_rows = cursor.execute(sql, (caption,)) self.db_conn.close() return effect_rows
以上就是省份添加的全部過程。
修改:
實際上修改和添加基本上是一樣的,接下來只介紹與添加不同的地方:
js:
/* 修改 */ function EditRow(){ // 顯示對話框,由於希望修改則將方法設置為PUT // 獲取選中的值,將其賦值到頁面上,然后ajax提交 var row = $('#dg').datagrid('getSelected'); $('#dlg_summary').empty(); if(row){ METHOD = 'put'; $('#fm1').form('clear'); $('#fm1').form('load',row); $('#dlg').dialog('open').dialog('setTitle','修改省份'); }else{ $.messager.alert('警告', '請選擇要修改的行', 'warning'); } }
這里彈出模態對話框,與添加不同的是,修改需要將用戶原有數據存放在input標簽中,方便用戶進行修改。同時,將提交方法修改為put。
修改模態對話框示例截圖:
接下來用戶修改完成后的點擊保存,關於保存的js代碼詳見上文添加部分。
保存的后台業務處理handler方法:
def put(self, *args, **kwargs): """ 更新 :param args: :param kwargs: :return: """ ret = {'status': False, 'summary': ''} nid = self.get_argument('nid', None) caption = self.get_argument('caption', None) if not caption or not nid: ret['summary'] = '省份不能為空' else: try: region_service = RegionService(RegionRepository()) result = region_service.modify_province(nid, caption) if not result: ret['summary'] = '省份已經存在' else: ret['status'] = True except Exception as e: ret['summary'] = str(e) self.write(json.dumps(ret))
該方法與添加時的方法基本一致,這里不做過多介紹。
數據庫協調處理類對應的方法:
def modify_province(self, nid, caption): exist = self.regionRepository.exist_province(caption) if not exist: self.regionRepository.update_province(nid, caption) #更新省份 return True
數據庫操作對應類的方法:
def update_province(self, nid, caption):#更新省份 cursor = self.db_conn.connect() sql = """update province set caption=%s where nid=%s """ effect_rows = cursor.execute(sql, (caption, nid,)) self.db_conn.close() return effect_rows
以上就是省份數據修改的全部過程。
刪除:
js:
/* 刪除 */ function RemoveRow(){ // 獲取已經選中的行 var rows = $('#dg').datagrid('getSelections'); console.log(rows); if(rows.length<=0){ // 警告框 $.messager.alert('警告', '請選擇要刪除的行', 'warning'); }else if(rows.length>1){ $.messager.alert('警告', '不支持批量刪除'); }else{ // 確認框 $.messager.confirm('確定', '您確定要刪除嗎?', function (status) { #easyui訂制的確認框 if(status){ // 點擊確定 // 獲取當前選中行的值,Ajax發送到后台 var row = rows[0]; $.ajax({ url: 'province.html', type: 'delete', data: {nid: row.nid}, dataType: 'json', success: function (data) { if(data.status){ //刪除成功 $.messager.show({ #easyui訂制的messager框 msg:'刪除成功', showType:'slide', #淡出 showSpeed: 500, #速度 timeout: 5, #顯示5秒 style:{ right:'', top:document.body.scrollTop+document.documentElement.scrollTop, #在屏幕上方顯示 bottom:'' } }); // 重新加載表格 var rowIndex = $('#dg').datagrid('getRowIndex', row); $('#dg').datagrid('deleteRow',rowIndex); $('#dg').datagrid('reload'); // 刪除指定行 //var rowIndex = dt.datagrid('getRowIndex', row); //dt.datagrid('deleteRow',rowIndex); }else{ //刪除失敗 // $.messager.alert('錯誤信息', data.summary ,'error'); $.messager.show({ #顯示錯誤信息 icon: 'error', title:'錯誤信息', msg:data.summary, showType:'slide', timeout: 0, style:{ right:'', top:document.body.scrollTop+document.documentElement.scrollTop, bottom:'' } }); } } }); } }) } }
后台handler類對應方法:
def delete(self, *args, **kwargs): """ 刪除 :param args: :param kwargs: :return: """ ret = {'status': False, 'summary': ''} nid = self.get_argument('nid', None) if not nid: ret['summary'] = '請選擇要刪除的省份' else: # 調用service去刪除吧... # 如果刪除失敗,則顯示錯誤信息 try: region_service = RegionService(RegionRepository()) region_service.delete_province(nid) #根據nid刪除省份 ret['status'] = True except Exception as e: ret['summary'] = str(e) self.write(json.dumps(ret))
數據庫業務協調處理類對應的方法:
def delete_province(self, nid): self.regionRepository.remove_province(nid)
數據庫操作類對應方法:
def remove_province(self, nid): cursor = self.db_conn.connect() sql = """delete from province where nid=%s """ effect_rows = cursor.execute(sql, (nid,)) self.db_conn.close() return effect_rows
以上就是刪除的全部過程。
總結:本文主要以省份的增刪改查為例介紹了前端easyui的使用,后端handler、數據庫業務協調處理類、數據庫操作類的整個流程。下節我們將介紹市縣管理中與省份不同的部分,歡迎下次收看!如果本文對您有參考價值,歡迎幫博主點下文章下方的推薦,謝謝!