【開源】OSharp框架解說系列(2.1):EasyUI的后台界面搭建及極致重構


OSharp是什么?

  OSharp是個快速開發框架,但不是一個大而全的包羅萬象的框架,嚴格的說,OSharp中什么都沒有實現。與其他大而全的框架最大的不同點,就是OSharp只做抽象封裝,不做實現。依賴注入、ORM、對象映射、日志、緩存等等功能,都只定義了一套最基礎最通用的抽象封裝,提供了一套統一的API、約定與規則,並定義了部分執行流程,主要是讓項目在一定的規范下進行開發。所有的功能實現端,都是通過現有的成熟的第三方組件來實現的,除了EntityFramework之外,所有的第三方實現都可以輕松的替換成另一種第三方實現,OSharp框架正是要起隔離作用,保證這種變更不會對業務代碼造成影響,使用統一的API來進行業務實現,解除與第三方實現的耦合,保持業務代碼的規范與穩定。

本文已同步到系列目錄:OSharp快速開發框架解說系列

前言

  要了解一個東西長什么樣,至少得讓我們能看到,才能提出針對性的見解。所以,為了言之有物,而不是憑空漫談,我們先從UI說起,后台管理頁面的UI我們將使用應用比較普遍的easyui框架。

  以前在用easyui的時候,每個頁面都得從0做起,或者不厭其煩地由以前的頁面通過“復制-粘貼”的方式來修改,久頁久之,就會造成頁面龐大且難以維護。其實,前端的html,javascript代碼與后端的代碼是一樣的,通過一定的組織,把重復的代碼抽離出來,同樣也通過達到很好的復用率。而MVC的天生的Layout布局與分布視圖(Partial View),就是對重復代碼抽離的需求有很好的支持。

EasyUI-Layout布局

_Layout.cshtml

  MVC的布局,最先當然是作為根視圖存在的_Layout.cshtml了,_Layout.cshtml很簡單,只是負責一些樣式文件和公共腳本的引用。開發階段,先使用絕對地址進行引用,發布的時候再進行壓縮代碼的考慮。

  在_Layout.cshtml中,除了必需的 @RenderBody() ,還定義了兩個Section,分別為負責引用子級視圖樣式的 @RenderSection("header", false) 和負責引用子級視圖腳本的 @RenderSection("footer", false)

 1 @{
 2     Layout = null;
 3 }
 4 <!DOCTYPE html>
 5 <html>
 6 <head>
 7     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 8     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 9     <title>@ViewBag.Title - OSharp管理系統</title>
