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的布局在网上也很普遍,具体的就不说了,完整代码如下:
效果如下:
左导航数据加载
由上面的代码可知,左边导航菜单,完全是由JS解析后端返回的JSON数据来构建,使用后端来返回数据,而不是在前端构建菜单数据,主要是便于将来进行权限控制,后端可以根据当前用户的权限返回特定的菜单数据。后端代码如下:
上面的代码中,添加了一个 [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 如下,可以根据需求进行更改:
实例应用
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,用户列表(ViewsUsersIndex.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: "编号", 40, halign: "center", align: "right", sortable: true }, 13 { field: "Name", title: "用户名", 150, sortable: true }, 14 { field: "NickName", title: "用户昵称", 150, sortable: true }, 15 { field: "IsDeleted", title: "已删除", 80, sortable: true, align: "center", formatter: formatBoolean }, 16 { field: "CreatedTime", title: "创建时间", 150, halign: "center", align: "center", sortable: true, formatter: function (value) { return $.osharp.tools.formatDate(value); } } 17 ]]; 18 </script> 19 }
这样,便可以运行出用户列表的结果,如下
比如添加一个角色信息列表,视图(ViewsRolesIndex.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: "编号", 40, halign: "center", align: "right", sortable: true }, 13 { field: "Name", title: "角色名", 150, sortable: true }, 14 { field: "Remark", title: "角色描述", 150, sortable: true }, 15 { field: "CreatedTime", title: "创建时间", 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.2):EasyUI复杂布局及数据操作