• Web开发框架之权限管理系统


    记得我在很早之前,开始介绍我的Winform开发框架和我的WCF开发框架之初,我曾经给出下面的视图,介绍我整理的一个框架体系,其中包含有WInform开发框架以及我的Web开发框架,由于前段时间一直忙于Winform开发框架的提炼以及优化,并统一整理了很多Winform开发框架以及WCF开发框架的随笔文章。随着我的Winform逐步完善,终于有时间来整理介绍我的Web开发框架的事宜了,下面先介绍一下我最新优化整理的Web开发框架之权限管理系统,其中这个权限管理系统可以说是集众多宠爱于一身了,除了一贯的和代码生成工具集成,可生成基础性的框架代码外,还整合Winform开发框架继承而来的多数据库支持,在界面层,也就是Web权限管理系统,整合了JQuery的Easy-UI界面组件,功能强大的zTree控件、KindEditor在线编辑控件、界面层获取数据及保存使用基于JQuery的json数据操作,实现数据局部刷新等等操作,其中提供两种不同的菜单布局操作,非常方便应用于其他业务系统的界面。

    首先在介绍之前,我们来贴几个Web权限系统的图片进行感性的了解先。

    1)简洁的界面布局效果。这种效果顶部横幅比较紧凑,左边有一些常用的按钮操作,适合于菜单功能不太多的小业务系统,如我的Web权限系统。

    2)功能强大的界面企业业务系统布局。这种界面效果适合于功能比较众多,菜单展示进行分类管理等业务系统。这种框架顶部的菜单为一级菜单,单击一级菜单可以在左边展示二级菜单,这种效果可以不用一次性列出所有系统的功能,而是分层次进行功能展示。

    顶部的Tab选项卡每次打开页面的时候,增加一个Tab页,页面可以双击进行关闭,也可以右键弹出菜单进行更多操作,如下图所示。

    如果需要了解整个系统的效果,也可以下载《Web权限系统操作视屏》进行全面的了解。

    在提炼优化这个Web权限框架的过程中,碰到了不少的问题,一一进行解决,现总结一部分进行进行介绍。

    1)使用JSON数据构造zTree

    使用zTree确实比easy-ui自带的Tree好很多,功能也强大很多,由于我的Web权限中,各个模块几乎都需要树控件来展示相关的数据,如功能、组织机构等等。在对比了我自己的传统Tree、Easy-UI的Tree控件以及zTree后,发现使用zTree还是可以提高不少的界面分数的。但是其自带的例子,以及网上的例子,多数是使用预先弄好的树形数据,而我需要动态使用ashx进行树形数据的获取及生成,这确实费了一些周折来进行调试。

    首先要准备基于ashx处理程序生成的Tree数据,树的数据使用JSON格式。如功能定义的树形数据如下所示。

    复制代码
            /// <summary>
            /// 递归获取树形信息
            /// </summary>
            private string GetTreeJson(int PID, string folderIcon, string leafIcon)
            {
                string condition = string.Format("PID={0}", PID);
                List<FunctionInfo> nodeList = BLLFactory<Function>.Instance.Find(condition);
                StringBuilder content = new StringBuilder();
                foreach (FunctionInfo model in nodeList)
                {
                    int ParentID = (model.PID == -1 ? 0 : model.PID);
                    //string tempMenu = string.Format("{{ id:{0}, pId:{1}, name:\"{2}\",icon:\"{3}\" }},", model.ID, ParentID, model.Name, imgsrc); //简单的作法
                    string subMenu = this.GetTreeJson(model.ID, folderIcon, leafIcon);
                    string parentMenu = string.Format("{{ \"id\":{0}, \"pId\":{1}, \"name\":\"{2}\" ", model.ID, ParentID, model.Name);
                    if (string.IsNullOrEmpty(subMenu))
                    {
                        if (!string.IsNullOrEmpty(leafIcon))
                        {
                            parentMenu += string.Format(",\"icon\":\"{0}\" }},", leafIcon);
                        }
                        else
                        {
                            parentMenu += "},";
                        }
                    }
                    else
                    {
                        if (!string.IsNullOrEmpty(folderIcon))
                        {
                            parentMenu += string.Format(",\"icon\":\"{0}\" }},", folderIcon);
                        }
                        else
                        {
                            parentMenu += "},";
                        }
                    }
    
                    content.AppendLine(parentMenu.Trim());
                    content.AppendLine(subMenu.Trim());
                }
    
                return content.ToString().Trim();
            } 
    复制代码

    然后就是页面的调用了,这里为了增强体验效果,使用了基于javascript的JQuery的异步操作进行数据处理,而不是基于aspx后台页面的处理,如下所示。

    复制代码
            //重新加载树形结构(异步)
            function reloadTree() {
                $("#loading").show();
                $.getJSON("http://www.cnblogs.com/AjaxHandler/FunctionJson.ashx?r=" + Math.random() + "&op=tree", function (json) {
                    $.fn.zTree.init($("#treeDemo"), setting, json);
                    $.fn.zTree.getZTreeObj("treeDemo").expandAll(true);
    
                    var treeObj = $.fn.zTree.getZTreeObj("treeDemo");
                    var treeNodes = treeObj.getNodes();
                    if (treeNodes != null) {
                        loadData(treeNodes[0].id);
                    }
                });
                $("#loading").fadeOut(500);
            }
    复制代码

    这样处理的效果是页面只是局部刷新,体验很好。

    2)基于JQuery的数据加载及保存操作

    由于JQuery的方便性及良好体验性,我统一了数据的获取及保存操作。下面给出相关的处理代码供参考。

    复制代码
            //加载制定的对象数据
            function loadData(id) {
                $("#loading").show();
                $.getJSON("http://www.cnblogs.com/AjaxHandler/FunctionJson.ashx?r=" + Math.random() + "&op=findbyid&id=" + id, function (json) {
                    $("#txtID").val(json.ID);
                    $("#txtName").val(json.Name);
                    $("#txtControlID").val(json.ControlID);
                    $("#txtPID").val(json.PID);
                });
    
                $('#lbxRoles').empty();
                $.getJSON("http://www.cnblogs.com/AjaxHandler/RoleJson.ashx?r=" + Math.random() + "&op=getrolesbyfunction&id=" + id, function (json) {
                    $.each(json, function (i, item) {
                        $('#lbxRoles').append('<option value="' + item.ID + '">' + item.Name + '</option>');
                    });
                });
                $("#loading").fadeOut(500);
            }
    
            //保存对象数据
            function saveData() {
                $.ajax({
                    type: 'POST',
                    url: 'http://www.cnblogs.com/AjaxHandler/FunctionJson.ashx?r=' + Math.random() + '&op=insert',
                    async: false,
                    data: { ID: $("#txtID").val(), Name: $("#txtName").val(), ControlID: $("#txtControlID").val(), PID: $("#txtPID").val() },
                    success: function (id) {
                        alert("操作成功! ");
                        reloadTree();
    
                        if (id != "")
                            loadData(id);
                    },
                    error: function (xhr, status, error) {
                        alert("操作失败"); //xhr.responseText
                    }
                });
            }
    复制代码

    3)在数据操作等待的时候,页面中间显示Loading效果。

    整个系统,在各种请求操作,我都统一了作法,在界面显示Loading的等待效果,任务结束后关闭,这种效果在费事的操作,用户体检会好一些,下面看看其效果以及实现代码。

    增加下面javascript脚本

    复制代码
            //对象居中的函数,调用例子:$("#loading").center();
            jQuery.fn.center = function () {
                this.css("position", "absolute");
                this.css("top", Math.max(0, (($(window).height() - this.outerHeight()) / 2) +
                                                    $(window).scrollTop()) + "px");
                this.css("left", Math.max(0, (($(window).width() - this.outerWidth()) / 2) +
                                                    $(window).scrollLeft()) + "px");
                return this;
            }
    
            //初始化对象
            $(document).ready(function () {
    
                $("#loading").center();//loading的图片显示居中
            });
    复制代码

    然后再页面Body后面增加一行代码即可(默认loading图片不显示的哦)。

        <div id="loading" style="display: none;"><img alt="数据正在加载中..." src="http://images.cnblogs.com/loading02.gif" /></div>

    4)控件数据清空操作

    由于添加和编辑公用界面控件元素,因此我们在要添加数据的时候,需要清空或者设置某些控件的值,但我们的控件可能比较多,一种好的方法是利用JQuery的选择器功能来进行有目的的控件清空操作。

    如下面的例子所示。

    复制代码
            //新增清空控件
            function addData() {
                $("#txtPID").val($("#txtID").val());
                $("input[type=text][id*='txt']").val("");
                $("textarea[id*='txt']").empty();
                $("select[id*='lbx']").empty();
            }
    复制代码

    其中$("#txtPID").val($("#txtID").val());是把当前的用户作为添加数据的上级,其他的就是清空控件的数据了,不同的类型控件清空的步骤有些不同。

    5)Ashx处理程序的安全性考虑

    我们在系统中,多数都是调用ashx进行数据处理,虽然一般业务系统在VPN或者内网中运行,但是也要考虑用户没登陆的时候,不运行调用ashx程序,这样可以提高数据的安全性。

    默认的ashx处理程序是没有Session的操作的,所以我们需要修改其继承接口(多增加IReadOnlySessionState 的继承)

    ,然后才能调用Session来进行判断。

    复制代码
        /// <summary>
        /// 权限功能操作类
        /// </summary>
        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        public class FunctionJson : IHttpHandler, IReadOnlySessionState 
        {        
            public void ProcessRequest(HttpContext context)
            {
                //类对象要显式的实现接口 IReadOnlySessionState,才能访问Session数据
                if (context.Session["Identity"] == null)
                {
                    throw new ArgumentException("用户未登录!");
                }
    复制代码

    6)Tab界面布局的兼容。

    Web权限系统提供了两种常用的菜单布局进行管理,一般对于常用的业务系统肯定是没问题了。

    另外一种效果是系统复杂的时候用的,可以定义一级菜单和关联的二级菜单。

    对于后者,一级菜单打开的时候,可以关联打开一个新的页面,并且刷新二级菜单的关系。

    复制代码
            <!----------- 一级导航 ------------------>
            <ul class="navigation" style="display:block">
                <li><a href="#" onclick="showSubMenu('ListUser.aspx', '用户管理', 'default')">权限管理</a></li>
                <li><a href="#" onclick="showSubMenu('../Commonpage/MyJob.aspx', '事务中心开发中...', 'point', 'icon-organ')">事务中心</a></li>
                <li><a href="#" onclick="showSubMenu('../Commonpage/building.htm', '合同起草开发中...', '')">合同起草</a></li>
                <li><a href="#" onclick="showSubMenu('../Commonpage/building.htm', '合同管理开发中...', '')">合同管理</a></li>
                <li><a href="#" onclick="showSubMenu('../Commonpage/building.htm', '查询打印开发中...', '')">查询打印</a></li>
                <li><a href="#" onclick="showSubMenu('../Commonpage/building.htm', '知识管理开发中...', '')">知识管理</a></li>
                <li><a href="#" onclick="showSubMenu('../Commonpage/building.htm', '系统管理开发中...', '')">系统管理</a></li>
            </ul>
    复制代码

     好了,很多其他的特点,以后继续介绍,欢迎多多提出宝贵意见。

    主要研究技术:代码生成工具、Visio二次开发、送水管理软件等共享软件开发
    专注于Winform开发框架、WCF开发框架的研究及应用。
      转载请注明出处:
    撰写人:伍华聪  http://www.iqidi.com 
        
     

    一步一步Asp.Net MVC系列_权限管理数据库与ViewModel篇

     

    在上一篇中我们讲了大致怎么搭建一个项目,以及一个项目的基本构架,这一次,我们讲解基本的权限管理思路.

    说道权限管理,相信大家都不太陌生,这个东西几乎什么系统都会涉及到,因此,抽出时间去思考,去研究复用的模块,架构,就是一个非常好的提升水平的方式,特别是对于我们这些学生来说,没有太多经验,更需要去研究这些东西来更多的掌握实战方面的技巧.数据库设计是一个几乎人人都要面对的一个话题,我就来讲,我的数据库设计.当然只是个人见解,博客园的很多人都设计的各种各样的权限,各种各样的思路,但是可能大多数设计的比较复杂,我只是从一个菜鸟的角度讲解,我能够理解的权限管理.

    首先是数据库设计:

    在此期间,我们贴上powerdesinger设计图

    image

    大致就是7张表:

    tbModule模块表

    tbModulePermission模块权限表

    tbPermission权限表

    tbRole角色表

    tbRolePermission角色权限表

    tbUserRole用户角色表

    tbUser用户表

    首先要注意的就是命名:

    可以看到,我的数据库命名风格是非常统一的,呵呵,这个可能是习惯,tb代表table,如果是视图我就在前面加上v,后面全部是Pascal风格的命名,字母大写的方式.

    一个清晰的命名可以让我们很容易看清细节,起一个好名字非常重要.

    这也告诉我们,我们平时注意细节,命名,代码规范统一,让我们节省大量的时间.

    在博客园看到很多权限设计思路,

    我觉得上面这个可能是比较清晰的,呵呵,参考了好多人的设计,

    权限设计要设计那些呢?

    首先,一个模块对应多种权限,增删改查,导入导出等等等等,这里权限在mvc里面可以控制到每一个请求,每一个ajax响应,在mvc里面有一个很好的对应关系,一个模块对应就是一个控制器,一个action就是一个权限(当然这种可能太细了,但是我们可以先这么看),这样我们就需要在tbModule和tbPermission中间加一个tbModulePermission表了,因为不同角色的同一模块的权限可能都是不一样的,所以这里要加入一个tbModulePermission表.

    其次,角色的设计,角色拥有模块权限,每个用户对应多种角色,这个用户可能即是普通管理员,又是其他后台编辑,等等,权限当然是不一样的.

    这样一个思路就很明显了,角色直接拥有各种模块的功能权限,而用户可以扮演各种角色.

    至于字段的设计,我看了很多人的设计,很多细节的字段,比如IsDeleted,CreateUserID,CreateDate,ModifyUserID,ModifyDate,Description这些几乎大部分表都是需要的,多加一些表,更是让设计更灵活.

    数据库的设计ID用GUID的习惯,我还没有养成,可能现在没遇到麻烦吧,也是没有真正的工作经验,呵呵,别人都说应该主键用GUID,呵呵!

    我看到很多人直接设计Menu和Button来表示tbModule和tbPermission,其实感觉从命名来讲,页面任何一个ajax请求都可以算是一种功能权限,可能不是Button,如果用Permission表示可能更好一点,至于Menu,我认为用Module来命名可能更好,在tbModule里面有一个IsMenu字段来表示是否是菜单,里面有一个Controller字段,其实大致对应一个Controller控制器,每一个模块在mvc中以控制器的方式展示,我们在设计UI菜单的时候也可以通过反射的方式来获取Controller,对于菜单的设计更友好,不需要自己去配置URL,更自由.当然,在Permission表里面有PermissionAction和PermissionController字段表示,每一个动作请求和对应的控制器.

    image

    对于菜单的添加我们可以做到非常轻松,点点点就能够配置好,以前需要在数据库输入的一些预定义的数据!

    数据设计好了,这种数据库可能我感觉比较清晰.呵呵!

    接下来,我们继续谈关于设计的问题,上次基本上已经讲完关于数据访问层的设计,这次讲解,关于ViewModel的设计和公共类库的设计,

    ViewModel呵呵,我用它来干什么,我用它来做UI层和业务逻辑层交互的实体对象.

    可能大家觉得EF已经帮我们设计了数据库的实体对象,为什么还要另外一个单独的层来定义交互的实体对象?

    image

    我把这个ViewModel就是和界面交互的实体对象,在EF基本上是根据数据库直接生成的实体,所以命名字段都是跟数据库紧密结合在一起的,往往我们给UI层展示的时候,要做大量的处理,而UI交互,经常性我们用的后台框架都要求提供指定格式的json数据,我们以前甚至这么干过,从数据访问层直接select DeptID as id,name as title from deptment……………这种代码简直太坑爹了,而几乎数据库的东西和界面都关联到一起了,如果我们修改个字段,界面改点东西,两头都要动,所以,这种模式太痛苦了.

    因此,我专门独立了一层,用来做UI和业务逻辑层的交互使用,这样ViewModel与UI更友好,用例子说说吧!

    数据库的Permission在界面中大量显示为Button,我们就可以直接定义一个ViewModelButton类,来做UI和业务逻辑的交互,

    如果对于普通字段的添加,我相信在这个代码中,你的改动非常少,几乎只是EF模型重新生成,ViewModel,和html界面传参的修改,我们的目标是,让修改更简单,业务的变动,我们仍然能够清晰的把握细节.

       1:  /*  作者:       tianzh
       2:  *  创建时间:   2012/7/26 15:52:19
       3:  *
       4:  */
       5:  using System;
       6:  using System.Collections.Generic;
       7:  using System.Linq;
       8:  using System.Text;
       9:  using System.Web;
      10:  using TZHSWEET.Entity;
      11:   
      12:  namespace TZHSWEET.ViewModel
      13:  {
      14:      public class ViewModelButton
      15:      {
      16:          #region - 属性 -
      17:   
      18:          public int ID { get; set; }
      19:   
      20:          public string Action { get; set; }
      21:   
      22:          public string Name { get; set; }
      23:   
      24:          public bool IsVisible { get; set; }
      25:   
      26:          public string Script { get; set; }
      27:   
      28:          public string Icon { get; set; }
      29:   
      30:          public string Controller { get; set; }
      31:   
      32:          public string Description { get; set; }
      33:   
      34:          #endregion
      35:   
      36:          #region - 构造函数 -
      37:   
      38:          public ViewModelButton()
      39:          {
      40:   
      41:          }
      42:   
      43:          public ViewModelButton(HttpContextBase context)
      44:          {
      45:              ID = Convert.ToInt32(context.Request["ID"]);
      46:              Action = context.Request["Action"];
      47:              Name = context.Request["Name"];
      48:              IsVisible = context.Request["IsVisible"] == "1";
      49:              Script = context.Request["Script"];
      50:              Icon = context.Request["Icon"];
      51:              this.Controller = context.Request["Controller"];
      52:   
      53:   
      54:          }
      55:   
      56:          #endregion
      57:   
      58:          #region - 方法 -
      59:          #region ToEntity
      60:          public static tbPermission ToEntity(ViewModelButton permission)
      61:          {
      62:              tbPermission item = new tbPermission();
      63:              item.PermissionID = permission.ID;
      64:              item.PermissionName = permission.Name;
      65:              item.PermissionAction = permission.Action;
      66:              item.Script = permission.Script;
      67:              item.Icon = permission.Icon;
      68:              item.IsVisible = permission.IsVisible;
      69:              item.PermissionController = permission.Controller;
      70:              item.Description = permission.Description;
      71:              return item;
      72:          }
      73:   
      74:          #endregion
      75:          #region ToViewModel
      76:          /// <summary>
      77:          /// 转化为ViewModel
      78:          /// </summary>
      79:          /// <param name="permission"></param>
      80:          /// <returns></returns>
      81:          public static ViewModelButton ToViewModel(tbPermission permission)
      82:          {
      83:              ViewModelButton item = new ViewModelButton();
      84:              item.ID = permission.PermissionID;
      85:              item.Name = permission.PermissionName;
      86:              item.Action = permission.PermissionAction;
      87:              item.IsVisible = permission.IsVisible.Value;
      88:              item.Icon = permission.Icon;
      89:              item.Script = permission.Script;
      90:              item.Controller = permission.PermissionController;
      91:              item.Description = permission.Description;
      92:              return item;
      93:          }
      94:   
      95:          /// <summary>
      96:          /// 转化为List集合
      97:          /// </summary>
      98:          /// <param name="list"></param>
      99:          /// <returns></returns>
     100:          public static IEnumerable<ViewModelButton> ToListViewModel(IEnumerable<tbPermission> list)
     101:          {
     102:              var listModel = new List<ViewModelButton>();
     103:              foreach (tbPermission item in list)
     104:              {
     105:                  listModel.Add(ViewModelButton.ToViewModel(item));
     106:              }
     107:              return listModel;
     108:          } 
     109:          #endregion
     110:   
     111:          #endregion
     112:   
     113:   
     114:      }
     115:  }

    一般情况下,我们只需要定义实体,和一个构造函数,这个构造函数,大家可能觉得为什么要传递HttpContext?

    原因就是,封装Controller中的请求,把Controller中表单的数据请求单独用ViewModel处理,看看关于Controller的一个函数处理

    image

    我们把关于请求的数据从Controller中分离处理啊,Controller就相当简洁了,很清晰的看到具体的思路,而我们的业务逻辑层,就可以相当简洁的调用

    image

    而这个T是什么呢?T就是tbPermission实体这个东西了,是不是发现页面非常清晰,简洁?

    而我们经常通用的几种UI格式,LigerUI Grid格式请求,Select格式请求,GridTree格式请求,Tree格式请求,都是ViewModel中的封装,

    image

    GridRequest请求的ViewModel:

       1:   /*  作者:       tianzh
       2:   *  创建时间:   2012/7/19 18:16:08
       3:   *
       4:   */
       5:   /*  作者:       tianzh
       6:   *  创建时间:   2012/7/16 15:31:37
       7:   *
       8:   */
       9:   
      10:  using System;
      11:  using System.Collections.Generic;
      12:  using System.Linq;
      13:  using System.Text;
      14:  using System.Web;
      15:  namespace TZHSWEET.ViewModel
      16:  {
      17:      public class LigerUIGridRequest
      18:      {
      19:   
      20:          private string sortOrder;
      21:   
      22:          private int pageSize;
      23:          /// <summary>
      24:          /// 字段查看视图(暂时没做到)
      25:          /// </summary>
      26:          public string View
      27:          {
      28:              get;
      29:              set;
      30:          }
      31:          /// <summary>
      32:          /// 排序字段名称
      33:          /// </summary>
      34:          public string SortName { get; set; }
      35:          /// <summary>
      36:          /// 排序规则
      37:          /// </summary>
      38:          public string SortOrder
      39:          {
      40:              get
      41:              {
      42:                  if (this.sortOrder == "desc")
      43:                      return this.sortOrder;
      44:                  else
      45:                      return "asc";
      46:              }
      47:   
      48:              set
      49:              {
      50:                  this.sortOrder = value;
      51:              }
      52:          }
      53:          private int pageNumber;
      54:          /// <summary>
      55:          /// 页号
      56:          /// </summary>
      57:          public int PageNumber
      58:          {
      59:              get
      60:              {
      61:                  if (this.pageNumber <= 0)
      62:                      return 1;
      63:                  else
      64:                      return pageNumber;
      65:              }
      66:              set
      67:              {
      68:                  if (value <= 0)
      69:                      pageNumber = 1;
      70:                  else
      71:                      pageNumber = value;
      72:              }
      73:          }
      74:          /// <summary>
      75:          /// 每页的多少条数据
      76:          /// </summary>
      77:          public int PageSize 
      78:          {
      79:              get
      80:              {
      81:                  return this.pageSize;
      82:              }
      83:              set
      84:              { 
      85:                 this.pageSize= (value==0?10:value);
      86:              }
      87:          }
      88:          /// <summary>
      89:          /// 查询条件
      90:          /// </summary>
      91:          public string Where { get; set; }
      92:          /// <summary>
      93:          /// 初始化读取信息
      94:          /// </summary>
      95:          /// <param name="context"></param>
      96:          public LigerUIGridRequest(HttpContextBase context)
      97:          {
      98:              this.View = context.Request["view"];
      99:              this.SortName= context.Request["sortname"];
     100:             this.SortOrder = context.Request["sortorder"]=="desc"?"desc":"asc";
     101:             this.PageNumber =Convert.ToInt32(context.Request["page"]);
     102:             this.PageSize =Convert.ToInt32(context.Request["pagesize"]);
     103:              this.Where = context.Request["where"];
     104:   
     105:          }
     106:      }
     107:  }

    在这里我们可以做数据的校验工作,这样我们的控制器就非常非常的简洁了,

    在业务逻辑层,我们可以通过Request的ViewModel模型,进行数据处理,

    image

    可以看到我们仅仅通过数据的封装就让我们的UI和业务逻辑更简洁,更丰富,

    当然这个业务逻辑层写的很有问题..............就是业务逻辑层竟然直接返回json格式,这个大大的不好,因为我们的业务逻辑层尽可能的跟数据的展示格式没关系,应该直接返回数据,集合就直接返回IEnumerable<T>接口,而数据如何展示应该在控制器中控制.

    一个好一点的方法设计就是这样子的,

    image

    可以看到我们的工作大部分就是让不同的类减压,尽量更简单,单一指责,如果一个类的职责过多就更难维护,特别是控制器,很容易出现大量的代码,

    就像我们的这个

    image

    让代码更精简,这样我们才能更清晰,本来我们返回的Grid数据需要json化,我写了一个扩展方法,返回json,而且格式针对UI更友好,配合前台的封装的ajax,让消息的提示更友好,

    image

    image

    前台利用LigerUI中的封装,

    image

    image

    总结:可以看到我们每一部细节的改进,都能让我们代码更简洁,而且让我们的思路更清晰,清晰,才是我们的追求,清晰的代码是我们的追求!

     

    注意:项目心得分享请加群,请加群:190678518(已满) 190678030(未满) mvc学习系列,等我写完权限管理,上传源码到网盘,呵呵! 一直在努力,从未曾放弃,努力学习中..... 欢迎一起学习.net!
  • 相关阅读:
    zookeeper报错java.net.ConnectException: Connection refused: no further information
    dubbo 使用zookeeper 出现 Dubbo客户端调用报错NullPointerException
    docker下载镜像received unexpected Http status:500 Internal Server Error
    Spring Security 无法登陆,报错:There is no PasswordEncoder mapped for the id “null”
    js 解决中文乱码的问题
    使用Elasticsearch 出现的拒绝连接
    com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class cn.edu.
    yii2判断数据库字段is null
    MySQL之终端(Terminal)管理数据库、数据表、数据的基本操作
    yii2.0-rules验证规则应用实例
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2614577.html
Copyright © 2020-2023  润新知