10     <link href="/Content/themes/gray/easyui.css" rel="stylesheet" />
11     <link href="/Content/themes/icon.css" rel="stylesheet" />
12     <link href="/Content/osharp-icons.css" rel="stylesheet" />
13     <link href="/Content/osharp-admin.css" rel="stylesheet"/>
14     @RenderSection("header", false)
15 </head>
16 <body>
17     @RenderBody()
18     <script src="/Scripts/jquery-1.11.1.js" type="text/javascript"></script>
19     <script src="/Scripts/jquery.easyui-1.4.1.js" type="text/javascript"></script>
20     <script src="/Scripts/locale/easyui-lang-zh_CN.js" type="text/javascript"></script>
21     <script src="/Scripts/json2.js" type="text/javascript"></script>
22     <script src="/Scripts/osharp.global.js" type="text/javascript"></script>
23     <script src="/Scripts/osharp.easyui.js" type="text/javascript"></script>
24     <script src="/Scripts/osharp.data.js" type="text/javascript"></script>
25     @RenderSection("footer", false)
26 </body>
27 </html>

 后台的EasyUI-Layout布局

  一般來說,后台管理頁面都是這樣一個布局方式:

  1. 上邊一個頂欄
  2. 左邊一個手風琴或樹形的導航欄
  3. 中間是一個由iframe加載具體內容頁的多選項卡tab頁面

  這樣,就要用到easyui的easyui-layout來做整體布局,左邊的導航欄使用easyui-accordion,右邊加載內容頁的多選項卡使用easyui-tabs。easyui的布局在網上也很普遍,具體的就不說了,完整代碼如下:

  1 @{
  2     ViewBag.Title = "OSharp后台管理";
  3     Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
  4     string navDataUrl = Url.Action("GetNavData");
  5 }
  6 <div class="easyui-layout" data-options="fit:true">
  7     <div data-options="region:'north', height:50" style="padding: 10px;">
  8         <span style="font-size: 18px;">OSharp后台管理系統</span>
  9         <a href="/" target="_blank">返回首頁</a>
 10     </div>
 11     <div data-options="region:'west', split:true, minWidth:100, width:150, title:'導航菜單'">
 12         <div id="main-nav" class="easyui-accordion" data-options="fit:true, border:false, selected:true">
 13 
 14         </div>
 15     </div>
 16     <div data-options="region:'center'">
 17         <div id="main-tab" class="easyui-tabs" data-options="fit:true, border:false">
 18             <div title="我的主頁" iconcls="pic_209" style="padding: 5px;">
 19                 <iframe width="100%" height="100%" frameborder="0" src="@Url.Action("Welcome")" marginheight="0" marginwidth="0"></iframe>
 20             </div>
 21         </div>
 22     </div>
 23     <div data-options="region:'south', height:50">
 24         <p style="text-align:center; line-height:20px;">Copyright &copy; OSharp @DateTime.Now.Year</p>
 25     </div>
 26 </div>
 27 <div id="tab-menu" class="easyui-menu" style="width: 150px;">
 28     <div id="tab-menu-refresh" data-options="iconCls:'icon-reload'">刷新</div>
 29     <div id="tab-menu-openFrame" data-options="iconCls:'pic_138'">新窗口打開</div>
 30     <div class="menu-sep"></div>
 31     <div id="tab-menu-close" data-options="iconCls:'icon-remove'">關閉</div>
 32     <div id="tab-menu-closeleft" data-options="iconCls:'icon-undo'">關閉左邊</div>
 33     <div id="tab-menu-closeright" data-options="iconCls:'icon-redo'">關閉右邊</div>
 34     <div class="menu-sep"></div>
 35     <div id="tab-menu-closeother" data-options="iconCls:'pic_101'">關閉其他</div>
 36     <div id="tab-menu-closeall" data-options="iconCls:'pic_283'">關閉所有</div>
 37 </div>
 38 
 39 @section footer{
 40     <script type="text/javascript">
 41         $(function() {
 42             $.getJSON("@navDataUrl", function(data) {
 43                 if (data.length == 0) {
 44                     return;
 45                 }
 46                 //第一層生成手風琴的項
 47                 $.each(data, function(i, item) {
 48                     var id = item.Id;
 49                     $("#main-nav").accordion("add", {
 50                         title: item.Text,
 51                         content: "<ul id='tree-" + id + "'></ul>",
 52                         selected: true,
 53                         iconCls: item.IconCls
 54                     });
 55                     $.parser.parse();
 56                     //第二層生成樹節點
 57                     if (!item.Children || item.Children.length == 0) {
 58                         return true;
 59                     }
 60                     var treeData = transToTreeData(item.Children);
 61                     $("#tree-" + id).tree({
 62                         data: treeData,
 63                         onClick: function(node) {
 64                             if (node.attributes) {
 65                                 var tabTitle = node.text;
 66                                 var url = node.attributes.url;
 67                                 var icon = node.iconCls;
 68                                 addTab(tabTitle, url, icon);
 69                             }
 70                         }
 71                     });
 72                 });
 73             });
 74 
 75             $("#main-tab").tabs({
 76                 onContextMenu: function(e, title) {
 77                     e.preventDefault();
 78                     $("#tab-menu").menu("show", { left: e.pageX, top: e.pageY })
 79                         .data("tabTitle", title); //將點擊的Tab標題加到菜單數據中
 80                 }
 81             });
 82 
 83             $("#tab-menu").menu({
 84                 onClick: function(item) {
 85                     tabHandle(this, item.id);
 86                 }
 87             });
 88         });
 89 
 90         function addTab(title, url, icon) {
 91             var $mainTabs = $("#main-tab");
 92             if ($mainTabs.tabs("exists", title)) {
 93                 $mainTabs.tabs("select", title);
 94             } else {
 95                 $mainTabs.tabs("add", {
 96                     title: title,
 97                     closable: true,
 98                     icon: icon,
 99                     content: createFrame(url)
100                 });
101             }
102         }
103 
104         function createFrame(url) {
105             var html = '<iframe scrolling="auto" frameborder="0"  src="' + url + '" style="width:100%;height:99%;"></iframe>';
106             return html;
107         }
108 
109         function tabHandle(menu, type) {
110             var title = $(menu).data("tabTitle");
111             var $tab = $("#main-tab");
112             var tabs = $tab.tabs("tabs");
113             var index = $tab.tabs("getTabIndex", $tab.tabs("getTab", title));
114             var closeTitles = [];
115             switch (type) {
116                 case "tab-menu-refresh":
117                     var iframe = $(".tabs-panels .panel").eq(index).find("iframe");
118                     if (iframe) {
119                         var url = iframe.attr("src");
120                         iframe.attr("src", url);
121                     }
122                     break;
123                 case "tab-menu-openFrame":
124                     var iframe = $(".tabs-panels .panel").eq(index).find("iframe");
125                     if (iframe) {
126                         window.open(iframe.attr("src"));
127                     }
128                     break;
129                 case "tab-menu-close":
130                     closeTitles.push(title);
131                     break;
132                 case "tab-menu-closeleft":
133                     if (index == 0) {
134                         $.osharp.easyui.msg.tip("左邊沒有可關閉標簽。");
135                         return;
136                     }
137                     for (var i = 0; i < index; i++) {
138                         var opt = $(tabs[i]).panel("options");
139                         if (opt.closable) {
140                             closeTitles.push(opt.title);
141                         }
142                     }
143                     break;
144                 case "tab-menu-closeright":
145                     if (index == tabs.length - 1) {
146                         $.osharp.easyui.msg.tip("右邊沒有可關閉標簽。");
147                         return;
148                     }
149                     for (var i = index + 1; i < tabs.length; i++) {
150                         var opt = $(tabs[i]).panel("options");
151                         if (opt.closable) {
152                             closeTitles.push(opt.title);
153                         }
154                     }
155                     break;
156                 case "tab-menu-closeother":
157                     for (var i = 0; i < tabs.length; i++) {
158                         if (i == index) {
159                             continue;
160                         }
161                         var opt = $(tabs[i]).panel("options");
162                         if (opt.closable) {
163                             closeTitles.push(opt.title);
164                         }
165                     }
166                     break;
167                 case "tab-menu-closeall":
168                     for (var i = 0; i < tabs.length; i++) {
169                         var opt = $(tabs[i]).panel("options");
170                         if (opt.closable) {
171                             closeTitles.push(opt.title);
172                         }
173                     }
174                     break;
175             }
176             for (var i = 0; i < closeTitles.length; i++) {
177                 $tab.tabs("close", closeTitles[i]);
178             }
179         }
180 
181         function transToTreeData(data) {
182             return $.Enumerable.From(data).Select(function(m) {
183                 var obj = {};
184                 obj.id = m.Id;
185                 obj.text = m.Text;
186                 obj.iconCls = m.IconCls;
187                 obj.checked = m.Checked;
188                 if (m.Url) {
189                     obj.attributes = { url: m.Url };
190                 }
191                 if (m.Children && m.Children.length > 0) {
192                     obj.children = transToTreeData(m.Children);
193                 }
194                 return obj;
195             }).ToArray();
196         }
197     </script>
198 }
View Code

  效果如下:

  

 左導航數據加載

  由上面的代碼可知,左邊導航菜單,完全是由JS解析后端返回的JSON數據來構建,使用后端來返回數據,而不是在前端構建菜單數據,主要是便於將來進行權限控制,后端可以根據當前用戶的權限返回特定的菜單數據。后端代碼如下:

 1 [AjaxOnly]
 2 public ActionResult GetNavData()
 3 {
 4     List<TreeNode> nodes = new List<TreeNode>()
 5     {
 6         new TreeNode()
 7         {
 8             Text = "權限",
 9             IconCls = "pic_26",
10             Children = new List<TreeNode>()
11             {
12                 new TreeNode() { Text = "用戶管理", IconCls = "pic_5", Url = Url.Action("Index", "Users") },
13                 new TreeNode() { Text = "角色管理", IconCls = "pic_198", Url = Url.Action("Index", "Roles") },
14                 new TreeNode() { Text = "組織機構管理", IconCls = "pic_93", Url = Url.Action("Index", "Organizations") },
15             }
16         },
17         new TreeNode()
18         {
19             Text = "系統",
20             IconCls = "pic_100",
21             Children = new List<TreeNode>()
22             {
23                 new TreeNode() { Text = "操作日志", IconCls = "pic_125", Url = Url.Action("Index", "OperateLogs") },
24                 new TreeNode() { Text = "系統日志", IconCls = "pic_101", Url = Url.Action("Index", "SystemLogs") },
25                 new TreeNode() { Text = "系統設置", IconCls = "pic_89", Url = Url.Action("Index", "SystemSettings") }
26             }
27         }
28     };
29 
30     Action<ICollection<TreeNode>> action = list =>
31     {
32         foreach (var node in list)
33         {
34             node.Id = "node" + node.Text;
35         }
36     };
37 
38     foreach (var node in nodes)
39     {
40         node.Id = "node" + node.Text;
41         if (node.Children != null && node.Children.Count > 0)
42         {
43             action(node.Children);
44         }
45     }
46 
47     return Json(nodes, JsonRequestBehavior.AllowGet);
48 }
View Code

  上面的代碼中,添加了一個 [AjaxOnly],作用標記此方法只允許AJAX的調用方式,攔截非Ajax調用,在數據安全上能起到一定的作用。 

 1 /// <summary>
 2 /// 限制當前功能只允許以Ajax的方式來訪問
 3 /// </summary>
 4 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
 5 public class AjaxOnlyAttribute : ActionFilterAttribute
 6 {
 7     /// <summary>
 8     /// Called before an action method executes.
 9     /// </summary>
10     /// <param name="filterContext">The filter context.</param>
11     public override void OnActionExecuting(ActionExecutingContext filterContext)
12     {
13         if (!filterContext.HttpContext.Request.IsAjaxRequest())
14         {
15             filterContext.Result = new ContentResult
16             {
17                 Content = Resources.Mvc_ActionAttribute_AjaxOnlyMessage
18             };
19         }
20     }
21 }

  打上此自定義屬性后,如果使用非AJAX的方式來調用上面的GetNavData代碼,無法得到返回的JSON數據

  

  正確解析返回數據后,構建導航菜單,點擊菜單后打開相應的選項卡

  

