• 【tornado】系列项目(二)基于领域驱动模型的区域后台管理+前端easyui实现


    【tornado】系列项目(二)基于领域驱动模型的区域后台管理+前端easyui实现

     

    本项目是一个系列项目,最终的目的是开发出一个类似京东商城的网站。本文主要介绍后台管理中的区域管理,以及前端基于easyui插件的使用。本次增删改查因数据量少,因此采用模态对话框方式进行,关于数据量大采用跳转方式修改,详见博主后续博文。

    后台界面展示:

    地区管理包含省市县的管理。详见下文。

    一、数据库设计

    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
    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】系列项目(一)之基于领域驱动模型架构设计的京东用户管理后台 中有详细介绍,本博文不再赘述。

    三、路由映射

    1
    2
    3
    4
    5
    6
    (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:

    1
    2
    3
    4
    5
    class ProvinceManagerHandler(AdminRequestHandler):
     
        def get(self, *args, **kwargs):
            # 打开页面,显示所有的省
            self.render('Region/ProvinceManager.html')

     本Handler主要用于从模板展示,默认如果只有这一个handler,用户看到的将是空页面。关于数据的增删改查,详见下文。

    五、核心增删改查操作

    再介绍增删改查之前,我们先介绍母板文件layout的前端html和继承该模板的ProvinceManager.html部分JavaScript代码:

    母版layout html:

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <!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="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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <div>
        <table id="dg"></table>   #easyui订制table
     
        <div id="dlg" class="easyui-dialog" style="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=" 80px;">
                        <span>省份:</span>
                    </div>
                    <div class="group-input" style=" 300px;">
                        <input id="dlg_nid" style=" 200px;display: none"  name="nid"/>
                        <input id="dlg_province" style=" 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代码:

    1
    2
    3
    4
    5
    6
    7
    $(function () {
              // 加载表格数据
              InitTable();  #初始化表格内容(即查询)
              InitPagination();  #初始化分页
              InitMenu(); #初始化左侧菜单
     
          });

      首先介绍两个简单的js:

    1
    2
    3
    4
    5
    6
    7
    /*
            初始化左侧菜单
            */
            function InitMenu(){
                $('#aa').accordion('select',0);  #easyui语法:选择左侧第0个标签
                $('#jd_menu_province').addClass('active'); #让省份默认选中
            }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*
          初始化分页
           */
          function InitPagination(){
              var pager = $('#dg').datagrid('getPager');
              $(pager).pagination({
                  beforePageText: '第',
                  afterPageText: '页 共{pages}页',
                  displayMsg: '当前显示{from}-{to}条记录 共{total}条数据'
     
              })
          }

    关键的表格数据初始化js(查询js):

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    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
                    80, #宽度80px
                    align:'center'  #居中显示
                },
                {
                    field:'caption',
                    title:'标题',
                    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代码,接下来我们先看查询的后端业务处理类:

    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
    29
    30
    31
    32
    33
    34
    35
    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))  #返回给前端

    数据库业务协调处理类的对应操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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()

     数据库操作类相关操作:

    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
    29
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /*
            添加
            */
            function AddRow(){
                // 显示对话框,由于希望添加则将方法设置为POST
                $('#fm1').form('clear'); #清空上次form的内容
                $('#dlg').dialog('open').dialog('setTitle','创建省份'); #设置模态对话框标签是创建省份
                $('#dlg_summary').empty(); #清空错误信息
                METHOD = 'post';  #设置提交方式为post
            }

      增加操作实际上就做了一个操作:打开模态对话框。

    前端页面展示:

     

    当用户输入需要添加的省份,接下来点击保存按钮,数据将被写入数据库并在前端显示:

    保存的js代码:

    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
    /*
        保存按钮
         */
        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');
        }

      增加对应的后端业务处理方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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)) #返回给前端

     数据库协调处理类对应的方法:

    1
    2
    3
    4
    5
    def create_province(self, caption):
        exist = self.regionRepository.exist_province(caption)#先判断省份是否存在,如果存在,该方法返回值为None
        if not exist:
             self.regionRepository.add_province(caption) #创建省份
             return True

     数据库对应操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /*
          修改
          */
          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方法:

    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
    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))

      该方法与添加时的方法基本一致,这里不做过多介绍。

    数据库协调处理类对应的方法:

    1
    2
    3
    4
    5
    def modify_province(self, nid, caption):
           exist = self.regionRepository.exist_province(caption)
           if not exist:
                self.regionRepository.update_province(nid, caption) #更新省份
                return True

    数据库操作对应类的方法:

    1
    2
    3
    4
    5
    6
    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:

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    /*
           删除
           */
           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类对应方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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))

      数据库业务协调处理类对应的方法:

    1
    2
    3
    def delete_province(self, nid):
     
           self.regionRepository.remove_province(nid)

      数据库操作类对应方法:

    1
    2
    3
    4
    5
    6
    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、数据库业务协调处理类、数据库操作类的整个流程。

     
     
     
  • 相关阅读:
    hdu2328 Corporate Identity
    hdu1238 Substrings
    hdu4300 Clairewd’s message
    hdu3336 Count the string
    hdu2597 Simpsons’ Hidden Talents
    poj3080 Blue Jeans
    poj2752 Seek the Name, Seek the Fame
    poj2406 Power Strings
    hust1010 The Minimum Length
    hdu1358 Period
  • 原文地址:https://www.cnblogs.com/zcok168/p/10090764.html
Copyright © 2020-2023  润新知