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布局
一般來說,后台管理頁面都是這樣一個布局方式:
- 上邊一個頂欄
- 左邊一個手風琴或樹形的導航欄
- 中間是一個由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 © 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 }
效果如下:
左導航數據加載
由上面的代碼可知,左邊導航菜單,完全是由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 }
上面的代碼中,添加了一個 [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 的提取原理如下:
- javascript 的變量均是全局變量,並且是有前后順序的,就可以按需要進行重新賦值
- 在 父視圖(_Layout)中初始化 javascript變量,並在適當的位置(變量真正使用之前)向 子視圖(Partial View)開放 RenderSection
- 子視圖(Partial View)按需要對 父視圖(_Layout)中定義的 javascript變量 進行重新賦值
- 正在的運算邏輯運算的時候,使用的就是重新賦值的新值了,以達到復用 Layout 的目的
- 父視圖中需要的 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)
實例應用
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快速開發框架解說系列