• operamasksui2.0 +MVC4.0+EF5.0实战之四 部门管理功能及网格控件(datagrid)


      前几篇侧重点还是在布局,下面,主角出场,网格控件的地位和意义已无需再说,内容也比较多,预计得分几篇才能说完,本文是一些基础的东西,但不乏需要注意的地方。

      对于MIS系统来说,公司的组织架构是一个基础的功能(网站系统则没有所谓的部门及成员,而侧重于以个体为单位的会员),也即通常所说的部门。与前面说的菜单类似,通常也是采取自关联形成树形结构。为了方便维护,设计上采取左侧树,右侧网格的方式,先上效果图,以便有个直观的印象。

      先说一下后台基本工作。

      采用Code First模式,首先创建部门实体。  

    View Code
    View Code 
    
    using Model.Framework;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Script.Serialization;
    using System.Web.Mvc;
    
    namespace Model.Sys
    {
        public  class Department : BaseEntity
        {
    
            [DisplayName("内码")]
            public string ID { get; set; }
    
            [DisplayName("部门名称")]
            [StringLength(20)]
            [Required()]
            [Remote("CheckExistForName", "Department", AdditionalFields = "Name,ParentID,ID", ErrorMessage = "上级部门下已存在该名称的部门,请确认")]
            public string Name { get; set; }
    
            [DisplayName("电话")]       
            public string Tel { get; set; }
    
            [DisplayName("传真")]
            public string Fax { get; set; }
    
            [DisplayName("地址")]
            public string Address { get; set; }
    
            [DisplayName("描述")]
            public string Describe { get; set; }
    
            [DisplayName("创建时间")]        
            public Nullable<System.DateTime> CreateDate { get; set; }
            
            [DisplayName("使用标志")]
            [Required()]
            public string UseFlag { get; set; }
    
            [DisplayName("备注")]
            public string Remark { get; set; }
    
            [DisplayName("排序号")]
            public string SortNo { get; set; }
    
           
            public string ParentID { get; set; }
            [DisplayName("上级部门")]   
            [ForeignKey("ParentID")]   
            public  Department ParentDept { get; set; }
    
    
            public string CreateUserID { get; set; }
            [DisplayName("创建人")]
            [ForeignKey("CreateUserID")]        
            public  User CreateUser { get; set; }
    
    
            public   ICollection<Department> SonDepts { get; set; }      
            public   ICollection<User> Users { get; set; }

      继承的BaseEntity是为了方便以后为所有实体加统一的方法预留的,目前为空,你可以无视。另外,用了一些数据声明和验证的东西,暂不做详细说明。这里有一点必须注意,去除virtual关键字,否则在执行Json序列化时,就会报检测到循环引用的错误。

      然后在数据库里插入几条测试数据(以下是使用EntityFramework的迁移功能,在Configuration类的Seed方法里加入测试数据,关于迁移功能请参见我之前的一篇译稿前半部分 Asp.Net MVC4.0 官方教程 入门指南之八--为Movie模型和库表添加字段),当然你也可以在数据库里手工添加。          

    View Code
                context.Department.AddOrUpdate(
                    p => p.ID,
                    //new Department { ID = "1", Name = "部门组织", ParentID = null, UseFlag = "4" },
                    new Department { ID = "2", Name = "软件公司", ParentID = null, UseFlag = "4" },
                    new Department { ID = "3", Name = "研发部", ParentID = "2", SortNo = "01", UseFlag = "4" },
                    new Department { ID = "4", Name = "产品部", ParentID = "2", SortNo = "02", UseFlag = "4" },
                    new Department { ID = "5", Name = "办公室", ParentID = "2", SortNo = "03", UseFlag = "4" },
                    new Department { ID = "6", Name = "信息中心", ParentID = "5", UseFlag = "4" },
                    new Department { ID = "11", Name = "部门11", ParentID = "4", SortNo = "11", UseFlag = "4" },
                    new Department { ID = "12", Name = "部门12", ParentID = "4", SortNo = "12", UseFlag = "4" },
                    new Department { ID = "13", Name = "部门13", ParentID = "4", SortNo = "13", UseFlag = "4" },
                    new Department { ID = "14", Name = "部门14", ParentID = "4", SortNo = "14", UseFlag = "4" },
                    new Department { ID = "15", Name = "部门15", ParentID = "4", SortNo = "15", UseFlag = "4" },
                    new Department { ID = "16", Name = "部门16", ParentID = "4", SortNo = "16", UseFlag = "4" },
                    new Department { ID = "17", Name = "部门17", ParentID = "4", SortNo = "17", UseFlag = "4" },
                    new Department { ID = "18", Name = "部门18", ParentID = "4", SortNo = "18", UseFlag = "4" },
                    new Department { ID = "19", Name = "部门19", ParentID = "4", SortNo = "19", UseFlag = "4" },
                    new Department { ID = "20", Name = "部门20", ParentID = "4", SortNo = "20", UseFlag = "4" },
                    new Department { ID = "21", Name = "部门21", ParentID = "4", SortNo = "21", UseFlag = "4" }
                    );

       以上是后台的基础性工作,关于前台调用的后台方法,跟前台一块描述,这样联系更紧密一些。

      新建一个控制器,命名为DepartmentContorller,空模板空支架,也就是完全自己控制,不用mvc脚手架自动生成。然后在其Index方法里右键,选择生成视图,命名为ListPag,打开ListPage.cshtml 

      1.在head标签内部加入对om相关css样式表的引用

        @Styles.Render("~/OperaMasksUI/css/default/om-default.css")     

      2.在</body>标签之前加入以下对js文件的引用

          @Scripts.Render("~/OperaMasksUI/js/jquery163.min.js")

              @Scripts.Render("~/OperaMasksUI/js/operamasks-ui200.min.js")

      3.部门管理功能我们想实现左侧树右侧网格的效果,因此需要用到前面已经说过的布局控件,如下所示          

      <div id="page" >
            <div id="west-panel">
                <ul id="tree"></ul>
            </div>
            <div id="center-panel">            
                <table id="datagrid"  ></table>
            </div>
        </div>

      对应的初始化js为

     function LoadLayout()
            {
                $('#page').omBorderLayout({
                    panels: [
                        {
                            id: "west-panel",
                            title: "部门组织",
                            region: "west",
                            resizable: true,
                            collapsible: true,
                             200
                        },
                    {
                        id: "center-panel",
                        region: "center",
                        header: false
                    }
                    ],
                    hideCollapsBtn: true,
                    fit: true,
                    spacing: 7
                });
            }

      前面已经详细学习过布局控件的使用,在此就不再啰唆,仅将相关代码贴出来,以上效果就是仅使用左右布局,且左侧区域可折叠。

      然后是左侧树,与前面菜单类似,同样只贴出代码。

      树初始化js:

     function LoadTree()
            {
                $("#tree").omTree({
                    simpleDataModel: true,
                    dataSource: '@Url.Action("Tree")',
                    onClick: TreeNodeClick
    
                });
            }

      后台获取数据Tree方法为:

            //左侧部门树        
            public ActionResult Tree()
            {
                IQueryable<Department> all = DepartmentService.Query();
                var nodes = new List<TreeNode>();
                foreach (var item in all.ToList())
                {
                    TreeNode node = new TreeNode();
                    node.id = item.ID;
                    node.pid = item.ParentID;
                    node.text = item.Name;               
                    node.expanded = "true";
                    nodes.Add(node);
                }
                return Content(nodes.ToJsonString());   
            }

      下面,网格控件登场。

      初始化js:

         var rootID = "2";
            var defaultSort = { sortBy: 'Name', sortDir: 'asc' };
            function LoadDataGrid()
            {
       
                $('#datagrid').omGrid({
                    method:'POST',
                    title: '部门列表',
                    extraData: $.extend({ id: rootID }, defaultSort),
                    singleSelect: false,               
                    dataSource: '@Url.Action("DataGrid")',
                    colModel:
                    [
                        { header: '@Html.DisplayNameFor(model => model.Name)', name: 'Name',  200, align: 'center', sort: 'serverSide' },
                        { header: '@Html.DisplayNameFor(model => model.Tel)', name: 'Tel',  100, align: 'center'},                   
                        { header: '@Html.DisplayNameFor(model => model.Describe)', name: 'Describe',  'autoExpand', align: 'center' },
                        { header: '@Html.DisplayNameFor(model => model.SortNo)', name: 'SortNo',  40, align: 'center', sort: 'serverSide' },
                        { header: '操作', name: 'ID',  100, align: 'center', renderer:DatagridOpColumn },
    
                    ]               
                });
               
            }

      前面有两个变量,一个是部门根目录id,另外一个是默认排序,这两部分,都通过初始化参数extraData属性传到后台,至于colModel则指明每一列,注意列显示名称没有写死,而是通过@Html.DisplayNameFor(model => model.Name)方式从模型里取的,这样一旦模型的DisplayName改了,所有的前台页面都会自动更新,要使用这种方式,需要在首行加上@model Model.Sys.Department。

      dataSource指明取数据的后台方法,如下:  

            //右侧部门列表
            [HttpPost]
            public ContentResult DataGrid(FormCollection form)
            {
                string id = form["id"];
                //若为根目录,查询所有部门,否则附加查询限制条件
                /***处理略,之后篇章里详述统一查询处理**/
    //排序 Sort sort = new Sort(form["sortBy"], form["sortDir"]); //查询 IQueryable<Department> queryResult = DepartmentService.Query(model, sort); //分页 PageView pageView = new PageView(Int32.Parse(form["start"]), form["limit"]); //取得数据 var data = DataGrid<Department>.GetPageData(queryResult, pageView); //返回数据 return Content(data.ToJsonString()); }

      辅助的各个类如下:
      排序类Sort:

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Common.Query
    {
        public class Sort
        {
            public string SortType { get; set; }
            public string Field { get; set; }
    
            public Sort(string field, string sortType = "asc")
            {
                Field = field;
                SortType = sortType;
            }
            public string Expression
            {
                get
                {
                    return string.Format(" {0} {1} ", Field, SortType);
                }
            }
        }
    }

      分页类PageView:

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Common.Query
    {
        public class PageView
        {
            /// <summary>
            /// 页面索引
            /// </summary>
            public int PageIndex { get; set; }
    
            /// <summary>
            /// 页面记录数
            /// </summary>
            public int PageSize { get; set; }
    
            /// <summary>
            /// 记录起始数
            /// </summary>  
            public int RecordStart { get; set; }
            
            
            public PageView()
            {
    
            }
            public PageView(string pageIndex, string pageSize)
            {
    
                if (pageIndex != null )
                {
                    try
                    {
                        PageIndex = Int32.Parse(pageIndex);
                    }
                    catch 
                    {
                        PageIndex =1;
                    }
                   
                }
                if (pageSize != null)
                {
                    try
                    {
                        PageSize = Int32.Parse(pageSize);
                    }
                    catch
                    {
                        PageSize = 10;
                    }                
                }
               
            }
    
            public PageView(int recordStart, string pageSize)
            {
                if (pageSize != null)
                {
                    try
                    {
                        PageSize = Int32.Parse(pageSize);
                    }
                    catch
                    {
                        PageSize = 10;
                    }
                }
    
                PageIndex = (int)Math.Ceiling((double)recordStart / (double)PageSize);
    
            }
          
        }
    }

      其实,我原来的分页类里只有当前页码PageIndex和页面记录数PageSize,om传给后台的limit是页面记录数,而start居然是起始记录数(不得不说,om设计人员的思维模式……),因此不得不修改这个类来适应,加入重载初始化函数以及通过RecordStart和PageSize来换算出PageIndex。
      网格类DataGrid是为了前后台交换数据用的:

    View Code
    using Common.Query;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    
    namespace Model.Json
    {
        public class DataGridRow
        {
            public string id { get; set; }
            public List<string> cell { get; set; }
        }
        public class DataGrid<T>:Object
        {
             public int total { get; set; }
             public List<T> rows { get; set; }
             public static DataGrid<T> ConvertFromList(List<T> list) 
            {
                DataGrid<T> data = new DataGrid<T>();
    
                if (list != null)
                {
                    data.total = list.Count;
                    data.rows = list;               
                }
                else
                {
                    data.total = 0;
                }
                
                return data;
            }
    
             public static DataGrid<T> GetAllData(IQueryable<T> query)
             {
                 DataGrid<T> data = new DataGrid<T>();
    
                 if (query != null)
                 {
                     data.total = query.Count();
                     data.rows = query.ToList();
                 }
                 else
                 {
                     data.total = 0;
                 }
    
                 return data;
             }
             public static DataGrid<T> GetPageData(IQueryable<T> query,PageView pageView)
             {
                 DataGrid<T> data = new DataGrid<T>();
                 if (query != null)
                 {
                     data.total = query.Count();
                     if (pageView != null)
                     {
    
                         if (pageView.PageSize > 0 && pageView.PageIndex >= 0)
                         {
    
                             query = query.Skip(pageView.PageIndex * pageView.PageSize).Take(pageView.PageSize);
                         }
    
                     }      
                     data.rows = query.ToList();
                 }
                 else
                 {
                     data.total = 0;
                 }
    
                 return data;
             }
          
        }
     
    }


      前台还有一个部门树点击的事件处理,就是就部门的id传给后侧的网格:

          function TreeNodeClick(nodeData,event)
            {
                $('#datagrid').omGrid({ extraData: $.extend({ id: nodeData.id }, defaultSort) });
            }

       事实上,你看到的最终的结果,中间还是经历了一些曲折……

      首先一个问题跟本节内容关系不大,但是本节中暴露出来了,就是实战三中,点击功能菜单后,在右侧业务区域中动态添加tab页,嵌入iframe,相关的js如下  

      function TreeNodeClick(node, event)
     {
          $("#tabs").omTabs('add', {
          title: node.text,
          content: '<iframe scrolling="yes" frameborder="0"  src=' + node.url + ' style="100%;height:100%;"></iframe>',
          closable: true,
          tabId:node.id
          });
    }

      咋看上去是没问题,添加了tab页,当时运行也没发现问题,加了内容后就出问题了,高度!高度不能自动适配,只显示大概几百像素,没有填充整个tab页,即使设置了style="100%;height:100%;也没用,而在easyui中就没这问题,直接可以实现完美的完全填充效果,查找资料,反复试验,最终采取下面这种方式勉强达到效果:

    content: '<iframe id="frame" onload="$(this).height($(this).contents().find(' + "tabs" + ').height()-55)" scrolling="yes"  frameborder="0"  src=' + node.url + ' style="100%;height:100%"></iframe>'

      即使用js在iframe加载完成后,动态获取tab标签页的高度然后减去55px,设置为iframe的高度,至于为什么设置为55,一是tab标签头部自身25px,另外30是一些margin、border占用的,目测和试验55效果最好,未在多浏览器多显示器下测试,可能还有问题。若你有更好的解决方式,欢迎留言说明,先行谢过。

      第二个问题是关于服务器端排序问题,datagrid的colModel属性,可以设置各列的排序方式,客户端、服务器端或者自定义js函数,如果是采用服务器端排序,即设置 sort: 'serverSide'。另外,我后台分页,对IQureyable对象使用Skip方法,该方法要求必须有orderBy子句,从业务角度考虑,通常也需要在datagrid首次加载的时候设置一个默认排序字段和排序方式(asc或desc)。查看了官方示例和说明,datagrid自身属性没有排序相关内容,而是在其基础了外挂了一个排序插件,点击列标题的时候会向后台发送sortBy和sortDir。初始化的时候,则没有提供对外设置关于排序的方法和属性,因此,只能放到extraData属性中。结果问题就来了,调试时候发现,服务器端排序不起作用,发现前台传给后台的排序参数,始终是初始化中设置的Name和asc,点击列头根本不起作用。无奈之下只能查看om源码,幸好源码是开放的且注释比较多,找到了omGrid的_populate方法,大概在11040行,发现了问题所在,合并参数的时候,用初始化的参数,把排序两字段覆盖了,源代码如下: 

     var param =$.extend(true,{},this._extraData,op.extraData,{
                    start : limit * (nowPage - 1),
                    limit: limit,
                     _time_stamp_ : new Date().getTime()
                }); 

      问题是找到了,但是om没有提供任何关于排序的方法或属性,用于控制点击列头来排序这个过程,全部内置了。无奈之下,只有修改源代码,把排序两个字段传了过去  

     var param =$.extend(true,{},this._extraData,op.extraData,{
                    start : limit * (nowPage - 1),
                    limit: limit,
                    sortBy: this._extraData.sortBy,
                    sortDir: this._extraData.sortDir,
                    _time_stamp_ : new Date().getTime()
                });            

      小改动,改完后服务器端排序总算正常了,理论上对其他地方也没影响,应该不会因为改动带来新的问题。要设置初始加载后的默认排序字段,自身属性不提供,只能通过extraData这里加上,若自定义名字,不跟sortBy和sortDir重名,则后台方法就要分别处理,还要区分两种情况,若重名,则又会被初始参数覆盖,左右为难,这应该算一个BUG吧?

      本篇到此为止,这就是网格控件,实现了取数、展现、排序和分页,下节介绍增、删、改、查。

      最后,祝园子里各位新年快乐!

  • 相关阅读:
    Excel-单条件和多条件匹配搜索
    Excel-条件判断
    Excel-常用快捷键
    EXCEL-批量下拉填充
    Excel-数据分列的多种方法实现
    Excel-统一小括号格式(中文小括号,英文小括号)
    在WEB网页上模拟人的操作(批量操作)
    EXCEL-常用函数总结
    C语言学习——bsmap-2.74_main.cpp
    Linux --- awk
  • 原文地址:https://www.cnblogs.com/seawaving/p/2909518.html
Copyright © 2020-2023  润新知