EasyUI-datagrid布局

提取父視圖(模板)_DataGridLayout.cshtml

   在實踐中,我們會發現,大部分 datagrid 的代碼組織方式都相似的,不同的只是數據源不同,操作之后提交的URL不同。為了減少重復代碼,提高代碼的復用率,我們可以把共同的代碼提取出來,而MVC的 Layout 又剛好是支持嵌套的,那么,類似於前面的 _Layout.cshtml,我們可以提取一個datagrid的共同父視圖 _DataGridLayout.cshtml。

  _DataGridLayout.cshtml 的提取原理如下:

  1. javascript 的變量均是全局變量,並且是有前后順序的,就可以按需要進行重新賦值
  2. 在 父視圖(_Layout)中初始化 javascript變量,並在適當的位置(變量真正使用之前)向 子視圖(Partial View)開放 RenderSection
  3. 子視圖(Partial View)按需要對 父視圖(_Layout)中定義的 javascript變量 進行重新賦值
  4. 正在的運算邏輯運算的時候,使用的就是重新賦值的新值了,以達到復用 Layout 的目的
  5. 父視圖中需要的 C# 變量,通過在子視圖中定義 ViewBag 傳遞過去,比如dom元素的id,數據操作的URL等等

  根據 easyui-datagrid 的常用變量及上面的原理,定義的 _DataGridLayout.cshtml 大致結構如下,請結合注釋進行理解:

 1 @{
 2     Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
 3     string toolbarItem = ViewBag.ToolbarItem ?? "add,edit,save,cancel,delete";
 4 }
 5 @section header
 6 {
 7 <style type="text/css">
 8     html { font-family: sans-serif; }
 9     .datagrid-header-inner { font-weight: bold; }
10 </style>
11 }
12 @section footer
13 {
14     @*這里進行變量初始化*@
15     <script type="text/javascript">
16         //定義及初始化變量
17         var rownumbers = true, singleSelect = false, ctrlSelect = true, multiSort = false, pageSize = 25;
18         var grid, frozenColumns = [[]], columns = [[]], ...
19 
20         //前置邏輯,將在構造datagrid之前執行
21         var startfunction = function() { };
22         //后置邏輯,將在構造datagrid之后執行
23         var endfunction = function() { }; 
24 
25     </script>
26 
27     @*開放一個Section,讓子視圖(Partial View)可以插入代碼,對上面定義的變量進行重新賦值。*@
28     @RenderSection("customScript", true)
29 
30     @*這里才正在執行業務邏輯*@
31     <script type="text/javascript">
32         $(function () {
33             //執行前置邏輯
34             startfunction();
35 
36             //構造 datagrid
37             grid = $("#grid-@ViewBag.GridId").datagrid({
38                 title: "@ViewBag.Title",
39                 fit: true,
40                 frozenColumns: frozenColumns,
41                 columns: columns,
42                 fitColumns: false,
43                 url: "@ViewBag.GridDataUrl",
44                 ...
45             });
46             
47             //執行后置邏輯
48             endfunction();
49         });
50     </script>
51 }
52 @* 后台還有可能有需要執行的邏輯,開放一個Section *@
53 @RenderSection("endScript", false)
54 }
55 @* datagrid 前面有可能需要插入html,開放一個Section *@
56 @RenderSection("headHtml", false)
57 <div id="grid-@ViewBag.GridId"></div>
58 @* datagrid 后面有可能需要插入html,開放一個Section *@
59 @RenderSection("footHtml", false)

  結合實際需求,OSharp中定義的一個可用的 _DataGridLayout.cshtml 如下,可以根據需求進行更改:

  1 @{
  2     Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
  3     string toolbarItem = ViewBag.ToolbarItem ?? "add,edit,save,cancel,delete";
  4 }
  5 @section header{
  6     <style type="text/css">
  7         html {
  8             font-family: sans-serif;
  9         }
 10 
 11         .datagrid-header-inner {
 12             font-weight: bold;
 13         }
 14     </style>
 15 }
 16 @section footer{
 17     <script src="/Scripts/plugins/datagrid-filter.js" type="text/javascript"></script>
 18     <script src="/Scripts/plugins/datagrid-detailview.js" type="text/javascript"></script>
 19     <script type="text/javascript">
 20         var rownumbers = true, singleSelect = false, ctrlSelect = true, multiSort = false, pageSize = 25;
 21         var grid, frozenColumns = [[]], columns = [[]], filterData = [], enableFilterData = false, editIndex = undefined, columnMenu = undefined;
 22 
 23         var startfunction = function () {
 24         };
 25         var endfunction = function () {
 26         };
 27         var addObject = function () {
 28             return {};
 29         };
 30         var replaceSearchField = function (field) {
 31             return field;
 32         };
 33     </script>
 34     @RenderSection("paramInit", false)
 35     <script type="text/javascript">
 36         function formatBoolean(value) {
 37             var icon = value ? 'checkmark' : 'checknomark';
 38             return '<span class="tree-file icon-' + icon + '"></span>';
 39         }
 40 
 41         var addNewRow = function () {
 42             if (!endEditing()) {
 43                 $.osharp.easyui.msg.tip("請先提交或取消正在編輯的行。");
 44                 return;
 45             }
 46             grid.datagrid("appendRow", addObject() || {});
 47             editIndex = grid.datagrid("getRows").length - 1;
 48             grid.datagrid("selectRow", editIndex)
 49                 .datagrid("beginEdit", editIndex);
 50         };
 51 
 52         var beginEdit = function () {
 53             var row = grid.datagrid("getSelected");
 54             if (!row) {
 55                 $.osharp.easyui.msg.tip("請選擇要編輯的行。");
 56                 return;
 57             }
 58             var index = grid.datagrid("getRowIndex", row);
 59             beginEditRow(index);
 60         };
 61 
 62         var beginEditRow = function (index) {
 63             @if (toolbarItem == null || !toolbarItem.Contains(",save"))
 64             {
 65                 @Html.Raw("return;")
 66             }
 67 
 68             if (endEditing()) {
 69                 grid.datagrid("selectRow", index)
 70                     .datagrid("beginEdit", index);
 71                 editIndex = index;
 72             } else {
 73                 grid.datagrid("unselectRow", index)
 74                     .datagrid("selectRow", editIndex);
 75             }
 76         };
 77 
 78         var cancelEdit = function () {
 79             grid.datagrid("rejectChanges");
 80             editIndex = undefined;
 81         };
 82 
 83         var saveChanges = function () {
 84             if (!endEditing()) {
 85                 return;
 86             }
 87             var adds = grid.datagrid("getChanges", "inserted");
 88             if (adds && adds.length > 0) {
 89                 submitAdds(adds);
 90             }
 91             var edits = grid.datagrid("getChanges", "updated");
 92             if (edits && edits.length > 0) {
 93                 submitEdits(edits);
 94             }
 95         };
 96 
 97         var deleteRows = function () {
 98             var selectRows = grid.datagrid("getSelections");
 99             if (selectRows.length == 0) {
100                 $.osharp.easyui.msg.tip("請先選中要刪除的行。");
101                 return;
102             }
103             var ids = $.Enumerable.From(selectRows).Select(function (m) { return m.Id; }).ToArray();
104             $.osharp.easyui.msg.confirm("是否要刪除所有選中的行?此操作是不可恢復的。", null, function () {
105                 $.post("@ViewBag.DeleteUrl", { ids: JSON.stringify(ids) }, ajaxResultHandler);
106             });
107         };
108 
109         function endEditing() {
110             if (editIndex == undefined) {
111                 return true;
112             }
113             if (grid.datagrid("validateRow", editIndex)) {
114                 grid.datagrid("endEdit", editIndex);
115                 editIndex = undefined;
116                 return true;
117             } else {
118                 return false;
119             }
120         }
121 
122         function submitAdds(objs) {
123             $.post("@ViewBag.AddUrl", { dtos: JSON.stringify(objs) }, ajaxResultHandler);
124         }
125 
126         function submitEdits(objs) {
127             $.post("@ViewBag.EditUrl", { dtos: JSON.stringify(objs) }, ajaxResultHandler);
128         }
129 
130         function ajaxResultHandler(data) {
131             if (data.Type == "Success") {
132                 grid.datagrid("reload");
133             }
134             if (data.Type == "Error") {
135                 $.osharp.easyui.msg.error(data.Content);
136             } else {
137                 $.osharp.easyui.msg.tip(data.Content);
138             }
139         }
140 
141         var toolbarData = [
142             @if (toolbarItem.Contains("add"))
143             {
144                 @:{ text: "增加", iconCls: "icon-add", handler: addNewRow },
145             }
146             @if (toolbarItem.Contains("edit"))
147             {
148                 <text>
149             { text: "編輯", iconCls: "icon-edit", handler: beginEdit },
150             "-",
151             </text>
152             }
153             @if (toolbarItem.Contains("save"))
154             {
155                 @:{ text: "保存", iconCls: "icon-save", handler: saveChanges },
156             }
157             @if (toolbarItem.Contains("cancel"))
158             {
159                 <text>
160             { text: "取消", iconCls: "icon-undo", handler: cancelEdit },
161             "-",
162             </text>
163             }
164             @if (toolbarItem.Contains("delete"))
165             {
166                 @:{ text: "刪除", iconCls: "icon-remove", handler: deleteRows },
167             }
168         ];
169     </script>
170     @RenderSection("customScript", true)
171     <script type="text/javascript">
172         $(function () {
173             startfunction();
174 
175             grid = $("#grid-@ViewBag.GridId").datagrid({
176                 title: "@ViewBag.Title",
177                 fit: true,
178                 frozenColumns: frozenColumns,
179                 columns: columns,
180                 fitColumns: false,
181                 url: "@ViewBag.GridDataUrl",
182                 loadMsg: "正在加載數據,請稍候",
183                 toolbar: toolbarData,
184                 rownumbers: rownumbers,
185                 singleSelect: singleSelect,
186                 ctrlSelect: ctrlSelect,
187                 multiSort: multiSort,
188                 pagination: true,
189                 pageSize: pageSize,
190                 pageList: [10, 25, 50, 100, 200],
191                 remoteFilter: true,
192                 onBeforeLoad: beforeLoad,
193                 loadFilter: loadFilter,
194                 onLoadError: loadError,
195                 onDblClickRow: beginEditRow,
196                 onHeaderContextMenu: headerContextMenu,
197                 showFooter: true
198             });
199             if (enableFilterData) {
200                 grid.datagrid("enableFilter", filterData);
201             }
202             
203             endfunction();
204         });
205 
206         //Header右鍵
207         function headerContextMenu(e) {
208             e.preventDefault();
209             if (!columnMenu) {
210                 createColumnMenu();
211             }
212             columnMenu.menu("show", { left: e.pageX, top: e.pageY });
213         }
214 
215         function createColumnMenu() {
216             columnMenu = $("<div/>").appendTo("body");
217             columnMenu.menu({
218                 onClick: function (item) {
219                     if (item.iconCls == "icon-checkmark") {
220                         grid.datagrid("hideColumn", item.name);
221                         columnMenu.menu("setIcon", { target: item.target, iconCls: "icon-checknomark" });
222                     } else {
223                         grid.datagrid("showColumn", item.name);
224                         columnMenu.menu("setIcon", { target: item.target, iconCls: "icon-checkmark" });
225                     }
226                 }
227             });
228             var fields = grid.datagrid("getColumnFields");
229             for (var i = 0; i < fields.length; i++) {
230                 var field = fields[i];
231                 var col = grid.datagrid("getColumnOption", field);
232                 columnMenu.menu("appendItem", { text: col.title, name: field, iconCls: col.hidden ? "icon-checknomark" : "icon-checkmark" });
233             }
234         }
235 
236         //param的部分屬性與后台要求不符,重置屬性並刪除原有屬性
237         function beforeLoad(param) {
238             if (param.page) {
239                 param.pageIndex = param.page;
240                 delete param.page;
241             }
242             if (param.rows) {
243                 param.pageSize = param.rows;
244                 delete param.rows;
245             }
246             if (param.sort) {
247                 var array = param.sort.split(',');
248                 for (var i = 0; i < array.length; i++) {
249                     var field = array[i];
250                     array[i] = replaceSearchField(field);
251                 }
252                 param.sort = $.osharp.tools.array.expandAndToString(array, ",");
253                 param.sortField = param.sort;
254                 delete param.sort;
255             }
256             if (param.order) {
257                 param.sortOrder = param.order;
258                 delete param.order;
259             }
260             if (param.filterRules) {
261                 if (param.filterRules != "[]") {
262                     param.where = getFilterGroup(param.filterRules);
263                 }
264                 delete param.filterRules;
265             }
266         }
267 
268         function getFilterGroup(filterRules) {
269             var group = new $.osharp.filter.group();
270             var rules = eval(filterRules);
271             for (var i = 0; i < rules.length; i++) {
272                 var rule = rules[i];
273                 rule.field = replaceSearchField(rule.field);
274                 rule.op = rule.op == "beginwith" ? "startswith" : rule.op == "endwith" ? "endswith" : rule.op;
275 
276                 group.Rules.push(new $.osharp.filter.rule(rule.field, rule.value, rule.op));
277             }
278             return JSON.stringify(group);
279         }
280 
281         function loadFilter(data) {
282             if (data.Type != undefined && data.Type == "Error") {
283                 $.osharp.easyui.msg.error(data.Content);
284                 data.rows = [];
285                 data.total = 0;
286                 return data;
287             }
288             if (data.Rows != undefined && data.Total != undefined) {
289                 data.rows = data.Rows;
290                 data.total = data.Total;
291                 delete data.Rows;
292                 delete data.Total;
293             }
294             return data;
295         }
296 
297         function loadError() {
298             $.osharp.easyui.msg.error("遠程數據載入失敗,請重試或檢查參數。");
299         }
300 
301     </script>
302     @RenderSection("endScript", false)
303 }
304 @RenderBody()
305 @RenderSection("headHtml", false)
306 <div id="grid-@ViewBag.GridId"></div>
307 @RenderSection("footHtml", false)
View Code

