zoukankan      html  css  js  c++  java
  • 【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、数据库业务协调处理类、数据库操作类的整个流程。

     
     
     
  • 相关阅读:
    leetcode刷题笔记303题 区域和检索
    leetcode刷题笔记301题 删除无效的括号
    20201208日报
    20201118日报
    20201117日报
    20201116日报
    20201115日报
    20201114日报
    20201113日报
    20201112日报
  • 原文地址:https://www.cnblogs.com/zcok168/p/10090764.html
Copyright © 2011-2022 走看看