首页 > 代码库 > 【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构
【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构
〇、前言
要了解一个东西长什么样,至少得让我们能看到,才能提出针对性的见解。所以,为了言之有物,而不是凭空漫谈,我们先从UI说起,后台管理页面的UI我们将使用应用比较普遍的easyui框架。
以前在用easyui的时候,每个页面都得从0做起,或者不厌其烦地由以前的页面通过“复制-粘贴”的方式来修改,久页久之,就会造成页面庞大且难以维护。其实,前端的html,javascript代码与后端的代码是一样的,通过一定的组织,把重复的代码抽离出来,同样也通过达到很好的复用率。而MVC的天生的Layout布局与分布视图(Partial View),就是对重复代码抽离的需求有很好的支持。
一、目录
- 〇、前言
- 一、目录
- 二、easyui-layout布局
- 三、easyui-datagrid布局
- 四、开源说明
- 系列导航
二、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="http://www.mamicode.com/‘ + 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 ContentResult16 {17 Content = Resources.Mvc_ActionAttribute_AjaxOnlyMessage18 };19 }20 }21 }
打上此自定义属性后,如果使用非AJAX的方式来调用上面的GetNavData代码,无法得到返回的JSON数据
正确解析返回数据后,构建导航菜单,点击菜单后打开相应的选项卡
三、EasyUI-datagrid布局
在实践中,我们会发现,大部分 datagrid 的代码组织方式都相似的,不同的只是数据源不同,操作之后提交的URL不同。为了减少重复代码,提高代码的利用率,我们可以把共同的代码提取出来,而MVC的 Layout 又刚好是支持嵌套的,那么,类似于前面的 _Layout.cshtml,我们可以提取一个datagrid的共同父视图 _DataGridLayout.cshtml。
_DataGridLayout.cshtml 的提取原理如下:
- javascript 的变量均是全局变量,并且是有前后顺序的,就可以按需要进行重新赋值
- 在 父视图(_Layout)中初始化 javascript变量,并在适当的位置(变量真正使用之前)向 子视图(Partial View)开放 RenderSection
- 子视图(Partial View)按需要对 父视图(_Layout)中定义的 javascript变量 进行重新赋值
- 正在的运算逻辑运算的时候,使用的就是重新赋值的新值了,以达到利用 Layout 的目的。
根据 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 footer13 {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 //构造 datagrid37 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="http://www.mamicode.com/Scripts/plugins/datagrid-filter.js" type="text/javascript"></script> 18 <script src="http://www.mamicode.com/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 = http://www.mamicode.com/[], 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 =http://www.mamicode.com/ [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 onl oadError: loadError,195 onDblClickRow: beginEditRow,196 onHeaderContextMenu: headerContextMenu,197 showFooter: true198 });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 =http://www.mamicode.com/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框架解说系列(1):总体设计
- 【开源】OSharp框架解说系列(2):从后台UI说起-EasyUI的后台界面搭建
【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构