實例應用

   OSharp.Web組件中,定義了一個專用於返回表格數據的類,表格只需要行數據與總行數

 1 /// <summary>
 2 /// 列表數據,封裝列表的行數據與總記錄數
 3 /// </summary>
 4 /// <typeparam name="T"></typeparam>
 5 public class GridData<T>
 6 {
 7     public GridData()
 8         : this(new List<T>(), 0)
 9     { }
10 
11     public GridData(IEnumerable<T> rows, int total)
12     {
13         Rows = rows;
14         Total = total;
15     }
16 
17     /// <summary>
18     /// 獲取或設置 行數據
19     /// </summary>
20     public IEnumerable<T> Rows { get; set; }
21 
22     /// <summary>
23     /// 獲取或設置 數據行數
24     /// </summary>
25     public int Total { get; set; }
26 }

  通過這個類,就可以向easyui返回數據了,如下:

 1 [AjaxOnly]
 2 public ActionResult GridData()
 3 {
 4     List<object>data =new List<object>();
 5     for (int i = 1; i <= 20; i++)
 6     {
 7         var item = new { Id = i, Name = "UserName" + i, NickName = "用戶" + i, IsDeleted = false, CreatedTime = DateTime.Now.AddMinutes(i) };
 8         data.Add(item);
 9     }
10     return Json(new GridData<object>(data, data.Count), JsonRequestBehavior.AllowGet);
11 }

  有了前面定義的 datagrid 父視圖 _DataGridLayout.cshtml,用戶列表(Views\Users\Index.cshtml)的代碼就是如此的簡單,僅僅需要把columns重新賦值而已

 1 @{
 2     ViewBag.Title = "用戶信息列表";
 3     Layout = "~/Areas/Admin/Views/Shared/_DataGridLayout.cshtml";
 4 
 5     ViewBag.GridId = "users";
 6     ViewBag.GridDataUrl = Url.Action("GridData");
 7 }
 8 @section customScript
 9 {
10 <script type="text/javascript">
11     columns = [[
12         { field: "Id", title: "編號", width: 40, halign: "center", align: "right", sortable: true },
13         { field: "Name", title: "用戶名", width: 150, sortable: true },
14         { field: "NickName", title: "用戶昵稱", width: 150, sortable: true },
15         { field: "IsDeleted", title: "已刪除", width: 80, sortable: true, align: "center", formatter: formatBoolean },
16         { field: "CreatedTime", title: "創建時間", width: 150, halign: "center", align: "center", sortable: true, formatter: function (value) { return $.osharp.tools.formatDate(value); } }
17     ]];
18 </script>
19 }

   這樣,便可以運行出用戶列表的結果,如下

  

  比如添加一個角色信息列表,視圖(Views\Roles\Index.cshtml)也同用戶列表一樣,簡單到極致:

 1 @{
 2     ViewBag.Title = "角色信息列表";
 3     Layout = "~/Areas/Admin/Views/Shared/_DataGridLayout.cshtml";
 4 
 5     ViewBag.GridId = "roles";
 6     ViewBag.GridDataUrl = Url.Action("GridData");
 7 }
 8 @section customScript
 9 {
10     <script type="text/javascript">
11         columns = [[
12             { field: "Id", title: "編號", width: 40, halign: "center", align: "right", sortable: true },
13             { field: "Name", title: "角色名", width: 150, sortable: true },
14             { field: "Remark", title: "角色描述", width: 150, sortable: true },
15             { field: "CreatedTime", title: "創建時間", width: 150, halign: "center", align: "center", sortable: true, formatter: function (value) { return $.osharp.tools.formatDate(value); } }
16         ]];
17     </script>
18 }

  運行效果:

  

  就是這樣,多動腦,多總結,前端的代碼也同樣能像后台C#代碼一樣重構,重構到極致。

  未完待續。。。

開源說明

github.com

   OSharp項目已在github.com上開源,地址為:https://github.com/i66soft/osharp,歡迎閱讀代碼,歡迎 Fork,如果您認同 OSharp 項目的思想,歡迎參與 OSharp 項目的開發。

  在Visual Studio 2013中,可直接獲取 OSharp 的最新源代碼,獲取方式如下,地址為:https://github.com/i66soft/osharp.git

  

nuget

  OSharp的相關類庫已經發布到nuget上,歡迎試用,直接在nuget上搜索 “osharp” 關鍵字即可找到
  

系列導航

本文已同步到系列目錄:OSharp快速開發框架解說系列


免責聲明!

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



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