【tornado】系列項目(二)基於領域驅動模型的區域后台管理+前端easyui實現


本項目是一個系列項目,最終的目的是開發出一個類似京東商城的網站。本文主要介紹后台管理中的區域管理,以及前端基於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、數據庫業務協調處理類、數據庫操作類的整個流程。下節我們將介紹市縣管理中與省份不同的部分,歡迎下次收看!如果本文對您有參考價值,歡迎幫博主點下文章下方的推薦,謝謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM