operamasks-ui2.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,
                        width: 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', width: 200, align: 'center', sort: 'serverSide' },
                    { header: '@Html.DisplayNameFor(model => model.Tel)', name: 'Tel', width: 100, align: 'center'},                   
                    { header: '@Html.DisplayNameFor(model => model.Describe)', name: 'Describe', width: 'autoExpand', align: 'center' },
                    { header: '@Html.DisplayNameFor(model => model.SortNo)', name: 'SortNo', width: 40, align: 'center', sort: 'serverSide' },
                    { header: '操作', name: 'ID', width: 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="width:100%;height:100%;"></iframe>',
      closable: true,
      tabId:node.id
      });
}

  咋看上去是沒問題,添加了tab頁,當時運行也沒發現問題,加了內容后就出問題了,高度!高度不能自動適配,只顯示大概幾百像素,沒有填充整個tab頁,即使設置了style="width: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="width: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吧?

  本篇到此為止,這就是網格控件,實現了取數、展現、排序和分頁,下節介紹增、刪、改、查。

  最后,祝園子里各位新年快樂!


免責聲明!

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



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